From 38cc8ce8b07f4f7471cd66af93b266c900152d1b Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Sat, 6 May 2023 10:19:32 +0300 Subject: [PATCH] feat: use ServiceLoader to find implementations instead of searching classes in jars ServiceLoader is Java standard approach for locating implementaitons, and it allows pluggability without relying on a filesystem layout. Fixes https://github.com/apache/jmeter/issues/5883 --- build-logic/jvm/build.gradle.kts | 3 +- .../kotlin/build-logic.autoservice.gradle.kts | 50 +++++++ .../build-logic.java-library.gradle.kts | 1 + .../main/kotlin/build-logic.java.gradle.kts | 1 + .../kotlin/build-logic.jvm-library.gradle.kts | 3 +- .../main/kotlin/build-logic.kotlin.gradle.kts | 1 + build-logic/root-build/build.gradle.kts | 1 + .../kotlin/build-logic.root-build.gradle.kts | 1 + build.gradle.kts | 3 +- gradle.properties | 2 + settings.gradle.kts | 1 + src/bom-thirdparty/build.gradle.kts | 2 + .../json/render/RenderAsJmesPathRenderer.java | 3 + .../json/render/RenderAsJsonRenderer.java | 4 + .../ExportTransactionAndSamplerNames.java | 6 + .../RenderAsBoundaryExtractor.java | 3 + .../jmeter/visualizers/RenderAsCssJQuery.java | 3 + .../jmeter/visualizers/RenderAsDocument.java | 3 + .../jmeter/visualizers/RenderAsHTML.java | 3 + .../visualizers/RenderAsHTMLFormatted.java | 3 + .../visualizers/RenderAsHTMLWithEmbedded.java | 3 + .../jmeter/visualizers/RenderAsJSON.java | 3 + .../jmeter/visualizers/RenderAsRegexp.java | 3 + .../jmeter/visualizers/RenderAsText.java | 3 + .../jmeter/visualizers/RenderAsXML.java | 3 + .../jmeter/visualizers/RenderAsXPath.java | 3 + .../jmeter/visualizers/RenderAsXPath2.java | 3 + .../jmeter/visualizers/RenderInBrowser.java | 3 + .../jmeter/visualizers/RequestPanel.java | 43 ++---- .../jmeter/visualizers/RequestView.java | 3 + .../jmeter/visualizers/RequestViewRaw.java | 3 + .../jmeter/visualizers/ResultRenderer.java | 2 + .../ViewResultsFullVisualizer.java | 43 ++---- .../AbstractBackendListenerClient.java | 3 + .../backend/BackendListenerClient.java | 2 + .../backend/BackendListenerGui.java | 34 ++--- .../GraphiteBackendListenerClient.java | 4 + .../InfluxDBRawBackendListenerClient.java | 3 + .../InfluxdbBackendListenerClient.java | 4 + .../jmeter/engine/util/CompoundVariable.java | 19 +-- .../org/apache/jmeter/functions/Function.java | 6 +- .../apache/jmeter/gui/HtmlReportAction.java | 7 + .../jmeter/gui/action/AboutCommand.java | 3 + .../jmeter/gui/action/ActionRouter.java | 1 + .../apache/jmeter/gui/action/AddParent.java | 3 + .../action/AddThinkTimeBetweenEachStep.java | 3 + .../apache/jmeter/gui/action/AddToTree.java | 3 + .../gui/action/ApplyNamingConvention.java | 3 + .../jmeter/gui/action/ChangeLanguage.java | 3 + .../jmeter/gui/action/ChangeParent.java | 3 + .../apache/jmeter/gui/action/CheckDirty.java | 3 + .../org/apache/jmeter/gui/action/Clear.java | 3 + .../org/apache/jmeter/gui/action/Close.java | 3 + .../jmeter/gui/action/CollapseExpand.java | 3 + .../gui/action/CollapseExpandTreeBranch.java | 3 + .../gui/action/CompileJSR223TestElements.java | 6 + .../org/apache/jmeter/gui/action/Copy.java | 3 + .../gui/action/CreateFunctionDialog.java | 3 + .../org/apache/jmeter/gui/action/Cut.java | 3 + .../apache/jmeter/gui/action/Duplicate.java | 3 + .../apache/jmeter/gui/action/EditCommand.java | 3 + .../jmeter/gui/action/EnableComponent.java | 3 + .../apache/jmeter/gui/action/ExitCommand.java | 3 + .../org/apache/jmeter/gui/action/Help.java | 3 + .../org/apache/jmeter/gui/action/Load.java | 2 + .../jmeter/gui/action/LoadRecentProject.java | 3 + .../jmeter/gui/action/LogLevelCommand.java | 3 + .../gui/action/LoggerPanelEnableDisable.java | 3 + .../jmeter/gui/action/LookAndFeelCommand.java | 2 + .../org/apache/jmeter/gui/action/Move.java | 3 + .../jmeter/gui/action/OpenLinkAction.java | 3 + .../org/apache/jmeter/gui/action/Paste.java | 3 + .../apache/jmeter/gui/action/RemoteStart.java | 3 + .../org/apache/jmeter/gui/action/Remove.java | 3 + .../jmeter/gui/action/ResetSearchCommand.java | 3 + .../org/apache/jmeter/gui/action/Restart.java | 6 + .../jmeter/gui/action/RevertProject.java | 3 + .../jmeter/gui/action/SSLManagerCommand.java | 3 + .../org/apache/jmeter/gui/action/Save.java | 3 + .../jmeter/gui/action/SaveBeforeRun.java | 3 + .../jmeter/gui/action/SaveGraphics.java | 3 + .../jmeter/gui/action/SchematicView.java | 6 + .../jmeter/gui/action/SearchTreeCommand.java | 3 + .../org/apache/jmeter/gui/action/Start.java | 3 + .../jmeter/gui/action/StopStoppables.java | 3 + .../jmeter/gui/action/TemplatesCommand.java | 3 + .../apache/jmeter/gui/action/UndoCommand.java | 3 + .../org/apache/jmeter/gui/action/What.java | 3 + .../apache/jmeter/gui/action/ZoomInOut.java | 3 + .../apache/jmeter/gui/plugin/MenuCreator.java | 3 + .../apache/jmeter/gui/util/JMeterMenuBar.java | 47 ++---- .../apache/jmeter/gui/util/MenuFactory.java | 2 + .../RemoteThreadsLifeCycleListener.java | 3 + .../threads/RemoteThreadsListenerImpl.java | 38 ++--- .../org/apache/jmeter/util/JMeterUtils.java | 75 ++++++++++ .../org/apache/jorphan/test/AllTests.java | 2 + .../org/apache/jmeter/junit/JMeterTest.java | 1 + .../jmeter/testbeans/gui/PackageTest.java | 1 + .../jorphan/reflect/TestClassFinder.java | 17 ++- .../apache/jmeter/functions/BeanShell.java | 3 + .../org/apache/jmeter/functions/CSVRead.java | 3 + .../apache/jmeter/functions/ChangeCase.java | 3 + .../apache/jmeter/functions/CharFunction.java | 3 + .../functions/DateTimeConvertFunction.java | 3 + .../functions/DigestEncodeFunction.java | 3 + .../apache/jmeter/functions/EscapeHtml.java | 3 + .../functions/EscapeOroRegexpChars.java | 3 + .../apache/jmeter/functions/EscapeXml.java | 3 + .../apache/jmeter/functions/EvalFunction.java | 3 + .../jmeter/functions/EvalVarFunction.java | 3 + .../apache/jmeter/functions/FileToString.java | 3 + .../apache/jmeter/functions/FileWrapper.java | 2 + .../org/apache/jmeter/functions/Groovy.java | 3 + .../org/apache/jmeter/functions/IntSum.java | 3 + .../jmeter/functions/IsPropDefined.java | 3 + .../apache/jmeter/functions/IsVarDefined.java | 3 + .../jmeter/functions/IterationCounter.java | 3 + .../apache/jmeter/functions/JavaScript.java | 3 + .../jmeter/functions/Jexl2Function.java | 3 + .../jmeter/functions/Jexl3Function.java | 3 + .../apache/jmeter/functions/LogFunction.java | 3 + .../apache/jmeter/functions/LogFunction2.java | 3 + .../org/apache/jmeter/functions/LongSum.java | 3 + .../apache/jmeter/functions/MachineIP.java | 3 + .../apache/jmeter/functions/MachineName.java | 3 + .../org/apache/jmeter/functions/Property.java | 3 + .../apache/jmeter/functions/Property2.java | 3 + .../org/apache/jmeter/functions/Random.java | 3 + .../apache/jmeter/functions/RandomDate.java | 2 + .../functions/RandomFromMultipleVars.java | 3 + .../apache/jmeter/functions/RandomString.java | 3 + .../jmeter/functions/RegexFunction.java | 3 + .../apache/jmeter/functions/SamplerName.java | 3 + .../apache/jmeter/functions/SetProperty.java | 3 + .../jmeter/functions/SplitFunction.java | 3 + .../jmeter/functions/StringFromFile.java | 3 + .../apache/jmeter/functions/StringToFile.java | 3 + .../apache/jmeter/functions/TestPlanName.java | 3 + .../jmeter/functions/ThreadGroupName.java | 3 + .../apache/jmeter/functions/ThreadNumber.java | 3 + .../apache/jmeter/functions/TimeFunction.java | 3 + .../apache/jmeter/functions/TimeShift.java | 2 + .../org/apache/jmeter/functions/UnEscape.java | 3 + .../apache/jmeter/functions/UnEscapeHtml.java | 3 + .../apache/jmeter/functions/UrlDecode.java | 3 + .../apache/jmeter/functions/UrlEncode.java | 3 + .../org/apache/jmeter/functions/Uuid.java | 3 + .../org/apache/jmeter/functions/Variable.java | 3 + .../org/apache/jmeter/functions/XPath.java | 3 + .../jmeter/functions/FunctionServicesTest.kt | 43 ++++++ .../apache/jorphan/reflect/ClassFinder.java | 132 ++++++++++++++++- .../CollectServiceLoadExceptionHandler.java | 40 +++++ .../IgnoreServiceLoadExceptionHandler.java | 27 ++++ .../apache/jorphan/reflect/JMeterService.java | 39 +++++ ...gAndIgnoreServiceLoadExceptionHandler.java | 54 +++++++ .../RethrowServiceLoadExceptionHandler.java | 28 ++++ .../reflect/ServiceLoadExceptionHandler.java | 36 +++++ .../jorphan/reflect/ServiceLoadFailure.java | 51 +++++++ .../gui/action/ParseCurlCommandAction.java | 7 + .../http/proxy/DefaultSamplerCreator.java | 2 + .../protocol/http/proxy/SamplerCreator.java | 2 + .../http/proxy/SamplerCreatorFactory.java | 56 +++---- .../sampler/AccessLogSamplerBeanInfo.java | 138 +++++++++--------- .../protocol/http/util/accesslog/Filter.java | 2 + .../http/util/accesslog/LogFilter.java | 3 + .../http/util/accesslog/LogParser.java | 2 + .../accesslog/OrderPreservingLogParser.java | 3 + .../http/util/accesslog/SessionFilter.java | 3 + .../util/accesslog/SharedTCLogParser.java | 3 + .../http/util/accesslog/TCLogParser.java | 3 + .../http/visualizers/RequestViewHTTP.java | 3 + .../java/config/gui/JavaConfigGui.java | 29 ++-- .../java/sampler/JavaSamplerClient.java | 2 + .../jmeter/protocol/java/test/JavaTest.java | 5 +- .../jmeter/protocol/java/test/SleepTest.java | 4 + .../java/control/gui/JUnitTestSamplerGui.java | 12 +- src/test-services/build.gradle.kts | 25 ++++ .../jmeter/util/services/AbstractService.kt | 25 ++++ .../ServiceNotImplementingInterface.kt | 25 ++++ .../util/services/ServiceThrowingException.kt | 31 ++++ .../services/ServiceWithPrivateConstructor.kt | 25 ++++ .../jmeter/util/services/WorkableService.kt | 31 ++++ .../jmeter/util/services/loadServices.kt | 32 ++++ .../src/test/kotlin/ServiceLoaderTest.kt | 98 +++++++++++++ xdocs/changes.xml | 1 + xdocs/usermanual/jmeter_tutorial.xml | 54 ++++++- 186 files changed, 1558 insertions(+), 297 deletions(-) create mode 100644 build-logic/jvm/src/main/kotlin/build-logic.autoservice.gradle.kts create mode 100644 src/functions/src/test/kotlin/org/apache/jmeter/functions/FunctionServicesTest.kt create mode 100644 src/jorphan/src/main/java/org/apache/jorphan/reflect/CollectServiceLoadExceptionHandler.java create mode 100644 src/jorphan/src/main/java/org/apache/jorphan/reflect/IgnoreServiceLoadExceptionHandler.java create mode 100644 src/jorphan/src/main/java/org/apache/jorphan/reflect/JMeterService.java create mode 100644 src/jorphan/src/main/java/org/apache/jorphan/reflect/LogAndIgnoreServiceLoadExceptionHandler.java create mode 100644 src/jorphan/src/main/java/org/apache/jorphan/reflect/RethrowServiceLoadExceptionHandler.java create mode 100644 src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadExceptionHandler.java create mode 100644 src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadFailure.java create mode 100644 src/test-services/build.gradle.kts create mode 100644 src/test-services/src/main/kotlin/org/apache/jmeter/util/services/AbstractService.kt create mode 100644 src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceNotImplementingInterface.kt create mode 100644 src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceThrowingException.kt create mode 100644 src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceWithPrivateConstructor.kt create mode 100644 src/test-services/src/main/kotlin/org/apache/jmeter/util/services/WorkableService.kt create mode 100644 src/test-services/src/main/kotlin/org/apache/jmeter/util/services/loadServices.kt create mode 100644 src/test-services/src/test/kotlin/ServiceLoaderTest.kt diff --git a/build-logic/jvm/build.gradle.kts b/build-logic/jvm/build.gradle.kts index 0c8b9388a6c..7044d76d981 100644 --- a/build-logic/jvm/build.gradle.kts +++ b/build-logic/jvm/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { api(projects.verification) api("com.github.vlsi.crlf:com.github.vlsi.crlf.gradle.plugin:1.88") api("com.github.vlsi.gradle-extensions:com.github.vlsi.gradle-extensions.gradle.plugin:1.88") - api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21") + api("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:1.8.21") + api("org.jetbrains.kotlin.kapt:org.jetbrains.kotlin.kapt.gradle.plugin:1.8.21") api("org.jetbrains.dokka:org.jetbrains.dokka.gradle.plugin:1.8.10") } diff --git a/build-logic/jvm/src/main/kotlin/build-logic.autoservice.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.autoservice.gradle.kts new file mode 100644 index 00000000000..532256faa93 --- /dev/null +++ b/build-logic/jvm/src/main/kotlin/build-logic.autoservice.gradle.kts @@ -0,0 +1,50 @@ +/* + * 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. + */ + +import com.github.vlsi.gradle.dsl.configureEach + +plugins { + id("java-library") +} + +dependencies { + annotationProcessor("com.google.auto.service:auto-service") + compileOnlyApi("com.google.auto.service:auto-service-annotations") +} + +tasks.configureEach { + // Verify @AutoService annotations + options.compilerArgs.add("-Averify=true") +} + +plugins.withId("org.jetbrains.kotlin.jvm") { + apply(plugin = "org.jetbrains.kotlin.kapt") + + dependencies { + "kapt"("com.google.auto.service:auto-service") + // Unfortunately, plugins.withId("..kapt") can't be used as kapt plugin adds configuration via plugins.withId + findProject(":src:bom-thirdparty")?.let { + "kapt"(platform(it)) + } + } +} + +tasks.configureEach { + manifest { + attributes["JMeter-Skip-Class-Scanning"] = "true" + } +} diff --git a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts index 534f894dfc1..b2d6967d718 100644 --- a/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts +++ b/build-logic/jvm/src/main/kotlin/build-logic.java-library.gradle.kts @@ -18,4 +18,5 @@ plugins { id("build-logic.java") id("java-library") + id("build-logic.autoservice") } diff --git a/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts index 175c9740af3..3f4a60691fb 100644 --- a/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts +++ b/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts @@ -59,6 +59,7 @@ dependencies { } findProject(":src:bom-thirdparty")?.let{ api(platform(it)) + annotationProcessor(platform(it)) } } diff --git a/build-logic/jvm/src/main/kotlin/build-logic.jvm-library.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.jvm-library.gradle.kts index cabe6e02820..7f5f0315fd0 100644 --- a/build-logic/jvm/src/main/kotlin/build-logic.jvm-library.gradle.kts +++ b/build-logic/jvm/src/main/kotlin/build-logic.jvm-library.gradle.kts @@ -16,8 +16,7 @@ */ plugins { - id("build-logic.java") - id("java-library") + id("build-logic.java-library") } if (file("src/main/groovy").isDirectory || file("src/test/groovy").isDirectory) { diff --git a/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts index 65cb342e6d1..59a63eb01d1 100644 --- a/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts +++ b/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts @@ -25,6 +25,7 @@ plugins { id("build-logic.test-base") id("com.github.autostyle") kotlin("jvm") + kotlin("kapt") apply false } dependencies { diff --git a/build-logic/root-build/build.gradle.kts b/build-logic/root-build/build.gradle.kts index a467ad083af..5e3d3e23bd8 100644 --- a/build-logic/root-build/build.gradle.kts +++ b/build-logic/root-build/build.gradle.kts @@ -27,4 +27,5 @@ dependencies { api("com.github.vlsi.gradle-extensions:com.github.vlsi.gradle-extensions.gradle.plugin:1.88") api("org.nosphere.apache.rat:org.nosphere.apache.rat.gradle.plugin:0.8.0") api("org.jetbrains.gradle.plugin.idea-ext:org.jetbrains.gradle.plugin.idea-ext.gradle.plugin:1.1.7") + api("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:1.8.21") } diff --git a/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts b/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts index 2e75d5e448a..7fcb96ff8d9 100644 --- a/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts +++ b/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts @@ -26,6 +26,7 @@ plugins { id("com.github.vlsi.ide") id("org.nosphere.apache.rat") id("org.jetbrains.gradle.plugin.idea-ext") + kotlin("jvm") apply false } ide { diff --git a/build.gradle.kts b/build.gradle.kts index 91cb5261d7e..50ac4136f7d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -61,6 +61,7 @@ val notPublishedProjects by extra { projects.src.release, projects.src.testkit, projects.src.testkitWiremock, + projects.src.testServices, ).mapTo(mutableSetOf()) { it.dependencyProject } } @@ -86,7 +87,7 @@ publishedProjects.forEach {project -> throw IllegalStateException( "Project ${project.path} is listed in publishedProjects, however it misses maven-publish plugin. " + "Please add maven-publish plugin (e.g. replace build-logic.jvm-library with build-logic.jvm-published-library) or " + - "move the project to the list of published ones" + "move the project to the list of notPublishedProjects" ) } } diff --git a/gradle.properties b/gradle.properties index 3801fc003f3..eb14a438816 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,6 +21,8 @@ org.gradle.parallel=true org.gradle.caching=true #org.gradle.caching.debug=true +kapt.include.compile.classpath=false + # See https://github.com/gradle/gradle/pull/11358 , https://issues.apache.org/jira/browse/INFRA-14923 # repository.apache.org does not yet support .sha256 and .sha512 checksums systemProp.org.gradle.internal.publish.checksums.insecure=true diff --git a/settings.gradle.kts b/settings.gradle.kts index 2537f530c18..307ffd7b29e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -76,6 +76,7 @@ include( "src:release", "src:testkit", "src:testkit-wiremock", + "src:test-services", "src:dist", "src:dist-check" ) diff --git a/src/bom-thirdparty/build.gradle.kts b/src/bom-thirdparty/build.gradle.kts index c7de1182025..3483b7c2c5d 100644 --- a/src/bom-thirdparty/build.gradle.kts +++ b/src/bom-thirdparty/build.gradle.kts @@ -42,6 +42,8 @@ dependencies { api("bsf:bsf:2.4.0") api("cglib:cglib-nodep:3.3.0") + api("com.google.auto.service:auto-service:1.0.1") + api("com.google.auto.service:auto-service-annotations:1.0.1") api("com.fasterxml.jackson.core:jackson-annotations:2.13.4") api("com.fasterxml.jackson.core:jackson-core:2.13.4") api("com.fasterxml.jackson.core:jackson-databind:2.13.4.2") diff --git a/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJmesPathRenderer.java b/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJmesPathRenderer.java index d2131a3350e..45e1241d3cc 100644 --- a/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJmesPathRenderer.java +++ b/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJmesPathRenderer.java @@ -19,6 +19,7 @@ import org.apache.jmeter.extractor.json.jmespath.JMESPathCache; import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.ResultRenderer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,11 +27,13 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.auto.service.AutoService; /** * Implement ResultsRender for JMES Path tester * @since 5.2 */ +@AutoService(ResultRenderer.class) public class RenderAsJmesPathRenderer extends AbstractRenderAsJsonRenderer { private static final Logger log = LoggerFactory.getLogger(RenderAsJmesPathRenderer.class); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); diff --git a/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJsonRenderer.java b/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJsonRenderer.java index e7d856db82c..0aa1a01132c 100644 --- a/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJsonRenderer.java +++ b/src/components/src/main/java/org/apache/jmeter/extractor/json/render/RenderAsJsonRenderer.java @@ -21,13 +21,17 @@ import org.apache.jmeter.extractor.json.jsonpath.JSONManager; import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.ResultRenderer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Implement ResultsRender for JSON Path tester * @since 3.0 */ +@AutoService(ResultRenderer.class) public class RenderAsJsonRenderer extends AbstractRenderAsJsonRenderer { private static final Logger log = LoggerFactory.getLogger(RenderAsJsonRenderer.class); diff --git a/src/components/src/main/java/org/apache/jmeter/gui/action/ExportTransactionAndSamplerNames.java b/src/components/src/main/java/org/apache/jmeter/gui/action/ExportTransactionAndSamplerNames.java index 093f2173919..cc988f7d9f7 100644 --- a/src/components/src/main/java/org/apache/jmeter/gui/action/ExportTransactionAndSamplerNames.java +++ b/src/components/src/main/java/org/apache/jmeter/gui/action/ExportTransactionAndSamplerNames.java @@ -53,10 +53,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Export transactions names for web report * @since 3.3 */ +@AutoService({ + Command.class, + MenuCreator.class +}) public class ExportTransactionAndSamplerNames extends AbstractAction implements MenuCreator { private static final Logger log = LoggerFactory.getLogger(ExportTransactionAndSamplerNames.class); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java index e48002e246f..236d2db7f33 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java @@ -41,9 +41,12 @@ import org.apache.jorphan.gui.GuiUtils; import org.apache.jorphan.gui.JLabeledTextField; +import com.google.auto.service.AutoService; + /** * Implement ResultsRender for Boundary Extractor tester */ +@AutoService(ResultRenderer.class) public class RenderAsBoundaryExtractor implements ResultRenderer, ActionListener { private static final String BOUNDARY_EXTRACTOR_TESTER_COMMAND = "boundary_extractor_tester"; // $NON-NLS-1$ diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java index e802246feab..aa0be670a02 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java @@ -49,10 +49,13 @@ import org.apache.jorphan.gui.JLabeledTextField; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import com.google.auto.service.AutoService; + /** * Implement ResultsRender for CSS/JQuery tester * @since 2.10 */ +@AutoService(ResultRenderer.class) public class RenderAsCssJQuery implements ResultRenderer, ActionListener { private static final String CSSJQUEY_TESTER_COMMAND = "cssjquery_tester"; // $NON-NLS-1$ diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsDocument.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsDocument.java index d7fde8f6419..0bb01d437d0 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsDocument.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsDocument.java @@ -23,6 +23,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + +@AutoService(ResultRenderer.class) public class RenderAsDocument extends SamplerResultTab implements ResultRenderer { private static final Logger log = LoggerFactory.getLogger(RenderAsDocument.class); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTML.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTML.java index 043e7bc9f9e..cd26262a158 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTML.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTML.java @@ -33,6 +33,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + +@AutoService(ResultRenderer.class) public class RenderAsHTML extends SamplerResultTab implements ResultRenderer { private static final Logger log = LoggerFactory.getLogger(RenderAsHTML.class); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLFormatted.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLFormatted.java index cfbb7d208e2..66b2b84ab47 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLFormatted.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLFormatted.java @@ -21,6 +21,9 @@ import org.apache.jmeter.util.JMeterUtils; import org.jsoup.Jsoup; +import com.google.auto.service.AutoService; + +@AutoService(ResultRenderer.class) public class RenderAsHTMLFormatted extends SamplerResultTab implements ResultRenderer { /** {@inheritDoc} */ diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java index d92f150120c..4fa67e05694 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java @@ -20,6 +20,9 @@ import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + +@AutoService(ResultRenderer.class) public class RenderAsHTMLWithEmbedded extends RenderAsHTML implements ResultRenderer { diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsJSON.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsJSON.java index b1550a754b0..b99d3d6c590 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsJSON.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsJSON.java @@ -29,6 +29,9 @@ import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; +import com.google.auto.service.AutoService; + +@AutoService(ResultRenderer.class) public class RenderAsJSON extends SamplerResultTab implements ResultRenderer { private static final String TAB_SEPARATOR = " "; //$NON-NLS-1$ diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java index 375d90299d5..01d8358e033 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java @@ -50,9 +50,12 @@ import org.apache.oro.text.regex.Perl5Compiler; import org.apache.oro.text.regex.Perl5Matcher; +import com.google.auto.service.AutoService; + /** * Implement ResultsRender for Regexp tester */ +@AutoService(ResultRenderer.class) public class RenderAsRegexp implements ResultRenderer, ActionListener { private static final String REGEXP_TESTER_COMMAND = "regexp_tester"; // $NON-NLS-1$ diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsText.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsText.java index 34616fe9824..5d315677a52 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsText.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsText.java @@ -20,6 +20,9 @@ import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + +@AutoService(ResultRenderer.class) public class RenderAsText extends SamplerResultTab implements ResultRenderer { /** {@inheritDoc} */ diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXML.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXML.java index 5c8b6af25bc..beb976f2ad0 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXML.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXML.java @@ -53,6 +53,9 @@ import org.w3c.tidy.Tidy; import org.xml.sax.SAXException; +import com.google.auto.service.AutoService; + +@AutoService(ResultRenderer.class) public class RenderAsXML extends SamplerResultTab implements ResultRenderer { diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java index df39c62e71f..6058ce7ca3f 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java @@ -59,10 +59,13 @@ import org.w3c.dom.Document; import org.xml.sax.SAXException; +import com.google.auto.service.AutoService; + /** * Implement ResultsRender for XPath tester */ +@AutoService(ResultRenderer.class) public class RenderAsXPath implements ResultRenderer, ActionListener { private static final Logger log = LoggerFactory.getLogger(RenderAsXPath.class); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java index e36484bf90f..42214a70afe 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java @@ -48,9 +48,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Implement ResultsRender for XPath tester */ +@AutoService(ResultRenderer.class) public class RenderAsXPath2 implements ResultRenderer, ActionListener { private static final Logger log = LoggerFactory.getLogger(RenderAsXPath2.class); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderInBrowser.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderInBrowser.java index 575313c359a..eab0bfd39ac 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderInBrowser.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderInBrowser.java @@ -27,6 +27,8 @@ import javax.swing.JProgressBar; import javax.swing.SwingUtilities; +import com.google.auto.service.AutoService; + import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.util.JMeterUtils; @@ -43,6 +45,7 @@ * {@link ResultRenderer} implementation that uses JAVAFX WebEngine to render as browser do * @since 3.2 */ +@AutoService(ResultRenderer.class) public class RenderInBrowser extends SamplerResultTab implements ResultRenderer { private JFXPanel jfxPanel; diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestPanel.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestPanel.java index ac3a6a09e17..2b668a8ec39 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestPanel.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestPanel.java @@ -18,11 +18,9 @@ package org.apache.jmeter.visualizers; import java.awt.BorderLayout; -import java.io.IOException; import java.util.ArrayDeque; -import java.util.Collections; import java.util.Deque; -import java.util.List; +import java.util.ServiceLoader; import javax.swing.JPanel; import javax.swing.JTabbedPane; @@ -30,6 +28,7 @@ import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,37 +51,23 @@ public class RequestPanel { */ public RequestPanel() { listRequestView = new ArrayDeque<>(); - List classesToAdd = Collections. emptyList(); - try { - classesToAdd = JMeterUtils.findClassesThatExtend(RequestView.class); - } catch (IOException e1) { - // ignored - } String rawTab = JMeterUtils.getResString(RequestViewRaw.KEY_LABEL); // $NON-NLS-1$ - Object rawObject = null; - for (String clazz : classesToAdd) { - try { - // Instantiate requestview classes - final RequestView requestView = Class.forName(clazz) - .asSubclass(RequestView.class) - .getDeclaredConstructor().newInstance(); - if (rawTab.equals(requestView.getLabel())) { - rawObject = requestView; // use later - } else { - listRequestView.add(requestView); - } - } - catch (NoClassDefFoundError e) { - log.error("Exception registering implementation: [{}] of interface: [{}], a dependency used by the plugin class is missing", - clazz, RequestView.class, e); - } catch (Exception e) { - log.error("Exception registering implementation: [{}] of interface: [{}], a jar is probably missing", - clazz, RequestView.class, e); + RequestView rawObject = null; + for (RequestView requestView : JMeterUtils.loadServicesAndScanJars( + RequestView.class, + ServiceLoader.load(RequestView.class), + Thread.currentThread().getContextClassLoader(), + new LogAndIgnoreServiceLoadExceptionHandler(log) + )) { + if (rawTab.equals(requestView.getLabel())) { + rawObject = requestView; // use later + } else { + listRequestView.add(requestView); } } // place raw tab in first position (first tab) if (rawObject != null) { - listRequestView.addFirst((RequestView) rawObject); + listRequestView.addFirst(rawObject); } // Prepare the Request tabbed pane diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestView.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestView.java index 792f5e2ffcb..e84cc4c0388 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestView.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestView.java @@ -19,12 +19,15 @@ import javax.swing.JPanel; +import org.apache.jorphan.reflect.JMeterService; + /** * Interface for request panel in View Results Tree * All classes which implements this interface is display * on bottom tab in request panel * */ +@JMeterService public interface RequestView { /** diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestViewRaw.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestViewRaw.java index c721a152d55..b253e0381d7 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RequestViewRaw.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RequestViewRaw.java @@ -30,10 +30,13 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.gui.GuiUtils; +import com.google.auto.service.AutoService; + /** * (historical) Panel to view request data * */ +@AutoService(RequestView.class) public class RequestViewRaw implements RequestView { // Used by Request Panel diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/ResultRenderer.java b/src/components/src/main/java/org/apache/jmeter/visualizers/ResultRenderer.java index 3f9e5f22b98..dba2ba3e580 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/ResultRenderer.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/ResultRenderer.java @@ -22,11 +22,13 @@ import javax.swing.JTabbedPane; import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.reflect.JMeterService; /** * Interface to results render */ +@JMeterService public interface ResultRenderer { void clearData(); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java b/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java index 4f44ca2219e..5ec83c5e5c6 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java @@ -25,7 +25,6 @@ import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; -import java.io.IOException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -36,6 +35,7 @@ import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.ServiceLoader; import java.util.Set; import java.util.stream.Collectors; @@ -73,6 +73,7 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.gui.AbstractVisualizer; import org.apache.jorphan.gui.JMeterUIDefaults; +import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler; import org.apache.jorphan.util.StringWrap; import org.apiguardian.api.API; import org.slf4j.Logger; @@ -454,38 +455,24 @@ private Component createComboRender() { selectRenderPanel.addActionListener(this); // if no results render in jmeter.properties, load Standard (default) - List classesToAdd = Collections.emptyList(); - try { - classesToAdd = JMeterUtils.findClassesThatExtend(ResultRenderer.class); - } catch (IOException e1) { - // ignored - } String defaultRenderer = expandToClassname(".RenderAsText"); // $NON-NLS-1$ if (VIEWERS_ORDER.length() > 0) { defaultRenderer = expandToClassname(VIEWERS_ORDER.split(",", 2)[0]); } - Object defaultObject = null; - Map map = new HashMap<>(classesToAdd.size()); - for (String clazz : classesToAdd) { - try { - // Instantiate render classes - final ResultRenderer renderer = Class.forName(clazz) - .asSubclass(ResultRenderer.class) - .getDeclaredConstructor().newInstance(); - if (defaultRenderer.equals(clazz)) { - defaultObject=renderer; - } - renderer.setBackgroundColor(getBackground()); - map.put(renderer.getClass().getName(), renderer); - } catch (NoClassDefFoundError e) { // NOSONAR See bug 60583 - if (e.getMessage() != null && e.getMessage().contains("javafx")) { - log.info("Add JavaFX to your Java installation if you want to use renderer: {}", clazz); - } else { - log.warn("Error loading result renderer: {}", clazz, e); - } - } catch (Exception e) { - log.warn("Error loading result renderer: {}", clazz, e); + ResultRenderer defaultObject = null; + Map map = new HashMap<>(); + for (ResultRenderer renderer : JMeterUtils.loadServicesAndScanJars( + ResultRenderer.class, + ServiceLoader.load(ResultRenderer.class), + Thread.currentThread().getContextClassLoader(), + new LogAndIgnoreServiceLoadExceptionHandler(log) + )) { + // Instantiate render classes + if (defaultRenderer.equals(renderer.getClass().getName())) { + defaultObject = renderer; } + renderer.setBackgroundColor(getBackground()); + map.put(renderer.getClass().getName(), renderer); } if (VIEWERS_ORDER.length() > 0) { Arrays.stream(VIEWERS_ORDER.split(",")) diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/AbstractBackendListenerClient.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/AbstractBackendListenerClient.java index ec0a870e431..79cdd58a6ec 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/AbstractBackendListenerClient.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/AbstractBackendListenerClient.java @@ -24,6 +24,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * An abstract implementation of the BackendListenerClient interface. This * implementation provides default implementations of most of the methods in the @@ -49,6 +51,7 @@ * @see BackendListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent) * @since 2.13 */ +@AutoService(BackendListenerClient.class) public abstract class AbstractBackendListenerClient implements BackendListenerClient { private static final Logger log = LoggerFactory.getLogger(AbstractBackendListenerClient.class); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerClient.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerClient.java index e3aee0aa100..b8ab2dc4e79 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerClient.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerClient.java @@ -21,6 +21,7 @@ import org.apache.jmeter.config.Arguments; import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.reflect.JMeterService; /** * This interface defines the interactions between the {@link BackendListener} @@ -63,6 +64,7 @@ * * @since 2.13 */ +@JMeterService public interface BackendListenerClient { /** diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerGui.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerGui.java index dc98715df5f..2e79427ed17 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerGui.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/BackendListenerGui.java @@ -20,11 +20,10 @@ import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; +import java.util.ServiceLoader; import java.util.Set; import javax.swing.ComboBoxModel; @@ -33,7 +32,6 @@ import javax.swing.JPanel; import javax.swing.JTextField; -import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.config.Argument; import org.apache.jmeter.config.Arguments; @@ -45,7 +43,7 @@ import org.apache.jmeter.testelement.property.JMeterProperty; import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.gui.AbstractListenerGui; -import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,7 +75,6 @@ public class BackendListenerGui extends AbstractListenerGui implements ActionLis /** The current className of the Backend listener **/ private String className; - /** * Create a new BackendListenerGui as a standalone component. */ @@ -117,25 +114,18 @@ private void init() {// called from ctor, so must not be overridable * @return a panel containing the relevant components */ private JPanel createClassnamePanel() { - List possibleClasses = new ArrayList<>(); - - try { - // Find all the classes which implement the BackendListenerClient - // interface. - possibleClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), - new Class[] { BackendListenerClient.class }); - - // Remove the BackendListener class from the list since it only - // implements the interface for error conditions. - - possibleClasses.remove(BackendListener.class.getName() + "$ErrorBackendListenerClient"); - } catch (Exception e) { - log.debug("Exception getting interfaces.", e); - } - JLabel label = new JLabel(JMeterUtils.getResString("backend_listener_classname")); // $NON-NLS-1$ - classnameCombo = new JComboBox<>(possibleClasses.toArray(ArrayUtils.EMPTY_STRING_ARRAY)); + String[] listenerClasses = JMeterUtils.loadServicesAndScanJars( + BackendListenerClient.class, + ServiceLoader.load(BackendListenerClient.class), + Thread.currentThread().getContextClassLoader(), + new LogAndIgnoreServiceLoadExceptionHandler(log) + ).stream() + .map(s -> s.getClass().getName()) + .sorted() + .toArray(String[]::new); + classnameCombo = new JComboBox<>(listenerClasses); classnameCombo.addActionListener(this); classnameCombo.setEditable(false); label.setLabelFor(classnameCombo); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java index 16d8b4a7dbc..36ad35ec000 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java @@ -37,17 +37,21 @@ import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; +import org.apache.jmeter.visualizers.backend.BackendListenerClient; import org.apache.jmeter.visualizers.backend.BackendListenerContext; import org.apache.jmeter.visualizers.backend.SamplerMetric; import org.apache.jmeter.visualizers.backend.UserMetric; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Graphite based Listener using Pickle Protocol * @see Graphite Overview * @since 2.13 */ +@AutoService(BackendListenerClient.class) public class GraphiteBackendListenerClient extends AbstractBackendListenerClient implements Runnable { //+ Argument names diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxDBRawBackendListenerClient.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxDBRawBackendListenerClient.java index f959691f177..57be7a8a39f 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxDBRawBackendListenerClient.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxDBRawBackendListenerClient.java @@ -29,6 +29,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Implementation of {@link BackendListenerClient} to write the response times * of every sample to InfluxDB. If more "raw" information is required in InfluxDB @@ -38,6 +40,7 @@ * * @since 5.3 */ +@AutoService(BackendListenerClient.class) public class InfluxDBRawBackendListenerClient implements BackendListenerClient { private static final Logger log = LoggerFactory.getLogger(InfluxDBRawBackendListenerClient.class); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxdbBackendListenerClient.java b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxdbBackendListenerClient.java index 0dc8d4f7fc8..11a8739b8c9 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxdbBackendListenerClient.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/backend/influxdb/InfluxdbBackendListenerClient.java @@ -36,6 +36,7 @@ import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; +import org.apache.jmeter.visualizers.backend.BackendListenerClient; import org.apache.jmeter.visualizers.backend.BackendListenerContext; import org.apache.jmeter.visualizers.backend.ErrorMetric; import org.apache.jmeter.visualizers.backend.SamplerMetric; @@ -43,12 +44,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Implementation of {@link AbstractBackendListenerClient} to write to InfluxDB * using a custom schema; since JMeter 5.2, this also support the InfluxDB v2. * * @since 3.2 */ +@AutoService(BackendListenerClient.class) public class InfluxdbBackendListenerClient extends AbstractBackendListenerClient implements Runnable { private static final Logger log = LoggerFactory.getLogger(InfluxdbBackendListenerClient.class); diff --git a/src/core/src/main/java/org/apache/jmeter/engine/util/CompoundVariable.java b/src/core/src/main/java/org/apache/jmeter/engine/util/CompoundVariable.java index 1059d4675da..b298fde061d 100644 --- a/src/core/src/main/java/org/apache/jmeter/engine/util/CompoundVariable.java +++ b/src/core/src/main/java/org/apache/jmeter/engine/util/CompoundVariable.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.ServiceLoader; import org.apache.jmeter.functions.Function; import org.apache.jmeter.functions.InvalidVariableException; @@ -30,7 +31,7 @@ import org.apache.jmeter.threads.JMeterContext; import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.util.JMeterUtils; -import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,15 +70,15 @@ public class CompoundVariable implements Function { log.info("Note: Function class names must not contain the string: '{}'", notContain); } - List classes = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), - new Class[] { Function.class }, true, contain, notContain); - for (String clazzName : classes) { - Function tempFunc = Class.forName(clazzName) - .asSubclass(Function.class) - .getDeclaredConstructor().newInstance(); - String referenceKey = tempFunc.getReferenceKey(); + for (Function function : JMeterUtils.loadServicesAndScanJars( + Function.class, + ServiceLoader.load(Function.class), + Thread.currentThread().getContextClassLoader(), + new LogAndIgnoreServiceLoadExceptionHandler(log) + )) { + String referenceKey = function.getReferenceKey(); if (referenceKey.length() > 0) { // ignore self - functions.put(referenceKey, tempFunc.getClass()); + functions.put(referenceKey, function.getClass()); } } diff --git a/src/core/src/main/java/org/apache/jmeter/functions/Function.java b/src/core/src/main/java/org/apache/jmeter/functions/Function.java index ff27cd5595a..fb4d759d108 100644 --- a/src/core/src/main/java/org/apache/jmeter/functions/Function.java +++ b/src/core/src/main/java/org/apache/jmeter/functions/Function.java @@ -23,18 +23,20 @@ import org.apache.jmeter.engine.util.CompoundVariable; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.samplers.Sampler; +import org.apache.jorphan.reflect.JMeterService; /** * Methods that a function must implement */ +@JMeterService public interface Function { /** * Given the previous SampleResult and the current Sampler, return a string * to use as a replacement value for the function call. Assume * "setParameter" was previously called. * - * This method must be threadsafe - multiple threads will be using the same - * object. + *

