From f2151451e0b037dc1afb2f228f70cefe1f2b03a7 Mon Sep 17 00:00:00 2001 From: jvangaalen Date: Wed, 4 Dec 2024 08:34:00 +0100 Subject: [PATCH 01/20] Fix memory leak when transaction has parents Improve sampler cleanup during thread run --- .../apache/jmeter/samplers/SampleResult.java | 103 +++++++++++++++++- .../apache/jmeter/threads/JMeterThread.java | 8 +- 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java index 66589162432..a9ee530588c 100644 --- a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -32,7 +32,10 @@ import org.apache.jmeter.assertions.AssertionResult; import org.apache.jmeter.gui.Searchable; import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; +import org.apache.jmeter.control.TransactionSampler; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.util.JOrphanUtils; import org.slf4j.Logger; @@ -1594,10 +1597,62 @@ public void setStartNextThreadLoop(boolean startNextThreadLoop) { } /** - * Clean up cached data + * Clean up cached data, but only if this is a root result (no parent) + * and not part of a parent transaction. */ public void cleanAfterSample() { + // Only clean if this is a root result (no parent) + if (parent != null) { + return; + } + + // Check if we're part of a parent transaction by walking up the sampler hierarchy + JMeterContext context = JMeterContextService.getContext(); + if (context != null) { + Sampler currentSampler = context.getCurrentSampler(); + if (currentSampler instanceof TransactionSampler) { + TransactionSampler transSampler = (TransactionSampler) currentSampler; + // Get the parent sampler from the transaction + Sampler parentSampler = transSampler.getSubSampler(); + // If there's a parent sampler and it's a transaction, we're nested + if (parentSampler instanceof TransactionSampler) { + return; + } + } + } + + cleanRecursively(); + } + + /** + * Internal method to clean this result and all its sub-results + */ + private void cleanRecursively() { + // Clean sub-results first + if (subResults != null) { + for (SampleResult subResult : subResults) { + if (subResult != null) { + subResult.cleanRecursively(); + } + } + subResults.clear(); + subResults = null; + } + + // Clean assertion results + if (assertionResults != null) { + assertionResults.clear(); + assertionResults = null; + } + + // Clear only memory-heavy data and caches, preserve samplerData + this.parent = null; this.responseDataAsString = null; + this.responseData = EMPTY_BA; + this.responseHeaders = ""; + this.requestHeaders = ""; + this.samplerData = null; + } @Override @@ -1665,4 +1720,50 @@ public TestLogicalAction getTestLogicalAction() { public void setTestLogicalAction(TestLogicalAction testLogicalAction) { this.testLogicalAction = testLogicalAction; } + + /** + * Create a deep copy for async listeners + * @return A deep copy of this result + */ + public SampleResult cloneForListeners() { + // Create clone with the correct type + SampleResult clone; + try { + // Use the same constructor that was used to create this instance + clone = this.getClass().getConstructor(this.getClass()).newInstance(this); + } catch (Exception e) { + // Fallback to base class if constructor is not available + log.debug("Could not create clone with type: " + this.getClass().getName() + ", using base class", e); + clone = new SampleResult(this); + } + + // Deep copy mutable fields that the copy constructor doesn't handle deeply + if (responseData != EMPTY_BA) { + clone.responseData = responseData.clone(); + } + + // Deep copy subResults + if (subResults != null) { + clone.subResults = new ArrayList<>(subResults.size()); + for (SampleResult sub : subResults) { + SampleResult subClone = sub.cloneForListeners(); + subClone.setParent(clone); + clone.subResults.add(subClone); + } + } + + // Deep copy assertion results + if (assertionResults != null) { + clone.assertionResults = new ArrayList<>(assertionResults.size()); + for (AssertionResult assertionResult : assertionResults) { + clone.assertionResults.add(assertionResult); // AssertionResult is immutable + } + } + + // Clear only the caches and unnecessary references in the clone + clone.responseDataAsString = null; + clone.parent = null; // Parent reference not needed in the clone + + return clone; + } } diff --git a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java index 8b7f13f7900..fae46708c2e 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java @@ -678,6 +678,12 @@ private SampleResult doEndTransactionSampler( notifyListeners(transactionPack.getSampleListeners(), transactionResult); } compiler.done(transactionPack); + + // Clean up the transaction result after listeners have processed it + if (transactionResult != null) { + transactionResult.cleanAfterSample(); + } + return transactionResult; } @@ -1038,7 +1044,7 @@ void notifyTestListeners() { } private void notifyListeners(List listeners, SampleResult result) { - SampleEvent event = new SampleEvent(result, threadGroup.getName(), threadVars); + SampleEvent event = new SampleEvent(result.cloneForListeners(), threadGroup.getName(), threadVars); notifier.notifyListeners(event, listeners); } From afc2337f059dd8ff1dcb8f80cb7e8f4fe0cb5722 Mon Sep 17 00:00:00 2001 From: jvangaalen Date: Wed, 4 Dec 2024 09:43:11 +0100 Subject: [PATCH 02/20] Fix styling --- .../apache/jmeter/samplers/SampleResult.java | 26 +++++++++---------- .../apache/jmeter/threads/JMeterThread.java | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java index a9ee530588c..2675bd75661 100644 --- a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -30,12 +30,12 @@ import java.util.concurrent.TimeUnit; import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.control.TransactionSampler; import org.apache.jmeter.gui.Searchable; import org.apache.jmeter.testelement.TestPlan; import org.apache.jmeter.threads.JMeterContext; -import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; -import org.apache.jmeter.control.TransactionSampler; +import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.util.JOrphanUtils; import org.slf4j.Logger; @@ -1605,7 +1605,7 @@ public void cleanAfterSample() { if (parent != null) { return; } - + // Check if we're part of a parent transaction by walking up the sampler hierarchy JMeterContext context = JMeterContextService.getContext(); if (context != null) { @@ -1620,14 +1620,14 @@ public void cleanAfterSample() { } } } - + cleanRecursively(); } - + /** * Internal method to clean this result and all its sub-results */ - private void cleanRecursively() { + private void cleanRecursively() { // Clean sub-results first if (subResults != null) { for (SampleResult subResult : subResults) { @@ -1638,13 +1638,13 @@ private void cleanRecursively() { subResults.clear(); subResults = null; } - + // Clean assertion results if (assertionResults != null) { assertionResults.clear(); assertionResults = null; } - + // Clear only memory-heavy data and caches, preserve samplerData this.parent = null; this.responseDataAsString = null; @@ -1736,12 +1736,12 @@ public SampleResult cloneForListeners() { log.debug("Could not create clone with type: " + this.getClass().getName() + ", using base class", e); clone = new SampleResult(this); } - + // Deep copy mutable fields that the copy constructor doesn't handle deeply if (responseData != EMPTY_BA) { clone.responseData = responseData.clone(); } - + // Deep copy subResults if (subResults != null) { clone.subResults = new ArrayList<>(subResults.size()); @@ -1751,7 +1751,7 @@ public SampleResult cloneForListeners() { clone.subResults.add(subClone); } } - + // Deep copy assertion results if (assertionResults != null) { clone.assertionResults = new ArrayList<>(assertionResults.size()); @@ -1759,11 +1759,11 @@ public SampleResult cloneForListeners() { clone.assertionResults.add(assertionResult); // AssertionResult is immutable } } - + // Clear only the caches and unnecessary references in the clone clone.responseDataAsString = null; clone.parent = null; // Parent reference not needed in the clone - + return clone; } } diff --git a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java index fae46708c2e..3efca9d13a6 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java @@ -678,12 +678,12 @@ private SampleResult doEndTransactionSampler( notifyListeners(transactionPack.getSampleListeners(), transactionResult); } compiler.done(transactionPack); - + // Clean up the transaction result after listeners have processed it if (transactionResult != null) { transactionResult.cleanAfterSample(); } - + return transactionResult; } From 78a51ceb09cb43dca8418abc9bc62581a30b39be Mon Sep 17 00:00:00 2001 From: jvangaalen Date: Sun, 8 Dec 2024 16:47:11 +0100 Subject: [PATCH 03/20] Revert "doc: update documentation for sampleresult.default.encoding default value" This reverts commit 4340b2a048b04169b98d54f6a1a1fb4ca7059cc7. --- bin/jmeter.properties | 4 +-- .../apache/jmeter/samplers/SampleResult.java | 7 +++- xdocs/changes.xml | 35 +++++++++++++++++++ xdocs/usermanual/properties_reference.xml | 2 +- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/bin/jmeter.properties b/bin/jmeter.properties index bac04e85bc9..db56807105e 100644 --- a/bin/jmeter.properties +++ b/bin/jmeter.properties @@ -1101,8 +1101,8 @@ csvdataset.file.encoding_list=UTF-8|UTF-16|ISO-8859-15|US-ASCII # List of extra HTTP methods that should be available in select box #httpsampler.user_defined_methods=VERSION-CONTROL,REPORT,CHECKOUT,CHECKIN,UNCHECKOUT,MKWORKSPACE,UPDATE,LABEL,MERGE,BASELINE-CONTROL,MKACTIVITY -# The encoding to be used if none is provided (default UTF-8 since JMeter 5.6.1) -#sampleresult.default.encoding=UTF-8 +# The encoding to be used if none is provided (default ISO-8859-1) +#sampleresult.default.encoding=ISO-8859-1 # CookieManager behaviour - should cookies with null/empty values be deleted? # Default is true. Use false to revert to original behaviour diff --git a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java index 2675bd75661..189f49e3d0f 100644 --- a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -66,10 +66,15 @@ public class SampleResult implements Serializable, Cloneable, Searchable { private static final String INVALID_CALL_SEQUENCE_MSG = "Invalid call sequence"; // $NON-NLS-1$ + // Bug 33196 - encoding ISO-8859-1 is only suitable for Western countries + // However the suggested System.getProperty("file.encoding") is Cp1252 on + // Windows + // So use a new property with the original value as default + // needs to be accessible from test code /** * The default encoding to be used to decode the responseData byte array. * The value is defined by the property "sampleresult.default.encoding" - * with a default of {@link #DEFAULT_HTTP_ENCODING} if that is not defined. + * with a default of DEFAULT_HTTP_ENCODING if that is not defined. */ protected static final String DEFAULT_ENCODING = JMeterUtils.getPropDefault("sampleresult.default.encoding", // $NON-NLS-1$ diff --git a/xdocs/changes.xml b/xdocs/changes.xml index f50ac08b508..20182c4832e 100644 --- a/xdocs/changes.xml +++ b/xdocs/changes.xml @@ -69,6 +69,41 @@ Summary Timer relative to start of Thread Group instead of the start of the test.
  • 63576358 Ensure writable directories when copying template files while report generation.
  • +New and Noteworthy + +Improvements + +

    HTTP Samplers and Test Script Recorder

    +
      +
    • 6010Use UTF-8 as a default encoding in HTTP sampler. It enables sending parameter names, and filenames with unicode characters
    • +
    • 6010Test Recorder will use UTF-8 encoding by default, so it will infer human-readable arguments rather than percent-encoded ones
    • +
    + +Non-functional changes +
      +
    • 6000Add release-drafter for populating GitHub releases info based on the merged PRs
    • +
    • 5989Use Gradle toolchains for JDK provisioning, enable building and testing with different JDKs, start testing with Java 21
    • +
    • 5991Update jackson-core, jackson-databind, jackson-annotations to 2.15.2 (from 2.15.1)
    • +
    • 5993Update ph-commons to 10.2.5 (from 10.2.4)
    • +
    • 6017Update kotlin-stdlib to 1.8.22 (from 1.8.21)
    • +
    • 6020Update error_prone_annotations to 2.20.0 (from 2.19.1)
    • +
    • 6023Update checker-qual to 3.35.0 (from 3.34.0)
    • +
    + + + +Bug fixes + +

    Thread Groups

    +
      +
    • 6011Regression since 5.6: ThreadGroups are running endlessly in non-gui mode: use default value + for LoopController.continue_forever rather than initializing it in the constructor
    • +
    + +

    Other Samplers

    +
      +
    • 6012 Java Request sampler cannot be enabled again after disabling in UI (regression since 5.6)
    • +
    diff --git a/xdocs/usermanual/properties_reference.xml b/xdocs/usermanual/properties_reference.xml index d74ece0613d..8fc816a5c04 100644 --- a/xdocs/usermanual/properties_reference.xml +++ b/xdocs/usermanual/properties_reference.xml @@ -1399,7 +1399,7 @@ JMETER-SERVER The encoding to be used if none is provided.
    - Defaults to: UTF-8 (since 5.6.1) + Defaults to: ISO-8859-1
    CookieManager behaviour - should cookies with null/empty values be deleted?
    From cbf186f8372a82580fb308b5232ba5b8812fba74 Mon Sep 17 00:00:00 2001 From: jvangaalen Date: Tue, 10 Dec 2024 13:35:07 +0100 Subject: [PATCH 04/20] Clear transaction from subresults --- .../apache/jmeter/samplers/SampleResult.java | 110 +----------------- .../apache/jmeter/threads/JMeterThread.java | 8 +- .../apache/jmeter/threads/TestCompiler.java | 13 ++- .../org/apache/jmeter/junit/JMeterTest.java | 3 +- 4 files changed, 17 insertions(+), 117 deletions(-) diff --git a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java index 189f49e3d0f..66589162432 100644 --- a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -30,12 +30,9 @@ import java.util.concurrent.TimeUnit; import org.apache.jmeter.assertions.AssertionResult; -import org.apache.jmeter.control.TransactionSampler; import org.apache.jmeter.gui.Searchable; import org.apache.jmeter.testelement.TestPlan; -import org.apache.jmeter.threads.JMeterContext; import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; -import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.util.JOrphanUtils; import org.slf4j.Logger; @@ -66,15 +63,10 @@ public class SampleResult implements Serializable, Cloneable, Searchable { private static final String INVALID_CALL_SEQUENCE_MSG = "Invalid call sequence"; // $NON-NLS-1$ - // Bug 33196 - encoding ISO-8859-1 is only suitable for Western countries - // However the suggested System.getProperty("file.encoding") is Cp1252 on - // Windows - // So use a new property with the original value as default - // needs to be accessible from test code /** * The default encoding to be used to decode the responseData byte array. * The value is defined by the property "sampleresult.default.encoding" - * with a default of DEFAULT_HTTP_ENCODING if that is not defined. + * with a default of {@link #DEFAULT_HTTP_ENCODING} if that is not defined. */ protected static final String DEFAULT_ENCODING = JMeterUtils.getPropDefault("sampleresult.default.encoding", // $NON-NLS-1$ @@ -1602,62 +1594,10 @@ public void setStartNextThreadLoop(boolean startNextThreadLoop) { } /** - * Clean up cached data, but only if this is a root result (no parent) - * and not part of a parent transaction. + * Clean up cached data */ public void cleanAfterSample() { - // Only clean if this is a root result (no parent) - if (parent != null) { - return; - } - - // Check if we're part of a parent transaction by walking up the sampler hierarchy - JMeterContext context = JMeterContextService.getContext(); - if (context != null) { - Sampler currentSampler = context.getCurrentSampler(); - if (currentSampler instanceof TransactionSampler) { - TransactionSampler transSampler = (TransactionSampler) currentSampler; - // Get the parent sampler from the transaction - Sampler parentSampler = transSampler.getSubSampler(); - // If there's a parent sampler and it's a transaction, we're nested - if (parentSampler instanceof TransactionSampler) { - return; - } - } - } - - cleanRecursively(); - } - - /** - * Internal method to clean this result and all its sub-results - */ - private void cleanRecursively() { - // Clean sub-results first - if (subResults != null) { - for (SampleResult subResult : subResults) { - if (subResult != null) { - subResult.cleanRecursively(); - } - } - subResults.clear(); - subResults = null; - } - - // Clean assertion results - if (assertionResults != null) { - assertionResults.clear(); - assertionResults = null; - } - - // Clear only memory-heavy data and caches, preserve samplerData - this.parent = null; this.responseDataAsString = null; - this.responseData = EMPTY_BA; - this.responseHeaders = ""; - this.requestHeaders = ""; - this.samplerData = null; - } @Override @@ -1725,50 +1665,4 @@ public TestLogicalAction getTestLogicalAction() { public void setTestLogicalAction(TestLogicalAction testLogicalAction) { this.testLogicalAction = testLogicalAction; } - - /** - * Create a deep copy for async listeners - * @return A deep copy of this result - */ - public SampleResult cloneForListeners() { - // Create clone with the correct type - SampleResult clone; - try { - // Use the same constructor that was used to create this instance - clone = this.getClass().getConstructor(this.getClass()).newInstance(this); - } catch (Exception e) { - // Fallback to base class if constructor is not available - log.debug("Could not create clone with type: " + this.getClass().getName() + ", using base class", e); - clone = new SampleResult(this); - } - - // Deep copy mutable fields that the copy constructor doesn't handle deeply - if (responseData != EMPTY_BA) { - clone.responseData = responseData.clone(); - } - - // Deep copy subResults - if (subResults != null) { - clone.subResults = new ArrayList<>(subResults.size()); - for (SampleResult sub : subResults) { - SampleResult subClone = sub.cloneForListeners(); - subClone.setParent(clone); - clone.subResults.add(subClone); - } - } - - // Deep copy assertion results - if (assertionResults != null) { - clone.assertionResults = new ArrayList<>(assertionResults.size()); - for (AssertionResult assertionResult : assertionResults) { - clone.assertionResults.add(assertionResult); // AssertionResult is immutable - } - } - - // Clear only the caches and unnecessary references in the clone - clone.responseDataAsString = null; - clone.parent = null; // Parent reference not needed in the clone - - return clone; - } } diff --git a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java index 3efca9d13a6..8b7f13f7900 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java @@ -678,12 +678,6 @@ private SampleResult doEndTransactionSampler( notifyListeners(transactionPack.getSampleListeners(), transactionResult); } compiler.done(transactionPack); - - // Clean up the transaction result after listeners have processed it - if (transactionResult != null) { - transactionResult.cleanAfterSample(); - } - return transactionResult; } @@ -1044,7 +1038,7 @@ void notifyTestListeners() { } private void notifyListeners(List listeners, SampleResult result) { - SampleEvent event = new SampleEvent(result.cloneForListeners(), threadGroup.getName(), threadVars); + SampleEvent event = new SampleEvent(result, threadGroup.getName(), threadVars); notifier.notifyListeners(event, listeners); } diff --git a/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java b/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java index f212dda90a9..233cbb2ad17 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java @@ -115,10 +115,21 @@ public SamplePackage configureTransactionSampler(TransactionSampler transactionS } /** - * Reset pack to its initial state + * Reset pack to its initial state and clean up transaction results if needed * @param pack the {@link SamplePackage} to reset */ public void done(SamplePackage pack) { + Sampler sampler = pack.getSampler(); + if (sampler instanceof TransactionSampler) { + TransactionSampler transactionSampler = (TransactionSampler) sampler; + TransactionController controller = transactionSampler.getTransactionController(); + if (transactionSampler.isTransactionDone()) { + // Create new sampler for next iteration + TransactionSampler newSampler = new TransactionSampler(controller, transactionSampler.getName()); + SamplePackage newPack = transactionControllerConfigMap.get(controller); + newPack.setSampler(newSampler); + } + } pack.recoverRunningVersion(); } diff --git a/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java b/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java index 5fd25546bb7..798f83ce62a 100644 --- a/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java +++ b/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java @@ -581,7 +581,8 @@ static Stream serializableObjects() throws Throwable { return getObjects(Serializable.class) .stream() .map(Serializable.class::cast) - .filter(o -> !o.getClass().getName().endsWith("_Stub")); + .filter(o -> !o.getClass().getName().endsWith("_Stub")) + .filter(o -> o.getClass().getName().startsWith("org.apache.jmeter.")); } /* From 47cbb0575281be9ba62f51f9a131d3e2998ab41d Mon Sep 17 00:00:00 2001 From: jvangaalen Date: Tue, 10 Dec 2024 13:44:24 +0100 Subject: [PATCH 05/20] Revert "Revert "doc: update documentation for sampleresult.default.encoding default value"" This reverts commit 78a51ceb09cb43dca8418abc9bc62581a30b39be. --- bin/jmeter.properties | 4 +-- xdocs/changes.xml | 35 ----------------------- xdocs/usermanual/properties_reference.xml | 2 +- 3 files changed, 3 insertions(+), 38 deletions(-) diff --git a/bin/jmeter.properties b/bin/jmeter.properties index db56807105e..bac04e85bc9 100644 --- a/bin/jmeter.properties +++ b/bin/jmeter.properties @@ -1101,8 +1101,8 @@ csvdataset.file.encoding_list=UTF-8|UTF-16|ISO-8859-15|US-ASCII # List of extra HTTP methods that should be available in select box #httpsampler.user_defined_methods=VERSION-CONTROL,REPORT,CHECKOUT,CHECKIN,UNCHECKOUT,MKWORKSPACE,UPDATE,LABEL,MERGE,BASELINE-CONTROL,MKACTIVITY -# The encoding to be used if none is provided (default ISO-8859-1) -#sampleresult.default.encoding=ISO-8859-1 +# The encoding to be used if none is provided (default UTF-8 since JMeter 5.6.1) +#sampleresult.default.encoding=UTF-8 # CookieManager behaviour - should cookies with null/empty values be deleted? # Default is true. Use false to revert to original behaviour diff --git a/xdocs/changes.xml b/xdocs/changes.xml index 20182c4832e..f50ac08b508 100644 --- a/xdocs/changes.xml +++ b/xdocs/changes.xml @@ -69,41 +69,6 @@ Summary Timer relative to start of Thread Group instead of the start of the test.
  • 63576358 Ensure writable directories when copying template files while report generation.
  • -New and Noteworthy - -Improvements - -

    HTTP Samplers and Test Script Recorder

    -
      -
    • 6010Use UTF-8 as a default encoding in HTTP sampler. It enables sending parameter names, and filenames with unicode characters
    • -
    • 6010Test Recorder will use UTF-8 encoding by default, so it will infer human-readable arguments rather than percent-encoded ones
    • -
    - -Non-functional changes -
      -
    • 6000Add release-drafter for populating GitHub releases info based on the merged PRs
    • -
    • 5989Use Gradle toolchains for JDK provisioning, enable building and testing with different JDKs, start testing with Java 21
    • -
    • 5991Update jackson-core, jackson-databind, jackson-annotations to 2.15.2 (from 2.15.1)
    • -
    • 5993Update ph-commons to 10.2.5 (from 10.2.4)
    • -
    • 6017Update kotlin-stdlib to 1.8.22 (from 1.8.21)
    • -
    • 6020Update error_prone_annotations to 2.20.0 (from 2.19.1)
    • -
    • 6023Update checker-qual to 3.35.0 (from 3.34.0)
    • -
    - - - -Bug fixes - -

    Thread Groups

    -
      -
    • 6011Regression since 5.6: ThreadGroups are running endlessly in non-gui mode: use default value - for LoopController.continue_forever rather than initializing it in the constructor
    • -
    - -

    Other Samplers

    -
      -
    • 6012 Java Request sampler cannot be enabled again after disabling in UI (regression since 5.6)
    • -
    diff --git a/xdocs/usermanual/properties_reference.xml b/xdocs/usermanual/properties_reference.xml index 8fc816a5c04..d74ece0613d 100644 --- a/xdocs/usermanual/properties_reference.xml +++ b/xdocs/usermanual/properties_reference.xml @@ -1399,7 +1399,7 @@ JMETER-SERVER
    The encoding to be used if none is provided.
    - Defaults to: ISO-8859-1 + Defaults to: UTF-8 (since 5.6.1)
    CookieManager behaviour - should cookies with null/empty values be deleted?
    From 3fbe4503f3e06e6c8a66cbd15039d9b495677603 Mon Sep 17 00:00:00 2001 From: jvangaalen Date: Wed, 11 Dec 2024 22:30:39 +0100 Subject: [PATCH 06/20] Store raw body in responseData and only decompress when responseBody is accessed --- src/core/build.gradle.kts | 1 + .../apache/jmeter/samplers/SampleResult.java | 96 ++++++++++++++++++- src/protocol/http/build.gradle.kts | 1 - .../protocol/http/sampler/HTTPHC4Impl.java | 89 ++--------------- .../protocol/http/sampler/HTTPJavaImpl.java | 26 +---- 5 files changed, 105 insertions(+), 108 deletions(-) diff --git a/src/core/build.gradle.kts b/src/core/build.gradle.kts index 98141cdcab4..e788e130724 100644 --- a/src/core/build.gradle.kts +++ b/src/core/build.gradle.kts @@ -114,6 +114,7 @@ dependencies { isTransitive = false } implementation("org.apache.xmlgraphics:xmlgraphics-commons") + implementation("org.brotli:dec") implementation("org.freemarker:freemarker") implementation("org.jodd:jodd-core") implementation("org.jodd:jodd-props") diff --git a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java index 66589162432..3b0a53f784c 100644 --- a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -17,6 +17,10 @@ package org.apache.jmeter.samplers; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; @@ -25,9 +29,13 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; import org.apache.jmeter.assertions.AssertionResult; import org.apache.jmeter.gui.Searchable; @@ -160,6 +168,8 @@ public class SampleResult implements Serializable, Cloneable, Searchable { private byte[] responseData = EMPTY_BA; + private String contentEncoding; // Stores gzip/deflate encoding if response is compressed + private String responseCode = "";// Never return null private String label = "";// Never return null @@ -217,7 +227,7 @@ public class SampleResult implements Serializable, Cloneable, Searchable { // TODO do contentType and/or dataEncoding belong in HTTPSampleResult instead? private String dataEncoding;// (is this really the character set?) e.g. - // ISO-8895-1, UTF-8 + // ISO-8895-1, UTF-8 private String contentType = ""; // e.g. text/html; charset=utf-8 @@ -791,6 +801,27 @@ public void setResponseData(final String response, final String encoding) { * @return the responseData value (cannot be null) */ public byte[] getResponseData() { + if (responseData == null) { + return EMPTY_BA; + } + if (contentEncoding != null && responseData.length > 0) { + try { + switch (contentEncoding.toLowerCase(Locale.ROOT)) { + case "gzip": + return decompressGzip(responseData); + case "x-gzip": + return decompressGzip(responseData); + case "deflate": + return decompressDeflate(responseData); + case "br": + return decompressBrotli(responseData); + default: + return responseData; + } + } catch (IOException e) { + log.warn("Failed to decompress response data", e); + } + } return responseData; } @@ -802,12 +833,12 @@ public byte[] getResponseData() { public String getResponseDataAsString() { try { if(responseDataAsString == null) { - responseDataAsString= new String(responseData,getDataEncodingWithDefault()); + responseDataAsString= new String(getResponseData(),getDataEncodingWithDefault()); } return responseDataAsString; } catch (UnsupportedEncodingException e) { log.warn("Using platform default as {} caused {}", getDataEncodingWithDefault(), e.getLocalizedMessage()); - return new String(responseData,Charset.defaultCharset()); // N.B. default charset is used deliberately here + return new String(getResponseData(),Charset.defaultCharset()); // N.B. default charset is used deliberately here } } @@ -1665,4 +1696,63 @@ public TestLogicalAction getTestLogicalAction() { public void setTestLogicalAction(TestLogicalAction testLogicalAction) { this.testLogicalAction = testLogicalAction; } + + /** + * Sets the response data and its compression encoding. + * @param data The response data + * @param encoding The content encoding (e.g. gzip, deflate) + */ + public void setResponseData(byte[] data, String encoding) { + responseData = data == null ? EMPTY_BA : data; + contentEncoding = encoding; + responseDataAsString = null; + } + + private static byte[] decompressGzip(byte[] in) throws IOException { + try (GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(in)); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + byte[] buf = new byte[8192]; + int len; + while ((len = gis.read(buf)) > 0) { + out.write(buf, 0, len); + } + return out.toByteArray(); + } + } + + private static byte[] decompressDeflate(byte[] in) throws IOException { + // Try with ZLIB wrapper first + try { + return decompressWithInflater(in, false); + } catch (IOException e) { + // If that fails, try with NO_WRAP for raw DEFLATE + return decompressWithInflater(in, true); + } + } + + private static byte[] decompressWithInflater(byte[] in, boolean nowrap) throws IOException { + try (InflaterInputStream iis = new InflaterInputStream( + new ByteArrayInputStream(in), + new Inflater(nowrap)); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + byte[] buf = new byte[8192]; + int len; + while ((len = iis.read(buf)) > 0) { + out.write(buf, 0, len); + } + return out.toByteArray(); + } + } + + private static byte[] decompressBrotli(byte[] in) throws IOException { + try (InputStream bis = new org.brotli.dec.BrotliInputStream(new ByteArrayInputStream(in)); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + byte[] buf = new byte[8192]; + int len; + while ((len = bis.read(buf)) > 0) { + out.write(buf, 0, len); + } + return out.toByteArray(); + } + } } diff --git a/src/protocol/http/build.gradle.kts b/src/protocol/http/build.gradle.kts index 2b1dd7f1aca..2dfe96039dd 100644 --- a/src/protocol/http/build.gradle.kts +++ b/src/protocol/http/build.gradle.kts @@ -73,7 +73,6 @@ dependencies { implementation("dnsjava:dnsjava") implementation("org.apache.httpcomponents:httpmime") implementation("org.apache.httpcomponents:httpcore") - implementation("org.brotli:dec") implementation("com.miglayout:miglayout-swing") implementation("com.fasterxml.jackson.core:jackson-core") implementation("com.fasterxml.jackson.core:jackson-databind") diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java index bc4478eecb3..7cdf7f2482d 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java @@ -57,7 +57,6 @@ import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; -import org.apache.http.HttpResponseInterceptor; import org.apache.http.NameValuePair; import org.apache.http.StatusLine; import org.apache.http.auth.AuthSchemeProvider; @@ -72,7 +71,6 @@ import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.entity.InputStreamFactory; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; @@ -86,7 +84,6 @@ import org.apache.http.client.methods.HttpTrace; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.client.protocol.ResponseContentEncoding; import org.apache.http.config.Lookup; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; @@ -147,8 +144,6 @@ import org.apache.jmeter.protocol.http.control.DynamicKerberosSchemeFactory; import org.apache.jmeter.protocol.http.control.DynamicSPNegoSchemeFactory; import org.apache.jmeter.protocol.http.control.HeaderManager; -import org.apache.jmeter.protocol.http.sampler.hc.LaxDeflateInputStream; -import org.apache.jmeter.protocol.http.sampler.hc.LaxGZIPInputStream; import org.apache.jmeter.protocol.http.sampler.hc.LazyLayeredConnectionSocketFactory; import org.apache.jmeter.protocol.http.util.ConversionUtils; import org.apache.jmeter.protocol.http.util.HTTPArgument; @@ -166,7 +161,6 @@ import org.apache.jmeter.util.JsseSSLManager; import org.apache.jmeter.util.SSLManager; import org.apache.jorphan.util.JOrphanUtils; -import org.brotli.dec.BrotliInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -195,20 +189,8 @@ public class HTTPHC4Impl extends HTTPHCAbstractImpl { private static final boolean DISABLE_DEFAULT_UA = JMeterUtils.getPropDefault("httpclient4.default_user_agent_disabled", false); - private static final boolean GZIP_RELAX_MODE = JMeterUtils.getPropDefault("httpclient4.gzip_relax_mode", false); - - private static final boolean DEFLATE_RELAX_MODE = JMeterUtils.getPropDefault("httpclient4.deflate_relax_mode", false); - private static final Logger log = LoggerFactory.getLogger(HTTPHC4Impl.class); - private static final InputStreamFactory GZIP = - instream -> new LaxGZIPInputStream(instream, GZIP_RELAX_MODE); - - private static final InputStreamFactory DEFLATE = - instream -> new LaxDeflateInputStream(instream, DEFLATE_RELAX_MODE); - - private static final InputStreamFactory BROTLI = BrotliInputStream::new; - private static final class ManagedCredentialsProvider implements CredentialsProvider { private final AuthManager authManager; private final Credentials proxyCredentials; @@ -472,55 +454,6 @@ protected HttpResponse doSendRequest( } }; - private static final String[] HEADERS_TO_SAVE = new String[]{ - "content-length", - "content-encoding", - "content-md5" - }; - - /** - * Custom implementation that backups headers related to Compressed responses - * that HC core {@link ResponseContentEncoding} removes after uncompressing - * See Bug 59401 - */ - @SuppressWarnings("UnnecessaryAnonymousClass") - private static final HttpResponseInterceptor RESPONSE_CONTENT_ENCODING = new ResponseContentEncoding(createLookupRegistry()) { - @Override - public void process(HttpResponse response, HttpContext context) - throws HttpException, IOException { - ArrayList headersToSave = null; - - final HttpEntity entity = response.getEntity(); - final HttpClientContext clientContext = HttpClientContext.adapt(context); - final RequestConfig requestConfig = clientContext.getRequestConfig(); - // store the headers if necessary - if (requestConfig.isContentCompressionEnabled() && entity != null && entity.getContentLength() != 0) { - final Header ceheader = entity.getContentEncoding(); - if (ceheader != null) { - headersToSave = new ArrayList<>(3); - for(String name : HEADERS_TO_SAVE) { - Header[] hdr = response.getHeaders(name); // empty if none - headersToSave.add(hdr); - } - } - } - - // Now invoke original parent code - super.process(response, clientContext); - // Should this be in a finally ? - if(headersToSave != null) { - for (Header[] headers : headersToSave) { - for (Header headerToRestore : headers) { - if (response.containsHeader(headerToRestore.getName())) { - break; - } - response.addHeader(headerToRestore); - } - } - } - } - }; - /** * 1 HttpClient instance per combination of (HttpClient,HttpClientKey) */ @@ -558,19 +491,6 @@ protected HTTPHC4Impl(HTTPSamplerBase testElement) { super(testElement); } - /** - * Customize to plug Brotli - * @return {@link Lookup} - */ - private static Lookup createLookupRegistry() { - return - RegistryBuilder.create() - .register("br", BROTLI) - .register("gzip", GZIP) - .register("x-gzip", GZIP) - .register("deflate", DEFLATE).build(); - } - /** * Implementation that allows GET method to have a body */ @@ -675,7 +595,12 @@ protected HTTPSampleResult sample(URL url, String method, } HttpEntity entity = httpResponse.getEntity(); if (entity != null) { - res.setResponseData(readResponse(res, entity.getContent(), entity.getContentLength())); + Header contentEncodingHeader = entity.getContentEncoding(); + if (contentEncodingHeader != null) { + res.setResponseData(EntityUtils.toByteArray(entity), contentEncodingHeader.getValue()); + } else { + res.setResponseData(EntityUtils.toByteArray(entity)); + } } res.sampleEnd(); // Done with the sampling proper. @@ -1157,7 +1082,7 @@ private MutableTriple Date: Wed, 11 Dec 2024 23:05:49 +0100 Subject: [PATCH 07/20] Store raw body in responseData and only decompress when responseBody is accessed --- .../org/apache/jmeter/threads/TestCompiler.java | 13 +------------ .../java/org/apache/jmeter/junit/JMeterTest.java | 3 +-- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java b/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java index 233cbb2ad17..f212dda90a9 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java @@ -115,21 +115,10 @@ public SamplePackage configureTransactionSampler(TransactionSampler transactionS } /** - * Reset pack to its initial state and clean up transaction results if needed + * Reset pack to its initial state * @param pack the {@link SamplePackage} to reset */ public void done(SamplePackage pack) { - Sampler sampler = pack.getSampler(); - if (sampler instanceof TransactionSampler) { - TransactionSampler transactionSampler = (TransactionSampler) sampler; - TransactionController controller = transactionSampler.getTransactionController(); - if (transactionSampler.isTransactionDone()) { - // Create new sampler for next iteration - TransactionSampler newSampler = new TransactionSampler(controller, transactionSampler.getName()); - SamplePackage newPack = transactionControllerConfigMap.get(controller); - newPack.setSampler(newSampler); - } - } pack.recoverRunningVersion(); } diff --git a/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java b/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java index 798f83ce62a..5fd25546bb7 100644 --- a/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java +++ b/src/dist-check/src/test/java/org/apache/jmeter/junit/JMeterTest.java @@ -581,8 +581,7 @@ static Stream serializableObjects() throws Throwable { return getObjects(Serializable.class) .stream() .map(Serializable.class::cast) - .filter(o -> !o.getClass().getName().endsWith("_Stub")) - .filter(o -> o.getClass().getName().startsWith("org.apache.jmeter.")); + .filter(o -> !o.getClass().getName().endsWith("_Stub")); } /* From 46cf52b8b31f619ac4a950d3694e54a3f542fbda Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Wed, 11 Dec 2024 22:30:39 +0100 Subject: [PATCH 08/20] feat: add ResourceKeyed, LocalizedValue interfaces for combobox localization --- .../kotlin/build-logic.autostyle.gradle.kts | 7 ++- .../apache/jorphan/locale/ComboBoxValue.kt | 40 ++++++++++++ .../apache/jorphan/locale/LocalizedString.kt | 36 +++++++++++ .../apache/jorphan/locale/LocalizedValue.kt | 51 +++++++++++++++ .../org/apache/jorphan/locale/PlainValue.kt | 26 ++++++++ .../apache/jorphan/locale/ResourceKeyed.kt | 63 +++++++++++++++++++ .../jorphan/locale/ResourceLocalizer.kt | 40 ++++++++++++ .../org/apache/jorphan/util/EnumUtils.kt | 31 +++++---- 8 files changed, 276 insertions(+), 18 deletions(-) create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ComboBoxValue.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/locale/LocalizedString.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/locale/LocalizedValue.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/locale/PlainValue.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ResourceKeyed.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ResourceLocalizer.kt diff --git a/build-logic/verification/src/main/kotlin/build-logic.autostyle.gradle.kts b/build-logic/verification/src/main/kotlin/build-logic.autostyle.gradle.kts index ffcf7b8888e..a5c9dce78e3 100644 --- a/build-logic/verification/src/main/kotlin/build-logic.autostyle.gradle.kts +++ b/build-logic/verification/src/main/kotlin/build-logic.autostyle.gradle.kts @@ -101,7 +101,12 @@ plugins.withId("org.jetbrains.kotlin.jvm") { kotlin { license() trimTrailingWhitespace() - ktlint("0.40.0") + ktlint("0.40.0") { + filter { + // TODO: remove exclusion when update ktlint + exclude("**/org/apache/jorphan/locale/PlainValue.kt") + } + } endWithNewline() } } diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ComboBoxValue.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ComboBoxValue.kt new file mode 100644 index 00000000000..02ac6fbc174 --- /dev/null +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ComboBoxValue.kt @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.locale + +import org.apiguardian.api.API + +/** + * Marker interface for values that can be used in a combo box. + * + * This interface is intended to standardize handling of values for GUI components + * like combo boxes. Classes implementing this interface can define how their + * values are represented and localized, providing flexibility for different types + * of combo box content. + * + * Implementers may include: + * - Simple values (e.g., strings or plain objects). + * - Values with additional localization support for display in different locales. + * - Wrapper types that encapsulate richer behaviors for combo box items. + * + * This interface is marked as experimental and may change in future releases. + * + * @since 6.0.0 + */ +@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public interface ComboBoxValue diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/LocalizedString.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/LocalizedString.kt new file mode 100644 index 00000000000..97edda99918 --- /dev/null +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/LocalizedString.kt @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.locale + +import org.apiguardian.api.API +import org.jetbrains.annotations.NonNls + +@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public class LocalizedString( + override val resourceKey: @NonNls String, + private val resourceLocalizer: ResourceLocalizer, +) : ResourceKeyed, ComboBoxValue { + override fun toString(): String = + resourceLocalizer.localize(resourceKey) + + override fun equals(other: Any?): Boolean = + other is LocalizedString && other.resourceKey == resourceKey + + override fun hashCode(): Int = + resourceKey.hashCode() +} diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/LocalizedValue.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/LocalizedValue.kt new file mode 100644 index 00000000000..46ac72cacb2 --- /dev/null +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/LocalizedValue.kt @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.locale + +import org.apiguardian.api.API + +/** + * Represents a localized value that combines an object implementing the [ResourceKeyed] interface + * with a localization function. This allows the object's resource key to be localized dynamically + * into a string representation. + * + * @param T The type of the value, which must implement the [ResourceKeyed] interface. + * @property value The object implementing [ResourceKeyed], which holds a resource key for localization. + * @property localizer A function that takes a resource key string and returns its localized string representation. + * + * The localized representation of this object is produced by applying the `localizer` function + * to the resource key of the `value`. + * + * The class provides equality and hash code implementations based on the underlying `value`. + * + * @since 6.0.0 + */ +@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public class LocalizedValue( + public val value: T, + private val localizer: ResourceLocalizer, +) : ResourceKeyed by value, ComboBoxValue { + override fun toString(): String = + localizer.localize(value.resourceKey) + + override fun equals(other: Any?): Boolean = + other is LocalizedValue<*> && other.value == value + + override fun hashCode(): Int = + value.hashCode() +} diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/PlainValue.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/PlainValue.kt new file mode 100644 index 00000000000..8523be8769a --- /dev/null +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/PlainValue.kt @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.locale + +import org.apiguardian.api.API + +@JvmInline +@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public value class PlainValue(public val value: String): ComboBoxValue { + override fun toString(): String = value +} diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ResourceKeyed.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ResourceKeyed.kt new file mode 100644 index 00000000000..eaaca37e81e --- /dev/null +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ResourceKeyed.kt @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.locale + +import org.apiguardian.api.API +import org.jetbrains.annotations.NonNls + +/** + * Interface for enum types that provide their own localization resource key for GUI display. + * + * When an enum implements this interface, the [resourceKey] will be used for: + * - Storing the value in JMeter properties (instead of the enum name) + * - Looking up localized display text via `JMeterUtils.getResString(resourceKey)` + * - Binding to GUI components + * + * This allows enums to have stable, localization-friendly identifiers that are independent + * of the enum constant names. + * + * Example: + * ```java + * public enum ResponseMode implements ResourceKeyed { + * STORE_COMPRESSED("response_mode_store"), + * FETCH_DISCARD("response_mode_discard"); + * + * private final String key; + * ResponseMode(String key) { this.key = key; } + * + * @Override + * public String getResourceKey() { return key; } + * } + * ``` + * + * @since 6.0.0 + */ +@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public interface ResourceKeyed { + /** + * Returns the resource key used for localization and property storage. + * + * This key should: + * - Be stable across JMeter versions (don't change it) + * - Have a corresponding entry in messages.properties + * - Use lowercase with underscores by convention + * + * @return the resource key for this enum value + */ + public val resourceKey: @NonNls String +} diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ResourceLocalizer.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ResourceLocalizer.kt new file mode 100644 index 00000000000..05bea83838a --- /dev/null +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/locale/ResourceLocalizer.kt @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.locale + +import org.apiguardian.api.API +import org.jetbrains.annotations.Nls +import org.jetbrains.annotations.NonNls + +/** + * Functional interface for localizing resource strings. + * + * This interface provides a single method, [localize], which takes a non-localized input string + * and returns its localized representation. Implementations of this interface define how the + * localization is performed, such as looking up translations in a resource bundle or applying + * other localization mechanisms. + * + * The input string is expected to be a resource identifier or key, and the output is a string + * appropriate for display in a user interface or other localized context. + * + * @since 6.0.0 + */ +@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public fun interface ResourceLocalizer { + public fun localize(input: @NonNls String): @Nls String +} diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/util/EnumUtils.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/util/EnumUtils.kt index ecee2105e5d..27213ee68f7 100644 --- a/src/jorphan/src/main/kotlin/org/apache/jorphan/util/EnumUtils.kt +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/util/EnumUtils.kt @@ -18,6 +18,7 @@ @file:JvmName("EnumUtils") package org.apache.jorphan.util +import org.apache.jorphan.locale.ResourceKeyed import org.apiguardian.api.API import java.util.Collections.unmodifiableList import java.util.Collections.unmodifiableMap @@ -39,34 +40,30 @@ private val VALUE_MAP = object : ClassValue>>() { require(type.isEnum) { "Class $type is not an enum" } - @Suppress("UNCHECKED_CAST") - type as Class> return unmodifiableMap( - VALUES.get(type).associateBy { it.stringValue } + VALUES.get(type).associateBy { + (it as ResourceKeyed).resourceKey + } ) } } @Suppress("UNCHECKED_CAST") -@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") -public fun > values(klass: Class): List = - VALUES.get(klass) as List +@get:API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public val > Class.enumValues: List + get() = VALUES.get(this) as List @Suppress("UNCHECKED_CAST") -@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") -public fun > valueMap(klass: Class): List = - VALUES.get(klass) as List +@get:API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public val Class.enumValueMap: Map where T : Enum<*>, T : ResourceKeyed + get() = VALUE_MAP.get(this) as Map @Suppress("UNCHECKED_CAST") @API(status = API.Status.EXPERIMENTAL, since = "6.0.0") -public inline fun > valueOf(value: String): T? = - valueOf(T::class.java, value) +public inline fun valueOf(value: String): T? where T : Enum<*>, T : ResourceKeyed = + T::class.java.valueOf(value) @Suppress("UNCHECKED_CAST") @API(status = API.Status.EXPERIMENTAL, since = "6.0.0") -public fun > valueOf(klass: Class, value: String): T? = - VALUE_MAP.get(klass)[value] as T? - -@get:API(status = API.Status.EXPERIMENTAL, since = "6.0.0") -public val Enum<*>.stringValue: String - get() = toString() +public fun Class.valueOf(value: String): T? where T : Enum<*>, T : ResourceKeyed = + enumValueMap[value] From c01ddbee0a7660eefb53ac0d4c51d6476368e62e Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Mon, 1 Dec 2025 19:34:53 +0300 Subject: [PATCH 09/20] feat: support localization in JEditableCheckBox --- .../jmeter/control/gui/TestPlanGui.java | 9 ++- .../control/gui/TransactionControllerGui.java | 6 +- .../jmeter/threads/gui/ThreadGroupGui.java | 9 ++- .../jmeter/gui/JBooleanPropertyEditor.kt | 28 ++++---- .../jmeter/resources/messages.properties | 4 +- .../jmeter/resources/messages_fr.properties | 2 +- .../apache/jorphan/gui/JEditableCheckBox.kt | 64 +++++++++---------- .../http/config/gui/HttpDefaultsGui.java | 9 ++- .../http/config/gui/UrlConfigGui.java | 9 ++- .../http/control/gui/HttpTestSampleGui.java | 9 ++- .../java/control/gui/BeanShellSamplerGui.java | 3 +- 11 files changed, 86 insertions(+), 66 deletions(-) diff --git a/src/core/src/main/java/org/apache/jmeter/control/gui/TestPlanGui.java b/src/core/src/main/java/org/apache/jmeter/control/gui/TestPlanGui.java index 74b9b0f3fae..8528b531cde 100644 --- a/src/core/src/main/java/org/apache/jmeter/control/gui/TestPlanGui.java +++ b/src/core/src/main/java/org/apache/jmeter/control/gui/TestPlanGui.java @@ -57,17 +57,20 @@ public class TestPlanGui extends AbstractJMeterGuiComponent { private final JBooleanPropertyEditor functionalMode = new JBooleanPropertyEditor( TestPlanSchema.INSTANCE.getFunctionalMode(), - JMeterUtils.getResString("functional_mode")); + "functional_mode", + JMeterUtils::getResString); private final JBooleanPropertyEditor serializedMode = new JBooleanPropertyEditor( TestPlanSchema.INSTANCE.getSerializeThreadgroups(), - JMeterUtils.getResString("testplan.serialized")); + "testplan.serialized", + JMeterUtils::getResString); private final JBooleanPropertyEditor tearDownOnShutdown = new JBooleanPropertyEditor( TestPlanSchema.INSTANCE.getTearDownOnShutdown(), - JMeterUtils.getResString("teardown_on_shutdown")); + "teardown_on_shutdown", + JMeterUtils::getResString); /** A panel allowing the user to define variables. */ private final ArgumentsPanel argsPanel; diff --git a/src/core/src/main/java/org/apache/jmeter/control/gui/TransactionControllerGui.java b/src/core/src/main/java/org/apache/jmeter/control/gui/TransactionControllerGui.java index ee513facade..c54984b47c1 100644 --- a/src/core/src/main/java/org/apache/jmeter/control/gui/TransactionControllerGui.java +++ b/src/core/src/main/java/org/apache/jmeter/control/gui/TransactionControllerGui.java @@ -39,13 +39,15 @@ public class TransactionControllerGui extends AbstractControllerGui { private final JBooleanPropertyEditor generateParentSample = new JBooleanPropertyEditor( TransactionControllerSchema.INSTANCE.getGenearteParentSample(), - JMeterUtils.getResString("transaction_controller_parent")); + "transaction_controller_parent", + JMeterUtils::getResString); /** if selected, add duration of timers to total runtime */ private final JBooleanPropertyEditor includeTimers = new JBooleanPropertyEditor( TransactionControllerSchema.INSTANCE.getIncludeTimers(), - JMeterUtils.getResString("transaction_controller_include_timers")); + "transaction_controller_parent", + JMeterUtils::getResString); /** * Create a new TransactionControllerGui instance. diff --git a/src/core/src/main/java/org/apache/jmeter/threads/gui/ThreadGroupGui.java b/src/core/src/main/java/org/apache/jmeter/threads/gui/ThreadGroupGui.java index 3ddcd1f96ee..3c096d0b0eb 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/gui/ThreadGroupGui.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/gui/ThreadGroupGui.java @@ -65,7 +65,8 @@ public class ThreadGroupGui extends AbstractThreadGroupGui implements ItemListen private final JBooleanPropertyEditor scheduler = new JBooleanPropertyEditor( ThreadGroupSchema.INSTANCE.getUseScheduler(), - JMeterUtils.getResString("scheduler")); + "scheduler", + JMeterUtils::getResString); private final JTextField duration = new JTextField(); private final JLabel durationLabel = labelFor(duration, "duration"); @@ -76,7 +77,8 @@ public class ThreadGroupGui extends AbstractThreadGroupGui implements ItemListen private final JBooleanPropertyEditor sameUserBox = new JBooleanPropertyEditor( AbstractThreadGroupSchema.INSTANCE.getSameUserOnNextIteration(), - JMeterUtils.getResString("threadgroup_same_user")); + "threadgroup_same_user", + JMeterUtils::getResString); public ThreadGroupGui() { this(true); @@ -199,7 +201,8 @@ private void init() { // WARNING: called from ctor so must not be overridden (i. if (showDelayedStart) { delayedStart = new JBooleanPropertyEditor( ThreadGroupSchema.INSTANCE.getDelayedStart(), - JMeterUtils.getResString("delayed_start")); // $NON-NLS-1$ + "delayed_start", + JMeterUtils::getResString); // $NON-NLS-1$ threadPropsPanel.add(delayedStart, "span 2"); } scheduler.addPropertyChangeListener( diff --git a/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt b/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt index 1b4e70af789..c99f9e89bd3 100644 --- a/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt +++ b/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt @@ -20,9 +20,12 @@ package org.apache.jmeter.gui import org.apache.jmeter.testelement.TestElement import org.apache.jmeter.testelement.property.BooleanProperty import org.apache.jmeter.testelement.schema.BooleanPropertyDescriptor -import org.apache.jmeter.util.JMeterUtils import org.apache.jorphan.gui.JEditableCheckBox +import org.apache.jorphan.locale.LocalizedString +import org.apache.jorphan.locale.PlainValue +import org.apache.jorphan.locale.ResourceLocalizer import org.apiguardian.api.API +import org.jetbrains.annotations.NonNls /** * Provides editor component for boolean properties that accommodate both true/false and expression string. @@ -31,19 +34,20 @@ import org.apiguardian.api.API @API(status = API.Status.EXPERIMENTAL, since = "5.6") public class JBooleanPropertyEditor( private val propertyDescriptor: BooleanPropertyDescriptor<*>, - label: String, -) : JEditableCheckBox(label, DEFAULT_CONFIGURATION), Binding { + label: @NonNls String, + resourceLocalizer: ResourceLocalizer, +) : JEditableCheckBox(label, createConfiguration(resourceLocalizer), resourceLocalizer), Binding { private companion object { - @JvmField - val DEFAULT_CONFIGURATION: Configuration = Configuration( - startEditing = JMeterUtils.getResString("editable_checkbox.use_expression"), - trueValue = "true", - falseValue = "false", - extraValues = listOf( - "\${__P(property_name)}", - "\${variable_name}", + private fun createConfiguration(resourceLocalizer: ResourceLocalizer) = + Configuration( + useExpression = LocalizedString("edit_as_expression_action", resourceLocalizer), + trueValue = LocalizedString("editable_checkbox.true", resourceLocalizer), + falseValue = LocalizedString("editable_checkbox.false", resourceLocalizer), + extraValues = listOf( + PlainValue("\${__P(property_name)}"), + PlainValue("\${variable_name}"), + ) ) - ) } public fun reset() { diff --git a/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties b/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties index 23721c4b4c9..0f8fa876e96 100644 --- a/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties +++ b/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties @@ -315,7 +315,9 @@ duration_assertion_label=Duration in milliseconds\: duration_assertion_title=Duration Assertion duration_tooltip=Elapsed time of current running Test edit=Edit -editable_checkbox.use_expression=Use Expression +edit_as_expression_action=Use Expression +editable_checkbox.true=True +editable_checkbox.false=False email_results_title=Email Results en=English enable=Enable diff --git a/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties b/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties index ed0f7be54d1..2c0dde78f12 100644 --- a/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties +++ b/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties @@ -310,7 +310,7 @@ duration_assertion_label=Durée en millisecondes \: duration_assertion_title=Assertion Durée duration_tooltip=Temps passé depuis le début du test en cours edit=Editer -editable_checkbox.use_expression=Utiliser l'expression +edit_as_expression_action=Utiliser l'expression email_results_title=Résultat d'email en=Anglais enable=Activer diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt index ea825cf9206..86e98507f11 100644 --- a/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt @@ -17,7 +17,13 @@ package org.apache.jorphan.gui +import org.apache.jorphan.locale.ComboBoxValue +import org.apache.jorphan.locale.LocalizedString +import org.apache.jorphan.locale.PlainValue +import org.apache.jorphan.locale.ResourceKeyed +import org.apache.jorphan.locale.ResourceLocalizer import org.apiguardian.api.API +import org.jetbrains.annotations.NonNls import java.awt.Container import java.awt.FlowLayout import java.awt.event.ActionEvent @@ -28,7 +34,6 @@ import javax.swing.JComboBox import javax.swing.JLabel import javax.swing.JPanel import javax.swing.JPopupMenu -import javax.swing.SwingUtilities import javax.swing.event.ChangeEvent /** @@ -37,8 +42,9 @@ import javax.swing.event.ChangeEvent */ @API(status = API.Status.EXPERIMENTAL, since = "5.6") public open class JEditableCheckBox( - label: String, - private val configuration: Configuration + label: @NonNls String, + private val configuration: Configuration, + resourceLocalizer: ResourceLocalizer, ) : JPanel() { public companion object { public const val CHECKBOX_CARD: String = "checkbox" @@ -82,28 +88,29 @@ public open class JEditableCheckBox( /** * Supplies the parameters to [JEditableCheckBox]. */ + @API(status = API.Status.EXPERIMENTAL, since = "5.6.0") public data class Configuration( /** Menu item title to "start editing" the checkbox value. */ - val startEditing: String = "Use Expression", + val useExpression: LocalizedString, /** The title to be used for "true" value in the checkbox. */ - val trueValue: String = "true", + val trueValue: LocalizedString, /** The title to be used for "false" value in the checkbox. */ - val falseValue: String = "false", + val falseValue: LocalizedString, /** Extra values to be added for the combobox. */ - val extraValues: List = listOf(), + val extraValues: List = listOf(), ) private val cards = CardLayoutWithSizeOfCurrentVisibleElement() - private val useExpressionAction = object : AbstractAction(configuration.startEditing) { + private val useExpressionAction = object : AbstractAction(configuration.useExpression.toString()) { override fun actionPerformed(e: ActionEvent?) { cards.next(this@JEditableCheckBox) + comboBox.selectedItem = if (checkbox.isSelected) configuration.trueValue else configuration.falseValue comboBox.requestFocusInWindow() - fireValueChanged() } } - private val checkbox: JCheckBox = JCheckBox(label).apply { + private val checkbox: JCheckBox = JCheckBox(resourceLocalizer.localize(label)).apply { val cb = this componentPopupMenu = JPopupMenu().apply { add(useExpressionAction) @@ -113,34 +120,16 @@ public open class JEditableCheckBox( } } - private val comboBox: JComboBox = JComboBox().apply { + private val comboBox: JComboBox = JComboBox().apply { isEditable = true configuration.extraValues.forEach { addItem(it) } addItem(configuration.trueValue) addItem(configuration.falseValue) - addActionListener { - val jComboBox = it.source as JComboBox<*> - SwingUtilities.invokeLater { - if (jComboBox.isPopupVisible) { - fireValueChanged() - return@invokeLater - } - when (val value = jComboBox.selectedItem as String) { - configuration.trueValue, configuration.falseValue -> { - checkbox.isSelected = value == configuration.trueValue - cards.show(this@JEditableCheckBox, CHECKBOX_CARD) - checkbox.requestFocusInWindow() - fireValueChanged() - } - } - } - } - // TODO: trigger value changed when the text is changed } - private val textFieldLabel = JLabel(label).apply { + private val textFieldLabel = JLabel(resourceLocalizer.localize(label)).apply { labelFor = comboBox } @@ -190,19 +179,26 @@ public open class JEditableCheckBox( public var value: Value get() = when (components.indexOfFirst { it.isVisible }) { 0 -> if (checkbox.isSelected) Value.Boolean.TRUE else Value.Boolean.FALSE - else -> Value.Text(comboBox.selectedItem as String) + else -> + when (val value = comboBox.selectedItem) { + is ResourceKeyed -> + when (value.resourceKey) { + configuration.trueValue.resourceKey -> Value.Boolean.TRUE + configuration.falseValue.resourceKey -> Value.Boolean.FALSE + else -> Value.Text(value.resourceKey) + } + else -> Value.Text(value?.toString() ?: "") + } } set(value) { when (value) { is Value.Boolean -> { - comboBox.selectedItem = "" checkbox.isSelected = value.value cards.show(this, CHECKBOX_CARD) } is Value.Text -> { - checkbox.isSelected = false - comboBox.selectedItem = value.value + comboBox.selectedItem = PlainValue(value.value) cards.show(this, EDITABLE_CARD) } } diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java index 8b49bcbb4ec..c056b8a8f02 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java @@ -61,14 +61,17 @@ public class HttpDefaultsGui extends AbstractConfigGui { private UrlConfigGui urlConfigGui; private final JBooleanPropertyEditor retrieveEmbeddedResources = new JBooleanPropertyEditor( HTTPSamplerBaseSchema.INSTANCE.getRetrieveEmbeddedResources(), - JMeterUtils.getResString("web_testing_retrieve_images")); + "web_testing_retrieve_images", + JMeterUtils::getResString); private final JBooleanPropertyEditor concurrentDwn = new JBooleanPropertyEditor( HTTPSamplerBaseSchema.INSTANCE.getConcurrentDownload(), - JMeterUtils.getResString("web_testing_concurrent_download")); + "web_testing_concurrent_download", + JMeterUtils::getResString); private JTextField concurrentPool; private final JBooleanPropertyEditor useMD5 = new JBooleanPropertyEditor( HTTPSamplerBaseSchema.INSTANCE.getStoreAsMD5(), - JMeterUtils.getResString("response_save_as_md5")); // $NON-NLS-1$ + "response_save_as_md5", + JMeterUtils::getResString); // $NON-NLS-1$ private JTextField embeddedAllowRE; // regular expression used to match against embedded resource URLs to allow private JTextField embeddedExcludeRE; // regular expression used to match against embedded resource URLs to discard private JTextField sourceIpAddr; // does not apply to Java implementation diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java index 2cb82f654c8..055a81551b6 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java @@ -380,19 +380,22 @@ protected Component getPathPanel() { useKeepAlive = new JBooleanPropertyEditor( HTTPSamplerBaseSchema.INSTANCE.getUseKeepalive(), - JMeterUtils.getResString("use_keepalive")); + "use_keepalive", + JMeterUtils::getResString); JFactory.small(useKeepAlive); useKeepAlive.setVisible(getUrlConfigDefaults().isUseKeepAliveVisible()); useMultipart = new JBooleanPropertyEditor( HTTPSamplerBaseSchema.INSTANCE.getUseMultipartPost(), - JMeterUtils.getResString("use_multipart_for_http_post")); // $NON-NLS-1$ + "use_multipart_for_http_post", + JMeterUtils::getResString); JFactory.small(useMultipart); useMultipart.setVisible(getUrlConfigDefaults().isUseMultipartVisible()); useBrowserCompatibleMultipartMode = new JBooleanPropertyEditor( HTTPSamplerBaseSchema.INSTANCE.getUseBrowserCompatibleMultipart(), - JMeterUtils.getResString("use_multipart_mode_browser")); // $NON-NLS-1$ + "use_multipart_mode_browser", + JMeterUtils::getResString); JFactory.small(useBrowserCompatibleMultipartMode); useBrowserCompatibleMultipartMode.setVisible(getUrlConfigDefaults().isUseBrowserCompatibleMultipartModeVisible()); } diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java index 77863a286bd..88da93fb9d6 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java @@ -63,14 +63,17 @@ public class HttpTestSampleGui extends AbstractSamplerGui { private UrlConfigGui urlConfigGui; private final JBooleanPropertyEditor retrieveEmbeddedResources = new JBooleanPropertyEditor( HTTPSamplerBaseSchema.INSTANCE.getRetrieveEmbeddedResources(), - JMeterUtils.getResString("web_testing_retrieve_images")); + "web_testing_retrieve_images", + JMeterUtils::getResString); private final JBooleanPropertyEditor concurrentDwn = new JBooleanPropertyEditor( HTTPSamplerBaseSchema.INSTANCE.getConcurrentDownload(), - JMeterUtils.getResString("web_testing_concurrent_download")); + "web_testing_concurrent_download", + JMeterUtils::getResString); private JTextField concurrentPool; private final JBooleanPropertyEditor useMD5 = new JBooleanPropertyEditor( HTTPSamplerBaseSchema.INSTANCE.getStoreAsMD5(), - JMeterUtils.getResString("response_save_as_md5")); // $NON-NLS-1$ + "response_save_as_md5", + JMeterUtils::getResString); private JTextField embeddedAllowRE; // regular expression used to match against embedded resource URLs to allow private JTextField embeddedExcludeRE; // regular expression used to match against embedded resource URLs to exclude private JTextField sourceIpAddr; // does not apply to Java implementation diff --git a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/control/gui/BeanShellSamplerGui.java b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/control/gui/BeanShellSamplerGui.java index 019347e7952..739c92f1230 100644 --- a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/control/gui/BeanShellSamplerGui.java +++ b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/control/gui/BeanShellSamplerGui.java @@ -49,7 +49,8 @@ public class BeanShellSamplerGui extends AbstractSamplerGui { private final JBooleanPropertyEditor resetInterpreter = new JBooleanPropertyEditor( BeanShellSamplerSchema.INSTANCE.getResetInterpreter(), - JMeterUtils.getResString("bsh_script_reset_interpreter")); + "bsh_script_reset_interpreter", + JMeterUtils::getResString); private final FilePanelEntry filename = new FilePanelEntry(JMeterUtils.getResString("bsh_script_file"),".bsh"); // script file name (if present) From 4e3a778959417cdca3946aceb1fcab772c6a7395 Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Mon, 1 Dec 2025 20:02:44 +0300 Subject: [PATCH 10/20] feat: add "use expression" button to JEditableCheckBox for better UX --- .../jmeter/gui/JBooleanPropertyEditor.kt | 1 + .../jmeter/resources/messages.properties | 1 + .../jmeter/resources/messages_fr.properties | 1 + .../apache/jorphan/gui/JEditableCheckBox.kt | 11 +++++++ .../org/apache/jorphan/gui/JEllipsisButton.kt | 28 ++++++++++++++++++ .../org/apache/jorphan/gui/JSquareButton.kt | 29 +++++++++++++++++++ 6 files changed, 71 insertions(+) create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEllipsisButton.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JSquareButton.kt diff --git a/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt b/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt index c99f9e89bd3..a410eb700d1 100644 --- a/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt +++ b/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt @@ -41,6 +41,7 @@ public class JBooleanPropertyEditor( private fun createConfiguration(resourceLocalizer: ResourceLocalizer) = Configuration( useExpression = LocalizedString("edit_as_expression_action", resourceLocalizer), + useExpressionTooltip = LocalizedString("edit_as_expression_tooltip", resourceLocalizer), trueValue = LocalizedString("editable_checkbox.true", resourceLocalizer), falseValue = LocalizedString("editable_checkbox.false", resourceLocalizer), extraValues = listOf( diff --git a/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties b/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties index 0f8fa876e96..dc34c8f41cb 100644 --- a/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties +++ b/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties @@ -316,6 +316,7 @@ duration_assertion_title=Duration Assertion duration_tooltip=Elapsed time of current running Test edit=Edit edit_as_expression_action=Use Expression +edit_as_expression_tooltip=Switch to expression mode to use variables like ${__P(property)} editable_checkbox.true=True editable_checkbox.false=False email_results_title=Email Results diff --git a/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties b/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties index 2c0dde78f12..1fb8d18ea7b 100644 --- a/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties +++ b/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties @@ -311,6 +311,7 @@ duration_assertion_title=Assertion Durée duration_tooltip=Temps passé depuis le début du test en cours edit=Editer edit_as_expression_action=Utiliser l'expression +edit_as_expression_tooltip=Passer en mode expression pour utiliser des variables comme ${__P(property)} email_results_title=Résultat d'email en=Anglais enable=Activer diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt index 86e98507f11..30a01d1af44 100644 --- a/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt @@ -92,6 +92,8 @@ public open class JEditableCheckBox( public data class Configuration( /** Menu item title to "start editing" the checkbox value. */ val useExpression: LocalizedString, + /** Tooltip for "start editing" button. */ + val useExpressionTooltip: LocalizedString, /** The title to be used for "true" value in the checkbox. */ val trueValue: LocalizedString, /** The title to be used for "false" value in the checkbox. */ @@ -133,6 +135,12 @@ public open class JEditableCheckBox( labelFor = comboBox } + private val expressionButton = JEllipsisButton().apply { + // Tooltip will be set via configuration or use default + toolTipText = configuration.useExpressionTooltip.toString() + addActionListener(useExpressionAction) + } + @Transient private var changeEvent: ChangeEvent? = null @@ -143,6 +151,7 @@ public open class JEditableCheckBox( Container().apply { layout = FlowLayout(FlowLayout.LEADING, 0, 0) add(checkbox) + add(expressionButton) }, CHECKBOX_CARD ) @@ -165,6 +174,7 @@ public open class JEditableCheckBox( super.setEnabled(enabled) checkbox.isEnabled = enabled comboBox.isEnabled = enabled + expressionButton.isEnabled = enabled useExpressionAction.isEnabled = enabled } @@ -223,6 +233,7 @@ public open class JEditableCheckBox( public fun makeSmall() { JFactory.small(checkbox) + JFactory.small(expressionButton) // We do not make combobox small as the expression migh be hard to read } } diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEllipsisButton.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEllipsisButton.kt new file mode 100644 index 00000000000..6e1f0da4c6d --- /dev/null +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEllipsisButton.kt @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.gui + +import javax.swing.plaf.nimbus.NimbusStyle + +public class JEllipsisButton : JSquareButton("⋮") { + init { + putClientProperty("JComponent.sizeVariant", NimbusStyle.SMALL_KEY) + putClientProperty("JButton.square", true) + putClientProperty("JButton.thin", true) + } +} diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JSquareButton.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JSquareButton.kt new file mode 100644 index 00000000000..dbf40351dde --- /dev/null +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JSquareButton.kt @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.gui + +import org.jetbrains.annotations.Nls +import java.awt.Dimension +import javax.swing.JButton + +public open class JSquareButton(title: @Nls String) : JButton(title) { + override fun getPreferredSize(): Dimension { + val dimension = super.getPreferredSize() + return Dimension(dimension.height + 15, dimension.height) + } +} From 6bab0750d52d9606773c28cb63178193a1684489 Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Mon, 1 Dec 2025 19:38:34 +0300 Subject: [PATCH 11/20] feat: add JEnumPropertyEditor, JEditableComboBox --- .../org/apache/jmeter/config/CSVDataSet.java | 14 +- .../timers/ConstantThroughputTimer.java | 15 +- .../ConstantThroughputTimerBeanInfo.java | 1 - .../jmeter/testbeans/gui/EnumEditor.java | 40 ++- .../gui/GenericTestBeanCustomizer.java | 20 +- .../apache/jmeter/gui/JEnumPropertyEditor.kt | 136 +++++++++++ .../apache/jorphan/gui/JEditableComboBox.kt | 229 ++++++++++++++++++ 7 files changed, 410 insertions(+), 45 deletions(-) create mode 100644 src/core/src/main/kotlin/org/apache/jmeter/gui/JEnumPropertyEditor.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableComboBox.kt diff --git a/src/components/src/main/java/org/apache/jmeter/config/CSVDataSet.java b/src/components/src/main/java/org/apache/jmeter/config/CSVDataSet.java index 05f5f8f6f05..285810c5bcc 100644 --- a/src/components/src/main/java/org/apache/jmeter/config/CSVDataSet.java +++ b/src/components/src/main/java/org/apache/jmeter/config/CSVDataSet.java @@ -32,7 +32,7 @@ import org.apache.jmeter.threads.JMeterContext; import org.apache.jmeter.threads.JMeterVariables; import org.apache.jmeter.util.JMeterUtils; -import org.apache.jorphan.util.EnumUtils; +import org.apache.jorphan.locale.ResourceKeyed; import org.apache.jorphan.util.JMeterStopThreadException; import org.apache.jorphan.util.JOrphanUtils; import org.apache.jorphan.util.StringUtilities; @@ -70,20 +70,20 @@ public class CSVDataSet extends ConfigTestElement implements TestBean, LoopIterationListener, NoConfigMerge { - public enum ShareMode { + public enum ShareMode implements ResourceKeyed { ALL("shareMode.all"), GROUP("shareMode.group"), THREAD("shareMode.thread"); - private final String value; + private final String propertyName; - ShareMode(String value) { - this.value = value; + ShareMode(String propertyName) { + this.propertyName = propertyName; } @Override - public String toString() { - return value; + public String getResourceKey() { + return propertyName; } } diff --git a/src/components/src/main/java/org/apache/jmeter/timers/ConstantThroughputTimer.java b/src/components/src/main/java/org/apache/jmeter/timers/ConstantThroughputTimer.java index 21a6edb7aea..7fa9aaf9794 100644 --- a/src/components/src/main/java/org/apache/jmeter/timers/ConstantThroughputTimer.java +++ b/src/components/src/main/java/org/apache/jmeter/timers/ConstantThroughputTimer.java @@ -32,6 +32,7 @@ import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.collections.IdentityKey; +import org.apache.jorphan.locale.ResourceKeyed; import org.apache.jorphan.util.EnumUtils; import org.apiguardian.api.API; @@ -71,7 +72,7 @@ private static class ThroughputInfo{ /** * This enum defines the calculation modes used by the ConstantThroughputTimer. */ - public enum Mode { + public enum Mode implements ResourceKeyed { ThisThreadOnly("calcMode.1"), // NOSONAR Keep naming for compatibility AllActiveThreads("calcMode.2"), // NOSONAR Keep naming for compatibility AllActiveThreadsInCurrentThreadGroup("calcMode.3"), // NOSONAR Keep naming for compatibility @@ -89,6 +90,11 @@ public enum Mode { public String toString() { return propertyName; } + + @Override + public String getResourceKey() { + return propertyName; + } } /** @@ -161,15 +167,13 @@ public Mode getMode() { } @Deprecated - @SuppressWarnings("EnumOrdinal") public void setCalcMode(int mode) { - setMode(EnumUtils.values(Mode.class).get(mode)); + setMode(EnumUtils.getEnumValues(Mode.class).get(mode)); } - @SuppressWarnings("EnumOrdinal") @API(status = API.Status.MAINTAINED, since = "6.0.0") public void setMode(Mode newMode) { - getSchema().getCalcMode().set(this, newMode.toString()); + getSchema().getCalcMode().set(this, newMode.getResourceKey()); } /** @@ -296,7 +300,6 @@ public String toString() { * so the conversion only needs to happen once. */ @Override - @SuppressWarnings("EnumOrdinal") public void setProperty(JMeterProperty property) { String propertyName = property.getName(); if (propertyName.equals("calcMode")) { diff --git a/src/components/src/main/java/org/apache/jmeter/timers/ConstantThroughputTimerBeanInfo.java b/src/components/src/main/java/org/apache/jmeter/timers/ConstantThroughputTimerBeanInfo.java index 4f75ceaa707..4d6378dd469 100644 --- a/src/components/src/main/java/org/apache/jmeter/timers/ConstantThroughputTimerBeanInfo.java +++ b/src/components/src/main/java/org/apache/jmeter/timers/ConstantThroughputTimerBeanInfo.java @@ -26,7 +26,6 @@ */ public class ConstantThroughputTimerBeanInfo extends BeanInfoSupport { - @SuppressWarnings("EnumOrdinal") public ConstantThroughputTimerBeanInfo() { super(ConstantThroughputTimer.class); diff --git a/src/core/src/main/java/org/apache/jmeter/testbeans/gui/EnumEditor.java b/src/core/src/main/java/org/apache/jmeter/testbeans/gui/EnumEditor.java index 7c241aa4255..a77ac353f1d 100644 --- a/src/core/src/main/java/org/apache/jmeter/testbeans/gui/EnumEditor.java +++ b/src/core/src/main/java/org/apache/jmeter/testbeans/gui/EnumEditor.java @@ -31,6 +31,7 @@ import javax.swing.JList; import org.apache.jmeter.gui.ClearGui; +import org.apache.jorphan.locale.ResourceKeyed; import org.apache.jorphan.util.EnumUtils; /** @@ -41,14 +42,13 @@ * The provided GUI is a combo box with an option for each value in the enum. *

    */ -class EnumEditor extends PropertyEditorSupport implements ClearGui { +class EnumEditor & ResourceKeyed> extends PropertyEditorSupport implements ClearGui { + private final JComboBox combo; - private final JComboBox> combo; + private final T defaultValue; - private final Enum defaultValue; - - public EnumEditor(final PropertyDescriptor descriptor, final Class> enumClazz, final ResourceBundle rb) { - DefaultComboBoxModel> model = new DefaultComboBoxModel<>(); + public EnumEditor(final PropertyDescriptor descriptor, final Class enumClass, final ResourceBundle rb) { + DefaultComboBoxModel model = new DefaultComboBoxModel<>(); combo = new JComboBox<>(model); combo.setEditable(false); combo.setRenderer( @@ -56,19 +56,18 @@ public EnumEditor(final PropertyDescriptor descriptor, final Class list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - Enum enumValue = (Enum) value; - label.setText(rb.getString(EnumUtils.getStringValue(enumValue))); + label.setText(rb.getString(enumClass.cast(value).getResourceKey())); return label; } } ); - List> values = EnumUtils.values(enumClazz); - for(Enum e : values) { + List values = EnumUtils.getEnumValues(enumClass); + for (T e : values) { model.addElement(e); } Object def = descriptor.getValue(GenericTestBeanCustomizer.DEFAULT); if (def instanceof Enum enumValue) { - defaultValue = enumValue; + defaultValue = enumClass.cast(enumValue); } else if (def instanceof Integer index) { defaultValue = values.get(index); } else { @@ -99,25 +98,24 @@ public void setValue(Object value) { } else if (value instanceof Integer integer) { combo.setSelectedIndex(integer); } else if (value instanceof String string) { - ComboBoxModel> model = combo.getModel(); - for (int i = 0; i < model.getSize(); i++) { - Enum element = model.getElementAt(i); - if (EnumUtils.getStringValue(element).equals(string)) { - combo.setSelectedItem(element); - return; - } - } + setAsText(string); } } @Override public void setAsText(String value) { - throw new UnsupportedOperationException("Not supported yet. Use enum value rather than text, got " + value); + ComboBoxModel model = combo.getModel(); + for (int i = 0; i < model.getSize(); i++) { + T element = model.getElementAt(i); + if (value.equals(element.getResourceKey())) { + combo.setSelectedItem(element); + return; + } + } } @Override public void clearGui() { combo.setSelectedItem(defaultValue); } - } diff --git a/src/core/src/main/java/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java b/src/core/src/main/java/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java index 9f25190d335..b53bfca541b 100644 --- a/src/core/src/main/java/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java +++ b/src/core/src/main/java/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java @@ -43,7 +43,6 @@ import javax.swing.JScrollPane; import javax.swing.SwingConstants; -import org.apache.jmeter.JMeter; import org.apache.jmeter.gui.ClearGui; import org.apache.jmeter.testbeans.TestBeanHelper; import org.apache.jmeter.testelement.property.IntegerProperty; @@ -51,6 +50,7 @@ import org.apache.jmeter.testelement.property.LongProperty; import org.apache.jmeter.testelement.property.StringProperty; import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.locale.ResourceKeyed; import org.apache.jorphan.util.EnumUtils; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; @@ -338,9 +338,9 @@ public GenericTestBeanCustomizer(){ * @return a StringProperty containing the normalized enum value, or null if the property is invalid or unrecognized */ @API(status = API.Status.INTERNAL, since = "6.0.0") - public static > @Nullable JMeterProperty normalizeEnumProperty( + public static & ResourceKeyed> @Nullable JMeterProperty normalizeEnumProperty( Class klass, Class enumKlass, JMeterProperty property) { - List values = EnumUtils.values(enumKlass); + List values = EnumUtils.getEnumValues(enumKlass); T value; if (property instanceof IntegerProperty intProperty) { int index = intProperty.getIntValue(); @@ -362,26 +362,26 @@ public GenericTestBeanCustomizer(){ return null; } value = normalizeEnumStringValue(stringValue, klass, enumKlass); - if (stringValue.equals(EnumUtils.getStringValue(value))) { + if (stringValue.equals(value.getResourceKey())) { // If the input property was good enough, keep it return property; } } else { return null; } - return new StringProperty(property.getName(), EnumUtils.getStringValue(value)); + return new StringProperty(property.getName(), value.getResourceKey()); } @API(status = API.Status.INTERNAL, since = "6.0.0") - public static > T normalizeEnumStringValue(String value, Class klass, Class enumKlass) { + public static & ResourceKeyed> T normalizeEnumStringValue(String value, Class klass, Class enumKlass) { T enumValue = EnumUtils.valueOf(enumKlass, value); if (enumValue != null) { return enumValue; } - return normalizeEnumStringValue(value, klass, EnumUtils.values(enumKlass)); + return normalizeEnumStringValue(value, klass, EnumUtils.getEnumValues(enumKlass)); } - private static > T normalizeEnumStringValue(String value, Class klass, List values) { + private static & ResourceKeyed> T normalizeEnumStringValue(String value, Class klass, List values) { // Fallback: the value might be localized, thus check the current and root locales String bundleName = null; try { @@ -408,9 +408,9 @@ private static > T normalizeEnumStringValue(String value, Clas return findEnumValue(value, rootBundle, values); } - private static > @Nullable T findEnumValue(String stringValue, ResourceBundle rb, List values) { + private static & ResourceKeyed> @Nullable T findEnumValue(String stringValue, ResourceBundle rb, List values) { for (T enumValue : values) { - if (stringValue.equals(rb.getObject(enumValue.toString()))) { + if (stringValue.equals(rb.getObject(enumValue.getResourceKey()))) { log.debug("Converted {} to {} using Locale: {}", stringValue, enumValue, rb.getLocale()); return enumValue; } diff --git a/src/core/src/main/kotlin/org/apache/jmeter/gui/JEnumPropertyEditor.kt b/src/core/src/main/kotlin/org/apache/jmeter/gui/JEnumPropertyEditor.kt new file mode 100644 index 00000000000..1bc31d068fd --- /dev/null +++ b/src/core/src/main/kotlin/org/apache/jmeter/gui/JEnumPropertyEditor.kt @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.gui + +import org.apache.jmeter.testelement.TestElement +import org.apache.jmeter.testelement.property.StringProperty +import org.apache.jmeter.testelement.schema.StringPropertyDescriptor +import org.apache.jorphan.gui.JEditableComboBox +import org.apache.jorphan.locale.LocalizedString +import org.apache.jorphan.locale.LocalizedValue +import org.apache.jorphan.locale.PlainValue +import org.apache.jorphan.locale.ResourceKeyed +import org.apache.jorphan.locale.ResourceLocalizer +import org.apache.jorphan.util.enumValues +import org.apiguardian.api.API +import org.jetbrains.annotations.NonNls + +/** + * Property editor for enum values that implements [ResourceKeyed]. + * + * This editor provides a combo box that displays localized enum values and can also + * accept custom expressions like `${__P(property)}` for dynamic configuration. + * + * The editor: + * - Displays predefined enum values with localized text + * - Stores enum resource keys (not enum names) in the test element property + * - Allows switching to an editable text field for expressions + * - Automatically detects and handles both enum values and custom expressions + * + * Example usage in a GUI class: + * ```java + * private final JEnumPropertyEditor modeEditor; + * + * modeEditor = new JEnumPropertyEditor<>( + * schema.getResponseProcessingMode(), + * "response_mode_label", + * ResponseProcessingMode.class, + * JMeterUtils::getResString + * ); + * bindingGroup.add(modeEditor); + * ``` + * + * @param E the enum type that implements [ResourceKeyed] + * @property propertyDescriptor the property descriptor for the enum property + * @property label the label text to display next to the combo box + * @property enumClass the class of the enum type + * @since 6.0.0 + */ +@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public class JEnumPropertyEditor( + private val propertyDescriptor: StringPropertyDescriptor<*>, + label: @NonNls String, + enumClass: Class, + resourceLocalizer: ResourceLocalizer, +) : JEditableComboBox(label, createConfiguration(enumClass, resourceLocalizer), resourceLocalizer), Binding + where E : Enum, E : ResourceKeyed { + + private companion object { + @JvmStatic + private fun createConfiguration( + enumClass: Class, + resourceLocalizer: ResourceLocalizer + ): Configuration + where E : Enum, E : ResourceKeyed { + val resourceKeys = enumClass.enumValues.map { + LocalizedValue(it, resourceLocalizer) + } + + return Configuration( + useExpression = LocalizedString("edit_as_expression_action", resourceLocalizer), + useExpressionTooltip = LocalizedString("edit_as_expression_tooltip", resourceLocalizer), + values = resourceKeys, + extraValues = listOf( + PlainValue("\${__P(property_name)}"), + PlainValue("\${variable_name}"), + ) + ) + } + } + + /** + * Resets the editor to the default value specified in the property descriptor. + */ + public fun reset() { + value = PlainValue(propertyDescriptor.defaultValue ?: "") + } + + /** + * Updates the test element with the current value from the editor. + * + * The value is stored as-is (either a resource key or a custom expression). + */ + override fun updateElement(testElement: TestElement) { + val currentValue = value + if ((currentValue as? PlainValue)?.value.isNullOrBlank() && propertyDescriptor.defaultValue == null) { + // Remove property if empty and no default + testElement.removeProperty(propertyDescriptor.name) + } else { + testElement[propertyDescriptor] = when (currentValue) { + is ResourceKeyed -> currentValue.resourceKey + else -> currentValue.toString() + } + } + } + + /** + * Updates the editor UI from the test element's property value. + * + * Handles both enum resource keys and custom expression strings. + */ + override fun updateUi(testElement: TestElement) { + val property = testElement.getPropertyOrNull(propertyDescriptor) + value = PlainValue( + when (property) { + is StringProperty -> property.stringValue + null -> propertyDescriptor.defaultValue ?: "" + else -> property.stringValue + } + ) + } +} diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableComboBox.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableComboBox.kt new file mode 100644 index 00000000000..b1a220679c5 --- /dev/null +++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableComboBox.kt @@ -0,0 +1,229 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.gui + +import org.apache.jorphan.locale.ComboBoxValue +import org.apache.jorphan.locale.LocalizedString +import org.apache.jorphan.locale.LocalizedValue +import org.apache.jorphan.locale.PlainValue +import org.apache.jorphan.locale.ResourceKeyed +import org.apache.jorphan.locale.ResourceLocalizer +import org.apiguardian.api.API +import org.jetbrains.annotations.NonNls +import java.awt.Container +import java.awt.FlowLayout +import java.awt.event.ActionEvent +import javax.swing.AbstractAction +import javax.swing.Box +import javax.swing.JComboBox +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.JPopupMenu +import javax.swing.event.ChangeEvent + +/** + * A combo box that can display predefined enum values (with localized text) or switch to + * an editable text field for custom expressions like `${__P(property)}`. + * + * This component uses a CardLayout to switch between: + * - A non-editable combo box showing predefined values with localized display text + * - An editable combo box allowing custom text input (expressions) + * + * The component stores resource keys (non-localized) as values, but displays localized + * text to the user via a cell renderer. + * + * Example usage: + * ```kotlin + * val config = JEditableComboBox.Configuration( + * startEditing = JMeterUtils.getResString("editable_combobox_use_expression"), + * values = listOf("option_key_1", "option_key_2"), + * extraValues = listOf("\${__P(my_property)}", "\${variable_name}") + * ) + * val comboBox = JEditableComboBox("Label:", config) + * ``` + * + * @since 6.0.0 + */ +@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public open class JEditableComboBox( + label: @NonNls String, + private val configuration: Configuration, + localizer: ResourceLocalizer, +) : JPanel() { + public companion object { + public const val COMBO_CARD: String = "combo" + public const val EDITABLE_CARD: String = "editable" + public const val VALUE_PROPERTY: String = "value" + } + + /** + * Configuration for the editable combo box. + * + * @property useExpression Text for the menu item to switch to editable mode + * @property values List of predefined resource keys (stored values) + * @property extraValues Additional template values to show in editable mode (like expressions) + */ + public data class Configuration( + val useExpression: LocalizedString, + val useExpressionTooltip: LocalizedString, + val values: List>, + val extraValues: List = listOf() + ) + + private val cards = CardLayoutWithSizeOfCurrentVisibleElement() + + private val useExpressionAction = object : AbstractAction(configuration.useExpression.toString()) { + override fun actionPerformed(e: ActionEvent?) { + editableCombo.selectedItem = nonEditableCombo.selectedItem + cards.show(this@JEditableComboBox, EDITABLE_CARD) + editableCombo.requestFocusInWindow() + } + } + + private val nonEditableCombo: JComboBox = JComboBox().apply { + isEditable = false + componentPopupMenu = JPopupMenu().apply { + add(useExpressionAction) + } + + // Add predefined values + configuration.values.forEach { + addItem(it) + } + + addActionListener { + fireValueChanged() + } + } + + private val editableCombo: JComboBox = JComboBox().apply { + isEditable = true + + // Add template expressions first, then predefined values + configuration.extraValues.forEach { + addItem(it) + } + configuration.values.forEach { + addItem(it) + } + } + + private val comboLabel = JLabel(localizer.localize(label)).apply { + labelFor = nonEditableCombo + } + + private val editableLabel = JLabel(localizer.localize(label)).apply { + labelFor = editableCombo + } + + private val expressionButton = JEllipsisButton().apply { + toolTipText = configuration.useExpressionTooltip.toString() + addActionListener(useExpressionAction) + } + + @Transient + private var changeEvent: ChangeEvent? = null + + init { + layout = cards + add( + Container().apply { + layout = FlowLayout(FlowLayout.LEADING, 0, 0) + add(comboLabel) + add(Box.createHorizontalStrut(5)) + add(nonEditableCombo) + add(Box.createHorizontalStrut(3)) + add(expressionButton) + }, + COMBO_CARD + ) + add( + Container().apply { + layout = FlowLayout(FlowLayout.LEADING, 0, 0) + add(editableLabel) + add(Box.createHorizontalStrut(5)) + add(editableCombo) + }, + EDITABLE_CARD + ) + } + + private var oldValue = value + + override fun setEnabled(enabled: Boolean) { + super.setEnabled(enabled) + nonEditableCombo.isEnabled = enabled + editableCombo.isEnabled = enabled + expressionButton.isEnabled = enabled + useExpressionAction.isEnabled = enabled + } + + private fun fireValueChanged() { + val newValue = value + if (value != oldValue) { + firePropertyChange(VALUE_PROPERTY, oldValue, newValue) + oldValue = newValue + } + } + + /** + * Gets or sets the current value (resource key or custom expression). + */ + public var value: ComboBoxValue? + get() = when (components.indexOfFirst { it.isVisible }) { + 0 -> nonEditableCombo.selectedItem as? ComboBoxValue + else -> when (val value = editableCombo.selectedItem) { + is ComboBoxValue -> value + is String -> PlainValue(value) + else -> null + } + } + set(value) { + // The user might provide a free-text value which coincides with a resourceKey + // For instance, it might be the case when loading a value from jmx test plan + val knownValue = value?.let { findKnownValue(it) } + if (knownValue != null) { + // Predefined value - use non-editable combo + nonEditableCombo.selectedItem = knownValue + cards.show(this, COMBO_CARD) + } else { + // Custom expression - use editable combo + editableCombo.selectedItem = value + cards.show(this, EDITABLE_CARD) + } + fireValueChanged() + } + + private fun findKnownValue(value: ComboBoxValue): ComboBoxValue? { + return when (value) { + is PlainValue -> { + // Plain value might match with one of the known resource keys + configuration.values.find { it.value.resourceKey == value.value } + } + else -> { + configuration.values.find { it == value } + } + } + } + + public fun makeSmall() { + JFactory.small(comboLabel) + JFactory.small(editableLabel) + // Note: JFactory.small() doesn't support JComboBox + } +} From e3a85357efa2df0d44f1b386cafcf2c6fb55e098 Mon Sep 17 00:00:00 2001 From: jvangaalen Date: Mon, 1 Dec 2025 19:30:49 +0300 Subject: [PATCH 12/20] Store raw body in responseData and only decompress when responseBody is accessed --- .../apache/jmeter/samplers/SampleResult.java | 28 +- .../apache/jmeter/samplers/ResponseDecoder.kt | 95 +++++++ .../samplers/ResponseDecoderRegistry.kt | 188 ++++++++++++++ .../samplers/decoders/DeflateDecoder.kt | 72 ++++++ .../jmeter/samplers/decoders/GzipDecoder.kt | 39 +++ .../jmeter/resources/messages.properties | 7 + .../samplers/ResponseDecoderRegistryTest.kt | 184 ++++++++++++++ .../samplers/decoders/DeflateDecoderTest.kt | 88 +++++++ .../samplers/decoders/GzipDecoderTest.kt | 82 ++++++ src/protocol/http/build.gradle.kts | 4 +- .../http/config/gui/HttpDefaultsGui.java | 29 ++- .../http/control/gui/HttpTestSampleGui.java | 31 +-- .../http/sampler/HTTPAbstractImpl.java | 97 +------ .../protocol/http/sampler/HTTPHC4Impl.java | 89 +------ .../protocol/http/sampler/HTTPJavaImpl.java | 70 ++--- .../http/sampler/HTTPSamplerBase.java | 239 +++++++++++++----- .../http/sampler/HTTPSamplerBaseSchema.kt | 7 + .../http/sampler/decoders/BrotliDecoder.kt | 41 +++ .../sampler/decoders/BrotliDecoderTest.kt | 62 +++++ 19 files changed, 1138 insertions(+), 314 deletions(-) create mode 100644 src/core/src/main/kotlin/org/apache/jmeter/samplers/ResponseDecoder.kt create mode 100644 src/core/src/main/kotlin/org/apache/jmeter/samplers/ResponseDecoderRegistry.kt create mode 100644 src/core/src/main/kotlin/org/apache/jmeter/samplers/decoders/DeflateDecoder.kt create mode 100644 src/core/src/main/kotlin/org/apache/jmeter/samplers/decoders/GzipDecoder.kt create mode 100644 src/core/src/test/kotlin/org/apache/jmeter/samplers/ResponseDecoderRegistryTest.kt create mode 100644 src/core/src/test/kotlin/org/apache/jmeter/samplers/decoders/DeflateDecoderTest.kt create mode 100644 src/core/src/test/kotlin/org/apache/jmeter/samplers/decoders/GzipDecoderTest.kt create mode 100644 src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/decoders/BrotliDecoder.kt create mode 100644 src/protocol/http/src/test/kotlin/org/apache/jmeter/protocol/http/sampler/decoders/BrotliDecoderTest.kt diff --git a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java index 4888d250fcb..4eed1e9e9ce 100644 --- a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -17,6 +17,7 @@ package org.apache.jmeter.samplers; +import java.io.IOException; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; @@ -161,6 +162,8 @@ public class SampleResult implements Serializable, Cloneable, Searchable { private byte[] responseData = EMPTY_BA; + private String contentEncoding; // Stores gzip/deflate encoding if response is compressed + private String responseCode = "";// Never return null private String label = "";// Never return null @@ -792,6 +795,16 @@ public void setResponseData(final String response, final String encoding) { * @return the responseData value (cannot be null) */ public byte[] getResponseData() { + if (responseData == null) { + return EMPTY_BA; + } + if (contentEncoding != null && responseData.length > 0) { + try { + return ResponseDecoderRegistry.decode(contentEncoding, responseData); + } catch (IOException e) { + log.warn("Failed to decompress response data", e); + } + } return responseData; } @@ -803,12 +816,12 @@ public byte[] getResponseData() { public String getResponseDataAsString() { try { if(responseDataAsString == null) { - responseDataAsString= new String(responseData,getDataEncodingWithDefault()); + responseDataAsString= new String(getResponseData(),getDataEncodingWithDefault()); } return responseDataAsString; } catch (UnsupportedEncodingException e) { log.warn("Using platform default as {} caused {}", getDataEncodingWithDefault(), e.getLocalizedMessage()); - return new String(responseData,Charset.defaultCharset()); // N.B. default charset is used deliberately here + return new String(getResponseData(),Charset.defaultCharset()); // N.B. default charset is used deliberately here } } @@ -1666,4 +1679,15 @@ public TestLogicalAction getTestLogicalAction() { public void setTestLogicalAction(TestLogicalAction testLogicalAction) { this.testLogicalAction = testLogicalAction; } + + /** + * Sets the response data and its contentEncoding. + * @param data The response data + * @param contentEncoding The content contentEncoding (e.g. gzip, deflate) + */ + public void setResponseData(byte[] data, String contentEncoding) { + responseData = data == null ? EMPTY_BA : data; + this.contentEncoding = contentEncoding; + responseDataAsString = null; + } } diff --git a/src/core/src/main/kotlin/org/apache/jmeter/samplers/ResponseDecoder.kt b/src/core/src/main/kotlin/org/apache/jmeter/samplers/ResponseDecoder.kt new file mode 100644 index 00000000000..98954392729 --- /dev/null +++ b/src/core/src/main/kotlin/org/apache/jmeter/samplers/ResponseDecoder.kt @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.samplers + +import org.apache.jorphan.io.DirectAccessByteArrayOutputStream +import org.apache.jorphan.reflect.JMeterService +import org.apiguardian.api.API +import java.io.ByteArrayInputStream +import java.io.InputStream + +/** + * Interface for response data decoders that handle different content encodings. + * Implementations can be automatically discovered via [java.util.ServiceLoader]. + * + * To add a custom decoder: + * 1. Implement this interface + * 2. Create `META-INF/services/org.apache.jmeter.samplers.ResponseDecoder` file + * 4. Add your implementation's fully qualified class name to the file + * + * Example decoders: gzip, deflate, brotli + * + * @since 6.0.0 + */ +@JMeterService +@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public interface ResponseDecoder { + + /** + * Returns the content encodings handled by this decoder. + * These should match Content-Encoding header values (case-insensitive). + * + * A decoder can handle multiple encoding names (e.g., "gzip" and "x-gzip"). + * + * Examples: ["gzip", "x-gzip"], ["deflate"], ["br"] + * + * @return list of encoding names this decoder handles (must not be null or empty) + */ + public val encodings: List + + /** + * Decodes (decompresses) the given compressed data. + * + * @param compressed the compressed data to decode + * @return the decompressed data + * @throws java.io.IOException if decompression fails + */ + public fun decode(compressed: ByteArray): ByteArray { + val out = DirectAccessByteArrayOutputStream() + decodeStream(ByteArrayInputStream(compressed)).use { + it.transferTo(out) + } + return out.toByteArray() + } + + /** + * Creates a decompressing InputStream that wraps the given compressed input stream. + * This allows streaming decompression without buffering the entire response in memory. + * + * Used for scenarios like MD5 computation on decompressed data, where we want to + * compute the hash on-the-fly without storing the entire decompressed response. + * + * @param input the compressed input stream to wrap + * @return an InputStream that decompresses data as it's read + * @throws java.io.IOException if the decompressing stream cannot be created + */ + public fun decodeStream(input: InputStream): InputStream + + /** + * Returns the priority of this decoder. + * When multiple decoders are registered for the same encoding, + * the one with the highest priority is used. + * + * Default priority is 0. Built-in decoders use priority 0. + * Plugins can override built-in decoders by returning a higher priority. + * + * @return priority value (higher = preferred), default is 0 + */ + public val priority: Int + get() = 0 +} diff --git a/src/core/src/main/kotlin/org/apache/jmeter/samplers/ResponseDecoderRegistry.kt b/src/core/src/main/kotlin/org/apache/jmeter/samplers/ResponseDecoderRegistry.kt new file mode 100644 index 00000000000..f7fb3d22cb9 --- /dev/null +++ b/src/core/src/main/kotlin/org/apache/jmeter/samplers/ResponseDecoderRegistry.kt @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.samplers + +import org.apache.jmeter.samplers.decoders.DeflateDecoder +import org.apache.jmeter.samplers.decoders.GzipDecoder +import org.apache.jmeter.util.JMeterUtils +import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler +import org.apiguardian.api.API +import org.slf4j.LoggerFactory +import java.io.IOException +import java.io.InputStream +import java.util.Locale +import java.util.ServiceLoader +import java.util.concurrent.ConcurrentHashMap + +/** + * Registry for [ResponseDecoder] implementations. + * Provides centralized management of response decoders for different content encodings. + * + * Decoders are discovered via: + * - Built-in decoders (gzip, deflate) + * - ServiceLoader mechanism (META-INF/services) + * + * Thread-safe singleton registry. + * + * @since 6.0.0 + */ +@API(status = API.Status.EXPERIMENTAL, since = "6.0.0") +public object ResponseDecoderRegistry { + + private val log = LoggerFactory.getLogger(ResponseDecoderRegistry::class.java) + + /** + * Map of encoding name (lowercase) to decoder implementation. + * Uses ConcurrentHashMap for thread-safe access. + */ + private val decoders = ConcurrentHashMap() + + init { + // Register built-in decoders, this ensures the decoders are there even if service registration fails + registerDecoder(GzipDecoder()) + registerDecoder(DeflateDecoder()) + + // Load decoders via ServiceLoader + loadServiceLoaderDecoders() + } + + /** + * Loads decoders using ServiceLoader mechanism. + */ + private fun loadServiceLoaderDecoders() { + try { + JMeterUtils.loadServicesAndScanJars( + ResponseDecoder::class.java, + ServiceLoader.load(ResponseDecoder::class.java), + Thread.currentThread().contextClassLoader, + LogAndIgnoreServiceLoadExceptionHandler(log) + ).forEach { registerDecoder(it) } + } catch (e: Exception) { + log.error("Error loading ResponseDecoder services", e) + } + } + + /** + * Registers a decoder for all its encoding types. + * If a decoder already exists for an encoding, the one with higher priority is kept. + * + * @param decoder the decoder to register + */ + @JvmStatic + public fun registerDecoder(decoder: ResponseDecoder) { + val encodings = decoder.encodings + if (encodings.isEmpty()) { + log.warn("Decoder {} has null or empty encodings list, skipping registration", decoder.javaClass.name) + return + } + + for (encoding in encodings) { + val key = encoding.lowercase(Locale.ROOT) + + decoders.merge(key, decoder) { existing, newDecoder -> + // Keep the decoder with higher priority + if (newDecoder.priority > existing.priority) { + log.info( + "Replacing decoder for '{}': {} (priority {}) -> {} (priority {})", + encoding, + existing.javaClass.simpleName, existing.priority, + newDecoder.javaClass.simpleName, newDecoder.priority + ) + newDecoder + } else { + log.debug( + "Keeping existing decoder for '{}': {} (priority {}) over {} (priority {})", + encoding, + existing.javaClass.simpleName, existing.priority, + newDecoder.javaClass.simpleName, newDecoder.priority + ) + existing + } + } + } + } + + /** + * Decodes the given data using the decoder registered for the specified encoding. + * If no decoder is found for the encoding, returns the data unchanged. + * + * @param encoding the content encoding (e.g., "gzip", "deflate", "br") + * @param data the data to decode + * @return decoded data, or original data if no decoder found or encoding is null + * @throws IOException if decoding fails + */ + @JvmStatic + @Throws(IOException::class) + public fun decode(encoding: String?, data: ByteArray?): ByteArray { + if (encoding.isNullOrEmpty() || data == null || data.isEmpty()) { + return data ?: ByteArray(0) + } + + val decoder = decoders[encoding] ?: decoders[encoding.lowercase(Locale.ROOT)] + + if (decoder == null) { + log.debug("No decoder found for encoding '{}', returning data unchanged", encoding) + return data + } + + return decoder.decode(data) + } + + /** + * Creates a decompressing InputStream that wraps the given input stream using the decoder + * registered for the specified encoding. + * + * This enables streaming decompression without buffering the entire response in memory, + * which is useful for computing checksums on decompressed data or processing large responses. + * + * If no decoder is found for the encoding, returns the original input stream unchanged. + * + * @param encoding the content encoding (e.g., "gzip", "deflate", "br") + * @param input the input stream to wrap with decompression + * @return a decompressing InputStream, or the original stream if no decoder found or encoding is null + * @throws IOException if the decompressing stream cannot be created + * @since 6.0.0 + */ + @JvmStatic + @Throws(IOException::class) + public fun decodeStream(encoding: String?, input: InputStream): InputStream { + if (encoding.isNullOrEmpty()) { + return input + } + + val decoder = decoders[encoding] ?: decoders[encoding.lowercase(Locale.ROOT)] + + if (decoder == null) { + log.debug("No decoder found for encoding '{}', returning input stream unchanged", encoding) + return input + } + + return decoder.decodeStream(input) + } + + /** + * Checks if a decoder is registered for the given encoding. + * Primarily for testing purposes. + * + * @param encoding the encoding to check + * @return true if a decoder is registered for this encoding + */ + @JvmStatic + public fun hasDecoder(encoding: String): Boolean = + decoders.containsKey(encoding.lowercase(Locale.ROOT)) +} diff --git a/src/core/src/main/kotlin/org/apache/jmeter/samplers/decoders/DeflateDecoder.kt b/src/core/src/main/kotlin/org/apache/jmeter/samplers/decoders/DeflateDecoder.kt new file mode 100644 index 00000000000..db2653bb2ae --- /dev/null +++ b/src/core/src/main/kotlin/org/apache/jmeter/samplers/decoders/DeflateDecoder.kt @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.samplers.decoders + +import org.apache.jmeter.samplers.ResponseDecoder +import org.apache.jorphan.io.DirectAccessByteArrayOutputStream +import org.apiguardian.api.API +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import java.util.zip.Inflater +import java.util.zip.InflaterInputStream + +/** + * Decoder for deflate compressed response data. + * Attempts decompression with ZLIB wrapper first, falls back to raw DEFLATE if that fails. + * + * @since 6.0.0 + */ +@API(status = API.Status.INTERNAL, since = "6.0.0") +public class DeflateDecoder : ResponseDecoder { + override val encodings: List + get() = listOf("deflate") + + override fun decode(compressed: ByteArray): ByteArray { + // Try with ZLIB wrapper first + return try { + decompressWithInflater(compressed, nowrap = false) + } catch (e: IOException) { + // If that fails, try with NO_WRAP for raw DEFLATE + decompressWithInflater(compressed, nowrap = true) + } + } + + override fun decodeStream(input: InputStream): InputStream { + // For streaming, use ZLIB wrapper (nowrap=false) which is the most common case. + // The fallback to raw DEFLATE is only available in the byte array version + // since we cannot retry with a stream without buffering it first. + return InflaterInputStream(input, Inflater(false)) + } + + /** + * Decompresses data using Inflater with specified nowrap setting. + * + * @param compressed the compressed data + * @param nowrap if true, uses raw DEFLATE (no ZLIB wrapper) + * @return decompressed data + * @throws IOException if decompression fails + */ + private fun decompressWithInflater(compressed: ByteArray, nowrap: Boolean): ByteArray { + val out = DirectAccessByteArrayOutputStream() + InflaterInputStream(ByteArrayInputStream(compressed), Inflater(nowrap)).use { + it.transferTo(out) + } + return out.toByteArray() + } +} diff --git a/src/core/src/main/kotlin/org/apache/jmeter/samplers/decoders/GzipDecoder.kt b/src/core/src/main/kotlin/org/apache/jmeter/samplers/decoders/GzipDecoder.kt new file mode 100644 index 00000000000..b636f48fd80 --- /dev/null +++ b/src/core/src/main/kotlin/org/apache/jmeter/samplers/decoders/GzipDecoder.kt @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.samplers.decoders + +import org.apache.jmeter.samplers.ResponseDecoder +import org.apiguardian.api.API +import java.io.InputStream +import java.util.zip.GZIPInputStream + +/** + * Decoder for gzip compressed response data. + * Handles both "gzip" and "x-gzip" content encodings. + * + * @since 6.0.0 + */ +@API(status = API.Status.INTERNAL, since = "6.0.0") +public class GzipDecoder : ResponseDecoder { + override val encodings: List + get() = listOf("gzip", "x-gzip") + + override fun decodeStream(input: InputStream): InputStream { + return GZIPInputStream(input) + } +} diff --git a/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties b/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties index dc34c8f41cb..5791663bd1e 100644 --- a/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties +++ b/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties @@ -1002,6 +1002,13 @@ reportgenerator_summary_total=Total request_data=Request Data reset=Reset response_save_as_md5=Save response as MD5 hash? +expression_mode_button_tooltip=Switch to expression mode to use variables like ${__P(property)} +response_processing_title=Response Processing +response_processing_mode=Processing mode\: +response_processing_store_compressed=Store response (decompress on access) +response_processing_fetch_discard=Fetch and discard (headers only) +response_processing_checksum_encoded_md5=Checksum (MD5 of compressed) +response_processing_checksum_decoded_md5=Checksum (MD5 of decompressed) response_time_distribution_satisfied_label=Requests having \nresponse time <= {0}ms response_time_distribution_tolerated_label= Requests having \nresponse time > {0}ms and <= {1}ms response_time_distribution_untolerated_label=Requests having \nresponse time > {0}ms diff --git a/src/core/src/test/kotlin/org/apache/jmeter/samplers/ResponseDecoderRegistryTest.kt b/src/core/src/test/kotlin/org/apache/jmeter/samplers/ResponseDecoderRegistryTest.kt new file mode 100644 index 00000000000..441f61b818e --- /dev/null +++ b/src/core/src/test/kotlin/org/apache/jmeter/samplers/ResponseDecoderRegistryTest.kt @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.samplers + +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.util.zip.GZIPOutputStream + +class ResponseDecoderRegistryTest { + @Test + fun testBuiltInDecodersAreRegistered() { + assertTrue(ResponseDecoderRegistry.hasDecoder("gzip"), "gzip decoder should be registered") + assertTrue(ResponseDecoderRegistry.hasDecoder("x-gzip"), "x-gzip decoder should be registered") + assertTrue(ResponseDecoderRegistry.hasDecoder("deflate"), "deflate decoder should be registered") + } + + @Test + fun testDecodeWithGzip() { + val originalText = "Hello, World! This is a test of gzip compression." + val originalData = originalText.toByteArray(Charsets.UTF_8) + + // Compress with gzip + val compressed = compressGzip(originalData) + + // Decode using registry + val decoded = ResponseDecoderRegistry.decode("gzip", compressed) + + assertArrayEquals(originalData, decoded, "Decoded data should match original") + } + + @Test + fun testDecodeWithXGzip() { + val originalText = "Testing x-gzip encoding" + val originalData = originalText.toByteArray(Charsets.UTF_8) + + // Compress with gzip (x-gzip uses same compression) + val compressed = compressGzip(originalData) + + // Decode using registry with x-gzip encoding + val decoded = ResponseDecoderRegistry.decode("x-gzip", compressed) + + assertArrayEquals(originalData, decoded, "Decoded data should match original for x-gzip") + } + + @Test + fun testDecodeWithUnknownEncoding() { + val originalData = "Test data".toByteArray(Charsets.UTF_8) + + // Decode with unknown encoding should return original data + val result = ResponseDecoderRegistry.decode("unknown-encoding", originalData) + + assertArrayEquals(originalData, result, "Unknown encoding should return data unchanged") + } + + @Test + fun testDecodeWithNullEncoding() { + val originalData = "Test data".toByteArray(Charsets.UTF_8) + + // Decode with null encoding should return original data + val result = ResponseDecoderRegistry.decode(null, originalData) + + assertArrayEquals(originalData, result, "Null encoding should return data unchanged") + } + + @Test + fun testDecodeWithEmptyData() { + val emptyData = ByteArray(0) + + val result = ResponseDecoderRegistry.decode("gzip", emptyData) + + assertArrayEquals(emptyData, result, "Empty data should return empty data") + } + + @Test + fun testCaseInsensitiveEncoding() { + val originalText = "Case insensitive test" + val originalData = originalText.toByteArray(Charsets.UTF_8) + val compressed = compressGzip(originalData) + + // Test various case combinations + val decoded1 = ResponseDecoderRegistry.decode("GZIP", compressed) + val decoded2 = ResponseDecoderRegistry.decode("GZip", compressed) + val decoded3 = ResponseDecoderRegistry.decode("gzip", compressed) + + assertArrayEquals(originalData, decoded1, "GZIP should decode correctly") + assertArrayEquals(originalData, decoded2, "GZip should decode correctly") + assertArrayEquals(originalData, decoded3, "gzip should decode correctly") + } + + @Test + fun testRegisterCustomDecoder() { + // Create a custom decoder that reverses bytes (for testing) + val reverseDecoder = object : ResponseDecoder { + override val encodings: List + get() = listOf("test-reverse") + + override fun decode(compressed: ByteArray): ByteArray = + compressed.reversedArray() + + override fun decodeStream(input: InputStream): InputStream { + TODO("Not yet implemented") + } + } + + ResponseDecoderRegistry.registerDecoder(reverseDecoder) + + val data = "ABC".toByteArray(Charsets.UTF_8) + val decoded = ResponseDecoderRegistry.decode("test-reverse", data) + + assertEquals("CBA", decoded.toString(Charsets.UTF_8), "Custom decoder should reverse bytes") + } + + @Test + fun testDecoderPriority() { + // Register a low priority decoder + val lowPriorityDecoder = object : ResponseDecoder { + override val encodings: List + get() = listOf("priority-test") + + override fun decode(compressed: ByteArray): ByteArray = + "low".toByteArray(Charsets.UTF_8) + + override fun decodeStream(input: InputStream): InputStream { + TODO("Not yet implemented") + } + + override val priority: Int + get() = 1 + } + + // Register a high priority decoder for same encoding + val highPriorityDecoder = object : ResponseDecoder { + override val encodings: List + get() = listOf("priority-test") + + override fun decode(compressed: ByteArray): ByteArray = + "high".toByteArray(Charsets.UTF_8) + + override fun decodeStream(input: InputStream): InputStream { + TODO("Not yet implemented") + } + + override val priority: Int + get() = 10 + } + + ResponseDecoderRegistry.registerDecoder(lowPriorityDecoder) + ResponseDecoderRegistry.registerDecoder(highPriorityDecoder) + + val result = ResponseDecoderRegistry.decode("priority-test", "test".toByteArray(Charsets.UTF_8)) + + assertEquals("high", result.toString(Charsets.UTF_8), "Higher priority decoder should be used") + } + + /** + * Helper method to compress data with gzip + */ + private fun compressGzip(data: ByteArray): ByteArray { + val baos = ByteArrayOutputStream() + GZIPOutputStream(baos).use { gzipOut -> + gzipOut.write(data) + } + return baos.toByteArray() + } +} diff --git a/src/core/src/test/kotlin/org/apache/jmeter/samplers/decoders/DeflateDecoderTest.kt b/src/core/src/test/kotlin/org/apache/jmeter/samplers/decoders/DeflateDecoderTest.kt new file mode 100644 index 00000000000..add44e26304 --- /dev/null +++ b/src/core/src/test/kotlin/org/apache/jmeter/samplers/decoders/DeflateDecoderTest.kt @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.samplers.decoders + +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.io.ByteArrayOutputStream +import java.util.zip.Deflater +import java.util.zip.DeflaterOutputStream + +class DeflateDecoderTest { + private val decoder = DeflateDecoder() + + @Test + fun testGetEncodings() { + assertEquals(listOf("deflate"), decoder.encodings, "encodings") + } + + @Test + fun testGetPriority() { + assertEquals(0, decoder.priority, "Default priority should be 0") + } + + @Test + fun testDecodeDeflateWithZlibWrapper() { + val originalText = "Hello, World! This is a test message for deflate compression with ZLIB wrapper." + val originalData = originalText.toByteArray(Charsets.UTF_8) + + // Compress with ZLIB wrapper (default) + val compressed = compressDeflate(originalData, nowrap = false) + + // Decode + val decoded = decoder.decode(compressed) + + assertArrayEquals(originalData, decoded, "Decoded data should match original (ZLIB wrapper)") + } + + @Test + fun testDecodeDeflateRaw() { + val originalText = "Testing raw deflate without ZLIB wrapper." + val originalData = originalText.toByteArray(Charsets.UTF_8) + + // Compress with NO_WRAP (raw deflate) + val compressed = compressDeflate(originalData, nowrap = true) + + // Decode - should fallback to raw deflate + val decoded = decoder.decode(compressed) + + assertArrayEquals(originalData, decoded, "Decoded data should match original (raw deflate)") + } + + @Test + fun testDecodeEmptyData() { + val emptyCompressed = compressDeflate(ByteArray(0), nowrap = false) + val decoded = decoder.decode(emptyCompressed) + + assertEquals(0, decoded.size, "Empty data should decode to empty array") + } + + /** + * Helper method to compress data with deflate + * @param data the data to compress + * @param nowrap if true, uses raw deflate (no ZLIB wrapper) + */ + private fun compressDeflate(data: ByteArray, nowrap: Boolean): ByteArray { + val baos = ByteArrayOutputStream() + DeflaterOutputStream(baos, Deflater(Deflater.DEFAULT_COMPRESSION, nowrap)).use { deflaterOut -> + deflaterOut.write(data) + } + return baos.toByteArray() + } +} diff --git a/src/core/src/test/kotlin/org/apache/jmeter/samplers/decoders/GzipDecoderTest.kt b/src/core/src/test/kotlin/org/apache/jmeter/samplers/decoders/GzipDecoderTest.kt new file mode 100644 index 00000000000..9be1f83cbae --- /dev/null +++ b/src/core/src/test/kotlin/org/apache/jmeter/samplers/decoders/GzipDecoderTest.kt @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.samplers.decoders + +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import java.io.ByteArrayOutputStream +import java.util.zip.GZIPOutputStream + +class GzipDecoderTest { + private val decoder = GzipDecoder() + + @Test + fun testGetEncodings() { + assertEquals(listOf("gzip", "x-gzip"), decoder.encodings, "encodings") + } + + @Test + fun testGetPriority() { + assertEquals(0, decoder.priority, "Default priority should be 0") + } + + @Test + fun testDecodeGzipData() { + val originalText = "Hello, World! This is a test message for gzip compression." + val originalData = originalText.toByteArray(Charsets.UTF_8) + + // Compress data with gzip + val compressed = compressGzip(originalData) + + // Decode + val decoded = decoder.decode(compressed) + + assertArrayEquals(originalData, decoded, "Decoded data should match original") + assertEquals(originalText, decoded.toString(Charsets.UTF_8), "Decoded text should match original") + } + + @Test + fun testDecodeEmptyData() { + val emptyCompressed = compressGzip(ByteArray(0)) + val decoded = decoder.decode(emptyCompressed) + + assertEquals(0, decoded.size, "Empty data should decode to empty array") + } + + @Test + fun testDecodeInvalidData() { + val invalidData = "This is not gzip compressed data".toByteArray(Charsets.UTF_8) + + assertThrows(Exception::class.java) { + decoder.decode(invalidData) + } + } + + /** + * Helper method to compress data with gzip + */ + private fun compressGzip(data: ByteArray): ByteArray { + val baos = ByteArrayOutputStream() + GZIPOutputStream(baos).use { gzipOut -> + gzipOut.write(data) + } + return baos.toByteArray() + } +} diff --git a/src/protocol/http/build.gradle.kts b/src/protocol/http/build.gradle.kts index af6b3482f5d..7f2d78fa474 100644 --- a/src/protocol/http/build.gradle.kts +++ b/src/protocol/http/build.gradle.kts @@ -63,10 +63,12 @@ dependencies { implementation("dnsjava:dnsjava") implementation("org.apache.httpcomponents:httpmime") implementation("org.apache.httpcomponents:httpcore") - implementation("org.brotli:dec") implementation("com.miglayout:miglayout-swing") implementation("com.fasterxml.jackson.core:jackson-core") implementation("com.fasterxml.jackson.core:jackson-databind") + implementation("org.brotli:dec") { + because("BrotliDecoder for HTTP response decompression") + } testImplementation(testFixtures(projects.src.core)) testImplementation(testFixtures(projects.src.testkitWiremock)) testImplementation("org.wiremock:wiremock") diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java index c056b8a8f02..ae8a93272be 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java @@ -33,6 +33,7 @@ import org.apache.jmeter.config.gui.AbstractConfigGui; import org.apache.jmeter.gui.GUIMenuSortOrder; import org.apache.jmeter.gui.JBooleanPropertyEditor; +import org.apache.jmeter.gui.JEnumPropertyEditor; import org.apache.jmeter.gui.JTextComponentBinding; import org.apache.jmeter.gui.TestElementMetadata; import org.apache.jmeter.gui.util.HorizontalPanel; @@ -68,10 +69,13 @@ public class HttpDefaultsGui extends AbstractConfigGui { "web_testing_concurrent_download", JMeterUtils::getResString); private JTextField concurrentPool; - private final JBooleanPropertyEditor useMD5 = new JBooleanPropertyEditor( - HTTPSamplerBaseSchema.INSTANCE.getStoreAsMD5(), - "response_save_as_md5", - JMeterUtils::getResString); // $NON-NLS-1$ + private final JEnumPropertyEditor responseProcessingMode = + new JEnumPropertyEditor<>( + HTTPSamplerBaseSchema.INSTANCE.getResponseProcessingMode(), + "response_processing_mode", + HTTPSamplerBase.ResponseProcessingMode.class, + JMeterUtils::getResString + ); private JTextField embeddedAllowRE; // regular expression used to match against embedded resource URLs to allow private JTextField embeddedExcludeRE; // regular expression used to match against embedded resource URLs to discard private JTextField sourceIpAddr; // does not apply to Java implementation @@ -94,7 +98,7 @@ public HttpDefaultsGui() { retrieveEmbeddedResources, concurrentDwn, new JTextComponentBinding(concurrentPool, schema.getConcurrentDownloadPoolSize()), - useMD5, + responseProcessingMode, new JTextComponentBinding(embeddedAllowRE, schema.getEmbeddedUrlAllowRegex()), new JTextComponentBinding(embeddedExcludeRE, schema.getEmbeddedUrlExcludeRegex()), new JTextComponentBinding(sourceIpAddr, schema.getIpSource()), @@ -191,7 +195,7 @@ private void init() { // WARNING: called from ctor so must not be overridden (i. advancedPanel.add(createEmbeddedRsrcPanel()); advancedPanel.add(createSourceAddrPanel()); advancedPanel.add(getProxyServerPanel()); - advancedPanel.add(createOptionalTasksPanel()); + advancedPanel.add(createResponseProcessingPanel()); JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.add(JMeterUtils @@ -297,13 +301,12 @@ protected JPanel createSourceAddrPanel() { return sourceAddrPanel; } - protected JPanel createOptionalTasksPanel() { - // OPTIONAL TASKS - final JPanel checkBoxPanel = new VerticalPanel(); - checkBoxPanel.setBorder(BorderFactory.createTitledBorder( - JMeterUtils.getResString("optional_tasks"))); // $NON-NLS-1$ - checkBoxPanel.add(useMD5); - return checkBoxPanel; + protected JPanel createResponseProcessingPanel() { + JPanel panel = new JPanel(new MigLayout()); + panel.setBorder(BorderFactory.createTitledBorder( + JMeterUtils.getResString("response_processing_title"))); // $NON-NLS-1$ + panel.add(responseProcessingMode); + return panel; } @Override diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java index 88da93fb9d6..c195930cf76 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java @@ -32,6 +32,7 @@ import org.apache.jmeter.gui.GUIMenuSortOrder; import org.apache.jmeter.gui.JBooleanPropertyEditor; +import org.apache.jmeter.gui.JEnumPropertyEditor; import org.apache.jmeter.gui.JTextComponentBinding; import org.apache.jmeter.gui.TestElementMetadata; import org.apache.jmeter.gui.util.HorizontalPanel; @@ -70,10 +71,13 @@ public class HttpTestSampleGui extends AbstractSamplerGui { "web_testing_concurrent_download", JMeterUtils::getResString); private JTextField concurrentPool; - private final JBooleanPropertyEditor useMD5 = new JBooleanPropertyEditor( - HTTPSamplerBaseSchema.INSTANCE.getStoreAsMD5(), - "response_save_as_md5", - JMeterUtils::getResString); + private final JEnumPropertyEditor responseProcessingMode = + new JEnumPropertyEditor<>( + HTTPSamplerBaseSchema.INSTANCE.getResponseProcessingMode(), + "response_processing_mode", + HTTPSamplerBase.ResponseProcessingMode.class, + JMeterUtils::getResString + ); private JTextField embeddedAllowRE; // regular expression used to match against embedded resource URLs to allow private JTextField embeddedExcludeRE; // regular expression used to match against embedded resource URLs to exclude private JTextField sourceIpAddr; // does not apply to Java implementation @@ -103,7 +107,7 @@ protected HttpTestSampleGui(boolean ajp) { retrieveEmbeddedResources, concurrentDwn, new JTextComponentBinding(concurrentPool, schema.getConcurrentDownloadPoolSize()), - useMD5, + responseProcessingMode, new JTextComponentBinding(embeddedAllowRE, schema.getEmbeddedUrlAllowRegex()), new JTextComponentBinding(embeddedExcludeRE, schema.getEmbeddedUrlExcludeRegex()) ) @@ -260,7 +264,7 @@ private JPanel createAdvancedConfigPanel() { advancedPanel.add(getProxyServerPanel()); } - advancedPanel.add(createOptionalTasksPanel()); + advancedPanel.add(createResponseProcessingPanel()); return advancedPanel; } @@ -355,15 +359,12 @@ protected final JPanel getImplementationPanel(){ return implPanel; } - protected JPanel createOptionalTasksPanel() { - // OPTIONAL TASKS - final JPanel checkBoxPanel = new VerticalPanel(); - checkBoxPanel.setBorder(BorderFactory.createTitledBorder( - JMeterUtils.getResString("optional_tasks"))); // $NON-NLS-1$ - - checkBoxPanel.add(useMD5); - - return checkBoxPanel; + protected JPanel createResponseProcessingPanel() { + JPanel panel = new JPanel(new MigLayout()); + panel.setBorder(BorderFactory.createTitledBorder( + JMeterUtils.getResString("response_processing_title"))); // $NON-NLS-1$ + panel.add(responseProcessingMode, "span"); + return panel; } @SuppressWarnings("EnumOrdinal") diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPAbstractImpl.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPAbstractImpl.java index 9c724a01727..28546e6186c 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPAbstractImpl.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPAbstractImpl.java @@ -17,7 +17,6 @@ package org.apache.jmeter.protocol.http.sampler; -import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.Inet4Address; @@ -28,6 +27,7 @@ import java.net.SocketException; import java.net.URL; import java.net.UnknownHostException; +import java.util.List; import java.util.function.Predicate; import org.apache.jmeter.config.Arguments; @@ -42,6 +42,8 @@ import org.apache.jmeter.samplers.Interruptible; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.EnumUtils; +import org.jspecify.annotations.Nullable; /** * Base class for HTTP implementations used by the HTTPSamplerProxy sampler. @@ -230,12 +232,12 @@ protected String getIpSource() { * @throws UnknownHostException if the hostname/ip for {@link #getIpSource()} could not be resolved or not interface was found for it * @throws SocketException if an I/O error occurs */ - @SuppressWarnings("EnumOrdinal") protected InetAddress getIpSourceAddress() throws UnknownHostException, SocketException { final String ipSource = getIpSource(); if (!ipSource.isBlank()) { Class ipClass = null; - final SourceType sourceType = HTTPSamplerBase.SourceType.values()[testElement.getIpSourceType()]; + List sourceTypes = EnumUtils.getEnumValues(SourceType.class); + final SourceType sourceType = sourceTypes.get(testElement.getIpSourceType()); switch (sourceType) { case DEVICE -> ipClass = InetAddress.class; case DEVICE_IPV4 -> ipClass = Inet4Address.class; @@ -428,7 +430,7 @@ protected boolean isSuccessCode(int errorLevel) { * Closes the inputStream *

    * Invokes - * {@link HTTPSamplerBase#readResponse(SampleResult, InputStream, long)} + * {@link HTTPSamplerBase#readResponse(SampleResult, InputStream, long, String)} * * @param res * sample to store information about the response into @@ -436,93 +438,12 @@ protected boolean isSuccessCode(int errorLevel) { * input stream from which to read the response * @param responseContentLength * expected input length or zero - * @return the response or the MD5 of the response * @throws IOException * if reading the result fails */ - protected byte[] readResponse(SampleResult res, InputStream instream, - int responseContentLength) throws IOException { - return readResponse(res, instream, (long)responseContentLength); - } - /** - * Read response from the input stream, converting to MD5 digest if the - * useMD5 property is set. - *

    - * For the MD5 case, the result byte count is set to the size of the - * original response. - *

    - * Closes the inputStream - *

    - * Invokes - * {@link HTTPSamplerBase#readResponse(SampleResult, InputStream, long)} - * - * @param res - * sample to store information about the response into - * @param instream - * input stream from which to read the response - * @param responseContentLength - * expected input length or zero - * @return the response or the MD5 of the response - * @throws IOException - * if reading the result fails - */ - protected byte[] readResponse(SampleResult res, InputStream instream, - long responseContentLength) throws IOException { - return testElement.readResponse(res, instream, responseContentLength); - } - - /** - * Read response from the input stream, converting to MD5 digest if the - * useMD5 property is set. - *

    - * For the MD5 case, the result byte count is set to the size of the - * original response. - *

    - * Closes the inputStream - *

    - * Invokes {@link HTTPSamplerBase#readResponse(SampleResult, InputStream, long)} - * - * @param res - * sample to store information about the response into - * @param in - * input stream from which to read the response - * @param contentLength - * expected input length or zero - * @return the response or the MD5 of the response - * @throws IOException - * when reading the result fails - * @deprecated use {@link HTTPAbstractImpl#readResponse(SampleResult, BufferedInputStream, long)} - */ - @Deprecated - protected byte[] readResponse(SampleResult res, BufferedInputStream in, - int contentLength) throws IOException { - return testElement.readResponse(res, in, contentLength); - } - - /** - * Read response from the input stream, converting to MD5 digest if the - * useMD5 property is set. - *

    - * For the MD5 case, the result byte count is set to the size of the - * original response. - *

    - * Closes the inputStream - *

    - * Invokes {@link HTTPSamplerBase#readResponse(SampleResult, InputStream, long)} - * - * @param res - * sample to store information about the response into - * @param in - * input stream from which to read the response - * @param contentLength - * expected input length or zero - * @return the response or the MD5 of the response - * @throws IOException - * when reading the result fails - */ - protected byte[] readResponse(SampleResult res, BufferedInputStream in, - long contentLength) throws IOException { - return testElement.readResponse(res, in, contentLength); + protected void readResponse(SampleResult res, InputStream instream, + long responseContentLength, @Nullable String contentEncoding) throws IOException { + testElement.readResponse(res, instream, responseContentLength, contentEncoding); } /** diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java index 569bb164b6a..11db6bcb0e9 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java @@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.InetAddress; @@ -55,7 +56,6 @@ import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; -import org.apache.http.HttpResponseInterceptor; import org.apache.http.NameValuePair; import org.apache.http.StatusLine; import org.apache.http.auth.AuthSchemeProvider; @@ -70,7 +70,6 @@ import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.entity.InputStreamFactory; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; @@ -84,7 +83,6 @@ import org.apache.http.client.methods.HttpTrace; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.client.protocol.ResponseContentEncoding; import org.apache.http.config.Lookup; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; @@ -146,8 +144,6 @@ import org.apache.jmeter.protocol.http.control.DynamicKerberosSchemeFactory; import org.apache.jmeter.protocol.http.control.DynamicSPNegoSchemeFactory; import org.apache.jmeter.protocol.http.control.HeaderManager; -import org.apache.jmeter.protocol.http.sampler.hc.LaxDeflateInputStream; -import org.apache.jmeter.protocol.http.sampler.hc.LaxGZIPInputStream; import org.apache.jmeter.protocol.http.sampler.hc.LazyLayeredConnectionSocketFactory; import org.apache.jmeter.protocol.http.util.ConversionUtils; import org.apache.jmeter.protocol.http.util.HTTPArgument; @@ -165,7 +161,6 @@ import org.apache.jmeter.util.SSLManager; import org.apache.jorphan.util.JOrphanUtils; import org.apache.jorphan.util.StringUtilities; -import org.brotli.dec.BrotliInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -194,20 +189,8 @@ public class HTTPHC4Impl extends HTTPHCAbstractImpl { private static final boolean DISABLE_DEFAULT_UA = JMeterUtils.getPropDefault("httpclient4.default_user_agent_disabled", false); - private static final boolean GZIP_RELAX_MODE = JMeterUtils.getPropDefault("httpclient4.gzip_relax_mode", false); - - private static final boolean DEFLATE_RELAX_MODE = JMeterUtils.getPropDefault("httpclient4.deflate_relax_mode", false); - private static final Logger log = LoggerFactory.getLogger(HTTPHC4Impl.class); - private static final InputStreamFactory GZIP = - instream -> new LaxGZIPInputStream(instream, GZIP_RELAX_MODE); - - private static final InputStreamFactory DEFLATE = - instream -> new LaxDeflateInputStream(instream, DEFLATE_RELAX_MODE); - - private static final InputStreamFactory BROTLI = BrotliInputStream::new; - private static final class ManagedCredentialsProvider implements CredentialsProvider { private final AuthManager authManager; private final Credentials proxyCredentials; @@ -464,55 +447,6 @@ protected HttpResponse doSendRequest( } }; - private static final String[] HEADERS_TO_SAVE = new String[]{ - "content-length", - "content-encoding", - "content-md5" - }; - - /** - * Custom implementation that backups headers related to Compressed responses - * that HC core {@link ResponseContentEncoding} removes after uncompressing - * See Bug 59401 - */ - @SuppressWarnings("UnnecessaryAnonymousClass") - private static final HttpResponseInterceptor RESPONSE_CONTENT_ENCODING = new ResponseContentEncoding(createLookupRegistry()) { - @Override - public void process(HttpResponse response, HttpContext context) - throws HttpException, IOException { - ArrayList headersToSave = null; - - final HttpEntity entity = response.getEntity(); - final HttpClientContext clientContext = HttpClientContext.adapt(context); - final RequestConfig requestConfig = clientContext.getRequestConfig(); - // store the headers if necessary - if (requestConfig.isContentCompressionEnabled() && entity != null && entity.getContentLength() != 0) { - final Header ceheader = entity.getContentEncoding(); - if (ceheader != null) { - headersToSave = new ArrayList<>(3); - for(String name : HEADERS_TO_SAVE) { - Header[] hdr = response.getHeaders(name); // empty if none - headersToSave.add(hdr); - } - } - } - - // Now invoke original parent code - super.process(response, clientContext); - // Should this be in a finally ? - if(headersToSave != null) { - for (Header[] headers : headersToSave) { - for (Header headerToRestore : headers) { - if (response.containsHeader(headerToRestore.getName())) { - break; - } - response.addHeader(headerToRestore); - } - } - } - } - }; - /** * 1 HttpClient instance per combination of (HttpClient,HttpClientKey) */ @@ -549,19 +483,6 @@ protected HTTPHC4Impl(HTTPSamplerBase testElement) { super(testElement); } - /** - * Customize to plug Brotli - * @return {@link Lookup} - */ - private static Lookup createLookupRegistry() { - return - RegistryBuilder.create() - .register("br", BROTLI) - .register("gzip", GZIP) - .register("x-gzip", GZIP) - .register("deflate", DEFLATE).build(); - } - /** * Implementation that allows GET method to have a body */ @@ -665,8 +586,10 @@ protected HTTPSampleResult sample(URL url, String method, res.setEncodingAndType(ct); } HttpEntity entity = httpResponse.getEntity(); - if (entity != null) { - res.setResponseData(readResponse(res, entity.getContent(), entity.getContentLength())); + try (InputStream instream = entity.getContent()) { + Header contentEncodingHeader = entity.getContentEncoding(); + String contentEncoding = contentEncodingHeader != null ? contentEncodingHeader.getValue() : null; + readResponse(res, instream, entity.getContentLength(), contentEncoding); } res.sampleEnd(); // Done with the sampling proper. @@ -1147,7 +1070,7 @@ private HttpClientState setupClient(HttpClientKey key, JMeterVariables jMeterVar } builder.setDefaultCredentialsProvider(credsProvider); } - builder.disableContentCompression().addInterceptorLast(RESPONSE_CONTENT_ENCODING); + builder.disableContentCompression(); // Disable automatic decompression if(BASIC_AUTH_PREEMPTIVE) { builder.addInterceptorFirst(PREEMPTIVE_AUTH_INTERCEPTOR); } else { diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPJavaImpl.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPJavaImpl.java index 527ed485aad..f2505416101 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPJavaImpl.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPJavaImpl.java @@ -31,7 +31,6 @@ import java.util.List; import java.util.Map; import java.util.function.Predicate; -import java.util.zip.GZIPInputStream; import org.apache.jmeter.protocol.http.control.AuthManager; import org.apache.jmeter.protocol.http.control.Authorization; @@ -219,15 +218,11 @@ protected HttpURLConnection setupConnection(URL u, String method, HTTPSampleResu /** * Reads the response from the URL connection. * - * @param conn - * URL from which to read response - * @param res - * {@link SampleResult} to read response into - * @return response content - * @exception IOException - * if an I/O exception occurs + * @param res {@link SampleResult} to read response into + * @param conn URL from which to read response + * @throws IOException if an I/O exception occurs */ - protected byte[] readResponse(HttpURLConnection conn, SampleResult res) throws IOException { + protected void readResponse(SampleResult res, HttpURLConnection conn) throws IOException { InputStream in; final long contentLength = conn.getContentLength(); @@ -236,26 +231,19 @@ protected byte[] readResponse(HttpURLConnection conn, SampleResult res) throws I log.info("Content-Length: 0, not reading http-body"); res.setResponseHeaders(getResponseHeaders(conn)); res.latencyEnd(); - return NULL_BA; + res.setResponseData(NULL_BA); + return; } - // works OK even if ContentEncoding is null - boolean gzipped = HTTPConstants.ENCODING_GZIP.equals(conn.getContentEncoding()); - - CountingInputStream instream = null; + CountingInputStream counterStream = null; try { - instream = new CountingInputStream(conn.getInputStream()); - if (gzipped) { - in = new GZIPInputStream(instream); - } else { - in = instream; - } + counterStream = new CountingInputStream(conn.getInputStream()); + in = counterStream; } catch (IOException e) { - if (! (e.getCause() instanceof FileNotFoundException)) - { + if (!(e.getCause() instanceof FileNotFoundException)) { log.error("readResponse: {}", e.toString()); Throwable cause = e.getCause(); - if (cause != null){ + if (cause != null) { log.error("Cause: {}", cause.toString()); if(cause instanceof Error error) { throw error; @@ -270,36 +258,21 @@ protected byte[] readResponse(HttpURLConnection conn, SampleResult res) throws I } res.setResponseHeaders(getResponseHeaders(conn)); res.latencyEnd(); - return NULL_BA; + res.setResponseData(NULL_BA); + return; } if(log.isInfoEnabled()) { log.info("Error Response Code: {}", conn.getResponseCode()); } - if (gzipped) { - in = new GZIPInputStream(errorStream); - } else { - in = errorStream; - } - } catch (Exception e) { - log.error("readResponse: {}", e.toString()); - Throwable cause = e.getCause(); - if (cause != null){ - log.error("Cause: {}", cause.toString()); - if(cause instanceof Error error) { - throw error; - } - } - in = conn.getErrorStream(); + in = errorStream; } - // N.B. this closes 'in' - byte[] responseData = readResponse(res, in, contentLength); - if (instream != null) { - res.setBodySize(instream.getBytesRead()); - instream.close(); + + readResponse(res, in, contentLength, conn.getContentEncoding()); + if (counterStream != null) { + res.setBodySize(counterStream.getBytesRead()); } - return responseData; } /** @@ -565,15 +538,10 @@ protected HTTPSampleResult sample(URL url, String method, boolean areFollowingRe res.setQueryString(putBody); } // Request sent. Now get the response: - byte[] responseData = readResponse(conn, res); + readResponse(res, conn); res.sampleEnd(); // Done with the sampling proper. - - // Now collect the results into the HTTPSampleResult: - - res.setResponseData(responseData); - int errorLevel = conn.getResponseCode(); String respMsg = conn.getResponseMessage(); String hdr=conn.getHeaderField(0); diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java index 6c3ef08bdd3..384b8a5aa9c 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java @@ -17,7 +17,6 @@ package org.apache.jmeter.protocol.http.sampler; -import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; @@ -69,11 +68,13 @@ import org.apache.jmeter.report.utils.MetricUtils; import org.apache.jmeter.samplers.AbstractSampler; import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.ResponseDecoderRegistry; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.testelement.TestIterationListener; import org.apache.jmeter.testelement.TestStateListener; import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.testelement.property.BooleanProperty; import org.apache.jmeter.testelement.property.CollectionProperty; import org.apache.jmeter.testelement.property.JMeterProperty; import org.apache.jmeter.testelement.schema.PropertiesAccessor; @@ -82,13 +83,15 @@ import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.io.DirectAccessByteArrayOutputStream; +import org.apache.jorphan.locale.ResourceKeyed; +import org.apache.jorphan.util.EnumUtils; import org.apache.jorphan.util.ExceptionUtils; import org.apache.jorphan.util.JOrphanUtils; import org.apache.jorphan.util.StringUtilities; import org.apache.oro.text.MalformedCachePatternException; import org.apache.oro.text.regex.Pattern; import org.apache.oro.text.regex.Perl5Matcher; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -206,6 +209,7 @@ public abstract class HTTPSamplerBase extends AbstractSampler private static final boolean IGNORE_FAILED_EMBEDDED_RESOURCES = JMeterUtils.getPropDefault("httpsampler.ignore_failed_embedded_resources", false); // $NON-NLS-1$ // default value: false + // TODO: replace with responseProcessingMode enum? private static final boolean IGNORE_EMBEDDED_RESOURCES_DATA = JMeterUtils.getPropDefault("httpsampler.embedded_resources_use_md5", false); // $NON-NLS-1$ // default value: false @@ -377,6 +381,54 @@ public static String[] getSourceTypeList() { } return displayStrings; } + + /** + * Enum for response processing modes that control how HTTP response data is handled. + * Supports different strategies for storing, discarding, or checksumming responses. + * + * @since 6.0.0 + */ + public enum ResponseProcessingMode implements ResourceKeyed { + /** + * Store compressed response data, decompress on-demand when accessed. + * Default mode for normal operation. Saves memory and supports lazy decompression. + */ + STORE_COMPRESSED("response_processing_store_compressed"), //$NON-NLS-1$ + + /** + * Fetch response data but discard it immediately. + * Useful when you only care about response code/headers, not the body. + * Avoids storing large responses in memory. + */ + FETCH_AND_DISCARD("response_processing_fetch_discard"), //$NON-NLS-1$ + + /** + * Compute MD5 checksum on the compressed response stream. + * Stores MD5 hash instead of full response. Useful for validating + * that compressed data hasn't been modified in transit. + */ + CHECKSUM_ENCODED_MD5("response_processing_checksum_encoded_md5"), //$NON-NLS-1$ + + /** + * Compute MD5 checksum on the decompressed response stream. + * Stores MD5 hash instead of full response. Uses streaming decompression + * to avoid buffering entire response in memory. This is the traditional + * "Store as MD5" mode from earlier versions. + */ + CHECKSUM_DECODED_MD5("response_processing_checksum_decoded_md5"); //$NON-NLS-1$ + + public final String propertyName; + + ResponseProcessingMode(String propertyName) { + this.propertyName = propertyName; + } + + @Override + public String getResourceKey() { + return propertyName; + } + } + /** * Determine if the file should be sent as the entire Content body, * i.e. without any additional wrapping. @@ -640,11 +692,56 @@ public String getImplementation() { return get(getSchema().getImplementation()); } + /** + * Gets the response processing mode for this sampler. + * Controls how response data is handled (stored, discarded, or checksummed). + * + * @return the current response processing mode + * @since 6.0.0 + */ + public ResponseProcessingMode getResponseProcessingMode() { + String value = get(getSchema().getResponseProcessingMode()); + return EnumUtils.valueOf(ResponseProcessingMode.class, value); + } + + /** + * Sets the response processing mode for this sampler. + * Controls how response data is handled (stored, discarded, or checksummed). + * + * @param mode the response processing mode to set + * @since 6.0.0 + */ + public void setResponseProcessingMode(ResponseProcessingMode mode) { + set(getSchema().getResponseProcessingMode(), mode.getResourceKey()); + } + + /** + * Returns whether this sampler should store response data as MD5 hash. + * + * @return true if MD5 mode is enabled (CHECKSUM_DECODED_MD5 or CHECKSUM_ENCODED_MD5) + * @deprecated Use {@link #getResponseProcessingMode()} instead. + * This method returns true if mode is any checksum mode. + */ + @Deprecated public boolean useMD5() { - return get(getSchema().getStoreAsMD5()); + ResponseProcessingMode mode = getResponseProcessingMode(); + return mode == ResponseProcessingMode.CHECKSUM_DECODED_MD5; } + /** + * Sets whether this sampler should store response data as MD5 hash. + * + * @param value true to enable MD5 mode (CHECKSUM_DECODED_MD5), + * false to use default mode (STORE_COMPRESSED) + * @deprecated Use {@link #setResponseProcessingMode(ResponseProcessingMode)} instead. + * This method sets mode to CHECKSUM_DECODED_MD5 if true, STORE_COMPRESSED if false. + */ + @Deprecated public void setMD5(boolean value) { + setResponseProcessingMode( + value ? ResponseProcessingMode.CHECKSUM_DECODED_MD5 : ResponseProcessingMode.STORE_COMPRESSED + ); + // Also set old property for backward compatibility with older code set(getSchema().getStoreAsMD5(), value); } @@ -1914,80 +2011,80 @@ public void testIterationStart(LoopIterationEvent event) { * @param sampleResult sample to store information about the response into * @param in input stream from which to read the response * @param length expected input length or zero - * @return the response or the MD5 of the response * @throws IOException if reading the result fails */ - public byte[] readResponse(SampleResult sampleResult, InputStream in, long length) throws IOException { + public void readResponse(SampleResult sampleResult, InputStream in, long length, @Nullable String contentEncoding) throws IOException { + ResponseProcessingMode responseProcessingMode = getResponseProcessingMode(); + if (responseProcessingMode == ResponseProcessingMode.CHECKSUM_DECODED_MD5) { + in = ResponseDecoderRegistry.decodeStream(contentEncoding, in); + contentEncoding = null; // already decoded + } - DirectAccessByteArrayOutputStream w = null; - try (Closeable ignore = in) { // NOSONAR No try with resource as performance is critical here - byte[] readBuffer = new byte[8192]; // 8kB is the (max) size to have the latency ('the first packet') - int bufferSize = 32;// Enough for MD5 + // 8kB is the (max) size to have the latency ('the first packet') + byte[] readBuffer = new byte[Math.toIntExact(length > 0 ? Math.min(length, 8192) : 8192)]; - MessageDigest md = null; - boolean knownResponseLength = length > 0;// may also happen if long value > int.max - if (useMD5()) { + MessageDigest md = null; + DirectAccessByteArrayOutputStream w = null; + switch (responseProcessingMode) { + case FETCH_AND_DISCARD -> { + } + case STORE_COMPRESSED -> { + w = new DirectAccessByteArrayOutputStream(Math.toIntExact(length > 0 ? Math.min(length, MAX_BUFFER_SIZE) : MAX_BUFFER_SIZE)); + } + case CHECKSUM_DECODED_MD5, CHECKSUM_ENCODED_MD5 -> { try { md = MessageDigest.getInstance("MD5"); //$NON-NLS-1$ } catch (NoSuchAlgorithmException e) { - log.error("Should not happen - could not find MD5 digest", e); - } - } else { - if (!knownResponseLength) { - bufferSize = 4 * 1024; - } else { - bufferSize = (int) Math.min(MAX_BUFFER_SIZE, length); + throw new IllegalStateException("MD5 digest algorithm not supported", e); } } + } - - int bytesReadInBuffer = 0; - long totalBytes = 0; - boolean first = true; - boolean storeInBOS = true; - while ((bytesReadInBuffer = in.read(readBuffer)) > -1) { - if (first) { - sampleResult.latencyEnd(); - first = false; - if (md == null) { - w = new DirectAccessByteArrayOutputStream(knownResponseLength ? bufferSize : 8192); - } - } - - if (md == null) { - if(storeInBOS) { - if(MAX_BYTES_TO_STORE_PER_REQUEST <= 0 || - (totalBytes+bytesReadInBuffer<=MAX_BYTES_TO_STORE_PER_REQUEST) || - JMeterContextService.getContext().isRecording()) { - w.write(readBuffer, 0, bytesReadInBuffer); - } else { - log.debug("Big response, truncating it to {} bytes", MAX_BYTES_TO_STORE_PER_REQUEST); - w.write(readBuffer, 0, (int)(MAX_BYTES_TO_STORE_PER_REQUEST-totalBytes)); - storeInBOS = false; - } - } - } else { - md.update(readBuffer, 0, bytesReadInBuffer); - } - totalBytes += bytesReadInBuffer; + int bytesReadInBuffer; + long totalBytes = 0; + boolean first = true; + boolean storeInBOS = true; + while ((bytesReadInBuffer = in.read(readBuffer)) != -1) { + if (bytesReadInBuffer == 0) { + continue; } - - if (first) { // Bug 46838 - if there was no data, still need to set latency + if (first) { sampleResult.latencyEnd(); - return new byte[0]; + first = false; } - if (md == null) { - return w.toByteArray(); - } else { - byte[] md5Result = md.digest(); - sampleResult.setBytes(totalBytes); - return JOrphanUtils.baToHexBytes(md5Result); + if (md != null) { + md.update(readBuffer, 0, bytesReadInBuffer); + } else if (storeInBOS && w != null) { + if (MAX_BYTES_TO_STORE_PER_REQUEST <= 0 || + (totalBytes + bytesReadInBuffer <= MAX_BYTES_TO_STORE_PER_REQUEST) || + JMeterContextService.getContext().isRecording()) { + w.write(readBuffer, 0, bytesReadInBuffer); + } else { + log.debug("Big response, truncating it to {} bytes", MAX_BYTES_TO_STORE_PER_REQUEST); + w.write(readBuffer, 0, (int) (MAX_BYTES_TO_STORE_PER_REQUEST - totalBytes)); + storeInBOS = false; + } } + totalBytes += bytesReadInBuffer; + } + + if (first) { // Bug 46838 - if there was no data, still need to set latency + sampleResult.latencyEnd(); + sampleResult.setResponseData(new byte[0]); + return; + } - } finally { - JOrphanUtils.closeQuietly(w); + byte[] resultBody; + if (w != null) { + resultBody = w.toByteArray(); + } else if (md != null) { + byte[] md5Result = md.digest(); + resultBody = JOrphanUtils.baToHexBytes(md5Result); + } else { + resultBody = new byte[0]; } + sampleResult.setResponseData(resultBody, contentEncoding); } /** @@ -2041,6 +2138,23 @@ void mergeFileProperties() { removeProperty(MIMETYPE); } + @Override + public void setProperty(JMeterProperty property) { + @SuppressWarnings("deprecation") + PropertyDescriptor storeAsMD5 = HTTPSamplerBaseSchema.INSTANCE.getStoreAsMD5(); + if (property.getName().equals(storeAsMD5.getName())) { + if (property instanceof BooleanProperty booleanProperty) { + setResponseProcessingMode( + booleanProperty.getBooleanValue() ? ResponseProcessingMode.CHECKSUM_DECODED_MD5 : ResponseProcessingMode.STORE_COMPRESSED + ); + } else { + setResponseProcessingMode(ResponseProcessingMode.STORE_COMPRESSED); + } + // keep usemd5 property for backward compatibility + } + super.setProperty(property); + } + /** * set IP source to use - does not apply to Java HTTP implementation currently * @@ -2133,7 +2247,10 @@ private static class ASyncSample implements Callable { CookieManager clonedCookieManager = (CookieManager) cookieManager.clone(); this.sampler.setCookieManagerProperty(clonedCookieManager); } - this.sampler.setMD5(this.sampler.useMD5() || IGNORE_EMBEDDED_RESOURCES_DATA); + ResponseProcessingMode responseProcessingMode = base.getResponseProcessingMode(); + this.sampler.setResponseProcessingMode( + IGNORE_EMBEDDED_RESOURCES_DATA ? ResponseProcessingMode.CHECKSUM_DECODED_MD5 : responseProcessingMode + ); this.jmeterContextOfParentThread = JMeterContextService.getContext(); } diff --git a/src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseSchema.kt b/src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseSchema.kt index 89bb2f58e28..d5e1b6cdf16 100644 --- a/src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseSchema.kt +++ b/src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseSchema.kt @@ -123,9 +123,16 @@ public abstract class HTTPSamplerBaseSchema : TestElementSchema() { public val embeddedUrlExcludeRegex: StringPropertyDescriptor by string("HTTPSampler.embedded_url_exclude_re") + @Deprecated(message = "Use responseProcessingMode instead") public val storeAsMD5: BooleanPropertyDescriptor by boolean("HTTPSampler.md5", default = false) + public val responseProcessingMode: StringPropertyDescriptor + by string( + "HTTPSampler.responseProcessingMode", + default = HTTPSamplerBase.ResponseProcessingMode.STORE_COMPRESSED.resourceKey + ) + public val postBodyRaw: BooleanPropertyDescriptor by boolean("HTTPSampler.postBodyRaw", default = false) diff --git a/src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/decoders/BrotliDecoder.kt b/src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/decoders/BrotliDecoder.kt new file mode 100644 index 00000000000..3e34d13972d --- /dev/null +++ b/src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/decoders/BrotliDecoder.kt @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.protocol.http.sampler.decoders + +import com.google.auto.service.AutoService +import org.apache.jmeter.samplers.ResponseDecoder +import org.apiguardian.api.API +import org.brotli.dec.BrotliInputStream +import java.io.InputStream + +/** + * Decoder for Brotli compressed response data. + * Handles "br" content encoding. + * + * @since 6.0.0 + */ +@AutoService(ResponseDecoder::class) +@API(status = API.Status.INTERNAL, since = "6.0.0") +public class BrotliDecoder : ResponseDecoder { + override val encodings: List + get() = listOf("br") + + override fun decodeStream(input: InputStream): InputStream { + return BrotliInputStream(input) + } +} diff --git a/src/protocol/http/src/test/kotlin/org/apache/jmeter/protocol/http/sampler/decoders/BrotliDecoderTest.kt b/src/protocol/http/src/test/kotlin/org/apache/jmeter/protocol/http/sampler/decoders/BrotliDecoderTest.kt new file mode 100644 index 00000000000..9eb2bcc11b9 --- /dev/null +++ b/src/protocol/http/src/test/kotlin/org/apache/jmeter/protocol/http/sampler/decoders/BrotliDecoderTest.kt @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.protocol.http.sampler.decoders + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import java.io.IOException +import java.util.Base64 + +/** + * Basic tests for BrotliDecoder. + * Full integration tests for brotli decompression are covered by HTTP sampler tests. + */ +class BrotliDecoderTest { + private val decoder = BrotliDecoder() + + @Test + fun testGetEncodings() { + assertEquals(listOf("br"), decoder.encodings, "encodings") + } + + @Test + fun testGetPriority() { + assertEquals(0, decoder.priority, "Default priority should be 0") + } + + @Test + fun testDecodeBrotliData() { + // Pre-compressed "Hello World" with Brotli + // Generated using: printf 'Hello World' | brotli | base64 + val compressed = Base64.getDecoder().decode("DwWASGVsbG8gV29ybGQD") + + val decoded = decoder.decode(compressed) + + assertEquals("Hello World", decoded.toString(Charsets.UTF_8), "Decoded text should match original") + } + + @Test + fun testDecodeInvalidData() { + val invalidData = "This is not brotli compressed data".toByteArray(Charsets.UTF_8) + + assertThrows(IOException::class.java) { + decoder.decode(invalidData) + } + } +} From cdb1de56fdb8a163f0446c5148a263785c0658f4 Mon Sep 17 00:00:00 2001 From: jgaalen Date: Sun, 18 Jan 2026 21:05:37 +0100 Subject: [PATCH 13/20] Clear transaction data --- .../apache/jmeter/samplers/SampleResult.java | 46 +++++++++++-------- .../apache/jmeter/threads/TestCompiler.java | 13 +++++- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java index 3b0a53f784c..7d695baa7f6 100644 --- a/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/src/core/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -143,6 +143,14 @@ public class SampleResult implements Serializable, Cloneable, Searchable { private static final String NULL_FILENAME = "NULL"; + // Reuse ByteArrayOutputStream instances to reduce memory allocations + private static final ThreadLocal threadLocalBAOS = + ThreadLocal.withInitial(ByteArrayOutputStream::new); + + // Reuse Inflater instances to minimize object creation + private static final ThreadLocal threadLocalInflater = + ThreadLocal.withInitial(() -> new Inflater(true)); + static { if (START_TIMESTAMP) { log.info("Note: Sample TimeStamps are START times"); @@ -286,6 +294,11 @@ public class SampleResult implements Serializable, Cloneable, Searchable { */ private transient volatile String responseDataAsString; + private static final String GZIP_ENCODING = "gzip"; + private static final String X_GZIP_ENCODING = "x-gzip"; + private static final String DEFLATE_ENCODING = "deflate"; + private static final String BROTLI_ENCODING = "br"; + public SampleResult() { this(USE_NANO_TIME, NANOTHREAD_SLEEP); } @@ -807,13 +820,13 @@ public byte[] getResponseData() { if (contentEncoding != null && responseData.length > 0) { try { switch (contentEncoding.toLowerCase(Locale.ROOT)) { - case "gzip": + case GZIP_ENCODING: return decompressGzip(responseData); - case "x-gzip": + case X_GZIP_ENCODING: return decompressGzip(responseData); - case "deflate": + case DEFLATE_ENCODING: return decompressDeflate(responseData); - case "br": + case BROTLI_ENCODING: return decompressBrotli(responseData); default: return responseData; @@ -1710,7 +1723,8 @@ public void setResponseData(byte[] data, String encoding) { private static byte[] decompressGzip(byte[] in) throws IOException { try (GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(in)); - ByteArrayOutputStream out = new ByteArrayOutputStream()) { + ByteArrayOutputStream out = threadLocalBAOS.get()) { + out.reset(); byte[] buf = new byte[8192]; int len; while ((len = gis.read(buf)) > 0) { @@ -1721,20 +1735,11 @@ private static byte[] decompressGzip(byte[] in) throws IOException { } private static byte[] decompressDeflate(byte[] in) throws IOException { - // Try with ZLIB wrapper first - try { - return decompressWithInflater(in, false); - } catch (IOException e) { - // If that fails, try with NO_WRAP for raw DEFLATE - return decompressWithInflater(in, true); - } - } - - private static byte[] decompressWithInflater(byte[] in, boolean nowrap) throws IOException { - try (InflaterInputStream iis = new InflaterInputStream( - new ByteArrayInputStream(in), - new Inflater(nowrap)); - ByteArrayOutputStream out = new ByteArrayOutputStream()) { + Inflater inflater = threadLocalInflater.get(); + inflater.reset(); + try (InputStream iis = new InflaterInputStream(new ByteArrayInputStream(in), inflater); + ByteArrayOutputStream out = threadLocalBAOS.get()) { + out.reset(); byte[] buf = new byte[8192]; int len; while ((len = iis.read(buf)) > 0) { @@ -1746,7 +1751,8 @@ private static byte[] decompressWithInflater(byte[] in, boolean nowrap) throws I private static byte[] decompressBrotli(byte[] in) throws IOException { try (InputStream bis = new org.brotli.dec.BrotliInputStream(new ByteArrayInputStream(in)); - ByteArrayOutputStream out = new ByteArrayOutputStream()) { + ByteArrayOutputStream out = threadLocalBAOS.get()) { + out.reset(); byte[] buf = new byte[8192]; int len; while ((len = bis.read(buf)) > 0) { diff --git a/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java b/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java index f212dda90a9..233cbb2ad17 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java @@ -115,10 +115,21 @@ public SamplePackage configureTransactionSampler(TransactionSampler transactionS } /** - * Reset pack to its initial state + * Reset pack to its initial state and clean up transaction results if needed * @param pack the {@link SamplePackage} to reset */ public void done(SamplePackage pack) { + Sampler sampler = pack.getSampler(); + if (sampler instanceof TransactionSampler) { + TransactionSampler transactionSampler = (TransactionSampler) sampler; + TransactionController controller = transactionSampler.getTransactionController(); + if (transactionSampler.isTransactionDone()) { + // Create new sampler for next iteration + TransactionSampler newSampler = new TransactionSampler(controller, transactionSampler.getName()); + SamplePackage newPack = transactionControllerConfigMap.get(controller); + newPack.setSampler(newSampler); + } + } pack.recoverRunningVersion(); } From c33d7e47d7c50b61236370a5a37821e8e1fc690a Mon Sep 17 00:00:00 2001 From: jgaalen Date: Mon, 19 Jan 2026 07:10:26 +0100 Subject: [PATCH 14/20] Improve timer stop on shutdown --- .../apache/jmeter/threads/JMeterThread.java | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java index 8b7f13f7900..c7070e9ea52 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java @@ -80,6 +80,10 @@ public class JMeterThread implements Runnable, Interruptible { private static final int RAMPUP_GRANULARITY = JMeterUtils.getPropDefault("jmeterthread.rampup.granularity", 1000); // $NON-NLS-1$ + /** How often to check for shutdown during timer delay, default 1000ms */ + private static final int TIMER_GRANULARITY = + JMeterUtils.getPropDefault("jmeterthread.timer.granularity", 1000); // $NON-NLS-1$ + private static final float TIMER_FACTOR = JMeterUtils.getPropDefault("timer.factor", 1.0f); private static final TimerService TIMER_SERVICE = TimerService.getInstance(); @@ -1008,21 +1012,36 @@ private void delay(List timers) { totalDelay += delay; } if (totalDelay > 0) { - try { - if (scheduler) { - // We reduce pause to ensure end of test is not delayed by a sleep ending after test scheduled end - // See Bug 60049 - totalDelay = TIMER_SERVICE.adjustDelay(totalDelay, endTime, false); - if (totalDelay < 0) { - log.debug("The delay would be longer than the scheduled period, so stop thread now."); - running = false; - return; + if (scheduler) { + // We reduce pause to ensure end of test is not delayed by a sleep ending after test scheduled end + // See Bug 60049 + totalDelay = TIMER_SERVICE.adjustDelay(totalDelay, endTime, false); + if (totalDelay < 0) { + log.debug("The delay would be longer than the scheduled period, so stop thread now."); + running = false; + return; + } + } + // Use granular sleeps to allow quick response to shutdown + long start = System.currentTimeMillis(); + long end = start + totalDelay; + long now; + long pause = TIMER_GRANULARITY; + while (running && (now = System.currentTimeMillis()) < end) { + long togo = end - now; + if (togo < pause) { + pause = togo; + } + try { + TimeUnit.MILLISECONDS.sleep(pause); + } catch (InterruptedException e) { + if (running) { // NOSONAR running may have been changed from another thread + log.warn("The delay timer was interrupted - Loss of delay for {} was {}ms out of {}ms", + threadName, System.currentTimeMillis() - start, totalDelay); } + Thread.currentThread().interrupt(); + break; } - TimeUnit.MILLISECONDS.sleep(totalDelay); - } catch (InterruptedException e) { - log.warn("The delay timer was interrupted - probably did not wait as long as intended."); - Thread.currentThread().interrupt(); } } } From 12732a880c50422b2960d95cef52051141ef65d2 Mon Sep 17 00:00:00 2001 From: jgaalen Date: Mon, 19 Jan 2026 19:43:21 +0100 Subject: [PATCH 15/20] Merge jmeter latest and fixes --- .asf.yaml | 3 + .github/release-drafter.yml | 3 - .../workflows/gradle-dependency-submit.yaml | 31 + .../workflows/gradle-wrapper-validation.yml | 4 +- .github/workflows/main.yml | 40 +- .github/workflows/matrix.js | 7 +- .github/workflows/release-drafter.yml | 30 +- .gitignore | 3 + .ratignore | 10 +- bin/jmeter | 35 +- bin/jmeter.bat | 61 +- bin/jmeter.properties | 4 +- bin/jmeter.sh | 7 +- bin/mirror-server.cmd | 4 +- bin/mirror-server.sh | 4 +- .../content/pages/ResponseTimes.html.fmkr | 2 +- bin/saveservice.properties | 2 - bin/templates/mongodb.jmx | 194 - bin/templates/templates.xml | 23 - bin/testfiles/BatchTestLocal.xml | 980 +-- bin/testfiles/Bug52968.xml | 36 +- bin/testfiles/FTP_TESTS.jmx | 17 +- bin/testfiles/GenTest210.expected.jmx | 1479 ++++ bin/testfiles/GenTest210.jmx | 26 - bin/testfiles/GenTest25.expected.jmx | 1396 ++++ bin/testfiles/GenTest251.expected.jmx | 1394 ++++ bin/testfiles/GenTest26.expected.jmx | 1431 ++++ bin/testfiles/GenTest27.expected.jmx | 1393 ++++ bin/testfiles/GenTest27_original.expected.jmx | 1415 ++++ bin/testfiles/GenTest2_13.expected.jmx | 1495 ++++ bin/testfiles/GenTest2_13.jmx | 27 - bin/testfiles/GenTest3_0.expected.jmx | 1453 ++++ bin/testfiles/GuiTest.expected.jmx | 794 ++ bin/testfiles/GuiTest231.expected.jmx | 1153 +++ .../GuiTest231_original.expected.jmx | 1222 +++ bin/testfiles/HTMLParserTestFile_2.xml | 120 +- bin/testfiles/JMS_TESTS.jmx | 30 +- .../ProxyServerTestPlan.expected.jmx | 44 + bin/testfiles/SavedGenTest210.jmx | 26 - bin/testfiles/SavedGenTest2_13.jmx | 27 - bin/testfiles/TCP_TESTS.jmx | 20 +- .../gradle-plugin/build.gradle.kts | 4 +- ...-logic.kotlin-dsl-gradle-plugin.gradle.kts | 4 +- build-logic-commons/settings.gradle.kts | 4 + build-logic/batchtest/build.gradle.kts | 4 +- .../jmeter/buildtools/batchtest/BatchTest.kt | 48 +- .../buildtools/batchtest/BatchTestServer.kt | 28 +- build-logic/build-parameters/build.gradle.kts | 21 +- build-logic/build.gradle.kts | 2 +- build-logic/checksum.xml | 134 - build-logic/gradle.properties | 3 - .../00/8118b3bcdb1a5000.fingerprints | 1 - .../04/ea8543c570faf804.fingerprints | 1 - .../06/3a1959eef8726006.fingerprints | 1 - ...e38bc93d90b852d88465dd3a1959eef8726006.asc | 26 - .../07/402123d2f5f4af07.fingerprints | 1 - ...441e504a937f43eb0daef96a65176a0fb1cd0b.asc | 27 - .../0b/6a65176a0fb1cd0b.fingerprints | 1 - .../0c/0ae7bbd7fee66e0c.fingerprints | 1 - .../0e/105cb91cac2aee0e.fingerprints | 1 - .../0f/7a01b0f236e5430f.fingerprints | 1 - ...cc4c7594d09e2182c60e0f7a01b0f236e5430f.asc | 16 - .../0f/aff3e378166b1f0f.fingerprints | 1 - ...ed3b4843daacc79de555557457ca33c3ce9e15.asc | 16 - .../15/7457ca33c3ce9e15.fingerprints | 1 - ...d52b5a8295137c88fb5748dddafa7674e54418.asc | 16 - .../18/dddafa7674e54418.fingerprints | 1 - .../1a/1db198f93525ec1a.fingerprints | 1 - ...9f1ee92b19609de816fde81db198f93525ec1a.asc | 26 - .../1c/83552a552a0d431c.fingerprints | 1 - .../1e/6005789e24e5ad1e.fingerprints | 1 - .../1f/9d29ae4a6b50e01f.fingerprints | 1 - .../21/60eb70ddaac2ec21.fingerprints | 1 - .../25/2c7b12f2a511e325.fingerprints | 1 - ...5f3b8e59e6e63aa78067482c7b12f2a511e325.asc | 16 - .../25/9351716690874f25.fingerprints | 1 - .../25/a182f48d9c2c0825.fingerprints | 1 - .../2b/d89d05374952262b.fingerprints | 1 - .../2f/315693699f8d102f.fingerprints | 1 - .../30/37de9e37c5b67c30.fingerprints | 1 - .../34/e98008460eb9bb34.fingerprints | 1 - .../36/01f3a913fb698736.fingerprints | 1 - .../36/3bd211f725778c36.fingerprints | 1 - .../36/6c70a3b7599c5736.fingerprints | 1 - ...beab2d799c020f17c69126b16698a4adf4d638.asc | 16 - .../38/b16698a4adf4d638.fingerprints | 1 - .../38/b5681e477ad61c38.fingerprints | 1 - .../3b/29579f18fa8fd93b.fingerprints | 1 - .../3b/b5cb27f94f97173b.fingerprints | 1 - ...01e2f8ef035068ec1139cc29579f18fa8fd93b.asc | 16 - ...d97a6a154e7810ee0bc832e2f38302c8075e3d.asc | 26 - .../3d/e2f38302c8075e3d.fingerprints | 1 - ...b1a49729b053caf015cee9a6adfc93ef34893e.asc | 16 - .../3e/a6adfc93ef34893e.fingerprints | 1 - .../41/9a347756830c4541.fingerprints | 1 - .../45/1669c4bb543e0445.fingerprints | 1 - ...15ad56144df2376f49d98b1669c4bb543e0445.asc | 21 - .../4b/71499a87dc1ff84b.fingerprints | 1 - .../51/85911f425ec61b51.fingerprints | 1 - ...6e2c001948c5f2f38b0cc385911f425ec61b51.asc | 26 - .../52/33cd6733af5ec452.fingerprints | 1 - ...20d295bf0e61cb4cf0896c33cd6733af5ec452.asc | 24 - .../55/6525fd70cc303655.fingerprints | 1 - ...f833963889d7ed06f1e4dc6525fd70cc303655.asc | 24 - .../57/098a31f381819057.fingerprints | 1 - ...b9bdf40ed49debb5caaded5b30d3b10694f057.asc | 16 - .../57/5b30d3b10694f057.fingerprints | 1 - ...885c07a3204611fb184ef7098a31f381819057.asc | 16 - .../58/2161d72e7dcd4258.fingerprints | 1 - .../59/10da72cd7fbfa159.fingerprints | 1 - .../5a/083891ad4774845a.fingerprints | 1 - ...46dec275b1f230acce4eeb083891ad4774845a.asc | 26 - ...d16069219c90212a974d119ae296fd02e9f65b.asc | 26 - .../5b/32784d4f004b405b.fingerprints | 1 - .../5b/9ae296fd02e9f65b.fingerprints | 1 - .../5b/9df7f2349731d55b.fingerprints | 1 - ...4dac8350968ec2a8260584ee8ecbbbc188fd5d.asc | 15 - .../5d/ee8ecbbbc188fd5d.fingerprints | 1 - ...86f8b24c5bc02c94a0e0e486f75e83e1ee085f.asc | 26 - .../5f/86f75e83e1ee085f.fingerprints | 1 - .../60/7eb97d110dfadd60.fingerprints | 1 - ...789342f598ad5b1175ef357eb97d110dfadd60.asc | 16 - .../61/379ce192d401ab61.fingerprints | 1 - ...56c4f765c9ac3cb6b85d62379ce192d401ab61.asc | 26 - .../62/5a5c54a339b38462.fingerprints | 1 - .../6f/41321490758aad6f.fingerprints | 1 - ...aa0f2206de228f0db01ad741321490758aad6f.asc | 16 - ...57c388d7d1d9d031274cd0a5def5a76f94a471.asc | 26 - .../71/a5def5a76f94a471.fingerprints | 1 - .../71/c45d01093dcfc371.fingerprints | 1 - .../73/59a252fb1199d873.fingerprints | 1 - ...16eb882daf57a11477aaf559a252fb1199d873.asc | 16 - .../76/575d6c921d84ac76.fingerprints | 1 - .../76/5b05ccde140c2876.fingerprints | 1 - ...669810892cbd3148fa92995b05ccde140c2876.asc | 33 - .../7e/3ff44d37464bbb7e.fingerprints | 1 - .../80/ca890a5fa09cfd80.fingerprints | 1 - ...118c070cb22a0175a2e8d43d12ca2ac19f3181.asc | 26 - .../81/3d12ca2ac19f3181.fingerprints | 1 - ...a70c00df7af1b0d2f9dc74ddbcc1270a29d081.asc | 26 - .../81/ddbcc1270a29d081.fingerprints | 1 - .../83/6a0975f8b1127b83.fingerprints | 1 - .../87/0729a0aff8999a87.fingerprints | 1 - ...ba29d08d2e25ee84c132c30729a0aff8999a87.asc | 38 - .../89/926dfb2edb329089.fingerprints | 1 - .../8b/5d9ffe7b8e3dea8b.fingerprints | 1 - .../8c/0ac07d0bbd11498c.fingerprints | 1 - .../8f/dc0b7e986bd7398f.fingerprints | 1 - .../90/1b80c80e07bc7190.fingerprints | 1 - ...3a1affe42b5f53af19f780bcf4173966770193.asc | 16 - .../93/bcf4173966770193.fingerprints | 1 - .../94/59ba7bfead3d7f94.fingerprints | 1 - ...14760097dc5cfad0175ac2c9fbaa83a8753994.asc | 16 - .../94/c9fbaa83a8753994.fingerprints | 1 - ...23db1360d9029481e7f2efecdfea3cb4493b94.asc | 36 - .../94/ecdfea3cb4493b94.fingerprints | 1 - .../96/6601e5c08dccbb96.fingerprints | 1 - ...417737bd805456db3cbdde6601e5c08dccbb96.asc | 21 - ...900cd2fc9a9d906ecba48be3822b59020a349d.asc | 38 - .../9d/c1b12a5d99c0729d.fingerprints | 1 - .../9d/e3822b59020a349d.fingerprints | 1 - ...2231331bca7e1f292c9b88c1b12a5d99c0729d.asc | 21 - .../9e/d4ff1f46313c1a9e.fingerprints | 1 - .../9e/e0130a3ed5a2079e.fingerprints | 1 - ...1703b1d287caea3a60f931e0130a3ed5a2079e.asc | 16 - .../a0/7ec19439e4d4c2a0.fingerprints | 1 - .../a2/0cfe993cdbe1d0a2.fingerprints | 1 - .../a2/249dee8e2c07a0a2.fingerprints | 1 - ...3e8e4c6fb28d11748115c1249dee8e2c07a0a2.asc | 21 - .../a2/606cc6c4533e81a2.fingerprints | 1 - .../a2/92bd2d0b5b21aba2.fingerprints | 1 - .../a2/c3e640f38d845fa2.fingerprints | 1 - .../a3/501b5adef57ce6a3.fingerprints | 1 - ...d7cbff956830fe9f5a723afe6c7d77a1ce15a6.asc | 15 - .../a6/ac5ec74981f9cda6.fingerprints | 1 - ...ba03381ef6c89096acd985ac5ec74981f9cda6.asc | 21 - .../a6/fe6c7d77a1ce15a6.fingerprints | 1 - ...9082bc00e0324e2aef4cf00d3b328562a119a7.asc | 26 - .../a7/0d3b328562a119a7.fingerprints | 1 - .../a7/bc7df305d87bdfa7.fingerprints | 1 - ...f0aab4d0c1a4bdde340765b341ddb020fcb6ab.asc | 24 - .../ab/b341ddb020fcb6ab.fingerprints | 1 - .../ac/d908a43fb7ec07ac.fingerprints | 1 - .../ad/0da8a5ec02d11ead.fingerprints | 1 - ...7b94f8d0a46317a28d8ab30da8a5ec02d11ead.asc | 24 - ...b5fa4fe719d787fb3d3197f6d4a1d411e9d1ae.asc | 16 - .../ae/f6d4a1d411e9d1ae.fingerprints | 1 - .../b1/0e91c2de43b72bb1.fingerprints | 1 - ...6090d3223dca95bfd25c0e38f47d3e410c47b1.asc | 16 - ...92113263fc31c74ccbaab20e91c2de43b72bb1.asc | 16 - .../b1/38f47d3e410c47b1.fingerprints | 1 - .../b1/50c6cc55c6f24fb1.fingerprints | 1 - .../b2/1861c322c56014b2.fingerprints | 1 - ...96a5e3e70732eeb2e5007f1861c322c56014b2.asc | 24 - .../b3/8b2a34a7d4a9b8b3.fingerprints | 1 - ...5479e1055341431b4545ab72475fd306b9cab7.asc | 16 - .../b7/72475fd306b9cab7.fingerprints | 1 - .../b8/1b2718089ce964b8.fingerprints | 1 - ...fae244a81d64507b47182e1b2718089ce964b8.asc | 33 - .../b9/5f6ba89d4b0869b9.fingerprints | 1 - .../bd/9c4c23e6ffe405bd.fingerprints | 1 - ...a1a08c62fca78b79d3081164a16faaec16a4be.asc | 16 - .../be/64a16faaec16a4be.fingerprints | 1 - .../c0/1241bc872c5e4ec0.fingerprints | 1 - ...6de9055c4c75c7bd9830771241bc872c5e4ec0.asc | 24 - .../c1/abe9f3126bb741c1.fingerprints | 1 - .../c2/3624ee1473a843c2.fingerprints | 1 - ...184bcd55f4d016e30d4c9bf42e87f9665015c9.asc | 24 - .../c9/f42e87f9665015c9.fingerprints | 1 - .../cb/1e8f1d57a4450bcb.fingerprints | 1 - ...b4f1ef0fa761ecc4ea935c86fdc7e2a11262cb.asc | 16 - .../cb/86fdc7e2a11262cb.fingerprints | 1 - .../cb/b15ad8efbd28d3cb.fingerprints | 1 - .../d2/dcf4b49b4d5845d2.fingerprints | 1 - .../da/5a3d35a6757384da.fingerprints | 1 - .../dd/15c71c0a4e0b8edd.fingerprints | 1 - ...7929f83ad44c4590f6cc6815c71c0a4e0b8edd.asc | 26 - .../e0/6064b04a9dc688e0.fingerprints | 1 - .../e2/56028df552ba32e2.fingerprints | 1 - ...36a28bc89fb250272c9f7f56028df552ba32e2.asc | 38 - .../e3/2f3c9eeb05d1d1e3.fingerprints | 1 - ...85b3eff60b1b1bea94e0bb7c25280eae63ebe5.asc | 24 - .../e5/4713a6a15a6ea9e5.fingerprints | 1 - .../e5/7c25280eae63ebe5.fingerprints | 1 - ...10792983023d5d14c93b488d7f1bec1e2ecae7.asc | 16 - .../e7/8d7f1bec1e2ecae7.fingerprints | 1 - .../ec/1723844cf9a045ec.fingerprints | 1 - .../ec/2290c86d79f1dfec.fingerprints | 1 - ...d6ff19930adf43ac127792a50569c7ca7fa1f0.asc | 24 - .../f0/a50569c7ca7fa1f0.fingerprints | 1 - .../f8/bf935c771a8474f8.fingerprints | 1 - .../f8/c9f04e6e2dc4f7f8.fingerprints | 1 - ...5e5f90e43e837f9dd8c781bf935c771a8474f8.asc | 26 - .../fb/3d5839a2262cbbfb.fingerprints | 1 - .../fb/cfa67e8582f7e9fb.fingerprints | 1 - .../fe/891e4c2d471515fe.fingerprints | 1 - build-logic/jvm/build.gradle.kts | 12 +- .../build-logic.dokka-javadoc.gradle.kts | 14 +- .../main/kotlin/build-logic.java.gradle.kts | 22 +- .../main/kotlin/build-logic.kotlin.gradle.kts | 32 +- .../kotlin/build-logic.test-junit5.gradle.kts | 3 +- build-logic/root-build/build.gradle.kts | 12 +- build-logic/settings.gradle.kts | 48 +- build-logic/verification/build.gradle.kts | 16 +- .../kotlin/build-logic.autostyle.gradle.kts | 9 +- .../build-logic.checkerframework.gradle.kts | 61 - .../kotlin/build-logic.checkstyle.gradle.kts | 8 +- .../kotlin/build-logic.errorprone.gradle.kts | 4 +- .../build-logic.forbidden-apis.gradle.kts | 3 - .../build-logic.jacoco-aggregate.gradle.kts | 1 - .../main/kotlin/build-logic.jacoco.gradle.kts | 2 +- ...build-logic.sonarqube-aggregate.gradle.kts | 24 - .../kotlin/build-logic.spotbugs.gradle.kts | 71 - .../main/kotlin/build-logic.style.gradle.kts | 6 - build.gradle.kts | 38 +- checksum.xml | 419 - config/checkstyle/checkstyle.xml | 7 + gradle.properties | 13 +- .../00/4be257b370130000.fingerprints | 1 - .../00/8118b3bcdb1a5000.fingerprints | 1 - .../00/cfa2c539de0e0d00.fingerprints | 1 - .../01/1f7a8f87b9d8f501.fingerprints | 1 - ...6dc71e03feeb7f89d1f2491f7a8f87b9d8f501.asc | 38 - .../01/c59d5d06cf8d0e01.fingerprints | 1 - .../02/72ff58594f983302.fingerprints | 1 - .../04/ea8543c570faf804.fingerprints | 1 - .../06/3a1959eef8726006.fingerprints | 1 - ...e38bc93d90b852d88465dd3a1959eef8726006.asc | 26 - .../07/402123d2f5f4af07.fingerprints | 1 - ...88b8be8512d6c0ca185268c51e6cbc7ff46f0b.asc | 16 - ...441e504a937f43eb0daef96a65176a0fb1cd0b.asc | 27 - ...ca61ea0fc7e9ea1216f83396b88eacd463b90b.asc | 21 - .../0b/6a65176a0fb1cd0b.fingerprints | 1 - .../0b/74c249541619ff0b.fingerprints | 1 - .../0b/96b88eacd463b90b.fingerprints | 1 - .../0b/c51e6cbc7ff46f0b.fingerprints | 1 - .../0c/0ae7bbd7fee66e0c.fingerprints | 1 - .../0c/6b3225e6f495dc0c.fingerprints | 1 - .../0e/105cb91cac2aee0e.fingerprints | 1 - ...2c7ef8ada0f794b58c7c63436902af59edf60e.asc | 6 - .../0e/3684155e9365c30e.fingerprints | 1 - ...d266db4427983a1a4afb0c3684155e9365c30e.asc | 16 - .../0e/436902af59edf60e.fingerprints | 1 - .../0f/7a01b0f236e5430f.fingerprints | 1 - ...cc4c7594d09e2182c60e0f7a01b0f236e5430f.asc | 16 - .../0f/aff3e378166b1f0f.fingerprints | 1 - .../10/a7a66be60a24ce10.fingerprints | 1 - .../10/bb2fb7b24b12ae10.fingerprints | 1 - .../11/9f3ffb85a8582c11.fingerprints | 1 - ...123c1ed3f13a6a0140e166c71fb765cd9de313.asc | 26 - .../13/c71fb765cd9de313.fingerprints | 1 - .../14/3faad2cd5ecbb314.fingerprints | 1 - .../14/5cf48cd2f24c5214.fingerprints | 1 - ...e73d84ea4fcc47166087253faad2cd5ecbb314.asc | 26 - .../14/d7913335bfa51814.fingerprints | 1 - ...3d3a74b068eca354dc385ce126833f9cf64915.asc | 26 - ...ed3b4843daacc79de555557457ca33c3ce9e15.asc | 16 - .../15/7457ca33c3ce9e15.fingerprints | 1 - .../15/e126833f9cf64915.fingerprints | 1 - .../16/0588bc69a286ff16.fingerprints | 1 - .../16/92aeca6ac21db816.fingerprints | 1 - ...999bb69041cf17e73bb1cec038787776a19d18.asc | 7 - ...d52b5a8295137c88fb5748dddafa7674e54418.asc | 16 - .../18/c038787776a19d18.fingerprints | 1 - .../18/dddafa7674e54418.fingerprints | 1 - .../19/411063a3a0ffd119.fingerprints | 1 - .../19/9ff24f51b06dcc19.fingerprints | 1 - ...dee87612e9fb95f5c8d91e411063a3a0ffd119.asc | 117 - .../1a/1db198f93525ec1a.fingerprints | 1 - ...9f1ee92b19609de816fde81db198f93525ec1a.asc | 26 - ...0a37a2e0577f4baa095b52602ec18d20c4661c.asc | 26 - .../1c/602ec18d20c4661c.fingerprints | 1 - .../1c/8067ecaa8d58321c.fingerprints | 1 - .../1c/83552a552a0d431c.fingerprints | 1 - .../1c/ae5a7fb608a0221c.fingerprints | 1 - ...2137d875d833d9b23392ecae5a7fb608a0221c.asc | 33 - .../1d/9207518d06855c1d.fingerprints | 1 - .../1e/6005789e24e5ad1e.fingerprints | 1 - .../1e/9842fe565aa0601e.fingerprints | 1 - .../1e/d547b4a01f74ac1e.fingerprints | 1 - .../1f/9d29ae4a6b50e01f.fingerprints | 1 - .../20/011b32c57a810220.fingerprints | 1 - ...90c240ce51b4670d30ad1c38ee757d69184620.asc | 26 - .../20/38ee757d69184620.fingerprints | 1 - .../20/d364abaa39a47320.fingerprints | 1 - ...0ca873074092f806f59b65d364abaa39a47320.asc | 26 - .../21/60eb70ddaac2ec21.fingerprints | 1 - .../22/51f5b36c761aa122.fingerprints | 1 - .../22/fe694b892910dd22.fingerprints | 1 - .../23/528dc75aa68f0923.fingerprints | 1 - ...569a15406ae682380f3ef8528dc75aa68f0923.asc | 16 - .../24/b8c800fb51837224.fingerprints | 1 - .../25/2c7b12f2a511e325.fingerprints | 1 - ...5f3b8e59e6e63aa78067482c7b12f2a511e325.asc | 16 - .../25/9351716690874f25.fingerprints | 1 - .../25/a182f48d9c2c0825.fingerprints | 1 - .../29/16fbb6b6e3120829.fingerprints | 1 - ...9352189aeccfb4ae1f3bc616fbb6b6e3120829.asc | 21 - .../2b/d89d05374952262b.fingerprints | 1 - .../2c/5f69ad087600b22c.fingerprints | 1 - ...bd02b93e7a40482eb1d66a5f69ad087600b22c.asc | 16 - .../2d/127ce18327e55b2d.fingerprints | 1 - .../2d/923c08f9417b222d.fingerprints | 1 - .../2e/a1766be5f812ac2e.fingerprints | 1 - .../2f/315693699f8d102f.fingerprints | 1 - .../30/37de9e37c5b67c30.fingerprints | 1 - .../31/f8052e4e1bd0db31.fingerprints | 1 - .../34/31474e5e7c6b7034.fingerprints | 1 - ...2db2916149baa9d0c92f3731474e5e7c6b7034.asc | 26 - .../34/dee12b9896f97e34.fingerprints | 1 - ...d98c5fd55a8af232290e58dee12b9896f97e34.asc | 16 - .../34/e98008460eb9bb34.fingerprints | 1 - .../36/01f3a913fb698736.fingerprints | 1 - .../36/2be5d98f751f4136.fingerprints | 1 - .../36/3bd211f725778c36.fingerprints | 1 - .../36/6c70a3b7599c5736.fingerprints | 1 - ...8366594e746bf3c449f5622be5d98f751f4136.asc | 16 - .../37/218fa0f6a941a037.fingerprints | 1 - ...fd709cc4b0515f2e6af04e218fa0f6a941a037.asc | 16 - ...beab2d799c020f17c69126b16698a4adf4d638.asc | 16 - .../38/b16698a4adf4d638.fingerprints | 1 - .../38/b5681e477ad61c38.fingerprints | 1 - .../3a/03281aa0289ff53a.fingerprints | 1 - ...e2dd4b1fcf252fe4f0a1d103281aa0289ff53a.asc | 16 - .../3a/b9729775c0352d3a.fingerprints | 1 - ...5f005af50ae94e69701222b9729775c0352d3a.asc | 10 - .../3b/06ec66d653dc633b.fingerprints | 1 - .../3b/29579f18fa8fd93b.fingerprints | 1 - .../3b/b5cb27f94f97173b.fingerprints | 1 - ...01e2f8ef035068ec1139cc29579f18fa8fd93b.asc | 16 - .../3d/0a52516cf3e88b3d.fingerprints | 1 - ...d97a6a154e7810ee0bc832e2f38302c8075e3d.asc | 26 - .../3d/9a2c7a98e457c53d.fingerprints | 1 - ...acb037933cdeaab7bf77d49a2c7a98e457c53d.asc | 16 - .../3d/e2f38302c8075e3d.fingerprints | 1 - ...b1a49729b053caf015cee9a6adfc93ef34893e.asc | 16 - .../3e/a6adfc93ef34893e.fingerprints | 1 - .../3f/efd4147f269a813f.fingerprints | 1 - .../40/66b50994442d2d40.fingerprints | 1 - .../40/750f9a735eecf640.fingerprints | 1 - .../40/d8b9c51729d09d40.fingerprints | 1 - ...d744ace7ade6aa50dd591f66b50994442d2d40.asc | 15 - .../41/9a347756830c4541.fingerprints | 1 - .../42/a0f7d9615b3a5e42.fingerprints | 1 - .../43/a6ea2e2bf22e0543.fingerprints | 1 - ...77d51812e692011db11e66a6ea2e2bf22e0543.asc | 13 - ...4d86c56ca99373b6d8953dbe84d764623a3644.asc | 16 - .../44/5923a9d358adf744.fingerprints | 1 - .../44/be84d764623a3644.fingerprints | 1 - .../44/c1d2a95a576a4144.fingerprints | 1 - .../44/c7fddd147fa73f44.fingerprints | 1 - .../45/1669c4bb543e0445.fingerprints | 1 - .../45/353a436e043e3145.fingerprints | 1 - ...bc5d7dc2fb8dd68aa429bd353a436e043e3145.asc | 16 - ...15ad56144df2376f49d98b1669c4bb543e0445.asc | 21 - .../45/a98bd25be464ea45.fingerprints | 1 - .../46/6f67467bc1adb846.fingerprints | 1 - .../46/873a8e86b4372146.fingerprints | 1 - .../46/9a716f957bc42546.fingerprints | 1 - ...1b3de71713c9ec2e87cc26ee92349ad86de446.asc | 16 - .../46/ee92349ad86de446.fingerprints | 1 - ...54b35617dc255d9344bcfa873a8e86b4372146.asc | 24 - .../47/dd6aefcee9eb4747.fingerprints | 1 - .../48/1d185615d0a84648.fingerprints | 1 - ...aa7402ba75f78720d975211d185615d0a84648.asc | 16 - ...b06224fd8912e36603be79fefe78456eddc34a.asc | 21 - .../4a/fefe78456eddc34a.fingerprints | 1 - .../4b/0440006d577eae4b.fingerprints | 1 - .../4b/700e4f39bc05364b.fingerprints | 1 - .../4b/71499a87dc1ff84b.fingerprints | 1 - .../4e/ccfb05b8ccbc1f4e.fingerprints | 1 - .../4e/f5492b20d173a94e.fingerprints | 1 - .../4f/9ff25980f5ba7e4f.fingerprints | 1 - ...b8247af8a619a28f90fdfc9ff25980f5ba7e4f.asc | 19 - .../50/726f4e5c34cfd750.fingerprints | 1 - ...4454fad8697760f3e00d2e4a51a45b944ffd51.asc | 26 - .../51/4a51a45b944ffd51.fingerprints | 1 - ...c55a85d30c3499a3219500b7e2662a7640a051.asc | 10 - .../51/85911f425ec61b51.fingerprints | 1 - .../51/b7e2662a7640a051.fingerprints | 1 - ...6e2c001948c5f2f38b0cc385911f425ec61b51.asc | 26 - .../52/33cd6733af5ec452.fingerprints | 1 - .../52/74237ad167ffee52.fingerprints | 1 - .../52/8671a8df71296252.fingerprints | 1 - ...d6c97108b8585f91b158748671a8df71296252.asc | 26 - ...20d295bf0e61cb4cf0896c33cd6733af5ec452.asc | 24 - .../53/3046c9379f41ea53.fingerprints | 1 - ...2a008a91d11a7fac4a0786f13d3e721d56bd54.asc | 21 - .../54/cf9f423a7d348254.fingerprints | 1 - .../54/f13d3e721d56bd54.fingerprints | 1 - .../55/6525fd70cc303655.fingerprints | 1 - ...f833963889d7ed06f1e4dc6525fd70cc303655.asc | 24 - ...d04176586361fda94ee0315f7786df73e61f56.asc | 26 - .../56/5f7786df73e61f56.fingerprints | 1 - .../57/098a31f381819057.fingerprints | 1 - ...b9bdf40ed49debb5caaded5b30d3b10694f057.asc | 16 - .../57/5b30d3b10694f057.fingerprints | 1 - .../57/6366592024774157.fingerprints | 1 - ...885c07a3204611fb184ef7098a31f381819057.asc | 16 - ...d34ed6ff73de368a772a781063fe98bcecb758.asc | 16 - .../58/1063fe98bcecb758.fingerprints | 1 - .../58/2161d72e7dcd4258.fingerprints | 1 - .../59/10da72cd7fbfa159.fingerprints | 1 - .../5a/083891ad4774845a.fingerprints | 1 - ...46dec275b1f230acce4eeb083891ad4774845a.asc | 26 - .../5b/11cd01355c5bcf5b.fingerprints | 1 - ...d16069219c90212a974d119ae296fd02e9f65b.asc | 26 - .../5b/3260cb2def74135b.fingerprints | 1 - .../5b/32784d4f004b405b.fingerprints | 1 - .../5b/9ae296fd02e9f65b.fingerprints | 1 - .../5b/9df7f2349731d55b.fingerprints | 1 - .../5c/b89991d171a02f5c.fingerprints | 1 - ...4dac8350968ec2a8260584ee8ecbbbc188fd5d.asc | 15 - ...de772c338ba540e1057d0c8ea48d105232855d.asc | 16 - .../5d/8ea48d105232855d.fingerprints | 1 - .../5d/ee8ecbbbc188fd5d.fingerprints | 1 - ...86f8b24c5bc02c94a0e0e486f75e83e1ee085f.asc | 26 - .../5f/347cd0eaed975c5f.fingerprints | 1 - .../5f/86f75e83e1ee085f.fingerprints | 1 - .../5f/ae1c126549627b5f.fingerprints | 1 - ...1128c8fd304fe48e6eed2c347cd0eaed975c5f.asc | 16 - .../60/7eb97d110dfadd60.fingerprints | 1 - ...789342f598ad5b1175ef357eb97d110dfadd60.asc | 16 - .../61/379ce192d401ab61.fingerprints | 1 - ...56c4f765c9ac3cb6b85d62379ce192d401ab61.asc | 26 - .../62/064ac25270668162.fingerprints | 1 - ...97b4f5dcc46cea61059071a1ae06236ca2ba62.asc | 21 - .../62/5a5c54a339b38462.fingerprints | 1 - ...86bbf993566d8c2f4f7ed7d945e643368fef62.asc | 21 - .../62/7a8860944fad5f62.fingerprints | 1 - .../62/a1ae06236ca2ba62.fingerprints | 1 - ...f1bc78607808ec8e9f69437a8860944fad5f62.asc | 26 - .../62/d945e643368fef62.fingerprints | 1 - ...7366a3ffe6bf8f94b9136a9987b20c8f6a3064.asc | 21 - .../64/9987b20c8f6a3064.fingerprints | 1 - .../67/21939ff0ca2a6567.fingerprints | 1 - .../67/91a4ba316974a467.fingerprints | 1 - ...87a3fd0a54480f0badbebd21939ff0ca2a6567.asc | 26 - .../68/2f45f85cdf082e68.fingerprints | 1 - ...af5293c334c7c0b583227d2f45f85cdf082e68.asc | 21 - .../69/e68665c8f91bde69.fingerprints | 1 - .../6a/012579464d01c06a.fingerprints | 1 - .../6a/2591ba36fd00e56a.fingerprints | 1 - ...407e95050fb588296690862591ba36fd00e56a.asc | 26 - ...77dcfef2ee6eb2debedd2c012579464d01c06a.asc | 16 - .../6c/1afec329b615d06c.fingerprints | 1 - .../6e/b2ca0dfe9ed21c6e.fingerprints | 1 - ...2c0ce2e5c346b2d9bf2d8bb2ca0dfe9ed21c6e.asc | 16 - .../6f/41321490758aad6f.fingerprints | 1 - ...aa0f2206de228f0db01ad741321490758aad6f.asc | 16 - ...57c388d7d1d9d031274cd0a5def5a76f94a471.asc | 26 - .../71/a5def5a76f94a471.fingerprints | 1 - .../71/c45d01093dcfc371.fingerprints | 1 - .../71/c51e10bd96d06071.fingerprints | 1 - ...5a8c9fcf1016c0f54f2d4dc51e10bd96d06071.asc | 16 - .../72/a2115ae15f6b8b72.fingerprints | 1 - ...8075a251547bee249bc151a2115ae15f6b8b72.asc | 19 - .../73/59a252fb1199d873.fingerprints | 1 - ...16eb882daf57a11477aaf559a252fb1199d873.asc | 16 - .../73/8183e80d264ee073.fingerprints | 1 - .../76/164779204e106a76.fingerprints | 1 - ...a8cf92d409a73393d0b736bff2ee42c8282e76.asc | 26 - .../76/254e9c64c264c176.fingerprints | 1 - .../76/575d6c921d84ac76.fingerprints | 1 - .../76/5b05ccde140c2876.fingerprints | 1 - ...669810892cbd3148fa92995b05ccde140c2876.asc | 33 - ...255b26003ef6f82491acd7254e9c64c264c176.asc | 16 - .../76/bff2ee42c8282e76.fingerprints | 1 - ...c3b1929191df06136ccb2b164779204e106a76.asc | 26 - .../77/7ba5338873382b77.fingerprints | 1 - ...ba61cf501c09f70911b83b82216a03caa86c78.asc | 10 - .../78/82216a03caa86c78.fingerprints | 1 - .../7c/c3720ddc2e713b7c.fingerprints | 1 - ...0be7d076a0c89f5f07625ae57428da9e879e7d.asc | 16 - .../7d/e57428da9e879e7d.fingerprints | 1 - .../7e/3ff44d37464bbb7e.fingerprints | 1 - ...2ddd47e19863bb87211544cd3038fef07d567e.asc | 19 - .../7e/cd3038fef07d567e.fingerprints | 1 - .../7f/25a93b1970c22a7f.fingerprints | 1 - .../80/10314d676733c080.fingerprints | 1 - .../80/2ac7bf2f3349de80.fingerprints | 1 - .../80/bede11eaf1164480.fingerprints | 1 - .../80/ca890a5fa09cfd80.fingerprints | 1 - .../80/d068f0d7b6a63980.fingerprints | 1 - ...a9f95079e84ce201f7cf60bede11eaf1164480.asc | 21 - ...118c070cb22a0175a2e8d43d12ca2ac19f3181.asc | 26 - .../81/33aebc1f01c98081.fingerprints | 1 - .../81/3d12ca2ac19f3181.fingerprints | 1 - ...a70c00df7af1b0d2f9dc74ddbcc1270a29d081.asc | 26 - .../81/ddbcc1270a29d081.fingerprints | 1 - ...91fed3922d52296888bae9b6d3ab9bcc641282.asc | 26 - ...752a9878e937049ef3fba7d4012dda1f1f0f82.asc | 16 - .../82/b6d3ab9bcc641282.fingerprints | 1 - .../82/d4012dda1f1f0f82.fingerprints | 1 - .../83/31fa045762d93f83.fingerprints | 1 - .../83/6a0975f8b1127b83.fingerprints | 1 - .../83/b7d8b732f9ff1283.fingerprints | 1 - ...200ac4ae761f1614d6c46766d68daa073be985.asc | 11 - .../85/66d68daa073be985.fingerprints | 1 - .../85/7679164aa2590985.fingerprints | 1 - ...2b29e928995b9db963c636c7ca19b7b620d787.asc | 35 - .../87/0729a0aff8999a87.fingerprints | 1 - ...ba29d08d2e25ee84c132c30729a0aff8999a87.asc | 38 - ...4cb436c56916fc01eea4a77c30f7b1329dba87.asc | 38 - .../87/7c30f7b1329dba87.fingerprints | 1 - .../87/c7ca19b7b620d787.fingerprints | 1 - ...0a7a9cf46221a56b06ad64461a804f2609fd89.asc | 37 - .../89/461a804f2609fd89.fingerprints | 1 - .../89/4b1e11d5a4b91e89.fingerprints | 1 - .../89/926dfb2edb329089.fingerprints | 1 - ...a51e051f527e0c8e24d54d4b1e11d5a4b91e89.asc | 21 - .../8a/ef3fa25a9e1b028a.fingerprints | 1 - .../8b/5d9ffe7b8e3dea8b.fingerprints | 1 - .../8b/7999befba1039e8b.fingerprints | 1 - .../8c/0ac07d0bbd11498c.fingerprints | 1 - .../8d/2a742740e08e7f8d.fingerprints | 1 - ...19e50eac5a4b1dd390b72c2a742740e08e7f8d.asc | 33 - .../8d/fe4ce430fd91c78d.fingerprints | 1 - ...5f68d09d42ba7fac888df9a929ea2321fdbf8f.asc | 16 - .../8f/79e193516be7998f.fingerprints | 1 - ...9a3c6d105b9f57844a721d79e193516be7998f.asc | 16 - .../8f/a929ea2321fdbf8f.fingerprints | 1 - .../8f/dc0b7e986bd7398f.fingerprints | 1 - ...ac2213964abe1d1c147c0e1939a2520bab1d90.asc | 16 - .../90/1939a2520bab1d90.fingerprints | 1 - .../90/1b80c80e07bc7190.fingerprints | 1 - .../91/3967d4eda591b991.fingerprints | 1 - .../91/9f621f1ae6ef2591.fingerprints | 1 - .../92/8bcab185914c4a92.fingerprints | 1 - ...c641c3a62453ab390066c4a41f13c999945293.asc | 26 - ...3a1affe42b5f53af19f780bcf4173966770193.asc | 16 - .../93/a41f13c999945293.fingerprints | 1 - .../93/bcf4173966770193.fingerprints | 1 - .../94/326ba6bd408bfa94.fingerprints | 1 - .../94/59ba7bfead3d7f94.fingerprints | 1 - ...14760097dc5cfad0175ac2c9fbaa83a8753994.asc | 16 - .../94/be6397e958ab7594.fingerprints | 1 - .../94/c9fbaa83a8753994.fingerprints | 1 - ...23db1360d9029481e7f2efecdfea3cb4493b94.asc | 36 - .../94/ecdfea3cb4493b94.fingerprints | 1 - ...abfb0dbd7a754822d35dd9be6397e958ab7594.asc | 16 - .../96/6601e5c08dccbb96.fingerprints | 1 - ...417737bd805456db3cbdde6601e5c08dccbb96.asc | 21 - .../97/145d819475314d97.fingerprints | 1 - .../97/4830c9fb8b680a97.fingerprints | 1 - ...caa216b086a7b603b638c5145d819475314d97.asc | 16 - .../99/a7764f502a938c99.fingerprints | 1 - ...5e25b78ebb19e47c0a99bca7764f502a938c99.asc | 16 - ...92c12c7e3d336a452beb254e066e0459cd109b.asc | 26 - .../9b/4e066e0459cd109b.fingerprints | 1 - .../9c/c6fc46eb51cf569c.fingerprints | 1 - ...491704d613780d2bee00f2c6fc46eb51cf569c.asc | 16 - .../9d/43203be58f49479d.fingerprints | 1 - ...900cd2fc9a9d906ecba48be3822b59020a349d.asc | 38 - .../9d/c1b12a5d99c0729d.fingerprints | 1 - .../9d/e3822b59020a349d.fingerprints | 1 - ...2231331bca7e1f292c9b88c1b12a5d99c0729d.asc | 21 - ...3aabbb95ca37431e20422043203be58f49479d.asc | 26 - .../9e/d4ff1f46313c1a9e.fingerprints | 1 - .../9e/dff2d25e2cd6139e.fingerprints | 1 - .../9e/e0130a3ed5a2079e.fingerprints | 1 - ...1703b1d287caea3a60f931e0130a3ed5a2079e.asc | 16 - .../a0/7ec19439e4d4c2a0.fingerprints | 1 - .../a2/0cfe993cdbe1d0a2.fingerprints | 1 - .../a2/249dee8e2c07a0a2.fingerprints | 1 - ...3e8e4c6fb28d11748115c1249dee8e2c07a0a2.asc | 21 - .../a2/606cc6c4533e81a2.fingerprints | 1 - .../a2/92bd2d0b5b21aba2.fingerprints | 1 - .../a2/c3e640f38d845fa2.fingerprints | 1 - ...a37fbe4453c1073e7ef61d6449005f96bc97a3.asc | 37 - .../a3/501b5adef57ce6a3.fingerprints | 1 - .../a3/6449005f96bc97a3.fingerprints | 1 - ...dba0d142fe15d6a16f6b3789af105ceec705a4.asc | 37 - .../a4/89af105ceec705a4.fingerprints | 1 - .../a6/6e50bb68cc1699a6.fingerprints | 1 - .../a6/ac5ec74981f9cda6.fingerprints | 1 - ...ba03381ef6c89096acd985ac5ec74981f9cda6.asc | 21 - ...9082bc00e0324e2aef4cf00d3b328562a119a7.asc | 26 - .../a7/0d3b328562a119a7.fingerprints | 1 - .../a7/0e26a79ffe07a3a7.fingerprints | 1 - .../a7/7d1be4480b61e2a7.fingerprints | 1 - .../a7/bc7df305d87bdfa7.fingerprints | 1 - .../aa/3449ec3ac2efe8aa.fingerprints | 1 - ...1ac64f4e34e290503c90323449ec3ac2efe8aa.asc | 37 - .../aa/cb6d56b72fddf8aa.fingerprints | 1 - ...f0aab4d0c1a4bdde340765b341ddb020fcb6ab.asc | 24 - .../ab/3b2a06173073fdab.fingerprints | 1 - .../ab/b341ddb020fcb6ab.fingerprints | 1 - .../ab/c64121f5021754ab.fingerprints | 1 - .../ac/a7e989b0634097ac.fingerprints | 1 - .../ac/d908a43fb7ec07ac.fingerprints | 1 - .../ad/0da8a5ec02d11ead.fingerprints | 1 - ...7b94f8d0a46317a28d8ab30da8a5ec02d11ead.asc | 24 - ...b87b18a453990eac9453f87d713008cc07e9ad.asc | 26 - .../ad/7d713008cc07e9ad.fingerprints | 1 - .../ad/a8d88140c35897ad.fingerprints | 1 - .../ae/7494750bdf4f8fae.fingerprints | 1 - ...4be739ddae10f4473fecf0aef385ed6ca815ae.asc | 21 - .../ae/aef385ed6ca815ae.fingerprints | 1 - ...b5fa4fe719d787fb3d3197f6d4a1d411e9d1ae.asc | 16 - .../ae/f6d4a1d411e9d1ae.fingerprints | 1 - .../af/030dd9087c31c9af.fingerprints | 1 - .../af/c2148900bcd3c2af.fingerprints | 1 - ...4fe82e5a4c5377bca2edec5208812e1e4a6db0.asc | 16 - .../b0/5208812e1e4a6db0.fingerprints | 1 - .../b1/07d3516820bcf6b1.fingerprints | 1 - .../b1/0e91c2de43b72bb1.fingerprints | 1 - ...6090d3223dca95bfd25c0e38f47d3e410c47b1.asc | 16 - ...92113263fc31c74ccbaab20e91c2de43b72bb1.asc | 16 - .../b1/38f47d3e410c47b1.fingerprints | 1 - .../b1/50c6cc55c6f24fb1.fingerprints | 1 - ...5ee627345f3c1dd422b2e207d3516820bcf6b1.asc | 16 - ...d8419ba697f0e7fb85916ee91287822fdb81b1.asc | 26 - .../b1/e91287822fdb81b1.fingerprints | 1 - .../b2/1861c322c56014b2.fingerprints | 1 - ...96a5e3e70732eeb2e5007f1861c322c56014b2.asc | 24 - .../b2/eaae192d27ab3eb2.fingerprints | 1 - ...1708ea05adf6966c32d6454a1bac7f1e3b89b3.asc | 16 - .../b3/4a1bac7f1e3b89b3.fingerprints | 1 - .../b3/8b2a34a7d4a9b8b3.fingerprints | 1 - .../b4/2d0e1fb8fe4b68b4.fingerprints | 1 - ...89baf76217b843d66be55b2d0e1fb8fe4b68b4.asc | 26 - ...5479e1055341431b4545ab72475fd306b9cab7.asc | 16 - ...6a225fc79ce5cbbd7dd5bfa7f5ed0fa05d64b7.asc | 16 - .../b7/72475fd306b9cab7.fingerprints | 1 - .../b7/a7f5ed0fa05d64b7.fingerprints | 1 - .../b8/1b2718089ce964b8.fingerprints | 1 - ...fae244a81d64507b47182e1b2718089ce964b8.asc | 33 - .../b9/5f6ba89d4b0869b9.fingerprints | 1 - .../b9/e09f6a4522f77db9.fingerprints | 1 - .../b9/fb693bc97890abb9.fingerprints | 1 - .../ba/3595395eb3d8e1ba.fingerprints | 1 - ...c935821aa6a755bd337db53595395eb3d8e1ba.asc | 26 - .../ba/7c7d8456294423ba.fingerprints | 1 - ...5aed155021af8a6c6b7a4a7c7d8456294423ba.asc | 24 - .../ba/fcf74afdf5947aba.fingerprints | 1 - .../bb/1085f41881c989bb.fingerprints | 1 - .../bc/6b7edfc69f043bbc.fingerprints | 1 - .../bd/38185785755267bd.fingerprints | 1 - .../bd/8799af90c06efbbd.fingerprints | 1 - ...4555c237785ca8a6dc1fe58799af90c06efbbd.asc | 10 - .../bd/9c4c23e6ffe405bd.fingerprints | 1 - .../be/2bfd7825a8984fbe.fingerprints | 1 - ...a1a08c62fca78b79d3081164a16faaec16a4be.asc | 16 - .../be/590c2310cee1c9be.fingerprints | 1 - .../be/64a16faaec16a4be.fingerprints | 1 - ...2c31fc25fede4e7ad0d18c2bfd7825a8984fbe.asc | 21 - .../bf/5e975cb00c643dbf.fingerprints | 1 - ...f46bc0b86af5dc56df58f05e975cb00c643dbf.asc | 21 - .../c0/1241bc872c5e4ec0.fingerprints | 1 - ...6de9055c4c75c7bd9830771241bc872c5e4ec0.asc | 24 - .../c1/11f4ce313a637cc1.fingerprints | 1 - .../c1/abe9f3126bb741c1.fingerprints | 1 - .../c2/3624ee1473a843c2.fingerprints | 1 - .../c3/67631bc0568801c3.fingerprints | 1 - .../c3/80cfa7c482552dc3.fingerprints | 1 - ...2567d2589415bd74eb4c2867631bc0568801c3.asc | 26 - ...7e32d440ef90a83011a8fc6425559c47cc79c4.asc | 16 - .../c4/6425559c47cc79c4.fingerprints | 1 - .../c4/c189c86b813330c4.fingerprints | 1 - .../c4/d66472cf54179cc4.fingerprints | 1 - .../c5/1364c5e2df3e99c5.fingerprints | 1 - .../c6/2f53295a4bbac7c6.fingerprints | 1 - .../c7/5858009d3cc798c7.fingerprints | 1 - .../c8/3f606403dca455c8.fingerprints | 1 - ...184bcd55f4d016e30d4c9bf42e87f9665015c9.asc | 24 - .../c9/f42e87f9665015c9.fingerprints | 1 - .../cb/1e8f1d57a4450bcb.fingerprints | 1 - ...b4f1ef0fa761ecc4ea935c86fdc7e2a11262cb.asc | 16 - .../cb/4c00043bec5cb7cb.fingerprints | 1 - ...f086abb0ede8f7a04b76c34c00043bec5cb7cb.asc | 33 - .../cb/6e97418b04b11dcb.fingerprints | 1 - .../cb/86fdc7e2a11262cb.fingerprints | 1 - .../cb/b15ad8efbd28d3cb.fingerprints | 1 - ...58f9cc982ac877e7b295dd6e97418b04b11dcb.asc | 26 - .../cd/bb9cc415c9c063cd.fingerprints | 1 - .../ce/ccda41f01581c6ce.fingerprints | 1 - ...7e8893a6dcc33dd4a4d5b256e73ba9a0b592d0.asc | 15 - .../d0/56e73ba9a0b592d0.fingerprints | 1 - .../d0/9daadc1c9fcc82d0.fingerprints | 1 - ...5464315f0b98c77e6e8ecd9daadc1c9fcc82d0.asc | 26 - ...fab2e3c6490b421b25c76c9c8c892f91f8e6d1.asc | 26 - ...9751fc3f01f134f476464cd0eb627d4885ced1.asc | 24 - .../d1/9c8c892f91f8e6d1.fingerprints | 1 - .../d1/d0eb627d4885ced1.fingerprints | 1 - .../d2/dcf4b49b4d5845d2.fingerprints | 1 - .../d4/0162fe0cf6e18bd4.fingerprints | 1 - ...dbc3c1ab1f4468471656061c8d5ef0df2b70d4.asc | 16 - .../d4/1c8d5ef0df2b70d4.fingerprints | 1 - .../d4/7892707e9657ebd4.fingerprints | 1 - .../d4/a0fc00d356eef1d4.fingerprints | 1 - .../d4/a5c8dbb8a1599bd4.fingerprints | 1 - .../d5/1da784ccb5c46dd5.fingerprints | 1 - ...ac8cdc141af0ae468d16921da784ccb5c46dd5.asc | 37 - .../d9/39f8a1784230e2d9.fingerprints | 1 - .../da/5a3d35a6757384da.fingerprints | 1 - .../dd/15c71c0a4e0b8edd.fingerprints | 1 - ...cffe916b6670868fa0ea0391ae1504568ec4dd.asc | 26 - .../dd/91ae1504568ec4dd.fingerprints | 1 - ...7929f83ad44c4590f6cc6815c71c0a4e0b8edd.asc | 26 - .../de/106bde3d5d78bfde.fingerprints | 1 - .../de/cfca4a29d26468de.fingerprints | 1 - ...182e81c792928921dbcab4cfca4a29d26468de.asc | 16 - ...a628ffaf58480736b1079fd1031d14464180e0.asc | 26 - .../e0/6064b04a9dc688e0.fingerprints | 1 - .../e0/d1031d14464180e0.fingerprints | 1 - .../e1/72fefd1572eb75e1.fingerprints | 1 - ...e94e8ff0ab5af3b6f8366972fefd1572eb75e1.asc | 16 - .../e1/a9b9c4370b8833e1.fingerprints | 1 - .../e2/56028df552ba32e2.fingerprints | 1 - ...7ea1255546d6070a23aa00db45d3a62a183ce2.asc | 16 - ...36a28bc89fb250272c9f7f56028df552ba32e2.asc | 38 - .../e2/db45d3a62a183ce2.fingerprints | 1 - .../e3/2f3c9eeb05d1d1e3.fingerprints | 1 - .../e3/875c3ef9dc4638e3.fingerprints | 1 - ...c4b9dc1e031f788b12882b875c3ef9dc4638e3.asc | 16 - .../e3/e95b8ff73f6b84e3.fingerprints | 1 - ...85b3eff60b1b1bea94e0bb7c25280eae63ebe5.asc | 24 - .../e5/4713a6a15a6ea9e5.fingerprints | 1 - .../e5/7c25280eae63ebe5.fingerprints | 1 - .../e5/b4f772a2a08563e5.fingerprints | 1 - ...10792983023d5d14c93b488d7f1bec1e2ecae7.asc | 16 - .../e7/8d7f1bec1e2ecae7.fingerprints | 1 - .../e7/b0f3710fa64900e7.fingerprints | 1 - ...be5bcc9fec15518cfda882b0f3710fa64900e7.asc | 16 - .../e8/4f89f83c363d30e8.fingerprints | 1 - .../e8/f20db7fef61ce1e8.fingerprints | 1 - .../e9/cff46ee3c17e53e9.fingerprints | 1 - .../ea/4044edf1bb73efea.fingerprints | 1 - .../ea/5b93f1df7cdb6dea.fingerprints | 1 - ...9a30ff22b2c02f30261c305b93f1df7cdb6dea.asc | 26 - .../eb/995efbf4a3d20beb.fingerprints | 1 - ...bc987d1a7b91db6b0aaa81995efbf4a3d20beb.asc | 26 - .../ec/1723844cf9a045ec.fingerprints | 1 - .../ec/2290c86d79f1dfec.fingerprints | 1 - ...39e2a1eb9bc4e78f403b22f6bc09712c8df6ec.asc | 16 - .../ec/f6bc09712c8df6ec.fingerprints | 1 - .../ed/9a259c7ee636c5ed.fingerprints | 1 - .../ed/d3dbc823be4819ed.fingerprints | 1 - ...7417ac194160a3fabd04969a259c7ee636c5ed.asc | 26 - ...e770230e69cc6de143fb5b62c82e50836eb3ee.asc | 26 - .../ee/62c82e50836eb3ee.fingerprints | 1 - .../ef/ab049df4ab24c1ef.fingerprints | 1 - ...6260c72df74e66268f7770ab049df4ab24c1ef.asc | 26 - ...d6ff19930adf43ac127792a50569c7ca7fa1f0.asc | 24 - .../f0/a50569c7ca7fa1f0.fingerprints | 1 - .../f1/603dbe5f5c9fa9f1.fingerprints | 1 - .../f2/fc51362145a05ff2.fingerprints | 1 - .../f4/73f7734b17ec71f4.fingerprints | 1 - .../f4/92bf193ad552c6f4.fingerprints | 1 - .../f8/bf935c771a8474f8.fingerprints | 1 - .../f8/c9f04e6e2dc4f7f8.fingerprints | 1 - ...5e5f90e43e837f9dd8c781bf935c771a8474f8.asc | 26 - .../f9/9f7335d63326e7f9.fingerprints | 1 - ...13f67d71beec23add0ce0acb43338e060cf9fa.asc | 26 - .../fa/cb43338e060cf9fa.fingerprints | 1 - .../fb/3d5839a2262cbbfb.fingerprints | 1 - .../fb/cfa67e8582f7e9fb.fingerprints | 1 - .../fc/4006cba6d352f1fc.fingerprints | 1 - ...ef9646901a1652e5b3e5ea694db6f419c8d1fd.asc | 16 - .../fd/694db6f419c8d1fd.fingerprints | 1 - .../fe/891e4c2d471515fe.fingerprints | 1 - gradle/verification-keyring.keys | 7353 +++++++++++++++++ gradle/verification-metadata.xml | 1125 +++ gradle/wrapper/gradle-wrapper.jar | Bin 43504 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 9 +- gradlew.bat | 4 +- lib/aareadme.txt | 301 - renovate.json | 171 +- settings.gradle.kts | 106 +- src/bom-testing/build.gradle.kts | 36 +- src/bom-thirdparty/build.gradle.kts | 135 +- src/bom/build.gradle.kts | 6 +- .../apache/jmeter/util/BeanShellClient.java | 6 +- src/components/build.gradle.kts | 22 +- .../jmeter/assertions/BSFAssertion.java | 2 +- .../assertions/CompareAssertionBeanInfo.java | 14 +- .../jmeter/assertions/HTMLAssertion.java | 14 +- .../jmeter/assertions/JSONPathAssertion.java | 7 +- .../jmeter/assertions/JSR223Assertion.java | 2 +- .../jmeter/assertions/ResponseAssertion.java | 42 +- .../jmeter/assertions/SMIMEAssertion.java | 12 +- .../jmeter/assertions/SizeAssertion.java | 108 +- .../jmeter/assertions/XMLAssertion.java | 2 +- .../jmeter/assertions/XMLSchemaAssertion.java | 21 +- .../jmeter/assertions/XPath2Assertion.java | 4 +- .../jmeter/assertions/XPathAssertion.java | 4 +- .../jmeter/assertions/gui/AssertionGui.java | 5 +- .../assertions/gui/DurationAssertionGui.java | 6 +- .../assertions/gui/JSONPathAssertionGui.java | 6 +- .../assertions/gui/MD5HexAssertionGUI.java | 9 +- .../assertions/gui/SizeAssertionGui.java | 50 +- .../assertions/gui/XPath2AssertionGui.java | 3 +- .../assertions/gui/XPathAssertionGui.java | 3 +- .../jmespath/JMESPathAssertion.java | 3 - .../jmespath/gui/JMESPathAssertionGui.java | 6 +- .../org/apache/jmeter/config/CSVDataSet.java | 99 +- .../jmeter/config/CSVDataSetBeanInfo.java | 50 +- .../apache/jmeter/config/KeystoreConfig.java | 8 +- .../jmeter/config/KeystoreConfigBeanInfo.java | 12 +- .../jmeter/config/RandomVariableConfig.java | 27 +- .../config/RandomVariableConfigBeanInfo.java | 18 +- .../control/CriticalSectionController.java | 7 +- .../jmeter/control/ForeachController.java | 2 +- .../jmeter/control/IncludeController.java | 3 +- .../jmeter/control/SwitchController.java | 6 +- .../gui/CriticalSectionControllerGui.java | 6 +- .../control/gui/ForeachControlPanel.java | 3 +- .../control/gui/ModuleControllerGui.java | 2 +- .../jmeter/extractor/BSFPostProcessor.java | 2 +- .../jmeter/extractor/BoundaryExtractor.java | 20 +- .../extractor/DebugPostProcessorBeanInfo.java | 32 +- .../jmeter/extractor/HtmlExtractor.java | 6 +- .../jmeter/extractor/JSR223PostProcessor.java | 2 +- .../jmeter/extractor/JSoupExtractor.java | 4 +- .../jmeter/extractor/JoddExtractor.java | 4 +- .../jmeter/extractor/RegexExtractor.java | 12 +- .../jmeter/extractor/XPath2Extractor.java | 2 +- .../jmeter/extractor/XPathExtractor.java | 2 +- .../extractor/gui/BoundaryExtractorGui.java | 6 +- .../extractor/gui/HtmlExtractorGui.java | 6 +- .../extractor/gui/RegexExtractorGui.java | 6 +- .../extractor/gui/XPath2ExtractorGui.java | 3 +- .../extractor/gui/XPathExtractorGui.java | 3 +- .../json/jmespath/JMESPathCache.java | 4 +- .../json/jmespath/JMESPathExtractor.java | 9 +- .../jmespath/gui/JMESPathExtractorGui.java | 3 +- .../extractor/json/jsonpath/JSONManager.java | 4 +- .../json/jsonpath/JSONPostProcessor.java | 13 +- .../jsonpath/gui/JSONPostProcessorGui.java | 3 +- .../render/AbstractRenderAsJsonRenderer.java | 9 +- .../ExportTransactionAndSamplerNames.java | 5 +- .../jmeter/modifiers/BSFPreProcessor.java | 2 +- .../modifiers/BeanShellPreProcessor.java | 2 +- .../jmeter/modifiers/CounterConfig.java | 3 +- .../jmeter/modifiers/JSR223PreProcessor.java | 2 +- .../jmeter/modifiers/SampleTimeout.java | 3 +- .../jmeter/modifiers/UserParameters.java | 2 +- .../modifiers/gui/CounterConfigGui.java | 3 +- .../apache/jmeter/reporters/MailerModel.java | 20 +- .../jmeter/sampler/DebugSamplerBeanInfo.java | 24 +- .../org/apache/jmeter/sampler/TestAction.java | 4 +- .../jmeter/sampler/gui/TestActionGui.java | 27 +- .../org/apache/jmeter/timers/BSFTimer.java | 2 +- .../apache/jmeter/timers/BeanShellTimer.java | 2 +- .../timers/ConstantThroughputTimer.java | 112 +- .../ConstantThroughputTimerBeanInfo.java | 15 +- .../org/apache/jmeter/timers/JSR223Timer.java | 2 +- .../org/apache/jmeter/timers/SyncTimer.java | 12 +- .../jmeter/timers/SyncTimerBeanInfo.java | 4 +- .../ConstantPoissonProcessGenerator.java | 4 +- .../PreciseThroughputTimerBeanInfo.java | 16 +- .../ComponentTreeClonerForValidation.java | 3 +- .../jmeter/visualizers/BSFListener.java | 2 +- .../jmeter/visualizers/BeanShellListener.java | 2 +- .../visualizers/ComparisonVisualizer.java | 4 +- .../jmeter/visualizers/JSR223Listener.java | 2 +- .../jmeter/visualizers/MailerVisualizer.java | 3 +- .../RenderAsBoundaryExtractor.java | 10 +- .../jmeter/visualizers/RenderAsCssJQuery.java | 8 +- .../jmeter/visualizers/RenderAsHTML.java | 25 +- .../jmeter/visualizers/RenderAsJSON.java | 17 +- .../jmeter/visualizers/RenderAsRegexp.java | 6 +- .../jmeter/visualizers/RenderAsXML.java | 43 +- .../jmeter/visualizers/RenderAsXPath.java | 10 +- .../jmeter/visualizers/RenderAsXPath2.java | 10 +- .../jmeter/visualizers/RenderInBrowser.java | 3 - .../jmeter/visualizers/RequestViewRaw.java | 23 +- .../visualizers/RespTimeGraphChart.java | 2 +- .../visualizers/RespTimeGraphVisualizer.java | 16 +- .../jmeter/visualizers/SamplerResultTab.java | 28 +- .../visualizers/SearchTextExtension.java | 2 +- .../jmeter/visualizers/SearchTreePanel.java | 8 +- .../visualizers/StatGraphVisualizer.java | 26 +- .../jmeter/visualizers/StatVisualizer.java | 4 +- .../jmeter/visualizers/TreeNodeRenderer.java | 4 +- .../ViewResultsFullVisualizer.java | 51 +- .../XMLDefaultMutableTreeNode.java | 31 +- .../visualizers/backend/BackendListener.java | 10 +- .../backend/BackendListenerGui.java | 7 +- .../visualizers/backend/ErrorMetric.java | 12 +- .../visualizers/backend/SamplerMetric.java | 16 +- .../AbstractGraphiteMetricsSender.java | 5 +- .../GraphiteBackendListenerClient.java | 4 +- .../graphite/PickleGraphiteMetricsSender.java | 13 - .../graphite/TextGraphiteMetricsSender.java | 9 - .../backend/influxdb/HttpMetricsSender.java | 19 +- .../InfluxDBRawBackendListenerClient.java | 26 +- .../InfluxdbBackendListenerClient.java | 10 +- .../timers/ConstantThroughputTimerSchema.kt | 39 + ...onstantThroughputTimerResources.properties | 2 +- ...tantThroughputTimerResources_de.properties | 2 +- ...tantThroughputTimerResources_es.properties | 2 +- ...tantThroughputTimerResources_fr.properties | 2 +- ...tantThroughputTimerResources_ko.properties | 2 +- ...tThroughputTimerResources_pt_BR.properties | 2 +- ...tantThroughputTimerResources_tr.properties | 2 +- ...tThroughputTimerResources_zh_CN.properties | 2 +- .../jmeter/assertions/SizeAssertionTest.java | 18 +- .../assertions/TestJSONPathAssertion.java | 27 +- .../assertions/XMLSchemaAssertionTest.java | 11 +- .../jmeter/assertions/XPathAssertionTest.java | 7 +- .../jmeter/assertions/XmlAssertionTest.java | 14 +- .../jmespath/TestJMESPathAssertion.java | 24 +- .../apache/jmeter/config/TestCVSDataSet.java | 2 +- .../jmeter/control/TestWhileController.java | 6 +- .../extractor/TestBoundaryExtractor.java | 29 +- .../extractor/TestHtmlExtractorJSoup.java | 22 +- .../extractor/TestJSONPostProcessor.java | 122 +- .../jmeter/extractor/TestRegexExtractor.java | 31 +- .../jmeter/extractor/TestXPath2Extractor.java | 2 +- .../jmeter/extractor/TestXPathExtractor.java | 37 +- .../json/jmespath/TestJMESPathExtractor.java | 126 +- .../action/template/TestTemplateManager.java | 8 +- .../timers/ConstantThroughputTimerTest.java | 14 +- .../apache/jmeter/timers/SyncTimerTest.java | 4 +- .../jmeter/timers/TimerServiceTest.java | 46 +- .../visualizers/TestSampleCompareTo.java | 8 +- .../backend/SamplerMetricFixedModeTest.java | 10 +- .../backend/SamplerMetricTimedModeTest.java | 8 +- .../influxdb/HttpMetricsSenderTest.java | 9 +- .../assertions/AssertionResultExtensions.kt | 34 + .../jmeter/assertions/CompareAssertionTest.kt | 1 - src/core/build.gradle.kts | 32 +- .../main/java/org/apache/jmeter/JMeter.java | 225 +- .../org/apache/jmeter/JMeterGuiLauncher.kt | 3 +- .../org/apache/jmeter/ProxyAuthenticator.java | 11 +- .../java/org/apache/jmeter/SplashScreen.java | 2 +- .../assertions/CompareAssertionResult.java | 6 +- .../org/apache/jmeter/config/Argument.java | 9 +- .../org/apache/jmeter/config/Arguments.java | 16 +- .../jmeter/config/gui/ArgumentsPanel.java | 11 +- .../jmeter/config/gui/RowDetailDialog.java | 27 +- .../jmeter/control/GenericController.java | 12 +- .../apache/jmeter/control/IfController.java | 18 +- .../org/apache/jmeter/control/RunTime.java | 2 +- .../jmeter/control/TransactionController.java | 3 +- .../jmeter/control/gui/IfControllerPanel.java | 6 +- .../jmeter/control/gui/LoopControlPanel.java | 12 +- .../apache/jmeter/control/gui/RunTimeGui.java | 12 +- .../jmeter/control/gui/TestPlanGui.java | 6 +- .../control/gui/WhileControllerGui.java | 12 +- .../jmeter/control/gui/WorkBenchGui.java | 3 +- .../jmeter/engine/ClientJMeterEngine.java | 13 +- .../jmeter/engine/ConvertListeners.java | 4 +- .../org/apache/jmeter/engine/PreCompiler.java | 22 +- .../jmeter/engine/RemoteJMeterEngineImpl.java | 4 - .../jmeter/engine/StandardJMeterEngine.java | 64 +- .../org/apache/jmeter/engine/TreeCloner.java | 4 +- .../apache/jmeter/engine/TurnElementsOn.java | 4 +- .../jmeter/engine/util/CompoundVariable.java | 13 +- .../jmeter/engine/util/FunctionParser.java | 18 +- .../util/ReplaceFunctionsWithStrings.java | 6 +- .../engine/util/UndoVariableReplacement.java | 4 +- .../jmeter/engine/util/ValueReplacer.java | 3 +- .../jmeter/functions/AbstractFunction.java | 4 +- .../jmeter/functions/gui/FunctionHelper.java | 7 +- .../gui/AbstractJMeterGuiComponent.java | 9 +- .../org/apache/jmeter/gui/GuiPackage.java | 20 +- .../org/apache/jmeter/gui/HtmlReportUI.java | 52 +- .../apache/jmeter/gui/JMeterGUIComponent.java | 7 +- .../org/apache/jmeter/gui/LoggerPanel.java | 14 +- .../java/org/apache/jmeter/gui/MainFrame.java | 21 +- .../java/org/apache/jmeter/gui/NamePanel.java | 16 +- .../jmeter/gui/action/AbstractAction.java | 27 +- .../jmeter/gui/action/ChangeParent.java | 4 +- .../org/apache/jmeter/gui/action/Clear.java | 7 +- .../gui/action/CompileJSR223TestElements.java | 14 +- .../org/apache/jmeter/gui/action/Copy.java | 3 - .../apache/jmeter/gui/action/Duplicate.java | 3 - .../org/apache/jmeter/gui/action/Help.java | 13 +- .../jmeter/gui/action/LoadRecentProject.java | 45 +- .../jmeter/gui/action/LookAndFeelCommand.java | 2 +- .../jmeter/gui/action/RawTextSearcher.java | 8 +- .../jmeter/gui/action/RegexpSearcher.java | 4 +- .../org/apache/jmeter/gui/action/Restart.java | 4 +- .../org/apache/jmeter/gui/action/Save.java | 144 +- .../jmeter/gui/action/SaveGraphics.java | 4 +- .../jmeter/gui/action/SchematicView.java | 4 +- .../jmeter/gui/action/SearchTreeDialog.java | 63 +- .../org/apache/jmeter/gui/action/Start.java | 17 +- .../jmeter/gui/action/StopStoppables.java | 9 - .../gui/action/template/TemplateManager.java | 4 +- .../validation/TreeClonerForValidation.java | 6 +- .../gui/logging/GuiLogEventAppender.java | 3 +- .../jmeter/gui/logging/LogEventObject.java | 4 +- .../gui/menu/StaticJMeterGUIComponent.java | 2 +- .../jmeter/gui/tree/JMeterCellRenderer.java | 7 +- .../jmeter/gui/tree/JMeterTreeModel.java | 3 +- .../gui/tree/JMeterTreeTransferHandler.java | 2 +- .../apache/jmeter/gui/util/FileDialoger.java | 6 +- .../util/HeaderAsPropertyRendererWrapper.java | 3 +- .../jmeter/gui/util/HorizontalPanel.java | 4 +- .../apache/jmeter/gui/util/JDateField.java | 6 +- .../apache/jmeter/gui/util/JMeterMenuBar.java | 13 +- .../apache/jmeter/gui/util/JMeterToolBar.java | 34 +- .../gui/util/NumberFieldErrorListener.java | 8 +- .../jmeter/gui/util/PowerTableModel.java | 2 +- .../gui/util/TextAreaTableCellEditor.java | 4 +- .../jmeter/gui/util/TristateCheckBox.java | 18 +- .../apache/jmeter/gui/util/VerticalPanel.java | 4 +- .../config/ReportGeneratorConfiguration.java | 21 +- .../core/ControllerSamplePredicate.java | 7 - .../jmeter/report/core/CsvSampleReader.java | 12 +- .../jmeter/report/core/CsvSampleWriter.java | 12 +- .../apache/jmeter/report/core/JsonUtil.java | 6 +- .../org/apache/jmeter/report/core/Sample.java | 8 +- .../jmeter/report/core/SampleMetadata.java | 8 +- .../dashboard/AbstractDataExporter.java | 24 +- .../dashboard/HtmlTemplateExporter.java | 43 +- .../jmeter/report/dashboard/JsonExporter.java | 10 +- .../report/dashboard/JsonizerVisitor.java | 21 - .../report/dashboard/ReportGenerator.java | 9 +- .../report/dashboard/TemplateVisitor.java | 31 +- .../processor/AbstractAggregatorFactory.java | 20 - .../processor/AbstractSampleProcessor.java | 27 - .../processor/AbstractSampleSource.java | 38 - .../processor/AbstractSummaryConsumer.java | 17 - .../report/processor/AggregateConsumer.java | 17 - .../processor/ApdexSummaryConsumer.java | 26 +- .../report/processor/CsvFileSampleSource.java | 32 +- .../processor/ErrorsSummaryConsumer.java | 48 +- .../processor/ExternalSampleSorter.java | 6 +- .../report/processor/ListResultData.java | 15 - .../report/processor/MapResultData.java | 10 - .../report/processor/MaxAggregator.java | 20 - .../processor/MaxAggregatorFactory.java | 7 - .../report/processor/MeanAggregator.java | 20 - .../processor/MeanAggregatorFactory.java | 7 - .../processor/MedianAggregatorFactory.java | 7 - .../report/processor/MinAggregator.java | 20 - .../processor/PercentileAggregator.java | 20 - .../PercentileAggregatorFactory.java | 7 - .../processor/RequestsSummaryConsumer.java | 11 - .../processor/StatisticsSummaryConsumer.java | 29 - .../report/processor/SumAggregator.java | 20 - .../processor/SumAggregatorFactory.java | 7 - .../report/processor/TimeRateAggregator.java | 20 - .../processor/TimeRateAggregatorFactory.java | 7 - .../Top5ErrorsBySamplerConsumer.java | 21 - .../report/processor/ValueResultData.java | 10 - .../graph/AbstractGraphConsumer.java | 29 +- .../graph/AbstractOverTimeGraphConsumer.java | 18 - .../graph/AbstractSeriesSelector.java | 11 +- .../AbstractVersusRequestsGraphConsumer.java | 63 +- .../processor/graph/CodeSeriesSelector.java | 7 - .../graph/ConnectTimeValueSelector.java | 7 - .../graph/ElapsedTimeValueSelector.java | 5 - .../processor/graph/IndexedNameSelector.java | 7 - .../processor/graph/LatencyValueSelector.java | 7 - .../processor/graph/NameSeriesSelector.java | 7 - .../processor/graph/StaticSeriesSelector.java | 7 - .../processor/graph/StatusSeriesSelector.java | 7 - .../graph/TimeStampKeysSelector.java | 7 - .../impl/ActiveThreadsGraphConsumer.java | 13 - .../impl/BytesThroughputGraphConsumer.java | 13 - .../impl/CodesPerSecondGraphConsumer.java | 13 - .../ConnectTimeOverTimeGraphConsumer.java | 13 - .../graph/impl/CustomGraphConsumer.java | 17 +- .../impl/HitsPerSecondGraphConsumer.java | 13 - .../impl/LatencyOverTimeGraphConsumer.java | 13 - .../impl/LatencyVSRequestGraphConsumer.java | 12 - .../ResponseCustomGraphGraphConsumer.java | 13 - ...ResponseTimeDistributionGraphConsumer.java | 18 - .../ResponseTimeOverTimeGraphConsumer.java | 13 - .../ResponseTimePerSampleGraphConsumer.java | 18 - .../ResponseTimePercentilesGraphConsumer.java | 18 - .../ResponseTimeVSRequestGraphConsumer.java | 12 - ...ResponseTimeDistributionGraphConsumer.java | 14 +- .../graph/impl/TimeVSThreadGraphConsumer.java | 18 - .../graph/impl/TotalTPSGraphConsumer.java | 13 - .../TransactionsPerSecondGraphConsumer.java | 13 - .../jmeter/report/utils/MetricUtils.java | 4 +- .../jmeter/reporters/ResultCollector.java | 5 +- .../apache/jmeter/reporters/ResultSaver.java | 2 +- .../apache/jmeter/reporters/Summariser.java | 19 +- .../java/org/apache/jmeter/rmi/RmiUtils.java | 13 +- .../jmeter/samplers/AsynchSampleSender.java | 6 +- .../samplers/DiskStoreSampleSender.java | 4 +- .../samplers/RemoteSampleListenerImpl.java | 8 +- .../apache/jmeter/samplers/SampleResult.java | 28 +- .../samplers/SampleSaveConfiguration.java | 24 +- .../apache/jmeter/save/CSVSaveService.java | 145 +- .../org/apache/jmeter/save/SaveService.java | 73 +- .../save/converters/ConversionHelp.java | 8 +- .../converters/SampleResultConverter.java | 26 +- .../SampleSaveConfigurationConverter.java | 25 +- .../converters/StringPropertyConverter.java | 5 +- .../save/converters/TestElementConverter.java | 2 +- .../TestResultWrapperConverter.java | 3 +- .../apache/jmeter/services/FileServer.java | 31 +- .../org/apache/jmeter/swing/HtmlPane.java | 2 +- .../jmeter/testbeans/TestBeanHelper.java | 21 +- .../testbeans/gui/BooleanPropertyEditor.java | 8 +- .../testbeans/gui/ComboStringEditor.java | 21 +- .../jmeter/testbeans/gui/EnumEditor.java | 71 +- .../testbeans/gui/FieldStringEditor.java | 4 +- .../jmeter/testbeans/gui/FileEditor.java | 4 +- .../gui/GenericTestBeanCustomizer.java | 176 +- .../testbeans/gui/IntegerPropertyEditor.java | 4 +- .../testbeans/gui/LongPropertyEditor.java | 4 +- .../jmeter/testbeans/gui/PasswordEditor.java | 4 +- .../jmeter/testbeans/gui/TableEditor.java | 18 +- .../jmeter/testbeans/gui/TestBeanGUI.java | 6 +- .../jmeter/testbeans/gui/TextAreaEditor.java | 3 +- .../jmeter/testbeans/gui/WrapperEditor.java | 10 +- .../testelement/AbstractTestElement.java | 23 +- .../apache/jmeter/testelement/TestPlan.java | 7 +- .../property/AbstractProperty.java | 59 +- .../testelement/property/BooleanProperty.java | 4 +- .../property/CollectionProperty.java | 20 +- .../property/FunctionProperty.java | 4 +- .../testelement/property/MapProperty.java | 4 +- .../testelement/property/MultiProperty.java | 4 +- .../testelement/property/NumberProperty.java | 4 +- .../property/TestElementProperty.java | 4 +- .../FindTestElementsUpToRootTraverser.java | 4 +- .../apache/jmeter/threads/JMeterThread.java | 74 +- .../jmeter/threads/JMeterVariables.java | 4 +- .../threads/RemoteThreadsListenerImpl.java | 3 - .../apache/jmeter/threads/TestCompiler.java | 66 +- .../apache/jmeter/threads/ThreadGroup.java | 2 +- .../jmeter/threads/gui/ThreadGroupGui.java | 4 +- .../apache/jmeter/timers/TimerService.java | 2 +- .../jmeter/util/BSFJavaScriptEngine.java | 32 +- .../apache/jmeter/util/BSFTestElement.java | 12 +- .../jmeter/util/BeanShellBeanInfoSupport.java | 14 +- .../jmeter/util/BeanShellInterpreter.java | 171 +- .../apache/jmeter/util/BeanShellServer.java | 6 +- .../jmeter/util/BeanShellTestElement.java | 18 +- .../jmeter/util/CustomX509TrustManager.java | 28 +- .../java/org/apache/jmeter/util/Document.java | 2 +- .../util/HttpSSLProtocolSocketFactory.java | 5 +- .../org/apache/jmeter/util/JMeterUtils.java | 21 +- .../apache/jmeter/util/JSR223TestElement.java | 53 +- .../apache/jmeter/util/JsseSSLManager.java | 8 +- .../util/PropertiesBasedPrefixResolver.java | 4 +- .../org/apache/jmeter/util/SSLManager.java | 2 +- .../apache/jmeter/util/ScriptCacheKey.java | 22 +- .../jmeter/util/ScriptingBeanInfoSupport.java | 16 +- .../apache/jmeter/util/StringUtilities.java | 19 +- .../jmeter/util/XPathQueryCacheLoader.java | 10 +- .../org/apache/jmeter/util/XPathUtil.java | 78 +- .../jmeter/util/keystore/JmeterKeyStore.java | 26 +- .../org/apache/jmeter/visualizers/Sample.java | 4 +- .../jmeter/visualizers/TableSample.java | 4 +- .../visualizers/gui/AbstractVisualizer.java | 3 +- .../apache/jmeter/save/JMeterStaxDriver.kt | 29 + .../jmeter/save/XMLOutputFactoryDelegate.kt | 110 + .../jmeter/save/XMLStreamWriterSkipHeader.kt} | 32 +- .../testelement/schema/PropertyDescriptor.kt | 1 + .../java/org/apache/jmeter/JMeterTest.java | 86 +- .../jmeter/engine/DistributedRunnerTest.java | 4 +- .../apache/jmeter/engine/LocalHostTest.java | 13 +- .../jmeter/engine/util/TestValueReplacer.java | 4 +- .../gui/logging/TestGuiLogEventAppender.java | 4 +- .../report/core/CsvSampleReaderTest.java | 11 +- .../processor/ErrorsSummaryConsumerTest.java | 2 +- .../graph/impl/CustomGraphConsumerTest.java | 54 +- .../TestDataStrippingSampleSender.java | 9 +- .../jmeter/samplers/TestSampleResult.java | 10 +- .../samplers/TestSampleSaveConfiguration.java | 1 - .../jmeter/save/TestCSVSaveService.java | 4 +- .../gui/TestBooleanPropertyEditor.java | 8 +- .../testbeans/gui/TestComboStringEditor.java | 4 +- .../testbeans/gui/TestFieldStringEditor.java | 4 +- .../property/AbstractPropertyTest.java | 25 +- .../testelement/property/MapPropertyTest.java | 7 +- .../testelement/property/PackageTest.java | 4 +- .../jmeter/threads/TestJMeterThread.java | 6 +- .../TestUnmodifiableJMeterVariables.java | 32 +- .../jmeter/util/JSR223TestElementTest.java | 19 +- .../util/SecurityProviderLoaderTest.java | 2 +- .../jmeter/util/StringUtilitiesTest.java | 9 +- .../org/apache/jmeter/util/XPathUtilTest.java | 24 +- .../gui/AbstractJMeterGuiComponentTest.kt | 6 +- .../processor/Top5ErrorsSummaryDataTest.kt | 8 +- .../apache/jmeter/junit/JMeterTestCase.java | 4 +- .../apache/jmeter/resources/PackageTest.java | 28 +- .../treebuilder/TreeBuilderExtensions.kt | 34 + src/dist-check/build.gradle.kts | 14 +- .../jmeter/control/TestSwitchController.java | 18 +- .../ComponentReferenceFunctionTest.java | 20 +- .../apache/jmeter/gui/action/TestLoad.java | 7 +- .../org/apache/jmeter/junit/JMeterTest.java | 36 +- .../apache/jmeter/save/TestSaveService.java | 248 +- .../jmeter/testelement/TestElementTest.java | 5 +- .../jmeter/threads/ThreadGroupLoad.java | 83 +- .../jorphan/reflect/TestClassFinder.java | 16 +- .../gui/action/HtmlReportGeneratorTest.kt | 73 +- .../report/dashboard/ReportGeneratorTest.kt | 13 +- src/dist/build.gradle.kts | 106 +- src/dist/src/dist/expected_release_jars.csv | 246 +- .../sampler/gui/ExampleSamplerGui.java | 2 +- .../testbeans/example3/Example3BeanInfo.java | 8 +- src/functions/build.gradle.kts | 13 +- .../jmeter/functions/AbstractHostIPName.java | 8 +- .../apache/jmeter/functions/BeanShell.java | 11 +- .../apache/jmeter/functions/ChangeCase.java | 33 +- .../apache/jmeter/functions/CharFunction.java | 4 +- .../functions/DateTimeConvertFunction.java | 3 +- .../functions/DigestEncodeFunction.java | 31 +- .../apache/jmeter/functions/EscapeHtml.java | 8 +- .../apache/jmeter/functions/EscapeXml.java | 6 +- .../jmeter/functions/FileRowColContainer.java | 3 +- .../apache/jmeter/functions/FileToString.java | 12 +- .../apache/jmeter/functions/FileWrapper.java | 8 +- .../org/apache/jmeter/functions/Groovy.java | 8 +- .../apache/jmeter/functions/IsVarDefined.java | 2 +- .../jmeter/functions/IterationCounter.java | 2 +- .../apache/jmeter/functions/JavaScript.java | 2 +- .../jmeter/functions/Jexl2Function.java | 2 +- .../jmeter/functions/Jexl3Function.java | 5 +- .../apache/jmeter/functions/LogFunction.java | 26 +- .../apache/jmeter/functions/LogFunction2.java | 2 +- .../org/apache/jmeter/functions/LongSum.java | 3 +- .../org/apache/jmeter/functions/Property.java | 2 +- .../org/apache/jmeter/functions/Random.java | 2 +- .../apache/jmeter/functions/RandomDate.java | 20 +- .../functions/RandomFromMultipleVars.java | 8 +- .../apache/jmeter/functions/RandomString.java | 20 +- .../jmeter/functions/RegexFunction.java | 11 +- .../apache/jmeter/functions/SamplerName.java | 2 +- .../jmeter/functions/SplitFunction.java | 2 +- .../jmeter/functions/StringFromFile.java | 14 +- .../apache/jmeter/functions/StringToFile.java | 15 +- .../apache/jmeter/functions/TimeShift.java | 21 +- .../org/apache/jmeter/functions/UnEscape.java | 6 +- .../apache/jmeter/functions/UnEscapeHtml.java | 6 +- .../jmeter/functions/CSVReadFunctionTest.java | 4 +- .../jmeter/functions/RandomFunctionTest.java | 2 +- .../functions/StringFromFileFunctionTest.java | 7 +- .../jmeter/functions/SumFunctionTest.java | 2 +- .../apache/jmeter/functions/Synchronizer.java | 8 +- .../functions/TestRandomFromMultipleVars.java | 25 +- .../jmeter/functions/TestStringtoFile.java | 5 +- .../functions/TestTimeRandomDateFunction.java | 28 +- .../functions/TestTimeShiftFunction.java | 48 +- .../functions/XPathConcurrencyTest.java | 3 +- .../apache/jmeter/functions/ChangeCaseTest.kt | 3 +- .../jmeter/functions/FunctionServicesTest.kt | 3 +- .../jmeter/functions/IterationCounterTest.kt | 3 +- .../timers/ConstantThroughputTimerKtTest.kt | 17 +- src/jorphan/build.gradle.kts | 4 - .../apache/commons/cli/avalon/CLOption.java | 2 +- .../org/apache/jorphan/collections/Data.java | 8 +- .../apache/jorphan/collections/HashTree.java | 21 +- .../jorphan/collections/ListedHashTree.java | 3 +- .../org/apache/jorphan/exec/KeyToolUtils.java | 20 +- .../org/apache/jorphan/exec/StreamCopier.java | 3 +- .../apache/jorphan/exec/SystemCommand.java | 5 +- .../jorphan/gui/AbstractTreeTableModel.java | 4 +- .../org/apache/jorphan/gui/DynamicStyle.java | 18 +- .../org/apache/jorphan/gui/FocusActions.java | 4 +- .../org/apache/jorphan/gui/MenuScroller.java | 6 +- .../jorphan/gui/MinMaxLongRenderer.java | 4 +- .../apache/jorphan/gui/ObjectTableSorter.java | 16 +- .../org/apache/jorphan/gui/RateRenderer.java | 5 +- .../jorphan/gui/ui/KerningOptimizer.java | 4 +- .../jorphan/gui/ui/TextComponentUI.java | 4 +- .../java/org/apache/jorphan/io/TextFile.java | 3 +- .../apache/jorphan/locale/LocaleUtils.java | 176 + .../apache/jorphan/math/StatCalculator.java | 23 +- .../apache/jorphan/reflect/ClassFinder.java | 5 +- .../apache/jorphan/reflect/ClassTools.java | 7 +- .../org/apache/jorphan/util/BooleanUtils.java | 60 + .../org/apache/jorphan/util/CharUtils.java} | 22 +- .../org/apache/jorphan/util/Converter.java | 50 +- .../apache/jorphan/util/ExceptionUtils.java | 78 + .../org/apache/jorphan/util/HeapDumper.java | 8 +- .../org/apache/jorphan/util/JOrphanUtils.java | 64 +- .../apache/jorphan/util/StringUtilities.java | 206 +- .../org/apache/jorphan/util/StringWrap.java | 4 +- .../org/apache/jorphan/util/XMLBuffer.java | 2 +- .../jorphan/collections/FilterIterator.kt | 71 + .../org/apache/jorphan/io/BomInputStream.kt | 93 + .../apache/jorphan/io/CountingInputStream.kt | 83 + .../apache/jorphan/io/CountingOutputStream.kt | 50 + .../io/DirectAccessByteArrayOutputStream.kt | 133 + .../org/apache/jorphan/io/TeeOutputStream.kt | 117 + .../jorphan/text/RandomStringGenerator.kt | 265 + .../org/apache/jorphan/util/EnumUtils.kt | 72 + .../commons/cli/avalon/ClutilTestCase.java | 2 +- .../apache/jorphan/exec/TestKeyToolUtils.java | 2 +- .../jorphan/gui/MinMaxLongRendererTest.java | 6 +- .../jorphan/gui/ObjectTableSorterTest.java | 27 +- .../util/TestAlphaNumericKeyComparator.java | 2 +- .../apache/jorphan/util/TestJorphanUtils.java | 102 +- .../jorphan/collections/FilterIteratorTest.kt | 150 + .../DirectAccessByteArrayOutputStreamTest.kt | 76 + .../apache/jorphan/io/TeeOutputStreamTest.kt | 50 + .../jorphan/text/RandomStringGeneratorTest.kt | 258 + .../apache/jorphan/util/BooleanUtilsTest.kt | 62 + .../java/org/apache/jmeter/NewDriver.java | 14 +- src/licenses/build.gradle.kts | 15 +- src/licenses/licenses/hamcrest-date/LICENSE | 24 - src/licenses/licenses/hamcrest/LICENSE | 27 - src/licenses/licenses/svgSalamander/LICENSE | 28 - src/protocol/bolt/build.gradle.kts | 1 - .../config/BoltConnectionElementBeanInfo.java | 8 +- .../bolt/sampler/AbstractBoltTestElement.java | 10 +- .../protocol/bolt/sampler/BoltSampler.java | 11 +- .../BoltTestElementBeanInfoSupport.java | 14 +- src/protocol/ftp/build.gradle.kts | 6 - .../protocol/ftp/sampler/FTPSampler.java | 29 +- src/protocol/http/build.gradle.kts | 16 +- .../entity/mime/MultipartEntityBuilder2.java | 228 + .../http/config/MultipartUrlConfig.java | 6 +- .../http/config/gui/GraphQLUrlConfigGui.java | 20 +- .../http/config/gui/HttpDefaultsGui.java | 6 +- .../http/config/gui/UrlConfigGui.java | 7 +- .../protocol/http/control/AuthManager.java | 4 +- .../protocol/http/control/CacheManager.java | 57 +- .../protocol/http/control/CookieManager.java | 9 +- .../http/control/DNSCacheManager.java | 94 +- .../control/DelegatingKerberosScheme.java | 4 +- .../http/control/DelegatingSPNegoScheme.java | 4 +- .../control/DynamicKerberosSchemeFactory.java | 4 +- .../control/DynamicSPNegoSchemeFactory.java | 4 +- .../protocol/http/control/HeaderManager.java | 6 +- .../http/control/HttpMirrorServer.java | 6 +- .../http/control/KerberosManager.java | 6 +- .../control/gui/HttpMirrorControlGui.java | 4 +- .../http/control/gui/HttpTestSampleGui.java | 9 +- .../protocol/http/curl/ArgumentHolder.java | 8 +- .../protocol/http/curl/BasicCurlParser.java | 132 +- .../http/curl/FileArgumentHolder.java | 5 +- .../http/curl/StringArgumentHolder.java | 6 +- .../jmeter/protocol/http/gui/AuthPanel.java | 70 +- .../jmeter/protocol/http/gui/CookiePanel.java | 3 +- .../protocol/http/gui/DNSCachePanel.java | 3 +- .../protocol/http/gui/HTTPArgumentsPanel.java | 26 +- .../protocol/http/gui/HTTPFileArgsPanel.java | 10 +- .../jmeter/protocol/http/gui/HeaderPanel.java | 8 +- .../gui/action/ParseCurlCommandAction.java | 50 +- .../http/modifier/AnchorModifier.java | 6 +- .../http/modifier/RegExUserParameters.java | 6 +- .../http/modifier/URLRewritingModifier.java | 7 +- .../modifier/gui/RegExUserParametersGui.java | 6 +- .../protocol/http/parser/BaseParser.java | 4 +- .../protocol/http/parser/CssParser.java | 46 +- .../http/parser/CssParserCacheLoader.java | 17 +- .../protocol/http/parser/HTMLParser.java | 6 +- .../http/parser/HtmlParsingUtils.java | 7 +- .../protocol/http/parser/JTidyHTMLParser.java | 185 +- .../http/parser/JsoupBasedHtmlParser.java | 19 +- .../http/parser/LagartoBasedHtmlParser.java | 198 +- .../protocol/http/parser/URLCollection.java | 11 +- .../http/proxy/AbstractSamplerCreator.java | 4 +- .../http/proxy/DefaultSamplerCreator.java | 31 +- .../http/proxy/FormCharSetFinder.java | 4 +- .../protocol/http/proxy/HttpReplyHdr.java | 2 +- .../protocol/http/proxy/HttpRequestHdr.java | 6 +- .../jmeter/protocol/http/proxy/Proxy.java | 58 +- .../protocol/http/proxy/ProxyControl.java | 105 +- .../http/proxy/gui/ProxyControlGui.java | 43 +- .../http/proxy/gui/RecorderDialog.java | 6 +- .../http/sampler/AccessLogSampler.java | 24 +- .../sampler/AccessLogSamplerBeanInfo.java | 26 +- .../protocol/http/sampler/AjpSampler.java | 2 +- .../http/sampler/HTTPAbstractImpl.java | 38 +- .../protocol/http/sampler/HTTPFileImpl.java | 35 +- .../protocol/http/sampler/HTTPHC4Impl.java | 147 +- .../http/sampler/HTTPHCAbstractImpl.java | 17 +- .../protocol/http/sampler/HTTPJavaImpl.java | 19 +- .../http/sampler/HTTPSampleResult.java | 8 +- .../http/sampler/HTTPSamplerBase.java | 108 +- .../http/sampler/HTTPSamplerFactory.java | 6 +- .../http/sampler/HTTPSamplerProxy.java | 3 - .../sampler/HttpClientDefaultParameters.java | 4 +- .../protocol/http/sampler/PostWriter.java | 14 +- .../protocol/http/sampler/PutWriter.java | 9 +- .../http/sampler/ResourcesDownloader.java | 3 +- .../sampler/hc/LaxDeflateInputStream.java | 3 - .../http/sampler/hc/LaxGZIPInputStream.java | 3 - .../protocol/http/util/ConversionUtils.java | 28 +- .../http/util/GraphQLRequestParamUtils.java | 43 +- .../protocol/http/util/HTTPArgument.java | 10 +- .../protocol/http/util/HTTPFileArg.java | 12 +- .../http/util/HTTPResultConverter.java | 6 +- .../http/util/accesslog/SessionFilter.java | 5 - .../util/accesslog/SharedTCLogParser.java | 2 +- .../http/util/accesslog/TCLogParser.java | 2 +- .../http/visualizers/RequestViewHTTP.java | 39 +- .../jmeter/protocol/http/HttpClientState.kt} | 13 +- .../http/sampler/HTTPSamplerBaseSchema.kt | 2 +- .../jmeter/curl/BasicCurlParserTest.java | 63 +- .../action/ParseCurlCommandActionTest.java | 10 +- .../http/config/MultipartUrlConfigTest.java | 104 +- .../protocol/http/control/DnsManagerTest.java | 62 - .../http/control/TestAuthorization.java | 5 +- .../http/control/TestCacheManagerBase.java | 2 +- .../http/control/TestCacheManagerHC4.java | 15 - .../TestCacheManagerThreadIteration.java | 28 - .../TestCacheManagerUrlConnection.java | 2 +- .../http/control/TestHTTPMirrorThread.java | 7 +- .../http/modifier/TestAnchorModifier.java | 4 +- .../modifier/TestURLRewritingModifier.java | 2 +- .../http/parser/NotReusableParser.java | 4 +- .../protocol/http/parser/ReusableParser.java | 4 +- .../http/parser/TestBug60842HtmlParser.java | 10 +- .../protocol/http/parser/TestCssParser.java | 17 +- .../protocol/http/parser/TestHTMLParser.java | 83 +- .../http/proxy/TestHttpRequestHdr.java | 139 +- .../protocol/http/sampler/HTTPSampler3.java | 3 - .../protocol/http/sampler/PackageTest.java | 2 +- .../sampler/ParallelResourcesAndIpSource.java | 58 +- .../protocol/http/sampler/PostWriterTest.java | 42 +- .../http/sampler/SamplingNamingTest.java | 2 +- .../http/sampler/TestDecompression.java | 26 +- .../http/sampler/TestHTTPHC4Impl.java | 19 + ...stHTTPSamplersAgainstHttpMirrorServer.java | 114 +- .../protocol/http/sampler/TestRedirects.java | 4 +- .../sampler/TestSerializedHTTPSampler.java | 7 +- .../util/TestGraphQLRequestParamUtils.java | 39 +- .../protocol/http/util/TestHTTPArgument.java | 33 + .../protocol/http/util/TestHTTPFileArgs.java | 7 +- .../http/visualizers/RequestViewHTTPTest.java | 95 +- .../http/control/DNSCacheManagerTest.kt | 112 +- .../HttpSamplerDisableArgumentsTest.kt | 174 + .../http/sampler/HttpSamplerPrintDslTest.kt | 2 + .../protocol/http/sampler/HttpSamplerTest.kt | 38 +- .../http/sampler/HttpSamplerTestingUtils.kt | 40 + .../protocol/http/util/MockDnsServer.kt | 250 + .../control/HttpRequestDefaultsExtensions.kt | 53 + src/protocol/java/build.gradle.kts | 7 - .../java/config/gui/JavaConfigGui.java | 9 +- .../java/control/gui/JavaTestSamplerGui.java | 3 - .../protocol/java/sampler/BSFSampler.java | 12 +- .../protocol/java/sampler/JSR223Sampler.java | 2 +- .../protocol/java/sampler/JavaSampler.java | 8 +- .../java/sampler/JavaSamplerContext.java | 4 +- .../jmeter/protocol/java/test/JavaTest.java | 9 +- src/protocol/jdbc/build.gradle.kts | 6 - .../jdbc/AbstractJDBCTestElement.java | 127 +- .../jdbc/JDBCTestElementBeanInfoSupport.java | 24 +- .../jdbc/config/DataSourceElement.java | 33 +- .../config/DataSourceElementBeanInfo.java | 48 +- .../jdbc/processor/AbstractJDBCProcessor.java | 4 +- .../protocol/jdbc/sampler/JDBCSampler.java | 8 +- src/protocol/jms/build.gradle.kts | 10 +- .../org/apache/jmeter/protocol/jms/Utils.java | 31 +- .../jms/client/InitialContextFactory.java | 8 +- .../jmeter/protocol/jms/client/Publisher.java | 21 +- .../jms/client/ReceiveSubscriber.java | 39 +- .../jms/control/gui/JMSPropertiesPanel.java | 64 +- .../jms/control/gui/JMSSamplerGui.java | 6 +- .../protocol/jms/sampler/BaseJMSSampler.java | 18 +- .../jms/sampler/FixedQueueExecutor.java | 8 +- .../protocol/jms/sampler/JMSSampler.java | 69 +- .../protocol/jms/sampler/MessageAdmin.java | 10 +- .../jms/sampler/PublisherSampler.java | 54 +- .../protocol/jms/sampler/QueueExecutor.java | 4 +- .../jmeter/protocol/jms/sampler/Receiver.java | 20 +- .../jms/sampler/SubscriberSampler.java | 34 +- .../jms/sampler/TemporaryQueueExecutor.java | 12 +- .../sampler/TimeoutEnabledQueueRequestor.java | 18 +- .../sampler/render/BinaryMessageRenderer.java | 4 +- .../protocol/jms/sampler/render/FileKey.java | 4 +- .../sampler/render/ObjectMessageRenderer.java | 2 +- .../jms/sampler/PublisherSamplerTest.java | 2 + .../render/BinaryMessageRendererTest.java | 98 +- .../render/ObjectMessageRendererTest.java | 4 +- .../main/java/test/DummyAnnotatedTest.java | 14 +- src/protocol/junit/build.gradle.kts | 6 - .../java/control/gui/ClassFilter.java | 5 +- .../java/control/gui/JUnitTestSamplerGui.java | 14 +- .../protocol/java/sampler/JUnitSampler.java | 21 +- src/protocol/ldap/build.gradle.kts | 7 +- .../ldap/config/gui/LDAPArgumentsPanel.java | 7 +- .../ldap/config/gui/LdapExtConfigGui.java | 2 +- .../protocol/ldap/sampler/LDAPExtSampler.java | 35 +- .../protocol/ldap/sampler/LdapExtClient.java | 4 +- src/protocol/mail/build.gradle.kts | 7 - .../mail/sampler/MailReaderSampler.java | 23 +- .../sampler/gui/MailReaderSamplerGui.java | 2 +- .../protocol/smtp/sampler/SmtpSampler.java | 41 +- .../protocol/smtp/sampler/gui/SmtpPanel.java | 25 +- .../smtp/sampler/gui/SmtpSamplerGui.java | 4 +- .../sampler/protocol/SendMailCommand.java | 25 +- .../mail/sampler/TestMailReaderSampler.java | 2 + .../mongodb/config/MongoDBHolder.java | 55 - .../mongodb/config/MongoSourceElement.java | 376 - .../config/MongoSourceElementBeanInfo.java | 117 - .../mongodb/mongo/EvalResultHandler.java | 66 - .../protocol/mongodb/mongo/MongoDB.java | 75 - .../protocol/mongodb/mongo/MongoUtils.java | 50 - .../mongodb/sampler/MongoScriptRunner.java | 69 - .../mongodb/sampler/MongoScriptSampler.java | 143 - .../sampler/MongoScriptSamplerBeanInfo.java | 73 - .../MongoSourceElementResources.properties | 57 - .../MongoSourceElementResources_fr.properties | 57 - .../MongoSourceElementResources_ko.properties | 56 - .../MongoScriptSamplerResources.properties | 29 - .../MongoScriptSamplerResources_fr.properties | 29 - .../MongoScriptSamplerResources_ko.properties | 28 - src/protocol/native/build.gradle.kts | 4 - .../jmeter/protocol/system/SystemSampler.java | 7 +- .../protocol/system/gui/SystemSamplerGui.java | 14 +- src/protocol/tcp/build.gradle.kts | 7 - .../protocol/tcp/sampler/TCPClientImpl.java | 4 +- .../protocol/tcp/sampler/TCPSampler.java | 12 +- src/test-services/build.gradle.kts | 4 +- src/testkit-wiremock/build.gradle.kts | 2 +- .../jmeter/wiremock/RequestCountDown.java | 14 +- .../jmeter/testkit/ResourceLocator.java | 3 +- xdocs/changes.xml | 6 + xdocs/extending/notes_on_extending.txt | 10 +- xdocs/index.xml | 2 +- xdocs/package-lock.json | 34 + xdocs/package.json | 13 + xdocs/stylesheets/website-style.xsl | 16 +- xdocs/usermanual/component_reference.xml | 120 - xdocs/usermanual/get-started.xml | 3 +- xdocs/usermanual/jmeter_tutorial.xml | 8 +- xdocs/usermanual/properties_reference.xml | 4 - xdocs/yarn.lock | 13 + 1558 files changed, 33216 insertions(+), 16635 deletions(-) create mode 100644 .github/workflows/gradle-dependency-submit.yaml delete mode 100644 bin/templates/mongodb.jmx create mode 100644 bin/testfiles/GenTest210.expected.jmx create mode 100644 bin/testfiles/GenTest25.expected.jmx create mode 100644 bin/testfiles/GenTest251.expected.jmx create mode 100644 bin/testfiles/GenTest26.expected.jmx create mode 100644 bin/testfiles/GenTest27.expected.jmx create mode 100644 bin/testfiles/GenTest27_original.expected.jmx create mode 100644 bin/testfiles/GenTest2_13.expected.jmx create mode 100644 bin/testfiles/GenTest3_0.expected.jmx create mode 100644 bin/testfiles/GuiTest.expected.jmx create mode 100644 bin/testfiles/GuiTest231.expected.jmx create mode 100644 bin/testfiles/GuiTest231_original.expected.jmx create mode 100644 bin/testfiles/ProxyServerTestPlan.expected.jmx delete mode 100644 build-logic/checksum.xml delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/00/8118b3bcdb1a5000.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/04/ea8543c570faf804.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/06/3a1959eef8726006.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/06/cae38bc93d90b852d88465dd3a1959eef8726006.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/07/402123d2f5f4af07.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/0b/34441e504a937f43eb0daef96a65176a0fb1cd0b.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/0b/6a65176a0fb1cd0b.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/0c/0ae7bbd7fee66e0c.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/0e/105cb91cac2aee0e.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/0f/7a01b0f236e5430f.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/0f/afcc4c7594d09e2182c60e0f7a01b0f236e5430f.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/0f/aff3e378166b1f0f.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/15/56ed3b4843daacc79de555557457ca33c3ce9e15.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/15/7457ca33c3ce9e15.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/18/7cd52b5a8295137c88fb5748dddafa7674e54418.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/18/dddafa7674e54418.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/1a/1db198f93525ec1a.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/1a/679f1ee92b19609de816fde81db198f93525ec1a.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/1c/83552a552a0d431c.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/1e/6005789e24e5ad1e.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/1f/9d29ae4a6b50e01f.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/21/60eb70ddaac2ec21.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/25/2c7b12f2a511e325.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/25/475f3b8e59e6e63aa78067482c7b12f2a511e325.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/25/9351716690874f25.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/25/a182f48d9c2c0825.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/2b/d89d05374952262b.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/2f/315693699f8d102f.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/30/37de9e37c5b67c30.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/34/e98008460eb9bb34.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/36/01f3a913fb698736.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/36/3bd211f725778c36.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/36/6c70a3b7599c5736.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/38/19beab2d799c020f17c69126b16698a4adf4d638.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/38/b16698a4adf4d638.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/38/b5681e477ad61c38.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/3b/29579f18fa8fd93b.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/3b/b5cb27f94f97173b.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/3b/b801e2f8ef035068ec1139cc29579f18fa8fd93b.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/3d/1bd97a6a154e7810ee0bc832e2f38302c8075e3d.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/3d/e2f38302c8075e3d.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/3e/4db1a49729b053caf015cee9a6adfc93ef34893e.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/3e/a6adfc93ef34893e.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/41/9a347756830c4541.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/45/1669c4bb543e0445.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/45/7615ad56144df2376f49d98b1669c4bb543e0445.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/4b/71499a87dc1ff84b.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/51/85911f425ec61b51.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/51/ff6e2c001948c5f2f38b0cc385911f425ec61b51.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/52/33cd6733af5ec452.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/52/b920d295bf0e61cb4cf0896c33cd6733af5ec452.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/55/6525fd70cc303655.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/55/82f833963889d7ed06f1e4dc6525fd70cc303655.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/57/098a31f381819057.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/57/1ab9bdf40ed49debb5caaded5b30d3b10694f057.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/57/5b30d3b10694f057.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/57/a1885c07a3204611fb184ef7098a31f381819057.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/58/2161d72e7dcd4258.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/59/10da72cd7fbfa159.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/5a/083891ad4774845a.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/5a/dd46dec275b1f230acce4eeb083891ad4774845a.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/5b/12d16069219c90212a974d119ae296fd02e9f65b.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/5b/32784d4f004b405b.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/5b/9ae296fd02e9f65b.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/5b/9df7f2349731d55b.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/5d/124dac8350968ec2a8260584ee8ecbbbc188fd5d.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/5d/ee8ecbbbc188fd5d.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/5f/0186f8b24c5bc02c94a0e0e486f75e83e1ee085f.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/5f/86f75e83e1ee085f.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/60/7eb97d110dfadd60.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/60/a9789342f598ad5b1175ef357eb97d110dfadd60.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/61/379ce192d401ab61.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/61/8756c4f765c9ac3cb6b85d62379ce192d401ab61.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/62/5a5c54a339b38462.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/6f/41321490758aad6f.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/6f/7faa0f2206de228f0db01ad741321490758aad6f.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/71/9857c388d7d1d9d031274cd0a5def5a76f94a471.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/71/a5def5a76f94a471.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/71/c45d01093dcfc371.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/73/59a252fb1199d873.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/73/7616eb882daf57a11477aaf559a252fb1199d873.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/76/575d6c921d84ac76.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/76/5b05ccde140c2876.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/76/7c669810892cbd3148fa92995b05ccde140c2876.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/7e/3ff44d37464bbb7e.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/80/ca890a5fa09cfd80.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/81/28118c070cb22a0175a2e8d43d12ca2ac19f3181.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/81/3d12ca2ac19f3181.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/81/8da70c00df7af1b0d2f9dc74ddbcc1270a29d081.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/81/ddbcc1270a29d081.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/83/6a0975f8b1127b83.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/87/0729a0aff8999a87.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/87/2fba29d08d2e25ee84c132c30729a0aff8999a87.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/89/926dfb2edb329089.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/8b/5d9ffe7b8e3dea8b.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/8c/0ac07d0bbd11498c.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/8f/dc0b7e986bd7398f.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/90/1b80c80e07bc7190.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/93/2e3a1affe42b5f53af19f780bcf4173966770193.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/93/bcf4173966770193.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/94/59ba7bfead3d7f94.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/94/6214760097dc5cfad0175ac2c9fbaa83a8753994.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/94/c9fbaa83a8753994.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/94/ea23db1360d9029481e7f2efecdfea3cb4493b94.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/94/ecdfea3cb4493b94.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/96/6601e5c08dccbb96.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/96/aa417737bd805456db3cbdde6601e5c08dccbb96.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/9d/bc900cd2fc9a9d906ecba48be3822b59020a349d.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/9d/c1b12a5d99c0729d.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/9d/e3822b59020a349d.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/9d/e62231331bca7e1f292c9b88c1b12a5d99c0729d.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/9e/d4ff1f46313c1a9e.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/9e/e0130a3ed5a2079e.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/9e/fa1703b1d287caea3a60f931e0130a3ed5a2079e.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a0/7ec19439e4d4c2a0.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a2/0cfe993cdbe1d0a2.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a2/249dee8e2c07a0a2.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a2/253e8e4c6fb28d11748115c1249dee8e2c07a0a2.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a2/606cc6c4533e81a2.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a2/92bd2d0b5b21aba2.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a2/c3e640f38d845fa2.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a3/501b5adef57ce6a3.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a6/66d7cbff956830fe9f5a723afe6c7d77a1ce15a6.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a6/ac5ec74981f9cda6.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a6/dcba03381ef6c89096acd985ac5ec74981f9cda6.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a6/fe6c7d77a1ce15a6.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a7/019082bc00e0324e2aef4cf00d3b328562a119a7.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a7/0d3b328562a119a7.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/a7/bc7df305d87bdfa7.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/ab/08f0aab4d0c1a4bdde340765b341ddb020fcb6ab.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/ab/b341ddb020fcb6ab.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/ac/d908a43fb7ec07ac.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/ad/0da8a5ec02d11ead.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/ad/517b94f8d0a46317a28d8ab30da8a5ec02d11ead.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/ae/bdb5fa4fe719d787fb3d3197f6d4a1d411e9d1ae.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/ae/f6d4a1d411e9d1ae.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b1/0e91c2de43b72bb1.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b1/1a6090d3223dca95bfd25c0e38f47d3e410c47b1.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b1/2e92113263fc31c74ccbaab20e91c2de43b72bb1.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b1/38f47d3e410c47b1.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b1/50c6cc55c6f24fb1.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b2/1861c322c56014b2.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b2/d196a5e3e70732eeb2e5007f1861c322c56014b2.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b3/8b2a34a7d4a9b8b3.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b7/015479e1055341431b4545ab72475fd306b9cab7.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b7/72475fd306b9cab7.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b8/1b2718089ce964b8.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b8/31fae244a81d64507b47182e1b2718089ce964b8.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/b9/5f6ba89d4b0869b9.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/bd/9c4c23e6ffe405bd.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/be/41a1a08c62fca78b79d3081164a16faaec16a4be.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/be/64a16faaec16a4be.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/c0/1241bc872c5e4ec0.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/c0/636de9055c4c75c7bd9830771241bc872c5e4ec0.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/c1/abe9f3126bb741c1.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/c2/3624ee1473a843c2.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/c9/f3184bcd55f4d016e30d4c9bf42e87f9665015c9.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/c9/f42e87f9665015c9.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/cb/1e8f1d57a4450bcb.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/cb/2db4f1ef0fa761ecc4ea935c86fdc7e2a11262cb.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/cb/86fdc7e2a11262cb.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/cb/b15ad8efbd28d3cb.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/d2/dcf4b49b4d5845d2.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/da/5a3d35a6757384da.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/dd/15c71c0a4e0b8edd.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/dd/fa7929f83ad44c4590f6cc6815c71c0a4e0b8edd.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/e0/6064b04a9dc688e0.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/e2/56028df552ba32e2.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/e2/c536a28bc89fb250272c9f7f56028df552ba32e2.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/e3/2f3c9eeb05d1d1e3.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/e5/0785b3eff60b1b1bea94e0bb7c25280eae63ebe5.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/e5/4713a6a15a6ea9e5.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/e5/7c25280eae63ebe5.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/e7/8a10792983023d5d14c93b488d7f1bec1e2ecae7.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/e7/8d7f1bec1e2ecae7.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/ec/1723844cf9a045ec.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/ec/2290c86d79f1dfec.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/f0/34d6ff19930adf43ac127792a50569c7ca7fa1f0.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/f0/a50569c7ca7fa1f0.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/f8/bf935c771a8474f8.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/f8/c9f04e6e2dc4f7f8.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/f8/f05e5f90e43e837f9dd8c781bf935c771a8474f8.asc delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/fb/3d5839a2262cbbfb.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/fb/cfa67e8582f7e9fb.fingerprints delete mode 100644 build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/fe/891e4c2d471515fe.fingerprints delete mode 100644 build-logic/verification/src/main/kotlin/build-logic.checkerframework.gradle.kts delete mode 100644 build-logic/verification/src/main/kotlin/build-logic.spotbugs.gradle.kts delete mode 100644 checksum.xml delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/00/4be257b370130000.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/00/8118b3bcdb1a5000.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/00/cfa2c539de0e0d00.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/01/1f7a8f87b9d8f501.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/01/b46dc71e03feeb7f89d1f2491f7a8f87b9d8f501.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/01/c59d5d06cf8d0e01.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/02/72ff58594f983302.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/04/ea8543c570faf804.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/06/3a1959eef8726006.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/06/cae38bc93d90b852d88465dd3a1959eef8726006.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/07/402123d2f5f4af07.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0b/3288b8be8512d6c0ca185268c51e6cbc7ff46f0b.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0b/34441e504a937f43eb0daef96a65176a0fb1cd0b.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0b/66ca61ea0fc7e9ea1216f83396b88eacd463b90b.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0b/6a65176a0fb1cd0b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0b/74c249541619ff0b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0b/96b88eacd463b90b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0b/c51e6cbc7ff46f0b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0c/0ae7bbd7fee66e0c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0c/6b3225e6f495dc0c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0e/105cb91cac2aee0e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0e/1d2c7ef8ada0f794b58c7c63436902af59edf60e.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0e/3684155e9365c30e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0e/41d266db4427983a1a4afb0c3684155e9365c30e.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0e/436902af59edf60e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0f/7a01b0f236e5430f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0f/afcc4c7594d09e2182c60e0f7a01b0f236e5430f.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/0f/aff3e378166b1f0f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/10/a7a66be60a24ce10.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/10/bb2fb7b24b12ae10.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/11/9f3ffb85a8582c11.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/13/0a123c1ed3f13a6a0140e166c71fb765cd9de313.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/13/c71fb765cd9de313.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/14/3faad2cd5ecbb314.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/14/5cf48cd2f24c5214.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/14/b6e73d84ea4fcc47166087253faad2cd5ecbb314.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/14/d7913335bfa51814.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/15/223d3a74b068eca354dc385ce126833f9cf64915.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/15/56ed3b4843daacc79de555557457ca33c3ce9e15.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/15/7457ca33c3ce9e15.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/15/e126833f9cf64915.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/16/0588bc69a286ff16.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/16/92aeca6ac21db816.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/18/3f999bb69041cf17e73bb1cec038787776a19d18.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/18/7cd52b5a8295137c88fb5748dddafa7674e54418.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/18/c038787776a19d18.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/18/dddafa7674e54418.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/19/411063a3a0ffd119.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/19/9ff24f51b06dcc19.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/19/dddee87612e9fb95f5c8d91e411063a3a0ffd119.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1a/1db198f93525ec1a.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1a/679f1ee92b19609de816fde81db198f93525ec1a.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1c/050a37a2e0577f4baa095b52602ec18d20c4661c.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1c/602ec18d20c4661c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1c/8067ecaa8d58321c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1c/83552a552a0d431c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1c/ae5a7fb608a0221c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1c/b02137d875d833d9b23392ecae5a7fb608a0221c.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1d/9207518d06855c1d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1e/6005789e24e5ad1e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1e/9842fe565aa0601e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1e/d547b4a01f74ac1e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/1f/9d29ae4a6b50e01f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/20/011b32c57a810220.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/20/3690c240ce51b4670d30ad1c38ee757d69184620.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/20/38ee757d69184620.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/20/d364abaa39a47320.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/20/ee0ca873074092f806f59b65d364abaa39a47320.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/21/60eb70ddaac2ec21.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/22/51f5b36c761aa122.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/22/fe694b892910dd22.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/23/528dc75aa68f0923.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/23/b5569a15406ae682380f3ef8528dc75aa68f0923.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/24/b8c800fb51837224.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/25/2c7b12f2a511e325.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/25/475f3b8e59e6e63aa78067482c7b12f2a511e325.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/25/9351716690874f25.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/25/a182f48d9c2c0825.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/29/16fbb6b6e3120829.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/29/8a9352189aeccfb4ae1f3bc616fbb6b6e3120829.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/2b/d89d05374952262b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/2c/5f69ad087600b22c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/2c/a5bd02b93e7a40482eb1d66a5f69ad087600b22c.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/2d/127ce18327e55b2d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/2d/923c08f9417b222d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/2e/a1766be5f812ac2e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/2f/315693699f8d102f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/30/37de9e37c5b67c30.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/31/f8052e4e1bd0db31.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/34/31474e5e7c6b7034.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/34/4d2db2916149baa9d0c92f3731474e5e7c6b7034.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/34/dee12b9896f97e34.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/34/e0d98c5fd55a8af232290e58dee12b9896f97e34.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/34/e98008460eb9bb34.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/36/01f3a913fb698736.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/36/2be5d98f751f4136.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/36/3bd211f725778c36.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/36/6c70a3b7599c5736.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/36/908366594e746bf3c449f5622be5d98f751f4136.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/37/218fa0f6a941a037.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/37/a4fd709cc4b0515f2e6af04e218fa0f6a941a037.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/38/19beab2d799c020f17c69126b16698a4adf4d638.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/38/b16698a4adf4d638.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/38/b5681e477ad61c38.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3a/03281aa0289ff53a.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3a/1be2dd4b1fcf252fe4f0a1d103281aa0289ff53a.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3a/b9729775c0352d3a.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3a/e05f005af50ae94e69701222b9729775c0352d3a.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3b/06ec66d653dc633b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3b/29579f18fa8fd93b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3b/b5cb27f94f97173b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3b/b801e2f8ef035068ec1139cc29579f18fa8fd93b.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3d/0a52516cf3e88b3d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3d/1bd97a6a154e7810ee0bc832e2f38302c8075e3d.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3d/9a2c7a98e457c53d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3d/e2acb037933cdeaab7bf77d49a2c7a98e457c53d.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3d/e2f38302c8075e3d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3e/4db1a49729b053caf015cee9a6adfc93ef34893e.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3e/a6adfc93ef34893e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/3f/efd4147f269a813f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/40/66b50994442d2d40.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/40/750f9a735eecf640.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/40/d8b9c51729d09d40.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/40/dbd744ace7ade6aa50dd591f66b50994442d2d40.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/41/9a347756830c4541.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/42/a0f7d9615b3a5e42.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/43/a6ea2e2bf22e0543.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/43/d477d51812e692011db11e66a6ea2e2bf22e0543.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/44/3a4d86c56ca99373b6d8953dbe84d764623a3644.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/44/5923a9d358adf744.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/44/be84d764623a3644.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/44/c1d2a95a576a4144.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/44/c7fddd147fa73f44.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/45/1669c4bb543e0445.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/45/353a436e043e3145.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/45/3dbc5d7dc2fb8dd68aa429bd353a436e043e3145.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/45/7615ad56144df2376f49d98b1669c4bb543e0445.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/45/a98bd25be464ea45.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/46/6f67467bc1adb846.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/46/873a8e86b4372146.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/46/9a716f957bc42546.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/46/eb1b3de71713c9ec2e87cc26ee92349ad86de446.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/46/ee92349ad86de446.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/46/f254b35617dc255d9344bcfa873a8e86b4372146.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/47/dd6aefcee9eb4747.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/48/1d185615d0a84648.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/48/d9aa7402ba75f78720d975211d185615d0a84648.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/4a/59b06224fd8912e36603be79fefe78456eddc34a.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/4a/fefe78456eddc34a.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/4b/0440006d577eae4b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/4b/700e4f39bc05364b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/4b/71499a87dc1ff84b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/4e/ccfb05b8ccbc1f4e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/4e/f5492b20d173a94e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/4f/9ff25980f5ba7e4f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/4f/e5b8247af8a619a28f90fdfc9ff25980f5ba7e4f.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/50/726f4e5c34cfd750.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/51/184454fad8697760f3e00d2e4a51a45b944ffd51.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/51/4a51a45b944ffd51.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/51/4ac55a85d30c3499a3219500b7e2662a7640a051.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/51/85911f425ec61b51.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/51/b7e2662a7640a051.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/51/ff6e2c001948c5f2f38b0cc385911f425ec61b51.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/52/33cd6733af5ec452.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/52/74237ad167ffee52.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/52/8671a8df71296252.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/52/a6d6c97108b8585f91b158748671a8df71296252.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/52/b920d295bf0e61cb4cf0896c33cd6733af5ec452.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/53/3046c9379f41ea53.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/54/3f2a008a91d11a7fac4a0786f13d3e721d56bd54.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/54/cf9f423a7d348254.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/54/f13d3e721d56bd54.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/55/6525fd70cc303655.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/55/82f833963889d7ed06f1e4dc6525fd70cc303655.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/56/24d04176586361fda94ee0315f7786df73e61f56.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/56/5f7786df73e61f56.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/57/098a31f381819057.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/57/1ab9bdf40ed49debb5caaded5b30d3b10694f057.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/57/5b30d3b10694f057.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/57/6366592024774157.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/57/a1885c07a3204611fb184ef7098a31f381819057.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/58/06d34ed6ff73de368a772a781063fe98bcecb758.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/58/1063fe98bcecb758.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/58/2161d72e7dcd4258.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/59/10da72cd7fbfa159.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5a/083891ad4774845a.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5a/dd46dec275b1f230acce4eeb083891ad4774845a.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5b/11cd01355c5bcf5b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5b/12d16069219c90212a974d119ae296fd02e9f65b.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5b/3260cb2def74135b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5b/32784d4f004b405b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5b/9ae296fd02e9f65b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5b/9df7f2349731d55b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5c/b89991d171a02f5c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5d/124dac8350968ec2a8260584ee8ecbbbc188fd5d.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5d/4bde772c338ba540e1057d0c8ea48d105232855d.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5d/8ea48d105232855d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5d/ee8ecbbbc188fd5d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5f/0186f8b24c5bc02c94a0e0e486f75e83e1ee085f.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5f/347cd0eaed975c5f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5f/86f75e83e1ee085f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5f/ae1c126549627b5f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/5f/b01128c8fd304fe48e6eed2c347cd0eaed975c5f.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/60/7eb97d110dfadd60.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/60/a9789342f598ad5b1175ef357eb97d110dfadd60.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/61/379ce192d401ab61.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/61/8756c4f765c9ac3cb6b85d62379ce192d401ab61.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/62/064ac25270668162.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/62/4797b4f5dcc46cea61059071a1ae06236ca2ba62.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/62/5a5c54a339b38462.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/62/7186bbf993566d8c2f4f7ed7d945e643368fef62.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/62/7a8860944fad5f62.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/62/a1ae06236ca2ba62.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/62/d6f1bc78607808ec8e9f69437a8860944fad5f62.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/62/d945e643368fef62.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/64/187366a3ffe6bf8f94b9136a9987b20c8f6a3064.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/64/9987b20c8f6a3064.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/67/21939ff0ca2a6567.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/67/91a4ba316974a467.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/67/bc87a3fd0a54480f0badbebd21939ff0ca2a6567.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/68/2f45f85cdf082e68.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/68/e9af5293c334c7c0b583227d2f45f85cdf082e68.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/69/e68665c8f91bde69.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/6a/012579464d01c06a.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/6a/2591ba36fd00e56a.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/6a/84407e95050fb588296690862591ba36fd00e56a.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/6a/fa77dcfef2ee6eb2debedd2c012579464d01c06a.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/6c/1afec329b615d06c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/6e/b2ca0dfe9ed21c6e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/6e/c62c0ce2e5c346b2d9bf2d8bb2ca0dfe9ed21c6e.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/6f/41321490758aad6f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/6f/7faa0f2206de228f0db01ad741321490758aad6f.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/71/9857c388d7d1d9d031274cd0a5def5a76f94a471.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/71/a5def5a76f94a471.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/71/c45d01093dcfc371.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/71/c51e10bd96d06071.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/71/d75a8c9fcf1016c0f54f2d4dc51e10bd96d06071.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/72/a2115ae15f6b8b72.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/72/ce8075a251547bee249bc151a2115ae15f6b8b72.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/73/59a252fb1199d873.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/73/7616eb882daf57a11477aaf559a252fb1199d873.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/73/8183e80d264ee073.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/76/164779204e106a76.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/76/1aa8cf92d409a73393d0b736bff2ee42c8282e76.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/76/254e9c64c264c176.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/76/575d6c921d84ac76.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/76/5b05ccde140c2876.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/76/7c669810892cbd3148fa92995b05ccde140c2876.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/76/b0255b26003ef6f82491acd7254e9c64c264c176.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/76/bff2ee42c8282e76.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/76/e5c3b1929191df06136ccb2b164779204e106a76.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/77/7ba5338873382b77.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/78/08ba61cf501c09f70911b83b82216a03caa86c78.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/78/82216a03caa86c78.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/7c/c3720ddc2e713b7c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/7d/670be7d076a0c89f5f07625ae57428da9e879e7d.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/7d/e57428da9e879e7d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/7e/3ff44d37464bbb7e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/7e/4e2ddd47e19863bb87211544cd3038fef07d567e.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/7e/cd3038fef07d567e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/7f/25a93b1970c22a7f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/80/10314d676733c080.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/80/2ac7bf2f3349de80.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/80/bede11eaf1164480.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/80/ca890a5fa09cfd80.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/80/d068f0d7b6a63980.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/80/e3a9f95079e84ce201f7cf60bede11eaf1164480.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/81/28118c070cb22a0175a2e8d43d12ca2ac19f3181.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/81/33aebc1f01c98081.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/81/3d12ca2ac19f3181.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/81/8da70c00df7af1b0d2f9dc74ddbcc1270a29d081.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/81/ddbcc1270a29d081.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/82/3c91fed3922d52296888bae9b6d3ab9bcc641282.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/82/4f752a9878e937049ef3fba7d4012dda1f1f0f82.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/82/b6d3ab9bcc641282.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/82/d4012dda1f1f0f82.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/83/31fa045762d93f83.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/83/6a0975f8b1127b83.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/83/b7d8b732f9ff1283.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/85/60200ac4ae761f1614d6c46766d68daa073be985.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/85/66d68daa073be985.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/85/7679164aa2590985.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/87/042b29e928995b9db963c636c7ca19b7b620d787.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/87/0729a0aff8999a87.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/87/2fba29d08d2e25ee84c132c30729a0aff8999a87.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/87/394cb436c56916fc01eea4a77c30f7b1329dba87.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/87/7c30f7b1329dba87.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/87/c7ca19b7b620d787.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/89/160a7a9cf46221a56b06ad64461a804f2609fd89.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/89/461a804f2609fd89.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/89/4b1e11d5a4b91e89.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/89/926dfb2edb329089.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/89/f1a51e051f527e0c8e24d54d4b1e11d5a4b91e89.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8a/ef3fa25a9e1b028a.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8b/5d9ffe7b8e3dea8b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8b/7999befba1039e8b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8c/0ac07d0bbd11498c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8d/2a742740e08e7f8d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8d/5719e50eac5a4b1dd390b72c2a742740e08e7f8d.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8d/fe4ce430fd91c78d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8f/4c5f68d09d42ba7fac888df9a929ea2321fdbf8f.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8f/79e193516be7998f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8f/8f9a3c6d105b9f57844a721d79e193516be7998f.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8f/a929ea2321fdbf8f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/8f/dc0b7e986bd7398f.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/90/13ac2213964abe1d1c147c0e1939a2520bab1d90.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/90/1939a2520bab1d90.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/90/1b80c80e07bc7190.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/91/3967d4eda591b991.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/91/9f621f1ae6ef2591.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/92/8bcab185914c4a92.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/93/0cc641c3a62453ab390066c4a41f13c999945293.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/93/2e3a1affe42b5f53af19f780bcf4173966770193.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/93/a41f13c999945293.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/93/bcf4173966770193.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/94/326ba6bd408bfa94.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/94/59ba7bfead3d7f94.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/94/6214760097dc5cfad0175ac2c9fbaa83a8753994.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/94/be6397e958ab7594.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/94/c9fbaa83a8753994.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/94/ea23db1360d9029481e7f2efecdfea3cb4493b94.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/94/ecdfea3cb4493b94.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/94/feabfb0dbd7a754822d35dd9be6397e958ab7594.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/96/6601e5c08dccbb96.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/96/aa417737bd805456db3cbdde6601e5c08dccbb96.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/97/145d819475314d97.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/97/4830c9fb8b680a97.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/97/cdcaa216b086a7b603b638c5145d819475314d97.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/99/a7764f502a938c99.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/99/d75e25b78ebb19e47c0a99bca7764f502a938c99.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9b/3692c12c7e3d336a452beb254e066e0459cd109b.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9b/4e066e0459cd109b.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9c/c6fc46eb51cf569c.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9c/ca491704d613780d2bee00f2c6fc46eb51cf569c.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9d/43203be58f49479d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9d/bc900cd2fc9a9d906ecba48be3822b59020a349d.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9d/c1b12a5d99c0729d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9d/e3822b59020a349d.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9d/e62231331bca7e1f292c9b88c1b12a5d99c0729d.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9d/e83aabbb95ca37431e20422043203be58f49479d.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9e/d4ff1f46313c1a9e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9e/dff2d25e2cd6139e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9e/e0130a3ed5a2079e.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/9e/fa1703b1d287caea3a60f931e0130a3ed5a2079e.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a0/7ec19439e4d4c2a0.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a2/0cfe993cdbe1d0a2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a2/249dee8e2c07a0a2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a2/253e8e4c6fb28d11748115c1249dee8e2c07a0a2.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a2/606cc6c4533e81a2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a2/92bd2d0b5b21aba2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a2/c3e640f38d845fa2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a3/1fa37fbe4453c1073e7ef61d6449005f96bc97a3.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a3/501b5adef57ce6a3.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a3/6449005f96bc97a3.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a4/76dba0d142fe15d6a16f6b3789af105ceec705a4.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a4/89af105ceec705a4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a6/6e50bb68cc1699a6.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a6/ac5ec74981f9cda6.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a6/dcba03381ef6c89096acd985ac5ec74981f9cda6.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a7/019082bc00e0324e2aef4cf00d3b328562a119a7.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a7/0d3b328562a119a7.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a7/0e26a79ffe07a3a7.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a7/7d1be4480b61e2a7.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/a7/bc7df305d87bdfa7.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/aa/3449ec3ac2efe8aa.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/aa/3e1ac64f4e34e290503c90323449ec3ac2efe8aa.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/aa/cb6d56b72fddf8aa.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ab/08f0aab4d0c1a4bdde340765b341ddb020fcb6ab.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ab/3b2a06173073fdab.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ab/b341ddb020fcb6ab.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ab/c64121f5021754ab.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ac/a7e989b0634097ac.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ac/d908a43fb7ec07ac.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ad/0da8a5ec02d11ead.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ad/517b94f8d0a46317a28d8ab30da8a5ec02d11ead.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ad/6cb87b18a453990eac9453f87d713008cc07e9ad.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ad/7d713008cc07e9ad.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ad/a8d88140c35897ad.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ae/7494750bdf4f8fae.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ae/764be739ddae10f4473fecf0aef385ed6ca815ae.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ae/aef385ed6ca815ae.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ae/bdb5fa4fe719d787fb3d3197f6d4a1d411e9d1ae.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ae/f6d4a1d411e9d1ae.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/af/030dd9087c31c9af.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/af/c2148900bcd3c2af.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b0/314fe82e5a4c5377bca2edec5208812e1e4a6db0.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b0/5208812e1e4a6db0.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b1/07d3516820bcf6b1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b1/0e91c2de43b72bb1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b1/1a6090d3223dca95bfd25c0e38f47d3e410c47b1.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b1/2e92113263fc31c74ccbaab20e91c2de43b72bb1.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b1/38f47d3e410c47b1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b1/50c6cc55c6f24fb1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b1/635ee627345f3c1dd422b2e207d3516820bcf6b1.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b1/82d8419ba697f0e7fb85916ee91287822fdb81b1.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b1/e91287822fdb81b1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b2/1861c322c56014b2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b2/d196a5e3e70732eeb2e5007f1861c322c56014b2.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b2/eaae192d27ab3eb2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b3/2c1708ea05adf6966c32d6454a1bac7f1e3b89b3.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b3/4a1bac7f1e3b89b3.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b3/8b2a34a7d4a9b8b3.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b4/2d0e1fb8fe4b68b4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b4/5989baf76217b843d66be55b2d0e1fb8fe4b68b4.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b7/015479e1055341431b4545ab72475fd306b9cab7.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b7/656a225fc79ce5cbbd7dd5bfa7f5ed0fa05d64b7.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b7/72475fd306b9cab7.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b7/a7f5ed0fa05d64b7.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b8/1b2718089ce964b8.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b8/31fae244a81d64507b47182e1b2718089ce964b8.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b9/5f6ba89d4b0869b9.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b9/e09f6a4522f77db9.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/b9/fb693bc97890abb9.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ba/3595395eb3d8e1ba.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ba/53c935821aa6a755bd337db53595395eb3d8e1ba.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ba/7c7d8456294423ba.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ba/e85aed155021af8a6c6b7a4a7c7d8456294423ba.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ba/fcf74afdf5947aba.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/bb/1085f41881c989bb.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/bc/6b7edfc69f043bbc.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/bd/38185785755267bd.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/bd/8799af90c06efbbd.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/bd/9a4555c237785ca8a6dc1fe58799af90c06efbbd.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/bd/9c4c23e6ffe405bd.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/be/2bfd7825a8984fbe.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/be/41a1a08c62fca78b79d3081164a16faaec16a4be.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/be/590c2310cee1c9be.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/be/64a16faaec16a4be.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/be/fc2c31fc25fede4e7ad0d18c2bfd7825a8984fbe.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/bf/5e975cb00c643dbf.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/bf/d5f46bc0b86af5dc56df58f05e975cb00c643dbf.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c0/1241bc872c5e4ec0.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c0/636de9055c4c75c7bd9830771241bc872c5e4ec0.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c1/11f4ce313a637cc1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c1/abe9f3126bb741c1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c2/3624ee1473a843c2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c3/67631bc0568801c3.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c3/80cfa7c482552dc3.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c3/e52567d2589415bd74eb4c2867631bc0568801c3.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c4/4f7e32d440ef90a83011a8fc6425559c47cc79c4.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c4/6425559c47cc79c4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c4/c189c86b813330c4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c4/d66472cf54179cc4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c5/1364c5e2df3e99c5.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c6/2f53295a4bbac7c6.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c7/5858009d3cc798c7.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c8/3f606403dca455c8.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c9/f3184bcd55f4d016e30d4c9bf42e87f9665015c9.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/c9/f42e87f9665015c9.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/cb/1e8f1d57a4450bcb.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/cb/2db4f1ef0fa761ecc4ea935c86fdc7e2a11262cb.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/cb/4c00043bec5cb7cb.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/cb/58f086abb0ede8f7a04b76c34c00043bec5cb7cb.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/cb/6e97418b04b11dcb.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/cb/86fdc7e2a11262cb.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/cb/b15ad8efbd28d3cb.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/cb/c758f9cc982ac877e7b295dd6e97418b04b11dcb.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/cd/bb9cc415c9c063cd.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ce/ccda41f01581c6ce.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d0/077e8893a6dcc33dd4a4d5b256e73ba9a0b592d0.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d0/56e73ba9a0b592d0.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d0/9daadc1c9fcc82d0.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d0/cd5464315f0b98c77e6e8ecd9daadc1c9fcc82d0.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d1/6bfab2e3c6490b421b25c76c9c8c892f91f8e6d1.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d1/7b9751fc3f01f134f476464cd0eb627d4885ced1.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d1/9c8c892f91f8e6d1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d1/d0eb627d4885ced1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d2/dcf4b49b4d5845d2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d4/0162fe0cf6e18bd4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d4/07dbc3c1ab1f4468471656061c8d5ef0df2b70d4.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d4/1c8d5ef0df2b70d4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d4/7892707e9657ebd4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d4/a0fc00d356eef1d4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d4/a5c8dbb8a1599bd4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d5/1da784ccb5c46dd5.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d5/b4ac8cdc141af0ae468d16921da784ccb5c46dd5.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/d9/39f8a1784230e2d9.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/da/5a3d35a6757384da.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/dd/15c71c0a4e0b8edd.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/dd/1fcffe916b6670868fa0ea0391ae1504568ec4dd.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/dd/91ae1504568ec4dd.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/dd/fa7929f83ad44c4590f6cc6815c71c0a4e0b8edd.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/de/106bde3d5d78bfde.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/de/cfca4a29d26468de.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/de/f1182e81c792928921dbcab4cfca4a29d26468de.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e0/50a628ffaf58480736b1079fd1031d14464180e0.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e0/6064b04a9dc688e0.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e0/d1031d14464180e0.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e1/72fefd1572eb75e1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e1/76e94e8ff0ab5af3b6f8366972fefd1572eb75e1.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e1/a9b9c4370b8833e1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e2/56028df552ba32e2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e2/817ea1255546d6070a23aa00db45d3a62a183ce2.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e2/c536a28bc89fb250272c9f7f56028df552ba32e2.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e2/db45d3a62a183ce2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e3/2f3c9eeb05d1d1e3.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e3/875c3ef9dc4638e3.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e3/c8c4b9dc1e031f788b12882b875c3ef9dc4638e3.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e3/e95b8ff73f6b84e3.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e5/0785b3eff60b1b1bea94e0bb7c25280eae63ebe5.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e5/4713a6a15a6ea9e5.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e5/7c25280eae63ebe5.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e5/b4f772a2a08563e5.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e7/8a10792983023d5d14c93b488d7f1bec1e2ecae7.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e7/8d7f1bec1e2ecae7.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e7/b0f3710fa64900e7.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e7/c7be5bcc9fec15518cfda882b0f3710fa64900e7.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e8/4f89f83c363d30e8.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e8/f20db7fef61ce1e8.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/e9/cff46ee3c17e53e9.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ea/4044edf1bb73efea.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ea/5b93f1df7cdb6dea.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ea/5c9a30ff22b2c02f30261c305b93f1df7cdb6dea.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/eb/995efbf4a3d20beb.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/eb/adbc987d1a7b91db6b0aaa81995efbf4a3d20beb.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ec/1723844cf9a045ec.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ec/2290c86d79f1dfec.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ec/2f39e2a1eb9bc4e78f403b22f6bc09712c8df6ec.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ec/f6bc09712c8df6ec.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ed/9a259c7ee636c5ed.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ed/d3dbc823be4819ed.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ed/e77417ac194160a3fabd04969a259c7ee636c5ed.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ee/55e770230e69cc6de143fb5b62c82e50836eb3ee.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ee/62c82e50836eb3ee.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ef/ab049df4ab24c1ef.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/ef/d76260c72df74e66268f7770ab049df4ab24c1ef.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/f0/34d6ff19930adf43ac127792a50569c7ca7fa1f0.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/f0/a50569c7ca7fa1f0.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/f1/603dbe5f5c9fa9f1.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/f2/fc51362145a05ff2.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/f4/73f7734b17ec71f4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/f4/92bf193ad552c6f4.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/f8/bf935c771a8474f8.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/f8/c9f04e6e2dc4f7f8.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/f8/f05e5f90e43e837f9dd8c781bf935c771a8474f8.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/f9/9f7335d63326e7f9.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/fa/a413f67d71beec23add0ce0acb43338e060cf9fa.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/fa/cb43338e060cf9fa.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/fb/3d5839a2262cbbfb.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/fb/cfa67e8582f7e9fb.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/fc/4006cba6d352f1fc.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/fd/1def9646901a1652e5b3e5ea694db6f419c8d1fd.asc delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/fd/694db6f419c8d1fd.fingerprints delete mode 100644 gradle/checksum-dependency-plugin/cached-pgp-keys/fe/891e4c2d471515fe.fingerprints create mode 100644 gradle/verification-keyring.keys create mode 100644 gradle/verification-metadata.xml create mode 100644 src/components/src/main/kotlin/org/apache/jmeter/timers/ConstantThroughputTimerSchema.kt create mode 100644 src/components/src/test/kotlin/org/apache/jmeter/assertions/AssertionResultExtensions.kt create mode 100644 src/core/src/main/kotlin/org/apache/jmeter/save/JMeterStaxDriver.kt create mode 100644 src/core/src/main/kotlin/org/apache/jmeter/save/XMLOutputFactoryDelegate.kt rename src/{protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/DirectAccessByteArrayOutputStream.java => core/src/main/kotlin/org/apache/jmeter/save/XMLStreamWriterSkipHeader.kt} (50%) create mode 100644 src/core/src/testFixtures/kotlin/org/apache/jmeter/treebuilder/TreeBuilderExtensions.kt create mode 100644 src/jorphan/src/main/java/org/apache/jorphan/locale/LocaleUtils.java create mode 100644 src/jorphan/src/main/java/org/apache/jorphan/util/BooleanUtils.java rename src/{protocol/mongodb/build.gradle.kts => jorphan/src/main/java/org/apache/jorphan/util/CharUtils.java} (67%) create mode 100644 src/jorphan/src/main/java/org/apache/jorphan/util/ExceptionUtils.java create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/collections/FilterIterator.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/io/BomInputStream.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/io/CountingInputStream.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/io/CountingOutputStream.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/io/DirectAccessByteArrayOutputStream.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/io/TeeOutputStream.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/text/RandomStringGenerator.kt create mode 100644 src/jorphan/src/main/kotlin/org/apache/jorphan/util/EnumUtils.kt create mode 100644 src/jorphan/src/test/kotlin/org/apache/jorphan/collections/FilterIteratorTest.kt create mode 100644 src/jorphan/src/test/kotlin/org/apache/jorphan/io/DirectAccessByteArrayOutputStreamTest.kt create mode 100644 src/jorphan/src/test/kotlin/org/apache/jorphan/io/TeeOutputStreamTest.kt create mode 100644 src/jorphan/src/test/kotlin/org/apache/jorphan/text/RandomStringGeneratorTest.kt create mode 100644 src/jorphan/src/test/kotlin/org/apache/jorphan/util/BooleanUtilsTest.kt delete mode 100644 src/licenses/licenses/hamcrest-date/LICENSE delete mode 100644 src/licenses/licenses/hamcrest/LICENSE delete mode 100644 src/licenses/licenses/svgSalamander/LICENSE create mode 100644 src/protocol/http/src/main/java/org/apache/http/entity/mime/MultipartEntityBuilder2.java rename src/protocol/{mongodb/src/test/java/org/apache/jmeter/resources/ResourceKeyUsageTestMongodb.java => http/src/main/kotlin/org/apache/jmeter/protocol/http/HttpClientState.kt} (67%) delete mode 100644 src/protocol/http/src/test/java/org/apache/jmeter/protocol/http/control/DnsManagerTest.java create mode 100644 src/protocol/http/src/test/kotlin/org/apache/jmeter/protocol/http/sampler/HttpSamplerDisableArgumentsTest.kt create mode 100644 src/protocol/http/src/test/kotlin/org/apache/jmeter/protocol/http/sampler/HttpSamplerTestingUtils.kt create mode 100644 src/protocol/http/src/test/kotlin/org/apache/jmeter/protocol/http/util/MockDnsServer.kt create mode 100644 src/protocol/http/src/testFixtures/kotlin/org/apache/jmeter/protocol/http/control/HttpRequestDefaultsExtensions.kt delete mode 100644 src/protocol/mongodb/src/main/java/org/apache/jmeter/protocol/mongodb/config/MongoDBHolder.java delete mode 100644 src/protocol/mongodb/src/main/java/org/apache/jmeter/protocol/mongodb/config/MongoSourceElement.java delete mode 100644 src/protocol/mongodb/src/main/java/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementBeanInfo.java delete mode 100644 src/protocol/mongodb/src/main/java/org/apache/jmeter/protocol/mongodb/mongo/EvalResultHandler.java delete mode 100644 src/protocol/mongodb/src/main/java/org/apache/jmeter/protocol/mongodb/mongo/MongoDB.java delete mode 100644 src/protocol/mongodb/src/main/java/org/apache/jmeter/protocol/mongodb/mongo/MongoUtils.java delete mode 100644 src/protocol/mongodb/src/main/java/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptRunner.java delete mode 100644 src/protocol/mongodb/src/main/java/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSampler.java delete mode 100644 src/protocol/mongodb/src/main/java/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerBeanInfo.java delete mode 100644 src/protocol/mongodb/src/main/resources/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementResources.properties delete mode 100644 src/protocol/mongodb/src/main/resources/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementResources_fr.properties delete mode 100644 src/protocol/mongodb/src/main/resources/org/apache/jmeter/protocol/mongodb/config/MongoSourceElementResources_ko.properties delete mode 100644 src/protocol/mongodb/src/main/resources/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerResources.properties delete mode 100644 src/protocol/mongodb/src/main/resources/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerResources_fr.properties delete mode 100644 src/protocol/mongodb/src/main/resources/org/apache/jmeter/protocol/mongodb/sampler/MongoScriptSamplerResources_ko.properties create mode 100644 xdocs/package-lock.json create mode 100644 xdocs/package.json create mode 100644 xdocs/yarn.lock diff --git a/.asf.yaml b/.asf.yaml index 2eaa5c930b8..4dc8f59efac 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -8,6 +8,9 @@ github: - test - java + dependabot_alerts: true + dependabot_updates: false + enabled_merge_buttons: squash: true merge: false diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index f6010e0a3e6..0a09f1b3941 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,9 +1,6 @@ # See https://github.com/release-drafter/release-drafter#configuration-options name-template: 'v$RESOLVED_VERSION' tag-template: 'rel/v$RESOLVED_VERSION' -# Filter previous releases to consider only those with the target matching the current branch -filter-by-commitish: true -tag-prefix: REL exclude-labels: - 'skip-changelog' categories: diff --git a/.github/workflows/gradle-dependency-submit.yaml b/.github/workflows/gradle-dependency-submit.yaml new file mode 100644 index 00000000000..a264cf94d1f --- /dev/null +++ b/.github/workflows/gradle-dependency-submit.yaml @@ -0,0 +1,31 @@ +name: Dependency Submission + +# See https://github.com/gradle/actions/blob/768a17f3488dc3fe0155ff431553e1f53d57e22e/dependency-submission/README.md#the-dependency-submission-action +# The action allows GitHub to alert about reported vulnerabilities in the project +on: + push: + branches: + - master + +# Declare default permissions as read-only. +permissions: read-all + +jobs: + dependency-submission: + name: Submit dependencies + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout sources + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + with: + persist-credentials: false + - name: Set up JDK 21 + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + with: + distribution: zulu + java-version: 21 + server-id: central + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5 diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index b63fdf355cd..5d924bd25c5 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -6,5 +6,5 @@ jobs: name: "Validation" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v1 + - uses: actions/checkout@v5 + - uses: gradle/wrapper-validation-action@v3 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 08047d39f22..b645c63dae8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: # Number of jobs to generate in matrix.js MATRIX_JOBS: 4 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - id: set-matrix run: | node .github/workflows/matrix.js @@ -43,31 +43,31 @@ jobs: fail-fast: false # max-parallel: 4 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 50 - name: Set up Java ${{ matrix.java_version }}, oracle if: ${{ matrix.oracle_java_website != '' }} - uses: oracle-actions/setup-java@7a0114d66dbd02646abd345c3395b34c148e6126 # v1.3.2 + uses: oracle-actions/setup-java@fff43251af9936a0e6a4d5d0946e14f1680e9b6b # v1.5.0 with: website: ${{ matrix.oracle_java_website }} release: ${{ matrix.java_version }} - - name: Set up Java 17 and ${{ matrix.non_ea_java_version }}, ${{ matrix.java_distribution }} - uses: actions/setup-java@v4 + - name: Set up Java 21 and ${{ matrix.non_ea_java_version }}, ${{ matrix.java_distribution }} + uses: actions/setup-java@v5 with: - # The latest one will be the default, so we use Java 17 for launching Gradle + # The latest one will be the default, so we use Java 21 for launching Gradle java-version: | ${{ matrix.non_ea_java_version }} - 17 + 21 distribution: ${{ matrix.java_distribution }} architecture: x64 - name: Steps to reproduce - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | console.log('The following command might help reproducing CI results, use Java ${{ matrix.java_version }}') console.log('TZ="${{ matrix.tz }}" _JAVA_OPTIONS="${{ matrix.extraJvmArgs }}" ./gradlew build -x distTar -x distTarSource -x distTarSha512 -x distTarSourceSha512 ${{ matrix.extraGradleArgs }} -PtestExtraJvmArgs="${{ matrix.testExtraJvmArgs }}" -PtestDisableCaching="${{ matrix.testDisableCaching }}"') - - uses: burrunan/gradle-cache-action@v1 + - uses: burrunan/gradle-cache-action@v3 name: Test with: job-id: jdk${{ matrix.java_version }} @@ -77,30 +77,32 @@ jobs: properties: | testExtraJvmArgs=${{ matrix.testExtraJvmArgs }} testDisableCaching=${{ matrix.testDisableCaching }} - jdkBuildVersion=17 + jdkBuildVersion=21 jdkTestVersion=${{ matrix.java_version }} jdkTestVendor=${{ matrix.java_vendor }} + # We have a separate job with Errorprone verifications, so avoid duplicate warnings + enableErrorprone=false # We provision JDKs with GitHub Actions for caching purposes, so Gradle should rather fail in case JDK is not found org.gradle.java.installations.auto-download=false env: _JAVA_OPTIONS: ${{ matrix.extraJvmArgs }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} + DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} errorprone: - name: 'Error Prone (JDK 17)' + name: 'Error Prone (JDK 21)' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: 'Set up JDK 17' - uses: actions/setup-java@v4 + - uses: actions/checkout@v5 + - name: 'Set up JDK 21' + uses: actions/setup-java@v5 with: - java-version: 17 + java-version: 21 distribution: 'zulu' - - uses: burrunan/gradle-cache-action@v1 + - uses: burrunan/gradle-cache-action@v3 name: Test with: job-id: errprone multi-cache-enabled: false - arguments: --scan --no-parallel --no-daemon -PenableErrorprone classes + arguments: --scan --no-parallel --no-daemon classes env: - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} + DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} diff --git a/.github/workflows/matrix.js b/.github/workflows/matrix.js index acf5472f941..aad48000fde 100644 --- a/.github/workflows/matrix.js +++ b/.github/workflows/matrix.js @@ -28,6 +28,7 @@ matrix.addAxis({ values: [ '17', '21', + '25', eaJava, ] }); @@ -83,6 +84,8 @@ matrix.exclude({os: 'macos-latest', java_distribution: {value: 'oracle'}}) // Ignore builds with JAVA EA for now, see https://github.com/apache/jmeter/issues/6114 matrix.exclude({java_version: eaJava}) matrix.imply({java_version: eaJava}, {java_distribution: {value: 'oracle'}}) +// Oracle JDK is only supported for JDK 21 and later +matrix.imply({java_distribution: {value: 'oracle'}}, {java_version: v => v === eaJava || v >= 21}); // TODO: Semeru does not ship Java 21 builds yet matrix.exclude({java_distribution: {value: 'semeru'}, java_version: '21'}); // Ensure at least one job with "same" hashcode exists @@ -93,8 +96,8 @@ matrix.generateRow({os: 'windows-latest'}); // matrix.generateRow({os: 'ubuntu-latest'}); // Ensure there will be at least one job with Java 17 matrix.generateRow({java_version: "17"}); -// Ensure there will be at least one job with Java 21 -matrix.generateRow({java_version: "21"}); +// Ensure there will be at least one job with Java 25 +matrix.generateRow({java_version: "25"}); // Ensure there will be at least one job with Java EA // matrix.generateRow({java_version: eaJava}); const include = matrix.generateRows(process.env.MATRIX_JOBS || 5); diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 087a6b7d71b..664c65d35ac 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -8,20 +8,46 @@ on: - 'release/**' # pull_request_target allows PR from forks to access secrets, so please NEVER add pull_request_target +# Declare default permissions as read-only. +permissions: read-all + jobs: update_release_draft: # Skip release drafts in forks - if: github.repository_owner == 'apache' + if: vars.RUN_RELEASE_DRAFTER == 'true' name: Update Release Draft runs-on: ubuntu-latest + permissions: + # write permission is required to create a github release + contents: write steps: + - name: Get the current version + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + id: current_version + with: + # language=javascript + script: | + const response = await github.rest.repos.getContent({ + owner: context.repo.owner, + repo: context.repo.repo, + path: 'gradle.properties', + ref: context.sha + }); + const content = Buffer.from(response.data.content, 'base64').toString(); + const version = content.match(/^jmeter\.version=(.+)$/m)[1]; + console.log(`Version is ${version}`); + return version; + # Drafts your next Release notes as Pull Requests are merged into "master" - name: Update release body draft - uses: release-drafter/release-drafter@09c613e259eb8d4e7c81c2cb00618eb5fc4575a7 # v5 + uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6 id: prepare_release # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml with: # config-name: my-config.yml disable-autolabeler: true + publish: false + latest: ${{ github.ref_name == github.event.repository.default_branch }} + version: ${{ steps.current_version.outputs.result }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 2400313e97b..13f89d12a06 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,6 @@ jmeter-fb.* hs_err_pid* # other jvm related files .attach_* + +# npm/node dependencies and generated web assets +/xdocs/node_modules/ diff --git a/.ratignore b/.ratignore index f2ade8505bd..8a99b773812 100644 --- a/.ratignore +++ b/.ratignore @@ -1,8 +1,6 @@ -checksum.xml -build-logic/checksum.xml -build-logic/gradle/checksum-dependency-plugin/cached-pgp-keys/** gradle/wrapper/gradle-wrapper.jar -gradle/checksum-dependency-plugin/cached-pgp-keys/** +gradle/verification-keyring.keys +gradle/verification-metadata.xml **/.* .*/** CODE_OF_CONDUCT.md @@ -38,6 +36,10 @@ test/resources/** #**/*.log **/download_jmeter.cgi xdocs/extending/HTML_REPORT_README.TXT +xdocs/node_modules/** +xdocs/package.json +xdocs/package-lock.json +xdocs/yarn.lock # Rat should automatically detect below files as binary, however it fails **/*.sxi **/*.sxw diff --git a/bin/jmeter b/bin/jmeter index 055b2bafcbe..56550598ec8 100755 --- a/bin/jmeter +++ b/bin/jmeter @@ -115,18 +115,19 @@ if [ -z "$JAVA_HOME" ]; then JAVA_HOME="$JRE_HOME" fi -#--add-opens if JAVA 9 -JAVA9_OPTS= +# Module access for modern Java versions (required for JMeter components) +JAVA_OPTS="--add-opens java.desktop/sun.awt=ALL-UNNAMED --add-opens java.desktop/sun.swing=ALL-UNNAMED --add-opens java.desktop/javax.swing.text.html=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED --add-opens=java.desktop/sun.awt.shell=ALL-UNNAMED" # Minimal version to run JMeter -MINIMAL_VERSION=8 +MINIMAL_VERSION=17 -# Check if version is from OpenJDK or Oracle Hotspot JVM prior to 9 containing 1.${version}.x +# Check if version meets the minimal requirement CURRENT_VERSION=`"${JAVA_HOME}/bin/java" -version 2>&1 | awk -F'"' '/version/ {gsub("^1[.]", "", $2); gsub("[^0-9].*$", "", $2); print $2}'` -# Check if Java is present and the minimal version requirement -if [ "$CURRENT_VERSION" -gt "$MINIMAL_VERSION" ]; then - JAVA9_OPTS="--add-opens java.desktop/sun.awt=ALL-UNNAMED --add-opens java.desktop/sun.swing=ALL-UNNAMED --add-opens java.desktop/javax.swing.text.html=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED --add-opens=java.desktop/sun.awt.shell=ALL-UNNAMED" +# Check if Java is present and meets the minimal version requirement +if [ "$CURRENT_VERSION" -lt "$MINIMAL_VERSION" ]; then + echo "ERROR: Java version $CURRENT_VERSION is too low. JMeter requires Java $MINIMAL_VERSION or higher." + exit 1 fi : "${JMETER_OPTS:=""}" @@ -169,15 +170,19 @@ esac # Default to en_EN : "${JMETER_LANGUAGE:="-Duser.language=en -Duser.region=EN"}" -# Uncomment this to generate GC verbose file with Java prior to 9 -# VERBOSE_GC="-verbose:gc -Xloggc:gc_jmeter_%p.log -XX:+PrintGCDetails -XX:+PrintGCCause -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC -XX:+PrintGCApplicationConcurrentTime -XX:+PrintAdaptiveSizePolicy -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps" +# Legacy GC verbose options removed (Java 8/9 support discontinued) + +# Optimized GC logging for Java 17 with structured output and performance analysis +# Uncomment to enable comprehensive GC logging with rotation and detailed metrics +# VERBOSE_GC="-Xlog:gc,gc+heap,gc+regions,gc+refine,gc+phases:gc_jmeter_%p_%t.log:time,level,tags:filecount=5,filesize=50M" + +# Alternative: Minimal GC logging for production (uncomment if needed) +# VERBOSE_GC="-Xlog:gc:gc_jmeter_%p.log:time" -# Uncomment this to generate GC verbose file with Java 9 and above -# VERBOSE_GC="-Xlog:gc*,gc+age=trace,gc+heap=debug:file=gc_jmeter_%p.log" +# Docker support for Java 17+ +# Modern container memory detection is automatic in Java 17+ +# RUN_IN_DOCKER="-XX:+UseContainerSupport" -# Uncomment this if you run JMeter in DOCKER (need Java SE 8u131 or JDK 9) -# see https://blogs.oracle.com/java-platform-group/java-se-support-for-docker-cpu-and-memory-limits -# RUN_IN_DOCKER="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap" # Finally, some tracing to help in case things go astray: # You may want to add those settings: @@ -191,7 +196,7 @@ SYSTEM_PROPS="-Djava.security.egd=file:/dev/urandom" SERVER="-server" if [ -z "${JMETER_COMPLETE_ARGS}" ]; then - ARGS="$JAVA9_OPTS $SERVER $DUMP $HEAP $VERBOSE_GC $GC_ALGO $SYSTEM_PROPS $JMETER_LANGUAGE $RUN_IN_DOCKER" + ARGS="$JAVA_OPTS $SERVER $DUMP $HEAP $VERBOSE_GC $GC_ALGO $SYSTEM_PROPS $JMETER_LANGUAGE $RUN_IN_DOCKER" else ARGS="" fi diff --git a/bin/jmeter.bat b/bin/jmeter.bat index e39153dab5b..7a6dae72b3c 100644 --- a/bin/jmeter.bat +++ b/bin/jmeter.bat @@ -78,12 +78,19 @@ if not defined JMETER_LANGUAGE ( ) rem Minimal version to run JMeter -set MINIMAL_VERSION=1.8.0 +set MINIMAL_VERSION=17.0.0 -rem --add-opens if JAVA 9 -set JAVA9_OPTS= +rem Optimized GC logging for Java 17 with structured output and performance analysis +rem Uncomment to enable comprehensive GC logging with rotation and detailed metrics +rem set VERBOSE_GC=-Xlog:gc,gc+heap,gc+regions,gc+refine,gc+phases:gc_jmeter_%%p_%%t.log:time,level,tags:filecount=5,filesize=50M +rem Alternative: Minimal GC logging for production (uncomment if needed) +rem set VERBOSE_GC=-Xlog:gc:gc_jmeter_%%p.log:time + + +rem Module access for modern Java versions (required for JMeter components) +set JAVA_OPTS=--add-opens java.desktop/sun.awt=ALL-UNNAMED --add-opens java.desktop/sun.swing=ALL-UNNAMED --add-opens java.desktop/javax.swing.text.html=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/sun.awt.shell=ALL-UNNAMED for /f "tokens=3" %%g in ('java -version 2^>^&1 ^| findstr /i "version"') do ( rem @echo Debug Output: %%g @@ -95,36 +102,24 @@ if not defined JAVAVER ( goto pause ) - - -rem Check if version is from OpenJDK or Oracle Hotspot JVM prior to 9 containing 1.${version}.x -rem JAVAVER will be equal to "9.0.4" (quotes are part of the value) for Oracle Java 9 -rem JAVAVER will be equal to "1.8.0_161" (quotes are part of the value) for Oracle Java 8 -rem so we extract 2 chars starting from index 1 -IF "%JAVAVER:~1,2%"=="1." ( - set JAVAVER=%JAVAVER:"=% - for /f "delims=. tokens=1-3" %%v in ("%JAVAVER%") do ( - set current_minor=%%w +rem Extract major version number from Java version string +for /f "delims=. tokens=1" %%v in ("%JAVAVER:~1,-1%") do ( + set current_major=%%v ) -) else ( - rem Java 9 at least - set current_minor=9 - set JAVA9_OPTS=--add-opens java.desktop/sun.awt=ALL-UNNAMED --add-opens java.desktop/sun.swing=ALL-UNNAMED --add-opens java.desktop/javax.swing.text.html=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/sun.awt.shell=ALL-UNNAMED -) - -for /f "delims=. tokens=1-3" %%v in ("%MINIMAL_VERSION%") do ( - set minimal_minor=%%w +rem Extract minimal major version +for /f "delims=. tokens=1" %%v in ("%MINIMAL_VERSION%") do ( + set minimal_major=%%v ) -if not defined current_minor ( +if not defined current_major ( @echo Not able to find Java executable or version. Please check your Java installation. set ERRORLEVEL=2 goto pause ) -rem @echo Debug: CURRENT=%current_minor% - MINIMAL=%minimal_minor% -if %current_minor% LSS %minimal_minor% ( - @echo Error: Java version -- %JAVAVER% -- is too low to run JMeter. Needs a Java version greater than or equal to %MINIMAL_VERSION% + +if %current_major% LSS %minimal_major% ( + @echo Error: Java version -- %JAVAVER% -- is too low to run JMeter. Needs Java %MINIMAL_VERSION% or higher. set ERRORLEVEL=3 goto pause ) @@ -151,13 +146,7 @@ if not defined HEAP ( set HEAP=-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m ) -rem Uncomment this to generate GC verbose file with Java prior to 9 -rem set VERBOSE_GC=-verbose:gc -Xloggc:gc_jmeter_%%p.log -XX:+PrintGCDetails -XX:+PrintGCCause -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintAdaptiveSizePolicy - -rem Uncomment this to generate GC verbose file with Java 9 and above -rem set VERBOSE_GC=-Xlog:gc*,gc+age=trace,gc+heap=debug:file=gc_jmeter_%%p.log -rem You may want to add those settings -rem -XX:+ParallelRefProcEnabled -XX:+PerfDisableSharedMem +rem Legacy GC verbose options removed (Java 8/9 support discontinued) if not defined GC_ALGO ( set GC_ALGO=-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1ReservePercent=20 ) @@ -167,9 +156,9 @@ set SYSTEM_PROPS=-Djava.security.egd=file:/dev/urandom rem Always dump on OOM (does not cost anything unless triggered) set DUMP=-XX:+HeapDumpOnOutOfMemoryError -rem Uncomment this if you run JMeter in DOCKER (need Java SE 8u131 or JDK 9) -rem see https://blogs.oracle.com/java-platform-group/java-se-support-for-docker-cpu-and-memory-limits -rem set RUN_IN_DOCKER=-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap +rem Docker support for Java 17+ +rem Modern container memory detection is automatic in Java 17+ +rem set RUN_IN_DOCKER=-XX:+UseContainerSupport rem Additional settings that might help improve GUI performance on some platforms rem See: http://www.oracle.com/technetwork/java/perf-graphics-135933.html @@ -188,7 +177,7 @@ if not defined DDRAW ( rem Collect the settings defined above if not defined JMETER_COMPLETE_ARGS ( - set ARGS=%JAVA9_OPTS% %DUMP% %HEAP% %VERBOSE_GC% %GC_ALGO% %DDRAW% %SYSTEM_PROPS% %JMETER_LANGUAGE% %RUN_IN_DOCKER% + set ARGS=%JAVA_OPTS% %DUMP% %HEAP% %VERBOSE_GC% %GC_ALGO% %DDRAW% %SYSTEM_PROPS% %JMETER_LANGUAGE% %RUN_IN_DOCKER% ) else ( set ARGS= ) diff --git a/bin/jmeter.properties b/bin/jmeter.properties index bac04e85bc9..58c540cfd9c 100644 --- a/bin/jmeter.properties +++ b/bin/jmeter.properties @@ -202,9 +202,9 @@ #Components to not display in JMeter GUI (GUI class name or static label) # These elements are deprecated and will be removed in next version: -# MongoDB Script, MongoDB Source Config, Monitor Results +# Monitor Results # BSF Elements -not_in_menu=org.apache.jmeter.protocol.mongodb.sampler.MongoScriptSampler,org.apache.jmeter.protocol.mongodb.config.MongoSourceElement,\ +not_in_menu=\ org.apache.jmeter.timers.BSFTimer,org.apache.jmeter.modifiers.BSFPreProcessor,org.apache.jmeter.extractor.BSFPostProcessor,org.apache.jmeter.assertions.BSFAssertion,\ org.apache.jmeter.visualizers.BSFListener,org.apache.jmeter.protocol.java.sampler.BSFSampler,\ org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui diff --git a/bin/jmeter.sh b/bin/jmeter.sh index e5cf8a7e0c9..94843d2915c 100755 --- a/bin/jmeter.sh +++ b/bin/jmeter.sh @@ -90,14 +90,17 @@ fi JAVA9_OPTS= # Minimal version to run JMeter -MINIMAL_VERSION=8 +MINIMAL_VERSION=17 # Check if version is from OpenJDK or Oracle Hotspot JVM prior to 9 containing 1.${version}.x CURRENT_VERSION=`"${JAVA_HOME}/bin/java" -version 2>&1 | awk -F'"' '/version/ {gsub("^1[.]", "", $2); gsub("[^0-9].*$", "", $2); print $2}'` # Check if Java is present and the minimal version requirement -if [ "$CURRENT_VERSION" -gt "$MINIMAL_VERSION" ]; then +if [ "$CURRENT_VERSION" -ge "$MINIMAL_VERSION" ]; then JAVA9_OPTS="--add-opens java.desktop/sun.awt=ALL-UNNAMED --add-opens java.desktop/sun.swing=ALL-UNNAMED --add-opens java.desktop/javax.swing.text.html=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED --add-opens=java.desktop/sun.awt.shell=ALL-UNNAMED" +else + echo "JMeter requires Java $MINIMAL_VERSION or later. Current Java version is $CURRENT_VERSION" + exit 1 fi # Don't add additional arguments to the JVM start, except those needed for Java 9 diff --git a/bin/mirror-server.cmd b/bin/mirror-server.cmd index 6f76adf5951..1f86cff1ef1 100644 --- a/bin/mirror-server.cmd +++ b/bin/mirror-server.cmd @@ -27,8 +27,8 @@ set JMETER_CMD_LINE_ARGS=%* cd /D %~dp0 set CP=..\lib\ext\ApacheJMeter_http.jar;..\lib\ext\ApacheJMeter_core.jar;..\lib\jorphan.jar;..\lib\oro-2.0.8.jar -set CP=%CP%;..\lib\slf4j-api-1.7.25.jar;..\lib\jcl-over-slf4j-1.7.25.jar;..\lib\log4j-slf4j-impl-2.11.1.jar -set CP=%CP%;..\lib\log4j-api-2.11.1.jar;..\lib\log4j-core-2.11.1.jar;..\lib\log4j-1.2-api-2.11.1.jar +set CP=%CP%;..\lib\slf4j-api-1.7.36.jar;..\lib\jcl-over-slf4j-1.7.36.jar;..\lib\log4j-slf4j-impl-2.22.1.jar +set CP=%CP%;..\lib\log4j-api-2.22.1.jar;..\lib\log4j-core-2.22.1.jar;..\lib\log4j-1.2-api-2.22.1.jar java -cp %CP% org.apache.jmeter.protocol.http.control.HttpMirrorServer %JMETER_CMD_LINE_ARGS% diff --git a/bin/mirror-server.sh b/bin/mirror-server.sh index 7e969626258..1da2f9d0349 100755 --- a/bin/mirror-server.sh +++ b/bin/mirror-server.sh @@ -22,7 +22,7 @@ cd "$(dirname "$0")" || exit 1 CP=../lib/ext/ApacheJMeter_http.jar:../lib/ext/ApacheJMeter_core.jar:../lib/jorphan.jar:../lib/oro-2.0.8.jar -CP=${CP}:../lib/slf4j-api-1.7.25.jar:../lib/jcl-over-slf4j-1.7.25.jar:../lib/log4j-slf4j-impl-2.11.0.jar -CP=${CP}:../lib/log4j-api-2.11.1.jar:../lib/log4j-core-2.11.1.jar:../lib/log4j-1.2-api-2.11.1.jar +CP=${CP}:../lib/slf4j-api-1.7.36.jar:../lib/jcl-over-slf4j-1.7.36.jar:../lib/log4j-slf4j-impl-2.22.1.jar +CP=${CP}:../lib/log4j-api-2.22.1.jar:../lib/log4j-core-2.22.1.jar:../lib/log4j-1.2-api-2.22.1.jar java -cp $CP org.apache.jmeter.protocol.http.control.HttpMirrorServer "$@" diff --git a/bin/report-template/content/pages/ResponseTimes.html.fmkr b/bin/report-template/content/pages/ResponseTimes.html.fmkr index 9b59d484b74..e3fc05542bb 100644 --- a/bin/report-template/content/pages/ResponseTimes.html.fmkr +++ b/bin/report-template/content/pages/ResponseTimes.html.fmkr @@ -217,7 +217,7 @@

  • Hide all samples
  • -
  • Save as PNG
  • +
  • Save as PNG