This method must be thread-safe - multiple threads will be using the same + * object.

* @param previousResult The previous {@link SampleResult} * @param currentSampler The current {@link Sampler} * @return The replacement value, which was generated by the function diff --git a/src/core/src/main/java/org/apache/jmeter/gui/HtmlReportAction.java b/src/core/src/main/java/org/apache/jmeter/gui/HtmlReportAction.java index cecd111bdb5..42e1e175ef2 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/HtmlReportAction.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/HtmlReportAction.java @@ -30,9 +30,16 @@ import org.apache.jmeter.gui.action.AbstractAction; import org.apache.jmeter.gui.action.ActionNames; import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.Command; import org.apache.jmeter.gui.plugin.MenuCreator; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + +@AutoService({ + Command.class, + MenuCreator.class +}) public class HtmlReportAction extends AbstractAction implements MenuCreator { private static final Set commands = new HashSet<>(); private HtmlReportUI htmlReportPanel; diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/AboutCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/AboutCommand.java index d2003800788..4be2b383893 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/AboutCommand.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/AboutCommand.java @@ -42,10 +42,13 @@ import org.apache.jmeter.gui.util.EscapeDialog; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * About Command. It may be extended in the future to add a list of installed * protocols, config options, etc. */ +@AutoService(Command.class) public class AboutCommand extends AbstractAction { private static final Set commandSet; diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ActionRouter.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ActionRouter.java index 53110bd3a94..49b66206c14 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/ActionRouter.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ActionRouter.java @@ -309,6 +309,7 @@ private static void actionPerformed(Class action, ActionEvent } } + @SuppressWarnings("deprecation") private static List findClassesThatExtend(String className, String excluding, String[] searchPath) throws IOException, ClassNotFoundException { return ClassFinder.findClassesThatExtend( diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/AddParent.java b/src/core/src/main/java/org/apache/jmeter/gui/action/AddParent.java index 32dad85108c..4e8b9b8b420 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/AddParent.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/AddParent.java @@ -28,9 +28,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Implements the Add Parent menu command */ +@AutoService(Command.class) public class AddParent extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(AddParent.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/AddThinkTimeBetweenEachStep.java b/src/core/src/main/java/org/apache/jmeter/gui/action/AddThinkTimeBetweenEachStep.java index b7966e79469..44ec34fc653 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/AddThinkTimeBetweenEachStep.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/AddThinkTimeBetweenEachStep.java @@ -33,10 +33,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Add ThinkTime (TestAction + UniformRandomTimer) * @since 3.2 */ +@AutoService(Command.class) public class AddThinkTimeBetweenEachStep extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(AddThinkTimeBetweenEachStep.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/AddToTree.java b/src/core/src/main/java/org/apache/jmeter/gui/action/AddToTree.java index 8de19b98042..ecd423060c3 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/AddToTree.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/AddToTree.java @@ -32,6 +32,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + +@AutoService(Command.class) public class AddToTree extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(AddToTree.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ApplyNamingConvention.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ApplyNamingConvention.java index 2cda16bb13d..01ea85f2692 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/ApplyNamingConvention.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ApplyNamingConvention.java @@ -32,10 +32,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Allows to apply naming convention on nodes * @since 3.2 */ +@AutoService(Command.class) public class ApplyNamingConvention extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(ApplyNamingConvention.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeLanguage.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeLanguage.java index 1cb28073816..2d72b1f041f 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeLanguage.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeLanguage.java @@ -28,9 +28,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Change language */ +@AutoService(Command.class) public class ChangeLanguage extends AbstractActionWithNoRunningTest { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeParent.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeParent.java index 3396c51fed7..8c9729416a9 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeParent.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ChangeParent.java @@ -38,9 +38,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Allows to change Controller implementation */ +@AutoService(Command.class) public class ChangeParent extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(ChangeParent.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/CheckDirty.java b/src/core/src/main/java/org/apache/jmeter/gui/action/CheckDirty.java index 3ab37b81e55..eb3885d32cd 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/CheckDirty.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/CheckDirty.java @@ -33,10 +33,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Check if the TestPlan has been changed since it was last saved * */ +@AutoService(Command.class) public class CheckDirty extends AbstractAction implements HashTreeTraverser, ActionListener { private static final Logger log = LoggerFactory.getLogger(CheckDirty.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Clear.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Clear.java index 1f6964d0b87..a3f3a3998b7 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Clear.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Clear.java @@ -28,12 +28,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Handles the following actions: * - Clear (Data) * - Clear All (Data) * - Reset (Clear GUI) */ +@AutoService(Command.class) public class Clear extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(Clear.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Close.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Close.java index 405f39bb8b9..a0c8348ed4c 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Close.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Close.java @@ -29,11 +29,14 @@ import org.apache.jmeter.services.FileServer; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * This command clears the existing test plan, allowing the creation of a New * test plan. * */ +@AutoService(Command.class) public class Close extends AbstractActionWithNoRunningTest { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpand.java index 1008ef763ca..72da59d65fa 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpand.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpand.java @@ -25,10 +25,13 @@ import org.apache.jmeter.gui.GuiPackage; +import com.google.auto.service.AutoService; + /** * Processes the Collapse All and Expand All options. * */ +@AutoService(Command.class) public class CollapseExpand extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpandTreeBranch.java b/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpandTreeBranch.java index a70509d6faf..2b7af0730c5 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpandTreeBranch.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/CollapseExpandTreeBranch.java @@ -30,9 +30,12 @@ import org.apache.jmeter.gui.tree.JMeterTreeListener; import org.apache.jmeter.gui.tree.JMeterTreeNode; +import com.google.auto.service.AutoService; + /** * Processes the collapse and expand of a tree branch */ +@AutoService(Command.class) public class CollapseExpandTreeBranch extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java b/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java index f2dd30e9854..7539d09d6d9 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java @@ -38,10 +38,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Compile JSR223 Test Element that use Compilable script language * @since 5.1 */ +@AutoService({ + Command.class, + MenuCreator.class +}) public class CompileJSR223TestElements extends AbstractAction implements MenuCreator { private static final Logger log = LoggerFactory.getLogger(CompileJSR223TestElements.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Copy.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Copy.java index 96165d07d74..3979512efe2 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Copy.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Copy.java @@ -37,9 +37,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Implements the Copy menu command */ +@AutoService(Command.class) public class Copy extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(Copy.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/CreateFunctionDialog.java b/src/core/src/main/java/org/apache/jmeter/gui/action/CreateFunctionDialog.java index 371a7e15a25..5e4d43a6f0a 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/CreateFunctionDialog.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/CreateFunctionDialog.java @@ -23,6 +23,9 @@ import org.apache.jmeter.functions.gui.FunctionHelper; +import com.google.auto.service.AutoService; + +@AutoService(Command.class) public class CreateFunctionDialog extends AbstractAction { private static final Set commands; diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Cut.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Cut.java index 43e2ffbb09b..a5a8f201a3b 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Cut.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Cut.java @@ -24,9 +24,12 @@ import org.apache.jmeter.gui.GuiPackage; import org.apache.jmeter.gui.tree.JMeterTreeNode; +import com.google.auto.service.AutoService; + /** * Implements the Cut menu item command */ +@AutoService(Command.class) public class Cut extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Duplicate.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Duplicate.java index 683a4d8495e..996cc149e8d 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Duplicate.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Duplicate.java @@ -26,9 +26,12 @@ import org.apache.jmeter.gui.tree.JMeterTreeModel; import org.apache.jmeter.gui.tree.JMeterTreeNode; +import com.google.auto.service.AutoService; + /** * Implements the Duplicate menu command */ +@AutoService(Command.class) public class Duplicate extends AbstractAction { private static final HashSet commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/EditCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/EditCommand.java index 705286de042..a6e8c8025e9 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/EditCommand.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/EditCommand.java @@ -25,9 +25,12 @@ import org.apache.jmeter.gui.JMeterGUIComponent; import org.apache.jorphan.gui.ui.TextComponentUI; +import com.google.auto.service.AutoService; + /** * Implements the Edit menu item. */ +@AutoService(Command.class) public class EditCommand extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/EnableComponent.java b/src/core/src/main/java/org/apache/jmeter/gui/action/EnableComponent.java index 65f44071877..e5b9a225ed3 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/EnableComponent.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/EnableComponent.java @@ -26,9 +26,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Implements the Enable menu item. */ +@AutoService(Command.class) public class EnableComponent extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(EnableComponent.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ExitCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ExitCommand.java index e9478721f06..3868e3c844d 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/ExitCommand.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ExitCommand.java @@ -26,6 +26,9 @@ import org.apache.jmeter.gui.GuiPackage; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + +@AutoService(Command.class) public class ExitCommand extends AbstractActionWithNoRunningTest { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Help.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Help.java index ab39e349ee8..78f9767b5a8 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Help.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Help.java @@ -35,9 +35,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Implements the Help menu item. */ +@AutoService(Command.class) public class Help extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(Help.class); private static final boolean USE_LOCAL_HELP = diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Load.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Load.java index 0755b855b63..10b997d141d 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Load.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Load.java @@ -42,6 +42,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.io.StreamException; @@ -49,6 +50,7 @@ * Handles the Open (load a new file) and Merge commands. * */ +@AutoService(Command.class) public class Load extends AbstractActionWithNoRunningTest { private static final Logger log = LoggerFactory.getLogger(Load.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/LoadRecentProject.java b/src/core/src/main/java/org/apache/jmeter/gui/action/LoadRecentProject.java index 9b684ea07cd..0b394a749b4 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/LoadRecentProject.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/LoadRecentProject.java @@ -34,10 +34,13 @@ import javax.swing.JComponent; import javax.swing.JMenuItem; +import com.google.auto.service.AutoService; + /** * Handles the loading of recent files, and also the content and * visibility of menu items for loading the recent files */ +@AutoService(Command.class) public class LoadRecentProject extends Load { /** Prefix for the user preference key */ private static final String USER_PREFS_KEY = "recent_file_"; //$NON-NLS-1$ diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/LogLevelCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/LogLevelCommand.java index a21473f19c6..b00337f774a 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/LogLevelCommand.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/LogLevelCommand.java @@ -29,10 +29,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Implements log level setting menu item. * @since 3.2 */ +@AutoService(Command.class) public class LogLevelCommand extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(LogLevelCommand.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java b/src/core/src/main/java/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java index 7b7c30ee42c..f3b3c3e1d97 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java @@ -26,10 +26,13 @@ import org.apache.jmeter.gui.GuiPackage; +import com.google.auto.service.AutoService; + /** * Hide / unhide LoggerPanel. * */ +@AutoService(Command.class) public class LoggerPanelEnableDisable extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/LookAndFeelCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/LookAndFeelCommand.java index 260c59f4f83..0821b8a05d3 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/LookAndFeelCommand.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/LookAndFeelCommand.java @@ -40,10 +40,12 @@ import com.github.weisj.darklaf.LafManager; import com.github.weisj.darklaf.theme.DarculaTheme; import com.github.weisj.darklaf.theme.Theme; +import com.google.auto.service.AutoService; /** * Implements the Look and Feel menu item. */ +@AutoService(Command.class) public class LookAndFeelCommand extends AbstractAction { private static final String JMETER_LAF = "jmeter.laf"; // $NON-NLS-1$ diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Move.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Move.java index 90350e2a31b..d39bde84a55 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Move.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Move.java @@ -33,10 +33,13 @@ import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.testelement.TestPlan; +import com.google.auto.service.AutoService; + /** * Move a node up/down/left/right * */ +@AutoService(Command.class) public class Move extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/OpenLinkAction.java b/src/core/src/main/java/org/apache/jmeter/gui/action/OpenLinkAction.java index 418b1b0d1b2..be6ea6c430d 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/OpenLinkAction.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/OpenLinkAction.java @@ -29,6 +29,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + +@AutoService(Command.class) public class OpenLinkAction extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(OpenLinkAction.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Paste.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Paste.java index 6506d3faa5d..cc6f6703288 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Paste.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Paste.java @@ -33,9 +33,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Places a copied JMeterTreeNode under the selected node. */ +@AutoService(Command.class) public class Paste extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(Paste.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/RemoteStart.java b/src/core/src/main/java/org/apache/jmeter/gui/action/RemoteStart.java index bb7e67148d1..7ca26794da8 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/RemoteStart.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/RemoteStart.java @@ -33,6 +33,9 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.collections.HashTree; +import com.google.auto.service.AutoService; + +@AutoService(Command.class) public class RemoteStart extends AbstractAction { private static final String LOCAL_HOST = "127.0.0.1"; // NOSONAR $NON-NLS-1$ diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Remove.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Remove.java index 3e18f68e88a..d3a95d85172 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Remove.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Remove.java @@ -29,9 +29,12 @@ import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Implements the Remove menu item. */ +@AutoService(Command.class) public class Remove extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ResetSearchCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ResetSearchCommand.java index d10be439acd..32ae567bf0c 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/ResetSearchCommand.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ResetSearchCommand.java @@ -26,9 +26,12 @@ import org.apache.jmeter.gui.Searchable; import org.apache.jmeter.gui.tree.JMeterTreeNode; +import com.google.auto.service.AutoService; + /** * Reset Search */ +@AutoService(Command.class) public class ResetSearchCommand extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Restart.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Restart.java index 2b49b694988..e2fcc1d39fd 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Restart.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Restart.java @@ -39,11 +39,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Restart JMeter * Based on https://dzone.com/articles/programmatically-restart-java * @since 5.0 */ +@AutoService({ + Command.class, + MenuCreator.class +}) public class Restart extends AbstractActionWithNoRunningTest implements MenuCreator { private static final Logger log = LoggerFactory.getLogger(Restart.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/RevertProject.java b/src/core/src/main/java/org/apache/jmeter/gui/action/RevertProject.java index 23f5e5bc307..f05ebba94b6 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/RevertProject.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/RevertProject.java @@ -27,10 +27,13 @@ import org.apache.jmeter.gui.GuiPackage; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Handles the Revert Project command. * */ +@AutoService(Command.class) public class RevertProject extends AbstractActionWithNoRunningTest { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/SSLManagerCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/SSLManagerCommand.java index e3a1e47d5a1..9b0ec71ec37 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/SSLManagerCommand.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/SSLManagerCommand.java @@ -32,6 +32,8 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.util.SSLManager; +import com.google.auto.service.AutoService; + // /** * SSL Manager Command. The SSL Manager provides a mechanism to change your @@ -52,6 +54,7 @@ * already defined via the property. * */ +@AutoService(Command.class) public class SSLManagerCommand extends AbstractAction { private static final Set commandSet; static { diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Save.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Save.java index 9b6c6f42368..32e3846cac3 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Save.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Save.java @@ -56,12 +56,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Save the current test plan; implements: * Save * Save TestPlan As * Save (Selection) As */ +@AutoService(Command.class) public class Save extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(Save.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/SaveBeforeRun.java b/src/core/src/main/java/org/apache/jmeter/gui/action/SaveBeforeRun.java index ac4e1929f06..39191e52962 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/SaveBeforeRun.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/SaveBeforeRun.java @@ -23,11 +23,14 @@ import org.apache.jmeter.gui.GuiPackage; +import com.google.auto.service.AutoService; + /** * Save Before Run Action To save test plan before GUI execution * * @since 4.0 */ +@AutoService(Command.class) public class SaveBeforeRun extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/SaveGraphics.java b/src/core/src/main/java/org/apache/jmeter/gui/action/SaveGraphics.java index 481809c44ee..c73cabefc3d 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/SaveGraphics.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/SaveGraphics.java @@ -34,6 +34,8 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.Printable; +import com.google.auto.service.AutoService; + /** * SaveGraphics action is meant to be a generic reusable Action. The class will * use GUIPackage to get the current gui. Once it does, it checks to see if the @@ -42,6 +44,7 @@ * file if no extension is provided. If either .png or .tif is in the filename, * it will call SaveGraphicsService to save in the format. */ +@AutoService(Command.class) public class SaveGraphics extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/SchematicView.java b/src/core/src/main/java/org/apache/jmeter/gui/action/SchematicView.java index 33ec59f9442..8b0f1c9b645 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/SchematicView.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/SchematicView.java @@ -47,10 +47,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Schematic view of Test Plan * @since 5.1 */ +@AutoService({ + Command.class, + MenuCreator.class +}) public class SchematicView extends AbstractAction implements MenuCreator { private static final Logger log = LoggerFactory.getLogger(SchematicView.class); private static final String DEFAULT_XSL_FILE = diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/SearchTreeCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/SearchTreeCommand.java index 91f0c664527..0661c17a63f 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/SearchTreeCommand.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/SearchTreeCommand.java @@ -23,10 +23,13 @@ import javax.swing.JFrame; +import com.google.auto.service.AutoService; + /** * Search nodes for a text * TODO Enhance search dialog to select kind of nodes .... */ +@AutoService(Command.class) public class SearchTreeCommand extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/Start.java b/src/core/src/main/java/org/apache/jmeter/gui/action/Start.java index fee1a4baee5..b31d1f3d734 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/Start.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/Start.java @@ -45,6 +45,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Set of Actions to: *
    @@ -57,6 +59,7 @@ *
  • Validate a set of Thread Groups with/without sleeping on the timers depending on jmeter properties
  • *
*/ +@AutoService(Command.class) public class Start extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(Start.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/StopStoppables.java b/src/core/src/main/java/org/apache/jmeter/gui/action/StopStoppables.java index c92b6a09169..f4f5ff60a6e 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/StopStoppables.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/StopStoppables.java @@ -26,10 +26,13 @@ import org.apache.jmeter.gui.GuiPackage; import org.apache.jmeter.gui.Stoppable; +import com.google.auto.service.AutoService; + /** * Stops stopables (Proxy, Mirror) * @since 2.5.1 */ +@AutoService(Command.class) public class StopStoppables extends AbstractAction implements ActionListener { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/TemplatesCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/TemplatesCommand.java index 4896ae4e2a6..58b6c58b264 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/TemplatesCommand.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/TemplatesCommand.java @@ -21,10 +21,13 @@ import java.util.HashSet; import java.util.Set; +import com.google.auto.service.AutoService; + /** * Open Templates * @since 2.10 */ +@AutoService(Command.class) public class TemplatesCommand extends AbstractActionWithNoRunningTest { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/UndoCommand.java b/src/core/src/main/java/org/apache/jmeter/gui/action/UndoCommand.java index a4a56f66375..fdae2d28cec 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/UndoCommand.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/UndoCommand.java @@ -26,10 +26,13 @@ import org.apache.jmeter.gui.GuiPackage; import org.apache.jorphan.collections.HashTree; +import com.google.auto.service.AutoService; + /** * Menu command to serve Undo/Redo * @since 2.12 */ +@AutoService(Command.class) public class UndoCommand extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/What.java b/src/core/src/main/java/org/apache/jmeter/gui/action/What.java index 1d202e30c29..08384fc606a 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/What.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/What.java @@ -35,6 +35,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * * Debug class to show details of the currently selected object @@ -43,6 +45,7 @@ * Also enables/disables debug for the test element. * */ +@AutoService(Command.class) public class What extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(What.class); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/ZoomInOut.java b/src/core/src/main/java/org/apache/jmeter/gui/action/ZoomInOut.java index a6b496fe723..7209bfe7402 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/ZoomInOut.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/ZoomInOut.java @@ -24,10 +24,13 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.gui.JMeterUIDefaults; +import com.google.auto.service.AutoService; + /** * Zoom IN/OUT * @since 3.2 */ +@AutoService(Command.class) public class ZoomInOut extends AbstractAction { private static final Set commands = new HashSet<>(); diff --git a/src/core/src/main/java/org/apache/jmeter/gui/plugin/MenuCreator.java b/src/core/src/main/java/org/apache/jmeter/gui/plugin/MenuCreator.java index c4b902df6c9..3d03fed1e17 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/plugin/MenuCreator.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/plugin/MenuCreator.java @@ -21,9 +21,12 @@ import javax.swing.JMenuItem; import javax.swing.MenuElement; +import org.apache.jorphan.reflect.JMeterService; + /** * @since 2.10 */ +@JMeterService public interface MenuCreator { enum MENU_LOCATION { FILE, diff --git a/src/core/src/main/java/org/apache/jmeter/gui/util/JMeterMenuBar.java b/src/core/src/main/java/org/apache/jmeter/gui/util/JMeterMenuBar.java index 5072aa31ada..cef9cd26901 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/util/JMeterMenuBar.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/util/JMeterMenuBar.java @@ -19,14 +19,13 @@ import java.awt.Component; import java.awt.event.KeyEvent; -import java.io.IOException; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.ServiceLoader; import javax.swing.ButtonGroup; import javax.swing.JCheckBoxMenuItem; @@ -54,7 +53,7 @@ import org.apache.jmeter.util.LocaleChangeEvent; import org.apache.jmeter.util.LocaleChangeListener; import org.apache.jmeter.util.SSLManager; -import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler; import org.apache.jorphan.util.JOrphanUtils; import org.apache.logging.log4j.Level; import org.slf4j.Logger; @@ -92,7 +91,13 @@ public class JMeterMenuBar extends JMenuBar implements LocaleChangeListener { private JMenu remoteExit; private final Collection remoteEngineExit; private JMenu searchMenu; - private List menuCreators; + private final Collection menuCreators = + JMeterUtils.loadServicesAndScanJars( + MenuCreator.class, + ServiceLoader.load(MenuCreator.class), + Thread.currentThread().getContextClassLoader(), + new LogAndIgnoreServiceLoadExceptionHandler(log) + ); public static final String SYSTEM_LAF = "System"; // $NON-NLS-1$ public static final String CROSS_PLATFORM_LAF = "CrossPlatform"; // $NON-NLS-1$ @@ -192,9 +197,6 @@ public void setEditAddEnabled(boolean enabled) { * should be defined in a file somewhere, but that is for later. */ public void createMenuBar() { - - this.menuCreators = findMenuCreators(); - makeFileMenu(); makeEditMenu(); makeRunMenu(); @@ -217,35 +219,6 @@ public void createMenuBar() { this.add(helpMenu); } - private static List findMenuCreators() { - List creators = new ArrayList<>(); - try { - List listClasses = ClassFinder.findClassesThatExtend( - JMeterUtils.getSearchPaths(), - new Class[] {MenuCreator.class }); - for (String strClassName : listClasses) { - try { - log.debug("Loading menu creator class: {}", strClassName); - Class commandClass = Class.forName(strClassName); - if (!Modifier.isAbstract(commandClass.getModifiers())) { - log.debug("Instantiating: {}", commandClass); - MenuCreator creator = (MenuCreator) commandClass.getDeclaredConstructor().newInstance(); - creators.add(creator); - } - } catch (NoClassDefFoundError e) { - log.error("Exception registering implementation: [{}] of interface: [{}], a dependency used by the plugin class is missing", - strClassName, MenuCreator.class, e); - } catch (Exception e) { - log.error("Exception registering implementation: [{}] of interface: [{}], a jar is probably missing", - strClassName, MenuCreator.class, e); - } - } - } catch (IOException e) { - log.error("Exception finding implementations of {}", MenuCreator.class, e); - } - return creators; - } - private void makeHelpMenu() { helpMenu = makeMenuRes("help",'H'); //$NON-NLS-1$ @@ -590,7 +563,7 @@ private void makeSearchMenu() { * @param menuCreators * @param location */ - private static void addPluginsMenuItems(JMenu menu, List menuCreators, MENU_LOCATION location) { + private static void addPluginsMenuItems(JMenu menu, Collection menuCreators, MENU_LOCATION location) { for (MenuCreator menuCreator : menuCreators) { JMenuItem[] menuItems = menuCreator.getMenuItemsAtLocation(location); if (menuItems.length != 0) { diff --git a/src/core/src/main/java/org/apache/jmeter/gui/util/MenuFactory.java b/src/core/src/main/java/org/apache/jmeter/gui/util/MenuFactory.java index 4aafa803415..bc84feb9167 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/util/MenuFactory.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/util/MenuFactory.java @@ -124,6 +124,8 @@ private static Set classesToSkip() { private static void initializeMenus( Map> menus, Set elementsToSkip) { try { + // TODO: migrate to ServiceLoader or something else + @SuppressWarnings("deprecation") List guiClasses = ClassFinder .findClassesThatExtend( JMeterUtils.getSearchPaths(), diff --git a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java b/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java index f2c8d99012d..83dd5b71ba5 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsLifeCycleListener.java @@ -17,10 +17,13 @@ package org.apache.jmeter.threads; +import org.apache.jorphan.reflect.JMeterService; + /** * Interface notified when number of active threads changes * @since 2.10 */ +@JMeterService public interface RemoteThreadsLifeCycleListener { /** diff --git a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java b/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java index adb0f593a49..058702827ae 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java @@ -17,18 +17,16 @@ package org.apache.jmeter.threads; -import java.io.IOException; -import java.lang.reflect.Modifier; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; -import java.util.ArrayList; -import java.util.List; +import java.util.Collection; +import java.util.ServiceLoader; import org.apache.jmeter.gui.GuiPackage; import org.apache.jmeter.rmi.RmiUtils; import org.apache.jmeter.testelement.ThreadListener; import org.apache.jmeter.util.JMeterUtils; -import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +37,14 @@ public class RemoteThreadsListenerImpl extends UnicastRemoteObject implements RemoteThreadsListener, ThreadListener { private static final Logger log = LoggerFactory.getLogger(RemoteThreadsListenerImpl.class); - private final List listeners = new ArrayList<>(); + + private final Collection listeners = + JMeterUtils.loadServicesAndScanJars( + RemoteThreadsLifeCycleListener.class, + ServiceLoader.load(RemoteThreadsLifeCycleListener.class), + Thread.currentThread().getContextClassLoader(), + new LogAndIgnoreServiceLoadExceptionHandler(log) + ); /** * @@ -56,27 +61,6 @@ public class RemoteThreadsListenerImpl extends UnicastRemoteObject implements */ public RemoteThreadsListenerImpl() throws RemoteException { super(DEFAULT_LOCAL_PORT, RmiUtils.createClientSocketFactory(), RmiUtils.createServerSocketFactory()); - try { - List listClasses = ClassFinder.findClassesThatExtend( - JMeterUtils.getSearchPaths(), - new Class[] {RemoteThreadsLifeCycleListener.class }); - for (String strClassName : listClasses) { - try { - log.debug("Loading class: {}", strClassName); - Class commandClass = Class.forName(strClassName); - if (!Modifier.isAbstract(commandClass.getModifiers())) { - log.debug("Instantiating: {}", commandClass); - RemoteThreadsLifeCycleListener listener = (RemoteThreadsLifeCycleListener) commandClass.getDeclaredConstructor().newInstance(); - listeners.add(listener); - } - } catch (Exception e) { - log.error("Exception registering {} with implementation: {}", RemoteThreadsLifeCycleListener.class, - strClassName, e); - } - } - } catch (IOException e) { - log.error("Exception finding implementations of {}", RemoteThreadsLifeCycleListener.class, e); - } } private static int addOffset(int port, int offset) { diff --git a/src/core/src/main/java/org/apache/jmeter/util/JMeterUtils.java b/src/core/src/main/java/org/apache/jmeter/util/JMeterUtils.java index 3a8408bb2a7..de46f1b6841 100644 --- a/src/core/src/main/java/org/apache/jmeter/util/JMeterUtils.java +++ b/src/core/src/main/java/org/apache/jmeter/util/JMeterUtils.java @@ -27,19 +27,25 @@ import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Enumeration; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.MissingResourceException; import java.util.Properties; import java.util.ResourceBundle; +import java.util.ServiceLoader; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; @@ -59,6 +65,7 @@ import org.apache.jorphan.gui.JFactory; import org.apache.jorphan.gui.JMeterUIDefaults; import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.reflect.ServiceLoadExceptionHandler; import org.apache.jorphan.test.UnitTestManager; import org.apache.jorphan.util.JMeterError; import org.apache.jorphan.util.JOrphanUtils; @@ -316,6 +323,71 @@ public void initializeProperties(String file) { getProperties(file); } + /** + * Loads services implementing a given interface and scans JMeter search path for the implementations. + * This is a transition replacement for {@link ClassFinder}, and JMeter would migrate to {@link ServiceLoader}-only + * lookup in the future. + *

Note: it is not always safe to cache the result as {@code search_paths} property might change over time

+ * + * @param service interface that services should extend. + * @param serviceLoader ServiceLoader to fetch services. + * @param classLoader classLoader to use when searching for classes on the search path. + * @param exceptionHandler exception handler to use for services that fail to load. + * @return collection of services that load successfully + * @param type of service (class or interface) + */ + @API(status = API.Status.DEPRECATED, since = "5.6") + public static Collection loadServicesAndScanJars( + @SuppressWarnings("BoundedWildcard") Class service, + ServiceLoader serviceLoader, + ClassLoader classLoader, + ServiceLoadExceptionHandler exceptionHandler + ) { + Collection services = ClassFinder.loadServices(service, serviceLoader, exceptionHandler); + + List classesFromJars; + try (ClassFinder.Closeable ignored = ClassFinder.skipJarsWithJmeterSkipClassScanningAttribute()) { + classesFromJars = findClassesThatExtend(service); + } catch (IOException e) { + log.warn("Unable to lookup {} with ClassFinder.findClassesThatExtend. " + + "Will use only results from ServiceLoader ({} items found)", service, services.size(), e); + return services; + } + + if (classesFromJars.isEmpty()) { + return services; + } + + Set loadedClasses = new HashSet<>((int) (services.size() / 0.75f) + 1); + for (S s : services) { + loadedClasses.add(s.getClass().getName()); + } + + List result = new ArrayList<>(services.size() + classesFromJars.size()); + result.addAll(services); + for (String className : classesFromJars) { + // Ignore classes that we loaded previously (e.g. with a ServiceLoader) + if (!loadedClasses.add(className)) { + continue; + } + try { + Class klass = Class.forName(className, false, classLoader) + .asSubclass(service); + if (!Modifier.isAbstract(klass.getModifiers())) { + continue; + } + result.add(klass.getDeclaredConstructor().newInstance()); + } catch (Throwable e) { + if (e instanceof InvocationTargetException) { + //noinspection AssignmentToCatchBlockParameter + e = e.getCause(); + } + exceptionHandler.handle(service, className, e); + } + } + return result; + } + /** * Convenience method for * {@link ClassFinder#findClassesThatExtend(String[], Class[], boolean)} @@ -325,7 +397,10 @@ public void initializeProperties(String file) { * @param superClass - single class to search for * @return List of Strings containing discovered class names. * @throws IOException when the used {@link ClassFinder} throws one while searching for the class + * @deprecated use {@link #loadServicesAndScanJars(Class, ServiceLoader, ClassLoader, ServiceLoadExceptionHandler)} instead */ + @API(status = API.Status.DEPRECATED, since = "5.6") + @Deprecated public static List findClassesThatExtend(Class superClass) throws IOException { return ClassFinder.findClassesThatExtend(getSearchPaths(), new Class[]{superClass}, false); diff --git a/src/core/src/test/java/org/apache/jorphan/test/AllTests.java b/src/core/src/test/java/org/apache/jorphan/test/AllTests.java index bbbeefa46f4..e64fb568de8 100644 --- a/src/core/src/test/java/org/apache/jorphan/test/AllTests.java +++ b/src/core/src/test/java/org/apache/jorphan/test/AllTests.java @@ -321,8 +321,10 @@ private static void initializeManager(String[] args) { } } + @SuppressWarnings("deprecation") private static List findJMeterJUnitTests(String searchPathString) throws IOException { final String[] searchPaths = JOrphanUtils.split(searchPathString, ","); + // TODO: do we really need class searching here? return ClassFinder.findClasses(searchPaths, new JunitTestFilter()); } 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 7054ff33b5c..19152c90ab7 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 @@ -440,6 +440,7 @@ private void checkElementAlias(Object item) { public static Collection getObjects(Class extendsClass) throws Throwable { String exName = extendsClass.getName(); + @SuppressWarnings("deprecation") Iterator classes = ClassFinder .findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { extendsClass }).iterator(); List objects = new ArrayList<>(); diff --git a/src/dist-check/src/test/java/org/apache/jmeter/testbeans/gui/PackageTest.java b/src/dist-check/src/test/java/org/apache/jmeter/testbeans/gui/PackageTest.java index 03e9704f31e..f076fb32837 100644 --- a/src/dist-check/src/test/java/org/apache/jmeter/testbeans/gui/PackageTest.java +++ b/src/dist-check/src/test/java/org/apache/jmeter/testbeans/gui/PackageTest.java @@ -171,6 +171,7 @@ public void checkAllNecessaryKeysPresent() { public static Test suite() throws Exception { TestSuite suite = new TestSuite("Bean Resource Test Suite"); + @SuppressWarnings("deprecation") List testBeanClassNames = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { TestBean.class }); boolean errorDetected = false; diff --git a/src/dist-check/src/test/java/org/apache/jorphan/reflect/TestClassFinder.java b/src/dist-check/src/test/java/org/apache/jorphan/reflect/TestClassFinder.java index a8a355670fd..a035d321422 100644 --- a/src/dist-check/src/test/java/org/apache/jorphan/reflect/TestClassFinder.java +++ b/src/dist-check/src/test/java/org/apache/jorphan/reflect/TestClassFinder.java @@ -48,6 +48,7 @@ public void setUp() throws Exception { @Test public void testFindClassesThatExtendStringArrayClassOfQArray() throws IOException { + @SuppressWarnings("deprecation") List findClassesThatExtend = ClassFinder.findClassesThatExtend( libDirs, new Class[] { Exception.class }); @@ -56,6 +57,7 @@ public void testFindClassesThatExtendStringArrayClassOfQArray() throws IOExcepti @Test public void testFindClassesThatExtendStringArrayClassOfQArrayTrue() throws Exception { + @SuppressWarnings("deprecation") List findClassesThatExtend = ClassFinder.findClassesThatExtend( libDirs, new Class[] { Object.class }, @@ -66,6 +68,7 @@ public void testFindClassesThatExtendStringArrayClassOfQArrayTrue() throws Excep @Test public void testFindClassesThatExtendStringArrayClassOfQArrayFalse() throws Exception { + @SuppressWarnings("deprecation") List findClassesThatExtend = ClassFinder.findClassesThatExtend( libDirs, new Class[] { Exception.class }, @@ -77,6 +80,7 @@ public void testFindClassesThatExtendStringArrayClassOfQArrayFalse() throws Exce @Test public void testFindClassesThatExtendStringArrayClassOfQArrayBooleanStringString() throws Exception { + @SuppressWarnings("deprecation") List findClassesThatExtend = ClassFinder.findClassesThatExtend( libDirs, new Class[] { Exception.class }, @@ -90,6 +94,7 @@ public void testFindClassesThatExtendStringArrayClassOfQArrayBooleanStringString @Test public void testFindClassesThatExtendStringArrayClassOfQArrayBooleanStringStringTrue() throws Exception { + @SuppressWarnings("deprecation") List annotatedClasses = ClassFinder.findClassesThatExtend( libDirs, new Class[] { java.beans.Transient.class }, @@ -102,7 +107,7 @@ public void testFindClassesThatExtendStringArrayClassOfQArrayBooleanStringString @Test public void testFindAnnotatedClasses() throws Exception { - @SuppressWarnings("unchecked") + @SuppressWarnings({"deprecation", "unchecked"}) List annotatedClasses = ClassFinder.findAnnotatedClasses( libDirs, new Class[] { java.beans.Transient.class}); @@ -111,7 +116,7 @@ public void testFindAnnotatedClasses() throws Exception { @Test public void testFindAnnotatedInnerClasses() throws Exception { - @SuppressWarnings("unchecked") + @SuppressWarnings({"deprecation", "unchecked"}) List annotatedClasses = ClassFinder.findAnnotatedClasses(libDirs, new Class[] { java.lang.Deprecated.class}, true); Assert.assertTrue(annotatedClasses.stream().anyMatch(s->s.contains("$"))); @@ -119,12 +124,16 @@ public void testFindAnnotatedInnerClasses() throws Exception { @Test public void testFindClasses() throws IOException { - Assert.assertFalse(ClassFinder.findClasses(libDirs, className -> true).isEmpty()); + @SuppressWarnings("deprecation") + List classes = ClassFinder.findClasses(libDirs, className -> true); + Assert.assertFalse(classes.isEmpty()); } @Test public void testFindClassesNone() throws IOException { - Assert.assertTrue(ClassFinder.findClasses(libDirs, className -> false).isEmpty()); + @SuppressWarnings("deprecation") + List classes = ClassFinder.findClasses(libDirs, className -> false); + Assert.assertTrue(classes.isEmpty()); } } diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/BeanShell.java b/src/functions/src/main/java/org/apache/jmeter/functions/BeanShell.java index 88d64cd8e6b..f72284c877b 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/BeanShell.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/BeanShell.java @@ -32,10 +32,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * A function which understands BeanShell * @since 1.X */ +@AutoService(Function.class) public class BeanShell extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(BeanShell.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/CSVRead.java b/src/functions/src/main/java/org/apache/jmeter/functions/CSVRead.java index c9a80a34a5b..612a83ec1cb 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/CSVRead.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/CSVRead.java @@ -28,6 +28,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * The function represented by this class allows data to be read from CSV files. * Syntax is similar to StringFromFile function. The function allows the test to @@ -53,6 +55,7 @@ * {@code __CSVRead(*ONE,1);}, etc. * @since 1.9 */ +@AutoService(Function.class) public class CSVRead extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(CSVRead.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/ChangeCase.java b/src/functions/src/main/java/org/apache/jmeter/functions/ChangeCase.java index deb5c88a05f..38e15271a2d 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/ChangeCase.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/ChangeCase.java @@ -31,6 +31,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Change Case Function * @@ -45,6 +47,7 @@ * @since 4.0 * */ +@AutoService(Function.class) public class ChangeCase extends AbstractFunction { private static final Logger LOGGER = LoggerFactory.getLogger(ChangeCase.class); private static final List DESC = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/CharFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/CharFunction.java index 94e5df40c12..482c7ed6e6d 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/CharFunction.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/CharFunction.java @@ -28,10 +28,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Function to generate chars from a list of decimal or hex values * @since 2.3.3 */ +@AutoService(Function.class) public class CharFunction extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(CharFunction.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/DateTimeConvertFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/DateTimeConvertFunction.java index c382ba17476..47e9a0d612e 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/DateTimeConvertFunction.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/DateTimeConvertFunction.java @@ -31,6 +31,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * DateConvert function to change date format * Can optionally store it in a variable. @@ -38,6 +40,7 @@ * @since 4.0 * */ +@AutoService(Function.class) public class DateTimeConvertFunction extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(DateTimeConvertFunction.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/DigestEncodeFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/DigestEncodeFunction.java index 3bc2d990f57..36c9f479e84 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/DigestEncodeFunction.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/DigestEncodeFunction.java @@ -34,6 +34,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Digest Encode Function that provides computing of different SHA-XXX, can * uppercase the result and store it in a variable. @@ -44,6 +46,7 @@ * * @since 4.0 */ +@AutoService(Function.class) public class DigestEncodeFunction extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(DigestEncodeFunction.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeHtml.java b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeHtml.java index 4b3d4186c81..8606779ad07 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeHtml.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeHtml.java @@ -27,6 +27,8 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** *

Function which escapes the characters in a String using HTML entities.

* @@ -46,6 +48,7 @@ * @see StringEscapeUtils#escapeHtml4(String) (Commons Lang) * @since 2.3.3 */ +@AutoService(Function.class) public class EscapeHtml extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeOroRegexpChars.java b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeOroRegexpChars.java index 91c8d99641b..a3fbf62aa6c 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeOroRegexpChars.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeOroRegexpChars.java @@ -30,10 +30,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Escape ORO meta characters * @since 2.9 */ +@AutoService(Function.class) public class EscapeOroRegexpChars extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(EscapeOroRegexpChars.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeXml.java b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeXml.java index 4dce679a768..ac83b05b777 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/EscapeXml.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/EscapeXml.java @@ -27,6 +27,8 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** *

Function which escapes the characters in a String using XML 1.0 entities.

* @@ -43,6 +45,7 @@ * @see StringEscapeUtils#escapeXml10(String) (Commons Lang) * @since 3.2 */ +@AutoService(Function.class) public class EscapeXml extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/EvalFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/EvalFunction.java index 33c90be05f4..e5126c77311 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/EvalFunction.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/EvalFunction.java @@ -26,6 +26,8 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Function to evaluate a string which may contain variable or function references. * @@ -34,6 +36,7 @@ * Returns: the evaluated value * @since 2.3.1 */ +@AutoService(Function.class) public class EvalFunction extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/EvalVarFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/EvalVarFunction.java index 4d3521e81e3..71fc5bf4099 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/EvalVarFunction.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/EvalVarFunction.java @@ -29,6 +29,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Function to evaluate a string which may contain variable or function references. * @@ -37,6 +39,7 @@ * Returns: the evaluated value * @since 2.3.1 */ +@AutoService(Function.class) public class EvalVarFunction extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(EvalVarFunction.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/FileToString.java b/src/functions/src/main/java/org/apache/jmeter/functions/FileToString.java index 519fb699193..5f71500b708 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/FileToString.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/FileToString.java @@ -32,6 +32,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * FileToString Function to read a complete file into a String. *

@@ -50,6 +52,7 @@ * * @since 2.4 */ +@AutoService(Function.class) public class FileToString extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(FileToString.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/FileWrapper.java b/src/functions/src/main/java/org/apache/jmeter/functions/FileWrapper.java index a26b482643f..30710df41f6 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/FileWrapper.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/FileWrapper.java @@ -26,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + /** * This class wraps the FileRowColContainer for use across multiple threads. *

@@ -34,6 +35,7 @@ * together with the current line number. * */ +//@AutoService(Function.class) public final class FileWrapper { private static final Logger log = LoggerFactory.getLogger(FileWrapper.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java b/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java index 5e23c7b5a36..23612d0eb23 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java @@ -41,11 +41,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * __groovy function * Provides a Groovy interpreter * @since 3.1 */ +@AutoService(Function.class) public class Groovy extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(Groovy.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/IntSum.java b/src/functions/src/main/java/org/apache/jmeter/functions/IntSum.java index 92019be77c7..fb2db081ded 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/IntSum.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/IntSum.java @@ -27,12 +27,15 @@ import org.apache.jmeter.threads.JMeterVariables; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Provides an intSum function that adds two or more integer values. * * @see LongSum * @since 1.8.1 */ +@AutoService(Function.class) public class IntSum extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/IsPropDefined.java b/src/functions/src/main/java/org/apache/jmeter/functions/IsPropDefined.java index eb86e50151f..b50922d6a2b 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/IsPropDefined.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/IsPropDefined.java @@ -26,11 +26,14 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Test if a JMeter property is defined * * @since 4.0 */ +@AutoService(Function.class) public class IsPropDefined extends AbstractFunction { private static final List desc = new ArrayList<>(); private static final String KEY = "__isPropDefined"; diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/IsVarDefined.java b/src/functions/src/main/java/org/apache/jmeter/functions/IsVarDefined.java index edb8515e6a9..99a2e9e1b7c 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/IsVarDefined.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/IsVarDefined.java @@ -27,11 +27,14 @@ import org.apache.jmeter.threads.JMeterVariables; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Test if a JMeter variable is defined * * @since 4.0 */ +@AutoService(Function.class) public class IsVarDefined extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/IterationCounter.java b/src/functions/src/main/java/org/apache/jmeter/functions/IterationCounter.java index a5c730b905f..4742a9c560b 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/IterationCounter.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/IterationCounter.java @@ -29,11 +29,14 @@ import org.apache.jmeter.threads.JMeterVariables; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Counter that can be referenced anywhere in the Thread Group. It can be configured per User (Thread Local) * or globally. * @since 1.X */ +@AutoService(Function.class) public class IterationCounter extends AbstractFunction implements ThreadListener { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/JavaScript.java b/src/functions/src/main/java/org/apache/jmeter/functions/JavaScript.java index 00ea98bc6f7..bc4729b6a2f 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/JavaScript.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/JavaScript.java @@ -40,10 +40,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * javaScript function implementation that executes a piece of JavaScript (not Java!) code and returns its value * @since 1.9 */ +@AutoService(Function.class) public class JavaScript extends AbstractFunction { private static final String NASHORN_ENGINE_NAME = "nashorn"; //$NON-NLS-1$ diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Jexl2Function.java b/src/functions/src/main/java/org/apache/jmeter/functions/Jexl2Function.java index 7b6c185b27e..1d90bca74ca 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/Jexl2Function.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/Jexl2Function.java @@ -36,11 +36,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * A function which understands Commons JEXL2 * @since 2.6 */ // For unit tests, see TestJexlFunction +@AutoService(Function.class) public class Jexl2Function extends AbstractFunction implements ThreadListener { private static final Logger log = LoggerFactory.getLogger(Jexl2Function.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Jexl3Function.java b/src/functions/src/main/java/org/apache/jmeter/functions/Jexl3Function.java index ac74281d479..6c37bc92b09 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/Jexl3Function.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/Jexl3Function.java @@ -37,11 +37,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * A function which understands Commons JEXL3 * @since 3.0 */ // For unit tests, see TestJexlFunction +@AutoService(Function.class) public class Jexl3Function extends AbstractFunction implements ThreadListener { private static final Logger log = LoggerFactory.getLogger(Jexl3Function.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction.java index de5b0f9a572..a40d6d668dd 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction.java @@ -30,6 +30,8 @@ import org.slf4j.LoggerFactory; import org.slf4j.event.Level; +import com.google.auto.service.AutoService; + /** *

* Function to log a message. @@ -46,6 +48,7 @@ * Returns: - the input string * @since 2.2 */ +@AutoService(Function.class) public class LogFunction extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(LogFunction.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction2.java b/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction2.java index 9f45af1ae44..23901d84508 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction2.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/LogFunction2.java @@ -28,6 +28,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** *

* Function to log a message. @@ -43,6 +45,7 @@ * Returns: - Empty String (so can be used where return value would be a nuisance) * @since 2.2 */ +@AutoService(Function.class) public class LogFunction2 extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(LogFunction2.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/LongSum.java b/src/functions/src/main/java/org/apache/jmeter/functions/LongSum.java index 37a8e8de517..b29a3551d6d 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/LongSum.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/LongSum.java @@ -27,11 +27,14 @@ import org.apache.jmeter.threads.JMeterVariables; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Provides a longSum function that adds two or more long values. * @see IntSum * @since 2.3.2 */ +@AutoService(Function.class) public class LongSum extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java b/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java index 82acc7183b6..e80a944d748 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/MachineIP.java @@ -19,10 +19,13 @@ import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Return Machine IP * @since 2.6 */ +@AutoService(Function.class) public class MachineIP extends AbstractHostIPName { private static final String KEY = "__machineIP"; //$NON-NLS-1$ diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/MachineName.java b/src/functions/src/main/java/org/apache/jmeter/functions/MachineName.java index 50898adff74..8218b8e41fc 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/MachineName.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/MachineName.java @@ -19,10 +19,13 @@ import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Return Machine Host * @since 1.X */ +@AutoService(Function.class) public class MachineName extends AbstractHostIPName { private static final String KEY = "__machineName"; //$NON-NLS-1$ diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Property.java b/src/functions/src/main/java/org/apache/jmeter/functions/Property.java index b2168a307fa..db4be23411c 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/Property.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/Property.java @@ -27,6 +27,8 @@ import org.apache.jmeter.threads.JMeterVariables; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Function to get a JMeter property, and optionally store it * @@ -41,6 +43,7 @@ * - the property name itself * @since 2.0 */ +@AutoService(Function.class) public class Property extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Property2.java b/src/functions/src/main/java/org/apache/jmeter/functions/Property2.java index 701dd0fd46a..0694219ba67 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/Property2.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/Property2.java @@ -26,6 +26,8 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Function to get a JMeter property, or a default. Does not offer the option to * store the value, as it is just as easy to refetch it. This is a @@ -46,6 +48,7 @@ * not present - "1" (suitable for use in ThreadGroup GUI) * @since 2.0 */ +@AutoService(Function.class) public class Property2 extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Random.java b/src/functions/src/main/java/org/apache/jmeter/functions/Random.java index 20921a187d1..bfffeb7c7aa 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/Random.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/Random.java @@ -28,11 +28,14 @@ import org.apache.jmeter.threads.JMeterVariables; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Provides a Random function which returns a random long integer between a min * (first argument) and a max (second argument). * @since 1.9 */ +@AutoService(Function.class) public class Random extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/RandomDate.java b/src/functions/src/main/java/org/apache/jmeter/functions/RandomDate.java index 83243d76f4e..d28e815546f 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/RandomDate.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/RandomDate.java @@ -41,6 +41,7 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.auto.service.AutoService; /** * RandomDate Function generates a date in a specific range @@ -59,6 +60,7 @@ * * @since 3.3 */ +@AutoService(Function.class) public class RandomDate extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(RandomDate.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/RandomFromMultipleVars.java b/src/functions/src/main/java/org/apache/jmeter/functions/RandomFromMultipleVars.java index 7a22beca1e8..897fd48f11c 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/RandomFromMultipleVars.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/RandomFromMultipleVars.java @@ -31,6 +31,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Provides a RandomFromMultipleVars function which returns a random element from a multi valued extracted variable. * Those kind of variables are extracted by: @@ -41,6 +43,7 @@ * * @since 3.1 */ +@AutoService(Function.class) public class RandomFromMultipleVars extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(RandomFromMultipleVars.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/RandomString.java b/src/functions/src/main/java/org/apache/jmeter/functions/RandomString.java index 1dd00483928..e0528d1c770 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/RandomString.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/RandomString.java @@ -31,11 +31,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Provides a RandomString function which returns a random String of length (first argument) * using characters (second argument) * @since 2.6 */ +@AutoService(Function.class) public class RandomString extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(RandomString.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/RegexFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/RegexFunction.java index a3c50e2b4e6..8ea9e820194 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/RegexFunction.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/RegexFunction.java @@ -39,6 +39,8 @@ import org.apache.oro.text.regex.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import com.google.auto.service.AutoService; /** * Implements regular expression parsing of sample results and variables * @since 1.X @@ -46,6 +48,7 @@ // @see TestRegexFunction for unit tests +@AutoService(Function.class) public class RegexFunction extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(RegexFunction.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/SamplerName.java b/src/functions/src/main/java/org/apache/jmeter/functions/SamplerName.java index eedb707b96f..fa624db65f9 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/SamplerName.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/SamplerName.java @@ -27,10 +27,13 @@ import org.apache.jmeter.threads.JMeterVariables; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Function to return the name of the current sampler. * @since 2.5 */ +@AutoService(Function.class) public class SamplerName extends AbstractFunction { private static final String KEY = "__samplerName"; //$NON-NLS-1$ diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/SetProperty.java b/src/functions/src/main/java/org/apache/jmeter/functions/SetProperty.java index 7811c5686c1..5e8bce06cfa 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/SetProperty.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/SetProperty.java @@ -27,6 +27,8 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Function to set a JMeter property * @@ -40,6 +42,7 @@ * Returns: nothing or original value if the 3rd parameter is true * @since 2.1 */ +@AutoService(Function.class) public class SetProperty extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/SplitFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/SplitFunction.java index 17e8bb10120..825f0d8a629 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/SplitFunction.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/SplitFunction.java @@ -31,6 +31,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + // @see org.apache.jmeter.functions.PackageTest for unit tests /** @@ -53,6 +55,7 @@ * * @since 2.0.2 */ +@AutoService(Function.class) public class SplitFunction extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(SplitFunction.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/StringFromFile.java b/src/functions/src/main/java/org/apache/jmeter/functions/StringFromFile.java index fc2207feeeb..5e7081e1b01 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/StringFromFile.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/StringFromFile.java @@ -37,6 +37,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** *

StringFromFile Function to read a String from a text file.

* @@ -70,6 +72,7 @@ * Because function instances are shared, it does not make sense to use the thread number as part of the file name. * @since 1.9 */ +@AutoService(Function.class) public class StringFromFile extends AbstractFunction implements TestStateListener { private static final Logger log = LoggerFactory.getLogger(StringFromFile.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/StringToFile.java b/src/functions/src/main/java/org/apache/jmeter/functions/StringToFile.java index 25d644e8eea..41a363976be 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/StringToFile.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/StringToFile.java @@ -41,6 +41,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * StringToFile Function to write a String to a file * @@ -55,6 +57,7 @@ * * @since 5.2 */ +@AutoService(Function.class) public class StringToFile extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(StringToFile.class); private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/TestPlanName.java b/src/functions/src/main/java/org/apache/jmeter/functions/TestPlanName.java index c0ab9e8a943..ba298545ac2 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/TestPlanName.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/TestPlanName.java @@ -26,10 +26,13 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.services.FileServer; +import com.google.auto.service.AutoService; + /** * Returns Test Plan name * @since 2.6 */ +@AutoService(Function.class) public class TestPlanName extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/ThreadGroupName.java b/src/functions/src/main/java/org/apache/jmeter/functions/ThreadGroupName.java index 08a4386708f..36c6b38f46a 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/ThreadGroupName.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/ThreadGroupName.java @@ -26,11 +26,14 @@ import org.apache.jmeter.threads.JMeterContext; import org.apache.jmeter.threads.JMeterContextService; +import com.google.auto.service.AutoService; + /** * Returns Thread Group Name * * @since 5.0 */ +@AutoService(Function.class) public class ThreadGroupName extends AbstractFunctionByKey { private static final String KEY = "__threadGroupName"; //$NON-NLS-1$ diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/ThreadNumber.java b/src/functions/src/main/java/org/apache/jmeter/functions/ThreadNumber.java index 0c353c32e6c..12d3221e0ba 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/ThreadNumber.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/ThreadNumber.java @@ -25,10 +25,13 @@ import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.samplers.Sampler; +import com.google.auto.service.AutoService; + /** * Function to return the current thread number. * @since 1.X */ +@AutoService(Function.class) public class ThreadNumber extends AbstractFunction { private static final String KEY = "__threadNum"; //$NON-NLS-1$ diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java index 65e6c01a9dd..1dd087b7ed5 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java @@ -35,12 +35,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + // See org.apache.jmeter.functions.TestTimeFunction for unit tests /** * __time() function - returns the current time in milliseconds * @since 2.2 */ +@AutoService(Function.class) public class TimeFunction extends AbstractFunction { private static final String KEY = "__time"; // $NON-NLS-1$ diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/TimeShift.java b/src/functions/src/main/java/org/apache/jmeter/functions/TimeShift.java index 131d360daea..2b011401b81 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/TimeShift.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/TimeShift.java @@ -43,6 +43,7 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.auto.service.AutoService; /** * timeShifting Function permit to shift a date @@ -65,6 +66,7 @@ * * @since 3.3 */ +@AutoService(Function.class) public class TimeShift extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(TimeShift.class); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/UnEscape.java b/src/functions/src/main/java/org/apache/jmeter/functions/UnEscape.java index 910c4bb48f6..933af1421e5 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/UnEscape.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/UnEscape.java @@ -28,6 +28,8 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Function to unescape any Java literals found in the String. * For example, it will turn a sequence of '\' and 'n' into a newline character, @@ -36,6 +38,7 @@ * @see StringEscapeUtils#unescapeJava(String) * @since 2.3.3 */ +@AutoService(Function.class) public class UnEscape extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/UnEscapeHtml.java b/src/functions/src/main/java/org/apache/jmeter/functions/UnEscapeHtml.java index 97a8bafa9c7..85f57bbe731 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/UnEscapeHtml.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/UnEscapeHtml.java @@ -28,6 +28,8 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Function to unescape a string containing entity escapes * to a string containing the actual Unicode characters corresponding to the escapes. @@ -42,6 +44,7 @@ * @see StringEscapeUtils#unescapeHtml4(String) * @since 2.3.3 */ +@AutoService(Function.class) public class UnEscapeHtml extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/UrlDecode.java b/src/functions/src/main/java/org/apache/jmeter/functions/UrlDecode.java index a31f1aa2a40..9890ec650cd 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/UrlDecode.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/UrlDecode.java @@ -30,11 +30,14 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Function to decode a application/x-www-form-urlencoded string. * * @since 2.10 */ +@AutoService(Function.class) public class UrlDecode extends AbstractFunction { private static final String CHARSET_ENCODING = StandardCharsets.UTF_8.name(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/UrlEncode.java b/src/functions/src/main/java/org/apache/jmeter/functions/UrlEncode.java index 410ac89a585..e890c4383cc 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/UrlEncode.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/UrlEncode.java @@ -30,11 +30,14 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Function to encode a string to a application/x-www-form-urlencoded string. * * @since 2.10 */ +@AutoService(Function.class) public class UrlEncode extends AbstractFunction { private static final String CHARSET_ENCODING = StandardCharsets.UTF_8.name(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Uuid.java b/src/functions/src/main/java/org/apache/jmeter/functions/Uuid.java index 16461968ccd..b4b2ee370fb 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/Uuid.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/Uuid.java @@ -26,6 +26,8 @@ import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.samplers.Sampler; +import com.google.auto.service.AutoService; + /** * Function to create a UUID * @@ -36,6 +38,7 @@ * - A pseudo random UUID 4 * @since 2.9 */ +@AutoService(Function.class) public class Uuid extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Variable.java b/src/functions/src/main/java/org/apache/jmeter/functions/Variable.java index 18ee52db415..ce83531819d 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/Variable.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/Variable.java @@ -27,6 +27,8 @@ import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.util.JMeterUtils; +import com.google.auto.service.AutoService; + /** * Function to get a JMeter Variable * @@ -39,6 +41,7 @@ * - the default value if set, and if not the variable name itself * @since 2.3RC3 */ +@AutoService(Function.class) public class Variable extends AbstractFunction { private static final List desc = new ArrayList<>(); diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/XPath.java b/src/functions/src/main/java/org/apache/jmeter/functions/XPath.java index 1200ccc55a1..a2fe3adf688 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/XPath.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/XPath.java @@ -29,6 +29,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + // @see org.apache.jmeter.functions.PackageTest for unit tests /** @@ -45,6 +47,7 @@ * is opened and used for all threads. * @since 2.0.3 */ +@AutoService(Function.class) public class XPath extends AbstractFunction { private static final Logger log = LoggerFactory.getLogger(XPath.class); diff --git a/src/functions/src/test/kotlin/org/apache/jmeter/functions/FunctionServicesTest.kt b/src/functions/src/test/kotlin/org/apache/jmeter/functions/FunctionServicesTest.kt new file mode 100644 index 00000000000..32ad1d29132 --- /dev/null +++ b/src/functions/src/test/kotlin/org/apache/jmeter/functions/FunctionServicesTest.kt @@ -0,0 +1,43 @@ +/* + * 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.functions + +import org.apache.jmeter.util.JMeterUtils +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.util.ServiceLoader + +class FunctionServicesTest { + @Test + fun `__counter loads`() { + val functions = JMeterUtils.loadServicesAndScanJars( + Function::class.java, + ServiceLoader.load(Function::class.java), + Thread.currentThread().contextClassLoader + ) { service, className, throwable -> + throw IllegalStateException( + "Failed to load $service implementations, implementation: $className", + throwable + ) + }.map { it.referenceKey } + + Assertions.assertTrue("__counter" in functions) { + "__counter function should be discoverable with ServiceLoader.load(Function), all found functions are $functions" + } + } +} diff --git a/src/jorphan/src/main/java/org/apache/jorphan/reflect/ClassFinder.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ClassFinder.java index 7d04e1374e0..22d08caed99 100644 --- a/src/jorphan/src/main/java/org/apache/jorphan/reflect/ClassFinder.java +++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ClassFinder.java @@ -26,18 +26,24 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Objects; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; import java.util.Set; import java.util.TreeSet; +import java.util.jar.JarFile; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import org.apiguardian.api.API; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This class finds classes that extend one of a set of parent classes + * This class finds classes that extend one of a set of parent classes. */ public final class ClassFinder { private static final Logger log = LoggerFactory.getLogger(ClassFinder.class); @@ -46,10 +52,99 @@ public final class ClassFinder { private static final String DOT_CLASS = ".class"; // $NON-NLS-1$ private static final int DOT_CLASS_LEN = DOT_CLASS.length(); + private static final ThreadLocal SKIP_JARS_WITH_JMETER_SKIP_ATTRIBUTE = new ThreadLocal<>(); + + public static final String JMETER_SKIP_CLASS_SCANNING_ATTRIBUTE = "JMeter-Skip-Class-Scanning"; + + @API(status = API.Status.EXPERIMENTAL, since = "5.6") + public interface Closeable extends AutoCloseable { + @Override + void close(); + } + // static only private ClassFinder() { } + @API(status = API.Status.EXPERIMENTAL, since = "5.6") + public static boolean getSkipJarsWithJmeterSkipClassScanningAttribute() { + return Objects.equals(SKIP_JARS_WITH_JMETER_SKIP_ATTRIBUTE.get(), Boolean.TRUE); + } + + /** + * Configures if {@link ClassFinder} should skip jar files that have {@code JMeter-Skip-Class-Scanning: true} + * manifest attribute. + * JMeter will skip such jars when it uses both {@link java.util.ServiceLoader} and {@link ClassFinder}. + * However, {@link ClassFinder} was public, so it was possible that custom plugins could use it, and they should + * be able to find the implementations even if they are in jars with {@code JMeter-Skip-Class-Scanning: true}. + *

+ * Sample usage: + *

+     * List<String> classNames;
+     * try (ClassFinder.Closeable ignored = ClassFinder.skipJarsWithJmeterSkipClassScanningAttribute()) {
+     *   // findClassesThatExtend will not skip jars with JMeter-Skip-Class-Scanning: true manifest attribute
+     *   classNames = ClassFinder.findClassesThatExtend(...);
+     * 
+ * + * @return closeable that will reset "skip jar files with manifest entry" flag when closed. Use it in try-with-resources + */ + @API(status = API.Status.INTERNAL, since = "5.6") + public static Closeable skipJarsWithJmeterSkipClassScanningAttribute() { + SKIP_JARS_WITH_JMETER_SKIP_ATTRIBUTE.set(true); + return SKIP_JARS_WITH_JMETER_SKIP_ATTRIBUTE::remove; + } + + /** + * Loads services implementing a given interface. + * This is an intended replacement for {@code findClassesThatExtend}. + * + * @param service interface that services should extend. + * @param serviceLoader ServiceLoader to fetch services. + * @param exceptionHandler exception handler to use for services that fail to load. + * @return collection of services that load successfully + * @param type of service (class or interface) + */ + public static Collection loadServices( + @SuppressWarnings("BoundedWildcard") Class service, + ServiceLoader serviceLoader, + ServiceLoadExceptionHandler exceptionHandler + ) { + List result = new ArrayList<>(); + @SuppressWarnings("ForEachIterable") + Iterator it = serviceLoader.iterator(); + while (it.hasNext()) { + try { + // This can't be for-each loop because we need to catch exceptions from next() + result.add(it.next()); + } catch (ServiceConfigurationError e) { + // Java does not expose class name of the problematic class in question, so we extract it + // from the message + String message = e.getMessage(); + String className = ""; + if (message.startsWith(service.getName())) { + if (message.endsWith(" Unable to get public no-arg constructor")) { + className = message.substring( + service.getName().length() + ": ".length(), + message.length() - " Unable to get public no-arg constructor".length() + ); + } else if (message.endsWith(" not a subtype")) { + className = message.substring( + service.getName().length() + ": ".length(), + message.length() - " not a subtype".length() + ); + } else if (message.endsWith(" could not be instantiated")) { + className = message.substring( + service.getName().length() + ": ".length() + "Provider ".length(), + message.length() - " could not be instantiated".length() + ); + } + } + exceptionHandler.handle(service, className, e); + } + } + return Collections.unmodifiableCollection(result); + } + /** * Filter updates by only storing classes * that extend one of the parent classes @@ -176,7 +271,9 @@ public String toString() { * @param superClasses required parent class(es) * @return List of Strings containing discovered class names. * @throws IOException when scanning the classes fails + * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars} */ + @Deprecated public static List findClassesThatExtend(String[] paths, Class[] superClasses) throws IOException { return findClassesThatExtend(paths, superClasses, false); @@ -206,7 +303,9 @@ private static Set addJarsInPath(String[] paths) { * @param innerClasses should we include inner classes? * @return List containing discovered classes * @throws IOException when scanning for classes fails + * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars} */ + @Deprecated public static List findClassesThatExtend(String[] strPathsOrJars, final Class[] superClasses, final boolean innerClasses) throws IOException { @@ -223,7 +322,10 @@ public static List findClassesThatExtend(String[] strPathsOrJars, * @param notContains classname should not contain this string * @return List containing discovered classes * @throws IOException when scanning classes fails + * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars} */ + @API(status = API.Status.DEPRECATED, since = "5.6") + @Deprecated public static List findClassesThatExtend(String[] strPathsOrJars, final Class[] superClasses, final boolean innerClasses, String contains, String notContains) @@ -239,7 +341,10 @@ public static List findClassesThatExtend(String[] strPathsOrJars, * @param innerClasses should we include inner classes? * @return List containing discovered classes * @throws IOException when scanning classes fails + * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars} */ + @API(status = API.Status.DEPRECATED, since = "5.6") + @Deprecated public static List findAnnotatedClasses(String[] strPathsOrJars, final Class[] annotations, final boolean innerClasses) throws IOException { @@ -254,7 +359,10 @@ public static List findAnnotatedClasses(String[] strPathsOrJars, * @param annotations required annotations * @return List containing discovered classes * @throws IOException when scanning classes fails + * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars} */ + @API(status = API.Status.DEPRECATED, since = "5.6") + @Deprecated public static List findAnnotatedClasses(String[] strPathsOrJars, final Class[] annotations) throws IOException { @@ -272,7 +380,10 @@ public static List findAnnotatedClasses(String[] strPathsOrJars, * @param annotations true if classnames are annotations * @return List containing discovered classes * @throws IOException when scanning classes fails + * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars} */ + @API(status = API.Status.DEPRECATED, since = "5.6") + @Deprecated public static List findClassesThatExtend(String[] searchPathsOrJars, final Class[] classNames, final boolean innerClasses, String contains, String notContains, boolean annotations) @@ -307,7 +418,10 @@ public static List findClassesThatExtend(String[] searchPathsOrJars, * conform to * @return list of all classes in the jars, that conform to {@code filter} * @throws IOException when reading the jar files fails + * @deprecated use {@link #loadServices(Class, ServiceLoader, ServiceLoadExceptionHandler)} or {@code JMeterUtils#loadServicesAndScanJars} */ + @API(status = API.Status.DEPRECATED, since = "5.6") + @Deprecated public static List findClasses(String[] searchPathsOrJars, ClassFilter filter) throws IOException { if (log.isDebugEnabled()) { log.debug("findClasses with searchPathsOrJars : {} and classFilter : {}", @@ -352,10 +466,22 @@ private static String fixClassName(String strClassName) { } - private static void findClassesInOnePath(File file, Set listClasses, ClassFilter filter) throws IOException { + private static void findClassesInOnePath(File file, Set listClasses, ClassFilter filter) { if (file.isDirectory()) { findClassesInPathsDir(file.getAbsolutePath(), file, listClasses, filter); } else if (file.exists()) { + if (getSkipJarsWithJmeterSkipClassScanningAttribute() && file.getName().endsWith(DOT_JAR)) { + // Ignore jars with JMeter-Skip-Class-Scanning attribute + try (JarFile jar = new JarFile(file)) { + String value = jar.getManifest().getMainAttributes().getValue(JMETER_SKIP_CLASS_SCANNING_ATTRIBUTE); + if (Boolean.parseBoolean(value)) { + log.info("Jar {} is skipped for scanning since it has {}={} attribute", file, JMETER_SKIP_CLASS_SCANNING_ATTRIBUTE, value); + return; + } + } catch (IOException e) { + log.warn("Can not open the jar {}, message: {}", file.getAbsolutePath(), e.getLocalizedMessage(), e); + } + } try (ZipFile zipFile = new ZipFile(file); Stream entries = zipFile.stream()) { entries.filter(entry -> entry.getName().endsWith(DOT_CLASS)) @@ -371,7 +497,7 @@ private static void findClassesInOnePath(File file, Set listClasses, Cla } - private static void findClassesInPathsDir(String strPathElement, File dir, Set listClasses, ClassFilter filter) throws IOException { + private static void findClassesInPathsDir(String strPathElement, File dir, Set listClasses, ClassFilter filter) { File[] list = dir.listFiles(); if (list == null) { log.warn("{} is not a folder", dir.getAbsolutePath()); diff --git a/src/jorphan/src/main/java/org/apache/jorphan/reflect/CollectServiceLoadExceptionHandler.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/CollectServiceLoadExceptionHandler.java new file mode 100644 index 00000000000..41f80d4df87 --- /dev/null +++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/CollectServiceLoadExceptionHandler.java @@ -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.reflect; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Collects all the failures in a collection. + * @param the service type + */ +public class CollectServiceLoadExceptionHandler implements ServiceLoadExceptionHandler { + private final List> failures = new ArrayList<>(); + + @Override + public void handle(Class service, String className, Throwable throwable) { + failures.add(new ServiceLoadFailure<>(service, className, throwable)); + } + + public Collection> toCollection() { + return Collections.unmodifiableCollection(failures); + } +} diff --git a/src/jorphan/src/main/java/org/apache/jorphan/reflect/IgnoreServiceLoadExceptionHandler.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/IgnoreServiceLoadExceptionHandler.java new file mode 100644 index 00000000000..3ee2cf88b4b --- /dev/null +++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/IgnoreServiceLoadExceptionHandler.java @@ -0,0 +1,27 @@ +/* + * 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.reflect; + +/** + * Ignores all failures. + */ +public class IgnoreServiceLoadExceptionHandler implements ServiceLoadExceptionHandler { + @Override + public void handle(Class service, String className, Throwable throwable) { + } +} diff --git a/src/jorphan/src/main/java/org/apache/jorphan/reflect/JMeterService.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/JMeterService.java new file mode 100644 index 00000000000..c641256f03c --- /dev/null +++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/JMeterService.java @@ -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.jorphan.reflect; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * This is a marker annotation that describes JMeter will try using {@link java.util.ServiceLoader} to find the implementations. + * If the plugin exposes the service via service loader (META-INF/services) then it will improve JMeter startup. + *

Note: JMeter will still try scanning the classes in the jars for backward compatibility reasons, + * so if you expose services, then consider adding {@code JMeter-Skip-Class-Scanning: true} manifest attribute + * to your jar file. JMeter will skip scanning class files in such jars

+ * @since 5.6 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@API(status = API.Status.EXPERIMENTAL, since = "5.6") +public @interface JMeterService { +} diff --git a/src/jorphan/src/main/java/org/apache/jorphan/reflect/LogAndIgnoreServiceLoadExceptionHandler.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/LogAndIgnoreServiceLoadExceptionHandler.java new file mode 100644 index 00000000000..5959bb54cc7 --- /dev/null +++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/LogAndIgnoreServiceLoadExceptionHandler.java @@ -0,0 +1,54 @@ +/* + * 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.reflect; + +import org.slf4j.Logger; + +/** + * Logs all the failures to agiven {@link Logger} and ignores them. + */ +public class LogAndIgnoreServiceLoadExceptionHandler implements ServiceLoadExceptionHandler { + private final Logger log; + + public LogAndIgnoreServiceLoadExceptionHandler(Logger log) { + this.log = log; + } + + @Override + public void handle(Class service, String className, Throwable throwable) { + if (throwable instanceof NoClassDefFoundError) { + if (throwable.getMessage().contains("javafx")) { + log.error( + "Exception registering implementation: [{}] of interface: [{}], a dependency used by the plugin class is missing. " + + "Add JavaFX to your Java installation if you want to use renderer: {}", + className, service, className, throwable + ); + } else { + log.error( + "Exception registering implementation: [{}] of interface: [{}], a dependency used by the plugin class is missing", + className, service, throwable + ); + } + } else { + log.error( + "Exception registering implementation: [{}] of interface: [{}], a jar is probably missing", + className, service, throwable + ); + } + } +} diff --git a/src/jorphan/src/main/java/org/apache/jorphan/reflect/RethrowServiceLoadExceptionHandler.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/RethrowServiceLoadExceptionHandler.java new file mode 100644 index 00000000000..91400f3c8ee --- /dev/null +++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/RethrowServiceLoadExceptionHandler.java @@ -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.reflect; + +/** + * Rethrows service loading failures as {@link IllegalStateException}. + */ +public class RethrowServiceLoadExceptionHandler implements ServiceLoadExceptionHandler { + @Override + public void handle(Class service, String className, Throwable throwable) { + throw new IllegalStateException("Can't load class " + className + " for instantiating service " + service, throwable); + } +} diff --git a/src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadExceptionHandler.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadExceptionHandler.java new file mode 100644 index 00000000000..816c2931db6 --- /dev/null +++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadExceptionHandler.java @@ -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.reflect; + +import org.apiguardian.api.API; + +/** + * Service loading might fail (e.g. due to a missing dependency). + * This handler enables client code factor the failure handing. + * + * @param type of the service + * @since 5.6 + * @see IgnoreServiceLoadExceptionHandler + * @see LogAndIgnoreServiceLoadExceptionHandler + * @see RethrowServiceLoadExceptionHandler + */ +@FunctionalInterface +@API(status = API.Status.EXPERIMENTAL, since = "5.6") +public interface ServiceLoadExceptionHandler { + void handle(Class service, String className, Throwable throwable); +} diff --git a/src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadFailure.java b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadFailure.java new file mode 100644 index 00000000000..825f39aeb6f --- /dev/null +++ b/src/jorphan/src/main/java/org/apache/jorphan/reflect/ServiceLoadFailure.java @@ -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.reflect; + +public class ServiceLoadFailure { + private final Class service; + private final String className; + private final Throwable throwable; + + public ServiceLoadFailure(Class service, String className, Throwable throwable) { + this.service = service; + this.className = className; + this.throwable = throwable; + } + + public Class getService() { + return service; + } + + public String getClassName() { + return className; + } + + public Throwable getThrowable() { + return throwable; + } + + @Override + public String toString() { + return "ServiceLoadFailure{" + + "service=" + service + + ", className='" + className + '\'' + + ", throwable=" + throwable + + '}'; + } +} diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java index d9388b3050d..966c3502af4 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java @@ -63,6 +63,7 @@ import org.apache.jmeter.gui.action.AbstractAction; import org.apache.jmeter.gui.action.ActionNames; import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.Command; import org.apache.jmeter.gui.plugin.MenuCreator; import org.apache.jmeter.gui.tree.JMeterTreeModel; import org.apache.jmeter.gui.tree.JMeterTreeNode; @@ -110,12 +111,18 @@ import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; +import com.google.auto.service.AutoService; + /** * Opens a popup where user can enter a cURL command line and create a test plan * from it * * @since 5.1 */ +@AutoService({ + Command.class, + MenuCreator.class +}) public class ParseCurlCommandAction extends AbstractAction implements MenuCreator, ActionListener { // NOSONAR private static final Logger LOGGER = LoggerFactory.getLogger(ParseCurlCommandAction.class); private static final String ACCEPT_ENCODING = "Accept-Encoding"; diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java index 051389da248..27b9894be67 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java @@ -57,10 +57,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.auto.service.AutoService; /** * Default implementation that handles classical HTTP textual + Multipart requests */ +@AutoService(SamplerCreator.class) public class DefaultSamplerCreator extends AbstractSamplerCreator { private static final Logger log = LoggerFactory.getLogger(DefaultSamplerCreator.class); diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java index 81bc9d99f55..54768d353d7 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java @@ -23,10 +23,12 @@ import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.reflect.JMeterService; /** * Factory of sampler */ +@JMeterService public interface SamplerCreator { /** diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java index 3f3549cfb38..679db1dbe8b 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java @@ -17,14 +17,12 @@ package org.apache.jmeter.protocol.http.proxy; -import java.io.IOException; -import java.lang.reflect.Modifier; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.ServiceLoader; import org.apache.jmeter.util.JMeterUtils; -import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,41 +57,27 @@ public void setCounter(int value) { * Initialize factory from classpath */ private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final) - try { - List listClasses = ClassFinder.findClassesThatExtend( - JMeterUtils.getSearchPaths(), - new Class[] {SamplerCreator.class }); - for (String strClassName : listClasses) { - try { - if(log.isDebugEnabled()) { - log.debug("Loading class: {}", strClassName); + for (SamplerCreator creator : JMeterUtils.loadServicesAndScanJars( + SamplerCreator.class, + ServiceLoader.load(SamplerCreator.class), + Thread.currentThread().getContextClassLoader(), + new LogAndIgnoreServiceLoadExceptionHandler(log) + )) { + try { + String[] contentTypes = creator.getManagedContentTypes(); + for (String contentType : contentTypes) { + log.debug("Registering samplerCreator {} for content type:{}", + creator.getClass().getName(), contentType); + SamplerCreator oldSamplerCreator = samplerCreatorMap.put(contentType, creator); + if (oldSamplerCreator != null) { + log.warn("A sampler creator was already registered for:{}, class:{}, it will be replaced", + contentType, oldSamplerCreator.getClass()); } - Class commandClass = Class.forName(strClassName); - if (!Modifier.isAbstract(commandClass.getModifiers())) { - if(log.isDebugEnabled()) { - log.debug("Instantiating: {}", commandClass.getName()); - } - SamplerCreator creator = (SamplerCreator) commandClass.getDeclaredConstructor().newInstance(); - String[] contentTypes = creator.getManagedContentTypes(); - for (String contentType : contentTypes) { - if(log.isDebugEnabled()) { - log.debug("Registering samplerCreator {} for content type:{}", - commandClass.getName(), contentType); - } - SamplerCreator oldSamplerCreator = samplerCreatorMap.put(contentType, creator); - if(oldSamplerCreator!=null) { - log.warn("A sampler creator was already registered for:{}, class:{}, it will be replaced", - contentType, oldSamplerCreator.getClass()); - } - } - } - } catch (Exception e) { - log.error("Exception registering {} with implementation:{}", - SamplerCreator.class.getName(),strClassName, e); } + } catch (Exception e) { + log.error("Exception registering {} with implementation:{}", + SamplerCreator.class.getName(), creator.getClass(), e); } - } catch (IOException e) { - log.error("Exception finding implementations of {}", SamplerCreator.class, e); } } diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java index 45aa4e1b917..24955272148 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java @@ -18,90 +18,92 @@ package org.apache.jmeter.protocol.http.sampler; import java.beans.PropertyDescriptor; -import java.io.IOException; -import java.util.Collections; import java.util.List; +import java.util.ServiceLoader; +import java.util.stream.Collectors; import org.apache.jmeter.protocol.http.util.accesslog.Filter; import org.apache.jmeter.protocol.http.util.accesslog.LogParser; import org.apache.jmeter.testbeans.BeanInfoSupport; import org.apache.jmeter.testbeans.gui.FileEditor; import org.apache.jmeter.util.JMeterUtils; -import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class AccessLogSamplerBeanInfo extends BeanInfoSupport { private static final Logger log = LoggerFactory.getLogger(AccessLogSamplerBeanInfo.class); - private static final List LOG_PARSER_CLASSES = logParsers(); - - private static List logParsers() { - try { - return ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { LogParser.class }); - } catch (IOException e) { - log.warn("Could not find log parsers.", e); - return Collections.emptyList(); - } - } + private static final List LOG_PARSER_CLASSES = + JMeterUtils.loadServicesAndScanJars( + LogParser.class, + ServiceLoader.load(LogParser.class), + Thread.currentThread().getContextClassLoader(), + new LogAndIgnoreServiceLoadExceptionHandler(log) + ).stream() + .map(s -> s.getClass().getName()) + .sorted() + .collect(Collectors.toList()); public AccessLogSamplerBeanInfo() { super(AccessLogSampler.class); log.debug("Entered access log sampler bean info"); - try { - createPropertyGroup("defaults", // $NON-NLS-1$ - new String[] { "protocol", "domain", "portString", "imageParsing" });// $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ $NON-NLS-4$ - - createPropertyGroup("plugins", // $NON-NLS-1$ - new String[] { "parserClassName", "filterClassName" }); // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ - - createPropertyGroup("accesslogfile", // $NON-NLS-1$ - new String[] { "logFile" }); // $NON-NLS-1$ - - PropertyDescriptor p; - - p = property("parserClassName"); - p.setValue(NOT_UNDEFINED, Boolean.TRUE); - p.setValue(DEFAULT, AccessLogSampler.DEFAULT_CLASS); - p.setValue(NOT_OTHER, Boolean.TRUE); - p.setValue(NOT_EXPRESSION, Boolean.TRUE); - - log.debug("found parsers: {}", LOG_PARSER_CLASSES); - p.setValue(TAGS, LOG_PARSER_CLASSES.toArray(new String[LOG_PARSER_CLASSES.size()])); - - p = property("filterClassName"); // $NON-NLS-1$ - p.setValue(NOT_UNDEFINED, Boolean.FALSE); - p.setValue(DEFAULT, ""); // $NON-NLS-1$ - p.setValue(NOT_EXPRESSION, Boolean.TRUE); - List classes = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), - new Class[] { Filter.class }, false); - p.setValue(TAGS, classes.toArray(new String[classes.size()])); - - p = property("logFile"); // $NON-NLS-1$ - p.setValue(NOT_UNDEFINED, Boolean.TRUE); - p.setValue(DEFAULT, ""); - p.setPropertyEditorClass(FileEditor.class); - - p = property("domain"); // $NON-NLS-1$ - p.setValue(NOT_UNDEFINED, Boolean.TRUE); - p.setValue(DEFAULT, ""); - - p = property("protocol"); // $NON-NLS-1$ - p.setValue(NOT_UNDEFINED, Boolean.TRUE); - p.setValue(DEFAULT, "http"); // $NON-NLS-1$ - p.setValue(DEFAULT_NOT_SAVED, Boolean.TRUE); - - p = property("portString"); // $NON-NLS-1$ - p.setValue(NOT_UNDEFINED, Boolean.TRUE); - p.setValue(DEFAULT, ""); // $NON-NLS-1$ - - p = property("imageParsing"); // $NON-NLS-1$ - p.setValue(NOT_UNDEFINED, Boolean.TRUE); - p.setValue(DEFAULT, Boolean.FALSE); - p.setValue(NOT_OTHER, Boolean.TRUE); - } catch (IOException e) { - log.warn("couldn't find classes and set up properties", e); - throw new RuntimeException("Could not find classes with class finder", e); - } + createPropertyGroup("defaults", // $NON-NLS-1$ + new String[] { "protocol", "domain", "portString", "imageParsing" });// $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ $NON-NLS-4$ + + createPropertyGroup("plugins", // $NON-NLS-1$ + new String[] { "parserClassName", "filterClassName" }); // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ + + createPropertyGroup("accesslogfile", // $NON-NLS-1$ + new String[] { "logFile" }); // $NON-NLS-1$ + + PropertyDescriptor p; + + p = property("parserClassName"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, AccessLogSampler.DEFAULT_CLASS); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + + log.debug("found parsers: {}", LOG_PARSER_CLASSES); + p.setValue(TAGS, LOG_PARSER_CLASSES.toArray(new String[0])); + + p = property("filterClassName"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.FALSE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + String[] classes = JMeterUtils.loadServicesAndScanJars( + Filter.class, + ServiceLoader.load(Filter.class), + Thread.currentThread().getContextClassLoader(), + new LogAndIgnoreServiceLoadExceptionHandler(log) + ).stream() + .map(s -> s.getClass().getName()) + .sorted() + .toArray(String[]::new); + p.setValue(TAGS, classes); + + p = property("logFile"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p.setPropertyEditorClass(FileEditor.class); + + p = property("domain"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("protocol"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "http"); // $NON-NLS-1$ + p.setValue(DEFAULT_NOT_SAVED, Boolean.TRUE); + + p = property("portString"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property("imageParsing"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + p.setValue(NOT_OTHER, Boolean.TRUE); log.debug("Got to end of access log sampler bean info init"); } diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/Filter.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/Filter.java index 182c361b1fa..f78d6bf170b 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/Filter.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/Filter.java @@ -18,6 +18,7 @@ package org.apache.jmeter.protocol.http.util.accesslog; import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.reflect.JMeterService; /** * Description:
@@ -35,6 +36,7 @@ * */ +@JMeterService public interface Filter { /** diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java index 698e03953a7..ce9487fb127 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java @@ -29,6 +29,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Description:
*
@@ -69,6 +71,7 @@ * that should be replaced. */ @SuppressWarnings("InconsistentCapitalization") +@AutoService(Filter.class) public class LogFilter implements Filter, Serializable { private static final long serialVersionUID = 241L; diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java index d08809c9e56..c29fa43b356 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java @@ -18,6 +18,7 @@ package org.apache.jmeter.protocol.http.util.accesslog; import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.reflect.JMeterService; /** * Description:
@@ -34,6 +35,7 @@ * */ +@JMeterService public interface LogParser { /** diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java index 581a21acdcd..3f1f5dbc7bc 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java @@ -19,6 +19,9 @@ import org.apache.jmeter.testelement.TestElement; +import com.google.auto.service.AutoService; + +@AutoService(LogParser.class) public class OrderPreservingLogParser extends SharedTCLogParser { public OrderPreservingLogParser() { diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java index e573aec2181..6ffef587c35 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java @@ -37,9 +37,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Provides Session Filtering for the AccessLog Sampler. */ +@AutoService(Filter.class) public class SessionFilter implements Filter, Serializable, TestCloneable,ThreadListener { private static final java.util.regex.Pattern IP_PATTERN = java.util.regex.Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"); private static final long serialVersionUID = 233L; diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java index 9e429e03e18..d0702169561 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java @@ -23,6 +23,9 @@ import org.apache.jmeter.testelement.TestCloneable; import org.apache.jmeter.testelement.TestElement; +import com.google.auto.service.AutoService; + +@AutoService(LogParser.class) public class SharedTCLogParser extends TCLogParser implements TestCloneable { public SharedTCLogParser() { diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java index ce79dd0bab9..72a45b25d2e 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java @@ -36,6 +36,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Description:
*
@@ -72,6 +74,7 @@ */ @SuppressWarnings("InconsistentCapitalization") +@AutoService(LogParser.class) public class TCLogParser implements LogParser { protected static final Logger log = LoggerFactory.getLogger(TCLogParser.class); diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java index 0a0790ee02d..5eb198062ed 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java @@ -56,9 +56,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * Specializer panel to view a HTTP request parsed */ +@AutoService(RequestView.class) public class RequestViewHTTP implements RequestView { private static final Logger log = LoggerFactory.getLogger(RequestViewHTTP.class); diff --git a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java index 53b128ce785..ea25256a6ee 100644 --- a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java +++ b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java @@ -18,12 +18,13 @@ package org.apache.jmeter.protocol.java.config.gui; import java.awt.BorderLayout; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.ServiceLoader; import java.util.Set; +import java.util.stream.Collectors; import javax.swing.ImageIcon; import javax.swing.JLabel; @@ -47,7 +48,7 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.gui.JFactory; import org.apache.jorphan.gui.JLabeledChoice; -import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.reflect.LogAndIgnoreServiceLoadExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -136,21 +137,15 @@ private final void init() {// called from ctor, so must not be overridable * @return a panel containing the relevant components */ private JPanel createClassnamePanel() { - List possibleClasses = new ArrayList<>(); - - try { - // Find all the classes which implement the JavaSamplerClient - // interface. - possibleClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), - new Class[] { JavaSamplerClient.class }); - - // Remove the JavaConfig class from the list since it only - // implements the interface for error conditions. - - possibleClasses.remove(JavaSampler.class.getName() + "$ErrorSamplerClient"); - } catch (Exception e) { - log.debug("Exception getting interfaces.", e); - } + List possibleClasses = JMeterUtils.loadServicesAndScanJars( + JavaSamplerClient.class, + ServiceLoader.load(JavaSamplerClient.class), + Thread.currentThread().getContextClassLoader(), + new LogAndIgnoreServiceLoadExceptionHandler(log) + ).stream() + .map(s -> s.getClass().getName()) + .sorted() + .collect(Collectors.toList()); classNameLabeledChoice = new JLabeledChoice( JMeterUtils.getResString("protocol_java_classname"), diff --git a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java index ea884511a02..fdfff46b6fb 100644 --- a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java +++ b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java @@ -19,6 +19,7 @@ import org.apache.jmeter.config.Arguments; import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.reflect.JMeterService; /** * This interface defines the interactions between the JavaSampler and external @@ -59,6 +60,7 @@ * how to implement this interface. * */ +@JMeterService public interface JavaSamplerClient { /** * Do any initialization required by this client. It is generally diff --git a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/JavaTest.java b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/JavaTest.java index 83ee435a79f..5efcb1fe189 100644 --- a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/JavaTest.java +++ b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/JavaTest.java @@ -23,6 +23,7 @@ import org.apache.jmeter.config.Arguments; import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerClient; import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; import org.apache.jmeter.samplers.Interruptible; import org.apache.jmeter.samplers.SampleResult; @@ -30,6 +31,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * The JavaTest class is a simple sampler which is intended for * use when developing test plans. The sampler generates results internally, so @@ -66,7 +69,7 @@ * Note: this class was derived from {@link SleepTest}. * */ - +@AutoService(JavaSamplerClient.class) public class JavaTest extends AbstractJavaSamplerClient implements Serializable, Interruptible { private static final Logger LOG = LoggerFactory.getLogger(JavaTest.class); diff --git a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/SleepTest.java b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/SleepTest.java index 25ddbbdadf1..3a8ee150848 100644 --- a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/SleepTest.java +++ b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/test/SleepTest.java @@ -23,6 +23,7 @@ import org.apache.jmeter.config.Arguments; import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerClient; import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; import org.apache.jmeter.samplers.Interruptible; import org.apache.jmeter.samplers.SampleResult; @@ -30,6 +31,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.auto.service.AutoService; + /** * The SleepTest class is a simple example class for a JMeter * Java protocol client. The class implements the JavaSamplerClient @@ -47,6 +50,7 @@ * time. * */ +@AutoService(JavaSamplerClient.class) public class SleepTest extends AbstractJavaSamplerClient implements Serializable, Interruptible { private static final Logger LOG = LoggerFactory.getLogger(SleepTest.class); diff --git a/src/protocol/junit/src/main/java/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java b/src/protocol/junit/src/main/java/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java index da8a0540ed4..b2dc6e81759 100644 --- a/src/protocol/junit/src/main/java/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java +++ b/src/protocol/junit/src/main/java/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java @@ -171,10 +171,14 @@ private void setupClasslist(boolean initialize){ if(initialize) { synchronized (IS_INITILIAZED) { if(IS_INITILIAZED.compareAndSet(false, true)) { - annotatedTestClasses = ClassFinder.findAnnotatedClasses(SPATHS, - new Class[] {Test.class}, false); - junitTestClasses = ClassFinder.findClassesThatExtend(SPATHS, - new Class[] { TestCase.class }); + @SuppressWarnings("deprecation") + List annotatedClasses = ClassFinder.findAnnotatedClasses(SPATHS, + new Class[]{Test.class}, false); + annotatedTestClasses = annotatedClasses; + @SuppressWarnings("deprecation") + List junitClasses = ClassFinder.findClassesThatExtend(SPATHS, + new Class[]{TestCase.class}); + junitTestClasses = junitClasses; } if (junit4.isSelected()){ classList = annotatedTestClasses; diff --git a/src/test-services/build.gradle.kts b/src/test-services/build.gradle.kts new file mode 100644 index 00000000000..9efcceed316 --- /dev/null +++ b/src/test-services/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +plugins { + id("build-logic.jvm-library") +} + +dependencies { + implementation(projects.src.jorphan) + implementation(projects.src.core) +} diff --git a/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/AbstractService.kt b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/AbstractService.kt new file mode 100644 index 00000000000..b3cc906b979 --- /dev/null +++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/AbstractService.kt @@ -0,0 +1,25 @@ +/* + * 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.util.services + +import com.google.auto.service.AutoService + +@AutoService(AbstractServiceInterface::class) +public abstract class AbstractService : AbstractServiceInterface + +public interface AbstractServiceInterface diff --git a/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceNotImplementingInterface.kt b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceNotImplementingInterface.kt new file mode 100644 index 00000000000..e70ff8f82c0 --- /dev/null +++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceNotImplementingInterface.kt @@ -0,0 +1,25 @@ +/* + * 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.util.services + +import com.google.auto.service.AutoService + +@AutoService(NotImplementedInterface::class) +public class ServiceNotImplementingInterface + +public interface NotImplementedInterface diff --git a/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceThrowingException.kt b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceThrowingException.kt new file mode 100644 index 00000000000..7c16a54ae6c --- /dev/null +++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceThrowingException.kt @@ -0,0 +1,31 @@ +/* + * 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.util.services + +import com.google.auto.service.AutoService + +@AutoService(ServiceThrowingExceptionInterface::class) +public class ServiceThrowingException : ServiceThrowingExceptionInterface { + init { + throw ServiceFailureException("exception in constructor for test purposes") + } +} + +public class ServiceFailureException(message: String) : Throwable(message) + +public interface ServiceThrowingExceptionInterface diff --git a/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceWithPrivateConstructor.kt b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceWithPrivateConstructor.kt new file mode 100644 index 00000000000..2710984ea71 --- /dev/null +++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/ServiceWithPrivateConstructor.kt @@ -0,0 +1,25 @@ +/* + * 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.util.services + +import com.google.auto.service.AutoService + +@AutoService(ServiceWithPrivateConstructorInterface::class) +public class ServiceWithPrivateConstructor private constructor() : ServiceWithPrivateConstructorInterface + +public interface ServiceWithPrivateConstructorInterface diff --git a/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/WorkableService.kt b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/WorkableService.kt new file mode 100644 index 00000000000..af134427d2d --- /dev/null +++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/WorkableService.kt @@ -0,0 +1,31 @@ +/* + * 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.util.services + +import com.google.auto.service.AutoService + +@AutoService(WorkableServiceInterface::class) +public class WorkableService : WorkableServiceInterface { + override val name: String = "test service" + + override fun toString(): String = name +} + +public interface WorkableServiceInterface { + public val name: String +} diff --git a/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/loadServices.kt b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/loadServices.kt new file mode 100644 index 00000000000..81102d72056 --- /dev/null +++ b/src/test-services/src/main/kotlin/org/apache/jmeter/util/services/loadServices.kt @@ -0,0 +1,32 @@ +/* + * 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.util.services + +import org.apache.jmeter.util.JMeterUtils +import org.apache.jorphan.reflect.ServiceLoadExceptionHandler +import java.util.ServiceLoader + +public inline fun loadServices( + exceptionHandler: ServiceLoadExceptionHandler +): Collection = + JMeterUtils.loadServicesAndScanJars( + S::class.java, + ServiceLoader.load(S::class.java), + Thread.currentThread().contextClassLoader, + exceptionHandler + ) diff --git a/src/test-services/src/test/kotlin/ServiceLoaderTest.kt b/src/test-services/src/test/kotlin/ServiceLoaderTest.kt new file mode 100644 index 00000000000..3c4d1d1957a --- /dev/null +++ b/src/test-services/src/test/kotlin/ServiceLoaderTest.kt @@ -0,0 +1,98 @@ +/* + * 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. + */ + +import org.apache.jmeter.util.services.AbstractServiceInterface +import org.apache.jmeter.util.services.NotImplementedInterface +import org.apache.jmeter.util.services.ServiceThrowingExceptionInterface +import org.apache.jmeter.util.services.ServiceWithPrivateConstructorInterface +import org.apache.jmeter.util.services.WorkableServiceInterface +import org.apache.jmeter.util.services.loadServices +import org.apache.jorphan.reflect.CollectServiceLoadExceptionHandler +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll + +class ServiceLoaderTest { + @Test + fun `service without public constructor`() { + assertServiceLoad( + "[]", + "[service: org.apache.jmeter.util.services.ServiceWithPrivateConstructorInterface, className: org.apache.jmeter.util.services.ServiceWithPrivateConstructor, exceptionClass: java.util.ServiceConfigurationError, causeClass: java.lang.NoSuchMethodException]" + ) + } + + @Test + fun `service not implementing interface`() { + assertServiceLoad( + "[]", + "[service: org.apache.jmeter.util.services.NotImplementedInterface, className: org.apache.jmeter.util.services.ServiceNotImplementingInterface, exceptionClass: java.util.ServiceConfigurationError, causeClass: null]" + ) + } + + @Test + fun `service failing in constructor`() { + assertServiceLoad( + "[]", + "[service: org.apache.jmeter.util.services.ServiceThrowingExceptionInterface, className: org.apache.jmeter.util.services.ServiceThrowingException, exceptionClass: java.util.ServiceConfigurationError, causeClass: org.apache.jmeter.util.services.ServiceFailureException]" + ) + } + + @Test + fun `abstract service`() { + assertServiceLoad( + "[]", + "[service: org.apache.jmeter.util.services.AbstractServiceInterface, className: org.apache.jmeter.util.services.AbstractService, exceptionClass: java.util.ServiceConfigurationError, causeClass: java.lang.InstantiationException]" + ) + } + + @Test + fun `service loads`() { + assertServiceLoad( + "[test service]", + "[]" + ) + } + + private inline fun assertServiceLoad( + successMessage: String, + failureMessage: String, + ) { + val failures = CollectServiceLoadExceptionHandler() + val successes = loadServices(failures) + val allFailures = failures.toCollection() + assertAll( + { + assertEquals(successMessage, successes.toString()) { + "Successfully loaded services for ${S::class.java.name}" + } + }, + { + assertEquals( + failureMessage, + allFailures.map { + "service: ${it.service.name}, " + + "className: ${it.className}, " + + "exceptionClass: ${it.throwable::class.java.name}, " + + "causeClass: ${it.throwable.cause?.let { it::class.java.name }}" + }.toString(), + ) { + "All failures when loading service ${S::class.java.name} are $allFailures" + } + } + ) + } +} diff --git a/xdocs/changes.xml b/xdocs/changes.xml index 908c6f023f9..a16d490b6de 100644 --- a/xdocs/changes.xml +++ b/xdocs/changes.xml @@ -103,6 +103,7 @@ Summary
  • 5792Add KeyStroke for start_no_timers (Start no pauses: CRTL+SHIFT+n)
  • 5899Speed up CPU-bound tests by skipping recoverRunningVersion for elements that are shared between threads (the ones that implement NoThreadClone)
  • 5914Use Locale.ROOT instead of default locale for toUpperCase, and toLowerCase to avoid surprises with dotless I in tr_TR locale
  • +
  • 5885Use Java's ServiceLoader for loading plugins instead of classpath scanning. It enables faster startup
  • Non-functional changes diff --git a/xdocs/usermanual/jmeter_tutorial.xml b/xdocs/usermanual/jmeter_tutorial.xml index f32a8d0f887..04dfec7578f 100644 --- a/xdocs/usermanual/jmeter_tutorial.xml +++ b/xdocs/usermanual/jmeter_tutorial.xml @@ -168,7 +168,49 @@ write. - + +

    + JMeter uses classpath scanning to detect plugins (e.g. functions, components, views). However, classpath scanning + is expensive, + so JMeter also provides a service lookup mechanism to allow plugins to be found without scanning the classpath. + That is if a plugin registers a Java service with META-INF/services, then JMeter won't need to scan + the classpath to find it. +

    + +

    + Some of the existing features already use the new service lookup mechanism, but it is not yet used for all + features. + The interfaces that are supported for service loading are marked with + org.apache.jorphan.reflect.JMeterService + annotation. +

    + +

    + Implementing a service the same as regular interface implementation, except you need to register the service + in a META-INF/services/fully.qualified.interface.name. + For instance you could use @AutoService to generate the file automatically at the build time. + Here's how __counter function is declared in JMeter itself: + +import com.google.auto.service.AutoService; + +@AutoService(Function.class) +public class IterationCounter extends AbstractFunction implements ThreadListener { + +

    + +

    + For backward compatibility reasons, JMeter would still try searching the implementations with classpath scanning, + so you don't have to use META-INF/services for registering services. However, service lookup is much + faster, so exposing services would improve startup time, especially when there are many plugins. +

    + +

    + As you add META-INF/services to your plugins, you can add JMeter-Skip-Class-Scanning: true + manifest attribute so JMeter knows there's no need to scan the jar as it provides all the plugins via services. +

    +
    + +

    When writing any JMeter component, there are certain contracts you must be aware of – ways a @@ -327,7 +369,7 @@ Writing Visualizers is somewhat of a special case, however. - + Load Testing in GUI mode being a bad practice, you should not develop such plugin. Have a look at more up to date components like:

      @@ -537,7 +579,7 @@ added so that users can save a screen capture of any given visualizer. - + Load Testing in GUI mode being a bad practice, you should not develop such plugin. Have a look at more up to date components like:
        @@ -571,7 +613,7 @@ implement the interface, please look at GraphVisualizer. - + Load Testing in GUI mode being a bad practice, you should not develop such plugin. Have a look at more up to date components like:
          @@ -715,7 +757,7 @@ and predictable. - +

          In this part, we will go through the process of creating a simple component for JMeter that uses @@ -860,7 +902,7 @@ p.setPropertyEditorClass(FileEditor.class); - +

          JMeter uses Gradle to compile and build the distribution. JMeter has