From e49e3a03b53017506b6dcd42bff8ab848b7dc06e Mon Sep 17 00:00:00 2001 From: tms08012 Date: Thu, 7 Jun 2012 16:02:41 -0400 Subject: [PATCH] First setting of JMeter, which is compiling at this time. --- ApacheJmeter/.classpath | 78 + ApacheJmeter/.project | 17 + .../.settings/org.eclipse.jdt.core.prefs | 11 + ApacheJmeter/LICENSE | 2031 ++ ApacheJmeter/NOTICE | 36 + ApacheJmeter/README | 167 + ApacheJmeter/STATUS | 16 + ApacheJmeter/build.properties | 272 + ApacheJmeter/build.xml | 2616 +++ ApacheJmeter/checkstyle.xml | 105 + ApacheJmeter/doap_JMeter.rdf | 127 + ApacheJmeter/docs/building.html | 290 + ApacheJmeter/docs/changes.html | 966 + ApacheJmeter/docs/changes_history.html | 9872 +++++++++ ApacheJmeter/docs/css/style.css | 39 + ApacheJmeter/docs/demos/AssertionTestPlan.jmx | 128 + .../docs/demos/AuthManagerTestPlan.jmx | 151 + .../docs/demos/BeanShellAssertion.bsh | 53 + ApacheJmeter/docs/demos/ForEachTest2.jmx | 322 + .../docs/demos/HeaderManagerTestPlan.jmx | 95 + .../docs/demos/InterleaveTestPlan.jmx | 160 + .../docs/demos/InterleaveTestPlan2.jmx | 234 + .../docs/demos/JDBC-Pre-Post-Processor.jmx | 445 + ApacheJmeter/docs/demos/JMSPointToPoint.jmx | 103 + ApacheJmeter/docs/demos/LoopTestPlan.jmx | 124 + ApacheJmeter/docs/demos/OnceOnlyTestPlan.jmx | 132 + .../docs/demos/ProxyServerTestPlan.jmx | 27 + ApacheJmeter/docs/demos/SimpleTestPlan.jmx | 166 + .../docs/demos/URLRewritingExample.jmx | 142 + ApacheJmeter/docs/demos/forEachTestPlan.jmx | 123 + ApacheJmeter/docs/download_jmeter.cgi | 7 + ApacheJmeter/docs/download_jmeter.html | 540 + ApacheJmeter/docs/index.html | 517 + ApacheJmeter/docs/issues.html | 398 + ApacheJmeter/docs/jmeter_irc.html | 216 + ApacheJmeter/docs/localising/index.html | 516 + ApacheJmeter/docs/mail.html | 495 + ApacheJmeter/docs/mail2.html | 578 + ApacheJmeter/docs/nightly.html | 372 + ApacheJmeter/docs/svnindex.html | 312 + .../docs/usermanual/best-practices.html | 785 + ApacheJmeter/docs/usermanual/boss.html | 667 + .../usermanual/build-adv-web-test-plan.html | 330 + .../docs/usermanual/build-db-test-plan.html | 530 + .../docs/usermanual/build-ftp-test-plan.html | 535 + .../build-jms-point-to-point-test-plan.html | 638 + .../usermanual/build-jms-topic-test-plan.html | 565 + .../docs/usermanual/build-ldap-test-plan.html | 513 + .../usermanual/build-ldapext-test-plan.html | 1255 ++ .../usermanual/build-monitor-test-plan.html | 494 + .../docs/usermanual/build-test-plan.html | 504 + .../docs/usermanual/build-web-test-plan.html | 590 + .../docs/usermanual/build-ws-test-plan.html | 477 + .../docs/usermanual/component_reference.html | 17819 ++++++++++++++++ ApacheJmeter/docs/usermanual/functions.html | 4230 ++++ ApacheJmeter/docs/usermanual/get-started.html | 1570 ++ ApacheJmeter/docs/usermanual/glossary.html | 386 + .../docs/usermanual/hints_and_tips.html | 374 + ApacheJmeter/docs/usermanual/index.html | 1379 ++ ApacheJmeter/docs/usermanual/intro.html | 296 + .../docs/usermanual/ldapanswer_xml.html | 452 + .../docs/usermanual/ldapops_tutor.html | 457 + ApacheJmeter/docs/usermanual/listeners.html | 1276 ++ .../docs/usermanual/regular_expressions.html | 855 + ApacheJmeter/docs/usermanual/remote-test.html | 817 + ApacheJmeter/docs/usermanual/test_plan.html | 1269 ++ ApacheJmeter/eclipse.classpath | 89 + ApacheJmeter/eclipse.readme | 105 + ApacheJmeter/extras/ConvertHTTPSampler.txt | 16 + ApacheJmeter/extras/Test.jmx | 145 + ApacheJmeter/extras/addons.txt | 35 + ApacheJmeter/extras/addons.xml | 67 + ApacheJmeter/extras/build.xml | 167 + ApacheJmeter/extras/convertjmx.fdl | 27 + ApacheJmeter/extras/execcode.bsh | 36 + .../extras/jmeter-results-detail-report.xsl | 407 + .../jmeter-results-detail-report_21.xsl | 411 + ApacheJmeter/extras/jmeter-results-report.xsl | 291 + .../extras/jmeter-results-report_21.xsl | 303 + ApacheJmeter/extras/jmeter.fb | 62 + ApacheJmeter/extras/printvars.bsh | 29 + ApacheJmeter/extras/proxycert.cmd | 27 + ApacheJmeter/extras/proxycert.sh | 27 + ApacheJmeter/extras/remote.bsh | 48 + ApacheJmeter/extras/schematic.cmd | 24 + ApacheJmeter/extras/schematic.xml | 39 + ApacheJmeter/extras/schematic.xsl | 119 + ApacheJmeter/extras/startup.bsh | 99 + ApacheJmeter/fb-csv.xsl | 59 + ApacheJmeter/fb-excludes.xml | 85 + ApacheJmeter/lib/aareadme.txt | 194 + ApacheJmeter/lib/opt/README.txt | 8 + .../commons/cli/avalon/ClutilTestCase.java | 967 + .../assertions/MD5HexAssertionTest.java | 36 + .../assertions/ResponseAssertionTest.java | 253 + .../jmeter/assertions/SizeAssertionTest.java | 170 + .../assertions/XMLSchemaAssertionTest.java | 182 + .../jmeter/assertions/XPathAssertionTest.java | 322 + .../apache/jmeter/config/TestCVSDataSet.java | 199 + .../jmeter/config/gui/TestArgumentsPanel.java | 60 + .../jmeter/control/TestGenericController.java | 58 + .../jmeter/control/TestIfController.java | 176 + .../jmeter/control/TestInterleaveControl.java | 227 + .../jmeter/control/TestLoopController.java | 82 + .../control/TestOnceOnlyController.java | 331 + .../control/TestRandomOrderController.java | 73 + .../apache/jmeter/control/TestRunTime.java | 70 + .../jmeter/control/TestSwitchController.java | 298 + .../control/TestThroughputController.java | 216 + .../jmeter/control/TestWhileController.java | 365 + .../apache/jmeter/engine/TestTreeCloner.java | 81 + .../jmeter/engine/util/PackageTest.java | 218 + .../jmeter/engine/util/TestValueReplacer.java | 91 + .../jmeter/extractor/TestRegexExtractor.java | 412 + .../jmeter/extractor/TestXPathExtractor.java | 221 + .../apache/jmeter/functions/PackageTest.java | 969 + .../functions/TestFileRowColContainer.java | 143 + .../jmeter/functions/TestJexlFunction.java | 96 + .../jmeter/functions/TestRegexFunction.java | 357 + .../jmeter/functions/TestTimeFunction.java | 170 + .../apache/jmeter/gui/action/PackageTest.java | 37 + .../apache/jmeter/gui/action/TestLoad.java | 118 + .../apache/jmeter/gui/action/TestSave.java | 50 + .../jmeter/gui/util/TestMenuFactory.java | 53 + .../org/apache/jmeter/junit/JMeterTest.java | 657 + .../apache/jmeter/junit/JMeterTestCase.java | 177 + .../jmeter/junit/stubs/TestSampler.java | 74 + .../monitor/model/TestObjectFactory.java | 73 + .../model/benchmark/ParseBenchmark.java | 98 + .../http/config/MultipartUrlConfigTest.java | 141 + .../protocol/http/config/UrlConfigTest.java | 74 + .../http/control/TestAuthManager.java | 80 + .../http/control/TestCacheManager.java | 381 + .../http/control/TestCookieManager.java | 408 + .../http/control/TestHTTPMirrorThread.java | 399 + .../control/gui/TestHttpTestSampleGui.java | 46 + .../http/modifier/TestAnchorModifier.java | 342 + .../modifier/TestURLRewritingModifier.java | 267 + .../protocol/http/parser/TestHTMLParser.java | 318 + .../http/parser/TestHtmlParsingUtils.java | 107 + .../http/proxy/TestHttpRequestHdr.java | 627 + .../protocol/http/proxy/TestProxyControl.java | 150 + .../http/sampler/HTTPNullSampler.java | 46 + .../protocol/http/sampler/HTTPSampler3.java | 45 + .../http/sampler/NullURLConnection.java | 56 + .../protocol/http/sampler/PackageTest.java | 58 + .../protocol/http/sampler/PostWriterTest.java | 932 + .../protocol/http/sampler/PutWriterTest.java | 45 + .../http/sampler/TestHTTPSamplers.java | 348 + ...stHTTPSamplersAgainstHttpMirrorServer.java | 1369 ++ .../protocol/http/util/TestHTTPArgument.java | 96 + .../protocol/http/util/TestHTTPFileArg.java | 66 + .../protocol/http/util/TestHTTPFileArgs.java | 121 + .../protocol/http/util/TestHTTPUtils.java | 83 + .../http/util/accesslog/TestLogFilter.java | 158 + .../http/util/accesslog/TestTCLogParser.java | 62 + .../protocol/ldap/config/gui/PackageTest.java | 51 + .../tcp/sampler/BinaryTCPClientImplTest.java | 86 + ...LengthPrefixedBinaryTCPClientImplTest.java | 55 + .../tcp/sampler/TCPClientDecoratorTest.java | 247 + .../apache/jmeter/resources/PackageTest.java | 387 + .../apache/jmeter/samplers/NullSampler.java | 34 + .../jmeter/samplers/TestSampleResult.java | 328 + .../samplers/TestSampleSaveConfiguration.java | 139 + .../jmeter/save/TestCSVSaveService.java | 111 + .../apache/jmeter/save/TestSaveService.java | 157 + .../jmeter/services/TestFileServer.java | 119 + .../jmeter/testbeans/gui/PackageTest.java | 220 + .../gui/TestBooleanPropertyEditor.java | 55 + .../testbeans/gui/TestComboStringEditor.java | 55 + .../testbeans/gui/TestFieldStringEditor.java | 50 + .../jmeter/testelement/BarChartTest.java | 88 + .../jmeter/testelement/LineGraphTest.java | 88 + .../jmeter/testelement/PackageTest.java | 111 + .../testelement/property/PackageTest.java | 314 + .../threads/TestJMeterContextService.java | 52 + .../jmeter/threads/TestTestCompiler.java | 62 + .../org/apache/jmeter/timers/PackageTest.java | 102 + .../org/apache/jmeter/util/PackageTest.java | 66 + .../apache/jmeter/util/TestJMeterUtils.java | 40 + .../jmeter/visualizers/GenerateTreeGui.java | 255 + .../TestSamplingStatCalculator.java | 153 + .../org/apache/jorphan/TestFunctorUsers.java | 69 + .../org/apache/jorphan/TestXMLBuffer.java | 56 + .../jorphan/collections/PackageTest.java | 175 + .../jorphan/math/TestStatCalculator.java | 154 + .../apache/jorphan/reflect/TestFunctor.java | 225 + .../org/apache/jorphan/test/AllTests.java | 381 + .../apache/jorphan/util/TestJorphanUtils.java | 317 + ApacheJmeter/rat-excludes.txt | 28 + ApacheJmeter/res/META-INF/default.license | 202 + ApacheJmeter/res/META-INF/default.notice | 5 + ApacheJmeter/res/maven/ApacheJMeter.pom | 29 + .../res/maven/ApacheJMeter_components.pom | 45 + .../res/maven/ApacheJMeter_config.pom | 32 + ApacheJmeter/res/maven/ApacheJMeter_core.pom | 40 + ApacheJmeter/res/maven/ApacheJMeter_ftp.pom | 50 + .../res/maven/ApacheJMeter_functions.pom | 45 + ApacheJmeter/res/maven/ApacheJMeter_http.pom | 50 + ApacheJmeter/res/maven/ApacheJMeter_java.pom | 50 + ApacheJmeter/res/maven/ApacheJMeter_jdbc.pom | 50 + ApacheJmeter/res/maven/ApacheJMeter_jms.pom | 50 + .../res/maven/ApacheJMeter_junit-test.pom | 31 + ApacheJmeter/res/maven/ApacheJMeter_junit.pom | 44 + ApacheJmeter/res/maven/ApacheJMeter_ldap.pom | 49 + ApacheJmeter/res/maven/ApacheJMeter_mail.pom | 49 + .../res/maven/ApacheJMeter_monitors.pom | 54 + .../res/maven/ApacheJMeter_native.pom | 50 + .../res/maven/ApacheJMeter_parent.pom | 343 + .../res/maven/ApacheJMeter_report.pom | 49 + ApacheJmeter/res/maven/ApacheJMeter_tcp.pom | 49 + ApacheJmeter/res/maven/jorphan.pom | 32 + .../BSFAssertionResources.properties | 28 + .../BSFAssertionResources_fr.properties | 29 + .../BSFAssertionResources_pl.properties | 29 + .../BSFAssertionResources_pt_BR.properties | 28 + .../CompareAssertionResources.properties | 24 + .../CompareAssertionResources_fr.properties | 24 + .../JSR223AssertionResources.properties | 28 + .../JSR223AssertionResources_fr.properties | 29 + .../org/apache/jmeter/assertions/package.html | 33 + .../config/CSVDataSetResources.properties | 36 + .../config/CSVDataSetResources_de.properties | 29 + .../config/CSVDataSetResources_es.properties | 37 + .../config/CSVDataSetResources_fr.properties | 37 + .../config/CSVDataSetResources_pl.properties | 37 + .../CSVDataSetResources_pt_BR.properties | 36 + .../config/CSVDataSetResources_tr.properties | 30 + .../CSVDataSetResources_zh_TW.properties | 22 + .../config/KeystoreConfigResources.properties | 25 + .../KeystoreConfigResources_fr.properties | 25 + .../RandomVariableConfigResources.properties | 33 + ...andomVariableConfigResources_es.properties | 32 + ...andomVariableConfigResources_fr.properties | 32 + ...andomVariableConfigResources_pl.properties | 34 + ...omVariableConfigResources_pt_BR.properties | 31 + .../BSFPostProcessorResources.properties | 28 + .../BSFPostProcessorResources_fr.properties | 29 + .../BSFPostProcessorResources_pl.properties | 29 + ...BSFPostProcessorResources_pt_BR.properties | 28 + ...BeanShellPostProcessorResources.properties | 27 + ...nShellPostProcessorResources_de.properties | 24 + ...nShellPostProcessorResources_fr.properties | 28 + ...nShellPostProcessorResources_pl.properties | 29 + ...ellPostProcessorResources_pt_BR.properties | 27 + ...nShellPostProcessorResources_tr.properties | 25 + .../DebugPostProcessorResources.properties | 24 + .../DebugPostProcessorResources_de.properties | 24 + .../DebugPostProcessorResources_fr.properties | 25 + ...bugPostProcessorResources_pt_BR.properties | 24 + .../DebugPostProcessorResources_tr.properties | 25 + .../JSR223PostProcessorResources.properties | 28 + ...JSR223PostProcessorResources_fr.properties | 29 + .../BSFPreProcessorResources.properties | 28 + .../BSFPreProcessorResources_fr.properties | 29 + .../BSFPreProcessorResources_pt_BR.properties | 28 + .../BeanShellPreProcessorResources.properties | 27 + ...anShellPreProcessorResources_de.properties | 25 + ...anShellPreProcessorResources_fr.properties | 27 + ...hellPreProcessorResources_pt_BR.properties | 27 + ...anShellPreProcessorResources_tr.properties | 25 + .../JSR223PreProcessorResources.properties | 28 + .../JSR223PreProcessorResources_fr.properties | 29 + .../sampler/DebugSamplerResources.properties | 22 + .../DebugSamplerResources_de.properties | 22 + .../DebugSamplerResources_fr.properties | 23 + .../DebugSamplerResources_pt_BR.properties | 22 + .../DebugSamplerResources_tr.properties | 23 + .../timers/BSFTimerResources.properties | 28 + .../timers/BSFTimerResources_fr.properties | 29 + .../timers/BeanShellTimerResources.properties | 27 + .../BeanShellTimerResources_de.properties | 25 + .../BeanShellTimerResources_fr.properties | 28 + .../BeanShellTimerResources_pt_BR.properties | 27 + .../BeanShellTimerResources_tr.properties | 25 + ...onstantThroughputTimerResources.properties | 26 + ...tantThroughputTimerResources_de.properties | 26 + ...tantThroughputTimerResources_es.properties | 25 + ...tantThroughputTimerResources_fr.properties | 26 + ...tantThroughputTimerResources_ja.properties | 18 + ...tThroughputTimerResources_pt_BR.properties | 26 + ...tantThroughputTimerResources_tr.properties | 27 + ...tThroughputTimerResources_zh_TW.properties | 20 + .../timers/JSR223TimerResources.properties | 28 + .../timers/JSR223TimerResources_fr.properties | 29 + .../timers/SyncTimerResources.properties | 19 + .../timers/SyncTimerResources_de.properties | 18 + .../timers/SyncTimerResources_es.properties | 20 + .../timers/SyncTimerResources_fr.properties | 20 + .../SyncTimerResources_pt_BR.properties | 19 + .../timers/SyncTimerResources_tr.properties | 20 + .../BSFListenerResources.properties | 28 + .../BSFListenerResources_fr.properties | 29 + .../BSFListenerResources_pt_BR.properties | 28 + .../BeanShellListenerResources.properties | 27 + .../BeanShellListenerResources_de.properties | 24 + .../BeanShellListenerResources_fr.properties | 28 + ...eanShellListenerResources_pt_BR.properties | 27 + .../BeanShellListenerResources_tr.properties | 25 + .../JSR223ListenerResources.properties | 28 + .../JSR223ListenerResources_fr.properties | 29 + .../src/core/org/apache/jmeter/help.txt | 37 + .../org/apache/jmeter/images/icon.properties | 29 + .../apache/jmeter/images/icon_1.properties | 35 + .../images/toolbar/icons-toolbar.properties | 45 + .../jmeter/resources/messages.properties | 1198 ++ .../jmeter/resources/messages_de.properties | 575 + .../jmeter/resources/messages_es.properties | 1065 + .../jmeter/resources/messages_fr.properties | 1192 ++ .../jmeter/resources/messages_ja.properties | 480 + .../jmeter/resources/messages_no.properties | 155 + .../jmeter/resources/messages_pl.properties | 240 + .../resources/messages_pt_BR.properties | 902 + .../jmeter/resources/messages_tr.properties | 840 + .../resources/messages_zh_CN.properties | 439 + .../resources/messages_zh_TW.properties | 654 + .../apache/jmeter/visualizers/package.html | 33 + .../example2/Example2Resources.properties | 17 + .../example2/Example2Resources_es.properties | 18 + .../Example2Resources_pt_BR.properties | 17 + .../example2/Example2Resources_tr.properties | 18 + .../Example2Resources_zh_TW.properties | 18 + .../example3/Example3Resources.properties | 16 + .../org/apache/jmeter/functions/package.html | 41 + ApacheJmeter/src/i18nedit.properties | 25 + .../apache/commons/cli/avalon/package.html | 183 + .../cli/avalon/AbstractParserControl.java | 41 + .../commons/cli/avalon/CLArgsParser.java | 682 + .../apache/commons/cli/avalon/CLOption.java | 176 + .../cli/avalon/CLOptionDescriptor.java | 201 + .../org/apache/commons/cli/avalon/CLUtil.java | 108 + .../commons/cli/avalon/ParserControl.java | 38 + .../org/apache/commons/cli/avalon/Token.java | 71 + .../apache/commons/jexl/bsf/JexlEngine.java | 149 + .../org/apache/jmeter/DynamicClassLoader.java | 65 + .../src/org/apache/jmeter/JMeter.java | 1158 + .../src/org/apache/jmeter/JMeterReport.java | 404 + .../src/org/apache/jmeter/NewDriver.java | 222 + .../org/apache/jmeter/ProxyAuthenticator.java | 70 + .../apache/jmeter/assertions/Assertion.java | 44 + .../jmeter/assertions/AssertionResult.java | 165 + .../jmeter/assertions/BSFAssertion.java | 56 + .../assertions/BSFAssertionBeanInfo.java | 29 + .../jmeter/assertions/BeanShellAssertion.java | 132 + .../jmeter/assertions/CompareAssertion.java | 207 + .../assertions/CompareAssertionBeanInfo.java | 56 + .../assertions/CompareAssertionResult.java | 93 + .../jmeter/assertions/DurationAssertion.java | 72 + .../jmeter/assertions/HTMLAssertion.java | 375 + .../jmeter/assertions/JSR223Assertion.java | 63 + .../assertions/JSR223AssertionBeanInfo.java | 29 + .../jmeter/assertions/MD5HexAssertion.java | 111 + .../jmeter/assertions/ResponseAssertion.java | 528 + .../jmeter/assertions/SMIMEAssertion.java | 354 + .../assertions/SMIMEAssertionTestElement.java | 153 + .../jmeter/assertions/SizeAssertion.java | 240 + .../assertions/SubstitutionElement.java | 55 + .../jmeter/assertions/XMLAssertion.java | 111 + .../jmeter/assertions/XMLSchemaAssertion.java | 210 + .../jmeter/assertions/XPathAssertion.java | 261 + .../assertions/gui/AbstractAssertionGui.java | 53 + .../jmeter/assertions/gui/AssertionGui.java | 421 + .../assertions/gui/BeanShellAssertionGui.java | 168 + .../assertions/gui/DurationAssertionGui.java | 121 + .../assertions/gui/HTMLAssertionGui.java | 363 + .../assertions/gui/MD5HexAssertionGUI.java | 116 + .../assertions/gui/SMIMEAssertionGui.java | 244 + .../assertions/gui/SizeAssertionGui.java | 276 + .../assertions/gui/XMLAssertionGui.java | 76 + .../jmeter/assertions/gui/XMLConfPanel.java | 164 + .../assertions/gui/XMLSchemaAssertionGUI.java | 142 + .../assertions/gui/XPathAssertionGui.java | 123 + .../jmeter/assertions/gui/XPathPanel.java | 215 + .../org/apache/jmeter/config/Argument.java | 203 + .../org/apache/jmeter/config/Arguments.java | 259 + .../org/apache/jmeter/config/CSVDataSet.java | 270 + .../jmeter/config/CSVDataSetBeanInfo.java | 108 + .../apache/jmeter/config/ConfigElement.java | 51 + .../jmeter/config/ConfigTestElement.java | 56 + .../apache/jmeter/config/KeystoreConfig.java | 142 + .../jmeter/config/KeystoreConfigBeanInfo.java | 59 + .../org/apache/jmeter/config/LoginConfig.java | 79 + .../jmeter/config/RandomVariableConfig.java | 239 + .../config/RandomVariableConfigBeanInfo.java | 78 + .../jmeter/config/gui/AbstractConfigGui.java | 64 + .../jmeter/config/gui/ArgumentsPanel.java | 702 + .../jmeter/config/gui/LoginConfigGui.java | 167 + .../apache/jmeter/config/gui/ObsoleteGui.java | 72 + .../jmeter/config/gui/RowDetailDialog.java | 201 + .../jmeter/config/gui/SimpleConfigGui.java | 313 + .../org/apache/jmeter/control/Controller.java | 70 + .../jmeter/control/ForeachController.java | 194 + .../jmeter/control/GenericController.java | 402 + .../apache/jmeter/control/IfController.java | 209 + .../jmeter/control/IncludeController.java | 212 + .../jmeter/control/InterleaveControl.java | 174 + .../apache/jmeter/control/LoopController.java | 175 + .../jmeter/control/ModuleController.java | 201 + .../jmeter/control/NextIsNullException.java | 30 + .../jmeter/control/OnceOnlyController.java | 58 + .../jmeter/control/RandomController.java | 53 + .../jmeter/control/RandomOrderController.java | 88 + .../jmeter/control/ReplaceableController.java | 46 + .../org/apache/jmeter/control/RunTime.java | 134 + .../jmeter/control/SwitchController.java | 126 + .../control/TestFragmentController.java | 27 + .../jmeter/control/ThroughputController.java | 275 + .../jmeter/control/TransactionController.java | 302 + .../jmeter/control/TransactionSampler.java | 149 + .../jmeter/control/WhileController.java | 128 + .../control/gui/AbstractControllerGui.java | 63 + .../control/gui/ForeachControlPanel.java | 217 + .../jmeter/control/gui/IfControllerPanel.java | 193 + .../control/gui/IncludeControllerGui.java | 112 + .../control/gui/InterleaveControlGui.java | 89 + .../control/gui/LogicControllerGui.java | 64 + .../jmeter/control/gui/LoopControlPanel.java | 264 + .../control/gui/ModuleControllerGui.java | 262 + .../control/gui/OnceOnlyControllerGui.java | 56 + .../jmeter/control/gui/RandomControlGui.java | 89 + .../control/gui/RandomOrderControllerGui.java | 53 + .../apache/jmeter/control/gui/ReportGui.java | 186 + .../apache/jmeter/control/gui/RunTimeGui.java | 224 + .../control/gui/SwitchControllerGui.java | 96 + .../gui/TestFragmentControllerGui.java | 95 + .../jmeter/control/gui/TestPlanGui.java | 193 + .../control/gui/ThroughputControllerGui.java | 169 + .../control/gui/TransactionControllerGui.java | 86 + .../control/gui/WhileControllerGui.java | 145 + .../jmeter/control/gui/WorkBenchGui.java | 118 + .../jmeter/engine/ClientJMeterEngine.java | 195 + .../jmeter/engine/ConvertListeners.java | 94 + .../apache/jmeter/engine/JMeterEngine.java | 42 + .../jmeter/engine/JMeterEngineException.java | 44 + .../org/apache/jmeter/engine/PreCompiler.java | 106 + .../jmeter/engine/RemoteJMeterEngine.java | 43 + .../jmeter/engine/RemoteJMeterEngineImpl.java | 227 + .../jmeter/engine/StandardJMeterEngine.java | 616 + .../org/apache/jmeter/engine/TreeCloner.java | 103 + .../jmeter/engine/TreeClonerNoTimer.java | 54 + .../apache/jmeter/engine/TurnElementsOn.java | 53 + .../engine/event/LoopIterationEvent.java | 55 + .../engine/event/LoopIterationListener.java | 31 + .../apache/jmeter/engine/package-info.java | 23 + .../engine/util/AbstractTransformer.java | 49 + .../jmeter/engine/util/CompoundVariable.java | 225 + .../util/ConfigMergabilityIndicator.java | 37 + .../jmeter/engine/util/FunctionParser.java | 246 + .../jmeter/engine/util/NoConfigMerge.java | 32 + .../jmeter/engine/util/NoThreadClone.java | 32 + .../util/ReplaceFunctionsWithStrings.java | 95 + .../util/ReplaceStringWithFunctions.java | 47 + .../jmeter/engine/util/SimpleVariable.java | 69 + .../engine/util/UndoVariableReplacement.java | 48 + .../jmeter/engine/util/ValueReplacer.java | 142 + .../jmeter/engine/util/ValueTransformer.java | 53 + .../examples/sampler/ExampleSampler.java | 125 + .../sampler/gui/ExampleSamplerGui.java | 131 + .../examples/testbeans/example1/Example1.java | 61 + .../examples/testbeans/example2/Example2.java | 63 + .../testbeans/example2/Example2BeanInfo.java | 28 + .../examples/testbeans/example3/Example3.java | 156 + .../testbeans/example3/Example3BeanInfo.java | 55 + .../IllegalUserActionException.java | 42 + .../jmeter/extractor/BSFPostProcessor.java | 48 + .../extractor/BSFPostProcessorBeanInfo.java | 29 + .../extractor/BeanShellPostProcessor.java | 68 + .../BeanShellPostProcessorBeanInfo.java | 29 + .../jmeter/extractor/DebugPostProcessor.java | 154 + .../extractor/DebugPostProcessorBeanInfo.java | 57 + .../jmeter/extractor/JSR223PostProcessor.java | 50 + .../JSR223PostProcessorBeanInfo.java | 29 + .../jmeter/extractor/RegexExtractor.java | 468 + .../jmeter/extractor/XPathExtractor.java | 347 + .../extractor/gui/RegexExtractorGui.java | 244 + .../extractor/gui/XPathExtractorGui.java | 171 + .../jmeter/functions/AbstractFunction.java | 144 + .../jmeter/functions/AbstractHostIPName.java | 83 + .../apache/jmeter/functions/BeanShell.java | 154 + .../org/apache/jmeter/functions/CSVRead.java | 157 + .../apache/jmeter/functions/CharFunction.java | 90 + .../apache/jmeter/functions/EscapeHtml.java | 92 + .../apache/jmeter/functions/EvalFunction.java | 86 + .../jmeter/functions/EvalVarFunction.java | 97 + .../jmeter/functions/FileRowColContainer.java | 191 + .../apache/jmeter/functions/FileToString.java | 142 + .../apache/jmeter/functions/FileWrapper.java | 209 + .../org/apache/jmeter/functions/Function.java | 70 + .../org/apache/jmeter/functions/IntSum.java | 103 + .../functions/InvalidVariableException.java | 30 + .../jmeter/functions/IterationCounter.java | 120 + .../apache/jmeter/functions/JavaScript.java | 124 + .../jmeter/functions/Jexl2Function.java | 138 + .../apache/jmeter/functions/JexlFunction.java | 132 + .../apache/jmeter/functions/LogFunction.java | 181 + .../apache/jmeter/functions/LogFunction2.java | 117 + .../org/apache/jmeter/functions/LongSum.java | 102 + .../apache/jmeter/functions/MachineIP.java | 44 + .../apache/jmeter/functions/MachineName.java | 44 + .../org/apache/jmeter/functions/Property.java | 107 + .../apache/jmeter/functions/Property2.java | 105 + .../org/apache/jmeter/functions/Random.java | 108 + .../apache/jmeter/functions/RandomString.java | 130 + .../jmeter/functions/RegexFunction.java | 287 + .../apache/jmeter/functions/SamplerName.java | 87 + .../apache/jmeter/functions/SetProperty.java | 104 + .../jmeter/functions/SplitFunction.java | 129 + .../jmeter/functions/StringFromFile.java | 337 + .../apache/jmeter/functions/TestPlanName.java | 71 + .../apache/jmeter/functions/ThreadNumber.java | 61 + .../apache/jmeter/functions/TimeFunction.java | 130 + .../org/apache/jmeter/functions/UnEscape.java | 82 + .../apache/jmeter/functions/UnEscapeHtml.java | 88 + .../org/apache/jmeter/functions/Variable.java | 89 + .../org/apache/jmeter/functions/XPath.java | 125 + .../jmeter/functions/XPathFileContainer.java | 134 + .../apache/jmeter/functions/XPathWrapper.java | 133 + .../jmeter/functions/gui/FunctionHelper.java | 148 + .../functions/util/ArgumentDecoder.java | 41 + .../functions/util/ArgumentEncoder.java | 41 + .../gui/AbstractJMeterGuiComponent.java | 337 + .../gui/AbstractScopedJMeterGuiComponent.java | 127 + .../org/apache/jmeter/gui/CommentPanel.java | 74 + .../src/org/apache/jmeter/gui/GUIFactory.java | 174 + .../src/org/apache/jmeter/gui/GuiPackage.java | 774 + .../apache/jmeter/gui/JMeterFileFilter.java | 113 + .../apache/jmeter/gui/JMeterGUIComponent.java | 189 + .../org/apache/jmeter/gui/LoggerPanel.java | 103 + .../src/org/apache/jmeter/gui/MainFrame.java | 779 + .../src/org/apache/jmeter/gui/NamePanel.java | 134 + .../org/apache/jmeter/gui/OnErrorPanel.java | 105 + .../apache/jmeter/gui/ReportGuiPackage.java | 617 + .../apache/jmeter/gui/ReportMainFrame.java | 453 + .../apache/jmeter/gui/SavePropertyDialog.java | 154 + .../src/org/apache/jmeter/gui/Searchable.java | 33 + .../org/apache/jmeter/gui/ServerPanel.java | 182 + .../src/org/apache/jmeter/gui/Stoppable.java | 30 + .../apache/jmeter/gui/UnsharedComponent.java | 28 + .../jmeter/gui/action/AboutCommand.java | 122 + .../jmeter/gui/action/AbstractAction.java | 60 + .../apache/jmeter/gui/action/ActionNames.java | 99 + .../jmeter/gui/action/ActionRouter.java | 307 + .../apache/jmeter/gui/action/AddParent.java | 81 + .../apache/jmeter/gui/action/AddToTree.java | 90 + .../org/apache/jmeter/gui/action/Analyze.java | 57 + .../jmeter/gui/action/ChangeLanguage.java | 71 + .../jmeter/gui/action/ChangeParent.java | 89 + .../apache/jmeter/gui/action/CheckDirty.java | 160 + .../org/apache/jmeter/gui/action/Clear.java | 82 + .../org/apache/jmeter/gui/action/Close.java | 106 + .../jmeter/gui/action/CollapseExpand.java | 77 + .../org/apache/jmeter/gui/action/Command.java | 30 + .../org/apache/jmeter/gui/action/Copy.java | 120 + .../gui/action/CreateFunctionDialog.java | 52 + .../src/org/apache/jmeter/gui/action/Cut.java | 60 + .../apache/jmeter/gui/action/DragNDrop.java | 104 + .../apache/jmeter/gui/action/Duplicate.java | 68 + .../apache/jmeter/gui/action/EditCommand.java | 62 + .../jmeter/gui/action/EnableComponent.java | 85 + .../apache/jmeter/gui/action/ExitCommand.java | 78 + .../org/apache/jmeter/gui/action/Help.java | 110 + .../apache/jmeter/gui/action/KeyStrokes.java | 83 + .../org/apache/jmeter/gui/action/Load.java | 211 + .../jmeter/gui/action/LoadDraggedFile.java | 40 + .../jmeter/gui/action/LoadRecentProject.java | 258 + .../gui/action/LoggerPanelEnableDisable.java | 80 + .../jmeter/gui/action/LookAndFeelCommand.java | 155 + .../org/apache/jmeter/gui/action/Paste.java | 95 + .../jmeter/gui/action/RawTextSearcher.java | 80 + .../jmeter/gui/action/RegexpSearcher.java | 71 + .../apache/jmeter/gui/action/RemoteStart.java | 210 + .../org/apache/jmeter/gui/action/Remove.java | 95 + .../jmeter/gui/action/ResetSearchCommand.java | 68 + .../jmeter/gui/action/RevertProject.java | 75 + .../jmeter/gui/action/SSLManagerCommand.java | 136 + .../org/apache/jmeter/gui/action/Save.java | 169 + .../jmeter/gui/action/SaveGraphics.java | 128 + .../jmeter/gui/action/SearchTreeCommand.java | 54 + .../jmeter/gui/action/SearchTreeDialog.java | 197 + .../apache/jmeter/gui/action/Searcher.java | 34 + .../org/apache/jmeter/gui/action/Start.java | 133 + .../jmeter/gui/action/StopStoppables.java | 75 + .../org/apache/jmeter/gui/action/ToolBar.java | 70 + .../org/apache/jmeter/gui/action/What.java | 76 + .../jmeter/gui/tree/JMeterCellRenderer.java | 72 + .../jmeter/gui/tree/JMeterTreeListener.java | 326 + .../jmeter/gui/tree/JMeterTreeModel.java | 243 + .../jmeter/gui/tree/JMeterTreeNode.java | 200 + .../apache/jmeter/gui/tree/NamedTreeNode.java | 26 + .../apache/jmeter/gui/util/ButtonPanel.java | 123 + .../jmeter/gui/util/DirectoryDialoger.java | 63 + .../jmeter/gui/util/DirectoryPanel.java | 149 + .../apache/jmeter/gui/util/FileDialoger.java | 151 + .../apache/jmeter/gui/util/FileListPanel.java | 218 + .../org/apache/jmeter/gui/util/FilePanel.java | 165 + .../jmeter/gui/util/FocusRequester.java | 50 + .../gui/util/HeaderAsPropertyRenderer.java | 73 + .../jmeter/gui/util/HorizontalPanel.java | 75 + .../jmeter/gui/util/IconToolbarBean.java | 104 + .../apache/jmeter/gui/util/JDateField.java | 201 + .../jmeter/gui/util/JLabeledRadioI18N.java | 175 + .../apache/jmeter/gui/util/JMeterColor.java | 39 + .../apache/jmeter/gui/util/JMeterMenuBar.java | 748 + .../apache/jmeter/gui/util/JMeterToolBar.java | 206 + .../apache/jmeter/gui/util/MenuFactory.java | 700 + .../org/apache/jmeter/gui/util/MenuInfo.java | 56 + .../gui/util/NumberFieldErrorListener.java | 58 + .../jmeter/gui/util/PowerTableModel.java | 282 + .../jmeter/gui/util/ReportFileDialoger.java | 141 + .../jmeter/gui/util/ReportFilePanel.java | 143 + .../apache/jmeter/gui/util/ReportMenuBar.java | 488 + .../jmeter/gui/util/ReportMenuFactory.java | 342 + .../jmeter/gui/util/TextAreaCellRenderer.java | 51 + .../gui/util/TextAreaTableCellEditor.java | 322 + .../jmeter/gui/util/TextBoxDialoger.java | 233 + .../apache/jmeter/gui/util/VerticalPanel.java | 78 + .../jmeter/modifiers/BSFPreProcessor.java | 49 + .../modifiers/BSFPreProcessorBeanInfo.java | 29 + .../modifiers/BeanShellPreProcessor.java | 63 + .../BeanShellPreProcessorBeanInfo.java | 29 + .../jmeter/modifiers/CounterConfig.java | 242 + .../jmeter/modifiers/JSR223PreProcessor.java | 50 + .../modifiers/JSR223PreProcessorBeanInfo.java | 29 + .../jmeter/modifiers/UserParameters.java | 188 + .../modifiers/gui/CounterConfigGui.java | 151 + .../modifiers/gui/UserParametersGui.java | 404 + .../jmeter/monitor/model/Connector.java | 36 + .../jmeter/monitor/model/ConnectorImpl.java | 71 + .../org/apache/jmeter/monitor/model/Jvm.java | 24 + .../apache/jmeter/monitor/model/JvmImpl.java | 43 + .../apache/jmeter/monitor/model/Memory.java | 38 + .../jmeter/monitor/model/MemoryImpl.java | 61 + .../jmeter/monitor/model/ObjectFactory.java | 98 + .../jmeter/monitor/model/RequestInfo.java | 48 + .../jmeter/monitor/model/RequestInfoImpl.java | 91 + .../apache/jmeter/monitor/model/Status.java | 31 + .../jmeter/monitor/model/StatusImpl.java | 67 + .../jmeter/monitor/model/ThreadInfo.java | 44 + .../jmeter/monitor/model/ThreadInfoImpl.java | 81 + .../apache/jmeter/monitor/model/Worker.java | 64 + .../jmeter/monitor/model/WorkerImpl.java | 130 + .../apache/jmeter/monitor/model/Workers.java | 29 + .../jmeter/monitor/model/WorkersImpl.java | 44 + .../jmeter/monitor/parser/Constants.java | 90 + .../jmeter/monitor/parser/MonitorHandler.java | 364 + .../apache/jmeter/monitor/parser/Parser.java | 28 + .../jmeter/monitor/parser/ParserImpl.java | 119 + .../jmeter/monitor/util/MemoryBenchmark.java | 130 + .../org/apache/jmeter/monitor/util/Stats.java | 191 + .../apache/jmeter/plugin/JMeterPlugin.java | 25 + .../apache/jmeter/plugin/PluginManager.java | 73 + .../jmeter/processor/PostProcessor.java | 32 + .../apache/jmeter/processor/PreProcessor.java | 28 + .../gui/AbstractPostProcessorGui.java | 44 + .../gui/AbstractPreProcessorGui.java | 41 + .../protocol/ftp/config/gui/FtpConfigGui.java | 241 + .../ftp/control/gui/FtpTestSamplerGui.java | 101 + .../protocol/ftp/sampler/FTPSampler.java | 317 + .../http/config/MultipartUrlConfig.java | 177 + .../http/config/gui/HttpDefaultsGui.java | 190 + .../config/gui/MultipartUrlConfigGui.java | 106 + .../http/config/gui/UrlConfigGui.java | 744 + .../protocol/http/control/AuthManager.java | 294 + .../protocol/http/control/Authorization.java | 118 + .../protocol/http/control/CacheManager.java | 380 + .../jmeter/protocol/http/control/Cookie.java | 247 + .../protocol/http/control/CookieHandler.java | 51 + .../protocol/http/control/CookieManager.java | 393 + .../http/control/HC3CookieHandler.java | 194 + .../jmeter/protocol/http/control/Header.java | 99 + .../protocol/http/control/HeaderManager.java | 283 + .../http/control/HttpMirrorControl.java | 160 + .../http/control/HttpMirrorServer.java | 171 + .../http/control/HttpMirrorThread.java | 213 + .../http/control/RecordingController.java | 26 + .../http/control/gui/AjpSamplerGui.java | 51 + .../control/gui/HttpMirrorControlGui.java | 211 + .../http/control/gui/HttpTestSampleGui.java | 249 + .../http/control/gui/RecordController.java | 39 + .../http/control/gui/SoapSamplerGui.java | 173 + .../control/gui/WebServiceSamplerGui.java | 525 + .../jmeter/protocol/http/gui/AuthPanel.java | 414 + .../protocol/http/gui/CacheManagerGui.java | 136 + .../jmeter/protocol/http/gui/CookiePanel.java | 366 + .../protocol/http/gui/HTTPArgumentsPanel.java | 138 + .../protocol/http/gui/HTTPFileArgsPanel.java | 406 + .../jmeter/protocol/http/gui/HeaderPanel.java | 350 + .../http/modifier/AnchorModifier.java | 236 + .../protocol/http/modifier/ParamMask.java | 248 + .../protocol/http/modifier/ParamModifier.java | 158 + .../http/modifier/URLRewritingModifier.java | 224 + .../UserParameterXMLContentHandler.java | 136 + .../UserParameterXMLErrorHandler.java | 51 + .../http/modifier/UserParameterXMLParser.java | 69 + .../protocol/http/modifier/UserSequence.java | 95 + .../http/modifier/gui/AnchorModifierGui.java | 59 + .../http/modifier/gui/ParamModifierGui.java | 238 + .../modifier/gui/URLRewritingModifierGui.java | 132 + .../protocol/http/parser/HTMLParseError.java | 46 + .../http/parser/HTMLParseException.java | 46 + .../protocol/http/parser/HTMLParser.java | 207 + .../http/parser/HtmlParserHTMLParser.java | 191 + .../http/parser/HtmlParsingUtils.java | 393 + .../protocol/http/parser/JTidyHTMLParser.java | 233 + .../http/parser/RegexpHTMLParser.java | 204 + .../protocol/http/parser/URLCollection.java | 129 + .../protocol/http/parser/URLString.java | 85 + .../http/proxy/AbstractSamplerCreator.java | 123 + .../jmeter/protocol/http/proxy/Daemon.java | 158 + .../http/proxy/DefaultSamplerCreator.java | 348 + .../http/proxy/FormCharSetFinder.java | 135 + .../protocol/http/proxy/HttpReplyHdr.java | 292 + .../protocol/http/proxy/HttpRequestHdr.java | 414 + .../jmeter/protocol/http/proxy/Proxy.java | 585 + .../protocol/http/proxy/ProxyControl.java | 1051 + .../protocol/http/proxy/SamplerCreator.java | 57 + .../http/proxy/SamplerCreatorFactory.java | 99 + .../http/proxy/gui/ProxyControlGui.java | 825 + .../http/sampler/AccessLogSampler.java | 357 + .../sampler/AccessLogSamplerBeanInfo.java | 99 + .../protocol/http/sampler/AjpSampler.java | 520 + .../http/sampler/HTTPAbstractImpl.java | 262 + .../protocol/http/sampler/HTTPFileImpl.java | 85 + .../protocol/http/sampler/HTTPHC3Impl.java | 1126 + .../protocol/http/sampler/HTTPHC4Impl.java | 1158 + .../http/sampler/HTTPHCAbstractImpl.java | 163 + .../protocol/http/sampler/HTTPJavaImpl.java | 657 + .../http/sampler/HTTPSampleResult.java | 219 + .../protocol/http/sampler/HTTPSampler.java | 45 + .../protocol/http/sampler/HTTPSampler2.java | 86 + .../http/sampler/HTTPSamplerBase.java | 1862 ++ .../http/sampler/HTTPSamplerBaseBeanInfo.java | 34 + .../sampler/HTTPSamplerBaseConverter.java | 75 + .../http/sampler/HTTPSamplerFactory.java | 107 + .../http/sampler/HTTPSamplerProxy.java | 92 + .../sampler/HttpClientDefaultParameters.java | 151 + .../protocol/http/sampler/PostWriter.java | 458 + .../protocol/http/sampler/PutWriter.java | 113 + .../protocol/http/sampler/SoapSampler.java | 367 + .../http/sampler/WebServiceSampler.java | 693 + .../protocol/http/util/Base64Encoder.java | 83 + .../protocol/http/util/ConversionUtils.java | 225 + .../jmeter/protocol/http/util/DOMPool.java | 86 + .../protocol/http/util/EncoderCache.java | 76 + .../util/HC4TrustAllSSLSocketFactory.java | 99 + .../protocol/http/util/HTTPArgument.java | 225 + .../protocol/http/util/HTTPConstants.java | 29 + .../http/util/HTTPConstantsInterface.java | 68 + .../protocol/http/util/HTTPFileArg.java | 203 + .../protocol/http/util/HTTPFileArgs.java | 236 + .../http/util/HTTPResultConverter.java | 128 + .../http/util/LoopbackHTTPSocket.java | 97 + .../util/LoopbackHttpClientSocketFactory.java | 95 + .../http/util/SlowHC4SSLSocketFactory.java | 41 + .../http/util/SlowHC4SocketFactory.java | 56 + .../util/SlowHttpClientSocketFactory.java | 68 + .../protocol/http/util/WSDLException.java | 46 + .../jmeter/protocol/http/util/WSDLHelper.java | 427 + .../protocol/http/util/accesslog/Filter.java | 106 + .../http/util/accesslog/Generator.java | 136 + .../http/util/accesslog/LogFilter.java | 426 + .../http/util/accesslog/LogParser.java | 76 + .../protocol/http/util/accesslog/NVPair.java | 84 + .../accesslog/OrderPreservingLogParser.java | 46 + .../http/util/accesslog/SessionFilter.java | 215 + .../util/accesslog/SharedTCLogParser.java | 114 + .../util/accesslog/StandardGenerator.java | 219 + .../http/util/accesslog/TCLogParser.java | 558 + .../http/visualizers/RequestViewHTTP.java | 344 + .../protocol/java/config/JavaConfig.java | 120 + .../java/config/gui/JavaConfigGui.java | 253 + .../java/control/gui/BeanShellSamplerGui.java | 173 + .../java/control/gui/ClassFilter.java | 64 + .../java/control/gui/JUnitTestSamplerGui.java | 433 + .../java/control/gui/JavaTestSamplerGui.java | 96 + .../sampler/AbstractJavaSamplerClient.java | 82 + .../protocol/java/sampler/BSFSampler.java | 147 + .../java/sampler/BSFSamplerBeanInfo.java | 31 + .../java/sampler/BeanShellSampler.java | 185 + .../protocol/java/sampler/JSR223Sampler.java | 98 + .../java/sampler/JSR223SamplerBeanInfo.java | 28 + .../protocol/java/sampler/JUnitSampler.java | 708 + .../protocol/java/sampler/JavaSampler.java | 303 + .../java/sampler/JavaSamplerClient.java | 121 + .../java/sampler/JavaSamplerContext.java | 226 + .../jmeter/protocol/java/test/JavaTest.java | 359 + .../jmeter/protocol/java/test/SleepTest.java | 224 + .../jdbc/AbstractJDBCTestElement.java | 665 + .../jdbc/JDBCTestElementBeanInfoSupport.java | 89 + .../jdbc/config/DataSourceElement.java | 494 + .../config/DataSourceElementBeanInfo.java | 123 + .../jdbc/processor/AbstractJDBCProcessor.java | 58 + .../jdbc/processor/JDBCPostProcessor.java | 36 + .../processor/JDBCPostProcessorBeanInfo.java | 36 + .../jdbc/processor/JDBCPreProcessor.java | 36 + .../processor/JDBCPreProcessorBeanInfo.java | 36 + .../protocol/jdbc/sampler/JDBCSampler.java | 113 + .../jdbc/sampler/JDBCSamplerBeanInfo.java | 36 + .../org/apache/jmeter/protocol/jms/Utils.java | 193 + .../protocol/jms/client/ClientPool.java | 83 + .../jms/client/InitialContextFactory.java | 164 + .../jmeter/protocol/jms/client/Publisher.java | 228 + .../jms/client/ReceiveSubscriber.java | 305 + .../jms/control/gui/JMSPublisherGui.java | 333 + .../jms/control/gui/JMSSamplerGui.java | 284 + .../jms/control/gui/JMSSubscriberGui.java | 280 + .../protocol/jms/sampler/BaseJMSSampler.java | 370 + .../jms/sampler/FixedQueueExecutor.java | 102 + .../protocol/jms/sampler/JMSSampler.java | 533 + .../protocol/jms/sampler/MessageAdmin.java | 124 + .../jms/sampler/PublisherSampler.java | 479 + .../protocol/jms/sampler/QueueExecutor.java | 42 + .../jmeter/protocol/jms/sampler/Receiver.java | 145 + .../jms/sampler/SubscriberSampler.java | 485 + .../jms/sampler/TemporaryQueueExecutor.java | 59 + .../ldap/config/gui/LDAPArgument.java | 171 + .../ldap/config/gui/LDAPArguments.java | 258 + .../ldap/config/gui/LDAPArgumentsPanel.java | 381 + .../ldap/config/gui/LdapConfigGui.java | 440 + .../ldap/config/gui/LdapExtConfigGui.java | 704 + .../control/gui/LdapExtTestSamplerGui.java | 108 + .../ldap/control/gui/LdapTestSamplerGui.java | 106 + .../protocol/ldap/sampler/LDAPExtSampler.java | 1098 + .../protocol/ldap/sampler/LDAPSampler.java | 490 + .../protocol/ldap/sampler/LdapClient.java | 144 + .../protocol/ldap/sampler/LdapExtClient.java | 221 + .../protocol/mail/sampler/MailFileFolder.java | 189 + .../mail/sampler/MailFileMessage.java | 34 + .../protocol/mail/sampler/MailFileStore.java | 59 + .../mail/sampler/MailReaderSampler.java | 581 + .../sampler/gui/MailReaderSamplerGui.java | 317 + .../protocol/smtp/sampler/SmtpSampler.java | 393 + .../sampler/gui/SecuritySettingsPanel.java | 413 + .../protocol/smtp/sampler/gui/SmtpPanel.java | 1103 + .../smtp/sampler/gui/SmtpSamplerGui.java | 186 + .../LocalTrustStoreSSLSocketFactory.java | 134 + .../sampler/protocol/SendMailCommand.java | 774 + .../SynchronousTransportListener.java | 98 + .../protocol/TrustAllSSLSocketFactory.java | 142 + .../sampler/tools/CounterOutputStream.java | 64 + .../jmeter/protocol/system/NativeCommand.java | 105 + .../jmeter/protocol/system/StreamGobbler.java | 73 + .../jmeter/protocol/system/SystemSampler.java | 281 + .../protocol/system/gui/SystemSamplerGui.java | 217 + .../protocol/tcp/config/gui/TCPConfigGui.java | 200 + .../tcp/control/gui/TCPSamplerGui.java | 101 + .../tcp/sampler/AbstractTCPClient.java | 74 + .../tcp/sampler/BinaryTCPClientImpl.java | 141 + .../LengthPrefixedBinaryTCPClientImpl.java | 118 + .../protocol/tcp/sampler/ReadException.java | 53 + .../protocol/tcp/sampler/TCPClient.java | 94 + .../tcp/sampler/TCPClientDecorator.java | 85 + .../protocol/tcp/sampler/TCPClientImpl.java | 117 + .../protocol/tcp/sampler/TCPSampler.java | 511 + .../src/org/apache/jmeter/report/DataSet.java | 114 + .../org/apache/jmeter/report/ReportChart.java | 32 + .../org/apache/jmeter/report/ReportTable.java | 26 + .../jmeter/report/engine/ReportEngine.java | 37 + .../report/engine/StandardReportEngine.java | 74 + .../jmeter/report/engine/ValueReplacer.java | 148 + .../jmeter/report/gui/AbstractReportGui.java | 114 + .../apache/jmeter/report/gui/BarChartGui.java | 153 + .../jmeter/report/gui/LineGraphGui.java | 152 + .../jmeter/report/gui/ReportPageGui.java | 146 + .../apache/jmeter/report/gui/TableGui.java | 149 + .../report/gui/action/AbstractAction.java | 62 + .../report/gui/action/ReportActionRouter.java | 313 + .../report/gui/action/ReportAddParent.java | 82 + .../report/gui/action/ReportAddToTree.java | 82 + .../report/gui/action/ReportCheckDirty.java | 161 + .../jmeter/report/gui/action/ReportClose.java | 89 + .../jmeter/report/gui/action/ReportCopy.java | 114 + .../jmeter/report/gui/action/ReportCut.java | 59 + .../report/gui/action/ReportDragNDrop.java | 131 + .../report/gui/action/ReportEditCommand.java | 57 + .../gui/action/ReportEnableComponent.java | 73 + .../report/gui/action/ReportExitCommand.java | 87 + .../jmeter/report/gui/action/ReportHelp.java | 121 + .../jmeter/report/gui/action/ReportLoad.java | 140 + .../gui/action/ReportLookAndFeelCommand.java | 25 + .../jmeter/report/gui/action/ReportPaste.java | 69 + .../report/gui/action/ReportRemove.java | 83 + .../jmeter/report/gui/action/ReportSave.java | 131 + .../report/gui/action/ReportSaveGraphics.java | 109 + .../jmeter/report/gui/action/ReportStart.java | 105 + .../report/gui/tree/ReportCellRenderer.java | 60 + .../report/gui/tree/ReportTreeListener.java | 314 + .../report/gui/tree/ReportTreeModel.java | 182 + .../report/gui/tree/ReportTreeNode.java | 161 + .../report/writers/AbstractReportWriter.java | 99 + .../report/writers/DefaultPageSummary.java | 109 + .../report/writers/DefaultReportSummary.java | 75 + .../report/writers/HTMLReportWriter.java | 40 + .../jmeter/report/writers/PageSummary.java | 38 + .../jmeter/report/writers/ReportSummary.java | 50 + .../jmeter/report/writers/ReportWriter.java | 32 + .../writers/gui/HTMLReportWriterGui.java | 101 + .../reporters/AbstractListenerElement.java | 57 + .../apache/jmeter/reporters/FileReporter.java | 378 + .../apache/jmeter/reporters/MailerModel.java | 526 + .../reporters/MailerResultCollector.java | 54 + .../apache/jmeter/reporters/ResultAction.java | 88 + .../jmeter/reporters/ResultCollector.java | 604 + .../reporters/ResultCollectorHelper.java | 46 + .../apache/jmeter/reporters/ResultSaver.java | 284 + .../apache/jmeter/reporters/Summariser.java | 374 + .../jmeter/reporters/gui/ResultActionGui.java | 100 + .../jmeter/reporters/gui/ResultSaverGui.java | 189 + .../jmeter/reporters/gui/SummariserGui.java | 73 + .../apache/jmeter/sampler/DebugSampler.java | 142 + .../jmeter/sampler/DebugSamplerBeanInfo.java | 48 + .../org/apache/jmeter/sampler/TestAction.java | 168 + .../jmeter/sampler/gui/TestActionGui.java | 262 + .../jmeter/samplers/AbstractSampleSender.java | 51 + .../jmeter/samplers/AbstractSampler.java | 34 + .../jmeter/samplers/AsynchSampleSender.java | 159 + .../jmeter/samplers/BatchSampleSender.java | 199 + .../org/apache/jmeter/samplers/Clearable.java | 34 + .../samplers/DataStrippingSampleSender.java | 98 + .../samplers/DiskStoreSampleSender.java | 167 + .../src/org/apache/jmeter/samplers/Entry.java | 90 + .../jmeter/samplers/HoldSampleSender.java | 91 + .../apache/jmeter/samplers/Interruptible.java | 34 + .../samplers/RemoteListenerWrapper.java | 135 + .../jmeter/samplers/RemoteSampleListener.java | 65 + .../samplers/RemoteSampleListenerImpl.java | 126 + .../samplers/RemoteSampleListenerWrapper.java | 71 + .../samplers/RemoteTestListenerWrapper.java | 89 + .../apache/jmeter/samplers/Remoteable.java | 26 + .../apache/jmeter/samplers/SampleEvent.java | 182 + .../jmeter/samplers/SampleListener.java | 43 + .../apache/jmeter/samplers/SampleResult.java | 1301 ++ .../samplers/SampleSaveConfiguration.java | 830 + .../apache/jmeter/samplers/SampleSender.java | 50 + .../jmeter/samplers/SampleSenderFactory.java | 98 + .../org/apache/jmeter/samplers/Sampler.java | 35 + .../jmeter/samplers/StandardSampleSender.java | 82 + .../samplers/StatisticalSampleResult.java | 138 + .../samplers/StatisticalSampleSender.java | 223 + .../samplers/gui/AbstractSamplerGui.java | 63 + .../apache/jmeter/save/CSVSaveService.java | 1116 + .../jmeter/save/ListenerResultWrapper.java | 80 + .../apache/jmeter/save/OldSaveService.java | 530 + .../jmeter/save/SaveGraphicsService.java | 191 + .../org/apache/jmeter/save/SaveService.java | 614 + .../org/apache/jmeter/save/ScriptWrapper.java | 28 + .../jmeter/save/ScriptWrapperConverter.java | 121 + .../apache/jmeter/save/TestResultWrapper.java | 79 + .../converters/BooleanPropertyConverter.java | 61 + .../save/converters/ConversionHelp.java | 288 + .../save/converters/HashTreeConverter.java | 82 + .../converters/IntegerPropertyConverter.java | 60 + .../converters/LongPropertyConverter.java | 60 + .../converters/MultiPropertyConverter.java | 84 + .../save/converters/SampleEventConverter.java | 62 + .../converters/SampleResultConverter.java | 422 + .../SampleSaveConfigurationConverter.java | 168 + .../converters/StringPropertyConverter.java | 61 + .../save/converters/TestElementConverter.java | 129 + .../TestElementPropertyConverter.java | 144 + .../TestResultWrapperConverter.java | 107 + .../apache/jmeter/services/FileServer.java | 478 + .../src/org/apache/jmeter/swing/HtmlPane.java | 54 + .../jmeter/testbeans/BeanInfoSupport.java | 264 + .../org/apache/jmeter/testbeans/TestBean.java | 30 + .../jmeter/testbeans/TestBeanBeanInfo.java | 96 + .../jmeter/testbeans/TestBeanHelper.java | 192 + .../testbeans/gui/BooleanPropertyEditor.java | 50 + .../testbeans/gui/ComboStringEditor.java | 301 + .../testbeans/gui/FieldStringEditor.java | 132 + .../jmeter/testbeans/gui/FileEditor.java | 222 + .../gui/GenericTestBeanCustomizer.java | 747 + .../testbeans/gui/IntegerPropertyEditor.java | 44 + .../testbeans/gui/LongPropertyEditor.java | 44 + .../jmeter/testbeans/gui/PasswordEditor.java | 128 + .../testbeans/gui/SharedCustomizer.java | 30 + .../jmeter/testbeans/gui/TableEditor.java | 304 + .../jmeter/testbeans/gui/TestBeanGUI.java | 516 + .../testbeans/gui/TestBeanPropertyEditor.java | 28 + .../jmeter/testbeans/gui/TextAreaEditor.java | 112 + .../jmeter/testbeans/gui/TypeEditor.java | 35 + .../jmeter/testbeans/gui/WrapperEditor.java | 447 + .../jmeter/testelement/AbstractChart.java | 242 + .../testelement/AbstractScopedAssertion.java | 50 + .../AbstractScopedTestElement.java | 169 + .../jmeter/testelement/AbstractTable.java | 150 + .../testelement/AbstractTestElement.java | 586 + .../AbstractTestElementBeanInfo.java | 96 + .../apache/jmeter/testelement/BarChart.java | 111 + .../apache/jmeter/testelement/JTLData.java | 218 + .../apache/jmeter/testelement/LineChart.java | 155 + .../testelement/OnErrorTestElement.java | 73 + .../apache/jmeter/testelement/ReportPage.java | 100 + .../apache/jmeter/testelement/ReportPlan.java | 225 + .../org/apache/jmeter/testelement/Table.java | 39 + .../jmeter/testelement/TestCloneable.java | 26 + .../jmeter/testelement/TestElement.java | 203 + .../testelement/TestElementTraverser.java | 61 + .../jmeter/testelement/TestListener.java | 115 + .../apache/jmeter/testelement/TestPlan.java | 325 + .../jmeter/testelement/ThreadListener.java | 46 + .../testelement/VariablesCollection.java | 44 + .../apache/jmeter/testelement/WorkBench.java | 34 + .../property/AbstractProperty.java | 404 + .../testelement/property/BooleanProperty.java | 95 + .../property/CollectionProperty.java | 215 + .../testelement/property/DoubleProperty.java | 140 + .../testelement/property/FloatProperty.java | 140 + .../property/FunctionProperty.java | 127 + .../testelement/property/IntegerProperty.java | 145 + .../testelement/property/JMeterProperty.java | 94 + .../testelement/property/LongProperty.java | 141 + .../testelement/property/MapProperty.java | 173 + .../testelement/property/MultiProperty.java | 95 + .../testelement/property/NullProperty.java | 135 + .../testelement/property/NumberProperty.java | 71 + .../testelement/property/ObjectProperty.java | 106 + .../property/PropertyIterator.java | 27 + .../property/PropertyIteratorImpl.java | 47 + .../testelement/property/StringProperty.java | 104 + .../property/TestElementProperty.java | 162 + .../jmeter/threads/AbstractThreadGroup.java | 220 + .../FindTestElementsUpToRootTraverser.java | 100 + .../apache/jmeter/threads/JMeterContext.java | 208 + .../jmeter/threads/JMeterContextService.java | 146 + .../apache/jmeter/threads/JMeterThread.java | 901 + .../jmeter/threads/JMeterThreadMonitor.java | 26 + .../jmeter/threads/JMeterVariables.java | 151 + .../jmeter/threads/ListenerNotifier.java | 271 + .../jmeter/threads/PostThreadGroup.java | 28 + .../apache/jmeter/threads/SamplePackage.java | 230 + .../jmeter/threads/SetupThreadGroup.java | 33 + .../apache/jmeter/threads/TestCompiler.java | 303 + .../apache/jmeter/threads/ThreadGroup.java | 213 + .../threads/gui/AbstractThreadGroupGui.java | 182 + .../threads/gui/PostThreadGroupGui.java | 41 + .../threads/gui/SetupThreadGroupGui.java | 41 + .../jmeter/threads/gui/ThreadGroupGui.java | 277 + .../org/apache/jmeter/timers/BSFTimer.java | 56 + .../jmeter/timers/BSFTimerBeanInfo.java | 29 + .../apache/jmeter/timers/BeanShellTimer.java | 64 + .../jmeter/timers/BeanShellTimerBeanInfo.java | 29 + .../timers/ConstantThroughputTimer.java | 260 + .../ConstantThroughputTimerBeanInfo.java | 68 + .../apache/jmeter/timers/ConstantTimer.java | 109 + .../jmeter/timers/GaussianRandomTimer.java | 43 + .../org/apache/jmeter/timers/JSR223Timer.java | 59 + .../jmeter/timers/JSR223TimerBeanInfo.java | 29 + .../jmeter/timers/PoissonRandomTimer.java | 390 + .../org/apache/jmeter/timers/RandomTimer.java | 69 + .../org/apache/jmeter/timers/SyncTimer.java | 220 + .../jmeter/timers/SyncTimerBeanInfo.java | 37 + .../src/org/apache/jmeter/timers/Timer.java | 36 + .../jmeter/timers/UniformRandomTimer.java | 44 + .../timers/gui/AbstractRandomTimerGui.java | 192 + .../jmeter/timers/gui/AbstractTimerGui.java | 64 + .../jmeter/timers/gui/ConstantTimerGui.java | 131 + .../timers/gui/GaussianRandomTimerGui.java | 91 + .../timers/gui/PoissonRandomTimerGui.java | 69 + .../timers/gui/UniformRandomTimerGui.java | 92 + .../jmeter/util/BSFBeanInfoSupport.java | 46 + .../jmeter/util/BSFJavaScriptEngine.java | 241 + .../apache/jmeter/util/BSFTestElement.java | 206 + .../jmeter/util/BeanShellBeanInfoSupport.java | 63 + .../apache/jmeter/util/BeanShellClient.java | 115 + .../jmeter/util/BeanShellInterpreter.java | 225 + .../apache/jmeter/util/BeanShellServer.java | 103 + .../jmeter/util/BeanShellTestElement.java | 291 + .../src/org/apache/jmeter/util/CPSPauser.java | 58 + .../org/apache/jmeter/util/Calculator.java | 235 + .../org/apache/jmeter/util/ColorHelper.java | 64 + .../jmeter/util/CustomX509TrustManager.java | 100 + .../util/HttpSSLProtocolSocketFactory.java | 266 + .../org/apache/jmeter/util/JMeterUtils.java | 1298 ++ .../org/apache/jmeter/util/JMeterVersion.java | 106 + .../jmeter/util/JSR223BeanInfoSupport.java | 55 + .../apache/jmeter/util/JSR223TestElement.java | 188 + .../apache/jmeter/util/JsseSSLManager.java | 397 + .../apache/jmeter/util/LocaleChangeEvent.java | 45 + .../jmeter/util/LocaleChangeListener.java | 23 + .../org/apache/jmeter/util/NameUpdater.java | 190 + .../org/apache/jmeter/util/NamedObject.java | 23 + .../util/PropertiesBasedPrefixResolver.java | 99 + .../org/apache/jmeter/util/SSLManager.java | 292 + .../org/apache/jmeter/util/ScopePanel.java | 165 + .../jmeter/util/ScriptingBeanInfoSupport.java | 68 + .../apache/jmeter/util/ShutdownClient.java | 49 + .../apache/jmeter/util/SlowInputStream.java | 56 + .../apache/jmeter/util/SlowOutputStream.java | 55 + .../org/apache/jmeter/util/SlowSSLSocket.java | 354 + .../org/apache/jmeter/util/SlowSocket.java | 110 + .../apache/jmeter/util/StringUtilities.java | 53 + .../org/apache/jmeter/util/TidyException.java | 35 + .../src/org/apache/jmeter/util/XPathUtil.java | 423 + .../jmeter/util/keystore/JmeterKeyStore.java | 238 + .../jmeter/visualizers/AccumListener.java | 24 + .../visualizers/AssertionVisualizer.java | 119 + .../apache/jmeter/visualizers/AxisGraph.java | 421 + .../jmeter/visualizers/BSFListener.java | 75 + .../visualizers/BSFListenerBeanInfo.java | 29 + .../apache/jmeter/visualizers/BarGraph.java | 90 + .../jmeter/visualizers/BeanShellListener.java | 82 + .../BeanShellListenerBeanInfo.java | 29 + .../visualizers/CachingStatCalculator.java | 66 + .../visualizers/ComparisonVisualizer.java | 169 + .../jmeter/visualizers/DistributionGraph.java | 211 + .../DistributionGraphVisualizer.java | 236 + .../org/apache/jmeter/visualizers/Graph.java | 255 + .../jmeter/visualizers/GraphListener.java | 25 + .../jmeter/visualizers/GraphVisualizer.java | 456 + .../jmeter/visualizers/ImageVisualizer.java | 28 + .../jmeter/visualizers/JSR223Listener.java | 71 + .../visualizers/JSR223ListenerBeanInfo.java | 29 + .../apache/jmeter/visualizers/LineGraph.java | 261 + .../jmeter/visualizers/MailerVisualizer.java | 449 + .../jmeter/visualizers/ModelListener.java | 37 + .../jmeter/visualizers/MonitorAccumModel.java | 240 + .../jmeter/visualizers/MonitorGraph.java | 204 + .../visualizers/MonitorGuiListener.java | 23 + .../visualizers/MonitorHealthPanel.java | 144 + .../visualizers/MonitorHealthVisualizer.java | 188 + .../jmeter/visualizers/MonitorListener.java | 21 + .../jmeter/visualizers/MonitorModel.java | 127 + .../visualizers/MonitorPerformancePanel.java | 302 + .../jmeter/visualizers/MonitorStats.java | 167 + .../jmeter/visualizers/MonitorTabPane.java | 32 + .../apache/jmeter/visualizers/Printable.java | 30 + .../visualizers/PropertyControlGui.java | 252 + .../jmeter/visualizers/RenderAsHTML.java | 145 + .../visualizers/RenderAsHTMLWithEmbedded.java | 41 + .../jmeter/visualizers/RenderAsJSON.java | 117 + .../jmeter/visualizers/RenderAsRegexp.java | 239 + .../jmeter/visualizers/RenderAsText.java | 46 + .../jmeter/visualizers/RenderAsXML.java | 234 + .../jmeter/visualizers/RequestPanel.java | 121 + .../jmeter/visualizers/RequestView.java | 60 + .../jmeter/visualizers/RequestViewRaw.java | 109 + .../jmeter/visualizers/ResultRenderer.java | 61 + .../jmeter/visualizers/RunningSample.java | 361 + .../org/apache/jmeter/visualizers/Sample.java | 216 + .../jmeter/visualizers/SamplerResultTab.java | 544 + .../visualizers/SamplingStatCalculator.java | 287 + .../visualizers/SearchTextExtension.java | 284 + .../jmeter/visualizers/ServerPanel.java | 202 + .../jmeter/visualizers/SimpleDataWriter.java | 75 + .../apache/jmeter/visualizers/Spline3.java | 424 + .../jmeter/visualizers/SplineModel.java | 135 + .../jmeter/visualizers/SplineVisualizer.java | 337 + .../visualizers/StatGraphProperties.java | 58 + .../visualizers/StatGraphVisualizer.java | 868 + .../jmeter/visualizers/StatVisualizer.java | 362 + .../jmeter/visualizers/SummaryReport.java | 266 + .../jmeter/visualizers/TableSample.java | 132 + .../jmeter/visualizers/TableVisualizer.java | 329 + .../jmeter/visualizers/TreeNodeRenderer.java | 69 + .../ViewResultsFullVisualizer.java | 408 + .../apache/jmeter/visualizers/Visualizer.java | 45 + .../XMLDefaultMutableTreeNode.java | 212 + .../visualizers/gui/AbstractListenerGui.java | 64 + .../visualizers/gui/AbstractVisualizer.java | 354 + .../org/apache/jorphan/collections/Data.java | 691 + .../apache/jorphan/collections/HashTree.java | 1073 + .../collections/HashTreeTraverser.java | 76 + .../jorphan/collections/ListedHashTree.java | 270 + .../jorphan/collections/SearchByClass.java | 111 + .../jorphan/collections/SortedHashTree.java | 109 + .../jorphan/gui/AbstractTreeTableModel.java | 221 + .../org/apache/jorphan/gui/ComponentUtil.java | 95 + .../jorphan/gui/DefaultTreeTableModel.java | 60 + .../src/org/apache/jorphan/gui/GuiUtils.java | 67 + .../apache/jorphan/gui/JLabeledChoice.java | 303 + .../org/apache/jorphan/gui/JLabeledField.java | 39 + .../jorphan/gui/JLabeledPasswordField.java | 49 + .../apache/jorphan/gui/JLabeledTextArea.java | 261 + .../apache/jorphan/gui/JLabeledTextField.java | 243 + .../org/apache/jorphan/gui/JTreeTable.java | 67 + .../apache/jorphan/gui/NumberRenderer.java | 51 + .../apache/jorphan/gui/ObjectTableModel.java | 306 + .../org/apache/jorphan/gui/RateRenderer.java | 65 + .../org/apache/jorphan/gui/RendererUtils.java | 41 + .../jorphan/gui/RightAlignRenderer.java | 34 + .../apache/jorphan/gui/TreeTableModel.java | 56 + .../jorphan/gui/layout/VerticalLayout.java | 239 + .../src/org/apache/jorphan/io/TextFile.java | 207 + .../jorphan/logging/LoggingManager.java | 361 + .../apache/jorphan/math/NumberComparator.java | 46 + .../apache/jorphan/math/StatCalculator.java | 271 + .../jorphan/math/StatCalculatorInteger.java | 53 + .../jorphan/math/StatCalculatorLong.java | 58 + .../apache/jorphan/reflect/ClassFinder.java | 566 + .../apache/jorphan/reflect/ClassTools.java | 106 + .../org/apache/jorphan/reflect/Functor.java | 496 + .../apache/jorphan/test/UnitTestManager.java | 40 + .../org/apache/jorphan/util/Converter.java | 383 + .../org/apache/jorphan/util/HeapDumper.java | 208 + .../org/apache/jorphan/util/JMeterError.java | 47 + .../apache/jorphan/util/JMeterException.java | 46 + .../jorphan/util/JMeterStopTestException.java | 37 + .../util/JMeterStopTestNowException.java | 37 + .../util/JMeterStopThreadException.java | 37 + .../org/apache/jorphan/util/JOrphanUtils.java | 496 + .../org/apache/jorphan/util/XMLBuffer.java | 135 + .../AccessLogSamplerResources.properties | 31 + .../AccessLogSamplerResources_es.properties | 32 + .../AccessLogSamplerResources_fr.properties | 32 + ...AccessLogSamplerResources_pt_BR.properties | 31 + .../AccessLogSamplerResources_tr.properties | 32 + ...AccessLogSamplerResources_zh_TW.properties | 32 + .../sampler/BSFSamplerResources.properties | 29 + .../sampler/BSFSamplerResources_fr.properties | 29 + .../sampler/JSR223SamplerResources.properties | 28 + .../JSR223SamplerResources_fr.properties | 29 + .../DataSourceElementResources.properties | 46 + .../DataSourceElementResources_es.properties | 45 + .../DataSourceElementResources_fr.properties | 47 + ...ataSourceElementResources_pt_BR.properties | 45 + .../DataSourceElementResources_tr.properties | 45 + ...ataSourceElementResources_zh_TW.properties | 45 + .../JDBCPostProcessorResources.properties | 33 + .../JDBCPostProcessorResources_fr.properties | 33 + .../JDBCPreProcessorResources.properties | 33 + .../JDBCPreProcessorResources_fr.properties | 33 + .../sampler/JDBCSamplerResources.properties | 33 + .../JDBCSamplerResources_es.properties | 30 + .../JDBCSamplerResources_fr.properties | 33 + .../JDBCSamplerResources_pt_BR.properties | 30 + .../JDBCSamplerResources_tr.properties | 29 + .../JDBCSamplerResources_zh_TW.properties | 23 + .../protocol/mail/META-INF/javamail.providers | 16 + ApacheJmeter/src/test/AfterAnnotatedTest.java | 39 + .../src/test/BeforeAnnotatedTest.java | 39 + ApacheJmeter/src/test/DummyAnnotatedTest.java | 109 + .../src/test/Junit4AnnotationsTest.java | 55 + ApacheJmeter/src/test/RerunTest.java | 36 + ApacheJmeter/src/test/SetupTestError.java | 41 + ApacheJmeter/src/test/SetupTestFail.java | 41 + ApacheJmeter/src/test/TearDownTestFail.java | 36 + ApacheJmeter/src/woolfel/DummyTestCase.java | 99 + ApacheJmeter/src/woolfel/SubDummyTest.java | 40 + ApacheJmeter/src/woolfel/SubDummyTest2.java | 41 + ApacheJmeter/xdocs/building.xml | 75 + ApacheJmeter/xdocs/changes.xml | 137 + ApacheJmeter/xdocs/changes_history.xml | 3287 +++ ApacheJmeter/xdocs/css/style.css | 39 + .../xdocs/demos/AssertionTestPlan.jmx | 128 + .../xdocs/demos/AuthManagerTestPlan.jmx | 151 + .../xdocs/demos/BeanShellAssertion.bsh | 53 + ApacheJmeter/xdocs/demos/ForEachTest2.jmx | 322 + .../xdocs/demos/HeaderManagerTestPlan.jmx | 95 + .../xdocs/demos/InterleaveTestPlan.jmx | 160 + .../xdocs/demos/InterleaveTestPlan2.jmx | 234 + .../xdocs/demos/JDBC-Pre-Post-Processor.jmx | 445 + ApacheJmeter/xdocs/demos/JMSPointToPoint.jmx | 103 + ApacheJmeter/xdocs/demos/LoopTestPlan.jmx | 124 + ApacheJmeter/xdocs/demos/OnceOnlyTestPlan.jmx | 132 + .../xdocs/demos/ProxyServerTestPlan.jmx | 27 + ApacheJmeter/xdocs/demos/SimpleTestPlan.jmx | 166 + .../xdocs/demos/URLRewritingExample.jmx | 142 + ApacheJmeter/xdocs/demos/forEachTestPlan.jmx | 123 + ApacheJmeter/xdocs/download_jmeter.cgi | 7 + ApacheJmeter/xdocs/download_jmeter.xml | 152 + ApacheJmeter/xdocs/extending.xml | 152 + .../extending/JMeter Extension Scenario.xml | 202 + ApacheJmeter/xdocs/extending/index.xml | 861 + .../xdocs/extending/jmeter_tutorial_mike.sxw | Bin 0 -> 58860 bytes .../xdocs/extending/notes_on_extending.txt | 107 + ApacheJmeter/xdocs/index.xml | 110 + ApacheJmeter/xdocs/issues.xml | 92 + ApacheJmeter/xdocs/jmeter_irc.xml | 31 + ApacheJmeter/xdocs/localising/index.xml | 156 + ApacheJmeter/xdocs/mail.xml | 192 + ApacheJmeter/xdocs/mail2.xml | 169 + ApacheJmeter/xdocs/nightly.xml | 84 + ApacheJmeter/xdocs/overview.html | 24 + .../presentation/jmeter_presentation.sxi | Bin 0 -> 210385 bytes .../jmeter_presentation_part2.sxi | Bin 0 -> 10574 bytes .../xdocs/stylesheets/printable_project.xml | 26 + ApacheJmeter/xdocs/stylesheets/project.xml | 66 + ApacheJmeter/xdocs/stylesheets/site.vsl | 535 + ApacheJmeter/xdocs/stylesheets/site.xsl | 272 + .../xdocs/stylesheets/site_printable.vsl | 549 + ApacheJmeter/xdocs/svnindex.xml | 73 + .../xdocs/usermanual/best-practices.xml | 317 + ApacheJmeter/xdocs/usermanual/boss.xml | 217 + .../usermanual/build-adv-web-test-plan.xml | 73 + .../xdocs/usermanual/build-db-test-plan.xml | 186 + .../xdocs/usermanual/build-ftp-test-plan.xml | 196 + .../build-jms-point-to-point-test-plan.xml | 215 + .../usermanual/build-jms-topic-test-plan.xml | 199 + .../xdocs/usermanual/build-ldap-test-plan.xml | 152 + .../usermanual/build-ldapext-test-plan.xml | 432 + .../usermanual/build-monitor-test-plan.xml | 162 + .../xdocs/usermanual/build-test-plan.xml | 152 + .../xdocs/usermanual/build-web-test-plan.xml | 232 + .../xdocs/usermanual/build-ws-test-plan.xml | 184 + .../xdocs/usermanual/component_reference.xml | 5499 +++++ ApacheJmeter/xdocs/usermanual/functions.xml | 1226 ++ ApacheJmeter/xdocs/usermanual/get-started.xml | 548 + ApacheJmeter/xdocs/usermanual/glossary.xml | 104 + .../xdocs/usermanual/hints_and_tips.xml | 99 + .../include_controller_tutorial.sxw | Bin 0 -> 10186 bytes ApacheJmeter/xdocs/usermanual/index.xml | 201 + ApacheJmeter/xdocs/usermanual/intro.xml | 60 + .../xdocs/usermanual/ldapanswer_xml.xml | 211 + .../xdocs/usermanual/ldapops_tutor.xml | 116 + ApacheJmeter/xdocs/usermanual/listeners.xml | 485 + .../xdocs/usermanual/regular_expressions.xml | 247 + ApacheJmeter/xdocs/usermanual/remote-test.xml | 290 + ApacheJmeter/xdocs/usermanual/test_plan.xml | 505 + ApacheJmeter/xdocs/velocity.properties | 18 + 1310 files changed, 275071 insertions(+) create mode 100644 ApacheJmeter/.classpath create mode 100644 ApacheJmeter/.project create mode 100644 ApacheJmeter/.settings/org.eclipse.jdt.core.prefs create mode 100644 ApacheJmeter/LICENSE create mode 100644 ApacheJmeter/NOTICE create mode 100644 ApacheJmeter/README create mode 100644 ApacheJmeter/STATUS create mode 100644 ApacheJmeter/build.properties create mode 100644 ApacheJmeter/build.xml create mode 100644 ApacheJmeter/checkstyle.xml create mode 100644 ApacheJmeter/doap_JMeter.rdf create mode 100644 ApacheJmeter/docs/building.html create mode 100644 ApacheJmeter/docs/changes.html create mode 100644 ApacheJmeter/docs/changes_history.html create mode 100644 ApacheJmeter/docs/css/style.css create mode 100644 ApacheJmeter/docs/demos/AssertionTestPlan.jmx create mode 100644 ApacheJmeter/docs/demos/AuthManagerTestPlan.jmx create mode 100644 ApacheJmeter/docs/demos/BeanShellAssertion.bsh create mode 100644 ApacheJmeter/docs/demos/ForEachTest2.jmx create mode 100644 ApacheJmeter/docs/demos/HeaderManagerTestPlan.jmx create mode 100644 ApacheJmeter/docs/demos/InterleaveTestPlan.jmx create mode 100644 ApacheJmeter/docs/demos/InterleaveTestPlan2.jmx create mode 100644 ApacheJmeter/docs/demos/JDBC-Pre-Post-Processor.jmx create mode 100644 ApacheJmeter/docs/demos/JMSPointToPoint.jmx create mode 100644 ApacheJmeter/docs/demos/LoopTestPlan.jmx create mode 100644 ApacheJmeter/docs/demos/OnceOnlyTestPlan.jmx create mode 100644 ApacheJmeter/docs/demos/ProxyServerTestPlan.jmx create mode 100644 ApacheJmeter/docs/demos/SimpleTestPlan.jmx create mode 100644 ApacheJmeter/docs/demos/URLRewritingExample.jmx create mode 100644 ApacheJmeter/docs/demos/forEachTestPlan.jmx create mode 100644 ApacheJmeter/docs/download_jmeter.cgi create mode 100644 ApacheJmeter/docs/download_jmeter.html create mode 100644 ApacheJmeter/docs/index.html create mode 100644 ApacheJmeter/docs/issues.html create mode 100644 ApacheJmeter/docs/jmeter_irc.html create mode 100644 ApacheJmeter/docs/localising/index.html create mode 100644 ApacheJmeter/docs/mail.html create mode 100644 ApacheJmeter/docs/mail2.html create mode 100644 ApacheJmeter/docs/nightly.html create mode 100644 ApacheJmeter/docs/svnindex.html create mode 100644 ApacheJmeter/docs/usermanual/best-practices.html create mode 100644 ApacheJmeter/docs/usermanual/boss.html create mode 100644 ApacheJmeter/docs/usermanual/build-adv-web-test-plan.html create mode 100644 ApacheJmeter/docs/usermanual/build-db-test-plan.html create mode 100644 ApacheJmeter/docs/usermanual/build-ftp-test-plan.html create mode 100644 ApacheJmeter/docs/usermanual/build-jms-point-to-point-test-plan.html create mode 100644 ApacheJmeter/docs/usermanual/build-jms-topic-test-plan.html create mode 100644 ApacheJmeter/docs/usermanual/build-ldap-test-plan.html create mode 100644 ApacheJmeter/docs/usermanual/build-ldapext-test-plan.html create mode 100644 ApacheJmeter/docs/usermanual/build-monitor-test-plan.html create mode 100644 ApacheJmeter/docs/usermanual/build-test-plan.html create mode 100644 ApacheJmeter/docs/usermanual/build-web-test-plan.html create mode 100644 ApacheJmeter/docs/usermanual/build-ws-test-plan.html create mode 100644 ApacheJmeter/docs/usermanual/component_reference.html create mode 100644 ApacheJmeter/docs/usermanual/functions.html create mode 100644 ApacheJmeter/docs/usermanual/get-started.html create mode 100644 ApacheJmeter/docs/usermanual/glossary.html create mode 100644 ApacheJmeter/docs/usermanual/hints_and_tips.html create mode 100644 ApacheJmeter/docs/usermanual/index.html create mode 100644 ApacheJmeter/docs/usermanual/intro.html create mode 100644 ApacheJmeter/docs/usermanual/ldapanswer_xml.html create mode 100644 ApacheJmeter/docs/usermanual/ldapops_tutor.html create mode 100644 ApacheJmeter/docs/usermanual/listeners.html create mode 100644 ApacheJmeter/docs/usermanual/regular_expressions.html create mode 100644 ApacheJmeter/docs/usermanual/remote-test.html create mode 100644 ApacheJmeter/docs/usermanual/test_plan.html create mode 100644 ApacheJmeter/eclipse.classpath create mode 100644 ApacheJmeter/eclipse.readme create mode 100644 ApacheJmeter/extras/ConvertHTTPSampler.txt create mode 100644 ApacheJmeter/extras/Test.jmx create mode 100644 ApacheJmeter/extras/addons.txt create mode 100644 ApacheJmeter/extras/addons.xml create mode 100644 ApacheJmeter/extras/build.xml create mode 100644 ApacheJmeter/extras/convertjmx.fdl create mode 100644 ApacheJmeter/extras/execcode.bsh create mode 100644 ApacheJmeter/extras/jmeter-results-detail-report.xsl create mode 100644 ApacheJmeter/extras/jmeter-results-detail-report_21.xsl create mode 100644 ApacheJmeter/extras/jmeter-results-report.xsl create mode 100644 ApacheJmeter/extras/jmeter-results-report_21.xsl create mode 100644 ApacheJmeter/extras/jmeter.fb create mode 100644 ApacheJmeter/extras/printvars.bsh create mode 100644 ApacheJmeter/extras/proxycert.cmd create mode 100644 ApacheJmeter/extras/proxycert.sh create mode 100644 ApacheJmeter/extras/remote.bsh create mode 100644 ApacheJmeter/extras/schematic.cmd create mode 100644 ApacheJmeter/extras/schematic.xml create mode 100644 ApacheJmeter/extras/schematic.xsl create mode 100644 ApacheJmeter/extras/startup.bsh create mode 100644 ApacheJmeter/fb-csv.xsl create mode 100644 ApacheJmeter/fb-excludes.xml create mode 100644 ApacheJmeter/lib/aareadme.txt create mode 100644 ApacheJmeter/lib/opt/README.txt create mode 100644 ApacheJmeter/org/apache/commons/cli/avalon/ClutilTestCase.java create mode 100644 ApacheJmeter/org/apache/jmeter/assertions/MD5HexAssertionTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/assertions/ResponseAssertionTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/assertions/SizeAssertionTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/assertions/XMLSchemaAssertionTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/assertions/XPathAssertionTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/config/TestCVSDataSet.java create mode 100644 ApacheJmeter/org/apache/jmeter/config/gui/TestArgumentsPanel.java create mode 100644 ApacheJmeter/org/apache/jmeter/control/TestGenericController.java create mode 100644 ApacheJmeter/org/apache/jmeter/control/TestIfController.java create mode 100644 ApacheJmeter/org/apache/jmeter/control/TestInterleaveControl.java create mode 100644 ApacheJmeter/org/apache/jmeter/control/TestLoopController.java create mode 100644 ApacheJmeter/org/apache/jmeter/control/TestOnceOnlyController.java create mode 100644 ApacheJmeter/org/apache/jmeter/control/TestRandomOrderController.java create mode 100644 ApacheJmeter/org/apache/jmeter/control/TestRunTime.java create mode 100644 ApacheJmeter/org/apache/jmeter/control/TestSwitchController.java create mode 100644 ApacheJmeter/org/apache/jmeter/control/TestThroughputController.java create mode 100644 ApacheJmeter/org/apache/jmeter/control/TestWhileController.java create mode 100644 ApacheJmeter/org/apache/jmeter/engine/TestTreeCloner.java create mode 100644 ApacheJmeter/org/apache/jmeter/engine/util/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/engine/util/TestValueReplacer.java create mode 100644 ApacheJmeter/org/apache/jmeter/extractor/TestRegexExtractor.java create mode 100644 ApacheJmeter/org/apache/jmeter/extractor/TestXPathExtractor.java create mode 100644 ApacheJmeter/org/apache/jmeter/functions/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/functions/TestFileRowColContainer.java create mode 100644 ApacheJmeter/org/apache/jmeter/functions/TestJexlFunction.java create mode 100644 ApacheJmeter/org/apache/jmeter/functions/TestRegexFunction.java create mode 100644 ApacheJmeter/org/apache/jmeter/functions/TestTimeFunction.java create mode 100644 ApacheJmeter/org/apache/jmeter/gui/action/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/gui/action/TestLoad.java create mode 100644 ApacheJmeter/org/apache/jmeter/gui/action/TestSave.java create mode 100644 ApacheJmeter/org/apache/jmeter/gui/util/TestMenuFactory.java create mode 100644 ApacheJmeter/org/apache/jmeter/junit/JMeterTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/junit/JMeterTestCase.java create mode 100644 ApacheJmeter/org/apache/jmeter/junit/stubs/TestSampler.java create mode 100644 ApacheJmeter/org/apache/jmeter/monitor/model/TestObjectFactory.java create mode 100644 ApacheJmeter/org/apache/jmeter/monitor/model/benchmark/ParseBenchmark.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/config/MultipartUrlConfigTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/config/UrlConfigTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/control/TestAuthManager.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/control/TestCacheManager.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/control/TestCookieManager.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/control/TestHTTPMirrorThread.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/control/gui/TestHttpTestSampleGui.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/modifier/TestAnchorModifier.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/modifier/TestURLRewritingModifier.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/parser/TestHTMLParser.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/parser/TestHtmlParsingUtils.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/proxy/TestHttpRequestHdr.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/proxy/TestProxyControl.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/sampler/HTTPNullSampler.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/sampler/HTTPSampler3.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/sampler/NullURLConnection.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PostWriterTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PutWriterTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplers.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplersAgainstHttpMirrorServer.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPArgument.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPFileArg.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPFileArgs.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPUtils.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/util/accesslog/TestLogFilter.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/http/util/accesslog/TestTCLogParser.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/ldap/config/gui/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImplTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImplTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecoratorTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/resources/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/samplers/NullSampler.java create mode 100644 ApacheJmeter/org/apache/jmeter/samplers/TestSampleResult.java create mode 100644 ApacheJmeter/org/apache/jmeter/samplers/TestSampleSaveConfiguration.java create mode 100644 ApacheJmeter/org/apache/jmeter/save/TestCSVSaveService.java create mode 100644 ApacheJmeter/org/apache/jmeter/save/TestSaveService.java create mode 100644 ApacheJmeter/org/apache/jmeter/services/TestFileServer.java create mode 100644 ApacheJmeter/org/apache/jmeter/testbeans/gui/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/testbeans/gui/TestBooleanPropertyEditor.java create mode 100644 ApacheJmeter/org/apache/jmeter/testbeans/gui/TestComboStringEditor.java create mode 100644 ApacheJmeter/org/apache/jmeter/testbeans/gui/TestFieldStringEditor.java create mode 100644 ApacheJmeter/org/apache/jmeter/testelement/BarChartTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/testelement/LineGraphTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/testelement/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/testelement/property/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/threads/TestJMeterContextService.java create mode 100644 ApacheJmeter/org/apache/jmeter/threads/TestTestCompiler.java create mode 100644 ApacheJmeter/org/apache/jmeter/timers/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/util/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jmeter/util/TestJMeterUtils.java create mode 100644 ApacheJmeter/org/apache/jmeter/visualizers/GenerateTreeGui.java create mode 100644 ApacheJmeter/org/apache/jmeter/visualizers/TestSamplingStatCalculator.java create mode 100644 ApacheJmeter/org/apache/jorphan/TestFunctorUsers.java create mode 100644 ApacheJmeter/org/apache/jorphan/TestXMLBuffer.java create mode 100644 ApacheJmeter/org/apache/jorphan/collections/PackageTest.java create mode 100644 ApacheJmeter/org/apache/jorphan/math/TestStatCalculator.java create mode 100644 ApacheJmeter/org/apache/jorphan/reflect/TestFunctor.java create mode 100644 ApacheJmeter/org/apache/jorphan/test/AllTests.java create mode 100644 ApacheJmeter/org/apache/jorphan/util/TestJorphanUtils.java create mode 100644 ApacheJmeter/rat-excludes.txt create mode 100644 ApacheJmeter/res/META-INF/default.license create mode 100644 ApacheJmeter/res/META-INF/default.notice create mode 100644 ApacheJmeter/res/maven/ApacheJMeter.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_components.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_config.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_core.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_ftp.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_functions.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_http.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_java.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_jdbc.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_jms.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_junit-test.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_junit.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_ldap.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_mail.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_monitors.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_native.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_parent.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_report.pom create mode 100644 ApacheJmeter/res/maven/ApacheJMeter_tcp.pom create mode 100644 ApacheJmeter/res/maven/jorphan.pom create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pl.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/assertions/CompareAssertionResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/assertions/CompareAssertionResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/assertions/JSR223AssertionResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/assertions/JSR223AssertionResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/assertions/package.html create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_de.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_es.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_pl.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_tr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_zh_TW.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/KeystoreConfigResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/KeystoreConfigResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_es.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pl.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pl.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_de.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pl.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_tr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_de.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_tr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_de.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_tr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_de.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_tr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/BSFTimerResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/BSFTimerResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_de.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_tr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_de.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_es.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_ja.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_tr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_zh_TW.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/JSR223TimerResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/JSR223TimerResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_de.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_es.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_tr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_de.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_fr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_pt_BR.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_tr.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources.properties create mode 100644 ApacheJmeter/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources_fr.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/help.txt create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/images/icon.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/images/icon_1.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/images/toolbar/icons-toolbar.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/resources/messages.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/resources/messages_de.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/resources/messages_es.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/resources/messages_fr.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/resources/messages_ja.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/resources/messages_no.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/resources/messages_pl.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/resources/messages_pt_BR.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/resources/messages_tr.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/resources/messages_zh_CN.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/resources/messages_zh_TW.properties create mode 100644 ApacheJmeter/src/core/org/apache/jmeter/visualizers/package.html create mode 100644 ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources.properties create mode 100644 ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_es.properties create mode 100644 ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_pt_BR.properties create mode 100644 ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_tr.properties create mode 100644 ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_zh_TW.properties create mode 100644 ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3Resources.properties create mode 100644 ApacheJmeter/src/functions/org/apache/jmeter/functions/package.html create mode 100644 ApacheJmeter/src/i18nedit.properties create mode 100644 ApacheJmeter/src/jorphan/org/apache/commons/cli/avalon/package.html create mode 100644 ApacheJmeter/src/org/apache/commons/cli/avalon/AbstractParserControl.java create mode 100644 ApacheJmeter/src/org/apache/commons/cli/avalon/CLArgsParser.java create mode 100644 ApacheJmeter/src/org/apache/commons/cli/avalon/CLOption.java create mode 100644 ApacheJmeter/src/org/apache/commons/cli/avalon/CLOptionDescriptor.java create mode 100644 ApacheJmeter/src/org/apache/commons/cli/avalon/CLUtil.java create mode 100644 ApacheJmeter/src/org/apache/commons/cli/avalon/ParserControl.java create mode 100644 ApacheJmeter/src/org/apache/commons/cli/avalon/Token.java create mode 100644 ApacheJmeter/src/org/apache/commons/jexl/bsf/JexlEngine.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/DynamicClassLoader.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/JMeter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/JMeterReport.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/NewDriver.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/ProxyAuthenticator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/Assertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/AssertionResult.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/BSFAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/BSFAssertionBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/BeanShellAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertionBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertionResult.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/DurationAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/HTMLAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/JSR223Assertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/JSR223AssertionBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/MD5HexAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/ResponseAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/SMIMEAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/SMIMEAssertionTestElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/SizeAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/SubstitutionElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/XMLAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/XMLSchemaAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/XPathAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/AbstractAssertionGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/AssertionGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/BeanShellAssertionGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/DurationAssertionGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/HTMLAssertionGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/MD5HexAssertionGUI.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/SMIMEAssertionGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/SizeAssertionGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLAssertionGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLConfPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLSchemaAssertionGUI.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/XPathAssertionGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/assertions/gui/XPathPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/Argument.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/Arguments.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/CSVDataSet.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/CSVDataSetBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/ConfigElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/ConfigTestElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/KeystoreConfig.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/KeystoreConfigBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/LoginConfig.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/RandomVariableConfig.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/RandomVariableConfigBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/gui/AbstractConfigGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/gui/ArgumentsPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/gui/LoginConfigGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/gui/ObsoleteGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/gui/RowDetailDialog.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/config/gui/SimpleConfigGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/Controller.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/ForeachController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/GenericController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/IfController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/IncludeController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/InterleaveControl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/LoopController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/ModuleController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/NextIsNullException.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/OnceOnlyController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/RandomController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/RandomOrderController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/ReplaceableController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/RunTime.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/SwitchController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/TestFragmentController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/ThroughputController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/TransactionController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/TransactionSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/WhileController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/AbstractControllerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/ForeachControlPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/IfControllerPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/IncludeControllerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/InterleaveControlGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/LogicControllerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/LoopControlPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/ModuleControllerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/OnceOnlyControllerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/RandomControlGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/RandomOrderControllerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/ReportGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/RunTimeGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/SwitchControllerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/TestFragmentControllerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/TestPlanGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/ThroughputControllerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/TransactionControllerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/WhileControllerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/control/gui/WorkBenchGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/ClientJMeterEngine.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/ConvertListeners.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/JMeterEngine.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/JMeterEngineException.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/PreCompiler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/RemoteJMeterEngine.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/StandardJMeterEngine.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/TreeCloner.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/TreeClonerNoTimer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/TurnElementsOn.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/event/LoopIterationEvent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/event/LoopIterationListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/package-info.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/AbstractTransformer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/CompoundVariable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/ConfigMergabilityIndicator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/FunctionParser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/NoConfigMerge.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/NoThreadClone.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/ReplaceFunctionsWithStrings.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/ReplaceStringWithFunctions.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/SimpleVariable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/UndoVariableReplacement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/ValueReplacer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/engine/util/ValueTransformer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/examples/sampler/ExampleSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/examples/sampler/gui/ExampleSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example1/Example1.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example2/Example2.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example2/Example2BeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example3/Example3.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example3/Example3BeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/exceptions/IllegalUserActionException.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/BSFPostProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/BSFPostProcessorBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/BeanShellPostProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/BeanShellPostProcessorBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/DebugPostProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/DebugPostProcessorBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/JSR223PostProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/JSR223PostProcessorBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/RegexExtractor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/XPathExtractor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/gui/RegexExtractorGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/extractor/gui/XPathExtractorGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/AbstractFunction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/AbstractHostIPName.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/BeanShell.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/CSVRead.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/CharFunction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/EscapeHtml.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/EvalFunction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/EvalVarFunction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/FileRowColContainer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/FileToString.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/FileWrapper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/Function.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/IntSum.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/InvalidVariableException.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/IterationCounter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/JavaScript.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/Jexl2Function.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/JexlFunction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/LogFunction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/LogFunction2.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/LongSum.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/MachineIP.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/MachineName.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/Property.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/Property2.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/Random.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/RandomString.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/RegexFunction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/SamplerName.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/SetProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/SplitFunction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/StringFromFile.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/TestPlanName.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/ThreadNumber.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/TimeFunction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/UnEscape.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/UnEscapeHtml.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/Variable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/XPath.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/XPathFileContainer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/XPathWrapper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/gui/FunctionHelper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/util/ArgumentDecoder.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/functions/util/ArgumentEncoder.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/AbstractJMeterGuiComponent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/AbstractScopedJMeterGuiComponent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/CommentPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/GUIFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/GuiPackage.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/JMeterFileFilter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/JMeterGUIComponent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/LoggerPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/MainFrame.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/NamePanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/OnErrorPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/ReportGuiPackage.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/ReportMainFrame.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/SavePropertyDialog.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/Searchable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/ServerPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/Stoppable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/UnsharedComponent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/AboutCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/AbstractAction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/ActionNames.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/ActionRouter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/AddParent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/AddToTree.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Analyze.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/ChangeLanguage.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/ChangeParent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/CheckDirty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Clear.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Close.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/CollapseExpand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Command.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Copy.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/CreateFunctionDialog.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Cut.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/DragNDrop.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Duplicate.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/EditCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/EnableComponent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/ExitCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Help.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/KeyStrokes.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Load.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/LoadDraggedFile.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/LoadRecentProject.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/LookAndFeelCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Paste.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/RawTextSearcher.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/RegexpSearcher.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/RemoteStart.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Remove.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/ResetSearchCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/RevertProject.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/SSLManagerCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Save.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/SaveGraphics.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/SearchTreeCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/SearchTreeDialog.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Searcher.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/Start.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/StopStoppables.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/ToolBar.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/action/What.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterCellRenderer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeModel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeNode.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/tree/NamedTreeNode.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/ButtonPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/DirectoryDialoger.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/DirectoryPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/FileDialoger.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/FileListPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/FilePanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/FocusRequester.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/HorizontalPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/IconToolbarBean.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/JDateField.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/JLabeledRadioI18N.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterColor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterMenuBar.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterToolBar.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/MenuFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/MenuInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/NumberFieldErrorListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/PowerTableModel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/ReportFileDialoger.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/ReportFilePanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/ReportMenuBar.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/ReportMenuFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/TextAreaCellRenderer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/TextAreaTableCellEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/TextBoxDialoger.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/gui/util/VerticalPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/modifiers/BSFPreProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/modifiers/BSFPreProcessorBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/modifiers/BeanShellPreProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/modifiers/BeanShellPreProcessorBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/modifiers/CounterConfig.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/modifiers/JSR223PreProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/modifiers/JSR223PreProcessorBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/modifiers/UserParameters.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/modifiers/gui/CounterConfigGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/modifiers/gui/UserParametersGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/Connector.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/ConnectorImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/Jvm.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/JvmImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/Memory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/MemoryImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/ObjectFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/RequestInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/RequestInfoImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/Status.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/StatusImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/ThreadInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/ThreadInfoImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/Worker.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/WorkerImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/Workers.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/model/WorkersImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/parser/Constants.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/parser/MonitorHandler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/parser/Parser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/parser/ParserImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/util/MemoryBenchmark.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/monitor/util/Stats.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/plugin/JMeterPlugin.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/plugin/PluginManager.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/processor/PostProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/processor/PreProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/processor/gui/AbstractPostProcessorGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/processor/gui/AbstractPreProcessorGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ftp/config/gui/FtpConfigGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ftp/control/gui/FtpTestSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ftp/sampler/FTPSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/config/MultipartUrlConfig.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/MultipartUrlConfigGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/AuthManager.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Authorization.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CacheManager.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Cookie.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CookieHandler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CookieManager.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HC3CookieHandler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Header.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HeaderManager.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorControl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorServer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorThread.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/RecordingController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/AjpSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/HttpMirrorControlGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/RecordController.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/SoapSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/WebServiceSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/AuthPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/CacheManagerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/CookiePanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HTTPArgumentsPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HTTPFileArgsPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HeaderPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/AnchorModifier.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/ParamMask.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/ParamModifier.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/URLRewritingModifier.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLContentHandler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLErrorHandler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLParser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserSequence.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/AnchorModifierGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/ParamModifierGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/URLRewritingModifierGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParseError.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParseException.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HtmlParserHTMLParser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HtmlParsingUtils.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/JTidyHTMLParser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/RegexpHTMLParser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/URLCollection.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/URLString.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/AbstractSamplerCreator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/Daemon.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/FormCharSetFinder.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/HttpReplyHdr.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/HttpRequestHdr.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/Proxy.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/ProxyControl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/gui/ProxyControlGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AccessLogSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AjpSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPAbstractImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPFileImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHC3Impl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHCAbstractImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPJavaImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSampleResult.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSampler2.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HttpClientDefaultParameters.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/PostWriter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/PutWriter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/SoapSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/WebServiceSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/Base64Encoder.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/ConversionUtils.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/DOMPool.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/EncoderCache.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HC4TrustAllSSLSocketFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPArgument.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPConstants.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPConstantsInterface.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPFileArg.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPFileArgs.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPResultConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/LoopbackHTTPSocket.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/LoopbackHttpClientSocketFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHC4SSLSocketFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHC4SocketFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHttpClientSocketFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/WSDLException.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/WSDLHelper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/Filter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/Generator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/NVPair.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/StandardGenerator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/config/JavaConfig.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/BeanShellSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/ClassFilter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/JavaTestSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/AbstractJavaSamplerClient.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BSFSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BSFSamplerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BeanShellSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JSR223Sampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JSR223SamplerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JUnitSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSamplerContext.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/test/JavaTest.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/java/test/SleepTest.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/AbstractJDBCTestElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/JDBCTestElementBeanInfoSupport.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/config/DataSourceElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/config/DataSourceElementBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/AbstractJDBCProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/sampler/JDBCSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/Utils.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/ClientPool.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/InitialContextFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/Publisher.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/ReceiveSubscriber.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSPublisherGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSSubscriberGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/BaseJMSSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/FixedQueueExecutor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/JMSSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/MessageAdmin.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/PublisherSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/QueueExecutor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/Receiver.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/SubscriberSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/TemporaryQueueExecutor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgument.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArguments.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgumentsPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LdapConfigGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LdapExtConfigGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ldap/control/gui/LdapExtTestSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ldap/control/gui/LdapTestSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LDAPExtSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LDAPSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LdapClient.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LdapExtClient.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileFolder.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileMessage.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileStore.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailReaderSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/gui/MailReaderSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/SmtpSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SecuritySettingsPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/LocalTrustStoreSSLSocketFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/SendMailCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/SynchronousTransportListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/TrustAllSSLSocketFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/tools/CounterOutputStream.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/system/NativeCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/system/StreamGobbler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/system/SystemSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/system/gui/SystemSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/tcp/config/gui/TCPConfigGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/tcp/control/gui/TCPSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/AbstractTCPClient.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/ReadException.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClient.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecorator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClientImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/DataSet.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/ReportChart.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/ReportTable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/engine/ReportEngine.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/engine/StandardReportEngine.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/engine/ValueReplacer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/AbstractReportGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/BarChartGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/LineGraphGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/ReportPageGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/TableGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/AbstractAction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportActionRouter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportAddParent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportAddToTree.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCheckDirty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportClose.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCopy.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCut.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportDragNDrop.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportEditCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportEnableComponent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportExitCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportHelp.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportLoad.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportLookAndFeelCommand.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportPaste.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportRemove.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportSave.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportSaveGraphics.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportStart.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportCellRenderer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeModel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeNode.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/writers/AbstractReportWriter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/writers/DefaultPageSummary.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/writers/DefaultReportSummary.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/writers/HTMLReportWriter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/writers/PageSummary.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/writers/ReportSummary.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/writers/ReportWriter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/report/writers/gui/HTMLReportWriterGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/AbstractListenerElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/FileReporter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/MailerModel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/MailerResultCollector.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/ResultAction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/ResultCollector.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/ResultCollectorHelper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/ResultSaver.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/Summariser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/gui/ResultActionGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/gui/ResultSaverGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/reporters/gui/SummariserGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/sampler/DebugSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/sampler/DebugSamplerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/sampler/TestAction.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/sampler/gui/TestActionGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/AbstractSampleSender.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/AbstractSampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/AsynchSampleSender.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/BatchSampleSender.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/Clearable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/DataStrippingSampleSender.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/DiskStoreSampleSender.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/Entry.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/HoldSampleSender.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/Interruptible.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/RemoteListenerWrapper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListenerWrapper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/RemoteTestListenerWrapper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/Remoteable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/SampleEvent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/SampleListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/SampleResult.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/SampleSaveConfiguration.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/SampleSender.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/SampleSenderFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/Sampler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/StandardSampleSender.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/StatisticalSampleResult.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/StatisticalSampleSender.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/samplers/gui/AbstractSamplerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/CSVSaveService.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/ListenerResultWrapper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/OldSaveService.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/SaveGraphicsService.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/SaveService.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/ScriptWrapper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/ScriptWrapperConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/TestResultWrapper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/BooleanPropertyConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/ConversionHelp.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/HashTreeConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/IntegerPropertyConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/LongPropertyConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/MultiPropertyConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/SampleEventConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/SampleResultConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/SampleSaveConfigurationConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/StringPropertyConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/TestElementConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/TestElementPropertyConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/save/converters/TestResultWrapperConverter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/services/FileServer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/swing/HtmlPane.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/BeanInfoSupport.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/TestBean.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/TestBeanBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/TestBeanHelper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/BooleanPropertyEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/ComboStringEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/FieldStringEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/FileEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/IntegerPropertyEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/LongPropertyEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/PasswordEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/SharedCustomizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TableEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TestBeanGUI.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TestBeanPropertyEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TextAreaEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TypeEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testbeans/gui/WrapperEditor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/AbstractChart.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/AbstractScopedAssertion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/AbstractScopedTestElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTestElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTestElementBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/BarChart.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/JTLData.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/LineChart.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/OnErrorTestElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/ReportPage.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/ReportPlan.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/Table.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/TestCloneable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/TestElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/TestElementTraverser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/TestListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/TestPlan.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/ThreadListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/VariablesCollection.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/WorkBench.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/AbstractProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/BooleanProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/CollectionProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/DoubleProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/FloatProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/FunctionProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/IntegerProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/JMeterProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/LongProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/MapProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/MultiProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/NullProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/NumberProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/ObjectProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/PropertyIterator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/PropertyIteratorImpl.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/StringProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/testelement/property/TestElementProperty.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/AbstractThreadGroup.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/FindTestElementsUpToRootTraverser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/JMeterContext.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/JMeterContextService.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/JMeterThread.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/JMeterThreadMonitor.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/JMeterVariables.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/ListenerNotifier.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/PostThreadGroup.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/SamplePackage.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/SetupThreadGroup.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/TestCompiler.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/ThreadGroup.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/gui/PostThreadGroupGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/gui/SetupThreadGroupGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/threads/gui/ThreadGroupGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/BSFTimer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/BSFTimerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/BeanShellTimer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/BeanShellTimerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/ConstantThroughputTimer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/ConstantThroughputTimerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/ConstantTimer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/GaussianRandomTimer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/JSR223Timer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/JSR223TimerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/PoissonRandomTimer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/RandomTimer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/SyncTimer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/SyncTimerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/Timer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/UniformRandomTimer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/gui/AbstractRandomTimerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/gui/AbstractTimerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/gui/ConstantTimerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/gui/GaussianRandomTimerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/gui/PoissonRandomTimerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/timers/gui/UniformRandomTimerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/BSFBeanInfoSupport.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/BSFJavaScriptEngine.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/BSFTestElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/BeanShellBeanInfoSupport.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/BeanShellClient.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/BeanShellInterpreter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/BeanShellServer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/BeanShellTestElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/CPSPauser.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/Calculator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/ColorHelper.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/CustomX509TrustManager.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/JMeterUtils.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/JMeterVersion.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/JSR223BeanInfoSupport.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/JSR223TestElement.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/JsseSSLManager.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/LocaleChangeEvent.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/LocaleChangeListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/NameUpdater.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/NamedObject.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/PropertiesBasedPrefixResolver.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/SSLManager.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/ScopePanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/ScriptingBeanInfoSupport.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/ShutdownClient.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/SlowInputStream.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/SlowOutputStream.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/SlowSSLSocket.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/SlowSocket.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/StringUtilities.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/TidyException.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/XPathUtil.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/util/keystore/JmeterKeyStore.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/AccumListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/AssertionVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/AxisGraph.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/BSFListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/BSFListenerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/BarGraph.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/BeanShellListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/BeanShellListenerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/CachingStatCalculator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/ComparisonVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/DistributionGraph.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/DistributionGraphVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/Graph.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/GraphListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/GraphVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/ImageVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/JSR223Listener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/JSR223ListenerBeanInfo.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/LineGraph.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/MailerVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/ModelListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorAccumModel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorGraph.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorGuiListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorHealthPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorHealthVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorListener.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorModel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorPerformancePanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorStats.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorTabPane.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/Printable.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/PropertyControlGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsHTML.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsJSON.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsRegexp.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsText.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsXML.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/RequestPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/RequestView.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/RequestViewRaw.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/ResultRenderer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/RunningSample.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/Sample.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/SamplerResultTab.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/SamplingStatCalculator.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/SearchTextExtension.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/ServerPanel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/SimpleDataWriter.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/Spline3.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/SplineModel.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/SplineVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/StatGraphProperties.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/StatGraphVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/StatVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/SummaryReport.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/TableSample.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/TableVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/TreeNodeRenderer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/Visualizer.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/XMLDefaultMutableTreeNode.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/gui/AbstractListenerGui.java create mode 100644 ApacheJmeter/src/org/apache/jmeter/visualizers/gui/AbstractVisualizer.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/collections/Data.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/collections/HashTree.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/collections/HashTreeTraverser.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/collections/ListedHashTree.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/collections/SearchByClass.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/collections/SortedHashTree.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/AbstractTreeTableModel.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/ComponentUtil.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/DefaultTreeTableModel.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/GuiUtils.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/JLabeledChoice.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/JLabeledField.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/JLabeledPasswordField.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/JLabeledTextArea.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/JLabeledTextField.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/JTreeTable.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/NumberRenderer.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/ObjectTableModel.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/RateRenderer.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/RendererUtils.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/RightAlignRenderer.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/TreeTableModel.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/gui/layout/VerticalLayout.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/io/TextFile.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/logging/LoggingManager.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/math/NumberComparator.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/math/StatCalculator.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/math/StatCalculatorInteger.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/math/StatCalculatorLong.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/reflect/ClassFinder.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/reflect/ClassTools.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/reflect/Functor.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/test/UnitTestManager.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/util/Converter.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/util/HeapDumper.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/util/JMeterError.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/util/JMeterException.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/util/JMeterStopTestException.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/util/JMeterStopTestNowException.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/util/JMeterStopThreadException.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/util/JOrphanUtils.java create mode 100644 ApacheJmeter/src/org/apache/jorphan/util/XMLBuffer.java create mode 100644 ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources.properties create mode 100644 ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_es.properties create mode 100644 ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_fr.properties create mode 100644 ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_pt_BR.properties create mode 100644 ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_tr.properties create mode 100644 ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_zh_TW.properties create mode 100644 ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources.properties create mode 100644 ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources_fr.properties create mode 100644 ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources.properties create mode 100644 ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources_fr.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_es.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_fr.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_pt_BR.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_tr.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_zh_TW.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources_fr.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources_fr.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_es.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_fr.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_pt_BR.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_tr.properties create mode 100644 ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_zh_TW.properties create mode 100644 ApacheJmeter/src/protocol/mail/META-INF/javamail.providers create mode 100644 ApacheJmeter/src/test/AfterAnnotatedTest.java create mode 100644 ApacheJmeter/src/test/BeforeAnnotatedTest.java create mode 100644 ApacheJmeter/src/test/DummyAnnotatedTest.java create mode 100644 ApacheJmeter/src/test/Junit4AnnotationsTest.java create mode 100644 ApacheJmeter/src/test/RerunTest.java create mode 100644 ApacheJmeter/src/test/SetupTestError.java create mode 100644 ApacheJmeter/src/test/SetupTestFail.java create mode 100644 ApacheJmeter/src/test/TearDownTestFail.java create mode 100644 ApacheJmeter/src/woolfel/DummyTestCase.java create mode 100644 ApacheJmeter/src/woolfel/SubDummyTest.java create mode 100644 ApacheJmeter/src/woolfel/SubDummyTest2.java create mode 100644 ApacheJmeter/xdocs/building.xml create mode 100644 ApacheJmeter/xdocs/changes.xml create mode 100644 ApacheJmeter/xdocs/changes_history.xml create mode 100644 ApacheJmeter/xdocs/css/style.css create mode 100644 ApacheJmeter/xdocs/demos/AssertionTestPlan.jmx create mode 100644 ApacheJmeter/xdocs/demos/AuthManagerTestPlan.jmx create mode 100644 ApacheJmeter/xdocs/demos/BeanShellAssertion.bsh create mode 100644 ApacheJmeter/xdocs/demos/ForEachTest2.jmx create mode 100644 ApacheJmeter/xdocs/demos/HeaderManagerTestPlan.jmx create mode 100644 ApacheJmeter/xdocs/demos/InterleaveTestPlan.jmx create mode 100644 ApacheJmeter/xdocs/demos/InterleaveTestPlan2.jmx create mode 100644 ApacheJmeter/xdocs/demos/JDBC-Pre-Post-Processor.jmx create mode 100644 ApacheJmeter/xdocs/demos/JMSPointToPoint.jmx create mode 100644 ApacheJmeter/xdocs/demos/LoopTestPlan.jmx create mode 100644 ApacheJmeter/xdocs/demos/OnceOnlyTestPlan.jmx create mode 100644 ApacheJmeter/xdocs/demos/ProxyServerTestPlan.jmx create mode 100644 ApacheJmeter/xdocs/demos/SimpleTestPlan.jmx create mode 100644 ApacheJmeter/xdocs/demos/URLRewritingExample.jmx create mode 100644 ApacheJmeter/xdocs/demos/forEachTestPlan.jmx create mode 100644 ApacheJmeter/xdocs/download_jmeter.cgi create mode 100644 ApacheJmeter/xdocs/download_jmeter.xml create mode 100644 ApacheJmeter/xdocs/extending.xml create mode 100644 ApacheJmeter/xdocs/extending/JMeter Extension Scenario.xml create mode 100644 ApacheJmeter/xdocs/extending/index.xml create mode 100644 ApacheJmeter/xdocs/extending/jmeter_tutorial_mike.sxw create mode 100644 ApacheJmeter/xdocs/extending/notes_on_extending.txt create mode 100644 ApacheJmeter/xdocs/index.xml create mode 100644 ApacheJmeter/xdocs/issues.xml create mode 100644 ApacheJmeter/xdocs/jmeter_irc.xml create mode 100644 ApacheJmeter/xdocs/localising/index.xml create mode 100644 ApacheJmeter/xdocs/mail.xml create mode 100644 ApacheJmeter/xdocs/mail2.xml create mode 100644 ApacheJmeter/xdocs/nightly.xml create mode 100644 ApacheJmeter/xdocs/overview.html create mode 100644 ApacheJmeter/xdocs/presentation/jmeter_presentation.sxi create mode 100644 ApacheJmeter/xdocs/presentation/jmeter_presentation_part2.sxi create mode 100644 ApacheJmeter/xdocs/stylesheets/printable_project.xml create mode 100644 ApacheJmeter/xdocs/stylesheets/project.xml create mode 100644 ApacheJmeter/xdocs/stylesheets/site.vsl create mode 100644 ApacheJmeter/xdocs/stylesheets/site.xsl create mode 100644 ApacheJmeter/xdocs/stylesheets/site_printable.vsl create mode 100644 ApacheJmeter/xdocs/svnindex.xml create mode 100644 ApacheJmeter/xdocs/usermanual/best-practices.xml create mode 100644 ApacheJmeter/xdocs/usermanual/boss.xml create mode 100644 ApacheJmeter/xdocs/usermanual/build-adv-web-test-plan.xml create mode 100644 ApacheJmeter/xdocs/usermanual/build-db-test-plan.xml create mode 100644 ApacheJmeter/xdocs/usermanual/build-ftp-test-plan.xml create mode 100644 ApacheJmeter/xdocs/usermanual/build-jms-point-to-point-test-plan.xml create mode 100644 ApacheJmeter/xdocs/usermanual/build-jms-topic-test-plan.xml create mode 100644 ApacheJmeter/xdocs/usermanual/build-ldap-test-plan.xml create mode 100644 ApacheJmeter/xdocs/usermanual/build-ldapext-test-plan.xml create mode 100644 ApacheJmeter/xdocs/usermanual/build-monitor-test-plan.xml create mode 100644 ApacheJmeter/xdocs/usermanual/build-test-plan.xml create mode 100644 ApacheJmeter/xdocs/usermanual/build-web-test-plan.xml create mode 100644 ApacheJmeter/xdocs/usermanual/build-ws-test-plan.xml create mode 100644 ApacheJmeter/xdocs/usermanual/component_reference.xml create mode 100644 ApacheJmeter/xdocs/usermanual/functions.xml create mode 100644 ApacheJmeter/xdocs/usermanual/get-started.xml create mode 100644 ApacheJmeter/xdocs/usermanual/glossary.xml create mode 100644 ApacheJmeter/xdocs/usermanual/hints_and_tips.xml create mode 100644 ApacheJmeter/xdocs/usermanual/include_controller_tutorial.sxw create mode 100644 ApacheJmeter/xdocs/usermanual/index.xml create mode 100644 ApacheJmeter/xdocs/usermanual/intro.xml create mode 100644 ApacheJmeter/xdocs/usermanual/ldapanswer_xml.xml create mode 100644 ApacheJmeter/xdocs/usermanual/ldapops_tutor.xml create mode 100644 ApacheJmeter/xdocs/usermanual/listeners.xml create mode 100644 ApacheJmeter/xdocs/usermanual/regular_expressions.xml create mode 100644 ApacheJmeter/xdocs/usermanual/remote-test.xml create mode 100644 ApacheJmeter/xdocs/usermanual/test_plan.xml create mode 100644 ApacheJmeter/xdocs/velocity.properties diff --git a/ApacheJmeter/.classpath b/ApacheJmeter/.classpath new file mode 100644 index 0000000..67f4500 --- /dev/null +++ b/ApacheJmeter/.classpath @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/.project b/ApacheJmeter/.project new file mode 100644 index 0000000..355761f --- /dev/null +++ b/ApacheJmeter/.project @@ -0,0 +1,17 @@ + + + ApacheJmeter + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/ApacheJmeter/.settings/org.eclipse.jdt.core.prefs b/ApacheJmeter/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..54e493c --- /dev/null +++ b/ApacheJmeter/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/ApacheJmeter/LICENSE b/ApacheJmeter/LICENSE new file mode 100644 index 0000000..cf4809d --- /dev/null +++ b/ApacheJmeter/LICENSE @@ -0,0 +1,2031 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. + +============================ End of Apache License file V 2.0 =================== + +Third party licenses +==================== + +BeanShell 2.0b5 +========= +Licensed under The Sun Public License 1.0, http://java.sun.com/spl.html + +SUN PUBLIC LICENSE Version 1.0 + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original Code, + prior Modifications used by a Contributor, and the Modifications made + by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof and corresponding documentation released + with the source code. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified as + the Initial Developer in the Source Code notice required by Exhibit A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + + A. Any addition to or deletion from the contents of a file containing + Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, and + apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated documentation, interface definition files, scripts used + to control compilation and installation of an Executable, or source + code differential comparisons against either the Original Code or + another well known, available Covered Code of the Contributor's + choice. The Source Code can be in a compressed or archival form, + provided the appropriate decompression or de-archiving software is + widely available for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, to + cause the direction or management of such entity, whether by contract + or otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + +2. Source Code License. + +2.1 The Initial Developer Grant. + + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, modify, + display, perform, sublicense and distribute the Original Code (or + portions thereof) with or without Modifications, and/or as part of a + Larger Work; and + + (b) under Patent Claims infringed by the making, using or selling of + Original Code, to make, have made, use, practice, sell, and offer for + sale, and/or otherwise dispose of the Original Code (or portions + thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are effective + on the date Initial Developer first distributes Original Code under + the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused by: + i) the modification of the Original Code or ii) the combination of the + Original Code with other software or devices. + +2.2. Contributor Grant. + + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications created + by such Contributor (or portions thereof) either on an unmodified + basis, with other Modifications, as Covered Code and/or as part of a + Larger Work; and + + (b) under Patent Claims infringed by the making, using, or selling of + Modifications made by that Contributor either alone and/or in + combination with its Contributor Version (or portions of such + combination), to make, use, sell, offer for sale, have made, and/or + otherwise dispose of: 1) Modifications made by that Contributor (or + portions thereof); and 2) the combination of Modifications made by + that Contributor with its Contributor Version (or portions of such + combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are effective + on the date Contributor first makes Commercial Use of the Covered + Code. + + (d) notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; 3) for + infringements caused by: i) third party modifications of Contributor + Version or ii) the combination of Modifications made by that + Contributor with other software (except as part of the Contributor + Version) or other devices; or 4) under Patent Claims infringed by + Covered Code in the absence of Modifications made by that Contributor. + +3. Distribution Obligations. + +3.1. Application of License. + + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + +3.2. Availability of Source Code. + + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + +3.3. Description of Modifications. + + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + +3.4. Intellectual Property Matters. + + (a) Third Party Claims. + + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, Contributor + must include a text file with the Source Code distribution titled + "LEGAL'' which describes the claim and the party making the claim in + sufficient detail that a recipient will know whom to contact. If + Contributor obtains such knowledge after the Modification is made + available as described in Section 3.2, Contributor shall promptly + modify the LEGAL file in all copies Contributor makes available + thereafter and shall take other steps (such as notifying appropriate + mailing lists or newsgroups) reasonably calculated to inform those who + received the Covered Code that new knowledge has been obtained. + + (b) Contributor APIs. + + If Contributor's Modifications include an application programming + interface ("API") and Contributor has knowledge of patent licenses + which are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + + Contributor represents that, except as disclosed pursuant to Section + 3.4(a) above, Contributor believes that Contributor's Modifications + are Contributor's original creation(s) and/or Contributor has + sufficient rights to grant the rights conveyed by this License. + +3.5. Required Notices. + + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + +3.6. Distribution of Executable Versions. + + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + +3.7. Larger Works. + + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + +6.1. New Versions. + + Sun Microsystems, Inc. ("Sun") may publish revised and/or new versions + of the License from time to time. Each version will be given a + distinguishing version number. + +6.2. Effect of New Versions. + + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Sun. No one + other than Sun has the right to modify the terms applicable to Covered + Code created under this License. + +6.3. Derivative Works. + + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must: (a) rename Your license so that + the phrases "Sun," "Sun Public License," or "SPL" or any confusingly + similar phrase do not appear in your license (except to note that your + license differs from this License) and (b) otherwise make it clear + that Your version of the license contains terms which differ from the + Sun Public License. (Filling in the name of the Initial Developer, + Original Code or Contributor in the notice described in Exhibit A + shall not of themselves be deemed to be modifications of this + License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS'' BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declaratory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, all + end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in 48 + C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer software" + and "commercial computer software documentation," as such terms are + used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. + 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all + U.S. Government End Users acquire Covered Code with only those rights + set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + ?Multiple-Licensed?. ?Multiple-Licensed? means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the alternative licenses, if any, specified by the + Initial Developer in the file described in Exhibit A. + +Exhibit A -Sun Public License Notice. + + The contents of this file are subject to the Sun Public License + Version 1.0 (the "License"); you may not use this file except in + compliance with the License. A copy of the License is available at + http://www.sun.com/ + + The Original Code is _________________. The Initial Developer of the + Original Code is ___________. Portions created by ______ are Copyright + (C)_________. All Rights Reserved. + + Contributor(s): ______________________________________. + + Alternatively, the contents of this file may be used under the terms + of the _____ license (the ?[___] License?), in which case the + provisions of [______] License are applicable instead of those above. + If you wish to allow use of your version of this file only under the + terms of the [____] License and not to allow others to use your + version of this file under the SPL, indicate your decision by deleting + the provisions above and replace them with the notice and other + provisions required by the [___] License. If you do not delete the + provisions above, a recipient may use your version of this file under + either the SPL or the [___] License." + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] + +################################################################################ + +HtmlParser & HtmlLexer v2.1 +====================== + +also + +JUnit v4.10 +===== + +http://opensource.org/licenses/cpl1.0.txt +========================================= + +Common Public License Version 1.0 +Fri, 2007-06-01 17:16 - nelson + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and +documentation distributed under this Agreement, and + +b) in the case of each subsequent Contributor: + +i) changes to the Program, and + +ii) additions to the Program; + +where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from a +Contributor if it was added to the Program by such Contributor itself or anyone +acting on such Contributor's behalf. Contributions do not include additions to +the Program which: (i) are separate modules of software distributed in +conjunction with the Program under their own license agreement, and (ii) are not +derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents " mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + +a) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free copyright license to +reproduce, prepare derivative works of, publicly display, publicly perform, +distribute and sublicense the Contribution of such Contributor, if any, and such +derivative works, in source code and object code form. + +b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed +Patents to make, use, sell, offer to sell, import and otherwise transfer the +Contribution of such Contributor, if any, in source code and object code form. +This patent license shall apply to the combination of the Contribution and the +Program if, at the time the Contribution is added by the Contributor, such +addition of the Contribution causes such combination to be covered by the +Licensed Patents. The patent license shall not apply to any other combinations +which include the Contribution. No hardware per se is licensed hereunder. + +c) Recipient understands that although each Contributor grants the licenses +to its Contributions set forth herein, no assurances are provided by any +Contributor that the Program does not infringe the patent or other intellectual +property rights of any other entity. Each Contributor disclaims any liability to +Recipient for claims brought by any other entity based on infringement of +intellectual property rights or otherwise. As a condition to exercising the +rights and licenses granted hereunder, each Recipient hereby assumes sole +responsibility to secure any other intellectual property rights needed, if any. +For example, if a third party patent license is required to allow Recipient to +distribute the Program, it is Recipient's responsibility to acquire that license +before distributing the Program. + +d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright license set +forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its +own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and + +b) its license agreement: + +i) effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title and +non-infringement, and implied warranties or conditions of merchantability and +fitness for a particular purpose; + +ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and consequential +damages, such as lost profits; + +iii) states that any provisions which differ from this Agreement are offered +by that Contributor alone and not by any other party; and + +iv) states that source code for the Program is available from such +Contributor, and informs licensees how to obtain it in a reasonable manner on or +through a medium customarily used for software exchange. + +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and + +b) a copy of this Agreement must be included with each copy of the Program. + +Contributors may not remove or alter any copyright notices contained within the +Program. + +Each Contributor must identify itself as the originator of its Contribution, if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, if +a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, damages +and costs (collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to the +extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor to +control, and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may participate in +any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If that +Commercial Contributor then makes performance claims, or offers warranties +related to Product X, those performance claims and warranties are such +Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a court +requires any other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with its exercise of +rights under this Agreement, including but not limited to the risks and costs of +program errors, compliance with applicable laws, damage to or loss of data, +programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, such +provision shall be reformed to the minimum extent necessary to make such +provision valid and enforceable. + +If Recipient institutes patent litigation against a Contributor with respect to +a patent applicable to software (including a cross-claim or counterclaim in a +lawsuit), then any patent licenses granted by that Contributor to such Recipient +under this Agreement shall terminate as of the date such litigation is filed. In +addition, if Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the Program +itself (excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted under +Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue and +survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to time. +No one other than the Agreement Steward has the right to modify this Agreement. +IBM is the initial Agreement Steward. IBM may assign the responsibility to serve +as the Agreement Steward to a suitable separate entity. Each new version of the +Agreement will be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the Agreement +under which it was received. In addition, after a new version of the Agreement +is published, Contributor may elect to distribute the Program (including its +Contributions) under the new version. Except as expressly stated in Sections +2(a) and 2(b) above, Recipient receives no rights or licenses to the +intellectual property of any Contributor under this Agreement, whether +expressly, by implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. + +################################################################################ + +jCharts v0.75 +======= + + +jCharts License +---------------------------------------------------------------------------------------- + +* Copyright 2002 (C) Nathaniel G. Auvil. All Rights Reserved. +* +* Redistribution and use of this software and associated documentation +* ("Software"), with or without modification, are permitted provided +* that the following conditions are met: +* +* 1. Redistributions of source code must retain copyright +* statements and notices. Redistributions must also contain a +* copy of this document. +* +* 2. Redistributions in binary form must reproduce the +* above copyright notice, this list of conditions and the +* following disclaimer in the documentation and/or other +* materials provided with the distribution. +* +* 3. The name "jCharts" or "Nathaniel G. Auvil" must not be used to +* endorse or promote products derived from this Software without +* prior written permission of Nathaniel G. Auvil. For written +* permission, please contact nathaniel_auvil@users.sourceforge.net +* +* 4. Products derived from this Software may not be called "jCharts" +* nor may "jCharts" appear in their names without prior written +* permission of Nathaniel G. Auvil. jCharts is a registered +* trademark of Nathaniel G. Auvil. +* +* 5. Due credit should be given to the jCharts Project +* (http://jcharts.sourceforge.net/). +* +* THIS SOFTWARE IS PROVIDED BY Nathaniel G. Auvil AND CONTRIBUTORS +* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +* jCharts OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +* OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ + +JDOM v1.1.2 +==== + +/*-- + + $Id: LICENSE.txt,v 1.11 2004/02/06 09:32:57 jhunter Exp $ + + Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + + + +################################################################################ + +JTidy (r938) +===== + + Java HTML Tidy - JTidy + HTML parser and pretty printer + + Copyright (c) 1998-2000 World Wide Web Consortium (Massachusetts + Institute of Technology, Institut National de Recherche en + Informatique et en Automatique, Keio University). All Rights + Reserved. + + Contributing Author(s): + + Dave Raggett + Andy Quick (translation to Java) + Gary L Peskin (Java development) + Sami Lempinen (release management) + + The contributing author(s) would like to thank all those who + helped with testing, bug fixes, and patience. This wouldn't + have been possible without all of you. + + COPYRIGHT NOTICE: + + This software and documentation is provided "as is," and + the copyright holders and contributing author(s) make no + representations or warranties, express or implied, including + but not limited to, warranties of merchantability or fitness + for any particular purpose or that the use of the software or + documentation will not infringe any third party patents, + copyrights, trademarks or other rights. + + The copyright holders and contributing author(s) will not be + liable for any direct, indirect, special or consequential damages + arising out of any use of the software or documentation, even if + advised of the possibility of such damage. + + Permission is hereby granted to use, copy, modify, and distribute + this source code, or portions hereof, documentation and executables, + for any purpose, without fee, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented. + 2. Altered versions must be plainly marked as such and must + not be misrepresented as being the original source. + 3. This Copyright notice may not be removed or altered from any + source or altered source distribution. + + The copyright holders and contributing author(s) specifically + permit, without fee, and encourage the use of this source code + as a component for supporting the Hypertext Markup Language in + commercial products. If you use this source code in a product, + acknowledgment is not required but would be appreciated. + + +################################################################################ + + +Mozilla Rhino JavaScript v1.7R3 +======================== + +MPL 1.1 at http://www.mozilla.org/MPL/MPL-1.1.html + + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (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.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is ______________________________________. + + The Initial Developer of the Original Code is ________________________. + Portions created by ______________________ are Copyright (C) ______ + _______________________. All Rights Reserved. + + Contributor(s): ______________________________________. + + Alternatively, the contents of this file may be used under the terms + of the _____ license (the "[___] License"), in which case the + provisions of [______] License are applicable instead of those + above. If you wish to allow use of your version of this file only + under the terms of the [____] License and not to allow others to use + your version of this file under the MPL, indicate your decision by + deleting the provisions above and replace them with the notice and + other provisions required by the [___] License. If you do not delete + the provisions above, a recipient may use your version of this file + under either the MPL or the [___] License." + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] + +################################################################################ + +XPP3 v1.1.4c +==== + +Indiana University Extreme! Lab Software License + +Version 1.1.1 + +Copyright (c) 2002 Extreme! Lab, Indiana University. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + +3. The end-user documentation included with the redistribution, if any, + must include the following acknowledgment: + + "This product includes software developed by the Indiana University + Extreme! Lab (http://www.extreme.indiana.edu/)." + +Alternately, this acknowledgment may appear in the software itself, +if and wherever such third-party acknowledgments normally appear. + +4. The names "Indiana Univeristy" and "Indiana Univeristy Extreme! Lab" +must not be used to endorse or promote products derived from this +software without prior written permission. For written permission, +please contact http://www.extreme.indiana.edu/. + +5. Products derived from this software may not use "Indiana Univeristy" +name nor may "Indiana Univeristy" appear in their name, without prior +written permission of the Indiana University. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHORS, COPYRIGHT HOLDERS OR ITS CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################ + +XStream v1.4.2 +======= + +http://xstream.codehaus.org/license.html + +Copyright (c) 2003-2006, Joe Walnes +Copyright (c) 2006-2007, XStream Committers +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. Redistributions in binary form must reproduce +the above copyright notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. + +Neither the name of XStream nor the names of its contributors may be used to endorse +or promote products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +################################################################################ + +activation v1.1.1 +========== +and +mail v1.4.4 +==== + +JMeter includes the activation and mail jars under the CDDL license V1.0 + +Here follows the original dual license for the activation and mail jars: + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + +1. Definitions. + + 1.1. Contributor. means each individual or entity that creates or contributes to the creation of Modifications. + + 1.2. Contributor Version. means the combination of the Original Software, prior Modifications used by a Contributor (if any), and the Modifications made by that particular Contributor. + + 1.3. Covered Software. means (a) the Original Software, or (b) Modifications, or (c) the combination of files containing Original Software with files containing Modifications, in each case including portions thereof. + + 1.4. Executable. means the Covered Software in any form other than Source Code. + + 1.5. Initial Developer. means the individual or entity that first makes Original Software available under this License. + + 1.6. Larger Work. means a work which combines Covered Software or portions thereof with code not governed by the terms of this License. + + 1.7. License. means this document. + + 1.8. Licensable. means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein. + + 1.9. Modifications. means the Source Code and Executable form of any of the following: + + A. Any file that results from an addition to, deletion from or modification of the contents of a file containing Original Software or previous Modifications; + + B. Any new file that contains any part of the Original Software or previous Modification; or + + C. Any new file that is contributed or otherwise made available under the terms of this License. + + 1.10. Original Software. means the Source Code and Executable form of computer software code that is originally released under this License. + + 1.11. Patent Claims. means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor. + + 1.12. Source Code. means (a) the common form of computer software code in which modifications are made and (b) associated documentation included in or with such code. + + 1.13. You. (or .Your.) means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, .You. includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, .control. means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. + +2. License Grants. + + 2.1. The Initial Developer Grant. + + Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, the Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license: + + (a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer, to use, reproduce, modify, display, perform, sublicense and distribute the Original Software (or portions thereof), with or without Modifications, and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using or selling of Original Software, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Software (or portions thereof). + + (c) The licenses granted in Sections 2.1(a) and (b) are effective on the date Initial Developer first distributes or otherwise makes the Original Software available to a third party under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is granted: (1) for code that You delete from the Original Software, or (2) for infringements caused by: (i) the modification of the Original Software, or (ii) the combination of the Original Software with other software or devices. + + 2.2. Contributor Grant. + + Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: + + (a) under intellectual property rights (other than patent or trademark) Licensable by Contributor to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof), either on an unmodified basis, with other Modifications, as Covered Software and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: (1) Modifications made by that Contributor (or portions thereof); and (2) the combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination). + + (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor first distributes or otherwise makes the Modifications available to a third party. + + (d) Notwithstanding Section 2.2(b) above, no patent license is granted: (1) for any code that Contributor has deleted from the Contributor Version; (2) for infringements caused by: (i) third party modifications of Contributor Version, or (ii) the combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or (3) under Patent Claims infringed by Covered Software in the absence of Modifications made by that Contributor. + +3. Distribution Obligations. + + 3.1. Availability of Source Code. + Any Covered Software that You distribute or otherwise make available in Executable form must also be made available in Source Code form and that Source Code form must be distributed only under the terms of this License. You must include a copy of this License with every copy of the Source Code form of the Covered Software You distribute or otherwise make available. You must inform recipients of any such Covered Software in Executable form as to how they can obtain such Covered Software in Source Code form in a reasonable manner on or through a medium customarily used for software exchange. + + 3.2. Modifications. + The Modifications that You create or to which You contribute are governed by the terms of this License. You represent that You believe Your Modifications are Your original creation(s) and/or You have sufficient rights to grant the rights conveyed by this License. + + 3.3. Required Notices. + You must include a notice in each of Your Modifications that identifies You as the Contributor of the Modification. You may not remove or alter any copyright, patent or trademark notices contained within the Covered Software, or any notices of licensing or any descriptive text giving attribution to any Contributor or the Initial Developer. + + 3.4. Application of Additional Terms. + You may not offer or impose any terms on any Covered Software in Source Code form that alters or restricts the applicable version of this License or the recipients. rights hereunder. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, you may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear that any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer. + + 3.5. Distribution of Executable Versions. + You may distribute the Executable form of the Covered Software under the terms of this License or under the terms of a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable form does not attempt to limit or alter the recipient.s rights in the Source Code form from the rights set forth in this License. If You distribute the Covered Software in Executable form under a different license, You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer. + + 3.6. Larger Works. + You may create a Larger Work by combining Covered Software with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Software. + +4. Versions of the License. + + 4.1. New Versions. + Sun Microsystems, Inc. is the initial license steward and may publish revised and/or new versions of this License from time to time. Each version will be given a distinguishing version number. Except as provided in Section 4.3, no one other than the license steward has the right to modify this License. + + 4.2. Effect of New Versions. + You may always continue to use, distribute or otherwise make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. If the Initial Developer includes a notice in the Original Software prohibiting it from being distributed or otherwise made available under any subsequent version of the License, You must distribute and make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. Otherwise, You may also choose to use, distribute or otherwise make the Covered Software available under the terms of any subsequent version of the License published by the license steward. + + 4.3. Modified Versions. + When You are an Initial Developer and You want to create a new license for Your Original Software, You may create and use a modified version of this License if You: (a) rename the license and remove any references to the name of the license steward (except to note that the license differs from this License); and (b) otherwise make it clear that the license contains terms which differ from this License. + +5. DISCLAIMER OF WARRANTY. + + COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN .AS IS. BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +6. TERMINATION. + + 6.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive. + + 6.2. If You assert a patent infringement claim (excluding declaratory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You assert such claim is referred to as .Participant.) alleging that the Participant Software (meaning the Contributor Version where the Participant is a Contributor or the Original Software where the Participant is the Initial Developer) directly or indirectly infringes any patent, then any and all rights granted directly or indirectly to You by such Participant, the Initial Developer (if the Initial Developer is not the Participant) and all Contributors under Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively and automatically at the expiration of such 60 day notice period, unless if within such 60 day period You withdraw Your claim with respect to the Participant Software against such Participant either unilaterally or pursuant to a written agreement with Participant. + + 6.3. In the event of termination under Sections 6.1 or 6.2 above, all end user licenses that have been validly granted by You or any distributor hereunder prior to termination (excluding licenses granted to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTY.S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + + The Covered Software is a commercial item,. as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of .commercial computer software. (as that term is defined at 48 C.F.R. º 252.227-7014(a)(1)) and .commercial computer software documentation. as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Software with only those rights set forth herein. This U.S. Government Rights clause is in lieu of, and supersedes, any other FAR, DFAR, or other clause or provision that addresses Government rights in computer software under this License. + +9. MISCELLANEOUS. + + This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by the law of the jurisdiction specified in a notice contained within the Original Software (except to the extent applicable law, if any, provides otherwise), excluding such jurisdiction.s conflict-of-law provisions. Any litigation relating to this License shall be subject to the jurisdiction of the courts located in the jurisdiction and venue specified in a notice contained within the Original Software, with the losing party responsible for costs, including, without limitation, court costs and reasonable attorneys. fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. You agree that You alone are responsible for compliance with the United States export administration regulations (and the export control laws and regulation of any other countries) when You use, distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability. + + NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) + + The code released under the CDDL shall be governed by the laws of the State of California (excluding conflict-of-law provisions). Any litigation relating to this License shall be subject to the jurisdiction of the Federal Courts of the Northern District of California and the state courts of the State of California, with venue lying in Santa Clara County, California. + + +The GNU General Public License (GPL) Version 2, June 1991 + + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + + c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + + Copyright (C) + + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. + + +"CLASSPATH" EXCEPTION TO THE GPL VERSION 2 + +Certain source files distributed by Sun Microsystems, Inc. are subject to the following clarification and special exception to the GPL Version 2, but only where Sun has expressly included in the particular source file's header the words + +"Sun designates this particular file as subject to the "Classpath" exception as provided by Sun in the License file that accompanied this code." + +Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions of the GNU General Public License Version 2 cover the whole combination. + +As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module.? An independent module is a module which is not derived from or based on this library.? If you modify this library, you may extend this exception to your version of the library, but you are not obligated to do so.? If you do not wish to do so, delete this exception statement from your version. + +################################################################################ + Open Icon Library Licenses +========================================= + +Open Icon Library from +http://openiconlibrary.sourceforge.net/ + +Detailled Licenses information: +http://openiconlibrary.sourceforge.net/LICENSES.html + +============ Packages used by Apache JMeter ========= + +open_icon_library-devel-CC (Creative Commons and Public Domain only) +open_icon_library-CC (Creative Commons and Public Domain only) + List of licenses used in these packages: + Creative Commons Attribution + Creative Commons Attribution-Share Alike + Public Domain + +========Icons's sources used by Apache JMeter========== + +echo-icon-theme (echo) + link: https://fedorahosted.org/echo-icon-theme/ + license: CC-BY-SA 3.0 + License link: http://creativecommons.org/licenses/by-sa/3.0/ + formats: png + subdirectories: open_icon_library-devel/icons/echo + +Oxygen Icons 4.3.1 (KDE) (oxygen) + link: http://www.oxygen-icons.org/ + license: Dual: CC-BY-SA 3.0 or LGPL + License link: http://creativecommons.org/licenses/by-sa/3.0/ + http://creativecommons.org/licenses/LGPL/2.1/ + formats: svg, png + subdirectory: open_icon_library-devel/icons/oxygen + +Tango Icon Library 0.8.90 (tango) + link: http://tango.freedesktop.org/Tango_Icon_Library + license: Public Domain + License link: http://en.wikipedia.org/wiki/Public_domain + formats: svg, png + subdirectory: open_icon_library-devel/icons/tango + +============== CC-BY-SA 3.0 License ============= +http://creativecommons.org/licenses/by-sa/3.0/legalcode + +Creative Commons Attribution-ShareAlike 3.0 Unported License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. + +1. Definitions + + a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. + b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. + c. "Creative Commons Compatible License" means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. + d. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. + e. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. + f. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. + g. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. + h. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. + i. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. + j. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. + k. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. + +2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: + + a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; + b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; + c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, + d. to Distribute and Publicly Perform Adaptations. + e. For the avoidance of doubt: + i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; + ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, + iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. + +The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: + + a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. + b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. + c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. + d. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. + +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + + a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. + b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. + +8. Miscellaneous + + a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. + b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. + c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. + e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. + f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. + +################################################################################ + diff --git a/ApacheJmeter/NOTICE b/ApacheJmeter/NOTICE new file mode 100644 index 0000000..04b46b7 --- /dev/null +++ b/ApacheJmeter/NOTICE @@ -0,0 +1,36 @@ +Apache JMeter +Copyright 1998-2012 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +This product includes JavaScript support provided by the Mozilla Rhino project +See http://www.mozilla.org/rhino/ + +This product includes software developed by the +JDOM Project (http://www.jdom.org/). + +This product includes JUnit (http://www.junit.org/) +under the Common Public License Version 1.0: http://www.opensource.org/licenses/cpl.php + +This product includes XStream (http://xstream.codehaus.org/) +Copyright (c) 2003-2004, Joe Walnes All rights reserved. + +This product includes software developed by the Indiana University + Extreme! Lab (http://www.extreme.indiana.edu/). (XPP3 Pull Parser) + +This project includes HTMLParser (http://htmlparser.sourceforge.net/) +under the Common Public License Version 1.0: http://www.opensource.org/licenses/cpl.php + +This product includes software developed by the jCharts Project. +See http://jcharts.sourceforge.net/ + +This product includes BeanShell by Pat Niemeyer +http://www.beanshell.org/; Source: http://www.beanshell.org/developer.html + +This product includes JAF and JavaMail from Oracle(Sun) +The software is included under the CDDL License. + +The product includes icons from Open Icon Library +http://openiconlibrary.sourceforge.net/, of which the +Oxygen Icons 4.3.1 are added under the CC-BY-SA 3.0 license diff --git a/ApacheJmeter/README b/ApacheJmeter/README new file mode 100644 index 0000000..fb27acf --- /dev/null +++ b/ApacheJmeter/README @@ -0,0 +1,167 @@ + + A P A C H E J M E T E R + + + What is it? + ----------- + + Apache JMeter is a 100% pure Java application designed to test + and measure performance. It may be used as a highly portable + server benchmark as well as multiclient load generator. + + The Latest Version + ------------------ + + Details of the latest version can be found on the Java Apache + Project web site (http://jakarta.apache.org/jmeter). + + Requirements + ------------ + + The following requirements exist for running Apache JMeter: + + o Java Interpreter: + + A fully compliant Java 1.5 (or later) Runtime Environment is required + for Apache JMeter to execute. + + o Optional jars: + + Some jars are not included with JMeter. + If required, these should be downloaded and placed in the lib directory + + JDBC - available from database supplier + JMS - available from the JMS provider + + o Java Compiler [OPTIONAL]: + + A Java compiler is not needed since the distribution includes a + precompiled java binary archive. Note that a compiler is required + if you plan to build plugin classes for Apache JMeter. + + Installation Instructions + ------------------------- + + Note that spaces in directory names can cause problems. + + - Release builds + Unpack the binary archive into a suitable directory structure. + + - Nightly builds + Unpack BOTH the _bin and _lib archives into the SAME directory structure + + Running JMeter + -------------- + + Change to the bin directory and run the jmeter (Un*x) or jmeter.bat (Windows) file. + + For Windows (2K, XP etc), there are also some other scripts: + + jmeter-n.cmd - drop a JMX file on this and it will run it as a non-GUI test + jmeter-n-r.cmd - drop a JMX file on this and it will run it as a non-GUI remote (client-server) test + jmeter-t.cmd - drop a JMX file on this and it will open the file for running a GUI test + + Documentation + ------------- + The documentation available as of the date of this release is + also included, in HTML format, in the printable_docs/ directory, and it may + be browsed starting from the file called index.html. + + Build instructions + ------------------ + + - Release builds + Unpack the source archive into a suitable directory structure. + Most of the 3rd party library files can be extracted from the binary archive by unpacking it + into the same directory structure. + You can use Ant to download any missing files: + + ant download_jars + + - Nightly builds + Unpack the _src, _bin and _lib archives into the same directory structure. + + Please note: + To avoid unnecessary duplication, the nightly source archives do not contain + the source files which are needed to run JMeter (for example properties files and scripts). + + Any optional jars (see above) should be placed in lib/opt and/or lib. + + Jars in lib/opt will be used for building JMeter and running the unit test, but won't be used at run-time. + [This is useful for testing what happens if the optional jars are not downloaded + by other JMeter users]. + + JMeter is built using Ant. + + Change to the top-level directory and issue the command: + + ant download_jars ! only needs to be done once; will download any missing 3rd party jars + + ant + + This will compile the application and enable you to run jmeter from the bin + directory. + + ant test [-Djava.awt.headless=true] + + This will compile and run the unit tests. + The optional property definition is required if the system does not have a suitable GUI display. + + Licensing and legal issues + -------------------------- + + For legal and licensing issues, please look the files: + LICENSE + NOTICE + + This project includes HTMLParser. + For detailed information about HTMLParser, the project is + hosted on Sourceforge at http://htmlparser.sourceforge.net/ + + The developers of Apache JMeter are grateful to the developers + of HTMLParser for re-releasing htmlparser under CPL V1.0 + + HTMLParser was originally created by Somik Raha in 2000. + Derrick Oswald is the current lead developer and was kind + enough to assist JMeter. + + +Cryptographic Software Notice +----------------------------- + +This distribution may include software that has been designed for use +with cryptographic software. The country in which you currently reside +may have restrictions on the import, possession, use, and/or re-export +to another country, of encryption software. BEFORE using any encryption +software, please check your country's laws, regulations and policies +concerning the import, possession, or use, and re-export of encryption +software, to see if this is permitted. See +for more information. + +The U.S. Government Department of Commerce, Bureau of Industry and +Security (BIS), has classified this software as Export Commodity +Control Number (ECCN) 5D002.C.1, which includes information security +software using or performing cryptographic functions with asymmetric +algorithms. The form and manner of this Apache Software Foundation +distribution makes it eligible for export under the License Exception +ENC Technology Software Unrestricted (TSU) exception (see the BIS +Export Administration Regulations, Section 740.13) for both object +code and source code. + +The following provides more details on the included software that +may be subject to export controls on cryptographic software: + + Apache JMeter interfaces with the + Java Secure Socket Extension (JSSE) API to provide + + - HTTPS support + + Apache JMeter interfaces (via Apache HttpClient3) with the + Java Cryptography Extension (JCE) API to provide + + - NTLM authentication + + Apache JMeter does not include any implementation of JSSE or JCE. + + + Thank you for using Apache JMeter. \ No newline at end of file diff --git a/ApacheJmeter/STATUS b/ApacheJmeter/STATUS new file mode 100644 index 0000000..45b4753 --- /dev/null +++ b/ApacheJmeter/STATUS @@ -0,0 +1,16 @@ +STATUS as at September 2007 +====== + +SVN status: +- trunk is for development of 2.3.1+ +- branches/rel-2-2 is the current development branch for 2.3 final only + +Branch history: +-------------- + +rel-2-1:325546 created from trunk:325542 +rel-2-2:413997 created from rel-2-1:413996 +trunk:574067 moved to branches/java1.5_prototype-was_trunk:574058 +trunk:574063 created from branches/rel-2-2:574062 + +sebb AT apache DOT org \ No newline at end of file diff --git a/ApacheJmeter/build.properties b/ApacheJmeter/build.properties new file mode 100644 index 0000000..eaa23c3 --- /dev/null +++ b/ApacheJmeter/build.properties @@ -0,0 +1,272 @@ +# 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. + +# **** External jars (not built as part of JMeter) and needed for build/release **** + +# property name conventions: +# +# xxx.jar - name of the jar as used in JMeter +# +# The following properties are used to download the jars if necessary. +# +# xxx.loc - example location where the jar or zip can be found (omit trailing /) +# xxx.md5 - MD5 hash of the jar (used to check downloads) +# +# xxx.zip - name of zip file (if the jar is not available as an independent download) +# xxx.ent - the jar entry name in Zip file + +# Note that all the jars (apart from velocity and the Geronimo API jars) +# are contained in the JMeter binary release. + +maven2.repo = http://repo2.maven.org/maven2 + +apache-bsf.version = 2.4.0 +apache-bsf.jar = bsf-${apache-bsf.version}.jar +apache-bsf.loc = ${maven2.repo}/bsf/bsf/${apache-bsf.version} +apache-bsf.md5 = 16e82d858c648962fb5c959f21959039 + +apache-jsr223-api.version = 3.1 +apache-jsr223-api.jar = bsf-api-${apache-jsr223-api.version}.jar +apache-jsr223-api.loc = ${maven2.repo}/org/apache/bsf/bsf-api/${apache-jsr223-api.version} +apache-jsr223-api.md5 = 147c6cb39f889f640036f096f8a4bf59 + +avalon-framework.version = 4.1.4 +avalon-framework.jar = avalon-framework-${avalon-framework.version}.jar +avalon-framework.loc = ${maven2.repo}/avalon-framework/avalon-framework/${avalon-framework.version} +avalon-framework.md5 = 2C5306A09B22BD06A78343C0B55D021F + +beanshell.version = 2.0b5 +beanshell.jar = bsh-${beanshell.version}.jar +beanshell.loc = https://repository.jboss.org/nexus/content/repositories/thirdparty-releases/org/beanshell/bsh/${beanshell.version} +beanshell.md5 = 02F72336919D06A8491E82346E10B4D5 + +# Bouncy Castle jars (compile and test only - not distributed) +bcmail.version = 1.45 +bcmail.jar = bcmail-jdk15-${bcmail.version}.jar +bcmail.loc = ${maven2.repo}/org/bouncycastle/bcmail-jdk15/${bcmail.version} +bcmail.md5 = 13321fc7eff7bcada7b4fedfb592025c + +bcprov.version = 1.45 +bcprov.jar = bcprov-jdk15-${bcprov.version}.jar +bcprov.loc = ${maven2.repo}/org/bouncycastle/bcprov-jdk15/${bcprov.version} +bcprov.md5 = 2062f8e3d15748443ea60a94b266371c + +commons-codec.version = 1.6 +commons-codec.jar = commons-codec-${commons-codec.version}.jar +commons-codec.loc = ${maven2.repo}/commons-codec/commons-codec/${commons-codec.version} +commons-codec.md5 = 5970f54883b4831b24b97f1125ba27e6 + +commons-collections.version = 3.2.1 +commons-collections.jar = commons-collections-${commons-collections.version}.jar +commons-collections.loc = ${maven2.repo}/commons-collections/commons-collections/${commons-collections.version} +commons-collections.md5 = 13BC641AFD7FD95E09B260F69C1E4C91 + +commons-httpclient.version = 3.1 +commons-httpclient.jar = commons-httpclient-${commons-httpclient.version}.jar +commons-httpclient.loc = ${maven2.repo}/commons-httpclient/commons-httpclient/${commons-httpclient.version} +commons-httpclient.md5 = 8AD8C9229EF2D59AB9F59F7050E846A5 + +commons-io.version = 2.2 +commons-io.jar = commons-io-${commons-io.version}.jar +commons-io.loc = ${maven2.repo}/commons-io/commons-io/${commons-io.version} +commons-io.md5 = 6ad49e3e16c2342e9ee9599ce04775e6 + +commons-jexl.version = 1.1 +commons-jexl.jar = commons-jexl-${commons-jexl.version}.jar +commons-jexl.loc = ${maven2.repo}/commons-jexl/commons-jexl/${commons-jexl.version} +commons-jexl.md5 = 3F7735D20FCE1DBE05F62FF7A7B178DC + +commons-jexl2.version = 2.1.1 +commons-jexl2.jar = commons-jexl-${commons-jexl2.version}.jar +commons-jexl2.loc = ${maven2.repo}/org/apache/commons/commons-jexl/${commons-jexl2.version} +commons-jexl2.md5 = 4ad8f5c161dd3a50e190334555675db9 + +commons-lang.version = 2.6 +commons-lang.jar = commons-lang-${commons-lang.version}.jar +commons-lang.loc = ${maven2.repo}/commons-lang/commons-lang/${commons-lang.version} +commons-lang.md5 = 4d5c1693079575b362edf41500630bbd + +commons-logging.version = 1.1.1 +commons-logging.jar = commons-logging-${commons-logging.version}.jar +commons-logging.loc = ${maven2.repo}/commons-logging/commons-logging/${commons-logging.version} +# Checksum from binary release and Maven differ, but contents of jar are identical +#commons-logging.md5 = E2C390FE739B2550A218262B28F290CE +commons-logging.md5 = ed448347fc0104034aa14c8189bf37de + +commons-net.version = 3.1 +commons-net.jar = commons-net-${commons-net.version}.jar +commons-net.loc = ${maven2.repo}/commons-net/commons-net/${commons-net.version} +commons-net.md5 = 23c94d51e72f341fb412d6a015e16313 + +excalibur-datasource.version = 1.1.1 +excalibur-datasource.jar = excalibur-datasource-${excalibur-datasource.version}.jar +excalibur-datasource.loc = ${maven2.repo}/excalibur-datasource/excalibur-datasource/${excalibur-datasource.version} +excalibur-datasource.md5 = 59A9EDFF1005D70DFA638CF3A4D3AD6D + +excalibur-instrument.version = 1.0 +excalibur-instrument.jar = excalibur-instrument-${excalibur-instrument.version}.jar +excalibur-instrument.loc = ${maven2.repo}/excalibur-instrument/excalibur-instrument/${excalibur-instrument.version} +excalibur-instrument.md5 = 81BF95737C97A46836EA5F21F7C82719 + +excalibur-logger.version = 1.1 +excalibur-logger.jar = excalibur-logger-${excalibur-logger.version}.jar +excalibur-logger.loc = ${maven2.repo}/excalibur-logger/excalibur-logger/${excalibur-logger.version} +excalibur-logger.md5 = E8246C546B7B0CAFD65947E9B80BB884 + +excalibur-pool.version = 1.2 +excalibur-pool.jar = excalibur-pool-${excalibur-pool.version}.jar +excalibur-pool.loc = ${maven2.repo}/excalibur-pool/excalibur-pool/${excalibur-pool.version} +excalibur-pool.md5 = 0AF05C8811A2912D62D6E189799FD518 + +# Common file containing both htmlparser and htmllexer jars +htmlparser.version = 2.1 +htmllexer.loc = ${maven2.repo}/org/htmlparser/htmllexer/${htmlparser.version} +htmllexer.jar = htmllexer-${htmlparser.version}.jar +htmllexer.md5 = 1cb7184766a0c52f4d98d671bb08be19 + +htmlparser.loc = ${maven2.repo}/org/htmlparser/htmlparser/${htmlparser.version} +htmlparser.jar = htmlparser-${htmlparser.version}.jar +htmlparser.md5 = aa05b921026c228f92ef8b4a13c26f8d + +# Apache HttpClient 4.x +httpclient.version = 4.1.3 +# +httpclient.jar = httpclient-${httpclient.version}.jar +httpclient.loc = ${maven2.repo}/org/apache/httpcomponents/httpclient/${httpclient.version} +httpclient.md5 = d83032d08f17e9623911bae4ed52d0c7 + +# Required for HttpClient +httpmime.jar = httpmime-${httpclient.version}.jar +httpmime.loc = ${maven2.repo}/org/apache/httpcomponents/httpmime/${httpclient.version} +httpmime.md5 = 4825aaf8d1d5cda684d608271d7e061e + +# Required for HttpClient +httpcore.version = 4.1.4 +httpcore.jar = httpcore-${httpcore.version}.jar +httpcore.loc = ${maven2.repo}/org/apache/httpcomponents/httpcore/${httpcore.version} +httpcore.md5 = 8614e56c044b5ad79e4cf79fdc3013ee + +jakarta-oro.version = 2.0.8 +jakarta-oro.jar = oro-${jakarta-oro.version}.jar +jakarta-oro.loc = ${maven2.repo}/oro/oro/${jakarta-oro.version} +jakarta-oro.md5 = 42E940D5D2D822F4DC04C65053E630AB + +jcharts.version = 0.7.5 +jcharts.jar = jcharts-${jcharts.version}.jar +jcharts.loc = http://www.mvnsearch.org/maven2/jcharts/jcharts/${jcharts.version} +jcharts.md5 = 13927D8077C991E7EBCD8CB284746A7A + +jdom.version = 1.1.2 +jdom.jar = jdom-${jdom.version}.jar +jdom.loc = ${maven2.repo}/org/jdom/jdom/${jdom.version} +jdom.md5 = 742bb15c2eda90dff56e3d82cf40cd13 + +js_rhino.version = 1.7R3 +js_rhino.jar = rhino-${js_rhino.version}.jar +js_rhino.loc = ${maven2.repo}/org/mozilla/rhino/${js_rhino.version} +js_rhino.md5 = 9dbdb24663f20db43a2c29467c13a204 + +junit.version = 4.10 +junit.jar = junit-${junit.version}.jar +junit.loc = ${maven2.repo}/junit/junit/${junit.version} +junit.md5 = 68380001b88006ebe49be50cef5bb23a + +logkit.version = 2.0 +logkit.jar = logkit-${logkit.version}.jar +logkit.loc = ${maven2.repo}/logkit/logkit/${logkit.version} +logkit.md5 = 8D82A3E91AAE216D0A2A40B837A232FF + +soap.version = 2.3.1 +soap.jar = soap-${soap.version}.jar +soap.loc = ${maven2.repo}/soap/soap/${soap.version} +soap.md5 = AA1845E01FEE94FE4A63BBCAA55AD486 + +tidy.version = r938 +tidy.jar = jtidy-${tidy.version}.jar +tidy.loc = ${maven2.repo}/net/sf/jtidy/jtidy/${tidy.version} +tidy.md5 = 6A9121561B8F98C0A8FB9B6E57F50E6B + +# XStream can be found at: http://xstream.codehaus.org/ +xstream.version = 1.4.2 +xstream.jar = xstream-${xstream.version}.jar +xstream.loc = ${maven2.repo}/com/thoughtworks/xstream/xstream/${xstream.version} +xstream.md5 = 23947B036DD0D9CD23CB2F388C373181 + +# XMLPull is required by XStream 1.4.x +xmlpull.version = 1.1.3.1 +xmlpull.jar = xmlpull-${xmlpull.version}.jar +xmlpull.loc = ${maven2.repo}/xmlpull/xmlpull/${xmlpull.version} +xmlpull.md5 = cc57dacc720eca721a50e78934b822d2 + +xpp3.version = 1.1.4c +xpp3.jar = xpp3_min-${xpp3.version}.jar +xpp3.loc = ${maven2.repo}/xpp3/xpp3_min/${xpp3.version} +xpp3.md5 = DCD95BCB84B09897B2B66D4684C040DA + +# Xalan can be found at: http://xml.apache.org/xalan-j/ +xalan.version = 2.7.1 +xalan.jar = xalan-${xalan.version}.jar +xalan.loc = ${maven2.repo}/xalan/xalan/${xalan.version} +xalan.md5 = D43AAD24F2C143B675292CCFEF487F9C + +serializer.version = 2.7.1 +serializer.jar = serializer-${serializer.version}.jar +serializer.loc = ${maven2.repo}/xalan/serializer/${serializer.version} +# Checksum from binary release and Maven differ, but contents of jar are identical (apart from non-essential comment) +#serializer.md5 = F0FA654C1EA1186E9A5BD56E48E0D4A3 +serializer.md5 = a6b64dfe58229bdd810263fa0cc54cff + +# Xerces can be found at: http://xerces.apache.org/xerces2-j/ +xerces.version = 2.9.1 +xerces.jar = xercesImpl-${xerces.version}.jar +xerces.loc = ${maven2.repo}/xerces/xercesImpl/${xerces.version} +# Checksum from binary release and Maven differ, but contents of jar are identical (apart from EOLs in text files) +#xerces.md5 = DA09B75B562CA9A8E9A535D2148BE8E4 +xerces.md5 = f807f86d7d9db25edbfc782aca7ca2a9 + +xml-apis.version = 1.3.04 +xml-apis.jar = xml-apis-${xml-apis.version}.jar +xml-apis.loc = ${maven2.repo}/xml-apis/xml-apis/${xml-apis.version} +xml-apis.md5 = 9AE9C29E4497FC35A3EADE1E6DD0BBEB + +# Codecs were previously provided by Batik +xmlgraphics-commons.version = 1.3.1 +xmlgraphics-commons.jar = xmlgraphics-commons-${xmlgraphics-commons.version}.jar +xmlgraphics-commons.loc = ${maven2.repo}/org/apache/xmlgraphics/xmlgraphics-commons/${xmlgraphics-commons.version} +xmlgraphics-commons.md5 = E63589601D939739349A50A029DAB120 + +# JavaMail jars (N.B. these are available under CDDL) +activation.version = 1.1.1 +activation.jar = activation-${activation.version}.jar +activation.loc = ${maven2.repo}/javax/activation/activation/${activation.version} +activation.md5 = 46a37512971d8eca81c3fcf245bf07d2 + +javamail.version = 1.4.4 +javamail.jar = mail-${javamail.version}.jar +javamail.loc = ${maven2.repo}/javax/mail/mail/${javamail.version} +javamail.md5 = f30453ae9ee252c802d349009742065f + +# Geronimo JMS jar +jms.version = 1.1.1 +jms.jar = geronimo-jms_1.1_spec-${jms.version}.jar +jms.loc = ${maven2.repo}/org/apache/geronimo/specs/geronimo-jms_1.1_spec/${jms.version} +jms.md5 = d80ce71285696d36c1add1989b94f084 + +# The following jars are only needed for source distributions +# They are used for building the documentation +velocity.version = 1.7 +velocity.jar = velocity-${velocity.version}.jar +velocity.loc = ${maven2.repo}/org/apache/velocity/velocity/${velocity.version} +velocity.md5 = 3692dd72f8367cb35fb6280dc2916725 \ No newline at end of file diff --git a/ApacheJmeter/build.xml b/ApacheJmeter/build.xml new file mode 100644 index 0000000..8a7ebe4 --- /dev/null +++ b/ApacheJmeter/build.xml @@ -0,0 +1,2616 @@ + + + + + + N.B. To build JMeter from a release you need both the binary and source archives, + and these must be unpacked into the same directory structure. + + To download additional jars needed for building the code and documentation: + + ant download_jars + + + To build JMeter from source: + ant [install] + + To rebuild: + ant clean install + + To update documentation + ant docs-site + ant docs-printable + To build API documentation (Javadoc) + ant docs-api + To build all the docs + ant docs-all + + To build all and package up the files for distribution + ant distribution -Djmeter.version=vvvv [-Dsvn.revision=nnnnn] + + Add -Ddisable-svnCheck=true to disable svn check, if you build from src archive or offline + Add -Ddisable-check-versions=true to disable matching current svn revision and JMeterVersion.java, + if you want build your own custom JMeter package. + + To create a nightly build (separate bin/src/lib jars): + ant nightly [-Dsvn.revision=nnnnn] + + To create tar and tgz of the web-site documentation (docs and api) + ant site [ -Djmeter.version=vvvv ] + + + For more info: + ant -projecthelp + + To diagnose usage of deprecated APIs: + ant -Ddeprecation=on clean compile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jmeter.version = ${jmeter.version} + display.version = ${display.version} + implementation.version = ${implementation.version} + + + + + + + + + + + + + svn.revision = ${svn.revision} + jmeter.version = ${jmeter.version} + display.version = ${display.version} + implementation.version = ${implementation.version} + + + + + eclipse.anakia = ${eclipse.anakia} + + + + + + + + + + + + + AnakiaTask is not present, documentation will not be generated. + + + + + + Velocity version appears to be older than 1.5: the documentation may be generated with incorrect line endings. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cannot find all the required 3rd party libraries. + If building from a release, you can get most of them from the binary archive. + Use "ant download_jars" to download any missing jars. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Converting work files to eol=${eoltype} in ${workdir} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Creating JMeter distribution ${dist.name} ${svn.revision} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Updating POM files to version ${jmeter.version} + + + + + + + + Copying jar files ready for signing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Gump properties for this run + jmeter.version = ${jmeter.version} + gump.run = ${gump.run} + date.projectfile = ${date.projectfile} + version.projectfile = ${version.projectfile} + Build file: + version.build = ${version.build} + Java properties: + target.java.version = ${target.java.version} + src.java.version = ${src.java.version} + optimize = ${optimize} + deprecation = ${deprecation} + encoding = ${encoding} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Updating overview to ${docversion} + + + + + + + + + + + + + + + + + + + + + + + + + + Fixing EOL + + Removing unnecessary </br> tags + + Copying files + + + + + + + + + + + + + + + + + + + + + + + + + Fixing EOL + + Removing unnecessary </br> tags + + + Copying files + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Error detected in server log file. See above. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CSV Files are not identical. + ${batchtest.inp}${file.separator}${batchtest.name}.csv + ${batchtest.out}${file.separator}${batchtest.name}.csv + + + + + + + + XML Files are not identical. + ${batchtest.inp}${file.separator}${batchtest.name}.xml + ${batchtest.out}${file.separator}${batchtest.name}.xml + + + ${batchtest.name} output files compared OK + + + + + + + + + + + + Error detected in log file. See above. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + gump.run = ${gump.run} + java.awt.headless = ${java.awt.headless} + test.headless = ${test.headless} + user.dir = ${user.dir} + basedir = ${basedir} + test dir = ${build.test} + test dir gump = ${build.test.gump} + testsaveservice.saveout = ${testsaveservice.saveout} + test.encoding = ${test.encoding} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Checking ${jar} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bad Checksum: for ${file} + expected ${md5} + actual ${MD5} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/checkstyle.xml b/ApacheJmeter/checkstyle.xml new file mode 100644 index 0000000..161b67f --- /dev/null +++ b/ApacheJmeter/checkstyle.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/doap_JMeter.rdf b/ApacheJmeter/doap_JMeter.rdf new file mode 100644 index 0000000..24f936e --- /dev/null +++ b/ApacheJmeter/doap_JMeter.rdf @@ -0,0 +1,127 @@ + + + + + + 2006-02-17 + + Apache JMeter + + + Pure Java application for load and functional testing + Apache JMeter may be used to test performance both on static and dynamic resources (files, Servlets, Perl scripts, Java Objects, Data Bases and Queries, FTP Servers and more). It can be used to simulate a heavy load on a server, network or object to test its strength or to analyze overall performance under different load types. You can use it to make a graphical analysis of performance or to test your server/script/object behavior under heavy concurrent load. + + + + Java + + + + Apache Jakarta JMeter + 2006-06-14 + 2.2 + + + + + Apache Jakarta JMeter + 2007-09-04 + 2.3RC4 + + + + + Apache Jakarta JMeter + 2007-09-29 + 2.3 final + + + + + Apache Jakarta JMeter + 2007-11-30 + 2.3.1 final + + + + + Apache Jakarta JMeter + 2008-06-14 + 2.3.2 final + + + + + Apache Jakarta JMeter + 2009-05-24 + 2.3.3 final + + + + + Apache Jakarta JMeter + 2009-06-21 + 2.3.4 final + + + + + Apache Jakarta JMeter + 2010-07-12 + 2.4 final + + + + + Apache Jakarta JMeter + 2011-08-17 + 2.5 final + + + + + Apache Jakarta JMeter + 2011-10-03 + 2.5.1 final + + + + + Apache JMeter + 2012-02-01 + 2.6 final + + + + + Apache JMeter + 2012-05-27 + 2.7 final + + + + + + + + + + diff --git a/ApacheJmeter/docs/building.html b/ApacheJmeter/docs/building.html new file mode 100644 index 0000000..6a437a3 --- /dev/null +++ b/ApacheJmeter/docs/building.html @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - Building JMeter and Add-Ons + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +Building JMeter and Add-Ons +
+
+ + + +Note to developers: +This is a very brief overview. +There is more infomation on the JMeter Wiki. + + + +

+Building Add-Ons +

+

+ +There is no need to build JMeter if you just want to build an add-on. +Just download the binary archive and add the jars to the classpath. +You may want to also download the source so it can be used by the IDE. + +

+

+See the extras/addons* files in the source tree for some suggestions +

+

+Building JMeter +

+

+Acquiring the source +

+

+The full source is distributed alongside the binary, or it can be downloaded from SVN. +

+

+ +The source archive and SVN do not contain any of the required library files. +These need to be downloaded by running the Ant command: + +

+
+ant download_jars
+
+
+ + +

+

+Or you can download the binary distribution archive for a release and unpack it into the same directory structure as the source. +This will ensure that the lib/ directory contains the jar files needed for running JMeter. +There are a few additional jars that are needed to build JMeter, download these using: + +

+
+ant download_jars
+
+
+ +This will retrieve any missing jars. + +

+

+Compiling and packaging JMeter using Ant +

+

+ +JMeter can be built entirely using Ant. +The basic command is: + +

+
+ant [install]
+
+
+ +See build.xml for the other targets that can be used. + +

+

+Compiling and packaging JMeter using Eclipse +

+

+ +Once you have downloaded the source from SVN or the release archives and run the ant download_jars target to +install the dependent jars, you can configure Eclipse. The easiest way to do this is to replace the Eclipse .classpath +file with the eclipse.classpath file provided with JMeter. This will set up the source-paths and most of the libraries. + +

+ +See also the file eclipse.readme. + +

+ + +

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/changes.html b/ApacheJmeter/docs/changes.html new file mode 100644 index 0000000..4712da9 --- /dev/null +++ b/ApacheJmeter/docs/changes.html @@ -0,0 +1,966 @@ + + + + + + + + + + + + + + + + + + + +Apache JMeter - Changes + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +Changes +
+
+

+ + +
+ + +This page details the changes made in the current version only. + + + +
+ + +Earlier changes are detailed in the + +History of Previous Changes + +. + +
+

+

+Version 2.7 +

+

+New and Noteworthy +

+

+OS Process Sampler +

+

+A new System Sampler that can be used to execute commands on the local machine. + +


+

+ + +

+

+OS Process Sampler results example with DNS lookup command 'dig' + +


+

+ + +

+

+JMS Samplers improvements +

+

+Addition of a "Non Persistent Delivery" option to send "Non-Persistent" (Guaranteed to be delivered at most once. Message loss is not a concern.) JMS messages + +


+

+ + +

+

+Support sending of JMS Object Messages to enable sending Objects unmarshalled from XML by XStream + +


+

+ + +

+

+Enable setting JMS Properties through JMS Publisher sampler + +


+

+ + +

+

+Test Action sampler +

+

+Allow premature exit from a loop + +


+

+ + +

+

+Webservice Sampler improvements +

+

+Add a jmeter property soap.document_cache to control size of Document Cache + +


+

+ + +

+

+Make Maintain HTTP Session configurable + +


+

+ + +

+

+Aggregate graph: Clustered Bar char with average, median, 90% line, min and max columns +

+

+Aggregate graph changes to Clustered Bar chart, add more columns (median, 90% line, min, max) and options, fixed some bugs. + +


+

+ + +

+

+New settings for aggregate graph + +


+

+ + +

+

+Improvements of HTML report design generated by JMeter Ant task in extras folder +

+

+HTML report example + +


+

+ + +

+

+HTML report example with some assertion errors + +


+

+ + +

+

+Mailer Visualizer +

+

+

    + + +
  • +Enable authentication, and connection security with SSL or TLS +
  • + + +
  • +Improve GUI design +
  • + + +
  • +Add internationalisation (i18n) support +
  • + + +
+ + +


+

+ + +

+

+New Visual Indicator of number of ERROR/FATAL messages in logs +

+

+Indicator shows number of ERROR/FATAL messsages in logs, it can be clicked to toggle Log Viewer panel + +


+

+ + +

+

+Dialog box to show detail of a parameter row +

+

+Add a detail button on parameters table to show detail of a Row + +


+

+ + +

+

+Detail box example + +


+

+ + +

+

+Plugin writers +

+

+ +New interface org.apache.jmeter.engine.util.ConfigMergabilityIndicator has been introduced to tell whether a ConfigTestElement can be merged in Sampler (see Bug 53042): +
+ + + +

+public boolean applies(ConfigTestElement configElement);
+
+ + +

+

+New interface org.apache.jmeter.protocol.http.proxy.SamplerCreator to allow plugging HTTP based samplers that differ from default HTTP Samplers through Proxy during Recording Phase (see Bug 52674): +
+ + + +

+public String[] getManagedContentTypes();
+
+ + +
+public HTTPSamplerBase createSampler(HttpRequestHdr request, Map pageEncodings, Map formEncodings);
+
+ + +
+public void populateSampler(HTTPSamplerBase sampler, HttpRequestHdr request, Map pageEncodings, Map formEncodings) throws Exception;
+
+ + +

+

+Known bugs +

+

+The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified). +

+

+Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see Bug 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). + +

+

+Incompatible changes +

+

+ +When doing replacement of User Defined Variables, Proxy will not substitute partial values anymore when "Regexp matching" is used. It will use Perl 5 word matching ("\b") + +

+

+ +In User Defined Variables, Test Plan, HTTP Sampler Arguments Table, Java Request Defaults, JMS Sampler and Publisher, LDAP Request Defaults and LDAP Extended Request Defaults, rows with +empty Name and Value are no more saved. + +

+

+ +JMeter now expands the Test Plan tree to the testplan level and no further and selects the root of the tree. Furthermore default value of onload.expandtree is false. + +

+

+ +Graph Full Results Listener has been removed. + +

+

+ +When calling "Clear All" command, if Log Viewer is displayed its content will be cleared. + +

+

+Bug fixes +

+

+HTTP Samplers and Proxy +

+
    + + +
  • +Bug 52613 - Using Raw Post Body option, text gets encoded +
  • + + +
  • +Bug 52781 - Content-Disposition header garbled even if browser compatible headers is checked (HC4) +
  • + + +
  • +Bug 52796 - MonitorHandler fails to clear variables when starting a new parse +
  • + + +
  • +Bug 52871 - Multiple Certificates not working with HTTP Client 4 +
  • + + +
  • +Bug 52885 - Proxy : Recording issues with HTTPS, cookies starting with secure are partly truncated +
  • + + +
  • +Bug 52886 - Proxy : Recording issues with HTTPS when spoofing is on, secure cookies are not always changed +
  • + + +
  • +Bug 52897 - HTTPSampler : Using PUT method with HTTPClient4 and empty Content Encoding and sending files leads to NullPointerException +
  • + + +
  • +Bug 53145 - HTTP Sampler - function in path evaluated too early +
  • + + +
+

+Other Samplers +

+
    + + +
  • +Bug 51737 - TCPSampler : Packet gets converted/corrupted +
  • + + +
  • +Bug 52868 - BSF language list should be sorted +
  • + + +
  • +Bug 52869 - JSR223 language list currently uses BSF list which is wrong +
  • + + +
  • +Bug 52932 - JDBC Sampler : Sampler is not marked in error in an Exception which is not of class IOException, SQLException, IOException occurs +
  • + + +
  • +Bug 52916 - JDBC Exception if there is an empty user defined variable +
  • + + +
  • +Bug 52937 - Webservice Sampler : Clear Soap Documents Cache at end of Test +
  • + + +
  • +Bug 53027 - Jmeter starts throwing exceptions while using SMTP Sample in a test plan with HTTP Cookie Mngr or HTTP Request Defaults +
  • + + +
  • +Bug 53072 - JDBC PREPARED SELECT statements should return results in variables like non prepared SELECT +
  • + + +
+

+Controllers +

+
    + + +
  • +Bug 52968 - Option Start Next Loop in Thread Group does not mark parent Transaction Sampler in error when an error occurs +
  • + + +
  • +Bug 50898 - IncludeController : NullPointerException loading script in non-GUI mode if Includers use same element name +
  • + + +
+

+Listeners +

+
    + + +
  • +Bug 43450 - Listeners/Savers assume SampleResult count is always 1; fixed Generate Summary Results +
  • + + +
+

+Assertions +

+
    + + +
  • +Bug 52848 - NullPointer in "XPath Assertion" +
  • + + +
+

+Functions +

+
    + + +
+

+I18N +

+
    + + +
  • +Bug 52551 - Function Helper Dialog does not switch language correctly +
  • + + +
  • +Bug 52552 - Help reference only works in English +
  • + + +
+

+General +

+
    + + +
  • +Bug 52639 - JSplitPane divider for log panel should be hidden if log is not activated +
  • + + +
  • +Bug 52672 - Change Controller action deletes all but one child samplers +
  • + + +
  • +Bug 52694 - Deadlock in GUI related to non AWT Threads updating GUI +
  • + + +
  • +Bug 52678 - Proxy : When doing replacement of UserDefinedVariables, partial values should not be substituted +
  • + + +
  • +Bug 52728 - CSV Data Set Config element cannot coexist with BSF Sampler in same Thread Plan +
  • + + +
  • +Bug 52762 - Problem with multiples certificates: first index not used until indexes are restarted +
  • + + +
  • +Bug 52741 - TestBeanGUI default values do not work at second time or later +
  • + + +
  • +Bug 52783 - oro.patterncache.size property never used due to early init +
  • + + +
  • +Bug 52789 - Proxy with Regexp Matching can fail with NullPointerException in Value Replacement if value is null +
  • + + +
  • +Bug 52645 - Recording with Proxy leads to OutOfMemory +
  • + + +
  • +Bug 52679 - User Parameters columns narrow +
  • + + +
  • +Bug 52843 - Sample headerSize and bodySize not being accumulated for subsamples +
  • + + +
  • +Bug 52967 - The function __P() couldn't use default value when running with remote server in GUI mode. +
  • + + +
  • +Bug 50799 - Having a non-HTTP sampler in a http test plan prevents multiple header managers from working +
  • + + +
  • +Bug 52997 - Jmeter should not exit without saving Test Plan if saving before exit fails +
  • + + +
  • +Bug 53136 - Catching Throwable needs to be carefully handled +
  • + + +
+

+Improvements +

+

+HTTP Samplers +

+
    + + +
+

+Other samplers +

+
    + + +
  • +Bug 52775 - JMS Publisher : Add Non Persistent Delivery option +
  • + + +
  • +Bug 52810 - Enable setting JMS Properties through JMS Publisher sampler +
  • + + +
  • +Bug 52938 - Webservice Sampler : Add a jmeter property soap.document_cache to control size of Document Cache +
  • + + +
  • +Bug 52939 - Webservice Sampler : Make MaintainSession configurable +
  • + + +
  • +Bug 53073 - Allow to assign the OUT result of a JDBC CALLABLE to JMeter variables +
  • + + +
  • +Bug 53164 - New System Sampler +
  • + + +
  • +Bug 53172 - OS Process Sampler - allow specification of Environment Variables +
  • + + +
  • +Bug 52936 - JMS Publisher : Support sending of JMS Object Messages +
  • + + +
+

+Controllers +

+
    + + +
+

+Listeners +

+
    + + +
  • +Bug 52603 - MailerVisualizer : Enable SSL , TLS and Authentication +
  • + + +
  • +Bug 52698 - Remove Graph Full Results Listener +
  • + + +
  • +Bug 53070 - Change Aggregate graph to Clustered Bar chart, add more columns (median, 90% line, min, max) and options, fixed some bugs +
  • + + +
  • +Bug 53246 - Mailer Visualizer: improve GUI design and I18N +
  • + + +
+

+Timers, Assertions, Config, Pre- & Post-Processors +

+
    + + +
+

+Functions +

+
    + + +
+

+I18N +

+
    + + +
  • +Mailer Visualizer has been internationalized. French translation added. (see Bug 53246) +
  • + + +
+

+General +

+
    + + +
  • +Bug 45839 - Test Action : Allow premature exit from a loop +
  • + + +
  • +Bug 52614 - MailerModel.sendMail has strange way to calculate debug setting +
  • + + +
  • +Bug 52782 - Add a detail button on parameters table to show detail of a Row +
  • + + +
  • +Bug 52674 - Proxy : Add a Sampler Creator to allow plugging HTTP based samplers using potentially non textual POST Body (AMF, Silverlight...) and customizing them for others +
  • + + +
  • +Bug 52934 - GUI : Open Test plan with the tree expanded to the testplan level and no further and select the root of the tree +
  • + + +
  • +Bug 52941 - Improvements of HTML report design generated by JMeter Ant task extra +
  • + + +
  • +Bug 53042 - Introduce a new method in Sampler interface to allow Sampler to decide wether a config element applies to Sampler +
  • + + +
  • +Bug 52771 - Documentation : Added RSS feed on JMeter Home page under link "Subscribe to What's New" +
  • + + +
  • +Bug 42784 - Show the number of errors logged in the GUI +
  • + + +
  • +Bug 53256 - Make Clear All command clean LogViewer content +
  • + + +
  • +Bug 53261 - Make "Error/fatal" counter added in Bug 42784 open Log Viewer panel when Warn Indicator is clicked +
  • + + +
+

+Non-functional changes +

+
    + + +
  • +Upgraded to rhino 1.7R3 (was js-1.7R2.jar). +Note: the Maven coordinates for the jar were changed from rhino:js to org.mozilla:rhino. +This does not affect JMeter directly, but might cause problems if using JMeter in a Maven project +with other code that depends on an earlier version of the Rhino Javascript jar. + +
  • + + +
  • +Bug 52675 - Refactor Proxy and HttpRequestHdr to allow Sampler Creation by Proxy +
  • + + +
  • +Bug 52680 - Mention version in which function was introduced +
  • + + +
  • +Bug 52788 - HttpRequestHdr : Optimize code to avoid useless work +
  • + + +
  • +JMeter Ant (ant-jmeter-1.1.1.jar) task was upgraded from 1.0.9 to 1.1.1 +
  • + + +
  • +Updated to commons-io 2.2 (from 2.1) +
  • + + +
  • +Bug 53129 - Upgrade XStream from 1.3.1 to 1.4.2 +
  • + + +
  • +Updated to httpcomponents-client 4.1.3 (from 4.1.2) +
  • + + +
  • +Updated JMeter distributed testing guide (jmeter_distributed_testing_step_by_step.pdf). Changes source format to OpenOffice odt (from sxw) +
  • + + +
+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/changes_history.html b/ApacheJmeter/docs/changes_history.html new file mode 100644 index 0000000..fd02e41 --- /dev/null +++ b/ApacheJmeter/docs/changes_history.html @@ -0,0 +1,9872 @@ + + + + + + + + + + + + + + + + + + + +Apache JMeter - History of Previous Changes + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +History of Previous Changes +
+
+

+ + +
+ + +This page details the changes made in previous versions only. + + + +
+ + +Current changes are detailed in + +Changes + +. + +
+

+

+ +Changes sections are chronologically ordered from top (most recent) to bottom +(least recent) + +

+

+Version 2.6 +

+

+New and Noteworthy +

+

+Toolbar +

+

+A new toolbar on JMeter's main window + +


+

+ + +

+

+JMeter start test button +

+

+A new menu option and button allow to start a test ignoring the Pause Timers + +


+

+ + +

+

+JMeter GUI Look and Feel +

+

+Allow System or CrossPlatform LAF to be set from options menu + +


+

+ + +

+

+JMeter GUI - duplicate node +

+

+Add "duplicate node" in context menu + +


+

+ + +

+

+JMeter tree view - search facility +

+

+Functionality to search by keyword in Samplers Tree View + +


+

+ + +

+

+HTTP Request - raw request pane +

+

+Improve HTTP Request GUI to better show parameters without name (GWT RPC request or SOAP request for example) + +


+

+ + +

+

+HTTP Request - other changes +

+

+

    + + +
  • +Allow multiple selection in arguments panel +
  • + + +
  • +Allow to add (paste) entries from the clipboard to an arguments list +
  • + + +
  • +Ability to move variables up or down in HTTP Request +
  • + + +
+ + +


+

+ + +

+

+HTTP Request - file protocol +

+

+Better support for file: protocol in HTTP sampler + +


+

+ + +

+

+Retrieve embedded resources with file: protocol + +


+

+ + +

+

+HTTP Request - Ignore embedded resources failed +

+

+Enable "ignore failed" for embedded resources + +


+

+ + +

+

+Parent success with a embedded resource failed + +


+

+ + +

+

+View Results in Table - child sample display +

+

+Add option to TableVisualiser to display child samples instead of parent + +


+

+ + +

+

+Key Store - multiple certificates +

+

+Allowing multiple certificates (JKS) + +


+

+ + +

+

+Aggregate graph improvements +

+

+Some improvements on Aggregate Graph Listener: + +

    +
  • +new GUI for settings +
  • + + +
  • +dynamic graph size +
  • + + +
  • +allow to change fonts for title graph and legend +
  • + + +
  • +allow to change bar color (background and text values) +
  • + + +
  • +allow to draw or not bars outlines +
  • + + +
  • +allow to select only some samplers by a regexp filter +
  • + + +
  • +allow to define Y axis maximum scale +
  • + + +
+ + +


+

+ + +

+

+Aggregate Graph bar + +


+

+ + +

+

+Counter - new reset option +

+

+Add an option to reset counter on each Thread Group iteration + +


+

+ + +

+

+Functions +

+

+

    + + +
  • +Add a new function __RandomString to generate random Strings +
  • + + +
  • +Add a new function __TestPlanName returning the name of the current "Test Plan" +
  • + + +
  • +Add a new function __machineIP returning IP address +
  • + + +
  • +Add a new function __jexl2 to support Jexl2 +
  • + + +
+ + +


+

+ + +

+

+User Defined Variable improvements +

+

+

    +
  • +Add a comment field in User Defined Variables +
  • + + +
  • +Allow to add (paste) entries from the clipboard to an arguments list +
  • + + +
  • +Ability to move up or down variables in User Defined Variables +
  • + + +
+ + +


+

+ + +

+

+View Results Tree +

+

+In View Results Tree rather than showing just a message if the results are to big, show as much of the result as are configured + +


+

+ + +

+

+Controllers - change elements +

+

+Add ability to Change Controller elements + +


+

+ + +

+

+JDBC pre- and post-processor +

+

+Add JDBC pre- and post-processor + +


+

+ + +

+

+JDBC transaction isolation option +

+

+Allow to set the transaction isolation in the JDBC Connection Configuration + +


+

+ + +

+

+Poisson Timer +

+

+Add a Poisson based timer + +


+

+ + +

+

+GUI and OS interaction +

+

+Support for file Drag and Drop. + +


+

+ + +

+

+Confirm Remove Dialog box +

+

+Add a dialog box to confirm removing the element(s) when Remove action is called + +


+

+ +The dialogue can be skipped by setting the JMeter property + +confirm.delete.skip=true + + + +

+

+Remote batching support +

+

+Use external store to hold samples during distributed testing, +Added DiskStore remote sample sender: like Hold, but saves samples to disk until end of test + +


+

+ + +

+

+JMS Subscriber sampler +

+

+With JMS Subscriber, ability to use Selectors + +


+

+ + +

+

+New Logger Panel +

+

+A new Log Viewer has been added to the GUI and can be enabled from menu Options > Log Viewer: + +


+

+ + +

+

+This Log Viewer shows the jmeter.log file, and useful (for example) to debug BeanShell/BSF scripts: + +


+

+ + +

+

+The menu item Options / Choose Language is now fully functional +

+

+ +The menu item Options / Choose Language now changes all the displayed text to the new language provided +all messages are translated. You can help on this by translating into your language. + +

+

+Legacy JMX and JTL Avalon format support restored +

+

+ +Support for reading/writing the original Avalon XML format of JMX (script) and JTL (sample result) files was dropped in JMeter version 2.4. +JMeter can now read the Avalon format files again, however there is no support for saving files in the old format. + +

+

+JMeter jars available from Maven repository +

+

+ +JMeter jars are now available from Maven repository. + +

+

+Known bugs +

+

+ +The Include Controller has some problems in non-GUI mode (see Bugs 40671, 41286, 44973, 50898). +In particular, it can cause a NullPointerException if there are two include controllers with the same name. + +

+

+The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified). +

+

+Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see Bug 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). + +

+

+Incompatible changes +

+

+ +JMeter versions since 2.1 failed to create a container sample when loading embedded resources. +This has been corrected; can still revert to the Bug 51939 behaviour by setting the following property: + + +httpsampler.separate.container=false + + + +

+

+ +Mirror server now uses default port 8081, was 8080 before 2.5.1. + +

+

+ +TCP Sampler handles SocketTimeoutException, SocketException and InterruptedIOException differently since 2.6, when +these occurs, Sampler is marked as failed. + +

+

+ +Sample Sender implementations now resolve their configuration on Client side since 2.6. +This behaviour can be changed with property sample_sender_client_configured (set it to false). + +

+

+ +The HTTP User Parameter Modifier test element has been removed; it has been deprecated for a long time. + +

+

+Bug fixes +

+

+HTTP Samplers and Proxy +

+
    + + +
  • +Bug 51932 - CacheManager does not handle cache-control header with any attributes after max-age +
  • + + +
  • +Bug 51918 - GZIP compressed traffic produces errors, when multiple connections allowed +
  • + + +
  • +Bug 51939 - Should generate new parent sample if necessary when retrieving embedded resources +
  • + + +
  • +Bug 51942 - Synchronisation issue on CacheManager when Concurrent Download is used +
  • + + +
  • +Bug 51957 - Concurrent get can hang if a task does not complete +
  • + + +
  • +Bug 51925 - Calling Stop on Test leaks executor threads when concurrent download of resources is on +
  • + + +
  • +Bug 51980 - HtmlParserHTMLParser double-counts images used in links +
  • + + +
  • +Bug 52064 - OutOfMemory Risk in CacheManager +
  • + + +
  • +Bug 51919 - Random ConcurrentModificationException or NoSuchElementException in CookieManager#removeMatchingCookies when using Concurrent Download +
  • + + +
  • +Bug 52126 - HttpClient4 does not clear cookies between iterations +
  • + + +
  • +Bug 52129 - Reported Body Size is wrong when using HTTP Client 4 and Keep Alive connection +
  • + + +
  • +Bug 52137 - Problems with HTTP Cache Manager +
  • + + +
  • +Bug 52221 - Nullpointer Exception with use Retrieve Embedded Resource without HTTP Cache Manager +
  • + + +
  • +Bug 52310 - variable in IPSource failed HTTP request if "Concurrent Pool Size" is enabled +
  • + + +
  • +Bug 52371 - API Incompatibility - Methods in HTTPSampler2 now require PostMethod instead of HttpMethod[Base]. Reverted to original types. +
  • + + +
  • +Bug 49950 - Proxy : IndexOutOfBoundsException when recording with Proxy server +
  • + + +
  • +Bug 52409 - HttpSamplerBase#errorResult modifies sampleResult passed as parameter; +fix code which assumes that a new instance is created (i.e. when adding a sub-sample) + +
  • + + +
  • +Bug 52507 - Delete Http User Parameters modifier (deprecated, obsolete) +
  • + + +
+

+Other Samplers +

+
    + + +
  • +Bug 51996 - JMS Initial Context leak newly created Context when Multiple Thread enter InitialContextFactory#lookupContext at the same time +
  • + + +
  • +Bug 51691 - Authorization does not work for JMS Publisher and JMS Subscriber +
  • + + +
  • +Bug 52036 - Durable Subscription fails with ActiveMQ due to missing clientId field +
  • + + +
  • +Bug 52044 - JMS Subscriber used with many threads leads to javax.naming.NamingException: Something already bound with ActiveMQ +
  • + + +
  • +Bug 52072 - LengthPrefixedBinaryTcpClientImpl may end a sample prematurely +
  • + + +
  • +Bug 52390 - AbstractJDBCTestElement:Memory leak and synchronization issue in perConnCache +
  • + + +
+

+Controllers +

+
    + + +
  • +Bug 51865 - Infinite loop inside thread group does not work properly if "Start next loop after a Sample error" option set +
  • + + +
  • +Bug 51868 - A lot of exceptions in jmeter.log while using option "Start next loop" for thread +
  • + + +
  • +Bug 51866 - Counter under loop doesn't work properly if "Start next loop on error" option set for thread group +
  • + + +
  • +Bug 52296 - TransactionController + Children ThrouputController or InterleaveController leads to ERROR sampleEnd called twice java.lang.Throwable: Invalid call sequence when TPC does not run sample +
  • + + +
  • +Bug 52330 - With next-Loop-On-Error after error samples are not executed in next loop +
  • + + +
+

+Listeners +

+
    + + +
  • +Bug 52357 - View results in Table does not allow for multiple result samples +
  • + + +
  • +Bug 52491 - Incorrect parsing of Post data parameters in Tree Listener / Http Request view +
  • + + +
+

+Assertions +

+
    + + +
  • +Bug 52519 - XMLSchemaAssertion uses JMeter JVM file.encoding instead of response encoding +
  • + + +
+

+Functions +

+
    + + +
  • +The CRLF example for the char function was wrong; CRLF=(0xD,0xA), not (0xC,0xA) +
  • + + +
+

+I18N +

+
    + + +
+

+General +

+
    + + +
  • +Bug 51937 - JMeter does not handle missing TestPlan entry well +
  • + + +
  • +Bug 51988 - CSV Data Set Configuration does not resolve default delimiter for header parsing when variables field is empty +
  • + + +
  • +Bug 52003 - View Results Tree "Scroll automatically" does not scroll properly in case nodes are expanded +
  • + + +
  • +Bug 27112 - User Parameters should use scrollbars +
  • + + +
  • +Bug 52029 - Command-line shutdown only gets sent to last engine that was started +
  • + + +
  • +Bug 52093 - Toolbar ToolTips don't switch language +
  • + + +
  • +Bug 51733 - SyncTimer is messed up if you a interrupt a test plan +
  • + + +
  • +Bug 52118 - New toolbar : shutdown and stop buttons not disabled when no test is running +
  • + + +
  • +Bug 52125 - StatCalculator.addAll(StatCalculator calc) joins incorrect if there are more samples with the same response time in one of the TreeMap +
  • + + +
  • +Bug 52339 - JMeter Statistical mode in distributed testing shows wrong response time +
  • + + +
  • +Bug 52215 - Confusing synchronization in StatVisualizer, SummaryReport ,Summariser and issue in StatGraphVisualizer +
  • + + +
  • +Bug 52216 - TableVisualizer : currentData field is badly synchronized +
  • + + +
  • +Bug 52217 - ViewResultsFullVisualizer : Synchronization issues on root and treeModel +
  • + + +
  • +Bug 43294 - XPath Extractor namespace problems +
  • + + +
  • +Bug 52224 - TestBeanHelper does not support NOT_UNDEFINED == Boolean.FALSE +
  • + + +
  • +Bug 52279 - Switching to another language loses icons in Tree and logs error Can't obtain GUI class from ... +
  • + + +
  • +Bug 52280 - The menu item Options / Choose Language does not change all the displayed text to the new language +
  • + + +
  • +Bug 52376 - StatCalculator#addValue(T val, int sampleCount) should use long, not int +
  • + + +
  • +Bug 49374 - Encoding of embedded element URLs depend on the file.encoding property +
  • + + +
  • +Bug 52399 - URLRewritingModifier uses default file.encoding to match text content +
  • + + +
  • +Bug 50438 - code calculates average with integer math, expecting double value +
  • + + +
  • +Bug 52469 - Changes in Support of SSH-Tunneling of RMI traffic for Remote Testing +
  • + + +
  • +Bug 52466 - Upgrade Test Plan feature : NameUpdater does not upgrade properties +
  • + + +
  • +Bug 52503 - Unify File->Close and Window close file saving behaviour +
  • + + +
  • +Bug 52537 - Help does not scroll to correct anchor when file is first loaded +
  • + + +
+

+Improvements +

+

+HTTP Samplers +

+
    + + +
  • +Bug 51981 - Better support for file: protocol in HTTP sampler +
  • + + +
  • +Bug 52033 - Allowing multiple certificates (JKS) +
  • + + +
  • +Bug 52352 - Proxy : Support IPv6 URLs capture +
  • + + +
  • +Bug 44301 - Enable "ignore failed" for embedded resources +
  • + + +
+

+Other samplers +

+
    + + +
  • +Bug 51419 - JMS Subscriber: ability to use Selectors +
  • + + +
  • +Bug 52088 - JMS Sampler : Add a selector when REQUEST / RESPONSE is chosen +
  • + + +
  • +Bug 52104 - TCP Sampler handles badly errors +
  • + + +
  • +Bug 52087 - TCPClient interface does not allow for partial reads +
  • + + +
  • +Bug 52115 - SOAP/XML-RPC should not send a POST request when file to send is not found +
  • + + +
  • +Bug 40750 - TCPSampler : Behaviour when sockets are closed by remote host +
  • + + +
  • +Bug 52396 - TCP Sampler in "reuse connection mode" reuses previous sampler's connection even if it's configured with other host, port, user or password +
  • + + +
  • +Bug 52048 - BSFSampler, BSFPreProcessor and BSFPostProcessor should share the same GUI +
  • + + +
+

+Controllers +

+
    + + +
+

+Listeners +

+
    + + +
  • +Bug 52022 - In View Results Tree rather than showing just a message if the results are to big, show as much of the result as are configured +
  • + + +
  • +Bug 52201 - Add option to TableVisualiser to display child samples instead of parent +
  • + + +
  • +Bug 52214 - Save Responses to a file - improve naming algorithm +
  • + + +
  • +Bug 52340 - Allow remote sampling mode to be changed at run-time +
  • + + +
  • +Bug 52452 - Improvements on Aggregate Graph Listener (GUI and settings) +
  • + + +
  • +Resurrected OldSaveService to allow reading Avalon format JTL (result) files +
  • + + +
+

+Timers, Assertions, Config, Pre- & Post-Processors +

+
    + + +
  • +Bug 52128 - Add JDBC pre- and post-processor +
  • + + +
  • +Bug 52183 - SyncTimer could be improved (performance+reliability) +
  • + + +
  • +Bug 52317 - Counter : Add option to reset counter on each Thread Group iteration +
  • + + +
  • +Bug 37073 - Add a Poisson based timer +
  • + + +
  • +Bug 52497 - Improve DebugSampler and DebugPostProcessor +
  • + + +
+

+Functions +

+
    + + +
  • +Bug 52006 - Create a function RandomString to generate random Strings +
  • + + +
  • +Bug 52016 - It would be useful to support Jexl2 +
  • + + +
  • +__char() function now supports octal values +
  • + + +
  • +New function __machineIP returning IP address +
  • + + +
  • +Bug 51091 - New function returning the name of the current "Test Plan" +
  • + + +
+

+I18N +

+
    + + +
+

+General +

+
    + + +
  • +Bug 51892 - Default mirror port should be different from default proxy port +
  • + + +
  • +Bug 51817 - Moving variables up and down in User Defined Variables control +
  • + + +
  • +Bug 51876 - Functionality to search in Samplers TreeView +
  • + + +
  • +Bug 52019 - Add menu option to Start a test ignoring Pause Timers +
  • + + +
  • +Bug 52027 - Allow System or CrossPlatform LAF to be set from options menu +
  • + + +
  • +Bug 52037 - Remember user-set LaF over restarts. +
  • + + +
  • +Bug 51861 - Improve HTTP Request GUI to better show parameters without name (GWT RPC requests for example) (UNDER DEVELOPMENT) +
  • + + +
  • +Bug 52040 - Add a toolbar in JMeter main window +
  • + + +
  • +Bug 51816 - Comment Field in User Defined Variables control. +
  • + + +
  • +Bug 52052 - Using a delimiter to separate result-messages for JMS Subscriber +
  • + + +
  • +Bug 52103 - Add automatic scrolling option to table visualizer +
  • + + +
  • +Bug 52097 - Save As should point to same folder that was used to open a file if MRU list is used +
  • + + +
  • +Bug 52085 - Allow multiple selection in arguments panel +
  • + + +
  • +Bug 52099 - Allow to set the transaction isolation in the JDBC Connection Configuration +
  • + + +
  • +Bug 52116 - Allow to add (paste) entries from the clipboard to an arguments list +
  • + + +
  • +Bug 52160 - Don't display TestBeanGui items which are flagged as hidden +
  • + + +
  • +Bug 51886 - SampleSender configuration resolved partly on client and partly on server +
  • + + +
  • +Bug 52161 - Enable plugins to add own translation rules in addition to upgrade.properties. +Loads any additional properties found in META-INF/resources/org.apache.jmeter.nameupdater.properties files +
  • + + +
  • +Bug 42538 - Add "duplicate node" in context menu +
  • + + +
  • +Bug 46921 - Add Ability to Change Controller elements +
  • + + +
  • +Bug 52240 - TestBeans should support Boolean, Integer and Long +
  • + + +
  • +Bug 52241 - GenericTestBeanCustomizer assumes that the default value is the empty string +
  • + + +
  • +Bug 52242 - FileEditor does not allow output to be saved in a File +
  • + + +
  • +Bug 51093 - when loading a selection previously stored by "Save Selection As", show the file name in the blue window bar +
  • + + +
  • +Bug 50086 - Password fields not Hidden in JMS Publisher, JMS Subscriber, Mail Reader sampler, SMTP sampler and Database Configuration +
  • + + +
  • +Bug 29352 - Use external store to hold samples during distributed testing, Added DiskStore remote sample sender: like Hold, but saves samples to disk until end of test. +
  • + + +
  • +Bug 52333 - Reduce overhead in calculating SampleResult#nanoTimeOffset +
  • + + +
  • +Bug 52346 - Shutdown detects if there are any non-daemon threads left which prevent JVM exit. +
  • + + +
  • +Bug 52281 - Support for file Drag and Drop +
  • + + +
  • +Bug 52471 - Improve Mirror Server performance by Using Pool of threads instead of launching a Thread for each request +
  • + + +
  • +Resurrected OldSaveService to allow reading Avalon format JMX files (removed in 2.4) +
  • + + +
  • +Add a dialog box to confirm removing the element(s) when Remove action is called +
  • + + +
  • +Bug 41788 - Log viewer (console window) needed as an option +
  • + + +
  • +Add option to change the pause time (default 2000ms) in the daemon thread which checks for successful JVM exit. +The thread is not now started unless the pause time is greater than 0. + +
  • + + +
+

+Non-functional changes +

+
    + + +
  • +fixes to build.xml: support scripts; localise re-usable property names +
  • + + +
  • +Bug 51923 - Counter function bug or documentation issue ? (fixed docs) +
  • + + +
  • +Update velocity.jar to 1.7 (from 1.6.2) +
  • + + +
  • +Update js.jar to 1.7R3 (from 1.6R5) +
  • + + +
  • +Update commons-codec 1.5 => 1.6 +
  • + + +
  • +Update commons-io 2.0.1 => 2.1 +
  • + + +
  • +Update commons-jexl 2.0.1 => 2.1.1 +
  • + + +
  • +Update jdom 1.1 => 1.1.2 +
  • + + +
  • +Update junit 4.9 => 4.10 +
  • + + +
  • +Bug 51954 - Generated documents include entries which cause extra blank lines +
  • + + +
  • +Bug 52075 - JMeterProperty.clone() currently returns Object; it should return JMeterProperty +
  • + + +
  • +Updated httpcore to 4.1.4 +
  • + + +
  • +Bug 49753 - Please publish jMeter artifacts on Maven central repository +
  • + + +
+

+Version 2.5.1 +

+

+Summary of main changes +

+
    + + +
  • +HttpClient4 sampler now re-uses connections properly (previously it would use one per sample, which could quickly cause resource exhaustion). +
  • + + +
  • +Various fixes to JMS samplers +
  • + + +
  • +Functions are no longer spuriously invoked when used with a Configuration element +
  • + + +
  • +WebService sampler GUI has been re-organized for better design and more user-friendliness. Some improments on WSDL configuration assistant +
  • + + +
  • +Better handling of test shutdown. System.exit now only called if there is no other option; even this can be disabled. +
  • + + +
+

+Known bugs +

+

+ +The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. + +

+

+The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified). +

+

+The If Controller may cause an infinite loop if the condition is always false from the first iteration. +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). + +

+

+ +The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. + +

+

+Incompatible changes +

+

+ +The HttpClient4 and Commons HttpClient 3.1 samplers previously used a retry count of 3. +This has been changed to default to 1, to be compatible with the Java implementation. +The retry count can be overridden by setting the relevant JMeter property, for example: + +

+
+httpclient4.retrycount=3
+httpclient3.retrycount=3
+
+
+ + +

+

+Bug fixes +

+

+HTTP Samplers and Proxy +

+
    + + +
  • +Fix HttpClient 4 sampler so it reuses HttpClient instances and connections where possible. +
  • + + +
  • +Temporary fix to HC4 sampler to work round HTTPCLIENT-1120. +
  • + + +
  • +Bug 51863 - Lots of ESTABLISHED connections with HttpClient 4 implementation (vs HttpClient 3.1 impl) +
  • + + +
  • +Bug 51750 - Retrieve all embedded resources doesn't follow IFRAME +
  • + + +
  • +Bug 51752 - HTTP Cache is broken when using "Retrieve all embedded resources" with concurrent pool +
  • + + +
  • +Bug 39219 - HTTP Server: You can't stop it after File->Open +
  • + + +
  • +Bug 51775 - Port number duplicates in Host header when capturing by HttpClient (3.1 and 4.x) +
  • + + +
  • +Bug 50617 - Monitor Results legend show "dead" server although values from the server are retrieved +
  • + + +
+

+Other Samplers +

+
    + + +
  • +Bug 50424 - Web Methods drop down list box inconsistent +
  • + + +
  • +Bug 43293 - Java Request fields not cleared when creating new sampler +
  • + + +
  • +Bug 51830 - Webservice Soap Request triggers too many popups when Webservice WSDL URL is down +
  • + + +
  • +WebService(SOAP) request - add a connect timeout to get the wsdl used to populate Web Methods when server doesn't response +
  • + + +
  • +Bug 51841 - JMS : If an error occurs in ReceiveSubscriber constructor or Publisher, then Connections will stay open +
  • + + +
  • +Bug 51691 - Authorization does not work for JMS Publisher and JMS Subscriber +
  • + + +
  • +Bug 51840 - JMS : Cache of InitialContext has some issues +
  • + + +
  • +Bug 47888 - JUnit Sampler re-uses test object +
  • + + +
+

+Controllers +

+
    + + +
  • +If Controller - Fixed two regressions introduced by bug 50032 (see bug 50618 too) +
  • + + +
  • +If Controller - Catches a StackOverflowError when a condition returns always false (after at least one iteration with return true) See bug 50618 +
  • + + +
  • +Bug 51869 - NullPointer Exception when using Include Controller +
  • + + +
+

+Listeners +

+
    + + +
+

+Assertions +

+
    + + +
+

+Functions +

+
    + + +
  • +Bug 48943 - Functions are invoked additional times when used in combination with a Config Element +
  • + + +
+

+I18N +

+
    + + +
  • +WebService(SOAP) request - add I18N for some labels +
  • + + +
+

+General +

+
    + + +
  • +Bug 51831 - Cannot disable UDP server or change the maximum UDP port +
  • + + +
  • +Bug 51821 - Add short-cut for Enabling / Disabling (sub)tree or branches in test plan. +
  • + + +
  • +Bug 47921 - Variables not released for GC after JMeterThread exits. +
  • + + +
  • +Bug 51839 - "... end of run" printed prematurely +
  • + + +
  • +Bug 51847 - Some Junit tests are Locale sensitive and fail if Locale is different from US +
  • + + +
  • +Bug 51855 - Parent samples may have slightly inaccurate elapsed times +
  • + + +
  • +Bug 51880 - The shutdown command is not working if I invoke it before all the thread are started +
  • + + +
  • +Remote Shut host menu item was not being enabled. +
  • + + +
  • +Bug 51888 - Occasional deadlock when stopping a testplan +
  • + + +
+

+Improvements +

+

+HTTP Samplers +

+
    + + +
  • +Bug 51380 - Control reuse of cached SSL Context from iteration to iteration +
  • + + +
  • +Bug 51882 - HTTPHC3Client uses a default retry count of 3, make it configurable; default is now 1 +
  • + + +
  • +Change the default HttpClient 4 sampler retry count to 1 +
  • + + +
+

+Other samplers +

+
    + + +
  • +Beanshell Sampler now supports Interruptible interface +
  • + + +
  • +Bug 51605 - WebService(SOAP) Request - WebMethod field value changes surreptitiously for all the requests when a value is selected in a request +
  • + + +
  • +WebService(SOAP) Request - Reorganized GUI for better design and more user-friendliness +
  • + + +
+

+Controllers +

+
    + + +
+

+Listeners +

+
    + + +
  • +Bug 42246 - Need for a 'auto-scroll' option in "View Results Tree" and "Assertion Results" +
  • + + +
  • +View Results Tree: Regexp Tester - little improvements on user interface +
  • + + +
+

+Timers, Assertions, Config, Pre- & Post-Processors +

+
    + + +
  • +Bug 51885 - Allow a JMeter Variable as input to XPathExtractor +
  • + + +
+

+Functions +

+
    + + +
+

+I18N +

+
    + + +
+

+General +

+
    + + +
  • +Bug 51822 - (part 1) save 1 invocation of GuiPackage#getCurrentGui +
  • + + +
  • +Added AsynchSampleSender which sends samples from server to client asynchronously. +
  • + + +
  • +Upgraded to htmlparser 2.1; JavaMail 1.4.4; JUnit 4.9 +
  • + + +
+

+Non-functional changes +

+
    + + +
  • +Bug 49976 - FormCharSetFinder visibility is default instead of public. +
  • + + +
  • +Bug 50917 - Property CookieManager.save.cookies not honored when set from test plan +
  • + + +
  • +Improve error logging when Javascript errors are detected. +
  • + + +
  • +Updated documentation footer +
  • + + +
+

+Version 2.5 +

+

+Summary of main changes +

+
    + + +
  • +The HTTP implementation can now be selected at run-time, and JMeter now also supports Apache HttpComponents HttpClient 4.x. +Note that Commons HttpClient 3.1 is no longer actively developed, and support may be removed from JMeter in a future release. + +
  • + + +
  • +The HTTP sampler now allows concurrent downloads of embedded resources in an HTML page +
  • + + +
  • +The HTTP Sampler can now report the size of a request before decompression. +
  • + + +
  • +The JMS and Mail samplers have been improved. +
  • + + +
  • +The new Test Fragment Test Element makes using Include Controllers easier +
  • + + +
  • +There are various improvements to the View Results Tree Listener +
  • + + +
  • +Bug 30563 - Thread Group should have a start next loop option on Sample Error +
  • + + +
  • +There are two new Thread Group types - setUp and tearDown - which are run before and after the main Thread groups. +
  • + + +
  • +Client-Server mode now supports external stop/shutdown via UDP +
    + + +multiple JMeter server instances can be started on the same host without needing to change the port property. +
  • + + +
  • +Bug 50516 - "Host" header in HTTP Header Manager is not included in generated HTTP request +
  • + + +
+

+ + +

    + + +
+ + +

+

+Known bugs +

+

+ +The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. + +

+

+Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified). +

+

+ +The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. + +

+

+Incompatible changes +

+

+ +Unsupported methods are no longer converted to GET by the Commons HttpClient sampler. + +

+

+ +Removed method public static long currentTimeInMs(). +This has been replaced by the instance method public long currentTimeInMillis(). + +

+

+ +ProxyControl.getSamplerTypeName() now returns a String rather than an int. +This is internal to the workings of the JMeter Proxy & its GUI, so should not affect any user code. + +

+

+Bug fixes +

+

+HTTP Samplers and Proxy +

+
    + + +
  • +Bug 50178 - HeaderManager added as child of Thread Group can create concatenated HeaderManager names and OutOfMemoryException +
  • + + +
  • +Bug 50392 - value is trimmed when sending the request in Multipart +
  • + + +
  • +Bug 50686 - HeaderManager logging too verbose when merging instances +
  • + + +
  • +Bug 50963 - AjpSampler throws java.lang.StringIndexOutOfBoundsException +
  • + + +
  • +Bug 50516 - "Host" header in HTTP Header Manager is not included in generated HTTP request +
  • + + +
  • +Bug 50544 - In Apache Common Log the HEAD requests cause problems. +
  • + + +
  • +Bug 51268 - HTTPS request through an invalid proxy causes NullPointerException and does not show in result tree. +Rather than delegating to the JMeter thread handler for "unexpected" failures, ensure all Exceptions generate a sample error. + +
  • + + +
  • +Bug 51275 - Cookie Panel clearGui() sets incorrect default policy in Java 1.6 +
  • + + +
+

+Other Samplers +

+
    + + +
  • +Bug 50173 - JDBCSampler discards ResultSet from a PreparedStatement +
  • + + +
  • +Ensure JSR223 Sampler has access to the current SampleResult +
  • + + +
  • +Bug 50977 - Unable to set TCP Sampler for individual samples +
  • + + +
+

+Controllers +

+
    + + +
  • +Bug 50032 - Last_Sample_Ok along with other controllers doesnt work correctly when the threadgroup has multiple loops +
  • + + +
  • +Bug 50080 - Transaction controller incorrectly creates samples including timer duration +
  • + + +
  • +Bug 50134 - TransactionController : Reports bad response time when it contains other TransactionControllers +
  • + + +
+

+Listeners +

+
    + + +
  • +Bug 50367 - Clear / Clear all in View results tree does not clear selected element +
  • + + +
+

+Assertions +

+
    + + +
  • +Bug 51488 - Assertion: Variable name scope is shared among all assertions (and Bug 51255) +
  • + + +
+

+Functions +

+
    + + +
  • +Bug 50568 - Function __FileToString(): Could not read file when encoding option is blank/empty +
  • + + +
+

+I18N +

+
    + + +
  • +Bug 50811 - Incomplete Spanish translation +
  • + + +
+

+General +

+
    + + +
  • +Bug 49734 - Null pointer exception on stop Threads command (Run>Stop) +
  • + + +
  • +Bug 49666 - CSV Header read as data after EOF +
  • + + +
  • +Bug 45703 - Synchronizing Timer +
  • + + +
  • +Bug 50088 - fix getAvgPageBytes in SamplingStatCalculator so it returns what it should +
  • + + +
  • +Bug 50203 Cannot set property "jmeter.save.saveservice.default_delimiter=\t" +
  • + + +
  • +mirror-server.sh - fix classpath to use : separator (not ;) +
  • + + +
  • +Bug 50286 - URL Re-writing Modifier: extracted jsessionid value is incorrect when is between XML tags +
  • + + +
  • + +System.nanoTime() tends to drift relative to System.currentTimeMillis(). +Change SampleResult to recalculate offset each time. +Also enable reversion to using System.currentTimeMillis() only. + +
  • + + +
  • +Bug 50425 - Remove thread groups from Controller add menu +
  • + + +
  • + +Bug 50675 - CVS Data Set Config incompatible with Remote Start +Fixed RMI startup to provide location of JMX file relative to user.dir. + +
  • + + +
  • +Bug 50221 - Renaming elements in the tree does not resize label +
  • + + +
  • +Bug 51002 - Stop Thread if CSV file is not available. JMeter now treats IOError as EOF. +
  • + + +
  • +Define sun.net.http.allowRestrictedHeaders=true by default. This fixes Bug 51238. +
  • + + +
  • +Bug 51645 - CSVDataSet does not read UTF-8 files when file.encoding is UTF-8 +
  • + + +
+

+Improvements +

+

+HTTP Samplers +

+
    + + +
  • +AJP Sampler now implements Interruptible +
  • + + +
  • +Allow HTTP implementation to be selected at run-time +
  • + + +
  • +Bug 50684 - Optionally disable Content-Type and Transfer-Encoding in Multipart POST +
  • + + +
  • +Bug 50943 - Allowing concurrent downloads of embedded resources in html page +
  • + + +
  • +Bug 50170 - Bytes reported by http sampler is after GUnZip +
    + +Add optional properties to allow change the method to get response size +
  • + + +
  • +Hiding the proxy password on HTTP Sampler (just on GUI, not in JMX file) +
  • + + +
+

+Other samplers +

+
    + + +
  • +Bug 49622 - Allow sending messages without a subject (SMTP Sampler) +
  • + + +
  • +Bug 49603 - Allow accepting expired certificates on Mail Reader Sampler +
  • + + +
  • +Bug 49775 - Allow sending messages without a body +
  • + + +
  • +Bug 49862 - Improve SMTPSampler Request output. +
  • + + +
  • +Bug 50268 - Adds static and dynamic destinations to JMS Publisher +
  • + + +
  • +JMS Subscriber - Add dynamic destination +
  • + + +
  • +Bug 50666 - JMSSubscriber: support for durable subscriptions +
  • + + +
  • +Bug 50937 - TCP Sampler does not provide for / honor connect timeout +
  • + + +
  • +Bug 50569 - Jdbc Request Sampler to optionally store result set object data +
  • + + +
  • +Bug 51011 - Mail Reader: upon authentication failure, tell what you tried +
  • + + +
+

+Controllers +

+
    + + +
  • +Bug 50475 - Introduction of a Test Fragment Test Element for a better Include flow +
  • + + +
+

+Listeners +

+
    + + +
  • +View Results Tree - Add a dialog's text box on "Sampler result tab > Parsed" to display the long value with a double click on cell +
  • + + +
  • +Bug 37156 - Formatted view of Request in Results Tree +
  • + + +
  • +Bug 49365 - Allow result set to be written to file in a path relative to the loaded script +
  • + + +
  • +Bug 50579 - Error count is long, sample count is int. Changed sample count to long. +
  • + + +
  • +View Results Tree - Add new size fields: response headers and response body (in bytes) - derived from Bug 43363 +
  • + + +
+

+Timers, Assertions, Config, Pre- & Post-Processors +

+
    + + +
  • +Bug 48015 - Proposal new icons for pre-processor, post-processor and assertion elements +
  • + + +
  • +Bug 50962 - SizeAssertionGui validation prevents the use of variables for the size +
  • + + +
  • +Size Assertion - Add response size scope (full, headers, body, code, message) - derived from Bug 43363 +
  • + + +
+

+Functions +

+
    + + +
  • +Bug 49975 - New function returning the name of the current sampler +
  • + + +
+

+I18N +

+
    + + +
  • +Add French translation for the new labels and reduce size for some labels (by abbreviation) on HTTP Sample +
  • + + +
+

+General +

+
    + + +
  • +Bug 30563 - Thread Group should have a start next loop option on Sample Error +
  • + + +
  • +Bug 50347 - Eclipse setup instructions should remind user to download dependent jars +
  • + + +
  • +Bug 50490 - Setup and Post Thread Group enhancements for better test flow. +
  • + + +
  • +All BeansShell test elements now have the script variables "prev" and "Label" defined. +
  • + + +
  • +Bug 50708 - Classpath jar order in NewDriver not alphabetically +
  • + + +
  • +Bug 50659 - JMeter server does not support concurrent tests - prevent client from starting another +
  • + + +
  • +Added remote shutdown functionality +
  • + + +
  • +Client JMeter engine now supports external stop/shutdown via UDP +
  • + + +
  • +UDP shutdown can now use a range of ports, from jmeterengine.nongui.port=4445 to jmeterengine.nongui.maxport=4455, +allowing multiple JMeter instances on the same host without needing to change the port property. +
  • + + +
  • +Updated to httpcore 4.1.3 and httpclient 4.1.2 +
  • + + +
+

+Non-functional changes +

+
    + + +
  • +Bug 50008 - Allow BatchSampleSender to be subclassed +
  • + + +
  • +Bug 50450 - use System.array copy in jacobi solver as, being native, is more performant. +
  • + + +
  • +Bug 50487 - runSerialTest verifies objects that never need persisting +
  • + + +
  • +Use Thread.setDefaultUncaughtExceptionHandler() instead of private ThreadGroup +
  • + + +
  • +Update to Commons Net 3.0 +
  • + + +
+

+Version 2.4 +

+

+Summary of main changes +

+

+ + +

    + + +
  • +JMeter now requires at least Java 1.5. +
  • + + +
  • +HTTP Proxy can now record HTTPS sessions. +
  • + + +
  • +JUnit sampler now supports JUnit4 annotations. +
  • + + +
  • +Added JSR223 (javax.script) test elements. +
  • + + +
  • +MailReader Sampler can now use any protocol supported by the underlying implementation. +
  • + + +
  • +An SMTP Sampler has been added. +
  • + + +
  • +JMeter now allows users to provide their own Thread Group implementations. +
  • + + +
  • +View Results Tree now supports more display options, including search and Regex Testing. +
  • + + +
  • +StatCalculator performance is much improved; Aggregate Report etc. need far less memory. +
  • + + +
  • + +JMS samplers have been extensively reworked, and should no longer lose messages. +Correlation processing is improved. +JMS Publisher and Subscriber now support both Topics and Queues. + +
  • + + +
  • +Many other improvements have been made, please see below and in the manual. +
  • + + +
+ + +

+

+Known bugs +

+

+ +The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. + +

+

+Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified). +

+

+ +The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. + +

+

+Incompatible changes +

+

+ +HTTP Redirect now defaults to "Follow Redirects" rather than "Redirect Automatically". +This is to enable JMeter to track cookies that may be sent during redirects. +This does not affect existing test plans; it only affects the default for new HTTP Samplers. + +

+

+ +The Avalon file format for JMX and JTL files is no longer supported. +Any such files will need to be converted by reading them in JMeter 2.3.4 and resaving them. + +

+

+ +The XPath Assertion and XPath Extractor elements no longer fetch external DTDs by default; this can be changed in the GUI. + +

+

+ +JMSConfigGui has been renamed as JMSSamplerGui. +This does not affect existing test plans. + +

+

+ +The constructor public SampleResult(SampleResult res) has been changed to become a true "copy constructor". +It no longer calls addSubResult(). This may possibly affect some 3rd party add-ons. + +

+

+Bug fixes +

+

+HTTP Samplers and Proxy +

+
    + + +
  • +Bug 47445 - Using Proxy with https-spoofing secure cookies need to be unsecured +
  • + + +
  • +Bug 47442 - Missing replacement of https by http for certain conditions using https-spoofing +
  • + + +
  • +Bug 48451 - Error in: SoapSampler.setPostHeaders(PostMethod post) in the else branch +
  • + + +
  • +Bug 48542 - SoapSampler uses wrong response header field to decide if response is gzip encoded +
  • + + +
  • +Bug 48568 - CookieManager broken for AjpSampler +
  • + + +
  • +Bug 48570 - AjpSampler doesn't support query parameters (GET/POST) +
  • + + +
  • +Bug 46901 - HTTP Sampler does not process var/func refs correctly in first file parameter +
  • + + +
  • +Bug 43678 - Handle META tag http-equiv charset? +
  • + + +
  • +Bug 49294 - Images not downloaded from redirected-to pages +
  • + + +
  • +Bug 49560 - wrong "size in bytes" when following redirections +
  • + + +
+

+Other Samplers +

+
    + + +
  • +Bug 47420 - LDAP extended request not closing connections during add request +
  • + + +
  • +Bug 48573 - LDAPExtSampler directory context handling +
  • + + +
  • +Bug 47870 - JMSSubscriber fails due to NPE +
  • + + +
  • +Bug 47899 - NullPointerExceptions in JMS ReceiveSubscriber constructor +
  • + + +
  • +Bug 48144 - NPE in JMS OnMessageSubscriber +
  • + + +
  • +Bug 47992 - JMS Point-to-Point Request - Response option doesn't work +
  • + + +
  • +Bug 48579 - Single Bind does not show config information when LdapExt Sampler is accessed +
  • + + +
  • +Bug 49111 - "Message With ID Not Found" Error on JMS P2P sampler. +
  • + + +
  • +Bug 47949 - JMS Subscriber never receives all the messages +
  • + + +
  • +Bug 46142 - JMS Point-to-Point correlation problems +
  • + + +
  • +Bug 48747 - TCP Sampler swallows exceptions +
  • + + +
  • +Bug 48709 - TCP Sampler Config setting "classname" has no effect +
  • + + +
+

+Controllers +

+
    + + +
  • +Bug 47385 - TransactionController should set AllThreads and GroupThreads +
  • + + +
  • +Bug 47940 - Module controller incorrectly creates the replacement Sub Tree +
  • + + +
  • +Bug 47592 - Run Thread groups consecutively with "Stop test" on error, JMeter will not mark to finished +
  • + + +
  • +Bug 48786 - Run Thread groups consecutively: with "Stop test now" on error or manual stop, JMeter leaves the green box active +
  • + + +
  • +Bug 48727 - Cannot stop test if all thread groups are disabled +
  • + + +
+

+Listeners +

+
    + + +
  • +Bug 48603 - Mailer Visualiser sends two emails for a single failed response +
  • + + +
  • +Correct calculation of min/max/std.dev for aggregated samples (Summary Report) +
  • + + +
  • +Bug 48889 - Wrong response time with mode=Statistical and num_sample_threshold > 1 +
  • + + +
  • +Bug 47398 - SampleEvents are sent twice over RMI in distributed testing and non gui mode +
  • + + +
+

+Assertions +

+
    + + +
+

+Functions +

+
    + + +
+

+I18N +

+
    + + +
+

+General +

+
    + + +
  • +Bug 47646 - NullPointerException in the "Random Variable" element +
  • + + +
  • +Disallow adding any child elements to JDBC Configuration +
  • + + +
  • +BeanInfoSupport now caches getBeanDescriptor() - should avoid an NPE on non-Sun JVMs when using CSVDataSet (and some other TestBeans) +
  • + + +
  • +Bug 48350 - Deadlock on distributed testing with 2 clients +
  • + + +
  • +Bug 48901 - Endless wait by adding Synchronizing Timer +
  • + + +
  • +Bug 49149 - usermanual/index.html has typo in link to "Regular Expressions" page +
  • + + +
  • +Bug 49394 - Classcast Exception in ActionRouter.postActionPerformed +
  • + + +
  • +Bug 48136 - Essential files missing from source tarball. +
    + + +Source archives now contain all source files, including source files previously only provided in the binary archives. + +
  • + + +
  • +Bug 48331 - XpathExtractor does not return XML string representations for a Nodeset +
  • + + +
+

+Improvements +

+

+HTTP Samplers +

+
    + + +
  • +Bug 47622 - enable recording of HTTPS sessions +
  • + + +
  • +Allow Proxy Server to be specified on HTTP Sampler GUI and HTTP Config GUI +
  • + + +
  • +Bug 47461 - Update Cache Manager to handle Expires HTTP header +
  • + + +
  • +Bug 48153 - Support for Cache-Control and Expires headers +
  • + + +
  • +Bug 47946 - Proxy should enable Grouping inside a Transaction Controller +
  • + + +
  • +Bug 48300 - Allow override of IP source address for HTTP HttpClient requests +
  • + + +
  • +Bug 49083 - collapse '/pathsegment/..' in redirect URLs +
  • + + +
+

+Other samplers +

+
    + + +
  • +JUnit sampler now supports JUnit4 tests (using annotations) +
  • + + +
  • +Bug 47900 - Allow JMS SubscriberSampler to be interrupted +
  • + + +
  • +Added JSR223 Sampler +
  • + + +
  • +Bug 47556 - JMS-PointToPoint-Sampler Timeout field should use Strings +
  • + + +
  • +Bug 47947 - Mail Reader Sampler should allow port to be overridden +
  • + + +
  • +Bug 48155 - Multiple problems / enhancements with JMS protocol classes +
  • + + +
  • +Allow MailReader sampler to use arbitrary protocols +
  • + + +
  • +Bug 45053 - SMTP-Sampler for JMeter +
  • + + +
  • +Bug 49552 - Add Message Headers on SMTPSampler +
  • + + +
  • + +JMS Publisher and Subscriber now support both Topics and Queues. +Added read Timeout to JMS Subscriber. +General clean-up of JMS code. + +
  • + + +
+

+Controllers +

+
    + + +
  • +Bug 47909 - TransactionController should sum the latency +
  • + + +
  • +Bug 41418 - Exclude timer duration from Transaction Controller runtime in report +
  • + + +
  • +Bug 48749 - Allowing custom Thread Groups +
  • + + +
  • +Bug 43389 - Allow Include files to be found relative to the current JMX file +
  • + + +
+

+Listeners +

+
    + + +
  • +Added DataStrippingSample sender - supports "Stripped" and "StrippedBatch" modes. +
  • + + +
  • +Added Comparison Assertion Visualizer +
  • + + +
  • +Bug 47907 - Improvements (enhancements and I18N) Comparison Assertion and Comparison Visualizer +
  • + + +
  • +Bug 36726 - add search function to Tree View Listener +
  • + + +
  • +Bug 47869 - Ability to cleanup fields of SampleResult +
  • + + +
  • +Bug 47952 - Added JSR223 Listener +
  • + + +
  • +Bug 47474 - View Results Tree support for plugin renderers +
  • + + +
  • +Allow Idle Time to be saved to sample log files +
  • + + +
  • +Bug 48259 - Improve StatCalculator performance by using TreeMap +
  • + + +
  • +Listeners using SamplingStatCalculator have much reduced memory needs +as the Sample cache has been moved to the new CachingStatCalculator class. +In particular, Aggregate Report can now handle large numbers of samples. + +
  • + + +
  • +Aggregate Report and Summary Report now allow column headers to be optionally excluded +
  • + + +
  • +Bug 49506 - Add .csv File Extension in open dialog box from "read from file" functionality of listeners +
  • + + +
  • +Bug 49545 - Formatted (parsed) view of Sample Result in Results Tree +
  • + + +
+

+Timers, Assertions, Config, Pre- & Post-Processors +

+
    + + +
  • +Bug 47338 - XPath Extractor forces retrieval of document DTD +
  • + + +
  • +Added Comparison Assertion +
  • + + +
  • +Bug 47952 - Added JSR223 PreProcessor and PostProcessor +
  • + + +
  • +Added JSR223 Assertion +
  • + + +
  • +Added BSF Timer and JSR223 Timer +
  • + + +
  • +Bug 48511 - add parent,child,all selection to regex extractor +
  • + + +
  • +Add Sampler scope selection to XPathExtractor +
  • + + +
  • +Regular Expression Extractor, Response Assertion and Size Assertion can now be applied to a JMeter variable +
  • + + +
  • +Bug 46790 - CSV Data Set Config should be able to parse CSV headers +
  • + + +
+

+Functions +

+
    + + +
  • +Bug 47565 - [Function] FileToString +
  • + + +
+

+I18N +

+
    + + +
  • +Bug 47938 - Adding some French translations for new elements +
  • + + +
  • +Bug 48714 - add new French messages +
  • + + +
+

+General +

+
    + + +
  • +Bug 47223 - Slow Aggregate Report Performance (StatCalculator) +
  • + + +
  • +Bug 47980 - hostname resolves to 127.0.0.1 - specifiying IP not possible +
  • + + +
  • +Bug 47943 - DisabledComponentRemover is not used in Start class +
  • + + +
  • +HeapDumper class for runtime generation of dumps +
  • + + +
  • +Basic read-only JavaMail provider implementation for reading raw mail files +
  • + + +
  • +Bug 49540 - Sort "Add" menus alphabetically +
  • + + +
+

+Non-functional changes +

+
    + + +
  • +Beanshell, JavaMail and JMS API (Apache Geronimo) jars are now included in the binary archive. +
  • + + +
  • +Add TestBean Table Editor support +
  • + + +
  • +Removed all external libraries from SVN; added download_jars Ant target +
  • + + +
  • +Updated various jar files: + +
      + + +
    • +BeanShell - 2.0b4 => 2.0b5 +
    • + + +
    • +Commons Codec - 1.3 => 1.4 +
    • + + +
    • +Commons-Collections - 3.2 => 3.2.1 +
    • + + +
    • +JTidy => r938 +
    • + + +
    • +JUnit - 3.8.2 => 4.8.1 +
    • + + +
    • +Logkit - 1.2 => 2.0 +
    • + + +
    • +Xalan Serializer = 2.7.1 (previously erroneously shown as 2.9.1) +
    • + + +
    • +Xerces xml-apis = 1.3.04 (previously erroneously shown as 2.9.1) +
    • + + +
    • +Some jar files were renamed. +
    • + + +
    + + +
  • + + +
+

+Version 2.3.4 +

+

+Summary of main changes +

+

+ +This is a minor bug-fix release, mainly to correct some bugs that were accidentally added in 2.3.3. + +

+

+Known bugs +

+

+ +The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. + +

+

+Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified). +

+

+ +The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. + +

+

+Bug fixes +

+

+HTTP Samplers and Proxy +

+
    + + +
  • +Bug 47321 - HTTPSampler2 response timeout not honored +
  • + + +
+

+Other Samplers +

+
    + + +
  • +Bug 47290 - Infinite loop on connection factory lookup (JMS) +
  • + + +
  • +JDBC Sampler should not close Prepared or Callable statements as these are cached +
  • + + +
+

+Controllers +

+
    + + +
  • +Bug 39509 - Once-only controller running twice +
  • + + +
+

+Listeners +

+
    + + +
  • +Change ResultCollector to only warn if the directory was not created +
  • + + +
  • +Fix some synchronisation issues in ResultCollector and SampleResult (wrong locks were being used) +
  • + + +
+

+I18N +

+
    + + +
  • +Fixed bug introduced in 2.3.3: JMeter does not start up if there is no messages.properties file for the default Locale. +
  • + + +
+

+General +

+
    + + +
  • +Fix problems with remote clients - bug introduced in 2.3.3 +
  • + + +
  • +Bug 47377 - Make ClassFinder more robust and close zipfile resources +
  • + + +
  • +Fix some errors in generating the documentation (latent bug revealed in 2.3.3 when Velocity was upgraded) +
  • + + +
+

+Improvements +

+

+Other samplers +

+
    + + +
  • +Bug 47266 - FTP Request Sampler: allow specifying an FTP port, other than the default +
  • + + +
+

+Version 2.3.3 +

+

+Summary of main changes +

+

+ +The handling of test closedown is much improved. +The gradual "Shutdown" command now waits until all threads have stopped, +and does not report an error if threads don't stop within 5 seconds. +The immediate "Stop" command can now be used if "Shutdown" takes too long. +Also the immediate "Stop" command is able to interrupt samplers which support the new Interruptible interface (e.g. HTTP and SOAP, FTP). +This allows immediate completion of pending responses. +Non-GUI mode tests can also now be sent a "Shutdown" or "Stop" message. + +Test Action + now supports a "Stop Now" action, +as do the +Thread Group + and +Result Status Action Handler + Post Processor elements. + +

+

+ +HTTP Cookie handling is improved, and HTTP POST can now use variable file names correctly. +HTTP, SOAP/XML-RPC and WebService(SOAP) sampler character encodings updated to be more consistent. +HTTP Samplers now support connection and response timeouts (requires JVM 1.5 for the HTTP Java sampler). +Together with the closedown improvements described above, this should avoid most cases where a test run hangs. +Multiple Header Manager elements are now supported for a single HTTP sampler. +The Proxy Server is improved, and no longer stores "Host" headers by default. + +

+

+ +JDBC Request can optionally save the results of Select statements to variables. +JDBC Request now handles quoted strings and UTF-8, and can handle arbitrary variable types. + +

+

+ +There are several new + +functions + +: +__char() function: allows arbitrary Unicode characters to be entered in fields. +__unescape() function: allows Java-escaped strings to be used. +_unescapeHtml() function: decodes Html-encoded text. +__escapeHtml() function: encodes text using Html-encoding. +A reference to a missing function - e.g. ${__missing(a)} - is now treated the same as a missing variable. +Previously the function name - and leading { - were dropped. This makes it easier to debug test plans. + +

+

+ +Some Assertions can now be applied to sub-samples as well as (or instead of) just the parent sample. +There is a new +Random Variable + Configuration element. + +

+

+ +JMS samplers are much improved (see details below). The +TCP Sampler + now supports some additional clients and is a bit more flexible. + +

+

+ +Client-server mode has been improved, and the server can optionally use a fixed RMI port, which should help with setting up firewalls. + +

+

+ +Various I18N changes have been made; language change works better (though not perfect yet). +There are improved French translations as well as new Polish and Brazilian Portugese translations. + +

+

+ +The BeanShell jar is now included with the binary archive; there is no need to download it separately. + +

+

+Known bugs +

+

+ +The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. + +

+

+Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified). +

+

+ +The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. + +

+

+Incompatible changes +

+

+ +When loading sample results from a file, previous results are no longer cleared. +This allows one to merge multiple files. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. + +

+

+ +The test elements "Save Results to a file" and "Generate Summary Results" are now shown as Listeners. +They were previously shown as Post-Processors, even though they are implemented as Listeners. + +

+

+ +The Cookie Manager no longer saves incoming cookies as variables by default. +To save cookies as variables, define the property "CookieManager.save.cookies=true". +Also, cookies names are prefixed with "COOKIE_" before they are stored (this avoids accidental corruption of local variables) +To revert to the original behaviour, define the property "CookieManager.name.prefix= " (one or more spaces). + +

+

+ +The Counter element is now shown as a Configuration element. +It was previously shown as a Pre-Processor, even though it is implemented as a Config item. + +

+

+ +The above changes only affect the icons that are displayed and the locations in the GUI pop-up menus. +They do not affect test plans or test behaviour. + +

+

+ +The PreProcessors are now invoked directly by the JMeterThread class, +rather than by the TestCompiler#configureSampler() method. (JMeterThread handles the PostProcessors). +This does not affect test plans or behaviour, but could perhaps affect 3rd party add-ons (very unlikely). + +

+

+ +Moved the Scoping Rules sub-section from Section 3. "Building a Test Plan" to Section 4. "Elements of a test plan" + +

+

+ +The While controller now trims leading and trailing spaces from the condition value before it is compared +with LAST, blank or false. + +

+

+ +The "threadName" variable in the _jexl() and __javaScript() functions was previously misspelt as "theadName". + +

+

+ +The following deprecated methods were removed from JOrphanUtils: booleanToString(boolean) and valueOf(boolean). +Java 1.4+ has these methods in the Boolean class. + +

+

+ +The TestElement interface has some new methods: + +

    + + +
  • +void setProperty(String key, String value, String dflt) +
  • + + +
  • +void setProperty(String key, boolean value, boolean dflt) +
  • + + +
  • +void setProperty(String key, int value) +
  • + + +
  • +void setProperty(String key, int value, int dflt) +
  • + + +
  • +int getPropertyAsInt(String key, int defaultValue) +
  • + + +
+ +These are implemented in the AbstractTestElement class which all elements should extend so this is unlikely to cause a problem. + +

+

+Bug fixes +

+

+HTTP Samplers and Proxy +

+
    + + +
  • +Bug 46332 + - HTTP Cookie Manager ignores manually defined cookies (bug introduced in r707810) +
  • + + +
  • +Cookie Manager was not passing cookie policy to runtime threads so they always used compatibility mode +
  • + + +
  • +Add version attribute to JMeter Cookie class (needed for proper cookie support) +
  • + + +
  • +Cookie Manager now saves/restores cookie versions +
  • + + +
  • +Check validity of cookies before storing them. +
  • + + + +
  • +HTTPSamplers can now use variables in POSTed file names +
  • + + +
  • +Fix processing of first file name in HTTP POST so functions/variables work (bug introduced with multiple file support) +
  • + + +
  • +Bug 45831 + - WS Sampler reports incorrect throughput if SOAP packet creation fails +
  • + + +
  • +HTTP, SOAP/XML-RPC and WebService(SOAP) sampler character encodings updated to be more consistent +
  • + + + +
  • +Bug 46148 + - HTTP sampler fails on SSL requests when logging for jmeter.util is set to DEBUG +
  • + + +
  • +Fix Java 1.6 https error: java.net.SocketException: Unconnected sockets not implemented +
  • + + + +
  • +Bug 46838 + - if there was no data, still need to set latency in HTTPSampler +
  • + + +
  • +Bug 46993 + - Saving from Header Manager generates ClassCastException +
  • + + +
  • + + +Bug 46690 + - handling of 302 redirects with invalid relative paths. +JMeter now removes extraneous leading "../" segments (as do many browsers) + +
  • + + +
  • +Bug 44521 + - empty variables for a POST in the HTTP Request don't get ignored +
  • + + +
  • +Bug 46977 + - JMeter does not handle HTTP headers not delimited by whitespace +
  • + + +
  • +Fix bug in HTTP file: handling - read bytes, not characters in the default encoding. +
  • + + + +
  • +Remove Host from headers saved by the Proxy server, as that will normally be generated by the HTTP stack +
  • + + +
  • +Bug 45199 + - don't try to replace blank variables in Proxy recording +
  • + + +
  • +Change HTTPS spoofing so https: links are replaced even when URL match fails +
  • + + +
  • +Bug 46436 + - Improve error reporting in Proxy Gui +
  • + + +
  • +Bug 46435 + - More verbose error msg for error 501 (Proxy Server) +
  • + + +
+

+Other Samplers +

+
    + + +
  • +The "prev" and "sampler" objects are now defined for BSF test elements +
  • + + +
  • +Fix NPE (in DataSourceElement) when using JDBC in client-server mode +
  • + + +
  • +Bug 45425 + - JDBC Request does not support Unicode (changed sampler to use UTF-8) +
  • + + +
  • +Bug 46522 + - Incorrect "Response data" in JDBC sample when column names are missing +
  • + + +
  • +Bug 46821 + - JDBC select request doesn't store the first column in the variables +
  • + + +
  • +Bug 43791 + - ensure QueueReceiver is closed in JMS Point to Point sampler +
  • + + +
  • +Bug 46016 + - avoid possible NPE in JMSSampler +
  • + + +
  • +Bug 46142 + - JMS Receiver now uses MessageID +
  • + + +
  • +Bug 45458 + - Point to Point JMS in combination with authentication +
  • + + +
  • +Bug 45460 + - JMS TestPlan elements depend on resource property +
  • + + +
  • +Various ReceiveSubscriber thread-safety fixes +
  • + + +
  • +JMSPublisher and Subscriber fixes: thread-safety, support dynamic locale changes, locale independence for JMX attribute values +
  • + + +
  • +FTP Sampler now logs out before disconnecting. +
  • + + +
  • +TCP sampler now calls setupTest() and teardownTest() methods +
  • + + +
  • +Bug 45887 + - TCPSampler: timeout property incorrectly set +
  • + + +
+

+Controllers +

+
    + + +
  • +Fix NPE when using nested Transaction Controllers with parent samples +
  • + + +
  • +Fix processing of Transaction Controller parent mode so current sampler is set to actual sampler +
  • + + +
  • +Bug 44941 + - Throughput controllers should not share global counters +
  • + + +
  • +Bug 47120 + - Throughput Controller: change percent executions to total executions, the value is stored in a String and interpreted as 1 execution +
  • + + +
  • +Bug 47150 + - ThreadGroup with a loop count of zero causes infinite loop +
  • + + +
  • +Bug 47009 + - Insert parent caused child controller name to be reset +
  • + + +
  • +Bug 47165 + - Using duplicate Module Controller names in command line mode causes NPE +
  • + + +
+

+Listeners +

+
    + + +
  • +Mailer Visualizer documentation now agrees with code i.e. failure/success counts need to be exceeded to trigger the mail. +
  • + + +
  • +Mailer Visualizer now shows the failure count +
  • + + +
  • +Mailer Visualiser - fix parsing of multiple e-mail address when using Test button +
  • + + +
  • +Bug 45976 + - incomplete result file when using remote testing with more than 1 server +
  • + + +
  • +Fix Summariser so it works in client server mode +
  • + + +
  • +Bug 34096 + - Duplicate samples not eliminated when writing to CSV files +
  • + + +
  • +Save "Include group Name in Label" setting in Aggregate and Summary reports +
  • + + +
  • +The JMeter variable "sample_variables" is sent to all server instances to ensure the data is available to the client. +
  • + + +
  • +CSVSaveService - check for EOF while reading quoted string +
  • + + +
+

+Assertions +

+
    + + +
  • +Bug 45749 + - Response Assertion does not work with a substring that happens to be an invalid RE +
  • + + +
  • +Bug 45904 + - Allow 'Not' Response Assertion to succeed with null sample +
  • + + +
+

+Functions +

+
    + + +
  • +Fix regex function - was failing to process $m$mid$n$ correctly +
  • + + +
  • +Protect against possible NPE in RegexFunction if called during test shutdown. +
  • + + +
  • +Avoid NPE if XPath function does not match any nodes +
  • + + +
  • +Correct the variable name "theadName" to "threadName" in the __jexl() and __javaScript() functions +
  • + + +
  • +A reference to a missing function - e.g. ${__missing(a)} - is now treated the same as a missing variable. Previously the function name - and leading { - were dropped. +
  • + + +
+

+I18N +

+
    + + +
  • +Fixed language change handling for menus (does not yet work for TestBeans) +
  • + + +
  • +Add HeaderAsPropertyRenderer to support header resource names; use this to fix locale changes in various GUI elements +
  • + + +
  • +Bug 46424 + - corrections to French translation +
  • + + +
  • +Bug 46844 + - "Library" label in test plan are not I18N +
  • + + +
  • +Bug 47064 + - fixes for Mac LAF +
  • + + +
  • +Bug 47127 + - Unable to change language to pl_PL +
  • + + +
  • +Bug 47137 + - Labels in View Results Tree aren't I18N +
  • + + +
  • +Bug 46423 + - I18N of Proxy Recorder +
  • + + +
  • +Bug 45928 + - AJP/1.3 Sampler doesn't retrieve its label from messages.properties +
  • + + +
+

+General +

+
    + + +
  • +Prompt to overwrite an existing file when first saving a new test plan +
  • + + +
  • +Amend TestBeans to show the correct popup menu for Listeners +
  • + + +
  • +Bug 45185 + - CSV dataset blank delimiter causes OOM +
  • + + +
  • +Fix incorrect GUI classifications: +"Save Results to a file" and "Generate Summary Results" are now shown as Listeners. +"Counter" is now shown as a Configuration element. + +
  • + + +
  • +Bug 41608 + - misleading warning log message removed +
  • + + +
  • +Bug 46359 + - BSF JavaScript Preprocessor cannot access sampler variable on first interation (Implement temporary work-round for BSF-22) +
  • + + +
  • +Bug 46407 + - BSF elements do not load script files, attempt to interpret filename as script +
  • + + +
  • +Better handling of Exceptions during test shutdown +
  • + + +
  • +Fix potential thread safety issue in JMeterThread class +
  • + + +
  • +Bug 46491 + - Incorrect value for the last variable in "CSV Data Set Config" (error in processing quoted strings) +
  • + + + +
+

+Improvements +

+

+HTTP Samplers +

+
    + + +
  • +Bug 45479 + - Support for multiple HTTP Header Manager nodes +
  • + + +
  • +HTTP Samplers now support connection and request timeouts (requires Java 1.5 for Java Http sampler) +
  • + + +
  • +Apache SOAP 2.3.1 does not give access to HTTP response code/message, so WebService sampler now treats an empty response as an error +
  • + + +
  • +Mirror server now supports "X-Sleep" header - if this is set, the responding thread will wait for the specified number of milliseconds +
  • + + +
  • +Bug 45694 + - Support GZIP compressed logs in Access Log Sampler +
  • + + +
+

+Other samplers +

+
    + + +
  • +JDBC Request can optionally save the results of Select statements to variables. +
  • + + +
  • +JDBC Request now handles quoted strings. +
  • + + +
  • +JDBC Request now handles arbitrary variable types. +
  • + + +
  • +LDAP result data now formatted with line breaks +
  • + + +
  • +Bug 45200 + - MailReaderSampler: store the whole MIME message in the SamplerResult +
  • + + +
  • +Bug 45571 + - JMS Sampler correlation enhancement +
  • + + +
  • +Bug 46030 + - Extend TCP Sampler to Support Length-Prefixed Binary Data +
  • + + +
  • +Add classname field to TCP Sampler GUIs +
  • + + +
+

+Controllers +

+
    + + +
  • +Allow If Controller to use variable expressions (not just Javascript) +
  • + + +
  • +Trim spaces from While Controller condition before comparing against LAST, blank or false +
  • + + +
+

+Listeners +

+
    + + +
  • +Save Responses to a file can save the generated filename(s) to variables. +
  • + + +
  • +Add option to skip suffix generation in Save Responses to a File +
  • + + +
  • +Bug 43119 + - Save Responses to file: optionally omit the file number +
  • + + +
  • +Add BSF Listener element +
  • + + +
  • +Bug 47176 + - Monitor Results : improve load status graphic +
  • + + +
  • +Bug 40045 + - Allow Results monitor to select a specific connector +
  • + + +
  • +Read XML JTL files more efficiently - pass samples to visualisers as they are read, rather than saving them all and then processing them +
  • + + +
+

+Assertions, Config, Pre- & Post-Processors +

+
    + + +
  • +Bug 45903 + - allow Assertions to apply to sub-samples +
  • + + +
  • +Add Body (unescaped) source option to Regular Expression Extractor. +
  • + + +
  • +Random Variable - new configuration element to create random numeric variables +
  • + + +
+

+Functions +

+
    + + +
  • +Add OUT and log variables to __jexl() function +
  • + + +
  • +Use Script to evaluate __jexl() function so can have multiple statements. +
  • + + +
  • +Add log variable to the __javaScript() function +
  • + + +
  • +Added __char() function: allows arbitrary Unicode characters to be entered in fields. +
  • + + +
  • +Added __unescape() function: allows Java-escaped strings to be used. +
  • + + +
  • +Added __unescapeHtml() function: decodes Html-encoded text. +
  • + + +
  • +Added __escapeHtml() function: encodes text using Html-encoding. +
  • + + +
+

+I18N +

+
    + + +
  • +Bug 45929 + - improved French translations +
  • + + +
  • +Bug 47132 + - Brazilian Portuguese translations +
  • + + +
  • +Bug 46900 + - Polish translations +
  • + + +
  • +Added locales.add property to allow for new Locales +
  • + + +
+

+General +

+
    + + +
  • +Allow spaces in JMeter path names (apply work-round for + +Java Bug 4496398 + +) +
  • + + +
  • +Process JVM_ARGS last in script files so users can override default settings +
  • + + +
  • +Bug 46636 + - Allow server mode to optionally use a fixed rmi port +
  • + + +
  • +Make some samplers interruptible: HTTP (both), SoapSampler, FTPSampler +
  • + + +
  • +Test Action now supports "Stop Now" action, as do the Thread Group and Result Status Post Processor elements +
  • + + +
  • +The Menu items Stop and Shutdown now behave better. Shutdown will now wait until all threads exit. +In GUI mode it can be cancelled and Stop run instead. +Stop now reports if some threads will not exit, and exits if running in non-GUI mode +
  • + + +
  • +Add UDP server to wait for shutdown message if running in non-GUI mode; add UDP client to send the message. +
  • + + +
  • +Bug 41209 + - JLabeled* and ToolTips +
  • + + +
  • +Include BeanShell 2.0b4 jar in binary download. +
  • + + +
+

+Non-functional changes +

+
    + + +
  • +Introduce AbstractListenerGui class to make it easier to create Listeners with no visual output +
  • + + +
  • +Assertions are run after PostProcessors; change order of pop-up menus accordingly +
  • + + +
  • +Remove unnecessary clone() methods from function classes +
  • + + +
  • +Moved PreProcessor invocation to JMeterThread class +
  • + + +
  • +Made HashTree Map field final +
  • + + +
  • +Improve performance of calling ResultCollector#isSampleWanted() for multiple samples +
  • + + +
  • +Updated to new versions of: xmlgraphics-commons (1.3.1), jdom (1.1), xstream (1.3.1), velocity (1.6.2) +
  • + + +
+

+Version 2.3.2 +

+

+Summary of main changes +

+

+Bug fixes +

+

+ +Version 2.3.1 changed the way binary and text content types were determined as far as the View Results Tree Listener was concerned: +originally everything except "image/" content types were considered text, but 2.3.1 introduced a check +for specific content types. This has caused problems, +as several popular types were omitted and these were no longer shown by default in the Response tab. +Rather than try to list all the possible text types, JMeter now just checks for the following binary types: + +

    + + +
  • +image/* +
  • + + +
  • +audio/* +
  • + + +
  • +video/* +
  • + + +
+ +All other types are now assumed to be text. + +

+

+ +JMeter 2.3.1 introduced a bug in the Cookie Manager +- if "Clear Cookie each iteration" was selected, all threads would see the same cookies. +This bug has been corrected. + +

+

+Improvements +

+

+ +The Proxy server can now record binary requests. +By default the content types +application/x-amf and application/x-java-serialized-object +will be treated as binary and saved in a file. +To change the content types, update the property + +proxy.binary.types + +. + +

+

+ +The CSV Dataset configuration element has new file sharing options: per thread group, per thread, per identifier. +This allows for more flexible file processing, e.g. each thread can process the same data in the same order. + +

+

+Switch Controller now works properly with functions and variables, +and the condition can now be a name instead of a number. +Simple Controller now works properly under a While Controller +

+

+CSV fields in JTL files can now contain delimiters. +CSV and XML files can now contain additional variables (define the JMeter property + +sample_variables + +). +

+

+Response Assertion can now match on substrings (i.e. not regular expression). +Regex extractor can operate on variables. +

+

+ +XPath processing is improved; Tidy errors are handled better. + +

+

+Save Table Data buttons added to Summary and Aggregate reports to allow easy saving of the calculated data. +

+

+ +HTTP samplers can now save just the MD5 hash of responses, rather than the entire response. +As a special case, if the HTTP Sampler path starts with "http://" or "https://" then this is used as the full URL, +overriding the host and port fields. +The HTTP Samplers can now POST multiple files. +Webservice(SOAP) Sampler can now load local WSDL files using the "file:" protocol. + +

+

+ +A simple HTTP Cache Manager has been added. This needs further development. + +

+

+ +View Results Tree Listener now uses Tidy to display XML. +This should allow more content to be displayed succesfully. +It also avoids the need to download remote DTD files, which can slow the rendering considerably. + +

+

+ +MailReader sampler now supports POP3S and IMAPS protocols. Individual mails are now added as sub-samples. + +

+

+ +Various improvements to the BSF Sampler: now supports Jexl, and Javascript bug works properly. +Added BSF PreProcessor, PostProcessor and Assertion test elements. +All now have access to "props" JMeter Properties object. + +

+

+Number of classes loaded in non-GUI mode is much reduced. +

+

+Known bugs +

+

+ +The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. + +

+

+Once Only controller behaves OK under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified). +

+

+ +The menu item Options / Choose Language does not change all the displayed text to the new language. +To override the default local language, set the JMeter property "language" before starting JMeter. + +

+

+Incompatible changes +

+
    + + +
  • + +To reduce the number of classes loaded in non-GUI mode, +Functions will only be found if their classname contains the string +'.functions.' and does not contain the string '.gui.'. +All existing JMeter functions conform to this restriction. +To revert to earlier behaviour, comment or change the properties classfinder.functions.* in jmeter.properties. + +
  • + + +
  • +The reference value parameter for intSum() is now optional. +As a consequence, if a variable name is used, it must not be a valid integer. +
  • + + +
  • +The supplied TCPClient implementation no longer treats tcp.eolByte=0 as special. +To skip EOL checking, set tcp.eolByte=1000 (or some other value which is not a valid byte) + +
  • + + +
  • + +Leading and trailing spaces are trimmed from variable names in function calls. +For example, ${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY '. + +
  • + + +
  • + +Synchronization has been removed from the RunningSample class (it was not fully threadsafe anyway). +Developers of 3rd party add-ons that use the class may need to synchronize access. + +
  • + + +
+

+Bug fixes +

+
    + + +
  • +Check that the CSV delimiter is reasonable. +
  • + + +
  • +Fix Switch Controller to work properly with functions and variables +
  • + + +
  • +Bug 44011 + - application/soap+xml not treated as a text type +
  • + + +
  • +Bug 43427 + - Simple Controller is only partly executed in While loop +
  • + + +
  • +Bug 33954 + - Stack Overflow in If/While controllers (may have been fixed previously) +
  • + + +
  • +Bug 44022 + - Memory Leak when closing test plan +
  • + + +
  • +Bug 44042 + - Regression in Cookie Manager (Bug introduced in 2.3.1) +
  • + + +
  • +Bug 41028 + - JMeter server doesn't alert the user when the host is defined as a loopback address +
  • + + +
  • +Bug 44142 + - Function __machineName causes NPE if parameters are omitted. +
  • + + +
  • +Bug 44144 + - JMS point-to-point: request response test does not work +
  • + + +
  • +Bug 44314 + - Not possible to add more than one SyncTimer +
  • + + +
  • +Capture Tidy console error output and log it +
  • + + +
  • +Fix problems using Tidy(tolerant parser) in XPath Assertion and XPath Extractor +
  • + + +
  • +Bug 44374 + - improve timer calculation +
  • + + +
  • +Regular Expression Extractor now deletes all stale variables from previous matches. +
  • + + +
  • +Bug 44707 + - Running remote test changes internal test plan +
  • + + +
  • +Bug 44625 + - Cannot have two or more FTP samplers with different "put" and "get" actions +
  • + + +
  • +Bug 40850 + - BeanShell memory leak +
  • + + +
  • +Ensure ResponseCode and ResponseMessage are set for successful JDBC samples +
  • + + +
  • +FTPSampler now detects and reports failure to open the remote file +
  • + + +
  • +Class directories defined in search_paths and user.classpath no longer need trailing "/" +
  • + + +
  • +Bug 44852 + SOAP/ XML-RPC Request does not show Request details in View Results Tree +
  • + + +
  • +WebService(SOAP) Sampler ResponseData now includes the EOLs sent by server +
  • + + +
  • +Bug 44910 + - close previous socket (if any) in TCP Sampler +
  • + + +
  • +Bug 44912 + - Filter not working in Log Parser +
  • + + +
  • +The BeanShell and BSF component documentation made some incorrect references to the "SampleResponse" object; +this has been corrected to "SampleResult" +
  • + + +
  • +BSF Sampler now works properly with Javascript +
  • + + +
  • +Test Action "Stop Test" now works +
  • + + +
  • +Bug 42833 + - Argument class uses LinkedHashMap in getArgumentsAsMap() to preserve ordering +
  • + + +
  • +Bug 45093 + - SizeAssertion did not call getBytes() +
  • + + +
  • +Bug 45007 + - Rewrite Location headers when using Proxy HTTPS spoofing +
  • + + +
  • +Use CRLF rather than LF in Proxy when returning headers to the client +
  • + + +
  • +Bug 45007 + - fix content length header if content may have been changed +
  • + + +
+

+Improvements +

+
    + + +
  • +CSV files can now handle fields with embedded delimiters. +
  • + + +
  • +longSum() function added +
  • + + +
  • +Bug 43382 + - configure Tidy output (warnings, errors) for XPath Assertion and Post-Processor +
  • + + +
  • +Bug 43984 + - trim spaces from port field +
  • + + +
  • +Add optional comment to __log() function +
  • + + +
  • +Make Random function variable name optional +
  • + + +
  • +Reduce class loading in non-GUI mode by only looking for Functions in class names +that contain '.functions.' and don't contain '.gui.' +
  • + + +
  • +Bug 43379 + - Switch Controller now supports selection by name as well as number +
  • + + +
  • +Can specify list of variable names to be written to JTL files (CSV and XML format) +
  • + + +
  • +Now checks that the remoteStart options -r and -R are only used with non_GUI -n option +
  • + + +
  • +Bug 44184 + - Allow header to be saved with Aggregate Graph data +
  • + + +
  • +Added "Save Table Data" buttons to Aggregate and Summary Reports - save table as CSV format with header +
  • + + +
  • +Allow most functions to be used on the Test Plan. +Note __evalVar(), __split() and __regex() cannot be used on the Test Plan. +
  • + + +
  • +Allow Global properties to be loaded from a file, e.g. -Gglobal.properties +
  • + + +
  • +Add "Substring" option to Response Assertion +
  • + + +
  • +Bug 44378 + - Turkish localisation +
  • + + +
  • +Add optional output variable name to Jexl function +
  • + + +
  • +Add application/vnd.wap.xhtml+xml as a text type +
  • + + +
  • +Add means to override maximum display size in View Results Tree - set the property: view.results.tree.max_size +
  • + + +
  • +Use Tidy to display XML in View Results Tree Listener (avoids fetching DTDs) +
  • + + +
  • +Bug 44487 + - German translation +
  • + + +
  • + +As a special case, if the HTTP Sampler path starts with "http://" or "https://" then this is used as the full URL. + +
  • + + +
  • +Bug 44575 + - Result Saver can now save only successful results +
  • + + +
  • +Bug 44650 + - CSV Dataset now handles quoted column values +
  • + + +
  • +Bug 44600 + - 1-ms resolution timer when running with Java 1.5+ +
  • + + +
  • +Bug 44632 + - Text input enhancement to FTP Sampler +
  • + + +
  • +Bug 42204 + - add thread group name to Aggregate and Summary reports +
  • + + +
  • +FTP Sampler sets latency = time to login +
  • + + +
  • +FTP Sampler sets a URL if it can +
  • + + +
  • +Bug 41921 + - add option for samplers to store MD5 of response; done for HTTP Samplers. +
  • + + +
  • +Regex Function can now also be applied to a variable rather than just the previous sample result. +
  • + + +
  • +Remove HTML Parameter Mask,HTTP User Parameter Modifier from menus as they are deprecated +
  • + + +
  • +Bug 44807 + - allow session ids to be terminated by backslash +
  • + + +
  • +Bug 44784 + - allow for broken server returning additional charset +
  • + + +
  • +Added TESTSTART.MS property / variable = test start time in milliseconds +
  • + + +
  • +Add POP3S and IMAPS protocols to Mail Reader Sampler. +
  • + + +
  • +Mail Reader Sampler now creates a sub-sample for each mail. +
  • + + +
  • +The supplied TCPClient implementation no longer treats tcp.eolByte=0 as special. +To skip EOL checking, set tcp.eolByte=1000 (or some other value which is not a valid byte) + +
  • + + +
  • +JUnit sampler GUI now also finds Test classes defined in user.classpath +
  • + + +
  • + +Leading and trailing spaces are trimmed from variable names in function calls. +For example, ${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY ' + +
  • + + +
  • +Webservice(SOAP) Sampler can now load local WSDL files using the file: protocol +
  • + + +
  • +Bug 44872 + - Add "All Files" filter to Open File dialogs +
  • + + +
  • +Mirror server can now be run independently (mirror-server.cmd and mirror-server.sh) +
  • + + +
  • +Bug 19128 + - Added multiple file POST support to HTTP Samplers +
  • + + +
  • +Allow use of special name LAST to mean the last test run; applies to -t, -l, -j flags +
  • + + +
  • +Bug 44418 +/42178 - CSV Dataset file handling improvements +
  • + + +
  • +Give BeanShell, Javascript and Jexl functions access to JMeter properties via the "props" object +
  • + + +
  • +Give BSF Sampler access to JMeter Properties via "props" object +
  • + + +
  • +Add Jexl as a supported BSF Sampler language +
  • + + +
  • +Give Beanshell test elements access to JMeter Properties via "props" object +
  • + + +
  • +Added BSF PreProcessor, PostProcessor and Assertion test elements +
  • + + +
  • +All BSF elements now have access to System.out via the variable "OUT" +
  • + + +
  • +Summariser updated to handle variable names +
  • + + +
  • +Synchronisation added to Summary and Aggregate Report to try to prevent occasional lost samples +
  • + + +
  • +Bug 44808 +, +Bug 39641 + - Proxy support for binary requests +
  • + + +
  • +Bug 28502 + - HTTP Resource Cache +
  • + + +
+

+Non-functional changes +

+
    + + +
  • +Better handling of MirrorServer startup problems and improved unit test. +
  • + + +
  • +Build process now detects missing 3rd party libraries and reports need for both binary and source archives +
  • + + +
  • +Skip BeanShell tests if jar is not present +
  • + + +
  • +Update to Xerces 2.9.1, Xalan 2.7.1, Commons IO 1.4, Commons Lang 2.4, Commons-Logging 1.1.1, XStream 1.3, XPP3 1.1.4c +
  • + + +
  • +Use properties for log/logn function descriptions +
  • + + +
  • +Check that all jmx files in the demos directory can be loaded OK +
  • + + +
  • +Update copyright to 2008; use copy tag instead of numeric character in HTML output +
  • + + +
  • +Methods called from constructors must not be overridable: make GUI init methods private +
  • + + +
  • +Make static variables final if possible +
  • + + +
  • +Split changes into current and previous +
  • + + +
+

+Version 2.3.1 +

+

+Summary of changes +

+
+JMeter Proxy +
+

+ +The Proxy spoof function was broken in 2.3; it has been fixed. +Spoof now supports an optional parameter to limit spoofing to particular URLs. +This is useful for HTTPS pages that have insecure content - e.g. images/stylesheets may be accessed using HTTP. +Spoofed responses now drop the default port (443) from https links to make them work better. + +

+

+ +Ignored proxy samples are now visible in Listeners - the label is enclosed in [ and ] as an indication. +Proxy documentation has been improved. + +

+
+GUI changes +
+

+The Add menus show element types in the order in which they are processed +- see + +Test Plan Execution Order + +. +It is no longer possible to add test elements to inappropriate parts of the tree +- e.g. samplers cannot be added directly under a test plan. +This also applies to Paste and drag and drop. + +

+

+ +The File menu now supports a "Revert" option, which reloads the current file. +Also the last few file names used are remembered for easy reloading. + +

+

+ +The Options Menu now supports Collapse All and Expand All items to collapse and expand the test tree. + +

+
+Remote testing +
+

+ +The JMeter server now starts the RMI server directly (by default). +This simplifies testing, and means that the RMI server will be stopped when the server stops. + +

+

+ +Functions can now be used in Listener filenames (variables do not work). + +

+

+ +Command-line option -G can now be used to define properties for remote servers. +Option -X can be used to stop a remote server after a non-GUI run. +Server can be set to automatically exit after a single test (set property server.exitaftertest=true). + +

+
+Other enhancements +
+

+ +JMeter startup no longer loads as many classes; this should reduce memory requirements. + +

+

+ +Parameter and file support added to all BeanShell elements. +Javascript function now supports access to JMeter objects; +Jexl function always did have access, but the documentation has now been included. +New functions __eval() and __evalVar() for evaluating variables. + +

+

+ +CSV files with the correct header column names are now automatically recognised when loaded. +There is no need to configure the properties. + +

+

+ +The hostname can now be saved in CSV and XML output files. +New "Successes only" option added when saving result files. +Errors / Successes only option is now supported when loading XML and CSV files. + +

+

+ +General documentation improvements. + +

+
+HTTP +
+

+PUT and DELETE should now work properly. +Cookie Manager no longer clears manually entered cookies. + +

+

+Now handles the META tag http-equiv charset +

+
+JDBC +
+

+JDBC Sampler now allows INOUT and OUT parameters for Called procedures. +JDBC Sampler now allows per-thread connections - set Max Connections = 0 in JDBC Config. + +

+
+ +

+Incompatible changes +

+
    + + +
  • +JMeter server now creates the RMI registry by default. +If the RMI registry has already been started externally, this will generate a warning message, but the server will continue. +This should not affect JMeter testing. +However, if you are also using the RMI registry for other applications there may be problems. +For example, when the JMeter server shuts down it will stop the RMI registry. +Also user-written command files may need to be adjusted (the ones supplied with JMeter have been updated). +To revert to the earlier behaviour, define the JMeter property: + +server.rmi.create=false + +. + +
  • + + +
  • +The Proxy server removes If-Modified-Since and If-None-Match headers from generated Header Managers. +To revert to the previous behaviour, define the property proxy.headers.remove with no value +
  • + + +
+

+Bug fixes +

+
    + + +
  • +Bug 43430 + - Count of active threads is incorrect for remote samples +
  • + + +
  • +Throughput Controller was not working for "all thread" counts +
  • + + +
  • +If a POST body is built from parameter values only, these are now encoded if the checkbox is set. +
  • + + +
  • +Bug 43584 + - Assertion Failure Message contains a comma that is also used as the delimiter for CSV files +
  • + + +
  • +HTTP Mirror Server now always returns the exact same content, it used to return incorrect data if UTF-8 encoding was used for HTTP POST body, for example +
  • + + +
  • +Bug 43612 + - HTTP PUT does not honor request parameters +
  • + + +
  • +Bug 43694 + - ForEach Controller (empty collection processing error) +
  • + + +
  • +Bug 42012 + - Variable Listener filenames do not get processed in remote tests. +Filenames can now include function references; variable references do not work. +
  • + + +
  • +Ensure Listener nodes get own save configuration when copy-pasted +
  • + + +
  • +Correct Proxy Server include and exclude matching description - port and query are included, contrary to previously documented. +
  • + + +
  • +Aggregate Graph and Aggregate Report Column Header is KB/Sec; fixed the values to be KB rather than bytes +
  • + + +
  • + +Fix SamplingStatCalculator so it no longer adds elapsed time to endTime, as this is handled by SampleResult. +This corrects discrepancies between Summary Report and Aggregate Report throughput calculation. + +
  • + + +
  • +Default HTTPSampleResult to ISO-8859-1 encoding +
  • + + +
  • +Fix default encoding for blank encoding +
  • + + +
  • +Fix Https spoofing (port problem) which was broken in 2.3 +
  • + + +
  • +Fix HTTP (Java) sampler so http.java.sampler.retries means retries, i.e. does not include initial try +
  • + + +
  • +Fix SampleResult dataType checking to better detect TEXT documents +
  • + + +
+

+Improvements +

+
    + + +
  • +Add run_gui Ant target, to package and then start the JMeter GUI from Ant +
  • + + +
  • +Add File->Revert to easily drop the current changes and reload the project file currently loaded +
  • + + +
  • +Bug 31366 + - Remember recently opened file(s) +
  • + + +
  • +Bug 43351 + - Add support for Parameters and script file to all BeanShell test elements +
  • + + +
  • +SaveService no longer needs to instantiate classes +
  • + + +
  • +New functions: __eval() and __evalVar() +
  • + + +
  • +Menu items now appear in execution order +
  • + + +
  • +Test Plan items can now only be dropped/pasted/merged into parts of the tree where they are allowed +
  • + + +
  • +Property Display to show the value of System and JMeter properties and allow them to be changed +
  • + + +
  • +Bug 43451 + - Allow Regex Extractor to operate on Response Code/Message +
  • + + +
  • +JDBC Sampler now allows INOUT and OUT parameters for Called procedures +
  • + + +
  • +JDBC Sampler now allows per-thread connections +
  • + + +
  • +Cookie Manager not longer clears cookies defined in the GUI +
  • + + +
  • +HTTP Parameters without names are ignored (except for POST requests with no file) +
  • + + +
  • +"Save Selection As" added to main menu; now checks only item is selected +
  • + + +
  • +Test Plan now has Paste menu item (paste was already supported via ^V) +
  • + + +
  • +If the default delimiter does not work when loading a CSV file, guess the delimiter by analysing the header line. +
  • + + +
  • +Add optional "loopback" protocol for HttpClient sampler +
  • + + +
  • +HTTP Mirror Server now supports blocking waiting for more data to appear, if content-length header is present in request +
  • + + +
  • +HTTP Mirror Server GUI now has the Start and Stop buttons in a more visible place +
  • + + +
  • +Server mode now creates the RMI registry; to disable set the JMeter property server.rmi.create=false +
  • + + +
  • +HTTP Sampler now supports using MIME Type field to specify content-type request header when body is constructed from parameter values +
  • + + +
  • +Enable exit after a single server test - define JMeter property server.exitaftertest=true +
  • + + +
  • +Added -G option to set properties in remote servers +
  • + + +
  • +Added -X option to stop remote servers after non-GUI run +
  • + + +
  • +Bug 43485 + - Ability to specify keep-alive on SOAP/XML-RPC request +
  • + + +
  • +Bug 43678 + - Handle META tag http-equiv charset +
  • + + +
  • +Bug 42555 + - [I18N] Proposed corrections for the french translation +
  • + + +
  • +Bug 43727 + - Test Action does not support variables or functions +
  • + + +
  • +The Proxy server removes If-Modified-Since and If-None-Match headers from generated Header Managers by default. +To change the list of removed headers, define the property proxy.headers.remove as a comma-separated list of headers to remove +
  • + + +
  • +The javaScript function now has access to JMeter variables and context etc. See + +JavaScript function + +
  • + + +
  • +Use drop-down list for BSF Sampler language field +
  • + + +
  • +Add hostname to items that can be saved in CSV and XML output files. +
  • + + +
  • +Errors only flag is now supported when loading XML and CSV files +
  • + + +
  • +Ensure ResultCollector uses SaveService encoding +
  • + + +
  • +Proxy now rejects attempts to use it with https +
  • + + +
  • +Proxy spoofing can now use RE matching to determine which urls to spoof (useful if images are not https) +
  • + + +
  • +Proxy spoofing now drops the default HTTPS port (443) when converting https: links to http: +
  • + + +
  • +Add Successes Only logging and display +
  • + + +
  • +The JMeter log file name is formatted as a SimpleDateFormat (applied to the current date) if it contains paired single-quotes, .e.g. 'jmeter_'yyyyMMddHHmmss'.log' +
  • + + +
  • +Added Collapse All and Expand All Option menu items +
  • + + +
  • +Allow optional definition of extra content-types that are viewable as text +
  • + + +
+

+Non-functional Improvements +

+
    + + +
  • +Functor code tightened up; Functor can now be used with interfaces, as well as pre-defined targets and parameters. +
  • + + +
  • +Save graphics function now prompts before overwriting an existing file +
  • + + +
  • +Debug Sampler and Debug PostProcessor added. +
  • + + +
  • +Fixed up method names in Calculator and SamplingStatCalculator +
  • + + +
  • +Tidied up Listener documentation. +
  • + + +
+

+Version 2.3 +

+

+Fixes since 2.3RC4 +

+

+Bug fixes +

+
    + + +
  • +Fix NPE in SampleResultConverter - XStream PrettyPrintWriter cannot handle nulls +
  • + + +
  • +If Java HTTP sampler sees null ResponseMessage, replace with HTTP header +
  • + + +
  • +Bug 43332 + - 2.3RC4 does not clear Guis based on TestBean +
  • + + +
  • +Bug 42948 + - Problems with Proxy gui table fields in Java 1.6 +
  • + + +
  • +Fixup broken jmeter-server script +
  • + + +
  • +Bug 43364 + - option to revert If Controller to pre 2.3RC3 behaviour +
  • + + +
  • +Bug 43449 + - Statistical Remote mode does not handle Latency +
  • + + +
  • +Bug 43450 + (partial fix) - Allow SampleCount and ErrorCount to be saved to/restored from files +
  • + + +
+

+Improvements +

+
    + + +
  • +Add nameSpace option to XPath extractor +
  • + + +
  • +Add NULL parameter option to JDBC sampler +
  • + + +
  • +Add documentation links for Rhino and BeanShell to functions; clarify variables and properties +
  • + + +
  • +Ensure uncaught exceptions are logged +
  • + + +
  • +Look for user.properties and system.properties in JMeter bin directory if not found locally +
  • + + +
+

+Fixes since 2.3RC3 +

+
    + + +
  • +Fixed NPE in Summariser (bug introduced in 2.3RC3) +
  • + + +
  • +Fixed setup of proxy port (bug introduced in 2.3RC3) +
  • + + +
  • +Fixed errors when running non-GUI on a headless host (bug introduced in 2.3RC3) +
  • + + +
  • +Bug 43054 + - SSLManager causes stress tests to saturate and crash (bug introduced in 2.3RC3) +
  • + + +
  • +Clarified HTTP Request Defaults usage of the port field +
  • + + +
  • +Bug 43006 + - NPE if icon.properties file not found +
  • + + +
  • +Bug 42918 + - Size Assertion now treats an empty response as having zero length +
  • + + +
  • +Bug 43007 + - Test ends before all threadgroups started +
  • + + +
  • +Fix possible NPE in HTTPSampler2 if 302 does not have Location header. +
  • + + +
  • +Bug 42919 + - Failure Message blank in CSV output [now records first non-blank message] +
  • + + +
  • +Add link to Extending JMeter PDF +
  • + + +
  • +Allow for quoted charset in Content-Type parsing +
  • + + +
  • +Bug 39792 + - ClientJMeter synchronisation needed +
  • + + +
  • +Bug 43122 + - GUI changes not always picked up when short-cut keys used (bug introduced in 2.3RC3) +
  • + + +
  • +Bug 42947 + - TestBeanGUI changes not picked up when short-cut keys used +
  • + + +
  • +Added serializer.jar (needed for update to xalan 2.7.0) +
  • + + +
  • +Bug 38687 + - Module controller does not work in non-GUI mode +
  • + + +
+

+Improvements since 2.3RC3 +

+
    + + +
  • +Add stop thread option to CSV Dataset +
  • + + +
  • +Updated commons-httpclient to 3.1 +
  • + + +
  • +Bug 28715 + - allow variable cookie values (set CookieManager.allow_variable_cookies=false to disable) +
  • + + +
  • +Bug 40873 + - add JMS point-to-point non-persistent delivery option +
  • + + +
  • +Bug 43283 + - Save action adds .jmx if not present; checks for existing file on Save As +
  • + + +
  • +Control+A key does not work for Save All As; changed to Control+Shift+S +
  • + + +
  • +Bug 40991 + - Allow Assertions to check Headers +
  • + + +
+

+Version 2.3RC3 +

+

+Known problems/restrictions: +

+

+ +The JMeter remote server does not support multiple concurrent tests - each remote test should be run in a separate server. +Otherwise tests may fail with random Exceptions, e.g. ConcurrentModification Exception in StandardJMeterEngine. +See +Bug 43168 +. + +

+

+ +The default HTTP Request (not HTTPClient) sampler may not work for HTTPS connections via a proxy. +This appears to be due to a Java bug, see +Bug 39337 +. +To avoid the problem, try a more recent version of Java, or switch to the HTTPClient version of the HTTP Request sampler. + +

+

+Transaction Controller parent mode does not support nested Transaction Controllers. +Doing so may cause a Null Pointer Exception in TestCompiler. + +

+

+Thread active counts are always zero in CSV and XML files when running remote tests. + +

+

+The property file_format.testlog=2.1 is treated the same as 2.2. +However JMeter does honour the 3 testplan versions. +

+

+ + +Bug 22510 + - JMeter always uses the first entry in the keystore. + +

+

+ +Remote mode does not work if JMeter is installed in a directory where the path name contains spaces. + +

+

+ +BeanShell test elements leak memory. +This can be reduced by using a file instead of including the script in the test element. + +

+

+ +Variables and functions do not work in Listeners in client-server (remote) mode so they cannot be used +to name log files in client-server mode. + +

+

+ +CSV Dataset variables are defined after configuration processing is completed, +so they cannot be used for other configuration items such as JDBC Config. +(see +Bug 40394 +) + +

+

+Summary of changes (for more details, see below) +

+

+ +Some of the main enhancements are: + +

+
    + + +
  • +Htmlparser 2.0 now used for parsing +
  • + + +
  • +HTTP Authorisation now supports domain and realm +
  • + + +
  • +HttpClient options can be specified via httpclient.parameters file +
  • + + +
  • +HttpClient now behaves the same as Java Http for SSL certificates +
  • + + +
  • +HTTP Mirror Server to allow local testing of HTTP samplers +
  • + + +
  • +HTTP Proxy supports XML-RPC recording, and other proxy improvements +
  • + + +
  • +__V() function allows support of nested variable references +
  • + + +
  • +LDAP Ext sampler optionally parses result sets and supports secure mode +
  • + + +
  • +FTP Sampler supports Ascii/Binary mode and upload +
  • + + +
  • +Transaction Controller now optionally generates a Sample with subresults +
  • + + +
  • +HTTPS session contexts are now per-thread, rather than shared. This gives better emulation of multiple users +
  • + + +
  • +BeanShell elements now support ThreadListener and TestListener interfaces +
  • + + +
  • +Coloured icons in Tree View Listener and elsewhere to better differentiate failed samples. +
  • + + +
+

+ +The main bug fixes are: + +

+
    + + +
  • +HTTPS (SSL) handling now much improved +
  • + + +
  • +Various Remote mode bugs fixed +
  • + + +
  • +Control+C and Control+V now work in the test tree +
  • + + +
  • +Latency and Encoding now available in CSV log output +
  • + + +
  • +Test elements no longer default to previous contents; test elements no longer cleared when changing language. +
  • + + +
+

+Incompatible changes (usage): +

+

+ + + +N.B. The javax.net.ssl properties have been moved from jmeter.properties to system.properties, +and will no longer work if defined in jmeter.properties. + + + +
+ + +The new arrangement is more flexible, as it allows arbitrary system properties to be defined. + +

+

+ +SSL session contexts are now created per-thread, rather than being shared. +This generates a more realistic load for HTTPS tests. +The change is likely to slow down tests with many SSL threads. +The original behaviour can be enabled by setting the JMeter property: + +

+
+https.sessioncontext.shared=true
+
+
+ + +

+

+ +The LDAP Extended Sampler now uses the same panel for both Thread Bind and Single-Bind tests. +This means that any tests using the Single-bind test will need to be updated to set the username and password. + +

+

+ + +Bug 41140 +: JMeterThread behaviour was changed so that PostProcessors are run in forward order +(as they appear in the test plan) rather than reverse order as previously. +The original behaviour can be restored by setting the following JMeter property: + +
+ + +jmeterthread.reversePostProcessors=true + +

+

+ +The HTTP Authorisation Manager now has extra columns for domain and realm, +so the temporary work-round of using '\' and '@' in the username to delimit the domain and realm +has been removed. + +

+

+ +Control-Z no longer used for Remote Start All - this now uses Control+Shift+R + +

+

+ +HttpClient now uses pre-emptive authentication. +This can be changed by setting the following: + +

+
+jmeter.properties:
+httpclient.parameters.file=httpclient.parameters
+
+httpclient.parameters:
+http.authentication.preemptive$Boolean=false
+
+
+ + +

+

+ +The port field in HTTP Request Defaults is no longer ignored for https samplers if it is set to 80. + +

+

+Incompatible changes (development): +

+

+ + + +N.B. + +The clear() method was defined in the following interfaces: Clearable, JMeterGUIComponent and TestElement. +The methods serve different purposes, so two of them were renamed: +the Clearable method is now clearData() and the JMeterGUIComponent method is now clearGui(). +3rd party add-ons may need to be rebuilt. + +

+

+ +Calulator and SamplingStatCalculator classes no longer provide any formatting of their data. +Formatting should now be done using the jorphan.gui Renderer classes. + +

+

+ +Removed deprecated method JMeterUtils.split() - use JOrphanUtils version instead. + +

+

+ +Removed method saveUsingJPEGEncoder() from SaveGraphicsService. +It was unused so far, and used the only Sun-specific class in JMeter. + +

+

+New functionality/improvements: +

+
    + + +
  • +Add Domain and Realm support to HTTP Authorisation Manager +
  • + + +
  • +HttpClient now behaves the same as the JDK http sampler for invalid certificates etc +
  • + + +
  • +Added httpclient.parameters.file to allow HttpClient parameters to be defined +
  • + + +
  • +Bug 33964 + - Http Requests can send a file as the entire post body if name/type are omitted +
  • + + +
  • +Bug 41705 + - add content-encoding option to HTTP samplers for POST requests +
  • + + +
  • +Bug 40933 +, +Bug 40945 + - optional RE matching when retrieving embedded resource URLs +
  • + + +
  • +Bug 27780 + - (patch 19936) create multipart/form-data HTTP request without uploading file +
  • + + +
  • +Bug 42098 + - Use specified encoding for parameter values in HTTP GET +
  • + + +
  • +Bug 42506 + - JMeter threads now use independent SSL sessions +
  • + + +
  • +Bug 41707 + - HTTP Proxy XML-RPC support +
  • + + +
  • +Bug 41880 + - Add content-type filtering to HTTP Proxy Server +
  • + + +
  • +Bug 41876 + - Add more options to control what the HTTP Proxy generates +
  • + + +
  • +Bug 42158 + - Improve support for multipart/form-data requests in HTTP Proxy server +
  • + + +
  • +Bug 42173 + - Let HTTP Proxy handle encoding of request, and undecode parameter values +
  • + + +
  • +Bug 42674 + - default to pre-emptive HTTP authorisation if not specified +
  • + + +
  • +Support "file" protocol in HTTP Samplers +
  • + + +
  • +Http Autoredirects are now enabled by default when creating new samplers +
  • + + + +
  • +Bug 40103 + - various LDAP enhancements +
  • + + +
  • +Bug 40369 + - LDAP: Stable search results in sampler +
  • + + +
  • +Bug 40381 + - LDAP: more descriptive strings +
  • + + + +
  • +BeanShell Post-Processor no longer ignores samples with zero-length result data +
  • + + +
  • +Added beanshell.init.file property to run a BeanShell script at startup +
  • + + +
  • +Bug 39864 + - BeanShell init files now found from currrent or bin directory +
  • + + +
  • +BeanShell elements now support ThreadListener and TestListener interfaces +
  • + + +
  • +BSF Sampler passes additional variables to the script +
  • + + + +
  • +Added timeout for WebService (SOAP) Sampler +
  • + + + +
  • +Bug 40825 + - Add JDBC prepared statement support +
  • + + +
  • +Extend JDBC Sampler: Commit, Rollback, AutoCommit +
  • + + + +
  • +Bug 41457 + - Add TCP Sampler option to not re-use connections +
  • + + + +
  • +Bug 41522 + - Use JUnit sampler name in sample results +
  • + + + +
  • +Bug 42223 + - FTP Sampler can now upload files +
  • + + + +
  • +Bug 40804 + - Change Counter default to max = Long.MAX_VALUE +
  • + + + +
  • +Use property jmeter.home (if present) to override user.dir when starting JMeter +
  • + + +
  • +New -j option to easily change jmeter log file +
  • + + + +
  • +HTTP Mirror Server Workbench element +
  • + + + +
  • +Bug 41253 + - extend XPathExtractor to work with non-NodeList XPath expressions +
  • + + +
  • +Bug 42088 + - Add XPath Assertion for booleans +
  • + + + +
  • +Added __V variable function to resolve nested variable names +
  • + + + +
  • +Bug 40369 + - Equals Response Assertion +
  • + + +
  • +Bug 41704 + - Allow charset encoding to be specified for CSV DataSet +
  • + + +
  • +Bug 41259 + - Comment field added to all test elements +
  • + + +
  • +Add standard deviation to Summary Report +
  • + + +
  • +Bug 41873 + - Add name to AssertionResult and display AssertionResult in ViewResultsFullVisualizer +
  • + + +
  • +Bug 36755 + - Save XML test files with UTF-8 encoding +
  • + + +
  • +Use ISO date-time format for Tree View Listener (previously the year was not shown) +
  • + + +
  • +Improve loading of CSV files: if possible, use header to determine format; guess timestamp format if not milliseconds +
  • + + +
  • +Bug 41913 + - TransactionController now creates samples as sub-samples of the transaction +
  • + + +
  • +Bug 42582 + - JSON pretty printing in Tree View Listener +
  • + + +
  • +Bug 40099 + - Enable use of object variable in ForEachController +
  • + + + +
  • +Bug 39693 + - View Result Table uses icon instead of check box +
  • + + +
  • +Bug 39717 + - use icons in the results tree +
  • + + +
  • +Bug 42247 + - improve HCI +
  • + + +
  • +Allow user to cancel out of Close dialogue +
  • + + +
+

+Non-functional improvements: +

+
    + + +
  • +Functor calls can now be unit tested +
  • + + +
  • +Replace com.sun.net classes with javax.net +
  • + + +
  • +Extract external jar definitions into build.properties file +
  • + + +
  • +Use specific jar names in build classpaths so errors are detected sooner +
  • + + +
  • +Tidied up ORO calls; now only one cache, size given by oro.patterncache.size, default 1000 +
  • + + +
  • +Bug 42326 + - Order of elements in .jmx files changes +
  • + + +
+

+External jar updates: +

+
    + + +
  • +Htmlparser 2.0-20060923 +
  • + + +
  • +xstream 1.2.1/xpp3_min-1.1.3.4.O +
  • + + +
  • +Batik 1.6 +
  • + + +
  • +BSF 2.4.0 +
  • + + +
  • +commons-collections 3.2 +
  • + + +
  • +commons-httpclient-3.1-rc1 +
  • + + +
  • +commons-jexl 1.1 +
  • + + +
  • +commons-lang-2.3 (added) +
  • + + +
  • +JUnit 3.8.2 +
  • + + +
  • +velocity 1.5 +
  • + + +
  • +commons-io 1.3.1 (added) +
  • + + +
+

+Bug fixes: +

+
    + + +
  • +Bug 39773 + - NTLM now needs local host name - fix other call +
  • + + +
  • +Bug 40438 + - setting "httpclient.localaddress" has no effect +
  • + + +
  • +Bug 40419 + - Chinese messages translation fix +
  • + + +
  • +Bug 39861 + - fix typo +
  • + + +
  • +Bug 40562 + - redirects no longer invoke RE post processors +
  • + + +
  • +Bug 40451 + - set label if not set by sampler +
  • + + +
  • +Fix NPE in CounterConfig.java in Remote mode +
  • + + +
  • +Bug 40791 + - Calculator used by Summary Report +
  • + + +
  • +Bug 40772 + - correctly parse missing fields in CSV log files +
  • + + +
  • +Bug 40773 + - XML log file timestamp not parsed correctly +
  • + + +
  • +Bug 41029 + - JMeter -t fails to close input JMX file +
  • + + +
  • +Bug 40954 + - Statistical mode in distributed testing shows wrong results +
  • + + +
  • +Fix ClassCast Exception when using sampler that returns null, e..g TestAction +
  • + + +
  • +Bug 41140 + - Post-processors are run in reverse order +
  • + + +
  • +Bug 41277 + - add Latency and Encoding to CSV output +
  • + + +
  • +Bug 41414 + - Mac OS X may add extra item to -jar classpath +
  • + + +
  • +Fix NPE when saving thread counts in remote testing +
  • + + +
  • +Bug 34261 + - NPE in HtmlParser (allow for missing attributes) +
  • + + +
  • +Bug 40100 + - check FileServer type before calling close +
  • + + +
  • +Bug 39887 + - jmeter.util.SSLManager: Couldn't load keystore error message +
  • + + +
  • +Bug 41543 + - exception when webserver returns "500 Internal Server Error" and content-length is 0 +
  • + + +
  • +Bug 41416 + - don't use chunked input for text-box input in SOAP-RPC sampler +
  • + + +
  • +Bug 39827 + - SOAP Sampler content length for files +
  • + + +
  • +Fix Class cast exception in Clear.java +
  • + + +
  • +Bug 40383 + - don't set content-type if already set +
  • + + +
  • +Mailer Visualiser test button now works if test plan has not yet been saved +
  • + + +
  • +Bug 36959 + - Shortcuts "ctrl c" and "ctrl v" don't work on the tree elements +
  • + + +
  • +Bug 40696 + - retrieve embedded resources from STYLE URL() attributes +
  • + + +
  • +Bug 41568 + - Problem when running tests remotely when using a 'Counter' +
  • + + +
  • +Fixed various classes that assumed timestamps were always end time stamps: + +
      + + +
    • +SamplingStatCalculator +
    • + + +
    • +JTLData +
    • + + +
    • +RunningSample +
    • + + +
    + + +
  • + + +
  • +Bug 40325 + - allow specification of proxyuser and proxypassword for WebServiceSampler +
  • + + +
  • +Change HttpClient proxy definition to use NTCredentials; added http.proxyDomain property for this +
  • + + +
  • +Bug 40371 + - response assertion "pattern to test" scrollbar problem +
  • + + +
  • +Bug 40589 + - Unescape XML entities in embedded URLs +
  • + + +
  • +Bug 41902 + - NPE in HTTPSampler when responseCode = -1 +
  • + + +
  • +Bug 41903 + - ViewResultsFullVisualizer : status column looks bad when you do copy and paste +
  • + + +
  • +Bug 41837 + - Parameter value corruption in proxy +
  • + + +
  • +Bug 41905 + - Can't cut/paste/select Header Manager fields in Java 1.6 +
  • + + +
  • +Bug 41928 + - Make all request headers sent by HTTP Request sampler appear in sample result +
  • + + +
  • +Bug 41944 + - Subresults not handled recursively by ResultSaver +
  • + + +
  • +Bug 42022 + - HTTPSampler does not allow multiple headers of same name +
  • + + +
  • +Bug 42019 + - Content type not stored in redirected HTTP request with subresults +
  • + + +
  • +Bug 42057 + - connection can be null if method is null +
  • + + +
  • +Bug 41518 + - JMeter changes the HTTP header Content Type for POST request +
  • + + +
  • +Bug 42156 + - HTTPRequest HTTPClient incorrectly urlencodes parameter value in POST +
  • + + +
  • +Bug 42184 + - Number of bytes for subsamples not added to sample when sub samples are added +
  • + + +
  • +Bug 42185 + - If a HTTP Sampler follows a redirect, and is set up to download images, then images are downloaded multiple times +
  • + + +
  • +Bug 39808 + - Invalid redirect causes incorrect sample time +
  • + + +
  • +Bug 42267 + - Concurrent GUI update failure in Proxy Recording +
  • + + +
  • +Bug 30120 + - Name of simple controller is resetted if a new simple controller is added as child +
  • + + +
  • +Bug 41078 + - merge results in name change of test plan +
  • + + +
  • +Bug 40077 + - Creating new Elements copies values from Existing elements +
  • + + +
  • +Bug 42325 + - Implement the "clear" method for the LogicControllers +
  • + + +
  • +Bug 25441 + - TestPlan changes sometimes detected incorrectly (isDirty) +
  • + + +
  • +Bug 39734 + - Listeners shared after copy/paste operation +
  • + + +
  • +Bug 40851 + - Loop controller with 0 iterations, stops evaluating the iterations field +
  • + + +
  • +Bug 24684 + - remote startup problems if spaces in the path of the jmeter +
  • + + +
  • +Use Listener configuration when loading CSV data files +
  • + + +
  • +Function methods setParameters() need to be synchronized +
  • + + +
  • +Fix CLI long optional argument to require "=" (as for short options) +
  • + + +
  • +Fix SlowSocket to work properly with Httpclient (both http and https) +
  • + + +
  • +Bug 41612 + - Loop nested in If Controller behaves erratically +
  • + + +
  • +Bug 42232 + - changing language clears UDV contents +
  • + + +
  • +Jexl function did not allow variables +
  • + + +
+

+Version 2.2 +

+

+Incompatible changes: +

+

+ +The time stamp is now set to the sampler start time (it was the end). +To revert to the previous behaviour, change the property + +sampleresult.timestamp.start + + to false (or comment it) + +

+

+The JMX output format has been simplified and files are not backwards compatible +

+

+ +The JMeter.BAT file no longer changes directory to JMeter home, but runs from the current working directory. +The jmeter-n.bat and jmeter-t.bat files change to the directory containing the input file. + +

+

+ +Listeners are now started slightly later in order to allow variable names to be used. +This may cause some problems; if so define the following in jmeter.properties: + +
+ + +jmeterengine.startlistenerslater=false + +

+

+ +The GUI now expands the tree by default when loading a test plan. +This can be disabled by setting the JMeter property + +onload.expandtree=false + + + +

+

+Known problems: +

+
    + + +
  • +Post-processors run in reverse order (see +Bug 41140 +) +
  • + + +
  • +Module Controller does not work in non-GUI mode +
  • + + +
  • +Aggregate Report and some other listeners use increasing amounts of memory as a test progresses +
  • + + +
  • +Does not always handle non-default encoding properly +
  • + + +
  • +Spaces in the installation path cause problems for client-server mode +
  • + + +
  • +Change of Language does not propagate to all test elements +
  • + + +
  • +SamplingStatCalculator keeps a List of all samples for calculation purposes; +this can cause memory exhaustion in long-running tests +
  • + + +
  • +Does not properly handle server certificates if they are expired or not installed locally +
  • + + +
+

+New functionality: +

+
    + + +
  • +Report function +
  • + + +
  • +XPath Extractor Post-Processor. Handles single and multiple matches. +
  • + + +
  • +Simpler JMX file format (2.2) +
  • + + +
  • +BeanshellSampler code can update ResponseData directly +
  • + + +
  • +Bug 37490 + - Allow UDV as delay in Duration Assertion +
  • + + +
  • +Slow connection emulation for HttpClient +
  • + + +
  • +Enhanced JUnitSampler so that by default assert errors and exceptions are not appended to the error message. +Users must explicitly check append in the sampler +
  • + + +
  • +Enhanced the documentation for webservice sampler to explain how it works with CSVDataSet +
  • + + +
  • +Enhanced the documentation for javascript function to explain escaping comma +
  • + + +
  • +Allow CSV Data Set file names to be absolute +
  • + + +
  • +Report Tree compiler errors better +
  • + + +
  • +Don't reset Regex Extractor variable if default is empty +
  • + + +
  • +includecontroller.prefix property added +
  • + + +
  • +Regular Expression Extractor sets group count +
  • + + +
  • +Can now save entire screen as an image, not just the right-hand pane +
  • + + +
  • +Bug 38901 + - Add optional SOAPAction header to SOAP Sampler +
  • + + +
  • +New BeanShell test elements: Timer, PreProcessor, PostProcessor, Listener +
  • + + +
  • +__split() function now clears next variable, so it can be used with ForEach Controller +
  • + + +
  • +Bug 38682 + - add CallableStatement functionality to JDBC Sampler +
  • + + +
  • +Make it easier to change the RMI/Server port +
  • + + +
  • +Add property jmeter.save.saveservice.xml_pi to provide optional xml processing instruction in JTL files +
  • + + +
  • +Add bytes and URL to items that can be saved in sample log files (XML and CSV) +
  • + + +
  • +The Post-Processor "Save Responses to a File" now saves the generated file name with the +sample, and the file name can be included in the sample log file. + +
  • + + +
  • +Change jmeter.bat DOS script so it works from any directory +
  • + + +
  • +New -N option to define nonProxyHosts from command-line +
  • + + +
  • +New -S option to define system properties from input file +
  • + + +
  • +Bug 26136 + - allow configuration of local address +
  • + + +
  • +Expand tree by default when loading a test plan - can be disabled by setting property onload.expandtree=false +
  • + + +
  • +Bug 11843 + - URL Rewriter can now cache the session id +
  • + + +
  • +Counter Pre-Processor now supports formatted numbers +
  • + + +
  • +Add support for HEAD PUT OPTIONS TRACE and DELETE methods +
  • + + +
  • +Allow default HTTP implementation to be changed +
  • + + +
  • +Optionally save active thread counts (group and all) to result files +
  • + + +
  • +Variables/functions can now be used in Listener file names +
  • + + +
  • +New __time() function; define START.MS/START.YMD/START.HMS properties and variables +
  • + + +
  • +Add Thread Name to Tree and Table Views +
  • + + +
  • +Add debug functions: What class, debug on, debug off +
  • + + +
  • +Non-caching Calculator - used by Table Visualiser to reduce memory footprint +
  • + + +
  • +Summary Report - similar to Aggregate Report, but uses less memory +
  • + + +
  • +Bug 39580 + - recycle option for CSV Dataset +
  • + + +
  • +Bug 37652 + - support for Ajp Tomcat protocol +
  • + + +
  • +Bug 39626 + - Loading SOAP/XML-RPC requests from file +
  • + + +
  • +Bug 39652 + - Allow truncation of labels on AxisGraph +
  • + + +
  • +Allow use of htmlparser 1.6 +
  • + + +
  • +Bug 39656 + - always use SOAP action if it is provided +
  • + + +
  • +Automatically include properties from user.properties file +
  • + + +
  • +Add __jexl() function - evaluates Commons JEXL expressions +
  • + + +
  • +Optionally load JMeter properties from user.properties and system properties from system.properties. +
  • + + +
  • +Bug 39707 + - allow Regex match against URL +
  • + + +
  • +Add start time to Table Visualiser +
  • + + +
  • +HTTP Samplers can now extract embedded resources for any required media types +
  • + + +
+

+Bug fixes: +

+
    + + +
  • +Fix NPE when no module selected in Module Controller +
  • + + +
  • +Fix NPE in XStream when no ResponseData present +
  • + + +
  • +Remove ?xml prefix when running with Java 1.5 and no x-jars +
  • + + +
  • +Bug 37117 + - setProperty() function should return ""; added optional return of original setting +
  • + + +
  • +Fix CSV output time format +
  • + + +
  • +Bug 37140 + - handle encoding better in RegexFunction +
  • + + +
  • +Load all cookies, not just the first; fix class cast exception +
  • + + +
  • +Fix default Cookie path name (remove page name) +
  • + + +
  • +Fixed resultcode attribute name +
  • + + +
  • +Bug 36898 + - apply encoding to RegexExtractor +
  • + + +
  • +Add properties for saving subresults, assertions, latency, samplerData, responseHeaders, requestHeaders & encoding +
  • + + +
  • +Bug 37705 + - Synch Timer now works OK after run is stopped +
  • + + +
  • +Bug 37716 + - Proxy request now handles file Post correctly +
  • + + +
  • +HttpClient Sampler now saves latency +
  • + + +
  • +Fix NPE when using JavaScript function on Test Plan +
  • + + +
  • +Fix Base Href parsing in htmlparser +
  • + + +
  • +Bug 38256 + - handle cookie with no path +
  • + + +
  • +Bug 38391 + - use long when accumulating timer delays +
  • + + +
  • +Bug 38554 + - Random function now uses long numbers +
  • + + +
  • +Bug 35224 + - allow duplicate attributes for LDAP sampler +
  • + + +
  • +Bug 38693 + - Webservice sampler can now use https protocol +
  • + + +
  • +Bug 38646 + - Regex Extractor now clears old variables on match failure +
  • + + +
  • +Bug 38640 + - fix WebService Sampler pooling +
  • + + +
  • +Bug 38474 + - HTML Link Parser doesn't follow frame links +
  • + + +
  • +Bug 36430 + - Counter now uses long rather than int to increase the range +
  • + + +
  • +Bug 38302 + - fix XPath function +
  • + + +
  • +Bug 38748 + - JDBC DataSourceElement fails with remote testing +
  • + + +
  • +Bug 38902 + - sometimes -1 seems to be returned unnecessarily for response code +
  • + + +
  • +Bug 38840 + - make XML Assertion thread-safe +
  • + + +
  • +Bug 38681 + - Include controller now works in non-GUI mode +
  • + + +
  • +Add write(OS,IS) implementation to TCPClientImpl +
  • + + +
  • +Sample Result converter saves response code as "rc". Previously it saved as "rs" but read with "rc"; it will now also read with "rc". +The XSL stylesheets also now accept either "rc" or "rs" +
  • + + +
  • +Fix counter function so each counter instance is independent (previously the per-user counters were shared between instances of the function) +
  • + + +
  • +Fix TestBean Examples so that they work +
  • + + +
  • +Fix JTidy parser so it does not skip body tags with background images +
  • + + +
  • +Fix HtmlParser parser so it catches all background images +
  • + + +
  • +Bug 39252 + set SoapSampler sample result from XML data +
  • + + +
  • +Bug 38694 + - WebServiceSampler not setting data encoding correctly +
  • + + +
  • +Result Collector now closes input files read by listeners +
  • + + +
  • +Bug 25505 + - First HTTP sampling fails with "HTTPS hostname wrong: should be 'localhost'" +
  • + + +
  • +Bug 25236 + - remove double scrollbar from Assertion Result Listener +
  • + + +
  • +Bug 38234 + - Graph Listener divide by zero problem +
  • + + +
  • +Bug 38824 + - clarify behaviour of Ignore Status +
  • + + +
  • +Bug 38250 + - jmeter.properties "language" now supports country suffix, for zh_CN and zh_TW etc +
  • + + +
  • +jmeter.properties file is now closed after it has been read +
  • + + +
  • +Bug 39533 + - StatCalculator added wrong items +
  • + + +
  • +Bug 39599 + - ConcurrentModificationException +
  • + + +
  • +HTTPSampler2 now handles Auto and Follow redirects correctly +
  • + + +
  • +Bug 29481 + - fix reloading sample results so subresults not counted twice +
  • + + +
  • +Bug 30267 + - handle AutoRedirects properly +
  • + + +
  • +Bug 39677 + - allow for space in JMETER_BIN variable +
  • + + +
  • +Use Commons HttpClient cookie parsing and management. Fix various problems with cookie handling. +
  • + + +
  • +Bug 39773 + - NTCredentials needs host name +
  • + + +
+

+Other changes +

+
    + + +
  • +Updated to HTTPClient 3.0 (from 2.0) +
  • + + +
  • +Updated to Commons Collections 3.1 +
  • + + +
  • +Improved formatting of Request Data in Tree View +
  • + + +
  • +Expanded user documentation +
  • + + +
  • +Added MANIFEST, NOTICE and LICENSE to all jars +
  • + + +
  • +Extract htmlparser interface into separate jarfile to make it possible to replace the parser +
  • + + +
  • +Removed SQL Config GUI as no longer needed (or working!) +
  • + + +
  • +HTTPSampler no longer logs a warning for Page not found (404) +
  • + + +
  • +StringFromFile now callable as __StringFromFile (as well as _StringFromFile) +
  • + + +
  • +Updated to Commons Logging 1.1 +
  • + + +
+
+ +

+Version 2.1.1 +

+

+New functionality: +

+
    + + +
  • +New Include Controller allows a test plan to reference an external jmx file +
  • + + +
  • +New JUnitSampler added for using JUnit Test classes +
  • + + +
  • +New Aggregate Graph listener is capable of graphing aggregate statistics +
  • + + +
  • +Can provide additional classpath entries using the property user.classpath and on the Test Plan element +
  • + + +
+

+Bug fixes: +

+
    + + +
  • +AccessLog Sampler and JDBC test elements populated correctly from 2.0 test plans +
  • + + +
  • +BSF Sampler now populates filename and parameters from saved test plan +
  • + + +
  • +Bug 36500 + - handle missing data more gracefully in WebServiceSampler +
  • + + +
  • +Bug 35546 + - add merge to right-click menu +
  • + + +
  • +Bug 36642 + - Summariser stopped working in 2.1 +
  • + + +
  • +Bug 36618 + - CSV header line did not match saved data +
  • + + +
  • +JMeter should now run under JVM 1.3 (but does not build with 1.3) +
  • + + +
+

+Version 2.1 +

+

+New functionality: +

+
    + + +
  • +New Test Script file format - smaller, more compact, more readable +
  • + + +
  • +New Sample Result file format - smaller, more compact +
  • + + +
  • +XSchema Assertion +
  • + + +
  • +XML Tree display +
  • + + +
  • +CSV DataSet Config item +
  • + + +
  • +New JDBC Connection Pool Config Element +
  • + + +
  • +Synchronisation Timer +
  • + + +
  • +setProperty function +
  • + + +
  • +Save response data on error +
  • + + +
  • +Ant JMeter XSLT now optionally shows failed responses and has internal links +
  • + + +
  • +Allow JavaScript variable name to be omitted +
  • + + +
  • +Changed following Samplers to set sample label from sampler name +
  • + + +
  • +All Test elements can be saved as a graphics image to a file +
  • + + +
  • +Bug 35026 + - add RE pattern matching to Proxy +
  • + + +
  • +Bug 34739 + - Enhance constant Throughput timer +
  • + + +
  • +Bug 25052 + - use response encoding to create comparison string in Response Assertion +
  • + + +
  • +New optional icons +
  • + + +
  • +Allow icons to be defined via property files +
  • + + +
  • +New stylesheets for 2.1 format XML test output +
  • + + +
  • +Save samplers, config element and listeners as PNG +
  • + + +
  • +Enhanced support for WSDL processing +
  • + + +
  • +New JMS sampler for topic and queue messages +
  • + + +
  • +How-to for JMS samplers +
  • + + +
  • +Bug 35525 + - Added Spanish localisation +
  • + + +
  • +Bug 30379 + - allow server.rmi.port to be overridden +
  • + + +
  • +enhanced the monitor listener to save the calculated stats +
  • + + +
  • +Functions and variables now work at top level of test plan +
  • + + +
+

+Bug fixes: +

+
    + + +
  • +Bug 34586 + - XPath always remained as / +
  • + + +
  • +BeanShellInterpreter did not handle null objects properly +
  • + + +
  • +Fix Chinese resource bundle names +
  • + + +
  • +Save field names if required to CSV files +
  • + + +
  • +Ensure XML file is closed +
  • + + +
  • +Correct icons now displayed for TestBean components +
  • + + +
  • +Allow for missing optional jar(s) in creating menus +
  • + + +
  • +Changed Samplers to set sample label from sampler name as was the case for HTTP +
  • + + +
  • +Fix various samplers to avoid NPEs when incomplete data is provided +
  • + + +
  • +Fix Cookie Manager to use seconds; add debug +
  • + + +
  • +Bug 35067 + - set up filename when using -t option +
  • + + +
  • +Don't substitute TestElement.* properties by UDVs in Proxy +
  • + + +
  • +Bug 35065 + - don't save old extensions in File Saver +
  • + + +
  • +Bug 25413 + - don't enable Restart button unnecessarily +
  • + + +
  • +Bug 35059 + - Runtime Controller stopped working +
  • + + +
  • +Clear up any left-over connections created by LDAP Extended Sampler +
  • + + +
  • +Bug 23248 + - module controller didn't remember stuff between save and reload +
  • + + +
  • +Fix Chinese locales +
  • + + +
  • +Bug 29920 + - change default locale if necessary to ensure default properties are picked up when English is selected. +
  • + + +
  • +Bug fixes for Tomcat monitor captions +
  • + + +
  • +Fixed webservice sampler so it works with user defined variables +
  • + + +
  • +Fixed screen borders for LDAP config GUI elements +
  • + + +
  • +Bug 31184 + - make sure encoding is specified in JDBC sampler +
  • + + +
  • +TCP sampler - only share sockets with same host:port details; correct the manual +
  • + + +
  • +Extract src attribute for embed tags in JTidy and Html Parsers +
  • + + +
+

+Version 2.0.3 +

+

+New functionality: +

+
    + + +
  • +XPath Assertion and XPath Function +
  • + + +
  • +Switch Controller +
  • + + +
  • +ForEach Controller can now loop through sets of groups +
  • + + +
  • +Allow CSVRead delimiter to be changed (see jmeter.properties) +
  • + + +
  • +Bug 33920 + - allow additional property files +
  • + + +
  • +Bug 33845 + - allow direct override of Home dir +
  • + + +
+

+Bug fixes: +

+
    + + +
  • +Regex Extractor nested constant not put in correct place +Bug 32395 +
  • + + +
  • +Start time reset to now if necessary so that delay works OK. +
  • + + +
  • +Missing start/end times in scheduler are assumed to be now, not 1970 +
  • + + +
  • +Bug 28661 + - 304 responses not appearing in listeners +
  • + + +
  • +DOS scripts now handle different disks better +
  • + + +
  • +Bug 32345 + - HTTP Rewriter does not work with HTTP Request default +
  • + + +
  • +Catch Runtime Exceptions so an error in one Listener does not affect others +
  • + + +
  • +Bug 33467 + - __threadNum() extracted number wrongly +
  • + + +
  • +Bug 29186 +,33299 - fix CLI parsing of "-" in second argument +
  • + + +
  • +Fix CLI parse bug: -D arg1=arg2. Log more startup parameters. +
  • + + +
  • +Fix JTidy and HTMLParser parsers to handle form src= and link rel=stylesheet +
  • + + +
  • +JMeterThread now logs Errors to jmeter.log which were appearing on console +
  • + + +
  • +Ensure WhileController condition is dynamically checked +
  • + + +
  • +Bug 32790 + ensure If Controller condition is re-evaluated each time +
  • + + +
  • +Bug 30266 + - document how to display proxy recording responses +
  • + + +
  • +Bug 33921 + - merge should not change file name +
  • + + +
  • +Close file now gives chance to save changes +
  • + + +
  • +Bug 33559 + - fixes to Runtime Controller +
  • + + +
+

+Other changes: +

+
    + + +
  • +To help with variable evaluation, JMeterThread sets "sampling started" a bit earlier (see jmeter.properties) +
  • + + +
  • +Bug 33796 + - delete cookies with null/empty values +
  • + + +
  • +Better checking of parameter count in JavaScript function +
  • + + +
  • +Thread Group now defaults to 1 loop instead of forever +
  • + + +
  • +All Beanshell access is now via a single class; only need BSH jar at run-time +
  • + + +
  • +Bug 32464 + - document Direct Draw settings in jmeter.bat +
  • + + +
  • +Bug 33919 + - increase Counter field sizes +
  • + + +
  • +Bug 32252 + - ForEach was not initialising counters +
  • + + +
+

+Version 2.0.2 +

+

+New functionality: +

+
    + + +
  • +While Controller +
  • + + +
  • +BeanShell intilisation scripts +
  • + + +
  • +Result Saver can optionally save failed results only +
  • + + +
  • +Display as HTML has option not to download frames and images etc +
  • + + +
  • +Multiple Tree elements can now be enabled/disabled/copied/pasted at once +
  • + + +
  • +__split() function added +
  • + + +
  • +Bug 28699 + allow Assertion to regard unsuccessful responses - e.g. 404 - as successful +
  • + + +
  • +Bug 29075 + Regex Extractor can now extract data out of http response header as well as the body +
  • + + +
  • +__log() functions can now write to stdout and stderr +
  • + + +
  • +URL Modifier can now optionally ignore query parameters +
  • + + +
+

+Bug fixes: +

+
    + + +
  • +If controller now works after the first false condition +Bug 31390 +
  • + + +
  • +Regex GUI was losing track of Header/Body checkbox +Bug 29853 +
  • + + +
  • +Display as HTML now handles frames and relative images +
  • + + +
  • +Right-click open replaced by merge +
  • + + +
  • +Fix some drag and drop problems +
  • + + +
  • +Fixed foreach demo example so it works +
  • + + +
  • +Bug 30741 + SSL password prompt now works again +
  • + + +
  • +StringFromFile now closes files at end of test; start and end now optional as intended +
  • + + +
  • +Bug 31342 + Fixed text of SOAP Sampler headers +
  • + + +
  • +Proxy must now be stopped before it can be removed +Bug 25145 +
  • + + +
  • +Link Parser now supports BASE href +Bug 25490 +
  • + + +
  • +Bug 30917 + Classfinder ignores duplicate names +
  • + + +
  • +Bug 22820 + Allow Counter value to be cleared +
  • + + +
  • +Bug 28230 + Fix NPE in HTTP Sampler retrieving embedded resources +
  • + + +
  • +Improve handling of StopTest; catch and log some more errors +
  • + + +
  • +ForEach Controller no longer runs any samples if first variable is not defined +
  • + + +
  • +Bug 28663 + NPE in remote JDBC execution +
  • + + +
  • +Bug 30110 + Deadlock in stopTest processing +
  • + + +
  • +Bug 31696 + Duration not working correctly when using Scheduler +
  • + + +
  • +JMeterContext now uses ThreadLocal - should fix some potential NPE errors +
  • + + +
+

+Version 2.0.1 +

+

+Bug fix release. TBA. +

+

+Version 2.0 +

+
    + + +
  • +HTML parsing improved; now has choice of 3 parsers, and most embedded elements can now be detected and downloaded. +
  • + + +
  • +Redirects can now be delegated to URLConnection by defining the JMeter property HTTPSamper.delegateRedirects=true (default is false) +
  • + + +
  • +Stop Thread and Stop Test methods added for Samplers and Assertions etc. Samplers can call setStopThread(true) or setStopTest(true) if they detect an error that needs to stop the thread of the test after the sample has been processed +
  • + + +
  • +Thread Group Gui now has an extra pane to specify what happens after a Sampler error: Continue (as now), Stop Thread or Stop Test. + This needs to be extended to a lower level at some stage. +
  • + + +
  • +Added Shutdown to Run Menu. This is the same as Stop except that it lets the Threads finish normally (i.e. after the next sample has been completed) +
  • + + +
  • +Remote samples can be cached until the end of a test by defining the property hold_samples=true when running the server. +More work is needed to be able to control this from the GUI +
  • + + +
  • +Proxy server has option to skip recording browser headers +
  • + + +
  • +Proxy restart works better (stop waits for daemon to finish) +
  • + + +
  • +Scheduler ignores start if it has already passed +
  • + + +
  • +Scheduler now has delay function +
  • + + +
  • +added Summariser test element (mainly for non-GUI) testing. This prints summary statistics to System.out and/or the log file every so oftem (3 minutes by default). Multiple summarisers can be used; samples are accumulated by summariser name. +
  • + + +
  • +Extra Proxy Server options: +Create all samplers with keep-alive disabled +Add Separator markers between sets of samples +Add Response Assertion to first sampler in each set +
  • + + +
  • +Test Plan has a comment field +
  • + + + +
  • +Help Page can now be pushed to background +
  • + + +
  • +Separate Function help page +
  • + + +
  • +New / amended functions +
  • + + +
      + + +
    • +__property() and __P() functions +
    • + + +
    • +__log() and __logn() - for writing to the log file +
    • + + +
    • +_StringFromFile can now process a sequence of files, e.g. dir/file01.txt, dir/file02.txt etc +
    • + + +
    • +_StringFromFile() funtion can now use a variable or function for the file name +
    • + + +
    + + +
  • +New / amended Assertions +
  • + + +
      + + +
    • +Response Assertion now works for URLs, and it handles null data better +
    • + + +
    • +Response Assertion can now match on Response Code and Response message as well +
    • + + +
    • +HTML Assertion using JTidy to check for well-formed HTML +
    • + + +
    + + +
  • +If Controller (not fully functional yet) +
  • + + +
  • +Transaction Controller (aggregates the times of its children) +
  • + + +
  • +New Samplers +
  • + + +
      + + +
    • +Basic BSF Sampler (optional) +
    • + + +
    • +BeanShell Sampler (optional, needs to be downloaded from www.beanshell.org +
    • + + +
    • +Basic TCP Sampler +
    • + + +
    + + +
  • +Optionally start BeanShell server (allows remote access to JMeter variables and methods) +
  • + + +
+

+Version 1.9.1 +

+

+TBA +

+

+Version 1.9 +

+
    + + +
  • +Sample result log files can now be in CSV or XML format +
  • + + +
  • +New Event model for notification of iteration events during test plan run +
  • + + +
  • +New Javascript function for executing arbitrary javascript statements +
  • + + +
  • +Many GUI improvements +
  • + + +
  • +New Pre-processors and Post-processors replace Modifiers and Response-Based Modifiers. +
  • + + +
  • +Compatible with jdk1.3 +
  • + + +
  • +JMeter functions are now fully recursive and universal (can use functions as parameters to functions) +
  • + + +
  • +Integrated help window now supports hypertext links +
  • + + +
  • +New Random Function +
  • + + +
  • +New XML Assertion +
  • + + +
  • +New LDAP Sampler (alpha code) +
  • + + +
  • +New Ant Task to run JMeter (in extras folder) +
  • + + +
  • +New Java Sampler test implementation (to assist developers) +
  • + + +
  • +More efficient use of memory, faster loading of .jmx files +
  • + + +
  • +New SOAP Sampler (alpha code) +
  • + + +
  • +New Median calculation in Graph Results visualizer +
  • + + +
  • +Default config element added for developer benefit +
  • + + +
  • +Various performance enhancements during test run +
  • + + +
  • +New Simple File recorder for minimal GUI overhead during test run +
  • + + +
  • +New Function: StringFromFile - grabs values from a file +
  • + + +
  • +New Function: CSVRead - grabs multiple values from a file +
  • + + +
  • +Functions now longer need to be encoded - special values should be escaped +with "\" if they are literal values +
  • + + +
  • +New cut/copy/paste functionality +
  • + + +
  • +SSL testing should work with less user-fudging, and in non-gui mode +
  • + + +
  • +Mailer Model works in non-gui mode +
  • + + +
  • +New Througput Controller +
  • + + +
  • +New Module Controller +
  • + + +
  • +Tests can now be scheduled to run from a certain time till a certain time +
  • + + +
  • +Remote JMeter servers can be started from a non-gui client. Also, in gui mode, all remote servers can be started with a single click +
  • + + +
  • +ThreadGroups can now be run either serially or in parallel (default) +
  • + + +
  • +New command line options to override properties +
  • + + +
  • +New Size Assertion +
  • + + + +
+

+Version 1.8.1 +

+
    + + +
  • +Bug Fix Release. Many bugs were fixed. +
  • + + +
  • +Removed redundant "Root" node from test tree. +
  • + + +
  • +Re-introduced Icons in test tree. +
  • + + +
  • +Some re-organization of code to improve build process. +
  • + + +
  • +View Results Tree has added option to view results as web document (still buggy at this point). +
  • + + +
  • +New Total line in Aggregate Listener (still buggy at this point). +
  • + + +
  • +Improvements to ability to change JMeter's Locale settings. +
  • + + +
  • +Improvements to SSL Manager. +
  • + + +
+

+Version 1.8 +

+
    + + +
  • +Improvement to Aggregate report's calculations. +
  • + + +
  • +Simplified application logging. +
  • + + +
  • +New Duration Assertion. +
  • + + +
  • +Fixed and improved Mailer Visualizer. +
  • + + +
  • +Improvements to HTTP Sampler's recovery of resources (sockets and file handles). +
  • + + +
  • +Improving JMeter's internal handling of test start/stop. +
  • + + +
  • +Fixing and adding options to behavior of Interleave and Random Controllers. +
  • + + +
  • +New Counter config element. +
  • + + +
  • +New User Parameters config element. +
  • + + +
  • +Improved performance of file opener. +
  • + + +
  • +Functions and other elements can access global variables. +
  • + + +
  • +Help system available within JMeter's GUI. +
  • + + +
  • +Test Elements can be disabled. +
  • + + +
  • +Language/Locale can be changed while running JMeter (mostly). +
  • + + +
  • +View Results Tree can be configured to record only errors. +
  • + + +
  • +Various bug fixes. +
  • + + +
+

+Version 1.7.3 +

+
    + + +
  • +New Functions that provide more ability to change requests dynamically during test runs. +
  • + + +
  • +New language translations in Japanese and German. +
  • + + +
  • +Removed annoying Log4J error messages. +
  • + + +
  • +Improved support for loading JMeter 1.7 version test plan files (.jmx files). +
  • + + +
  • +JMeter now supports proxy servers that require username/password authentication. +
  • + + +
  • +Dialog box indicating test stopping doesn't hang JMeter on problems with stopping test. +
  • + + +
  • +GUI can run multiple remote JMeter servers (fixes GUI bug that prevented this). +
  • + + +
  • +Dialog box to help created function calls in GUI. +
  • + + +
  • +New Keep-alive switch in HTTP Requests to indicate JMeter should or should not use Keep-Alive for sockets. +
  • + + +
  • +HTTP Post requests can have GET style arguments in Path field. Proxy records them correctly now. +
  • + + +
  • +New User-defined test-wide static variables. +
  • + + +
  • +View Results Tree now displays more information, including name of request (matching the name +in the test tree) and full request and POST data. +
  • + + +
  • +Removed obsolete View Results Visualizer (use View Results Tree instead). +
  • + + +
  • +Performance enhancements. +
  • + + +
  • +Memory use enhancements. +
  • + + +
  • +Graph visualizer GUI improvements. +
  • + + +
  • +Updates and fixes to Mailer Visualizer. +
  • + + +
+

+Version 1.7.2 +

+
    + + +
  • +JMeter now notifies user when test has stopped running. +
  • + + +
  • +HTTP Proxy server records HTTP Requests with re-direct turned off. +
  • + + +
  • +HTTP Requests can be instructed to either follow redirects or ignore them. +
  • + + +
  • +Various GUI improvements. +
  • + + +
  • +New Random Controller. +
  • + + +
  • +New SOAP/XML-RPC Sampler. +
  • + + +
+

+Version 1.7.1 +

+
    + + +
  • +JMeter's architecture revamped for a more complete separation between GUI code and +test engine code. +
  • + + +
  • +Use of Avalon code to save test plans to XML as Configuration Objects +
  • + + +
  • +All listeners can save data to file and load same data at later date. +
  • + + +
+

+Version 1.7Beta +

+
    + + +
  • +Better XML support for special characters (Tushar Bhatia) +
  • + + +
  • +Non-GUI functioning & Non-GUI test plan execution (Tushar Bhatia) +
  • + + +
  • +Removing Swing dependence from base JMeter classes +
  • + + +
  • +Internationalization (Takashi Okamoto) +
  • + + +
  • +AllTests bug fix (neth6@atozasia.com) +
  • + + +
  • +ClassFinder bug fix (neth6@atozasia.com) +
  • + + +
  • +New Loop Controller +
  • + + +
  • +Proxy Server records HTTP samples from browser + (and documented in the user manual) +
  • + +
  • +Multipart Form support +
  • + + +
  • +HTTP Header class for Header customization +
  • + + +
  • +Extracting HTTP Header information from responses (Jamie Davidson) +
  • + + +
  • +Mailer Visualizer re-added to JMeter +
  • + + +
  • +JMeter now url encodes parameter names and values +
  • + + +
  • +listeners no longer give exceptions if their gui's haven't been initialized +
  • + + +
  • +HTTPS and Authorization working together +
  • + + +
  • +New Http sampling that automatically parses HTML response + for images to download, and includes the downloading of these + images in total time for request (Neth neth6@atozasia.com) +
  • + + +
  • +HTTP responses from server can be parsed for links and forms, + and dynamic data can be extracted and added to test samples + at run-time (documented) +
  • + + +
  • +New Ramp-up feature (Jonathan O'Keefe) +
  • + + +
  • +New visualizers (Neth) +
  • + + +
  • +New Assertions for functional testing +
  • + + +
+

+Version 1.6.1 +

+
    + + +
  • +Fixed saving and loading of test scripts (no more extra lines) +
  • + + +
  • +Can save and load special characters (such as "&" and "<"). +
  • + + +
  • +Can save and load timers and listeners. +
  • + + +
  • +Minor bug fix for cookies (if you cookie value + contained an "=", then it broke). +
  • + + +
  • +URL's can sample ports other than 80, and can test HTTPS, + provided you have the necessary jars (JSSE) +
  • + + +
+

+Version 1.6 Alpha +

+
    + + +
  • +New UI +
  • + + +
  • +Separation of GUI and Logic code +
  • + + +
  • +New Plug-in framework for new modules +
  • + + +
  • +Enhanced performance +
  • + + +
  • +Layering of test logic for greater flexibility +
  • + + +
  • +Added support for saving of test elements +
  • + + +
  • +Added support for distributed testing using a single client +
  • + + + +
+

+Version 1.5.1 +

+
    + + +
  • +Fixed bug that caused cookies not to be read if header name case not as expected. +
  • + + +
  • +Clone entries before sending to sampler - prevents relocations from messing up + information across threads +
  • + + +
  • +Minor bug fix to convenience dialog for adding paramters to test sample. + Bug prevented entries in dialog from appearing in test sample. +
  • + + +
  • +Added xerces.jar to distribution +
  • + + +
  • +Added junit.jar to distribution and created a few tests. +
  • + + +
  • +Started work on new framework. New files in cvs, but do not effect program yet. +
  • + + +
  • +Fixed bug that prevent HTTPJMeterThread from delaying according to chosen timer. +
  • + + +
+

+ + +

+Version 1.5 +

+ + +
    + + +
  • +Abstracted out the concept of the Sampler, SamplerController, and TestSample. + A Sampler represents code that understands a protocol (such as HTTP, + or FTP, RMI, SMTP, etc..). It is the code that actually makes the + connection to whatever is being tested. A SamplerController + represents code that understands how to organize and run a group + of test samples. It is what binds together a Sampler and its test + samples and runs them. A TestSample represents code that understands + how to gather information from the user about a particular test. + For a website, it would represent a URL and any information to be sent + with the URL. +
  • + + +
  • +The UI has been updated to make entering test samples more convenient. +
  • + + +
  • +Thread groups have been added, allowing a user to setup multiple test to run + concurrently, and to allow sharing of test samples between those tests. +
  • + + +
  • +It is now possible to save and load test samples. +
  • + + +
  • +....and many more minor changes/improvements... +
  • + + +
+ + +

+

+ + + +Apache JMeter 1.4.1-dev + + + +

    + + +
  • +Cleaned up URLSampler code after tons of patches for better readability. (SM) +
  • + + +
  • +Made JMeter send a special "user-agent" identifier. (SM) +
  • + + +
  • +Fixed problems with redirection not sending cookies and authentication info and removed + a warning with jikes compilation. Thanks to Wesley Tanaka for the patches (SM) +
  • + + +
  • +Fixed a bug in the URLSampler that caused to skip one URL when testing lists of URLs and + a problem with Cookie handling. Thanks to Graham Johnson for the patches (SM) +
  • + + +
  • +Fixed a problem with POST actions. Thanks to Stephen Schaub for the patch (SM) +
  • + + +
+ + +

+

+ + + +Apache JMeter 1.4 + + - Jul 11 1999 + +

    + + +
  • +Fixed a problem with POST actions. Thanks to Brendan Burns for the patch (SM) +
  • + + +
  • +Added close button to the About box for those window managers who don't provide it. + Thanks to Jan-Henrik Haukeland for pointing it out. (SM) +
  • + + +
  • +Added the simple Spline sample visualizer (JPN) +
  • + + +
+ +

+

+ +Apache JMeter 1.3 + + - Apr 16 1999 + +

    + + +
  • +Run the Garbage Collector and run finalization before starting to sampling to ensure + same state every time (SM) +
  • + + +
  • +Fixed some NullPointerExceptions here and there (SM) +
  • + + +
  • +Added HTTP authentication capabilities (RL) +
  • + + +
  • +Added windowed sample visualizer (SM) +
  • + + +
  • +Fixed stupid bug for command line arguments. Thanks to Jorge Bracer for pointing this out (SM) +
  • + + +
+ +

+

+ +Apache JMeter 1.2 + + - Mar 17 1999 + +

    + + +
  • +Integrated cookie capabilities with JMeter (SM) +
  • + + +
  • +Added the Cookie manager and Netscape file parser (SD) +
  • + + +
  • +Fixed compilation error for JDK 1.1 (SD) +
  • + +
+ +

+

+ + +Apache JMeter 1.1 + + - Feb 24 1999 + +

    + + +
  • +Created the opportunity to create URL aliasing from the properties file as well as the + ability to associate aliases to URL sequences instead of single URLs (SM) Thanks to + Simon Chatfield for the very nice suggestions + and code examples. +
  • + + +
  • +Removed the TextVisualizer and replaced it with the much more useful FileVisualizer (SM) +
  • + + +
  • +Added the known bug list (SM) +
  • + + +
  • +Removed the Java Apache logo (SM) +
  • + + +
  • +Fixed a couple of typos (SM) +
  • + + +
  • +Added UNIX makefile (SD) +
  • + +
+ +

+

+ + +Apache JMeter 1.0.1 + + - Jan 25 1999 + +

    + + +
  • +Removed pending issues doc issues (SM) +
  • + + +
  • +Fixed the unix script (SM) +
  • + + +
  • +Added the possibility of running the JAR directly using "java -jar + ApacheJMeter.jar" with Java 2 (SM) +
  • + + +
  • +Some small updates: fixed Swing location after Java 2(tm) release, license update and + small cleanups (SM) +
  • + + +
+ +

+

+ + +Apache JMeter 1.0 + + - Dec 15 1998 + +

    + + +
  • +Initial version. (SM) +
  • + + +
+ +

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/css/style.css b/ApacheJmeter/docs/css/style.css new file mode 100644 index 0000000..34dd386 --- /dev/null +++ b/ApacheJmeter/docs/css/style.css @@ -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. +*/ + +/*Shows the value of the name attribute when hovered*/ +/* Disabled +a[name]:hover:after{ + content: " #" attr(name); + font-size: 90%; + text-decoration: none; +} +*/ + +/* + * Hide class="sectionlink", except when an enclosing heading + * has the :hover property. + * Used to hide the ¶ marker for generating internal links + */ +.sectionlink { + display: none; +} +:hover > .sectionlink { + display: inline; + /* Green so shows up on section headings too */ + color: rgb(0,255,0); +} diff --git a/ApacheJmeter/docs/demos/AssertionTestPlan.jmx b/ApacheJmeter/docs/demos/AssertionTestPlan.jmx new file mode 100644 index 0000000..0cd3e3b --- /dev/null +++ b/ApacheJmeter/docs/demos/AssertionTestPlan.jmx @@ -0,0 +1,128 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836421000 + 1211836421000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + GET + true + false + false + false + + + + false + + + + + + </html> + + 2 + Assertion.response_data + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + assertion.dat + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/docs/demos/AuthManagerTestPlan.jmx b/ApacheJmeter/docs/demos/AuthManagerTestPlan.jmx new file mode 100644 index 0000000..4b792dc --- /dev/null +++ b/ApacheJmeter/docs/demos/AuthManagerTestPlan.jmx @@ -0,0 +1,151 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836473000 + 1211836473000 + false + continue + + + + + + + + http://localhost/secret + kevin + spot + + + + + + + + + + + localhost + + http + + / + + + + + + + + + http + + /secret/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /secret/index2.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /index.html + GET + true + false + false + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + auth-manager.dat + + + + + + diff --git a/ApacheJmeter/docs/demos/BeanShellAssertion.bsh b/ApacheJmeter/docs/demos/BeanShellAssertion.bsh new file mode 100644 index 0000000..062fb81 --- /dev/null +++ b/ApacheJmeter/docs/demos/BeanShellAssertion.bsh @@ -0,0 +1,53 @@ +/* + * 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. + * + */ + +// Sample BeanShell Assertion script +// Derived from http://www.mail-archive.com/jmeter-user@jakarta.apache.org/msg05597.html + +if (ResponseCode != null && ResponseCode.equals ("200") == false ) +{ + // this is standard stuff + Failure=true ; + FailureMessage ="Response code was not a 200 response code it was " + ResponseCode + "." ; + print ( "the return code is " + ResponseCode); // this goes to stdout + log.warn( "the return code is " + ResponseCode); // this goes to the JMeter log file +} else { + try + { + // non standard stuff where BeanShell assertion will be really powerful . + // in my example I just test the size , but you could extend it further + // to actually test the content against another file. + byte [] arr = (byte[]) ResponseData ; + // print ( arr.length ) ; // use this to determine the size + if (arr != null && arr.length != 25218) + { + Failure= true ; + FailureMessage = "The response data size was not as expected" ; + } + else if ( arr == null ) + { + Failure= true ; + FailureMessage = "The response data size was null" ; + } + } + catch ( Throwable t ) + { + print ( t ) ; + log.warn("Error: ",t); + } +} \ No newline at end of file diff --git a/ApacheJmeter/docs/demos/ForEachTest2.jmx b/ApacheJmeter/docs/demos/ForEachTest2.jmx new file mode 100644 index 0000000..b19c91f --- /dev/null +++ b/ApacheJmeter/docs/demos/ForEachTest2.jmx @@ -0,0 +1,322 @@ + + + + + false + + + + + false + + + + + 1 + false + continue + 1076438592000 + + false + 2 + + 1 + + + 1076438592000 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Sample 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + a b c d + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + (\w)\s + inputVar + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + For 1 ${returnVar} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + ${returnVar1} + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Sample 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + a b c d + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + (\w)\sx + inputVar + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + For 2 ${returnVar} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + ${returnVar} + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/docs/demos/HeaderManagerTestPlan.jmx b/ApacheJmeter/docs/demos/HeaderManagerTestPlan.jmx new file mode 100644 index 0000000..71621ee --- /dev/null +++ b/ApacheJmeter/docs/demos/HeaderManagerTestPlan.jmx @@ -0,0 +1,95 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836504000 + 1211836504000 + false + continue + + + + + + + + User-Agent + Mozilla/4.0 (compatible; MSIE 5.5; Windows 98) + + + + + + + + + jakarta.apache.org + + http + + / + GET + true + false + false + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + header-manager.dat + + + + + + diff --git a/ApacheJmeter/docs/demos/InterleaveTestPlan.jmx b/ApacheJmeter/docs/demos/InterleaveTestPlan.jmx new file mode 100644 index 0000000..1429f0e --- /dev/null +++ b/ApacheJmeter/docs/demos/InterleaveTestPlan.jmx @@ -0,0 +1,160 @@ + + + + + + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 5 + + 0 + 2 + false + 0 + continue + + + + + + 0 + + + + + + + ${server} + + http + + /site/news/index.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + + + + + + + GET + false + true + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/docs/demos/InterleaveTestPlan2.jmx b/ApacheJmeter/docs/demos/InterleaveTestPlan2.jmx new file mode 100644 index 0000000..1eaabee --- /dev/null +++ b/ApacheJmeter/docs/demos/InterleaveTestPlan2.jmx @@ -0,0 +1,234 @@ + + + + + + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 8 + + 0 + 1 + false + 0 + continue + + + + + + 1 + + + + + + + + + + + + + + + 1 + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + 1 + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + + + ${server} + + + + + GET + true + false + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/docs/demos/JDBC-Pre-Post-Processor.jmx b/ApacheJmeter/docs/demos/JDBC-Pre-Post-Processor.jmx new file mode 100644 index 0000000..5cdcb40 --- /dev/null +++ b/ApacheJmeter/docs/demos/JDBC-Pre-Post-Processor.jmx @@ -0,0 +1,445 @@ + + + + + Execute a series of concurrent valuations + false + true + + + + CalculateFees + 1 + = + + + CalculatePerformanceDetails + 1 + = + + + DriverURL + jdbc:jtds:sqlserver: + = + + + DatabasePort + 1433 + = + + + UseMiddleTierValuationEngine + 0 + = + + + MiddleTierRequestTimeout + 500000 + = + + + PricePropagationMode + 2 + = + + + + + + + + + + PCOQuality + 5 + = + + + ValueDate + 2011-07-21 + = + + + ReportingDate + 2011-07-21 12:30:08.337 + = + + + + + + + + Database + HSPAD_MI_440_SSD + = + + + DatabaseHost + GAIA + = + + + DatabaseUser + sa + = + + + DatabasePassword + sa2008 + = + + + + + + + + Pfo_1 + 1548 + = + + + Pfo_2 + 1611 + = + + + Pfo_3 + 1613 + = + + + CutOff Nr 11249, 2011-07-2 / 2011-07-21 12:30:08.337 / DailyNAV Estimate / Within Price Cut-Off + + + + false + + 5000 + + ${DriverURL}//${DatabaseHost}:${DatabasePort}/${Database} + net.sourceforge.jtds.jdbc.Driver + true + ${DatabasePassword} + 25 + 10000 + 60000 + ${DatabaseUser} + 4096 + Connect to local HSPAD_Demo_CO and set its isolation mode to SNAPSHOT (4096) and disable auto commit. + + + + continue + + false + 3 + + 3 + 0 + 1316530469000 + 1316530469000 + false + + + + + + + WorkBench + Concurrent Valuation Test Plan + PCO Valuation + + + + + + continue + + false + 1 + + 1 + 1 + 1320821253000 + 1320821253000 + false + + + + + + 1 + 0 + + + + + + UPDATE T_SettingGlobal SET UseMiddleTierValuationEngine=?, MiddleTierRequestTimeout=? + ${UseMiddleTierValuationEngine}, ${MiddleTierRequestTimeout} + BIT, INTEGER + Prepared Update Statement + + + + + + + Commit + + + + + + + + + + 1 + 0 + 0 + + + + + Update Statement + DBCC DROPCLEANBUFFERS + + + + + + + + + Update Statement + DBCC FREEPROCCACHE + + + + + + + + + + + + true + + + + + Update Statement + BEGIN TRAN COMMIT TRAN + + + + + false + + + + + Callable Statement + PfoVal_Recalculate ?, ?, 1 + ${Pfo_1}, ${PfoValInstance} + INTEGER, INTEGER + + + true + + + + groovy + + + import groovy.sql.Sql +import org.apache.jmeter.protocol.jdbc.config.DataSourceElement +try { + // build Pfo List + println("Building Portfolio List") + def pfoList = "<PfoList>" + def pfoNr = 1 + def pfo = vars.get("Pfo_" + pfoNr) + while(pfo != null) { + println("Pfo: $pfo"); + pfoList = pfoList + "<Pfo ID='$pfo' EmptyValuation='true' PropagatePrice='true'/>" + pfoNr++ + pfo = vars.get("Pfo_" + pfoNr) + } + pfoList = pfoList + "</PfoList>" + vars.put("PfoListXML", pfoList) +} catch (Exception e) { + println(e.toString()); +} + + + + + CreatePriceCutOff ?, ?, ?, ?, ?, ?, ?, ? + ${__threadNum},${ValueDate},${PCOQuality},${ReportingDate},]NULL[,${PCO},${PfoListXML},${PricePropagationMode} + VARCHAR, DATE, INTEGER,TIMESTAMP,INTEGER,OUT INTEGER,CLOB,INTEGER + Callable Statement + + + + + + + Prepared Select Statement + select Nr from PfoValInstance where Pfo=? AND PriceCutOff=? + ${Pfo_1},${PCO} + INTEGER,INTEGER + PfoValInstance + + + + + + DeletePriceCutOff ? + ${PCO} + INTEGER + Callable Statement + + + + + + + ${JMeterThread.last_sample_ok} + false + + + + + Commit + + + + + + Commit the transaction of the valuation + false + + + + + ${JMeterThread.last_sample_ok}==false + false + + + + + Rollback + + + + + + false + + + + + + + false + + saveConfig + + + false + true + false + + false + false + true + false + false + false + false + false + false + true + false + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + diff --git a/ApacheJmeter/docs/demos/JMSPointToPoint.jmx b/ApacheJmeter/docs/demos/JMSPointToPoint.jmx new file mode 100644 index 0000000..21e49bc --- /dev/null +++ b/ApacheJmeter/docs/demos/JMSPointToPoint.jmx @@ -0,0 +1,103 @@ + + + + + + + + false + false + + + + + + 1115386407000 + + + 5 + false + + false + 4 + + 1115386407000 + continue + 5 + + + + + + + + + + = + tcp://localhost:61616 + brokerURL + + + = + example.MyQueue + queue.MyQueue + + + = + example.Q.REQ + queue.Q.REQ + + + = + example.Q.RPL + queue.Q.RPL + + + + false + Q.RPL + 5000 + Q.REQ + + ConnectionFactory + org.activemq.jndi.ActiveMQInitialContextFactory + <msg>test</msg> + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + + + + + + + + + diff --git a/ApacheJmeter/docs/demos/LoopTestPlan.jmx b/ApacheJmeter/docs/demos/LoopTestPlan.jmx new file mode 100644 index 0000000..5ec16e6 --- /dev/null +++ b/ApacheJmeter/docs/demos/LoopTestPlan.jmx @@ -0,0 +1,124 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836533000 + 1211836533000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + + + + + + + + + http + + / + GET + true + false + false + false + + + + false + + + + + true + 5 + + + + + + + + + http + + /site/news.html + GET + true + false + false + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + loop-test.dat + + + + + + diff --git a/ApacheJmeter/docs/demos/OnceOnlyTestPlan.jmx b/ApacheJmeter/docs/demos/OnceOnlyTestPlan.jmx new file mode 100644 index 0000000..1715a67 --- /dev/null +++ b/ApacheJmeter/docs/demos/OnceOnlyTestPlan.jmx @@ -0,0 +1,132 @@ + + + + + + + + = + jakarta.apache.org + server + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 3 + + 0 + 2 + false + 0 + continue + + + + + + + + + + + + + + + + + + + + + + ${server} + + + + + GET + true + false + true + false + + + + false + + + + + + + + + + + + + + GET + true + false + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/docs/demos/ProxyServerTestPlan.jmx b/ApacheJmeter/docs/demos/ProxyServerTestPlan.jmx new file mode 100644 index 0000000..7fe6511 --- /dev/null +++ b/ApacheJmeter/docs/demos/ProxyServerTestPlan.jmx @@ -0,0 +1,27 @@ + + + + + + + 8080 + + + true + 0 + false + 0 + false + true + true + false + false + false + + + + + + + + diff --git a/ApacheJmeter/docs/demos/SimpleTestPlan.jmx b/ApacheJmeter/docs/demos/SimpleTestPlan.jmx new file mode 100644 index 0000000..a10f97e --- /dev/null +++ b/ApacheJmeter/docs/demos/SimpleTestPlan.jmx @@ -0,0 +1,166 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836583000 + 1211836583000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + + + + + + + + + + + http + + /ant/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /ant/antnews.html + GET + true + false + false + false + + + + false + + + + + + + + + + + + + http + + /log4j/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /log4j/docs/history.html + GET + true + false + false + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + simple-test.dat + + + + + + diff --git a/ApacheJmeter/docs/demos/URLRewritingExample.jmx b/ApacheJmeter/docs/demos/URLRewritingExample.jmx new file mode 100644 index 0000000..be60b1e --- /dev/null +++ b/ApacheJmeter/docs/demos/URLRewritingExample.jmx @@ -0,0 +1,142 @@ + + + + + + + + + false + false + + + + + 1200525828000 + + + 1 + false + + false + -1 + + 1200525828000 + continue + 0 + + + + + + + my.server.com + + + + / + GET + true + false + true + false + + + + false + + + + + + + false + false + SESSION_ID + false + true + + + + + + + = + user + true + username + true + + + = + password + true + password + true + + + + my.server.com + 80 + http + + /main.jsp + POST + true + false + true + false + + + + false + + + + + + + + my.server.com + + http + + /something_interesting.jsp + GET + true + false + true + false + + + + false + + + + + + + + my.server.com + + http + + /another.jsp + POST + true + false + true + false + + + + false + + + + + + + + diff --git a/ApacheJmeter/docs/demos/forEachTestPlan.jmx b/ApacheJmeter/docs/demos/forEachTestPlan.jmx new file mode 100644 index 0000000..4dacd01 --- /dev/null +++ b/ApacheJmeter/docs/demos/forEachTestPlan.jmx @@ -0,0 +1,123 @@ + + + + + false + + + + + false + + + + + 1 + false + continue + 1076438592000 + + false + 1 + + 1 + + + 1076438592000 + + + + + + + localhost + 80 + + + / + GET + true + false + true + false + + + + false + + + + + inputVar + <a href="([^"]+)" + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + localhost + 80 + + + ${returnVar} + GET + true + false + true + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/docs/download_jmeter.cgi b/ApacheJmeter/docs/download_jmeter.cgi new file mode 100644 index 0000000..5b64bb5 --- /dev/null +++ b/ApacheJmeter/docs/download_jmeter.cgi @@ -0,0 +1,7 @@ +#!/bin/sh +# Wrapper script around mirrors.cgi script +# (we must change to that directory in order for python to pick up the +# python includes correctly) +cd /www/www.apache.org/dyn/mirrors +/www/www.apache.org/dyn/mirrors/mirrors.cgi $* + \ No newline at end of file diff --git a/ApacheJmeter/docs/download_jmeter.html b/ApacheJmeter/docs/download_jmeter.html new file mode 100644 index 0000000..55f1774 --- /dev/null +++ b/ApacheJmeter/docs/download_jmeter.html @@ -0,0 +1,540 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - Downloads + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +Download Apache JMeter +
+
+

+ + We recommend you use a mirror to download our release + builds, but you + +must + + verify the integrity of + the downloaded files using signatures downloaded from our main + distribution directories. Recent releases (48 hours) may not yet + be available from the mirrors. + +

+

+ + You are currently using + +[preferred] + +. If you + encounter a problem with this mirror, please select another + mirror. If all mirrors are failing, there are + +backup + + + mirrors (at the end of the mirrors list) that should be + available. + +
+ + + [if-any logo] + + + + +[end] + +

+
+ + +

+ + Other mirrors: + + + + + + + + +

+ + +
+

+ + The + +KEYS + + link links to the code signing keys used to sign the product. + The + +PGP + + link downloads the OpenPGP compatible signature from our main site. + The + +MD5 + + link downloads the checksum from the main site. + +

+

+ + For more information concerning Apache JMeter, see the + +Apache JMeter + + site. + +

+

+ + + +KEYS + + + +

+
+

+

+ + + + +
+ +Apache JMeter 2.7 (Requires Java 1.5 or later) +
+
+ + + + +
+ +Binaries + +
+
+ + + + + + + + + + + +
+ +apache-jmeter-2.7.tgz + + + +md5 + + + +pgp + +
+ +apache-jmeter-2.7.zip + + + +md5 + + + +pgp + +
+
+

+ + + + +
+ +Source + +
+
+ + + + + + + + + + + +
+ +apache-jmeter-2.7_src.tgz + + + +md5 + + + +pgp + +
+ +apache-jmeter-2.7_src.zip + + + +md5 + + + +pgp + +
+
+

+
+

+

+ + + + +
+ +Archives +
+
+

+ + Older releases can be obtained from the archives. + +

+ +
+

+

+ + + + +
+ +Verification of downloads +
+
+

+ + It is essential that you verify the integrity of the downloaded files using the PGP or MD5 signatures. + Please read Verifying Apache Software Foundation Releases for more information on why you should verify our releases. + +

+

+ + The PGP signatures can be verified using PGP or GPG. First download the KEYS as well as the asc signature file for the relevant distribution. + Make sure you get these files from the main distribution site, rather than from a mirror. + Then verify the signatures using + +

+
+
+% pgpk -a KEYS
+% pgpv downloaded_file.asc
+or
+% pgp -ka KEYS
+% pgp downloaded_file.asc
+or
+% gpg --import KEYS
+% gpg --verify downloaded_file.asc
+
+
+

+ + Alternatively, you can verify the MD5 signature on the files. + This is not very secure, and should only be used to check that the file has been downloaded successfully. + +
+ + + A unix program called md5 or md5sum is included in many unix distributions. + It is also available as part of + +GNU Textutils + +. + +
+ + + Windows users can get binary md5 programs from + + +here + +, + + +here + +, or + + +here + +. + +

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/index.html b/ApacheJmeter/docs/index.html new file mode 100644 index 0000000..94cba6f --- /dev/null +++ b/ApacheJmeter/docs/index.html @@ -0,0 +1,517 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - Apache JMeter™ + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +Apache JMeter™ +
+
+

+ + The + +Apache JMeter™ + + desktop application is open source software, + a 100% pure Java application designed + to load test functional behavior and measure performance. It was + originally designed for testing Web Applications but has + since expanded to other test functions. + +

+

+What can I do with it? +

+

+ + Apache JMeter may be used to test performance both on static and dynamic + resources (files, Servlets, Perl scripts, Java Objects, Data Bases and + Queries, FTP Servers and more). It can be used to simulate a heavy +load on a server, network or object to test its strength or to analyze +overall performance under different load types. You can use it to make a +graphical analysis of performance or to test your server/script/object +behavior under heavy concurrent load. + +

+

+What does it do? +

+

+Apache JMeter features include: +

+
    + + +
  • +Can load and performance test many different server types: + +
      + + +
    • +Web - HTTP, HTTPS +
    • + + +
    • +SOAP +
    • + + +
    • +Database via JDBC +
    • + + +
    • +LDAP +
    • + + +
    • +JMS +
    • + + +
    • +Mail - SMTP(S), POP3(S) and IMAP(S) +
    • + + +
    • +Native commands or shell scripts +
    • + + +
    + + +
  • + + +
  • +Complete portability and + +100% Java purity + +. +
  • + + +
  • +Full + +multithreading + + framework allows concurrent sampling by many threads and + simultaneous sampling of different functions by separate thread groups. +
  • + + +
  • +Careful + +GUI + + design allows faster operation and more precise timings. +
  • + + +
  • +Caching and offline analysis/replaying of test results. +
  • + + +
  • + +Highly Extensible: + + + +
      + + +
    • +Pluggable Samplers allow unlimited testing capabilities. +
    • + + +
    • +Several load statistics may be choosen with + +pluggable timers + +. +
    • + + +
    • +Data analysis and + +visualization plugins + + allow great extensibility + as well as personalization. +
    • + + +
    • +Functions can be used to provide dynamic input to a test or provide data manipulation. +
    • + + +
    • +Scriptable Samplers (BeanShell is fully supported; and there is a sampler which supports BSF-compatible languages) +
    • + + +
    + + +
  • + + +
+

+JMeter is not a browser +

+

+ +JMeter is not a browser. +As far as web-services and remote services are concerned, JMeter looks like a browser (or rather, multiple browsers); +however JMeter does not perform all the actions supported by browsers. +In particular, JMeter does not execute the Javascript found in HTML pages. +Nor does it render the HTML pages as a browser does +(it's possible to view the response as HTML etc, but the timings are not included in any samples, and only one sample in one thread is ever viewed at a time). + +

+

+How do I do it? +

+ +

+Tutorials (PDF) +

+ +

+Further Information About JMeter +

+ +
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/issues.html b/ApacheJmeter/docs/issues.html new file mode 100644 index 0000000..2cd3f5a --- /dev/null +++ b/ApacheJmeter/docs/issues.html @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - Issues + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +Issue tracker +
+
+

+ +JMeter uses Bugzilla for issue tracking, i.e. for reporting bugs and requesting enhancements. + +

+

+ +Before creating a new issue, please check whether the issue has already been reported by searching Bugzilla. +It's also worth checking first on the JMeter user mailing list; others may already have a solution. + +

+
+

+

+ + + + +
+ +Known Bugs +
+
+ +
+

+

+ + + + +
+ +Raising an Issue +
+
+

+ +First check that the issue has not already been reported. +If reporting a bug, are you sure it really is a bug in JMeter, not just a misunderstanding of how JMeter works? + +

+

+ +If you have not already done so, you need to register an account first, using the "New Account" link at the top of the +main Bugzilla page: + +https://issues.apache.org/bugzilla/ + +. + +

+

+ +Make sure you read and understand the information on the account creation page before signing up. + +

+

+ +Once logged in, click "File a bug" and select JMeter from the list. + +

+
+

+

+ + + + +
+ +Required Information +
+
+

+ +Please make sure you provide sufficient information for others to be able to make use of the report effectively. +Use the checklist below to guide you. + +

+
    + + +
  • +JMeter version +
  • + + +
  • +Java version (output from java -version) +
  • + + +
  • +OS version +
  • + + +
  • +jmeter.log file (unlikely to contain sensitive information, but check before uploading) +
  • + + +
  • +JMX file if relevant (redact any sensitive information first), providing a simplified Test Plan (using DEBUG sampler) will ensure BUG is fixed much more rapidely than without it +
  • + + +
  • +JTL file if relevant (may need to redact sensitive information) +
  • + + +
  • +For a suspected bug, describe what you did, what happened, and how this differs from what you expected to happen. +Does it happen every time?. + +
  • + + +
  • +Add yourself in CC List to be notified when JMeter Team requires more information (in this case bug will be marked as NEEDINFO) +
  • + + +
  • +Select accurately the IMPORTANCE level, ENHANCEMENT means it's not a BUG while others mean it's a BUG +
  • + + +
  • +If you are providing a patch to fix a bug, please ensure it is in unified diff format. +If using Eclipse, please set the patch root to "Project", not the default "Workspace" which is harder to apply. +
  • + + +
  • +New source files can be provided as is; please ensure they have the standard Apache License header (as per other JMeter files). +Please do not use @author tags (credit will be given in the changes file). + +
  • + + +
  • +In the case of patches for new features, please also provide documentation patches if at all possible. +Components are documented in xdocs/usermanual/component_reference.xml. +
  • + + +
+

+ +See also the following + +Bug writing guidelines + +, +also the terms and conditions noted on the + +Bugzilla account creation page. + + +

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/jmeter_irc.html b/ApacheJmeter/docs/jmeter_irc.html new file mode 100644 index 0000000..9a85a1c --- /dev/null +++ b/ApacheJmeter/docs/jmeter_irc.html @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - JMeter on IRC + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +JMeter on IRC +
+
+

+JMeter developers often hang out on IRC to chat about development issues. +Users are also welcome to stop by and ask questions, offer feature suggestions, +or just chit-chat. +

+

+IRC Server: + +irc.us.freenode.net + +
+ + +Room: + +#jmeter + +

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/localising/index.html b/ApacheJmeter/docs/localising/index.html new file mode 100644 index 0000000..9b7f8eb --- /dev/null +++ b/ApacheJmeter/docs/localising/index.html @@ -0,0 +1,516 @@ + + + + + + + + + + + + + + + + + + + +Apache JMeter - JMeter Localisation (Translator's Guide) + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +Introduction +
+
+

+This document describes the process of creating and maintaining translated texts for JMeter in languages +other than English. English has been tacitly chosen as the project's primary (or "default") language -- despite its +obvious inadequacy for reasonably unambiguous communication -- as a tribute to the Power of the Empire :-) +
+ + +The metropolitan language texts are thus maintained by the software developers, while other project contributors +(called "translators" in this document) take care of maintaining the texts in the languages of the +provinces. The process of producing and maintaining the later is called "translation" in this document. +

+

+This document assumes you'll be using i18nEdit as your tool to edit properties files, and instructions will +be specific to this software, but this is not mandatory: the process should mostly work also if you prefer to use +another tool, such as or + +vi + + or + +Emacs + +. + + +

+This document describes 6 processes: +

+ + +
    + + +
  1. +Obtaining the current texts [translators]. +
  2. + + +
  3. +Providing the current texts to translators [developers]. +
  4. + + +
  5. +Downloading and running i18nEdit [everyone]. +
  6. + + +
  7. +Translating [translators]. +
  8. + + +
  9. +Submitting your translations to the project [translators]. +
  10. + + +
  11. +Merging in new translations [committers]. +
  12. + + +
+ + +

+
+

+

+ + + + +
+ +Obtaining the current texts +
+
+

+If you want to help with JMeter's translation process, start by reading this document. Then +send a message to + +dev@jmeter.apache.org + + +stating your intention. The files you need (*.properties and *.metaprop) are included in the source archive. +But if you are having any difficulty, one of the project contributors will be able to grab the current texts +from SVN and send them to you. You'll receive a jar, zip, tar or tgz file that you'll need to unpack in your +local disk. +

+

+If you are familiar with SVN or you're brave, feel free to anonymously connect to the Apache SVN server +and obtain the JMeter source yourself, as described in + + +http://jmeter.apache.org/svnindex.html + + +-- the files necessary to the translation process are all under the jmeter/src directory. + +

+

+Once you've unpacked or checked out the files, make sure to find file src/i18nedit.properties in there: +you'll need to know where it is to start working with i18nEdit. +

+
+

+

+ + + + +
+ +Providing the current texts to translators +
+
+

+If you have access to JMeter's SVN repository and you want to pack the files necessary for localisation +for sending to a translator, just go to the directory above the project root and issue the following command: +
+ + + + + +tar czf jmeter-localisation.tgz `find jmeter/src -name "*.properties" -o -name "*.metaprops"` + + + +Of course you could also send the translator the whole jmeter directory, but this will make his life easier. + +

+
+

+

+ + + + +
+ +Downloading and running i18nEdit +
+
+

+The runtime for i18nEdit can be obtained from + + +http://www.cantamen.com/i18nedit.php + +. +Download the binary distribution (i18nedit-1.0.0.jar) and save it locally. +

+

+To run i18nEdit, just make sure to have a reasonably modern Java Runtime Environment in your PATH, change +to the directory where you saved i18nedit-1.0.0.jar, then issue the following command: +
+ + + + + +java -jar i18nedit-1.0.0.jar + + + + +

+

+Then: + +

    + + +
  1. +If you've never run i18nEdit before, choose a language. The rest of this document assumes you chose UK English. +
  2. + + +
  3. +Select the "Projects" menu, then "Open project...". +
  4. + + +
  5. +Navigate to jmeter/src/, select i18nedit.properties, and press the "Open" button. +
  6. + + +
  7. +In the window that opens, select the "Project" menu, then "Project settings". Check that your target language +appears in the list in field "Additional locales (ISO codes)". Otherwise, add it now. Press "Save". +
  8. + + +
+ +You're now ready to start translating. + +

+
+

+

+ + + + +
+ +Translating +
+
+

+Before you start translating, select the "Project" menu, then "Translation settings". Choose work mode +"Directed translation (source to target)". Enter "en" (without the quotes) in the "Source localization" field. Enter +the ISO code of your target language in the "Target localization field". +

+

+Click on one of the editable fields in the right panel ("Comment" or "Content" for your language). Press F2. +i18nEdit will bring you to the first property that requires your attention, either because a translation does not yet +exist for it or because the English text has changed since the translation was provided. Enter or fix the text if +necessary, then press F2 again to repeat the process. +

+

+i18nEdit's on-line help is excellent: read through it for more information and tips. +

+
+

+

+ + + + +
+ +Submitting your translations to the project +
+
+

+Once you're done translating, just pack up the whole set of files in jmeter/src in a jar, zip, tar, +tgz, or alike and attach them to a JMeter bug report +(follow link to "Known bugs" in + +JMeter's home page + + for that). +

+
+

+

+ + + + +
+ +Merging in new translations +
+
+

+If you're a committer receiving text files from a translator, follow this steps to merge them into +the project: + +

    + + +
  1. +Unpack the files submitted by the translator in a separate directory. +
  2. + + +
  3. +Start i18nEdit as described in + +Downloading and running i18nEdit + + above. +
  4. + + +
  5. +If the translator worked in a new language, make sure it is listed in the Additional locales field in the Project Settings. +
  6. + + +
  7. +Open the "Team" menu and select "Merge changes as integrator". +
  8. + + +
  9. +Enter the path to the src directory in the files submitted by the translator. +
  10. + + +
  11. +Select the translator's target language. +
  12. + + +
  13. +Press "Perform merge". +
  14. + + +
  15. +Close i18nEdit and commit to SVN as usual (remember to Refresh your project if you're using Eclipse). +
  16. + + +
+ + +

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/mail.html b/ApacheJmeter/docs/mail.html new file mode 100644 index 0000000..8d9130a --- /dev/null +++ b/ApacheJmeter/docs/mail.html @@ -0,0 +1,495 @@ + + + + + + + + + + + + + + + + + + + +Apache JMeter - Mailing Lists + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +Mailing Lists - Guidelines +
+
+

+A mailing list is an electronic discussion forum that anyone can +subscribe to. When someone sends an email message to the mailing list, +a copy of that message is broadcast to everyone who is subscribed to +that mailing list. Mailing lists provide a simple and effective +communication mechanism. With potentially thousands of subscribers, +there is a common set of etiquette guidelines that you should observe. +Please keep on reading. + +

+

+ +Please note that usage of these mailing lists is subject to the + + +Public Forum Archive Policy + +. + +

+

+ + + + + Respect the mailing list type + + +
+ + + There are generally two types of lists. + +

+

+ + +

    + + +
  • + + The "User" lists where you can send questions and comments about + configuration, setup, usage and other "user" types of questions. + +
  • + + +
  • + + The "Developer" lists where you can send questions and + comments about the actual software source code and general + "development" types of questions. + +
  • + + +
+ + +

+

+Some questions are appropriate for posting on both the "user" and +the "developer" lists. In this case, pick one and only one. Do not +cross post. +

+

+Asking a configuration question on the developers list is frowned +upon because developers' time is as precious as yours. By contacting +them directly instead of the user base you are abusing resources. In +fact, it is unlikely that you will get a quicker answer, if at +all. +

+

+ + + + + Join the lists that are appropriate for your discussion. + + +
+ + +Please make sure that you are joining the list that is appropriate for the +topic or product that you would like to discuss. For example, +please do not join the Regexp mailing list and ask questions about Tomcat. +Instead, you should join the Tomcat User list and ask your questions +there. + +

+

+ + + + + Ask smart questions. + +
+ + + +Every volunteer project obtains its strength from the people involved +in it. You are welcome to join any of our mailing lists. You can +choose to lurk, or actively participate; it's up to you. The level of +community responsiveness to specific questions is generally directly +proportional to the amount of effort you spend formulating your +question. Eric Raymond and Rick Moen have even written an essay entitled + +" + +Asking +Smart Questions + +" + + precisely on this topic. Although somewhat +militant, it is definitely worth reading. +
+ + + + +Note + +: Please do NOT send your Java problems to the two authors. They welcome feedback on the FAQ's contents, but are simply not a Java help resource. Follow the essay's advice and + +choose your forum + + carefully. + +

+

+ + + + + Keep your email short and to the point; use a suitable subject line. + + +
+ + +If your email is more than about a page of text, chances are that it +won't get read by very many people. It is much better to try to pack a +lot of informative information (see above about asking smart questions) +into as small of an email as possible. If you are replying to a previous +email, it is a good idea to only quote the parts that you are replying +to and to remove the unnecessary bits. This makes it easier for people +to follow a thread as well as making the email archives easier to search +and read. + +

+

+ + + + + Start a new thread for a new topic + + +
+ + +When asing a new question, please start a new thread with an appropriate new subject line. +This makes it easier to read, and to find later in the archives. + +

+

+ + + + + Do your best to ensure that you are not sending HTML or + "Stylelized" email to the list. + + +
+ + +If you are using Outlook or Outlook Express or Eudora, chances are that +you are sending HTML email by default. There is usually a setting that +will allow you to send "Plain Text" email. If you are using Microsoft +products to send email, there are several bugs in the software that +prevent you from turning off the sending of HTML email. + +

+

+ + + + +Please don't send attachments or include large chunks of code + +
+ + +Attachments can be difficult to read and are rarely needed by all recipients. +Some mailing lists are set up to drop them. +If you need to send more than a few lines of code, ask first. +Note that code is often mangled by word-wrapping, so it is better to provide a link to a downloadable file. +If necessary, arrange with the person(s) responding to the posting how best to give access to the data, +should it prove necessary. + +

+

+ + + + + Watch where you are sending email. + + +
+ + +The majority of our mailing lists have set the Reply-To to go back to the +list. That means that when you Reply to a message, it will go to the list +and not to the original author directly. The reason is because it helps +facilitate discussion on the list for everyone to benefit from. Be careful +of this as sometimes you may intend to reply to a message directly to someone +instead of the entire list. + + + +The appropriate contents of the Reply-To header is an age-old debate that +should not be brought up on the mailing lists. You can +examine opposing points of view + + +condemning + + +our convention and + + + +condoning + + +it. Bringing this up for debate on a mailing list will add nothing +new and is considered off-topic. + + + + +

+

+ + + + + Do not cross post messages. + + +
+ + +In other words, pick a mailing list and send your messages to that mailing +list only. Do not send your messages to multiple mailing lists. The reason is +that people may be subscribed to one list and not to the other. Therefore, +some people will only see part of the conversation. + +

+
+

+

+ + + + +
+ +Conclusion +
+
+

+ + + +Now that you have read the guidelines above + +, + + +here + + + is the page that gives +you a listing of the different mailing lists that you can join. If you +managed to find this without reading the above information, chances +are you will be sent back here. You might as well read it now and save +yourself the embarrassment. + +

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/mail2.html b/ApacheJmeter/docs/mail2.html new file mode 100644 index 0000000..94dd3e5 --- /dev/null +++ b/ApacheJmeter/docs/mail2.html @@ -0,0 +1,578 @@ + + + + + + + + + + + + + + + + + + + +Apache JMeter - Mailing Lists + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +Mailing Lists - Guidelines +
+
+

+ + + +Before subscribing to any of the mailing lists, please make sure that you have +read and understand the + +guidelines + + +. + +

+

+ +Please note that usage of these mailing lists is subject to the + + +Public Forum Archive Policy + +. + +

+

+ +For details of how to subscribe/unsubscribe please read + + +Subscribing and Unsubscribing + + + +

+
+

+

+ + + + +
+ +JMeter lists and archives at the ASF +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +List Name + + + +Description + + + +Subscribe + + + +Unsubscribe + + + +Archive + +
+ +Apache JMeter User + + + + +This is the list where users of Apache JMeter meet and discuss issues. +
+Developers are also expected to be lurking on this list to offer support to users of JMeter. +
+This list starts part-way through Nov 2011, when JMeter became an independent Apache project. +For earlier postings, please see the Jakarta JMeter User list, below. + +
+
+ +user-subscribe@jmeter.apache.org + + + +user-unsubscribe@jmeter.apache.org + + + +Archive + +
+ +Jakarta JMeter User + + + + +This is the old JMeter user list from when JMeter was a sub-project of Apache Jakarta. + + + + +- + + + +- + + + +Archive + +
+ +Apache JMeter Developer + + + + +This is the list where participating developers meet and discuss issues, code changes/additions etc. +
+Please do not send usage questions to this list, see user list above. +
+This list also collects Wiki update messages. +
+This list starts part-way through Nov 2011, when JMeter became an independent Apache project. +For earlier postings, please see below. + +
+
+ +dev-subscribe@jmeter.apache.org + + + +dev-unsubscribe@jmeter.apache.org + + + +Archive + +
+ +Apache JMeter Commits + + + +SVN commit messages are sent here. +
+Prior to Nov 2011, they were sent to the Jakarta Notifications list, see below. + +
+
+ +commits-subscribe@jmeter.apache.org + + + +commits-unsubscribe@jmeter.apache.org + + + +Archive + +
+ +Apache JMeter Issues + + + +Bugzilla messages are sent here. +
+Prior to Nov 2011, they were sent to the Jakarta Notifications list, see below. + +
+
+ +issues-subscribe@jmeter.apache.org + + + +issues-unsubscribe@jmeter.apache.org + + + +Archive + +
+ +Jakarta Developer + + + + +Combined Jakarta developer list, April 2010 to November 2011 + + + + +- + + + +- + + + +Archive + +
+ +Jakarta JMeter Developer + + + + +Historical list, up to April 2010. + + + + +- + + + +- + + + +Archive + +
+ +Jakarta Notifications + + + + +Combined Jakarta notifications to November 2011. +Includes Bugzilla, SVN and Wiki commit mails for JMeter. + + + + +- + + + +- + + + +Archive + +
+
+

+

+ + + + +
+ +Other Archives and Searching +
+
+

+ +There are several 3rd party sites that archive and provide searching for our mailing lists: + +

+

+ + +

+ + +

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/nightly.html b/ApacheJmeter/docs/nightly.html new file mode 100644 index 0000000..5c34d7c --- /dev/null +++ b/ApacheJmeter/docs/nightly.html @@ -0,0 +1,372 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - Nightly builds for developers + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +Nightly builds for developers +
+
+

+

+

+What are the nightly builds? +

+

+ + The nightly builds are interim builds that are + +untested and unsupported + +. + + +Use at your own risk! + + + +
+ + + These unreleased builds may not even load, may have undocumented features, + known defects, and any number of other issues. + +
+ + + They are intended for use by developers and others wishing to help with resolving JMeter bugs. + +

+

+ + + + +These builds should not be used in production. + + + + +

+

+Where are the nightly builds? +

+

+JMeter CI builds are currently run by Jenkins and Buildbot +

+

+These are located at: + +

+ + +

+

+What do they consist of? +

+

+ +JMeter is distributed as a set of zip (or tar-gz) archive files. + +The files are called: + +

    + + +
  • +apache-jmeter-{version}_bin.zip - JMeter binaries +
  • + + +
  • +apache-jmeter-{version}_lib.zip - 3rd party jar files (rarely changes) +
  • + + +
  • +apache-jmeter-{version}_src.zip - JMeter source +
  • + + +
  • +apache-jmeter-{version}_api.zip - JMeter Javadoc (if available) +
  • + + +
+ + +

+Installing JMeter runtime +

+ +Download the _bin and _lib files + +
+ + +Unpack the archives into the same directory structure + +
+ + +The other archives are not needed to run JMeter. + + +

+Building JMeter +

+ +Download the _src, _bin and _lib files + +
+ + +Unpack all the archives into the + +same directory structure + +. + +
+ + + +

+

+Warning - please note! +

+

+ + + + + + +The nightly builds may or may not work properly - or at all. + + + + + + +

+

+ + If there is a problem with a particular version, + it may be worth reporting this on the JMeter-dev mailing list and/or trying again in a day or two. + +

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/svnindex.html b/ApacheJmeter/docs/svnindex.html new file mode 100644 index 0000000..4cc6797 --- /dev/null +++ b/ApacheJmeter/docs/svnindex.html @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + +Apache JMeter - Source Repositories + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +Download the Source +
+
+

+Most users of the source code probably don't need to have day to +day access to the source code as it changes. For these users we +provide easy to unpack source code downloads via our + +download page + +. +

+
+

+

+ + + + +
+ +Access the Version Controlled Source Code +
+
+

+For information on connecting to the ASF Subversion repositories, see the + +version control +page + +. +

+

+Modules available for access are listed below. +

+ + + + +
+ +Subversion + +
+
+

+ +Subversion + + is an open-source version control system. The root url of the +ASF Subversion repository is + +http://svn.apache.org/repos/asf/ + + for non-committers and + +https://svn.apache.org/repos/asf/ + + for committers. +

+

+ +NOTE + +: +When checking out a subproject using Subversion, ensure that you are checking out a tag, a branch or trunk (the main-line) and not all tags and branches to avoid filling up your hard-disk and wasting bandwidth. +

+ + + + + + + + + + + + + +
+ +Project + + + +http (read-only) + + + +https (committers) + + + +View-SVN + +
+ +Apache JMeter + + + +http://svn.apache.org/repos/asf/jmeter/trunk + + + +https://svn.apache.org/repos/asf/jmeter/trunk + + + +http://svn.apache.org/viewcvs.cgi/jmeter/ + +
+
+

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/best-practices.html b/ApacheJmeter/docs/usermanual/best-practices.html new file mode 100644 index 0000000..7130103 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/best-practices.html @@ -0,0 +1,785 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Best Practices + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +16. Best Practices +
+
+
+

+

+ + + + +
+ +16.1 Limit the Number of Threads +
+
+

+Your hardware's capabilities will limit the number of threads you can effectively +run with JMeter. It will also depend on how fast your server is (a faster server +gives makes JMeter work harder since it returns request quicker). The more +JMeter works, the less accurate its timing information will be. The more work +JMeter does, the more each thread has to wait to get access to the CPU, the more +inflated the timing information gets. If you need large-scale load testing, consider +running multiple non-GUI JMeter instances on multiple machines. +

+
+

+

+ + + + +
+ +16.2 Where to Put the Cookie Manager +
+
+

+See + +Building a Web Test + + +for information. +

+
+

+

+ + + + +
+ +16.3 Where to Put the Authorization Manager +
+
+

+See + +Building an Advanced +Web Test + + for information. +

+
+

+

+ + + + +
+ +16.4 Using the Proxy Server +
+
+

+Refer to +HTTP Proxy Server + for details on setting up the proxy +server. The most important thing to do is filter out all requests you aren't +interested in. For instance, there's no point in recording image requests (JMeter can +be instructed to download all images on a page - see +HTTP Request +). +These will just clutter your test plan. Most likely, there is an extension all your files +share, such as .jsp, .asp, .php, .html or the like. These you should "include" by +entering ".*\.jsp" as an "Include Pattern". +

+

+Alternatively, you can exclude images by entering ".*\.gif" as an "Exclude Pattern". +Depending on your application, this may or may not be a better way to go. You may +also have to exclude stylesheets, javascript files, and other included files. Test +out your settings to verify you are recording what you want, and then erase and start +fresh. +

+

+The Proxy Server expects to find a ThreadGroup element with a Recording Controller +under it where it will record HTTP Requests to. This conveniently packages all your samples under one +controller, which can be given a name that describes the test case. +

+

+Now, go through the steps of a Test Case. If you have no pre-defined test cases, use +JMeter to record your actions to define your test cases. Once you have finished a +definite series of steps, save the entire test case in an appropriately named file. Then, wipe +clean and start a new test case. By doing this, you can quickly record a large number of +test case "rough drafts". +

+

+One of the most useful features of the Proxy Server is that you can abstract out +certain common elements from the recorded samples. By defining some + + +user-defined variables + + at the Test Plan level or in + +User Defined Variables + elements, you can have JMeter automatically +replace values in you recorded samples. For instance, if you are testing an app on +server "xxx.example.com", then you can define a variable called "server" with the value of +"xxx.example.com", and anyplace that value is found in your recorded samples will be replaced +with "${server}". + + +

+ + +
Please note that matching is case-sensitive. +
+

+ + + +

+

+ +If JMeter does not record any samples, check that the brower really is using the proxy. +If the browser works OK even if JMeter is not running, then the browser cannot be using the proxy. +Some browsers ignore proxy settings for localhost or 127.0.0.1; try using the local hostname or IP instead. + +

+

+ +The error "unknown_ca" probably means that you are trying to record HTTPS, and the browser has not accepted the +JMeter Proxy server certificate. + +

+
+

+

+ + + + +
+ +16.5 User variables +
+
+

+ +Some test plans need to use different values for different users/threads. +For example, you might want to test a sequence that requires a unique login for each user. +This is easy to achieve with the facilities provided by JMeter. + +

+

+For example: +

+
    + + +
  • +Create a text file containing the user names and passwords, separated by commas. +Put this in the same directory as your test plan. + +
  • + + +
  • + +Add a CSV DataSet configuration element to the test plan. +Name the variables USER and PASS. + +
  • + + +
  • + +Replace the login name with ${USER} and the password with ${PASS} on the appropriate +samplers + +
  • + + +
+

+The CSV Data Set element will read a new line for each thread. + +

+
+

+

+ + + + +
+ +16.6 Reducing resource requirements +
+
+

+ +Some suggestions on reducing resource usage. + +

+
    + + +
  • +Use non-GUI mode: jmeter -n -t test.jmx -l test.jtl +
  • + + +
  • +Use as few Listeners as possible; if using the -l flag as above they can all be deleted or disabled. +
  • + + +
  • +Rather than using lots of similar samplers, +use the same sampler in a loop, and use variables (CSV Data Set) to vary the sample. +Or perhaps use the Access Log Sampler. +[The Include Controller does not help here, as it adds all the test elements in the file to the test plan.] + +
  • + + +
  • +Don't use functional mode +
  • + + +
  • +Use CSV output rather than XML +
  • + + +
  • +Only save the data that you need +
  • + + +
  • +Use as few Assertions as possible +
  • + + +
+

+ +If your test needs large amounts of data - particularly if it needs to be randomised - create the test data in a file +that can be read with CSV Dataset. This avoids wasting resources at run-time. + +

+
+

+

+ + + + +
+ +16.7 BeanShell server +
+
+

+ +The BeanShell interpreter has a very useful feature - it can act as a server, +which is accessible by telnet or http. + +

+

+ + +
+There is no security. Anyone who can connect to the port can issue any BeanShell commands. +These can provide unrestricted access to the JMeter application and the host. + + +Do not enable the server unless the ports are protected against access, e.g. by a firewall. + + + +
+

+

+ +If you do wish to use the server, define the following in jmeter.properties: + +

+
+
+beanshell.server.port=9000
+beanshell.server.file=../extras/startup.bsh
+
+
+

+ +In the above example, the server will be started, and will listen on ports 9000 and 9001. +Port 9000 will be used for http access. Port 9001 will be used for telnet access. +The startup.bsh file will be processed by the server, and can be used to define various functions and set up variables. +The startup file defines methods for setting and printing JMeter and system properties. +This is what you should see in the JMeter console: + +

+
+
+Startup script running
+Startup script completed
+Httpd started on port: 9000
+Sessiond started on port: 9001
+
+
+

+ +As a practical example, assume you have a long-running JMeter test running in non-GUI mode, +and you want to vary the throughput at various times during the test. +The test-plan includes a Constant Throughput Timer which is defined in terms of a property, +e.g. ${__P(throughput)}. +The following BeanShell commands could be used to change the test: + +

+
+
+printprop("throughput");
+curr=Integer.decode(args[0]); // Start value
+inc=Integer.decode(args[1]);  // Increment
+end=Integer.decode(args[2]);  // Final value
+secs=Integer.decode(args[3]); // Wait between changes
+while(curr <= end){
+  setprop("throughput",curr.toString()); // Needs to be a string here
+  Thread.sleep(secs*1000);
+  curr += inc;
+}
+printprop("throughput");
+
+
+

+The script can be stored in a file (throughput.bsh, say), and sent to the server using bshclient.jar. +For example: + +

+
+
+java -jar ../lib/bshclient.jar localhost 9000 throughput.bsh 70 5 100 60
+
+
+
+

+

+ + + + +
+ +16.8 BeanShell scripting +
+
+ + + + +
+ +16.8.1 Overview + +
+
+

+ +Each BeanShell test element has its own copy of the interpreter (for each thread). +If the test element is repeatedly called, e.g. within a loop, then the interpreter is retained +between invocations unless the "Reset bsh.Interpreter before each call" option is selected. + +

+

+ +Some long-running tests may cause the interpreter to use lots of memory; if this is the case try using the reset option. + +

+

+ +You can test BeanShell scripts outside JMeter by using the command-line interpreter: + +

+
+$ java -cp bsh-xxx.jar[;other jars as needed] bsh.Interperter file.bsh [parameters]
+or
+$ java -cp bsh-xxx.jar bsh.Interperter
+bsh% source("file.bsh");
+bsh% exit(); // or use EOF key (e.g. ^Z or ^D)
+
+
+ + +

+
+

+ + + + +
+ +16.8.2 Sharing Variables + +
+
+

+ +Variables can be defined in startup (initialisation) scripts. +These will be retained across invocations of the test element, unless the reset option is used.\ + +

+

+ +Scripts can also access JMeter variables using the get() and put() methods of the "vars" variable, +for example: + +vars.get("HOST"); vars.put("MSG","Successful"); + +. +The get() and put() methods only support variables with String values, +but there are also getObject() and putObject() methods which can be used for arbitrary objects. +JMeter variables are local to a thread, but can be used by all test elements (not just Beanshell). + +

+

+ +If you need to share variables between threads, then JMeter properties can be used: + +

+
+import org.apache.jmeter.util.JMeterUtils;
+String value=JMeterUtils.getPropDefault("name","");
+JMeterUtils.setProperty("name", "value");
+
+
+ +The sample .bshrc files contain sample definitions of getprop() and setprop() methods. + +

+

+ +Another possible method of sharing variables is to use the "bsh.shared" shared namespace. +For example: + +

+
+if (bsh.shared.myObj == void){
+    // not yet defined, so create it:
+    myObj=new AnyObject();
+}
+bsh.shared.myObj.process();
+
+
+ +Rather than creating the object in the test element, it can be created in the startup file +defined by the JMeter property "beanshell.init.file". This is only processed once. + +

+
+

+
+

+

+ + + + +
+ +16.9 Developing script functions in BeanShell, Javascript or Jexl etc. +
+
+

+ +It's quite hard to write and test scripts as functions. +However, JMeter has the BSF (and BeanShell) samplers which can be used instead. + +

+

+ +Create a simple Test Plan containing the BSF Sampler and Tree View Listener. +Code the script in the sampler script pane, and test it by running the test. +If there are any errors, these will show up in the Tree View. +Also the result of running the script will show up as the response. + +

+

+ +Once the script is working properly, it can be stored as a variable on the Test Plan. +The script variable can then be used to create the function call. +For example, suppose a BeanShell script is stored in the variable RANDOM_NAME. +The function call can then be coded as + +${__BeanShell(${RANDOM_NAME})} + +. +There is no need to escape any commas in the script, +because the function call is parsed before the variable's value is interpolated. + +

+
+

+

+ + + + +
+ +16.10 Parameterising tests +
+
+

+ +Often it is useful to be able to re-run the same test with different settings. +For example, changing the number of threads or loops, or changing a hostname. + +

+

+ +One way to do this is to define a set of variables on the Test Plan, and then use those variables in the test elements. +For example, one could define the variable LOOPS=10, and refer to that in the Thread Group as ${LOOPS}. +To run the test with 20 loops, just change the value of the LOOPS variable on the Test Plan. + +

+

+ +This quickly becomes tedious if you want to run lots of tests in non-GUI mode. +One solution to this is to define the Test Plan variable in terms of a property, +for example + +LOOPS=${__P(loops,10))} + +. +This uses the value of the property "loops", defaulting to 10 if the property is not found. +The "loops" property can then be defined on the JMeter command-line: + + +jmeter ... -Jloops=12 ... + +. +If there are a lot of properties that need to be changed together, +then one way to achieve this is to use a set of property files. +The appropriate property file can be passed in to JMeter using the -q command-line option. + +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/boss.html b/ApacheJmeter/docs/usermanual/boss.html new file mode 100644 index 0000000..362ce31 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/boss.html @@ -0,0 +1,667 @@ + + + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: My boss wants me to... + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +17. Help! My boss wants me to load test our web app! +
+
+

+This is a fairly open-ended proposition. There are a number of questions to +be asked first, and additionally a number of resources that will be needed. You +will need some hardware to run the benchmarks/load-tests from. A number of +tools will prove useful. There are a number of products to consider. And finally, +why is Java a good choice to implement a load-testing/Benchmarking product. + +

+ + + + +
+ +17.1 Questions to ask + +
+
+

+What is our anticipated average number of users (normal load) ? + +

+

+What is our anticipated peak number of users ? + +

+

+When is a good time to load-test our application (i.e. off-hours or week-ends), +bearing in mind that this may very well crash one or more of our servers ? + +

+

+Does our application have state ? If so, how does our application manage it +(cookies, session-rewriting, or some other method) ? + +

+

+What is the testing intended to achieve? +

+
+

+ + + + +
+ +17.2 Resources + +
+
+

+The following resources will prove very helpful. Bear in mind that if you +cannot locate these resources, + +you + + will become these resources. As you +already have your work cut out for you, it is worth knowing who the following +people are, so that you can ask them for help if you need it. + +

+ + + + +
+ +17.2.1 Network + +
+
+

+Who knows our network topology ? If you run into any firewall or + proxy issues, this will become very important. As well, a private + testing network (which will therefore have very low network latency) + would be a very nice thing. Knowing who can set one up for you + (if you feel that this is necessary) will be very useful. If the + application doesn't scale as expected, who can add additional + hardware ? + +

+
+

+ + + + +
+ +17.2.2 Application + +
+
+

+Who knows how our application functions ? The normal sequence is + +

    + + +
  • +test (low-volume - can we benchmark our application?) +
  • + + +
  • +benchmark (the average number of users) +
  • + + +
  • +load-test (the maximum number of users) +
  • + + +
  • +test destructively (what is our hard limit?) +
  • + + +
+ + The + +test + + process may progress from black-box testing to + white-box testing (the difference is that the first requires + no knowledge of the application [it is treated as a "black box"] + while the second requires some knowledge of the application). + It is not uncommon to discover problems with the application + during this process, so be prepared to defend your work. + +

+
+

+
+

+ + + + +
+ +17.3 What platform should I use to run the benchmarks/load-tests ? + +
+
+

+This should be a widely-used piece of hardware, with a standard +(i.e. vanilla) software installation. Remember, if you publish your results, +the first thing your clients will do is hire a graduate student to verify them. +You might as well make it as easy for this person as you possibly can. + +

+

+For Windows, Windows XP Professional should be a minimum (the others +do not multi-thread past 50-60 connections, and you probably anticipate +more users than that). + +

+

+Good free platforms include the linuxes, the BSDs, and Solaris Intel. If +you have a little more money, there are commercial linuxes. +This may be worth it if you need the support. + +

+

+ +For non-Windows platforms, investigate "ulimit -n unlimited" with a view to +including it in your user account startup scripts (.bashrc or .cshrc scripts +for the testing account). + +

+

+As you progress to larger-scale benchmarks/load-tests, this platform +will become the limiting factor. So it's worth using the best hardware and +software that you have available. Remember to include the hardware/software +configuration in your published benchmarks. + +

+

+Don't forget JMeter batch mode. This can be useful if you have a powerful server +that supports Java but perhaps does not have a fast graphics implementation, +or where you need to login remotely. +Batch (non-GUI) mode can reduce the network traffic compared with using a remote display or client-server mode. +The batch log file can then be loaded into JMeter on a workstation for analysis, or you can +use CSV output and import the data into a spreadsheet. +

+
+

+ + + + +
+ +17.4 Tools + +
+
+

+The following tools will all prove useful. It is definitely worthwhile to +become familiar with them. This should include trying them out, and reading the +appropriate documentation (man-pages, info-files, application --help messages, +and any supplied documentation). + +

+ + + + +
+ +17.4.1 ping + +
+
+

+ + This can be used to establish whether or not you can reach your + target site. Options can be specified so that 'ping' provides the + same type of route reporting as 'traceroute'. + +

+
+

+ + + + +
+ +17.4.2 nslookup/dig + +
+
+

+ + While the + +user + + will normally use a human-readable internet + address, + +you + + may wish to avoid the overhead of DNS lookups when + performing benchmarking/load-testing. These can be used to determine + the unique address (dotted quad) of your target site. + +

+
+

+ + + + +
+ +17.4.3 traceroute + +
+
+

+ + If you cannot "ping" your target site, this may be used to determine + the problem (possibly a firewall or a proxy). It can also be used + to estimate the overall network latency (running locally should give + the lowest possible network latency - remember that your users will + be running over a possibly busy internet). Generally, the fewer hops + the better. + +

+
+

+
+

+ + + + +
+ +17.5 What other products are there ? + +
+
+

+There are a number of commercial products, which generally have fairly +hefty pricetags. If you can justify it, these are probably the way to go. +If, however, these products do not do exactly what you want, or you are on a +limited budget, the following are worth a look. In fact, you should probably +start by trying the Apache + +ab + + tool, as it may very well do the job +if your requirements are not particularly complicated. + +

+ + + + +
+ +17.5.1 Apache 'ab' tool + +
+
+

+ + You should definitely start with this one. It handles HTTP 'get' requests + very well, and can be made to handle HTTP 'post' requests with a little + effort. Written in 'C', it performs very well, and offers good (if basic) + performance reporting. + +

+
+

+ + + + +
+ +17.5.2 HttpUnit + +
+
+

+ + This is worth a look. It is a library (and therefore of more interest to + developers) that can be used to perform HTTP tests/benchmarks. It is + intended to be used instead of a web browser (therefore no GUI) in + conjunction with + +JUnit + +. + +

+
+

+ + + + +
+ +17.5.3 Microsoft WAS + +
+
+

+ + This is definitely worth a look. It has an excellent user interface + but it may not do exactly what you want. If this is the case, be aware + that the functionality of this product is not likely to change. + +

+
+

+ + + + +
+ +17.5.4 JMeter + +
+
+

+ + If you have non-standard requirements, then this solution offers an + open-source community to provide them (of course, if you are reading + + +this + +, you are probably already committed to this one). This + product is free to evolve along with your requirements. + +

+
+

+
+

+ + + + +
+ +17.6 Why Java ? + +
+
+

+Why not Perl or C ? + +

+

+Well, Perl might be a very good choice except that the Benchmark package +seems to give fairly fuzzy results. Also, simulating multiple users with +Perl is a tricky proposition (multiple connections can be simulated by forking +many processes from a shell script, but these will not be threads, they will +be processes). However, the Perl community is very large. If you find that +someone has already written something that seems useful, this could be a very +good solution. + +

+

+C, of course, is a very good choice (check out the Apache + +ab + + tool). +But be prepared to write all of the custom networking, threading, and state +management code that you will need to benchmark your application. + +

+

+Java gives you (for free) the custom networking, threading, and state +management code that you will need to benchmark your application. Java is +aware of HTTP, FTP, and HTTPS - as well as RMI, IIOP, and JDBC (not to mention +cookies, URL-encoding, and URL-rewriting). In addition Java gives you automatic +garbage-collection, and byte-code level security. + +

+

+And once Microsoft moves to a CLR (common language run-time) a Windows Java +solution will not be any slower than any other type of solution on the Windows +platform. + +

+
+

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/build-adv-web-test-plan.html b/ApacheJmeter/docs/usermanual/build-adv-web-test-plan.html new file mode 100644 index 0000000..682a87f --- /dev/null +++ b/ApacheJmeter/docs/usermanual/build-adv-web-test-plan.html @@ -0,0 +1,330 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Building an Advanced Web Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +6. Building an Advanced Web Test Plan +
+
+

+In this section, you will learn how to create advanced + + +Test Plans + + to test a Web site. +

+

+For an example of a basic Test Plan, see + + +Building a Web Test Plan + +. +

+
+

+

+ + + + +
+ +6.1 Handling User Sessions With URL Rewriting +
+
+

+If your web application uses URL rewriting rather than cookies to save session information, +then you'll need to do a bit of extra work to test your site. +

+

+To respond correctly to URL rewriting, JMeter needs to parse the HTML +received from the server and retrieve the unique session ID. Use the appropriate +HTTP URL Re-writing Modifier + +to accomplish this. Simply enter the name of your session ID parameter into the modifier, and it +will find it and add it to each request. If the request already has a value, it will be replaced. +If "Cache Session Id?" is checked, then the last found session id will be saved, +and will be used if the previous HTTP sample does not contain a session id. + +

+ +

URL Rewriting Example

+ + +

+Download + +this example + +. In Figure 1 is shown a +test plan using URL rewriting. Note that the URL Re-writing modifier is added to the SimpleController, +thus assuring that it will only affect requests under that SimpleController. +

+ + +


+Figure 1 - Test Tree +

+ + +

+In Figure 2, we see the URL Re-writing modifier GUI, which just has a field for the user to specify +the name of the session ID parameter. There is also a checkbox for indicating that the session ID should +be part of the path (separated by a ";"), rather than a request parameter +

+ + +


+Figure 2 - Request parameters +

+ + +
+

+

+ + + + +
+ +6.2 Using a Header Manager +
+
+

+The +HTTP Header Manager + lets you customize what information +JMeter sends in the HTTP request header. This header includes properties like "User-Agent", +"Pragma", "Referer", etc. +

+

+The +HTTP Header Manager +, like the +HTTP Cookie Manager +, +should probably be added at the Thread Group level, unless for some reason you wish to +specify different headers for the different +HTTP Request + objects in +your test. +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/build-db-test-plan.html b/ApacheJmeter/docs/usermanual/build-db-test-plan.html new file mode 100644 index 0000000..bcdb640 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/build-db-test-plan.html @@ -0,0 +1,530 @@ + + + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Building a Database Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +7. Building a Database Test Plan +
+
+

+In this section, you will learn how to create a basic + + +Test Plan + + to test a database server. +You will create ten users that send five SQL requests to the database server. +Also, you will tell the users to run their tests three times. So, the total number +of requests is (10 users) x (2 requests) x (repeat 3 times) = 60 JDBC requests. +To construct the Test Plan, you will use the following elements: + + +Thread Group + +, + +JDBC Request +, +Graph Results +. +

+

+ + +
This example uses the MySQL database driver. +To use this driver, its containing .jar file must be copied to the JMeter + + +lib + + directory (see + +JMeter's Classpath + + +for more details). +
+

+
+

+

+ + + + +
+ +7.1 Adding Users +
+
+

+The first step you want to do with every JMeter Test Plan is to add a + + +Thread Group + + element. The Thread Group +tells JMeter the number of users you want to simulate, how often the users should +send requests, and the how many requests they should send. +

+

+Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup. +

+

+You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element. +

+

+Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 7.1 +below) +

+


+ +Figure 7.1. Thread Group with Default Values +

+

+Start by providing a more descriptive name for our Thread Group. In the name +field, enter JDBC Users. +

+

+ + +
You will need a valid database, database table, and user-level access to that +table. In the example shown here, the database is 'mydb' and the table name is +'Stocks'. +
+

+

+Next, increase the number of users to 10. +

+

+In the next field, the Ramp-Up Period, leave the the default value of 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users. +

+

+Finally, enter a value of 3 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox. +

+

+ + +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). +
+

+

+See Figure 7.2 for the completed JDBC Users Thread Group. +

+


+ +Figure 7.2. JDBC Users Thread Group +

+
+

+

+ + + + +
+ +7.2 Adding JDBC Requests +
+
+

+Now that we have defined our users, it is time to define the tasks that they +will be performing. In this section, you will specify the JDBC requests to +perform. +

+

+Begin by selecting the JDBC Users element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element --> JDBC Connection Configuration. +Then, select this new element to view its Control Panel (see Figure 7.3). +

+

+Set up the following fields (these assume we will be using a local MySQL database called test): +

+
    + + +
  • +Variable name bound to pool. This needs to uniquely identify the configuration. It is used by the JDBC Sampler to identify the configuration to be used. +
  • + + +
  • +Database URL: jdbc:mysql://localhost:3306/test +
  • + + +
  • +JDBC Driver class: com.mysql.jdbc.Driver +
  • + + +
  • +Username: guest +
  • + + +
  • +Password: password for guest +
  • + + +
+

+The other fields on the screen can be left as the defaults. +

+

+JMeter creates a database connection pool with the configuration settings as specified in the Control Panel. +The pool is referred to in JDBC Requests in the 'Variable Name' field. +Several different JDBC Configuration elements can be used, but they must have unique names. +Every JDBC Request must refer to a JDBC Configuration pool. +More than one JDBC Request can refer to the same pool. + +

+


+ +Figure 7.3. JDBC Configuration +

+

+Selecting the JDBC Users element again. Click your right mouse button +to get the Add menu, and then select Add --> Sampler --> JDBC Request. +Then, select this new element to view its Control Panel (see Figure 7.4). +

+


+ +Figure 7.4. JDBC Request +

+

+In our Test Plan, we will make two JDBC requests. The first one is for +Eastman Kodak stock, and the second is Pfizer stock (obviously you should +change these to examples appropriate for your particular database). These +are illustrated below. +

+

+ + +
JMeter sends requests in the order that you add them to the tree. +
+

+

+Start by editing the following properties (see Figure 7.5): + +

    + + +
  • +Change the Name to "Kodak". +
  • + + +
  • +Enter the Pool Name: MySQL (same as in the configuration element) +
  • + + +
  • +Enter the SQL Query String field. +
  • + + +
+ + +

+


+ +Figure 7.5. JDBC Request for Eastman Kodak stock +

+

+Next, add the second JDBC Request and edit the following properties (see +Figure 7.6): + +

    + + +
  • +Change the Name to "Pfizer". +
  • + + +
  • +Enter the SQL Query String field. +
  • + + +
+ + +

+


+ +Figure 7.6. JDBC Request for Pfizer stock +

+
+

+

+ + + + +
+ +7.3 Adding a Listener to View/Store the Test Results +
+
+

+The final element you need to add to your Test Plan is a + + +Listener + +. This element is +responsible for storing all of the results of your JDBC requests in a file +and presenting a visual model of the data. +

+

+Select the JDBC Users element and add a +Graph Results + +listener (Add --> Listener --> Graph Results). +

+


+ +Figure 7.7. Graph results Listener +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/build-ftp-test-plan.html b/ApacheJmeter/docs/usermanual/build-ftp-test-plan.html new file mode 100644 index 0000000..f482f5b --- /dev/null +++ b/ApacheJmeter/docs/usermanual/build-ftp-test-plan.html @@ -0,0 +1,535 @@ + + + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Building an FTP Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +8. Building an FTP Test Plan +
+
+

+In this section, you will learn how to create a basic + + +Test Plan + + to test an FTP site. You will +create four users that send requests for two files on the O'Reilly FTP site. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (4 users) x (2 requests) x (repeat 2 times) = 16 FTP requests. To +construct the Test Plan, you will use the following elements: + + +Thread Group + +, + +FTP Request +, + +FTP Request Defaults +, and + +Spline Visualizer +. +

+

+ + +
This example uses the O'Reilly FTP site, www.oro.com. Please be considerate +when running this example, and (if possible) consider running against another +FTP site. +
+

+
+

+

+ + + + +
+ +8.1 Adding Users +
+
+

+The first step you want to do with every JMeter Test Plan is to add a + + +Thread Group + + element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. +

+

+Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup. +

+

+You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element. +

+

+Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 8.1 +below) +

+


+ +Figure 8.1. Thread Group with Default Values +

+

+Start by providing a more descriptive name for our Thread Group. In the name +field, enter O'Reilly Users. +

+

+Next, increase the number of users to 4. +

+

+In the next field, the Ramp-Up Period, leave the the default value of 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users. +

+

+Finally, enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox. +

+

+ + +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). +
+

+

+See Figure 8.2 for the completed O'Reilly Users Thread Group. +

+


+ +Figure 8.2. O'Reilly Users Thread Group +

+
+

+

+ + + + +
+ +8.2 Adding Default FTP Request Properties +
+
+

+Now that we have defined our users, it is time define the tasks that they +will be performing. In this section, you will specify the default settings +for your FTP requests. And then, in section 8.3, you will add FTP Request +elements which use some of the default settings you specified here. +

+

+Begin by selecting the O'Reilly Users element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element --> FTP Request +Defaults. Then, select this new element to view its Control Panel (see Figure 8.3). + +

+


+ +Figure 8.3. FTP Request Defaults +

+

+ +Like most JMeter elements, the +FTP Request Defaults + Control +Panel has a name field that you can modify. In this example, leave this field with +the default value. +

+

+Skip to the next field, which is the FTP Server's Server Name/IP. For the +Test Plan that you are building, all FTP requests will be sent to the same +FTP server, ftp.oro.com. Enter this domain name into the field. +This is the only field that we will specify a default, so leave the remaining +fields with their default values. +

+

+ + +
The FTP Request Defaults element does not tell JMeter +to send an FTP request. It simply defines the default values that the +FTP Request elements use. +
+

+

+See Figure 8.4 for the completed FTP Request Defaults element +

+


+ +Figure 8.4. FTP Defaults for our Test Plan +

+
+

+

+ + + + +
+ +8.3 Adding FTP Requests +
+
+

+In our Test Plan, we need to make two FTP requests. The first one is for the +O'Reilly mSQL Java README file (ftp://ftp.oro.com/pub/msql/java/README), and the +second is for the tutorial file (ftp://ftp.oro.com/pub/msql/java/tutorial.txt). +

+

+ + +
JMeter sends requests in the order that they appear in the tree. +
+

+

+Start by adding the first +FTP Request + +to the O'Reilly Users element (Add --> Sampler --> FTP Request). +Then, select the FTP Request element in the tree and edit the following properties +(see Figure 8.5): + +

    + + +
  1. +Change the Name to "README". +
  2. + + +
  3. +Change the File to Retrieve From Server field to "pub/msql/java/README". +
  4. + + +
  5. +Change the Username field to "anonymous". +
  6. + + +
  7. +Change the Password field to "anonymous". +
  8. + + +
+ + +

+

+ + +
You do not have to set the Server Name field because you already specified +this value in the FTP Request Defaults element. +
+

+


+ +Figure 8.5. FTP Request for O'Reilly mSQL Java README file +

+

+Next, add the second FTP Request and edit the following properties (see +Figure 8.6: + +

    + + +
  1. +Change the Name to "tutorial". +
  2. + + +
  3. +Change the File to Retrieve From Server field to "pub/msql/java/tutorial.txt". +
  4. + + +
  5. +Change the Username field to "anonymous". +
  6. + + +
  7. +Change the Password field to "anonymous". +
  8. + + +
+ + +

+


+ +Figure 8.6. FTP Request for O'Reilly mSQL Java tutorial file +

+
+

+

+ + + + +
+ +8.4 Adding a Listener to View/Store the Test Results +
+
+

+The final element you need to add to your Test Plan is a + + +Listener + +. This element is +responsible for storing all of the results of your FTP requests in a file and presenting +a visual model of the data. +

+

+Select the O'Reilly Users element and add a +Spline Visualizer + +listener (Add --> Listener --> Spline Visualizer). +

+


+ +Figure 8.7. Spline Visualizer Listener +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/build-jms-point-to-point-test-plan.html b/ApacheJmeter/docs/usermanual/build-jms-point-to-point-test-plan.html new file mode 100644 index 0000000..5089949 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/build-jms-point-to-point-test-plan.html @@ -0,0 +1,638 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Building a JMS (Java Messaging Service) Point-to-Point Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +11. Building a JMS Point-to-Point Test Plan +
+
+

+ + +
+ Make sure the required jar files are in JMeter's lib directory. If they are not, shutdown JMeter, + copy the jar files over and restart JMeter. + See + +Getting Started + + for details. + +
+

+

+In this section, you will learn how to create a + + +Test Plan + + to test a JMS Point-to-Point messaging solution. +The setup of the test is 1 threadgroup with 5 threads sending 4 messages each through a request queue. +A fixed reply queue will be used for monitoring the reply messages. +To construct the Test Plan, you will use the +following elements: + + +Thread Group + +, + +JMS Point-to-Point +, and + +Graph Results +. + +

+

+General notes on JMS: There are currently two JMS samplers. One uses JMS topics +and the other uses queues. Topic messages are commonly known as pub/sub messaging. +Topic messaging is generally used in cases where a message is published by a producer and +consumed by multiple subscribers. A JMS sampler needs the JMS implementation jar files; +for example, from Apache ActiveMQ. See + +here + + for the list +of jars provided by ActiveMQ 3.0. +

+
+

+

+ + + + +
+ +11.1 Adding a Thread Group +
+
+

+The first step you want to do with every JMeter Test Plan is to add a + + +Thread Group + + element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. + +

+

+Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup. +

+

+You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element. +

+

+Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 11.1 +below) +

+


+ +Figure 11.1. Thread Group with Default Values +

+

+Start by providing a more descriptive name for our Thread Group. In the name +field, enter Point-to-Point. +

+

+Next, increase the number of users (called threads) to 5. +

+

+In the next field, the Ramp-Up Period, leave set the value to 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users. +

+

+Clear the checkbox labeled "Forever", and enter a value of 4 in the Loop +Count field. This property tells JMeter how many times to repeat your test. +If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox. +

+

+ + +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). +
+

+
+

+

+ + + + +
+ +11.2 Adding JMS Point-to-Point Sampler +
+
+

+Start by adding the sampler +JMS Point-to-Point + +to the Point-to-Point element (Add --> Sampler --> JMS Point-to-Point). +Then, select the JMS Point-to-Point sampler element in the tree. + In building the example a configuration will be provided that works with ActiveMQ 3.0. + +

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +Name + + + +Value + + + +Description + +
+ +JMS Resources + +
+ +QueueuConnectionFactory + + + +ConnectionFactory + + + + This is the default JNDI entry for the connection factory within active mq. + +
+ +JNDI Name Request Queue + + + +Q.REQ + + + +This is equal to the JNDI name defined in the JNDI properties. + +
+ +JNDI Name Reply Queue + + + +Q.RPL + + + +This is equal to the JNDI name defined in the JNDI properties. + +
+ +Message Properties + +
+ +Communication Style + + + +Request Response + + + +This means that you need at least a service that responds to the requests. + +
+ +Content + + + +test + + + +This is just the content of the message. + +
+ +JMS Properties + + + +  + + + +Nothing needed for active mq. + +
+ +JNDI Properties + +
+ +InitialContextFactory + + + +org.apache.activemq.jndi.ActiveMQInitialContextFactory + + + +The standard InitialContextFactory for Active MQ + +
+ + Properties + +
+ +queue.Q.REQ + + + +example.A + + + +This defines a JNDI name Q.REQ for the request queue that points to the queue example.A + +
+ +queue.Q.RPL + + + +example.B + + + +This defines a JNDI name Q.RPL for the reply queue that points to the queue example.B + +
+ +Provider URL + +
+ +Provider URL + + + +tcp://localhost:61616 + + + +This defines the URL of the active mq messaging system. + +
+ + +

+
+

+

+ + + + +
+ +11.3 Adding a Listener to View Store the Test Results +
+
+

+The final element you need to add to your Test Plan is a + + +Listener + +. This element is +responsible for storing all of the results of your JMS requests in a file and presenting +a visual model of the data. + +

+

+Select the Thread Group element and add a + +Graph Results + listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename. + +

+


+ +Figure 11.2. Graph Results Listener +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/build-jms-topic-test-plan.html b/ApacheJmeter/docs/usermanual/build-jms-topic-test-plan.html new file mode 100644 index 0000000..67812a7 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/build-jms-topic-test-plan.html @@ -0,0 +1,565 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Building a JMS (Java Messaging Service) Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +12. Building a JMS Topic Test Plan +
+
+

+ + +
+JMS requires some optional jars to be downloaded. Please refer to + +Getting Started + + for full details. + +
+

+

+In this section, you will learn how to create a + + +Test Plan + + to test JMS Providers. You will +create five subscribers and one publisher. You will create 2 thread groups and set +each one to 10 iterations. The total messages is (6 threads) x (1 message) x +(repeat 10 times) = 60 messages. To construct the Test Plan, you will use the +following elements: + + +Thread Group + +, + +JMS Publisher +, + +JMS Subscriber +, and + +Graph Results +. +

+

+General notes on JMS: There are currently two JMS samplers. One uses JMS topics +and the other uses queues. Topic messages are commonly known as pub/sub messaging. +Topic messaging is generally used in cases where a message is published by a producer and +consumed by multiple subscribers. Queue messaging is generally used for transactions +where the sender expects a response. Messaging systems are quite different from +normal HTTP requests. In HTTP, a single user sends a request and gets a response. +Messaging system can work in sychronous and asynchronous mode. A JMS sampler needs +the JMS implementation jar files; for example, from Apache ActiveMQ. +See + +here + + for the list of jars provided by ActiveMQ 3.0. +

+
+

+

+ + + + +
+ +12.1 Adding Users +
+
+

+The first step is add a + +Thread Group + + + element. The Thread Group tells JMeter the number of users you want to simulate, + how often the users should send requests, and how many requests they should +send. +

+

+Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup. +

+

+You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element. +

+

+Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 12.1 +below) +

+


+ +Figure 12.1. Thread Group with Default Values +

+

+Start by providing a more descriptive name for our Thread Group. In the name +field, enter Subscribers. +

+

+Next, increase the number of users (called threads) to 5. +

+

+In the next field, the Ramp-Up Period, set the value to 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, JMeter will immediately start all users. +

+

+Clear the checkbox labeled "Forever", and enter a value of 10 in the Loop +Count field. This property tells JMeter how many times to repeat your test. +If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox. +

+

+Repeat the process and add another thread group. For the second thread +group, enter "Publisher" in the name field, set the number of threads to 1, +and set the iteration to 10. + +

+

+ + +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). +
+

+
+

+

+ + + + +
+ +12.2 Adding JMS Subscriber and Publisher +
+
+

+Make sure the required jar files are in JMeter's lib directory. If they are +not, shutdown JMeter, copy the jar files over and restart JMeter. +

+

+Start by adding the sampler +JMS Subscriber + +to the Subscribers element (Add --> Sampler --> JMS Subscriber). +Then, select the JMS Subscriber element in the tree and edit the following properties: + + +

    + + +
  1. +Change the Name field to "Sample Subscriber" +
  2. + + +
  3. +If the JMS provider uses the jndi.properties file, check the box +
  4. + + +
  5. +Enter the name of the InitialContextFactory class. For example, with ActiveMQ 5.4, the value is "org.apache.activemq.jndi.ActiveMQInitialContextFactory" +
  6. + + +
  7. +Enter the provider URL. This is the URL for the JNDI server, if there is one. For example, with ActiveMQ 5.4 on local machine with default port, the value is "tcp://localhost:61616" +
  8. + + +
  9. +Enter the name of the connection factory. Please refer to the documentation +of the JMS provider for the information. For ActiveMQ, the default is "ConnectionFactory" +
  10. + + +
  11. +Enter the name of the message topic. For ActiveMQ Dynamic Topics (create topics dynamically), example value is "dynamicTopics/MyStaticTopic1" +Note: Setup at startup mean that JMeter starting to listen on the Destination at beginning of test without name change possibility. +Setup on Each sample mean that JMeter (re)starting to listen before run each JMS Subscriber sample, +this last option permit to have Destination name with some JMeter variables +
  12. + + +
  13. +If the JMS provider requires authentication, check "required" and enter the +username and password. For example, Orion JMS requires authentication, while ActiveMQ +and MQSeries does not +
  14. + + +
  15. +Enter 10 in "Number of samples to aggregate". For performance reasons, the sampler +will aggregate messages, since small messages will arrive very quickly. If the sampler +didn't aggregate the messages, JMeter wouldn't be able to keep up. +
  16. + + +
  17. +If you want to read the response, check the box +
  18. + + +
  19. +There are two client implementations for subscribers. If the JMS provider +exhibits zombie threads with one client, try the other. +
  20. + + +
+ + +

+


+ +Figure 12.2. JMS Subscriber +

+

+Next add the sampler +JMS Publisher + +to the Publisher element (Add --> Sampler --> JMS Subscriber). +Then, select the JMS Publisher element in the tree and edit the following properties: + +

+
    + + +
  1. +Change the Name field to "Sample Publisher". +
  2. + + +
  3. +If the JMS provider uses the jndi.properties file, check the box +
  4. + + +
  5. +Enter the name of the InitialContextFactory class. For example, with ActiveMQ 5.4, the value is "org.apache.activemq.jndi.ActiveMQInitialContextFactory" +
  6. + + +
  7. +Enter the provider URL. This is the URL for the JNDI server, if there is one. For example, with ActiveMQ 5.4 on local machine with default port, the value is "tcp://localhost:61616" +
  8. + + +
  9. +Enter the name of the connection factory. Please refer to the documentation +of the JMS provider for the information. For ActiveMQ, the default is "ConnectionFactory" +
  10. + + +
  11. +Enter the name of the message topic. For ActiveMQ Dynamic Topics (create topics dynamically), example value is "dynamicTopics/MyStaticTopic1". +Note: Setup at startup mean that JMeter starting connection with the Destination at beginning of test without name change possibility. +Setup on Each sample mean that JMeter (re)starting the connection before run each JMS Publisher sample, +this last option permit to have Destination name with some JMeter variables +
  12. + + +
  13. +If the JMS provider requires authentication, check "required" and enter the +username and password. For example, Orion JMS requires authentication, while ActiveMQ +and MQSeries does not +
  14. + + +
  15. +Enter 10 in "Number of samples to aggregate". For performance reasons, the sampler +will aggregate messages, since small messages will arrive very quickly. If the sampler +didn't aggregate the messages, JMeter wouldn't be able to keep up. +
  16. + + +
  17. +Select the appropriate configuration for getting the message to publish. If you +want the sampler to randomly select the message, place the messages in a directory +and select the directory using browse. +
  18. + + +
  19. +Select the message type. If the message is in object format or map message, make sure the +message is generated correctly. +
  20. + + +
+

+

+


+ +Figure 12.3. JMS Publisher +

+
+

+

+ + + + +
+ +12.3 Adding a Listener to View Store the Test Results +
+
+

+The final element you need to add to your Test Plan is a + + +Listener + +. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data. +

+

+Select the Test Plan element and add a +Graph Results + listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename. +

+


+ +Figure 12.4. Graph Results Listener +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/build-ldap-test-plan.html b/ApacheJmeter/docs/usermanual/build-ldap-test-plan.html new file mode 100644 index 0000000..ca74f8e --- /dev/null +++ b/ApacheJmeter/docs/usermanual/build-ldap-test-plan.html @@ -0,0 +1,513 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Building an LDAP Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +9a. Building an LDAP Test Plan +
+
+

+In this section, you will learn how to create a basic Test Plan to test an LDAP server. +You will create four users that send requests for four tests on the LDAP server.Also, you will tell +the users to run their tests twice. So, the total number of requests is (4 users) x (4 requests) x +repeat 2 times) = 32 LDAP requests. To construct the Test Plan, you will use the following elements: + + +Thread Group + +, + +LDAP Request +, + +LDAP Request Defaults +, and + +View Results in Table + +. +

+

+This example assumes that the LDAP Server is installed in your Local machine. +

+
+

+

+ + + + +
+ +9a.1 Adding Users +
+
+

+The first step you want to do with every JMeter Test Plan is to add a Thread Group element. +The Thread Group tells JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. +

+

+Go ahead and add the ThreadGroup element by first selecting the Test Plan, clicking your +right mouse button to get the Add menu, and then select Add-->ThreadGroup. You should now see the +Thread Group element under Test Plan. If you do not see the element, then "expand" the Test Plan tree by +clicking on the Test Plan element. + +


+ +Figure 9a.1. Thread Group with Default Values +

+ + + +

+
+

+

+ + + + +
+ +9a.2 Adding Login Config Element +
+
+

+Begin by selecting the Siptech Users element. Click your right mouse +button to get the Add menu, and then select Add --> Config Element --> Login Config Element. +Then, select this new element to view its Control Panel. +

+

+Like most JMeter elements, the Login Config Element Control Panel has a name +field that you can modify. In this example, leave this field with the default value. +

+


+ + Figure 9a.2 Login Config Element for our Test Plan +

+

+ + +

+Enter Username field to "your Server Username", +
+ + + The password field to "your Server Passowrd" +

+ + + +

+These values are default for the LDAP Requests. +

+
+

+
+

+

+ + + + +
+ +9a.3 Adding LDAP Request Defaults +
+
+

+Begin by selecting the Siptech Users element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element -->LDAP Request Defaults. Then, +select this new element to view its Control Panel. +

+

+Like most JMeter elements, the LDAP Request Defaults Control Panel has a name +field that you can modify. In this example, leave this field with the default value. +

+


+ + Figure 9a.3 LDAP Defaults for our Test Plan +

+

+ + +
Enter DN field to "your Server Root Dn". +
+ + + Enter LDAP Server's Servername field to "localhost". +
+ + + The port to 389. +
+ + + These values are default for the LDAP Requests. +
+

+
+

+

+ + + + +
+ +9a.4 Adding LDAP Requests +
+
+

+In our Test Plan, we need to make four LDAP requests. +

+
    + + +
  1. +Inbuilt Add Test +
  2. + + +
  3. +Inbuilt Modify Test +
  4. + + +
  5. +Inbuilt Delete Test +
  6. + + +
  7. +Inbuilt Search Test +
  8. + + +
+

+JMeter sends requests in the order that you add them to the tree. +Start by adding the first LDAP Request to the Siptech Users element (Add --> +Sampler --> LDAP Request). Then, select the LDAP Request element in the tree +and edit the following properties +

+
    + + +
  1. +Change the Name to "Inbuilt-Add Test". +
  2. + + +
  3. +Select the Add test Radio button +
  4. + + +
+


+ + Figure 9a.4.1 LDAP Request for Inbuilt Add test +

+

+You do not have to set the Server Name field, port field, Username, Password +and DN because you already specified this value in the Login Config Element and +LDAP Request Defaults. +

+

+Next, add the second LDAP Request and edit the following +properties +

+
    + + +
  1. +Change the Name to "Inbuilt-Modify Test". +
  2. + + +
  3. +Select the Modify test Radio button +
  4. + + +
+


+ + Figure 9a.4.2 LDAP Request for Inbuilt Modify test +

+
    + + +
  1. +Change the Name to "Inbuilt-Delete Test". +
  2. + + +
  3. +Select the Delete test Radio button +
  4. + + +
+


+ + Figure 9a.4.3 LDAP Request for Inbuilt Delete test +

+
    + + +
  1. +Change the Name to "Inbuilt-Search Test". +
  2. + + +
  3. +Select the Search test Radio button +
  4. + + +
+


+ + Figure 9a.4.4 LDAP Request for Inbuilt Search test +

+
+

+

+ + + + +
+ +9a.5 Adding a Listener to View/Store the Test Results +
+
+

+The final element you need to add to your Test Plan is a Listener. + This element is responsible for storing all of the results of your LDAP +requests in a file and presenting a visual model of the data.Select the Siptech +Users element and add a View Results in Table (Add --> Listener -->View Results in Table) +

+


+ + Figure 9a.5 View result in Table Listener +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/build-ldapext-test-plan.html b/ApacheJmeter/docs/usermanual/build-ldapext-test-plan.html new file mode 100644 index 0000000..3e26933 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/build-ldapext-test-plan.html @@ -0,0 +1,1255 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Building an Extended LDAP Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +9b. Building an Extended LDAP Test Plan +
+
+

+ +In this section, you will learn how to create a basic Test Plan to test an LDAP +server. +

+

+ +As the Extended LDAP Sampler is highly configurable, this also means that it takes +some time to build a correct testplan. You can however tune it exactly up to your +needs. + +

+

+ +You will create four users that send requests for four tests on the LDAP server.Also, you will tell +the users to run their tests twice. So, the total number of requests is (4 users) x (4 requests) x +repeat 2 times) = 32 LDAP requests. To construct the Test Plan, you will use the following elements: +
+ + + + +Thread Group + +, +
+ + + +Adding LDAP Extended Request Defaults +, +
+ + + +Adding LDAP Requests +, and +
+ + + +Adding a Listener to View/Store the Test Results + + +

+

+ +This example assumes that the LDAP Server is installed in your Local machine. + +

+

+ +For the less experienced LDAP users, I build a + +small +LDAP tutorial + + which shortly explains +the several LDAP operations that can be used in building a complex testplan. + +

+

+ +Take care when using LDAP special characters in the distinghuished name, in that case (eg, you want to use a + sign in a +distinghuished name) you need to escape the character by adding an "\" sign before that character. +extra exeption: if you want to add a \ character in a distinguished name (in an add or rename operation), you need to use 4 backslashes. +examples: +cn=dolf\+smits to add/search an entry with the name like cn=dolf+smits +cn=dolf \\ smits to search an entry with the name cn=dolf \ smits +cn=c:\\\\log.txt to add an entry with a name like cn=c:\log.txt + +

+ + + + +
+ +9b.1 Adding Users + +
+
+

+ +The first step you want to do with every JMeter Test Plan is to add a Thread Group element. +The Thread Group tells JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. + +

+

+ +Go ahead and add the ThreadGroup element by first selecting the Test Plan, clicking your +right mouse button to get the Add menu, and then select Add-->ThreadGroup. You should now see the +Thread Group element under Test Plan. If you do not see the element, then "expand" the Test Plan tree by +clicking on the Test Plan element. + +

+

+ + +


+ +Figure 9b.1. Thread Group with Default Values +

+ + + +

+
+

+ + + + +
+ +9b.2 Adding LDAP Extended Request Defaults + +
+
+

+ +Begin by selecting the Thread Group element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element -->LDAP Extended Request Defaults. Then, +select this new element to view its Control Panel. + +

+

+ +Like most JMeter elements, the LDAP Extended Request Defaults Control Panel has a name +field that you can modify. In this example, leave this field with the default value. + +

+

+


+
+ + + Figure 9b.2 LDAP Defaults for our Test Plan +

+ + +

+

+ + For each of the different operations, some default values can be filled in. + In All cases, when a default is filled in, this is used for the LDAP extended requests. + For each requst, you can override the defaults by filling in the values in the LDAP extended request sampler. + When no valueis entered which is necesarry for a test, the test will fail in an unpredictable way! + +

+
+

+ + + + +
+ +9b.3 Adding LDAP Requests + +
+
+

+ +In our Test Plan, we want to use all 8 LDAP requests. + +

+
    + + +
  1. + +Thread bind + +
  2. + + +
  3. + +Search Test + +
  4. + + +
  5. + +Compare Test + +
  6. + + +
  7. + +Single bind/unbind Test + +
  8. + + +
  9. + +Add Test + +
  10. + + +
  11. + +Modify Test + +
  12. + + +
  13. + +Delete Test + +
  14. + + +
  15. + +Rename entry (moddn) + +
  16. + + +
  17. + +Thread unbind + +
  18. + + +
+

+ +JMeter sends requests in the order that you add them to the tree. + +

+

+ +Adding a requests always start by: +
+ + +Adding the LDAP Extended Request to the Thread Group element (Add --> +Sampler --> LDAP Ext Request). Then, select the LDAP Ext Request element in the tree +and edit the following properties. +

+ + + + +
+ +9b.3.1 Adding a Thread bind Request + +
+
+

+ + +

    + + +
  1. + +Select the "Thread bind" button. + +
  2. + + +
  3. + +enter the hostname value from the LDAP server in the Servername field + +
  4. + + +
  5. + +Enter the portnumber from the LDAP server (389) in the port field + +
  6. + + +
  7. + +(optional) enter the baseDN in the DN field, this baseDN will be used as thestarting point for searches, add, deletes etc. +
    + + +take care that this must be the uppermost shared level for all your request, eg When all information is stored under ou=people, dc=siemens, dc=com, you can use this value in the basedn. +
    + + +You cannot search or rename anymore in the subtree ou=users,dc=siemens,dc=com! +
    + + +If you need to search or rename objects in both subtrees, use the common denominator (dc=siemens,dc=com) as the baseDN. + +
  8. + + +
  9. + +(Optional) enter the distinghuised name from the user you want to use for authentication. +When this field is kept empty, an anonymous bind will be established. + +
  10. + + +
  11. + +(optional) Enter the password for the user you want to authenticate with, an empty password will also lead to an anonymous bind. + +
  12. + + +
+ + +

+

+ + +


+ +Figure 9b.3.1. Thread Bind example +

+ + +

+
+

+ + + + +
+ +9b.3.2 Adding a search Request + +
+
+

+ + +

    + + +
  1. + +Select the "Search Test" button. + +
  2. + + +
  3. + +(Optional) enter the searchbase under which you want to perform the search, relative to the basedn, used in the thread bind request. +
    + + +When left empty, the basedn is used as a search base, this files is important if you want to use a "base-entry" or "one-level" search (see below) + +
  4. + + +
  5. + +Enter the searchfilter, any decent LDAP serach filter will do, but for now, use something simple, like cn=john doe + +
  6. + + +
  7. + +(optional) enter the scope in the scope field, it has three options: + +
      + + +
    1. +Base level, Enter the value 0 +
      + +only the given searchbase is used, only for checking attributes or existence. + +
    2. + + +
    3. +One level, Enter the value 1 +
      + +Only search in one level below given searchbase is used + +
    4. + + +
    5. +Subtree, Enter the value 2 +
      + + Searches for object at any point below the given basedn + +
    6. +
    + + +
  8. + + +
  9. + +(Optional) Sizelimit, specifies the maximun number of returned entries, + +
  10. + + +
  11. + +(optional) Timelimit, psecifies the maximum number of miliseconds, the SERVER can use for performing the search. it is NOT the maximun time the application will wait! +
    + + +When a very large returnset is returned, from a very fast server, over a very slow line, you may have to wait for ages for the completion of the search request, but this parameter will not influence this. + +
  12. + + +
  13. +(Optional) Attributes you want in the search answer. This can be used to limit the size of the answer, especially when an onject has very large attributes (like jpegPhoto). There are three possibilities: + +
      +
    1. +Leave empty (the default setting must also be empty) This will return all attributes. + +
    2. + + +
    3. +Put in one empty value (""), it will request a non-existent attributes, so in reality it returns no attributes + +
    4. + + +
    5. +Put in the attributes, seperated by a semi-colon. It will return only the requested attributes + +
    6. +
    +
  14. + + +
  15. + +(Optional) Return object, possible values are "true" and "false". True will return all java-object attributes, it will add these to the requested attributes, as specified above. +
    + + +false will mean no java-object attributes will be returned. + +
  16. + + +
  17. + +(Optional) Dereference aliases. possible values "true" and "false". True will mean it will follow references, false says it will not. + +
  18. + + +
+ + +

+

+ + +


+ +Figure 9b.3.2. search request example +

+ + +

+
+

+ + + + +
+ +9b.3.3 Adding a Compare Request + +
+
+

+ + +

    + + +
  1. + +Select the "Compare" button. + +
  2. + + +
  3. + +enter the entryname form the object on which you want the compare operation to work, relative to the basedn, eg "cn=john doe,ou=people" + +
  4. + + +
  5. + +Enter the compare filter, this must be in the form "attribute=value", eg "mail=John.doe@siemens.com" + +
  6. + + +
+ + +

+

+ + +


+ +Figure 9b.3.3. Compare example +

+ + +

+
+

+ + + + +
+ +9b.3.4 Adding a Single bind/unbind + +
+
+

+ + +

    + + +
  1. + +Select the "Single bind/unbind" button. + +
  2. + + +
  3. + +Enter the FULL distinghuised name from the user you want to use for authentication. +
    + + +eg. cn=john doe,ou=people,dc=siemens,dc=com +When this field is kept empty, an anonymous bind will be established. + +
  4. + + +
  5. + +Enter the password for the user you want to authenticate with, an empty password will also lead to an anonymous bind. + +
  6. + + +
  7. + +Take care: This single bind/unbind is in reality two seperate operations but cannot easily be split! + +
  8. + + +
+ + +

+

+ + +


+ +Figure 9b.3.4. Single bind/unbind example +

+ + +

+
+

+ + + + +
+ +9b.3.5 Adding an Add Request + +
+
+

+ + +

    + + +
  1. + +Select the "Add" button. + +
  2. + + +
  3. + +Enter the distinghuised name for the object to add, relative to the basedn. + +
  4. + + +
  5. + +Add a line in the "add test" table, fill in the attribute and value. +
    + + +When you need the same attribute more than once, just add a new line, add the attribute again, and a different value. +
    + + +All necessary attributes and values must be specified to pass the test, see picture! +
    + + +(sometimes the server adds the attribute "objectClass=top", this might give a problem. + +
  6. + + +
+ + +

+

+ + +


+ +Figure 9b.3.5. Add request example +

+ + +

+
+

+ + + + +
+ +9b.3.6 Adding a Modify Request + +
+
+

+ + +

    + + +
  1. + +Select the "Modify test" button. + +
  2. + + +
  3. + +Enter the distinghuised name for the object to modify, relative to the basedn. + +
  4. + + +
  5. + +Add a line in the "modify test" table, with the "add" button. + +
  6. + + +
  7. + +You need to enter the attribute you want to modify, (optional) a value, and the opcode. The meaning of this opcode: + +
      +
    1. +add +
      + + this will mean that the attribute value (not optional in this case) willbe added to the attribute. +
      + + +When the attribute is not existing, it will be created and the value added +
      + + +When it is existing, and defined multi-valued, the new value is added. +
      + + +when it is existing, but single valued, it will fail. +
    2. + + +
    3. +replace +
      + + +This will overwrite the attribute with the given new value (not optional here) +
      + + +When the attribute is not existing, it will be created and the value added +
      + + +When it is existing, old values are removed, the new value is added. +
    4. + + +
    5. +delete +
      + + +When no value is given, all values will be removed +
      + + +When a value is given, only that value will be removed +
      + + + when the given value is not existing, the test will fail + +
    6. +
    + + +
  8. + + +
  9. + +(Optional) Add more modifications in the "modify test" table. +
    + + +All modifications which are specified must succeed, to let the modification test pass. When one modification fails, NO modifications at all will be made and the entry will remain unchanged. + +
  10. + + +
+ + +

+

+ + +


+ +Figure 9b.3.6. Modify example +

+ + +

+
+

+ + + + +
+ +9b.3.7 Adding a Delete Request + +
+
+

+ + +

    + + +
  1. + +Select the "Delete" button. + +
  2. + + +
  3. + +enter the name of the entry, relative to the baseDN, in the Delete-Field. +
    + + +that is, if you want to remove "cn=john doe,ou=people,dc=siemens,dc=com", and you set the baseDN to "dc=siemens,dc=com", +you need to enter "cn=john doe,ou=people" in the Delete-field. + +
  4. + + +
+ + +

+

+ + +


+ +Figure 9b.3.7. Delete example +

+ + +

+
+

+ + + + +
+ +9b.3.8 Adding a Rename Request (moddn) + +
+
+

+ + +

    + + +
  1. + +Select the "Rename Entry" button. + +
  2. + + +
  3. + +enter the name of the entry, relative to the baseDN, in the "old entry name-Field". +
    + + +that is, if you want to rename "cn=john doe,ou=people,dc=siemens,dc=com", and you set the baseDN to "dc=siemens,dc=com", +you need to enter "cn=john doe,ou=people" in the old entry name-field. + +
  4. + + +
  5. + +enter the new name of the entry, relative to the baseDN, in the "new distinghuised name-Field". +
    + + +whne you only change the RDN, it will simply rename the entry +
    + + +when you also add a differten subtree, eg you change from cn=john doe,ou=people to cn=john doe,ou=users, it will move the entry. +You can also move a complete subtree (If your LDAP server supports this!!!!), eg ou=people,ou=retired, to ou=oldusers,ou=users, this will move the complete subtee, plus all retired people in the subtree to the new place in the tree. + +
  6. + + +
+ + +

+

+ + +


+ +Figure 9b.3.8. Rename example +

+ + +

+
+

+ + + + +
+ +9b.3.9 Adding an unbind Request + +
+
+

+ + +

    + + +
  1. + +Select the "Thread unbind" button. +This will be enough as it just closes the current connection. +The information which is needed is already known by the system + +
  2. +
+ + +

+

+ + +


+ +Figure 9b.3.9. Unbind example +

+ + +

+
+

+
+

+ + + + +
+ +9b.4 Adding a Listener to View/Store the Test Results + +
+
+

+ +The final element you need to add to your Test Plan is a Listener. + This element is responsible for storing all of the results of your LDAP +requests in a file and presenting a visual model of the data.Select the Thread group +element and add a View Results Tree (Add --> Listener -->View Results Tree) + +

+

+ + +


+ +Figure 9b.4. View result Tree Listener +

+ + +

+

+ +In this listener you have three tabs to view, the sampler result, the request and the response data. + +

    + + +
  1. + +The sampler result just contains the response time, the returncode and return message + +
  2. + + +
  3. + +The request gives a short description of the request that was made, in practice no relevant information +is contained here. + +
  4. + + +
  5. + +The response data contains the full details of the sent request, as well the full details of the received answer, +this is given in a (self defined) xml-style. + + +The full description can be found here. + + + +
  6. + + +
+ + +

+
+

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/build-monitor-test-plan.html b/ApacheJmeter/docs/usermanual/build-monitor-test-plan.html new file mode 100644 index 0000000..249489a --- /dev/null +++ b/ApacheJmeter/docs/usermanual/build-monitor-test-plan.html @@ -0,0 +1,494 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Building a Monitor Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +13. Building a Monitor Test Plan +
+
+

+In this section, you will learn how to create a + + +Test Plan + + to monitor webservers. Monitors +are useful for a stress testing and system management. Used with stress +testing, the monitor provides additional information about server performance. +It also makes it easier to see the relationship between server performance +and response time on the client side. As a system administration tool, the +monitor provides an easy way to monitor multiple servers from one console. +The monitor was designed to work with the status servlet in Tomcat 5. In +theory, any servlet container that supports JMX (Java Management Extension) +can port the status servlet to provide the same information. +

+

+For those who want to use the monitor with other servlet or EJB containers, +Tomcat's status servlet should work with other containers for the memory +statistics without any modifications. To get thread information, you will +need to change the MBeanServer lookup to retrieve the correct MBeans. +

+
+

+

+ + + + +
+ +13.1 Adding A Server +
+
+

+The first step is to add a + +Thread Group + + +element. The Thread Group tells JMeter the number of threads you want. Always use +1, since we are using JMeter as a monitor. This is very important for those not +familiar with server monitors. As a general rule, using multiple threads for a +single server is bad and can create significant stress. + +

+

+Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup. +

+

+You should now see the Thread Group element under Test Plan. If you do not +see the element, "expand" the Test Plan tree by clicking on the Test Plan element. +

+


+ +Figure 13.1. Thread Group with Default Values +

+

+Change the loop count to forever (or some large number) so that enough samples are generated. +

+
+

+

+ + + + +
+ +13.2 HTTP Auth Manager +
+
+

+Add the +HTTP Authorization Manager + to the Thread Group element +(Add --> Config element --> HTTP Authorization Manager). Enter the username +and password for your webserver. Important note: the monitor only works with +Tomcat5 build 5.0.19 and newer. For instructions on how to setup Tomcat, please +refer to tomcat 5 documentation. +

+
    + + +
  1. + leave the base URL blank +
  2. + + +
  3. + enter the username +
  4. + + +
  5. + enter the password +
  6. + + +
+
+

+

+ + + + +
+ +13.3 Adding HTTP Request +
+
+

+Add the +HTTP Request + to the Thread Group element +(Add --> Sampler --> HTTP Request). Then, select the HTTP Request element +in the tree and edit the following properties): + +

    + + +
  1. +Change the Name field to "Server Status". +
  2. + + +
  3. +Enter the IP address or Hostname +
  4. + + +
  5. +Enter the port number +
  6. + + +
  7. +Set the Path field to "/manager/status" if you're using Tomcat. +
  8. + + +
  9. +Add a request parameter named "XML" in uppercase. Give it a value of +"true" in lowercase. +
  10. + + +
  11. +Check "Use as Monitor" at the bottom of the sampler +
  12. + + +
+ + +

+
+

+

+ + + + +
+ +13.4 Adding Constant Timer +
+
+

+Add a timer to this thread group (Add --> Timer --> Constant Timer). +Enter 5000 milliseconds in the "Thread Delay" box. In general, using intervals shorter +than 5 seconds will add stress to your server. Find out what is an acceptable interval +before you deploy the monitor in your production environment. +

+
+

+

+ + + + +
+ +13.5 Adding a Listener to Store the Results +
+
+

+If you want to save the raw results from the server, add a simple data + + +Listener + +. If you want to save the + calculated statistics, enter a filename in the listener. If you want to save both + the raw data and statistics, make sure you use different filenames. +

+

+Select the thread group element and add a +Simple Data Writer + listener +(Add --> Listener --> Simple Data Writer). Next, you need to specify a directory +and filename of the output file. You can either type it into the filename field, or +select the Browse button and browse to a directory and then enter a filename. +

+
+

+

+ + + + +
+ +13.6 Adding Monitor Results +
+
+

+Add the + +Listener + + by selecting the +test plan element (Add --> Listener -- > Monitor Results). + +
+ + +By default, the Listener will select the results from the first connector in the sample response. +The Connector prefix field can be used to select a different connector. +If specified, the Listener will choose the first connector which matches the prefix. +If no match is found, then the first connector is selected. + +

+

+There are two tabs in +the monitor results listener. The first is the "Health", which displays the status of +the last sample the monitor received. The second tab is "Performance", which shows a +historical view of the server's performance. + +

+


+

+

+A quick note about how health is calculated. Typically, a server will crash if +it runs out of memory, or reached the maximum number of threads. In the case of +Tomcat 5, once the threads are maxed out, requests are placed in a queue until a +thread is available. The relative importance of threads vary between containers, so +the current implementation uses 50/50 to be conservative. A container that is more +efficient with thread management might not see any performance degradation, but +the used memory definitely will show an impact. +

+


+

+

+The performance graph shows for different lines. The free memory line shows how +much free memory is left in the current allocated block. Tomcat 5 returns the maximum +memory, but it is not graphed. In a well tuned environment, the server should never +reach the maximum memory. +

+

+Note the graph has captions on both sides of the graph. On the left is percent and +the right is dead/healthy. If the memory line spikes up and down rapidly, it could +indicate memory thrashing. In those situations, it is a good idea to profile the +application with Borland OptimizeIt or JProbe. What you want to see is a regular +pattern for load, memory and threads. Any erratic behavior usually indicates poor +performance or a bug of some sort. +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/build-test-plan.html b/ApacheJmeter/docs/usermanual/build-test-plan.html new file mode 100644 index 0000000..a4bb48e --- /dev/null +++ b/ApacheJmeter/docs/usermanual/build-test-plan.html @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Building a Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +3. Building a Test Plan +
+
+

+A test plan describes a series of steps JMeter will execute when run. A complete +test plan will consist of one or more Thread Groups, logic conrollers, sample generating +controllers, listeners, timers, assertions, and configuration elements. + +

+ + + + +
+ +3.1 Adding and Removing Elements + +
+
+

+Adding + +elements to a test plan + + can be done by right-clicking on an element in the +tree, and choosing a new element from the "add" list. Alternatively, elements can +be loaded from file and added by choosing the "merge" or "open" option. +

+

+To remove an element, make sure the element is selected, right-click on the element, +and choose the "remove" option. +

+
+

+ + + + +
+ +3.2 Loading and Saving Elements + +
+
+

+To load an element from file, right click on the existing tree element to which +you want to add the loaded element, and select the "merge" option. Choose the file where +your elements are saved. JMeter will merge the elements into the tree. +

+

+To save tree elements, right click on an element and choose the "Save Selection As ..." option. +JMeter will save the element selected, plus all child elements beneath it. In this way, +you can save test tree fragments and individual elements for later use. +

+

+ + +
The workbench is not automatically saved with the test plan, but it can be saved separately as above. +
+

+
+

+ + + + +
+ +3.3 Configuring Tree Elements + +
+
+

+Any element in the test tree will present controls in JMeter's right-hand frame. These +controls allow you to configure the behavior of that particular test element. What can be +configured for an element depends on what type of element it is. +

+

+ + +
The Test Tree itself can be manipulated by dragging and dropping components around the test tree. +
+

+
+

+ + + + +
+ +3.4 Saving the Test Plan + +
+
+

+Although it is not required, we recommend that you save the Test Plan to a +file before running it. To save the Test Plan, select "Save" or "Save Test Plan As ..." from the +File menu (with the latest release, it is no longer necessary to select the +Test Plan element first). +

+

+ + +
JMeter allows you to save the entire Test Plan tree or +only a portion of it. To save only the elements located in a particular "branch" +of the Test Plan tree, select the Test Plan element in the tree from which to start +the "branch", and then click your right mouse button to access the "Save Selection As ..." menu item. +Alternatively, select the appropriate Test Plan element and then select "Save Selection As ..." from +the Edit menu. + +
+

+
+

+ + + + +
+ +3.5 Running a Test Plan + +
+
+

+To run your test plan, choose "Start" (Control + r) from the "Run" menu item. +When JMeter is running, it shows a small green box at the right hand end of the section just under the menu bar. +You can also check the "Run" menu. +If "Start" is disabled, and "Stop" is enabled, +then JMeter is running your test plan (or, at least, it thinks it is). +

+

+ +The numbers to the left of the green box are the number of active threads / total number of threads. +These only apply to a locally run test; they do not include any threads started on remote systems when using client-server mode. + +

+
+

+ + + + +
+ +3.6 Stopping a Test + +
+
+

+ +There are two types of stop command available from the menu: + +

    + + +
  • +Stop (Control + '.') - stops the threads immediately if possible. +In Versions of JMeter after 2.3.2, many samplers are now Interruptible which means that active samples can be terminated early. +The stop command will check that all threads have stopped within the default timeout, which is 5000 ms = 5 seconds. +[This can be changed using the JMeter property + +jmeterengine.threadstop.wait + +] +If the threads have not stopped, then a message is displayed. +The Stop command can be retried, but if it fails, then it is necessary to exit JMeter to clean up. + +
  • + + +
  • +Shutdown (Control + ',')- requests the threads to stop at the end of any current work. +Will not interrupt any active samples. +The modal shutdown dialog box will remain active until all threads have stopped. +
  • + + +
+ +Versions of JMeter after 2.3.2 allow a Stop to be initiated if Shutdown is taking too long. +Close the Shutdown dialog box and select Run/Stop, or just press Control + '.'. + +

+

+ +When running JMeter in non-GUI mode, there is no Menu, and JMeter does not react to keystrokes such as Control + '.'. +So in versions after 2.3.2, JMeter non-GUI mode will listen for commands on a specific port +(default 4445, see the JMeter property + +jmeterengine.nongui.port + +). +In versions after 2.4, JMeter supports automatic choice of an alternate port if the default port is being used +(for example by another JMeter instance). In this case, JMeter will try the next higher port, continuing until +it reaches the JMeter property + +jmeterengine.nongui.maxport + +) which defaults to 4455. +If + +maxport + + is less than or equal to + +port + +, port scanning will not take place. +Note that JMeter 2.4 and earlier did not set up the listener for non-GUI clients, only non-GUI standalone tests; +this has been fixed. + +
+ + +The chosen port is displayed in the console window. + +
+ + +The commands currently supported are: + +

    + + +
  • +Shutdown - graceful shutdown +
  • + + +
  • +StopTestNow - immediate shutdown +
  • + + +
+ +These commands can be sent by using the + +shutdown[.cmd|.sh] + + or + +stoptest[.cmd|.sh] + + script +respectively. The scripts are to be found in the JMeter + +bin + + directory. +The commands will only be accepted if the script is run from the same host. + +

+
+

+ + + + +
+ +3.7 Error reporting + +
+
+

+ +JMeter reports warnings and errors to the jmeter.log file, as well as some information on the test run itself. +Just occasionally there may be some errors that JMeter is unable to trap and log; these will appear on the command console. +If a test is not behaving as you expect, please check the log file in case any errors have been reported (e.g. perhaps a syntax error in a function call). + +

+

+ +Sampling errors (e.g. HTTP 404 - file not found) are not normally reported in the log file. +Instead these are stored as attributes of the sample result. +The status of a sample result can be seen in the various different Listeners. + +

+
+

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/build-web-test-plan.html b/ApacheJmeter/docs/usermanual/build-web-test-plan.html new file mode 100644 index 0000000..c52c07e --- /dev/null +++ b/ApacheJmeter/docs/usermanual/build-web-test-plan.html @@ -0,0 +1,590 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Building a Web Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +5. Building a Web Test Plan +
+
+

+In this section, you will learn how to create a basic + + +Test Plan + + to test a Web site. You will +create five users that send requests to two pages on the JMeter Web site. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (5 users) x (2 requests) x (repeat 2 times) = 20 HTTP requests. To +construct the Test Plan, you will use the following elements: + + +Thread Group + +, + +HTTP Request +, + +HTTP Request Defaults +, and + +Graph Results +. +

+

+For a more advanced Test Plan, see + + +Building an Advanced Web Test Plan + +. +

+
+

+

+ + + + +
+ +5.1 Adding Users +
+
+

+The first step you want to do with every JMeter Test Plan is to add a + + +Thread Group + + element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. +

+

+Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup. +

+

+You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element. +

+

+Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 5.1 +below) +

+


+ +Figure 5.1. Thread Group with Default Values +

+

+Start by providing a more descriptive name for our Thread Group. In the name +field, enter JMeter Users. +

+

+Next, increase the number of users (called threads) to 5. +

+

+In the next field, the Ramp-Up Period, leave the the default value of 1 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users. +

+

+Finally enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. If you enter a loop count value of 1, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox. +

+

+ + +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). +
+

+

+See Figure 5.2 for the completed JMeter Users Thread Group. +

+


+ +Figure 5.2. JMeter Users Thread Group +

+
+

+

+ + + + +
+ +5.2 Adding Default HTTP Request Properties +
+
+

+Now that we have defined our users, it is time to define the tasks that they +will be performing. In this section, you will specify the default settings +for your HTTP requests. And then, in section 5.3, you will add HTTP Request +elements which use some of the default settings you specified here. +

+

+Begin by selecting the JMeter Users (Thread Group) element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element --> HTTP Request +Defaults. Then, select this new element to view its Control Panel (see Figure 5.3). + +

+


+ +Figure 5.3. HTTP Request Defaults +

+

+ +Like most JMeter elements, the +HTTP Request Defaults + Control +Panel has a name field that you can modify. In this example, leave this field with +the default value. +

+

+Skip to the next field, which is the Web Server's Server Name/IP. For the +Test Plan that you are building, all HTTP requests will be sent to the same +Web server, jmeter.apache.org. Enter this domain name into the field. +This is the only field that we will specify a default, so leave the remaining +fields with their default values. +

+

+ + +
The HTTP Request Defaults element does not tell JMeter +to send an HTTP request. It simply defines the default values that the +HTTP Request elements use. +
+

+

+See Figure 5.4 for the completed HTTP Request Defaults element +

+


+ +Figure 5.4. HTTP Defaults for our Test Plan +

+
+

+

+ + + + +
+ +5.3 Adding Cookie Support +
+
+

+Nearly all web testing should use cookie support, unless your application +specifically doesn't use cookies. To add cookie support, simply add an + +HTTP Cookie Manager + to each + +Thread +Group + + in your test plan. This will ensure that each thread gets its own +cookies, but shared across all +HTTP Request + objects. +

+

+To add the +HTTP Cookie Manager +, simply select the + + +Thread Group + +, and choose Add --> +Config Element --> HTTP +Cookie Manager, either from the Edit Menu, or from the right-click pop-up menu. +

+
+

+

+ + + + +
+ +5.4 Adding HTTP Requests +
+
+

+In our Test Plan, we need to make two HTTP requests. The first one is for the +JMeter home page (http://jmeter.apache.org/), and the second one is for the +Changes page (http://jmeter.apache.org/changes.html). +

+

+ + +
JMeter sends requests in the order that they appear in the tree. +
+

+

+Start by adding the first +HTTP Request + +to the JMeter Users element (Add --> Sampler --> HTTP Request). +Then, select the HTTP Request element in the tree and edit the following properties +(see Figure 5.5): + +

    + + +
  1. +Change the Name field to "Home Page". +
  2. + + +
  3. +Set the Path field to "/". Remember that you do not have to set the Server +Name field because you already specified this value in the HTTP Request Defaults +element. +
  4. + + +
+ + +

+


+ +Figure 5.5. HTTP Request for JMeter Home Page +

+

+Next, add the second HTTP Request and edit the following properties (see +Figure 5.6: + +

    + + +
  1. +Change the Name field to "Changes". +
  2. + + +
  3. +Set the Path field to "/changes.html". +
  4. + + +
+ + +

+


+ +Figure 5.6. HTTP Request for JMeter Changes Page +

+
+

+

+ + + + +
+ +5.5 Adding a Listener to View Store the Test Results +
+
+

+The final element you need to add to your Test Plan is a + + +Listener + +. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data. +

+

+Select the JMeter Users element and add a +Graph Results + listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename. +

+


+ +Figure 5.7. Graph Results Listener +

+
+

+

+ + + + +
+ +5.6 Logging in to a web-site +
+
+

+ +It's not the case here, but some web-sites require you to login before permitting you to perform certain actions. +In a web-browser, the login will be shown as a form for the user name and password, +and a button to submit the form. +The button generates a POST request, passing the values of the form items as parameters. + +

+

+ +To do this in JMeter, add an HTTP Request, and set the method to POST. +You'll need to know the names of the fields used by the form, and the target page. +These can be found out by inspecting the code of the login page. +[If this is difficult to do, you can use the + +JMeter Proxy Recorder + + to record the login sequence.] +Set the path to the target of the submit button. +Click the Add button twice and enter the username and password details. +Sometimes the login form contains additional hidden fields. +These will need to be added as well. + +

+


+ +Figure 5.8. Sample HTTP login request +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/build-ws-test-plan.html b/ApacheJmeter/docs/usermanual/build-ws-test-plan.html new file mode 100644 index 0000000..0a51498 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/build-ws-test-plan.html @@ -0,0 +1,477 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Building a WebService Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +10. Building a WebService Test Plan +
+
+

+In this section, you will learn how to create a + + +Test Plan + + to test a WebService. You will +create five users that send requests to One page. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (5 users) x (1 requests) x (repeat 2 times) = 10 HTTP requests. To +construct the Test Plan, you will use the following elements: + + +Thread Group + +, + +WebService(SOAP) Request +, and + +Graph Results +. +

+

+If the sampler appears to be getting an error from the webservice, double check the +SOAP message and make sure the format is correct. In particular, make sure the +xmlns attributes are exactly the same as the WSDL. If the xml namespace is +different, the webservice will likely return an error. + + +Xmethods + + contains a list of public webservice for those who want to test +their test plan. +

+
+

+

+ + + + +
+ +10.1 Adding Users +
+
+

+The first step you want to do with every JMeter Test Plan is to add a + + +Thread Group + + element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. +

+

+Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup. +

+

+You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element. +

+

+Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure 10.1 +below) +

+


+ +Figure 10.1. Thread Group with Default Values +

+

+Start by providing a more descriptive name for our Thread Group. In the name +field, enter Jakarta Users. +

+

+Next, increase the number of users (called threads) to 10. +

+

+In the next field, the Ramp-Up Period, leave the the default value of 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users. +

+

+Finally, clear the checkbox labeled "Forever", and enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox. +

+

+ + +
In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). +
+

+

+See Figure 10.2 for the completed Jakarta Users Thread Group. +

+


+ +Figure 10.2. Jakarta Users Thread Group +

+
+

+

+ + + + +
+ +10.2 Adding WebService Requests +
+
+

+In our Test Plan, we will use a .NET webservice. Since you're using +the webservice sampler, we won't go into the details of writing a +webservice. If you don't know how to write a webservice, google for +webservice and familiarize yourself with writing webservices for +Java and .NET. It should be noted there is a significant difference +between how .NET and Java implement webservices. The topic is too +broad to cover in the user manual. Please refer to other sources to +get a better idea of the differences. +

+

+ + +
JMeter sends requests in the order that they appear in the tree. +
+

+

+Start by adding the sampler +WebService(SOAP) Request + +to the Jakarta Users element (Add --> Sampler --> WebService(SOAP) Request). +Then, select the webservice Request element in the tree and edit the following properties +(see Figure 10.5): + +

    + + +
  1. +Change the Name field to "WebService(SOAP) Request". +
  2. + + +
  3. +Enter the WSDL URL and click "Load WSDL". +
  4. + + +
+ + +

+


+ +Figure 10.3. Webservice Request +

+

+If the WSDL file was loaded correctly, the "Web Methods" drop down should +be populated. If the drop down remains blank, it means there was a problem +getting the WSDL. You can test the WSDL using a browser that reads XML. +For example, if you're testing an IIS webservice the URL will look like this: +http://localhost/myWebService/Service.asmx?WSDL. At this point, SOAPAction, URL +and SOAP Data should be blank. +

+

+Next, select the web method and click "Configure". The sampler should +populate the "URL" and "SOAPAction" text fields. Assuming the WSDL is valid, +the correct soap action should be entered. + +

+

+The last step is to paste the SOAP message in the "SOAP/XML-RPC Data" +text area. You can optionally save the soap message to a file and browse +to the location. For convienance, there is a third option of using a +message folder. The sampler will randomly select files from a given +folder and use the text for the soap message. +

+

+If you do not want JMeter to read the response from the SOAP Webservice, +uncheck "Read Soap Responses." If the test plan is intended to stress test +a webservice, the box should be unchecked. If the test plan is a functional +test, the box should be checked. When "Read Soap Responses" is unchecked, +no result will be displayed in view result tree or view results in table. +

+

+An important note on the sampler. It will automatically use the proxy host +and port passed to JMeter from command line, if those fields in the sampler are +left blank. If a sampler has values in the proxy host and port text field, it +will use the ones provided by the user. If no host or port are provided and +JMeter wasn't started with command line options, the sampler will fail +silently. This behavior may not be what users expect. +

+

+ +Note: + + If you're using Cassini webserver, it does not work correctly and is not a reliable webserver. Cassini is meant to be a simple example and isn't a full blown webserver like IIS. Cassini does not close connections correctly, which causes JMeter to hang or not get the response contents. +

+

+Currently, only .NET uses SOAPAction, so it is normal to have a blank SOAPAction for all other webservices. The list includes JWSDP, Weblogic, Axis, The Mind Electric Glue, and gSoap. +

+
+

+

+ + + + +
+ +10.3 Adding a Listener to View Store the Test Results +
+
+

+The final element you need to add to your Test Plan is a + + +Listener + +. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data. +

+

+Select the Jakarta Users element and add a +Graph Results + listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename. +

+


+ +Figure 10.7. Graph Results Listener +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/component_reference.html b/ApacheJmeter/docs/usermanual/component_reference.html new file mode 100644 index 0000000..a01abaa --- /dev/null +++ b/ApacheJmeter/docs/usermanual/component_reference.html @@ -0,0 +1,17819 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Component Reference + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + + +
+ +18.0 Introduction +
+
+ + + +

+ + + +

+ + +

+ + +
+ Several test elements use JMeter properties to control their behaviour. + These properties are normally resolved when the class is loaded. + This generally occurs before the test plan starts, so it's not possible to change the settings by using the __setProperty() function. + +
+

+ + +

+ + +

+ + +
+
+

+

+ + + + +
+ +18.1 Samplers +
+
+ + + +

+ + Samplers perform the actual work of JMeter. + Each sampler (except Test Action) generates one or more sample results. + The sample results have various attributes (success/fail, elapsed time, data size etc) and can be viewed in the various listeners. + +

+ + +
+ + + + +
+ +

+18.1.1 FTP Request +

+
+
+ +This controller lets you send an FTP "retrieve file" or "upload file" request to an FTP server. +If you are going to send multiple requests to the same FTP server, consider +using a +FTP Request Defaults + Configuration +Element so you do not have to enter the same information for each FTP Request Generative +Controller. When downloading a file, it can be stored on disk (Local File) or in the Response Data, or both. + +

+ +Latency is set to the time it takes to login (versions of JMeter after 2.3.1). + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
Server Name or IPDomain name or IP address of the FTP server. + +Yes +
PortPort to use. If this is >0, then this specific port is used, otherwise JMeter uses the default FTP port. + +No +
Remote File:File to retrieve or name of destination file to upload. + +Yes +
Local File:File to upload, or destination for downloads (defaults to remote file name). + +Yes, if uploading (*) +
Local File Contents:Provides the contents for the upload, overrides the Local File property. + +Yes, if uploading (*) +
get(RETR) / put(STOR)Whether to retrieve or upload a file. + +Yes +
Use Binary mode ?Check this to use Binary mode (default Ascii) + +Yes +
Save File in Response ? + Whether to store contents of retrieved file in response data. + If the mode is Ascii, then the contents will be visible in the Tree View Listener. + + +Yes, if downloading +
UsernameFTP account username. + +Usually +
PasswordFTP account password. N.B. This will be visible in the test plan. + +Usually +
+

+

See Also: +

+

+

+
+ + + + +
+ +

+18.1.2 HTTP Request +

+
+
+ + +

+This sampler lets you send an HTTP/HTTPS request to a web server. It + also lets you control whether or not JMeter parses HTML files for images and + other embedded resources and sends HTTP requests to retrieve them. + The following types of embedded resource are retrieved: +

+ + +
    + + +
  • +images +
  • + + +
  • +applets +
  • + + +
  • +stylesheets +
  • + + +
  • +external scripts +
  • + + +
  • +frames, iframes +
  • + + +
  • +background images (body, table, TD, TR) +
  • + + +
  • +background sound +
  • + + +
+ + +

+ + The default parser is htmlparser. + This can be changed by using the property "htmlparser.classname" - see jmeter.properties for details. + +

+ + +

+If you are going to send multiple requests to the same web server, consider + using an +HTTP Request Defaults + + Configuration Element so you do not have to enter the same information for each + HTTP Request. +

+ + + +

+Or, instead of manually adding HTTP Requests, you may want to use + JMeter's +HTTP Proxy Server + to create + them. This can save you time if you have a lot of HTTP requests or requests with many + parameters. +

+ + + +

+ +There are two different screens for defining the samplers: + + + +

    + + +
  • +AJP/1.3 Sampler - uses the Tomcat mod_jk protocol (allows testing of Tomcat in AJP mode without needing Apache httpd) + The AJP Sampler does not support multiple file upload; only the first file will be used. + +
  • + + +
  • +HTTP Request - this has an implementation drop-down box, which selects the HTTP protocol implementation to be used: +
  • + + +
      + + +
    • +Java - uses the HTTP implementation provided by the JVM. + This has some limitations in comparison with the HttpClient implementations - see below. +
    • + + +
    • +HTTPClient3.1 - uses Apache Commons HttpClient 3.1. + This is no longer being developed, and support for this may be dropped in a future JMeter release. +
    • + + +
    • +HTTPClient4 - uses Apache HttpComponents HttpClient 4.x. +
    • + + +
    + + +
+ + +

+ + +

+The Java HTTP implementation has some limitations: +

+ + +
    + + +
  • +There is no control over how connections are re-used. + When a connection is released by JMeter, it may or may not be re-used by the same thread. +
  • + + +
  • +The API is best suited to single-threaded usage - various settings (e.g. proxy) + are defined via system properties, and therefore apply to all connections. +
  • + + +
  • +There is a bug in the handling of HTTPS via a Proxy (the CONNECT is not handled correctly). + See Java bugs 6226610 and 6208335. + +
  • + + +
  • +It does not support virtual hosts. +
  • + + +
+ + +

+Note: the FILE protocol is intended for testing puposes only. + It is handled by the same code regardless of which HTTP Sampler is used. +

+ + +

+If the request requires server or proxy login authorization (i.e. where a browser would create a pop-up dialog box), + you will also have to add an +HTTP Authorization Manager + Configuration Element. + For normal logins (i.e. where the user enters login information in a form), you will need to work out what the form submit button does, + and create an HTTP request with the appropriate method (usually POST) + and the appropriate parameters from the form definition. + If the page uses HTTP, you can use the JMeter Proxy to capture the login sequence. + +

+ + +

+ + In versions of JMeter up to 2.2, only a single SSL context was used for all threads and samplers. + This did not generate the proper load for multiple users. + A separate SSL context is now used for each thread. + To revert to the original behaviour, set the JMeter property: + +

+
+https.sessioncontext.shared=true
+
+
+ + By default, the SSL context is retained for the duration of the test. + In versions of JMeter from 2.5.1, the SSL session can be optionally reset for each test iteration. + To enable this, set the JMeter property: + +
+
+https.use.cached.ssl.context=false
+
+
+ + Note: this does not apply to the Java HTTP implementation. + +

+ + +

+ + JMeter defaults to the SSL protocol level TLS. + If the server needs a different level, e.g. SSLv3, change the JMeter property, for example: + +

+
+https.default.protocol=SSLv3
+
+
+ + +

+ + +

+ + JMeter also allows one to enable additional protocols, by changing the property + +https.socket.protocols + +. + +

+ + +

+If the request uses cookies, then you will also need an + +HTTP Cookie Manager +. You can + add either of these elements to the Thread Group or the HTTP Request. If you have + more than one HTTP Request that needs authorizations or cookies, then add the + elements to the Thread Group. That way, all HTTP Request controllers will share the + same Authorization Manager and Cookie Manager elements. +

+ + + +

+If the request uses a technique called "URL Rewriting" to maintain sessions, + then see section + + +6.1 Handling User Sessions With URL Rewriting + + + for additional configuration steps. +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
Server + Domain name or IP address of the web server. e.g. www.example.com. [Do not include the http:// prefix.] + Note: in JMeter 2.5 (and later) if the "Host" header is defined in a Header Manager, then this will be used + as the virtual host name. + + +Yes, unless provided by HTTP Request Defaults +
PortPort the web server is listening to. Default: 80 + +No +
Connect TimeoutConnection Timeout. Number of milliseconds to wait for a connection to open. + +No +
Response TimeoutResponse Timeout. Number of milliseconds to wait for a response. + +No +
Server (proxy)Hostname or IP address of a proxy server to perform request. [Do not include the http:// prefix.] + +No +
PortPort the proxy server is listening to. + +No, unless proxy hostname is specified +
Username(Optional) username for proxy server. + +No +
Password(Optional) password for proxy server. (N.B. this is stored unencrypted in the test plan) + +No +
ImplementationJava, HttpClient3.1, HttpClient4. + If not specified (and not defined by HTTP Request Defaults), the default depends on the value of the JMeter property + + +jmeter.httpsampler + +, failing that, the Java implementation is used. + +No +
ProtocolHTTP, HTTPS or FILE. Default: HTTP + +No +
MethodGET, POST, HEAD, TRACE, OPTIONS, PUT, DELETE + +Yes +
Content EncodingContent encoding to be used (for POST and FILE) + +No +
Redirect Automatically + Sets the underlying http protocol handler to automatically follow redirects, + so they are not seen by JMeter, and thus will not appear as samples. + Should only be used for GET and HEAD requests. + The HttpClient sampler will reject attempts to use it for POST or PUT. + + +Warning: see below for information on cookie and header handling. + + + + +No +
Follow Redirects + This only has any effect if "Redirect Automatically" is not enabled. + If set, the JMeter sampler will check if the response is a redirect and follow it if so. + The initial redirect and further responses will appear as additional samples. + The URL and data fields of the parent sample will be taken from the final (non-redirected) + sample, but the parent byte count and elapsed time include all samples. + The latency is taken from the initial response (versions of JMeter after 2.3.4 - previously it was zero). + Note that the HttpClient sampler may log the following message: +
+ + + "Redirect requested but followRedirects is disabled" +
+ + + This can be ignored. + +
+ + + In versions after 2.3.4, JMeter will collapse paths of the form '/../segment' in + both absolute and relative redirect URLs. For example http://host/one/../two => http://host/two. + If necessary, this behaviour can be suppressed by setting the JMeter property + + +httpsampler.redirect.removeslashdotdot=false + + + +
+No +
Use KeepAliveJMeter sets the Connection: keep-alive header. This does not work properly with the default HTTP implementation, as connection re-use is not under user-control. + It does work with the Apache HttpComponents HttpClient implementations. + +No +
Use multipart/form-data for HTTP POST + Use a multipart/form-data or application/x-www-form-urlencoded post request + + +No +
Browser-compatible headers + When using multipart/form-data, this suppresses the Content-Type and + Content-Transfer-Encoding headers; only the Content-Disposition header is sent. + + +No +
PathThe path to resource (for example, /servlets/myServlet). If the +resource requires query string parameters, add them below in the +"Send Parameters With the Request" section. + + + +As a special case, if the path starts with "http://" or "https://" then this is used as the full URL. + + + +In this case, the server, port and protocol are ignored; parameters are also ignored for GET and DELETE methods. + + +Yes +
Send Parameters With the RequestThe query string will + be generated from the list of parameters you provide. Each parameter has a + +name + + and + + +value + +, the options to encode the parameter, and an option to include or exclude an equals sign (some applications + don't expect an equals when the value is the empty string). The query string will be generated in the correct fashion, depending on + the choice of "Method" you made (ie if you chose GET or DELETE, the query string will be + appended to the URL, if POST or PUT, then it will be sent separately). Also, if you are + sending a file using a multipart form, the query string will be created using the + multipart form specifications. + + +See below for some further information on parameter handling. + + + +

+ + Additionally, you can specify whether each parameter should be URL encoded. If you are not sure what this + means, it is probably best to select it. If your values contain characters such as & or spaces, or + question marks, then encoding is usually required. +

+
+No +
File Path:Name of the file to send. If left blank, JMeter + does not send a file, if filled in, JMeter automatically sends the request as + a multipart form request. + +

+ + If it is a POST or PUT request and there is a single file whose 'name' attribute (below) is omitted, + then the file is sent as the entire body + of the request, i.e. no wrappers are added. This allows arbitrary bodies to be sent. This functionality is present for POST requests + after version 2.2, and also for PUT requests after version 2.3. + + +See below for some further information on parameter handling. + + + +

+ + +
+No +
Parameter name:Value of the "name" web request parameter. + +No +
MIME TypeMIME type (for example, text/plain). + If it is a POST or PUT request and either the 'name' atribute (below) are omitted or the request body is + constructed from parameter values only, then the value of this field is used as the value of the + content-type request header. + + +No +
Retrieve All Embedded Resources from HTML FilesTell JMeter to parse the HTML file +and send HTTP/HTTPS requests for all images, Java applets, JavaScript files, CSSs, etc. referenced in the file. + See below for more details. + + +No +
Use as monitorFor use with the +Monitor Results + listener. + +No +
Save response as MD5 hash? + If this is selected, then the response is not stored in the sample result. + Instead, the 32 character MD5 hash of the data is calculated and stored instead. + This is intended for testing large amounts of data. + + +No +
Embedded URLs must match: + If present, this must be a regular expression that is used to match against any embedded URLs found. + So if you only want to download embedded resources from http://example.com/, use the expression: + http://example\.com/.* + + +No +
Use concurrent poolUse a pool of concurrent connections to get embedded resources. + +No +
SizePool size for concurrent connections used to get embedded resources. + +No +
Source IP address: + [Only for HTTP Request HTTPClient] + Override the default local IP address for this sample. + The JMeter host must have multiple IP addresses (i.e. IP aliases or network interfaces). + If the property + +httpclient.localaddress + + is defined, that is used for all HttpClient requests. + + +No +
+

+

+ + + +N.B. + + when using Automatic Redirection, cookies are only sent for the initial URL. +This can cause unexpected behaviour for web-sites that redirect to a local server. +E.g. if www.example.com redirects to www.example.co.uk. +In this case the server will probably return cookies for both URLs, but JMeter will only see the cookies for the last +host, i.e. www.example.co.uk. If the next request in the test plan uses www.example.com, +rather than www.example.co.uk, it will not get the correct cookies. +Likewise, Headers are sent for the initial request, and won't be sent for the redirect. +This is generally only a problem for manually created test plans, +as a test plan created using a recorder would continue from the redirected URL. + +

+

+ + + +Parameter Handling: + +
+ + +For the POST and PUT method, if there is no file to send, and the name(s) of the parameter(s) are omitted, +then the body is created by concatenating all the value(s) of the parameters. +Note that the values are concatenated without adding any end-of-line characters. +These can be added by using the __char() function in the value fields. +This allows arbitrary bodies to be sent. +The values are encoded if the encoding flag is set (versions of JMeter after 2.3). +See also the MIME Type above how you can control the content-type request header that is sent. + +
+ + +For other methods, if the name of the parameter is missing, +then the parameter is ignored. This allows the use of optional parameters defined by variables. +(versions of JMeter after 2.3) + +

+
+ +

+Since JMeter 2.6, you have the option to switch to Post Body when a request has only unnamed parameters +(or no parameters at all). +This option is useful in the following cases (amongst others): + +

    + + +
  • +GWT RPC HTTP Request +
  • + + +
  • +JSON REST HTTP Request +
  • + + +
  • +XML REST HTTP Request +
  • + + +
  • +SOAP HTTP Request +
  • + + +
+ +Note that once you leave the Tree node, you cannot switch back to the parameter tab unless you clear the Post Body tab of data. + +

+

+ +In Post Body mode, each line will be sent with CRLF appended, apart from the last line. +To send a CRLF after the last line of data, just ensure that there is an empty line following it. +(This cannot be seen, except by noting whether the cursor can be placed on the subsequent line.) + +

+


+Figure 1 - HTTP Request with one unnamed parameter +

+


+Figure 2 - Confirm dialog to switch +

+


+Figure 3 - HTTP Request using RAW Post body +

+

+ + + +Method Handling: + +
+ + +The POST and PUT request methods work similarly, except that the PUT method does not support multipart requests. +The PUT method body must be provided as one of the following: + +

    + + +
  • +define the body as a file +
  • + + +
  • +define the body as parameter value(s) with no name +
  • + + +
+ +If you define any parameters with a name in either the sampler or Http +defaults then nothing is sent. +The GET and DELETE request methods work similarly to each other. + +

+

+Upto and including JMeter 2.1.1, only responses with the content-type "text/html" were scanned for +embedded resources. Other content-types were assumed to be something other than HTML. +JMeter 2.1.2 introduces the a new property + +HTTPResponse.parsers + +, which is a list of parser ids, + e.g. + +htmlParser + + and + +wmlParser + +. For each id found, JMeter checks two further properties: +

+
    + + +
  • +id.types - a list of content types +
  • + + +
  • +id.className - the parser to be used to extract the embedded resources +
  • + + +
+

+See jmeter.properties file for the details of the settings. + If the HTTPResponse.parser property is not set, JMeter reverts to the previous behaviour, + i.e. only text/html responses will be scanned +

+ +Emulating slow connections (HttpClient only): + +
+ +
+
+# Define characters per second > 0 to emulate slow connections
+#httpclient.socket.http.cps=0
+#httpclient.socket.https.cps=0
+
+
+

+ +Response size calculation + +
+ + +Optional properties to allow change the method to get response size: +
+ + + +

    +
  • +Gets the real network size in bytes for the body response + +
    +sampleresult.getbytes.body_real_size=true
    +
    +
  • + + +
  • +Add HTTP headers to full response size + +
    +sampleresult.getbytes.headers_size=true
    +
    +
  • +
+ + + +

+ + +
+The Java and HttpClient3 inplementations do not include transport overhead such as +chunk headers in the response body size. +
+ + +The HttpClient4 implementation does include the overhead in the response body size, +so the value may be greater than the number of bytes in the response content. + +
+

+ + + +

+ + +
Versions of JMeter before 2.5 returns only data response size (uncompressed if request uses gzip/defate mode). + +
+ +To return to settings before version 2.5, set the two properties to false. +
+

+ + +

+

+ + + +Retry handling + +
+ + +In version 2.5 of JMeter, the HttpClient4 and Commons HttpClient 3.1 samplers used the default retry count, which was 3. +In later versions, the retry count has been set to 1, which is what the Java implementation appears to do. +The retry count can be overridden by setting the relevant JMeter property, for example: + +

+
+httpclient4.retrycount=3
+httpclient3.retrycount=3
+
+
+ + +

+

See Also: +

+

+

+
+ + + + +
+ +

+18.1.3 JDBC Request +

+
+
+

+This sampler lets you send an JDBC Request (an SQL query) to a database. +

+ + +

+Before using this you need to set up a + +JDBC Connection Configuration + Configuration element + +

+ + +

+ +If the Variable Names list is provided, then for each row returned by a Select statement, the variables are set up +with the value of the corresponding column (if a variable name is provided), and the count of rows is also set up. +For example, if the Select statement returns 2 rows of 3 columns, and the variable list is + +A,,C + +, +then the following variables will be set up: + +

+
+A_#=2 (number of rows)
+A_1=column 1, row 1
+A_2=column 1, row 2
+C_#=2 (number of rows)
+C_1=column 3, row 1
+C_2=column 3, row 2
+
+
+ +If the Select statement returns zero rows, then the A_# and C_# variables would be set to 0, and no other variables would be set. + +

+ + +

+ +Old variables are cleared if necessary - e.g. if the first select retrieves 6 rows and a second select returns only 3 rows, +the additional variables for rows 4, 5 and 6 will be removed. + +

+ + +

+ + + +Note: + + The latency time is set from the time it took to acquire a connection. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
Variable Name + Name of the JMeter variable that the connection pool is bound to. + This must agree with the 'Variable Name' field of a JDBC Connection Configuration. + + +Yes +
Query TypeSet this according to the statement type: + +
    + + +
  • +Select Statement +
  • + + +
  • +Update Statement - use this for Inserts as well +
  • + + +
  • +Callable Statement +
  • + + +
  • +Prepared Select Statement +
  • + + +
  • +Prepared Update Statement - use this for Inserts as well +
  • + + +
  • +Commit +
  • + + +
  • +Rollback +
  • + + +
  • +Autocommit(false) +
  • + + +
  • +Autocommit(true) +
  • + + +
  • +Edit - this should be a variable reference that evaluates to one of the above +
  • + + +
+ + +
+Yes +
SQL Query + SQL query. + Do not enter a trailing semi-colon. + There is generally no need to use { and } to enclose Callable statements; + however they mey be used if the database uses a non-standard syntax. + [The JDBC driver automatically converts the statement if necessary when it is enclosed in {}]. + For example: + +
    + + +
  • +select * from t_customers where id=23 +
  • + + +
  • +CALL SYSCS_UTIL.SYSCS_EXPORT_TABLE (null,?, ?, null, null, null) + +
      + + +
    • +Parameter values: tablename,filename +
    • + + +
    • +Parameter types: VARCHAR,VARCHAR +
    • + + +
    + + +
  • + + The second example assumes you are using Apache Derby. + +
+ + +
+Yes +
Parameter values + Comma-separated list of parameter values. Use ]NULL[ to indicate a NULL parameter. + (If required, the null string can be changed by defining the property "jdbcsampler.nullmarker".) + +
+ + + The list must be enclosed in double-quotes if any of the values contain a comma or double-quote, + and any embedded double-quotes must be doubled-up, for example: + +
+"Dbl-Quote: "" and Comma: ,"
+
+ + There must be as many values as there are placeholders in the statement. + +
+Yes, if a prepared or callable statement has parameters +
Parameter types + Comma-separated list of SQL parameter types (e.g. INTEGER, DATE, VARCHAR, DOUBLE). + These are defined as fields in the class java.sql.Types, see for example: + + +Javadoc for java.sql.Types + +. + [Note: JMeter will use whatever types are defined by the runtime JVM, + so if you are running on a different JVM, be sure to check the appropriate document] + If the callable statement has INOUT or OUT parameters, then these must be indicated by prefixing the + appropriate parameter types, e.g. instead of "INTEGER", use "INOUT INTEGER". + If not specified, "IN" is assumed, i.e. "DATE" is the same as "IN DATE". + +
+ + + If the type is not one of the fields found in java.sql.Types, versions of JMeter after 2.3.2 also + accept the corresponding integer number, e.g. since INTEGER == 4, you can use "INOUT 4". + +
+ + + There must be as many types as there are placeholders in the statement. + +
+Yes, if a prepared or callable statement has parameters +
Variable NamesComma-separated list of variable names to hold values returned by Select statements, Prepared Select Statements or CallableStatement. + Note that when used with CallableStatement, list of variables must be in the same sequence as the OUT parameters returned by the call. + If there are less variable names than OUT parameters only as many results shall be stored in the thread-context variables as variable names were supplied. + If more variable names than OUT parameters exist, the additional variables will be ignored + +No +
Result Variable Name + If specified, this will create an Object variable containing a list of row maps. + Each map contains the column name as the key and the column data as the value. Usage: +
+ + + + +columnValue = vars.getObject("resultObject").get(0).get("Column Name"); + + + +
+No +
+

+

See Also: +

+

+

+ + +
Versions of JMeter after 2.3.2 use UTF-8 as the character encoding. Previously the platform default was used. +
+

+

+
+ + + + +
+ +

+18.1.4 Java Request +

+
+
+

+This sampler lets you control a java class that implements the + + + +org.apache.jmeter.protocol.java.sampler.JavaSamplerClient + + + interface. +By writing your own implementation of this interface, +you can use JMeter to harness multiple threads, input parameter control, and +data collection. +

+ + +

+The pull-down menu provides the list of all such implementations found by +JMeter in its classpath. The parameters can then be specified in the +table below - as defined by your implementation. Two simple examples (JavaTest and SleepTest) are provided. + +

+ + +

+ +The JavaTest example sampler can be useful for checking test plans, because it allows one to set +values in almost all the fields. These can then be used by Assertions, etc. +The fields allow variables to be used, so the values of these can readily be seen. + +

+ + +

Control Panel

+
+

+ + +
The Add/Delete buttons don't serve any purpose at present. +
+

+

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this sampler + that is shown in the tree. + +No +
ClassnameThe specific implementation of + the JavaSamplerClient interface to be sampled. + +Yes +
Send Parameters with RequestA list of + arguments that will be passed to the sampled class. All arguments + are sent as Strings. + +No +
+

+

+
+

+The sleep time is calculated as follows: +

+
+
+SleepTime is in milliseconds
+SleepMask is used to add a "random" element to the time:
+totalSleepTime = SleepTime + (System.currentTimeMillis() % SleepMask)
+
+
+ + + + +
+ +

+18.1.5 SOAP/XML-RPC Request +

+
+
+

+This sampler lets you send a SOAP request to a webservice. It can also be +used to send XML-RPC over HTTP. It creates an HTTP POST request, with the specified XML as the +POST content. +To change the "Content-type" from the default of "text/xml", use a HeaderManager. +Note that the sampler will use all the headers from the HeaderManager. +If a SOAP action is specified, that will override any SOAPaction in the HeaderManager. +The primary difference between the soap sampler and +webservice sampler, is the soap sampler uses raw post and does not require conformance to +SOAP 1.1. +

+ + +

+ + +
For versions of JMeter later than 2.2, the sampler no longer uses chunked encoding by default. +
+ + +For screen input, it now always uses the size of the data. +
+ + +File input uses the file length as determined by Java. +
+ + +On some OSes this may not work for all files, in which case add a child Header Manager +with Content-Length set to the actual length of the file. +
+ + +Or set Content-Length to -1 to force chunked encoding. + +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this sampler + that is shown in the tree. + +No +
URLThe URL to direct the SOAP request to. + +Yes +
Send SOAP actionSend a SOAP action header? (overrides the Header Manager) + +No +
Use KeepAliveIf set, sends Connection: keep-alive, else sends Connection: close + +No +
Soap/XML-RPC DataThe Soap XML message, or XML-RPC instructions. + Not used if the filename is provided. + + +No +
FilenameIf specified, then the contents of the file are sent, and the Data field is ignored + +No +
+

+

+
+ + + + +
+ +

+18.1.6 WebService(SOAP) Request +

+
+
+

+This sampler has been tested with IIS Webservice running .NET 1.0 and .NET 1.1. + It has been tested with SUN JWSDP, IBM webservices, Axis and gSoap toolkit for C/C++. + The sampler uses Apache SOAP driver to serialize the message and set the header + with the correct SOAPAction. Right now the sampler doesn't support automatic WSDL + handling, since Apache SOAP currently does not provide support for it. Both IBM + and SUN provide WSDL drivers. There are 3 options for the post data: text area, + external file, or directory. If you want the sampler to randomly select a message, + use the directory. Otherwise, use the text area or a file. The if either the + file or path are set, it will not use the message in the text area. If you need + to test a soap service that uses different encoding, use the file or path. If you + paste the message in to text area, it will not retain the encoding and will result + in errors. Save your message to a file with the proper encoding, and the sampler + will read it as java.io.FileInputStream. +

+ + +

+An important note on the sampler is it will automatically use the proxy host + and port passed to JMeter from command line, if those fields in the sampler are + left blank. If a sampler has values in the proxy host and port text field, it + will use the ones provided by the user. This behavior may not be what users + expect. +

+ + +

+By default, the webservice sampler sets SOAPHTTPConnection.setMaintainSession + (true). If you need to maintain the session, add a blank Header Manager. The + sampler uses the Header Manager to store the SOAPHTTPConnection object, since + the version of apache soap does not provide a easy way to get and set the cookies. +

+ + +

+ +Note: + + If you are using CSVDataSet, do not check "Memory Cache". If memory + cache is checked, it will not iterate to the next value. That means all the requests + will use the first value. +

+ + +

+Make sure you use <soap:Envelope rather than <Envelope. For example: +

+ + +
+
+<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope 
+xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
+xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+<soap:Body>
+<foo xmlns="http://clients-xlmns"/>
+</soap:Body>
+</soap:Envelope>
+
+
+ + +

+ + +
The SOAP library that is used does not support SOAP 1.2, only SOAP 1.1. +Also the library does not provide access to the HTTP response code (e.g. 200) or message (e.g. OK). +To get round this, versions of JMeter after 2.3.2 check the returned message length. +If this is zero, then the request is marked as failed. + +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this sampler + that is shown in the tree. + +No +
WSDL URLThe WSDL URL with the service description. + Versions of JMeter after 2.3.1 support the file: protocol for local WSDL files. + + +No +
Web MethodsWill be populated from the WSDL when the Load WSDL button is pressed. + Select one of the methods and press the Configure button to populate the Protocol, Server, Port, Path and SOAPAction fields. + + +No +
ProtocolHTTP or HTTPS are acceptable protocol. + +Yes +
Server Name or IPThe hostname or IP address. + +Yes +
Port NumberPort Number. + +Yes +
TimeoutConnection timeout. + +No +
PathPath for the webservice. + +Yes +
SOAPActionThe SOAPAction defined in the webservice description or WSDL. + +Yes +
Soap/XML-RPC DataThe Soap XML message + +Yes +
Soap fileFile containing soap message + +No +
Message(s) FolderFolder containing soap files. Files are choose randomly during test. + +No +
Memory cache + When using external files, setting this causes the file to be processed once and caches the result. + This may use a lot of memory if there are many different large files. + + +Yes +
Read SOAP ResponseRead the SOAP reponse (consumes performance). Permit to have assertions or post-processors + +No +
Use HTTP ProxyCheck box if http proxy should be used + +No +
Server Name or IPProxy hostname + +No +
Port NumberProxy host port + +No +
+

+

+
+ + + + +
+ +

+18.1.7 LDAP Request +

+
+
+This Sampler lets you send a different Ldap request(Add, Modify, Delete and Search) to an LDAP server. + +

+If you are going to send multiple requests to the same LDAP server, consider + using an +LDAP Request Defaults + + Configuration Element so you do not have to enter the same information for each + LDAP Request. +

+ The same way the +Login Config Element + also using for Login and password. + +

Control Panel

+
+

+There are two ways to create test cases for testing an LDAP Server. +

+
    +
  1. +Inbuilt Test cases. +
  2. + + +
  3. +User defined Test cases. +
  4. +
+

+There are four test scenarios of testing LDAP. The tests are given below: +

+
    + + +
  1. +Add Test +
  2. + + +
      +
    1. +Inbuilt test : + +

      +This will add a pre-defined entry in the LDAP Server and calculate + the execution time. After execution of the test, the created entry will be + deleted from the LDAP + Server. +

      +
    2. + + +
    3. +User defined test : + +

      +This will add the entry in the LDAP Server. User has to enter all the + attributes in the table.The entries are collected from the table to add. The + execution time is calculated. The created entry will not be deleted after the + test. +

      +
    4. +
    + + + +
  3. +Modify Test +
  4. + + +
      +
    1. +Inbuilt test : + +

      +This will create a pre-defined entry first, then will modify the + created entry in the LDAP Server.And calculate the execution time. After + execution + of the test, the created entry will be deleted from the LDAP Server. +

      +
    2. + + +
    3. +User defined test + +

      +This will modify the entry in the LDAP Server. User has to enter all the + attributes in the table. The entries are collected from the table to modify. + The execution time is calculated. The entry will not be deleted from the LDAP + Server. +

      +
    4. +
    + + + +
  5. +Search Test +
  6. + + +
      +
    1. +Inbuilt test : + +

      +This will create the entry first, then will search if the attributes + are available. It calculates the execution time of the search query. At the + end of the execution,created entry will be deleted from the LDAP Server. +

      +
    2. + + +
    3. +User defined test + +

      +This will search the user defined entry(Search filter) in the Search + base (again, defined by the user). The entries should be available in the LDAP + Server. The execution time is calculated. +

      +
    4. +
    + + + +
  7. +Delete Test +
  8. + + +
      +
    1. +Inbuilt test : + +

      +This will create a pre-defined entry first, then it will be deleted + from the LDAP Server. The execution time is calculated. +

      +
    2. + + + +
    3. +User defined test + +

      +This will delete the user-defined entry in the LDAP Server. The entries + should be available in the LDAP Server. The execution time is calculated. +

      +
    4. +
    +
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
Server Name or IPDomain name or IP address of the LDAP server. + JMeter assumes the LDAP server is listening on the default port(389). + +Yes +
Portdefault port(389). + +Yes +
root DNDN for the server to communicate + +Yes +
UsernameLDAP server username. + +Usually +
PasswordLDAP server password. (N.B. this is stored unencrypted in the test plan) + +Usually +
Entry DNthe name of the context to create or Modify; may not be empty Example: do you want to add cn=apache,ou=test + you have to add in table name=cn, value=apache + + +Yes +
Deletethe name of the context to Delete; may not be empty + +Yes +
Search basethe name of the context or object to search + +Yes +
Search filter the filter expression to use for the search; may not be null + +Yes +
add test this name, value pair to added in the given context object + +Yes +
modify test this name, value pair to add or modify in the given context object + +Yes +
+

+

See Also: +

+

+

+
+ + + + +
+ +

+18.1.8 LDAP Extended Request +

+
+
+This Sampler can send all 8 different LDAP request to an LDAP server. It is an extended version of the LDAP sampler, + therefore it is harder to configure, but can be made much closer resembling a real LDAP session. + +

+If you are going to send multiple requests to the same LDAP server, consider + using an +LDAP Extended Request Defaults + + Configuration Element so you do not have to enter the same information for each + LDAP Request. +

+ +

Control Panel

+
+

+There are nine test operations defined. These operations are given below: +

+
    + + +
  1. + +Thread bind + +
  2. + + +

    +Any LDAP request is part of an LDAP session, so the first thing that should be done is starting a session to the LDAP server. + For starting this session a thread bind is used, which is equal to the LDAP "bind" operation. + The user is requested to give a username (Distinguished name) and password, + which will be used to initiate a session. + When no password, or the wrong password is specified, an anonymous session is started. Take care, + omitting the password will not fail this test, a wrong password will. + (N.B. this is stored unencrypted in the test plan) +

    + + +

    +Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeDescriptionRequired
    NameDescriptive name for this sampler that is shown in the tree. + +No +
    ServernameThe name (or IP-address) of the LDAP server. + +Yes +
    PortThe port number that the LDAP server is listening to. If this is omitted + JMeter assumes the LDAP server is listening on the default port(389). + +No +
    DNThe distinguished name of the base object that will be used for any subsequent operation. + It can be used as a starting point for all operations. You cannot start any operation on a higher level than this DN! + +No +
    UsernameFull distinguished name of the user as which you want to bind. + +No +
    PasswordPassword for the above user. If omitted it will result in an anonymous bind. + If is is incorrect, the sampler will return an error and revert to an anonymous bind. (N.B. this is stored unencrypted in the test plan) + +No +
    +

    + + +
    + + + +
  3. + +Thread unbind + +
  4. + + +

    +This is simply the operation to end a session. + It is equal to the LDAP "unbind" operation. +

    + + +

    +Parameters + + + + + + + +
    AttributeDescriptionRequired
    NameDescriptive name for this sampler that is shown in the tree. + +No +
    +

    + + + +
    + + + +
  5. + +Single bind/unbind + +
  6. + + +

    + This is a combination of the LDAP "bind" and "unbind" operations. + It can be used for an authentication request/password check for any user. It will open an new session, just to + check the validity of the user/password combination, and end the session again. +

    + + +

    +Parameters + + + + + + + + + + + + + + + + + +
    AttributeDescriptionRequired
    NameDescriptive name for this sampler that is shown in the tree. + +No +
    UsernameFull distinguished name of the user as which you want to bind. + +Yes +
    PasswordPassword for the above user. If omitted it will result in an anonymous bind. + If is is incorrect, the sampler will return an error. (N.B. this is stored unencrypted in the test plan) + +No +
    +

    + + + +
    + + + +
  7. + +Rename entry + +
  8. + + +

    +This is the LDAP "moddn" operation. It can be used to rename an entry, but + also for moving an entry or a complete subtree to a different place in + the LDAP tree. +

    + + +

    +Parameters + + + + + + + + + + + + + + + + + +
    AttributeDescriptionRequired
    NameDescriptive name for this sampler that is shown in the tree. + +No +
    Old entry nameThe current distinguished name of the object you want to rename or move, + relative to the given DN in the thread bind operation. + +Yes +
    New distinguished nameThe new distinguished name of the object you want to rename or move, + relative to the given DN in the thread bind operation. + +Yes +
    +

    + + + +
    + + + +
  9. + +Add test + +
  10. + + +

    +This is the ldap "add" operation. It can be used to add any kind of + object to the LDAP server. +

    + + +

    +Parameters + + + + + + + + + + + + + + + + + +
    AttributeDescriptionRequired
    NameDescriptive name for this sampler that is shown in the tree. + +No +
    Entry DNDistinguished name of the object you want to add, relative to the given DN in the thread bind operation. + +Yes +
    Add testA list of attributes and their values you want to use for the object. + If you need to add a multiple value attribute, you need to add the same attribute with their respective + values several times to the list. + +Yes +
    +

    + + + +
    + + + +
  11. + +Delete test + +
  12. + + +

    + This is the LDAP "delete" operation, it can be used to delete an + object from the LDAP tree +

    + + +

    +Parameters + + + + + + + + + + + + +
    AttributeDescriptionRequired
    NameDescriptive name for this sampler that is shown in the tree. + +No +
    DeleteDistinguished name of the object you want to delete, relative to the given DN in the thread bind operation. + +Yes +
    +

    + + + +
    + + + +
  13. + +Search test + +
  14. + + +

    +This is the LDAP "search" operation, and will be used for defining searches. +

    + + +

    +Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeDescriptionRequired
    NameDescriptive name for this sampler that is shown in the tree. + +No +
    Search baseDistinguished name of the subtree you want your + search to look in, relative to the given DN in the thread bind operation. + +No +
    Search Filtersearchfilter, must be specified in LDAP syntax. + +Yes +
    ScopeUse 0 for baseobject-, 1 for onelevel- and 2 for a subtree search. (Default=0) + +No +
    Size LimitSpecify the maximum number of results you want back from the server. (default=0, which means no limit.) When the sampler hits the maximum number of results, it will fail with errorcode 4 + +No +
    Time LimitSpecify the maximum amount of (cpu)time (in miliseconds) that the server can spend on your search. Take care, this does not say anything about the responsetime. (default is 0, which means no limit) + +No +
    AttributesSpecify the attributes you want to have returned, seperated by a semicolon. An empty field will return all attributes + +No +
    Return objectWhether the object will be returned (true) or not (false). Default=false + +No +
    Dereference aliasesIf true, it will dereference aliases, if false, it will not follow them (default=false) + +No +
    +

    + + + +
    + + + +
  15. + +Modification test + +
  16. + + +

    +This is the LDAP "modify" operation. It can be used to modify an object. It + can be used to add, delete or replace values of an attribute. +

    + + +

    +Parameters + + + + + + + + + + + + + + + + + +
    AttributeDescriptionRequired
    NameDescriptive name for this sampler that is shown in the tree. + +No +
    Entry nameDistinguished name of the object you want to modify, relative + to the given DN in the thread bind operation + +Yes +
    Modification testThe attribute-value-opCode triples. The opCode can be any + valid LDAP operationCode (add, delete/remove or replace). If you don't specify a value with a delete operation, + all values of the given attribute will be deleted. If you do specify a value in a delete operation, only + the given value will be deleted. If this value is non-existent, the sampler will fail the test. + +Yes +
    +

    + + + +
    + + + +
  17. + +Compare + +
  18. + + +

    +This is the LDAP "compare" operation. It can be used to compare the value + of a given attribute with some already known value. In reality this is mostly + used to check whether a given person is a member of some group. In such a case + you can compare the DN of the user as a given value, with the values in the + attribute "member" of an object of the type groupOfNames. + If the compare operation fails, this test fails with errorcode 49. +

    + + +

    +Parameters + + + + + + + + + + + + + + + + + +
    AttributeDescriptionRequired
    NameDescriptive name for this sampler that is shown in the tree. + +No +
    Entry DNThe current distinguished name of the object of + which you want to compare an attribute, relative to the given DN in the thread bind operation. + +Yes +
    Compare filterIn the form "attribute=value" + +Yes +
    +

    + + +
+

See Also: +

+

+

+
+ + + + +
+ +

+18.1.9 Access Log Sampler +

+
+
+
+

+(Alpha Code) +

+
+

+AccessLogSampler was designed to read access logs and generate http requests. +For those not familiar with the access log, it is the log the webserver maintains of every +request it accepted. This means the every image and html file. The current implementation +is complete, but some features have not been enabled. There is a filter for the access +log parser, but I haven't figured out how to link to the pre-processor. Once I do, changes +to the sampler will be made to enable that functionality. +

+ + +

+Tomcat uses the common format for access logs. This means any webserver that uses the +common log format can use the AccessLogSampler. Server that use common log format include: +Tomcat, Resin, Weblogic, and SunOne. Common log format looks +like this: +

+ + +

+127.0.0.1 - - [21/Oct/2003:05:37:21 -0500] "GET /index.jsp?%2Findex.jsp= HTTP/1.1" 200 8343 +

+ + +

+The current implemenation of the parser only looks at the text within the quotes. +Everything else is stripped out and igored. For example, the response code is completely +ignored by the parser. For the future, it might be nice to filter out entries that +do not have a response code of 200. Extending the sampler should be fairly simple. There +are two interfaces you have to implement. +

+ + +

+org.apache.jmeter.protocol.http.util.accesslog.LogParser +

+ + +

+org.apache.jmeter.protocol.http.util.accesslog.Generator +

+ + +

+The current implementation of AccessLogSampler uses the generator to create a new +HTTPSampler. The servername, port and get images are set by AccessLogSampler. Next, +the parser is called with integer 1, telling it to parse one entry. After that, +HTTPSampler.sample() is called to make the request. + + + + +

+
+            samp = (HTTPSampler) GENERATOR.generateRequest();
+            samp.setDomain(this.getDomain());
+            samp.setPort(this.getPort());
+            samp.setImageParser(this.isImageParser());
+            PARSER.parse(1);
+            res = samp.sample();
+            res.setSampleLabel(samp.toString());
+
+
+ + + + +The required methods in LogParser are: setGenerator(Generator) and parse(int). +Classes implementing Generator interface should provide concrete implementation +for all the methods. For an example of how to implement either interface, refer to +StandardGenerator and TCLogParser. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
ServerDomain name or IP address of the web server. + +Yes +
PortPort the web server is listening to. + +No (defaults to 80) +
Log parser classThe log parser class is responsible for parsing the logs. + +Yes (default provided) +
FilterThe filter class is used to filter out certain lines. + +No +
Location of log fileThe location of the access log file. + +Yes +
+

+

+ +The TCLogParser processes the access log independently for each thread. +The SharedTCLogParser and OrderPreservingLogParser share access to the file, +i.e. each thread gets the next entry in the log. + +

+

+ +The SessionFilter is intended to handle Cookies across threads. +It does not filter out any entries, but modifies the cookie manager so that the cookies for a given IP are +processed by a single thread at a time. If two threads try to process samples from the same client IP address, +then one will be forced to wait until the other has completed. + +

+

+ +The LogFilter is intended to allow access log entries to be filtered by filename and regex, +as well as allowing for the replacement of file extensions. However, it is not currently possible +to configure this via the GUI, so it cannot really be used. + +

+

+
+ + + + +
+ +

+18.1.10 BeanShell Sampler +

+
+
+

+This sampler allows you to write a sampler using the BeanShell scripting language. + +

+

+ + + +For full details on using BeanShell, please see the + +BeanShell website. + + + + +

+ + +

+ +The test element supports the ThreadListener and TestListener interface methods. +These must be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. + +

+ + +

+ +From JMeter version 2.5.1, the BeanShell sampler also supports the Interruptible interface. +The interrupt() method can be defined in the script or the init file. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + The name is stored in the script variable Label + +No +
Reset bsh.Interpreter before each call + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see + +Best Practices - BeanShell scripting + +. + + +Yes +
ParametersParameters to pass to the BeanShell script. + This is intended for use with script files; for scripts defined in the GUI, you can use whatever + variable and function references you need within the script itself. + The parameters are stored in the following variables: + +
    + + +
  • +Parameters - string containing the parameters as a single variable +
  • + + +
  • +bsh.args - String array containing parameters, split on white-space +
  • + + +
+
+No +
Script fileA file containing the BeanShell script to run. + The file name is stored in the script variable FileName + +No +
ScriptThe BeanShell script to run. + The return value (if not null) is stored as the sampler result. + +Yes (unless script file is provided) +
+

+

+ +N.B. Each Sampler instance has its own BeanShell interpeter, +and Samplers are only called from a single thread + +

+

+ +If the property "beanshell.sampler.init" is defined, it is passed to the Interpreter +as the name of a sourced file. +This can be used to define common methods and variables. +There is a sample init file in the bin directory: BeanShellSampler.bshrc. + +

+

+ +If a script file is supplied, that will be used, otherwise the script will be used. +

+

+Before invoking the script, some variables are set up in the BeanShell interpreter: + +

+

+The contents of the Parameters field is put into the variable "Parameters". + The string is also split into separate tokens using a single space as the separator, and the resulting list + is stored in the String array bsh.args. +

+

+The full list of BeanShell variables that is set up is as follows: +

+
    + + +
  • +log - the Logger +
  • + + +
  • +Label - the Sampler label +
  • + + +
  • +FileName - the file name, if any +
  • + + +
  • +Parameters - text from the Parameters field +
  • + + +
  • +bsh.args - the parameters, split as described above +
  • + + +
  • +SampleResult - pointer to the current SampleResult +
  • + + +
  • +ResponseCode = 200 +
  • + + +
  • +ResponseMessage = "OK" +
  • + + +
  • +IsSuccess = true +
  • + + +
  • +ctx - JMeterContext +
  • + + +
  • +vars - + +JMeterVariables + + - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.remove("VAR3"); vars.putObject("OBJ1",new Object()); +
  • + + +
  • +props - JMeterProperties (class java.util.Properties)- e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
+

+When the script completes, control is returned to the Sampler, and it copies the contents + of the following script variables into the corresponding variables in the SampleResult: +

+
    + + +
  • +ResponseCode - for example 200 +
  • + + +
  • +ResponseMessage - for example "OK" +
  • + + +
  • +IsSuccess - true/false +
  • + + +
+

+The SampleResult ResponseData is set from the return value of the script. + Since version 2.1.2, if the script returns null, it can set the response directly, by using the method + SampleResult.setResponseData(data), where data is either a String or a byte array. + The data type defaults to "text", but can be set to binary by using the method + SampleResult.setDataType(SampleResult.BINARY). + +

+

+The SampleResult variable gives the script full access to all the fields and + methods in the SampleResult. For example, the script has access to the methods + setStopThread(boolean) and setStopTest(boolean). + + Here is a simple (not very useful!) example script: +

+
+
+if (bsh.args[0].equalsIgnoreCase("StopThread")) {
+    log.info("Stop Thread detected!");
+    SampleResult.setStopThread(true);
+}
+return "Data from sample with Label "+Label;
+//or, since version 2.1.2
+SampleResult.setResponseData("My data");
+return null;
+
+
+

+Another example: +
+ + ensure that the property + +beanshell.sampler.init=BeanShellSampler.bshrc + + is defined in jmeter.properties. +The following script will show the values of all the variables in the ResponseData field: + +

+
+
+return getVariables();
+
+
+

+ +For details on the methods available for the various classes (JMeterVariables, SampleResult etc) please check the Javadoc or the source code. +Beware however that misuse of any methods can cause subtle faults that may be difficult to find ... + +

+

+
+ + + + +
+ +

+18.1.11 BSF Sampler +

+
+
+

+This sampler allows you to write a sampler using a BSF scripting language. +
+ + + See the + +Apache Bean Scripting Framework + + + website for details of the languages supported. + You may need to download the appropriate jars for the language; they should be put in the JMeter + +lib + + directory. + +

+ + +

+By default, JMeter supports the following languages: +

+ + +
    + + +
  • +javascript +
  • + + +
  • +jexl (JMeter version 2.3.2 and later) +
  • + + +
  • +xslt +
  • + + +
+ + +

+ + +
Unlike the BeanShell sampler, the interpreter is not saved between invocations. +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
Scripting LanguageName of the BSF scripting language to be used. + N.B. Not all the languages in the drop-down list are supported by default. + The following are supported: jexl, javascript, xslt. + Others may be available if the appropriate jar is installed in the JMeter lib directory. + + +Yes +
Script FileName of a file to be used as a BSF script + +No +
ParametersList of parameters to be passed to the script file or the script. + +No +
ScriptScript to be passed to BSF language + +Yes (unless script file is provided) +
+

+

+ +If a script file is supplied, that will be used, otherwise the script will be used. +

+

+ +Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. + +

+
    + + +
  • +log - the Logger +
  • + + +
  • +Label - the Sampler label +
  • + + +
  • +FileName - the file name, if any +
  • + + +
  • +Parameters - text from the Parameters field +
  • + + +
  • +args - the parameters, split as described above +
  • + + +
  • +SampleResult - pointer to the current SampleResult +
  • + + +
  • +sampler - pointer to current Sampler +
  • + + +
  • +ctx - JMeterContext +
  • + + +
  • +vars - + +JMeterVariables + + - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.remove("VAR3"); vars.putObject("OBJ1",new Object()); +
  • + + +
  • +props - JMeterProperties (class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
  • +OUT - System.out - e.g. OUT.println("message") +
  • + + +
+

+ +The SampleResult ResponseData is set from the return value of the script. +If the script returns null, it can set the response directly, by using the method +SampleResult.setResponseData(data), where data is either a String or a byte array. +The data type defaults to "text", but can be set to binary by using the method +SampleResult.setDataType(SampleResult.BINARY). + +

+

+ +The SampleResult variable gives the script full access to all the fields and +methods in the SampleResult. For example, the script has access to the methods +setStopThread(boolean) and setStopTest(boolean). + +

+

+ +Unlike the Beanshell Sampler, the BSF Sampler does not set the ResponseCode, ResponseMessage and sample status via script variables. +Currently the only way to changes these is via the SampleResult methods: + +

    + + +
  • +SampleResult.setSuccessful(true/false) +
  • + + +
  • +SampleResult.setResponseCode("code") +
  • + + +
  • +SampleResult.setResponseMessage("message") +
  • + + +
+ + +

+

+
+ + + + +
+ +

+18.1.11.1 JSR223 Sampler +

+
+
+ + +

+ +The JSR223 Sampler allows JSR223 script code to be used to perform a sample. +For details, see +BSF Sampler +. + +

+ + +

+ + +
Unlike the BeanShell sampler, the interpreter is not saved between invocations. +
+

+ + +

+
+ + + + +
+ +

+18.1.12 TCP Sampler +

+
+
+ + +

+ + The TCP Sampler opens a TCP/IP connection to the specified server. + It then sends the text, and waits for a response. + +
+ + + If "Re-use connection" is selected, connections are shared between Samplers in the same thread, + provided that the exact same host name string and port are used. + Different hosts/port combinations will use different connections, as will different threads. + +
+ + + If an error is detected - or "Re-use connection" is not selected - the socket is closed. + Another socket will be reopened on the next sample. + +
+ + + The following properties can be used to control its operation: + +

+ + +
    + + +
  • +tcp.status.prefix - text that precedes a status number +
  • + + +
  • +tcp.status.suffix - text that follows a status number +
  • + + +
  • +tcp.status.properties - name of property file to convert status codes to messages +
  • + + +
  • +tcp.handler - Name of TCP Handler class (default TCPClientImpl) - only used if not specified on the GUI +
  • + + +
+ + The class that handles the connection is defined by the GUI, failing that the property tcp.handler. + If not found, the class is then searched for in the package org.apache.jmeter.protocol.tcp.sampler. + +

+ + Users can provide their own implementation. + The class must extend org.apache.jmeter.protocol.tcp.sampler.TCPClient. + +

+ + +

+ + The following implementations are currently provided. + +

    + + +
  • +TCPClientImpl +
  • + + +
  • +BinaryTCPClientImpl +
  • + + +
  • +LengthPrefixedBinaryTCPClientImpl +
  • + + +
+ + The implementations behave as follows: + +

+ + +

+ +TCPClientImpl + +
+ + + This implementation is fairly basic. + When reading the response, it reads until the end of line byte, if this is defined + by setting the property + +tcp.eolByte + +, otherwise until the end of the input stream. + You can control charset encoding by setting + +tcp.charset + +, which will default to Platform default encoding. + +

+ + +

+ +BinaryTCPClientImpl + +
+ + + This implementation converts the GUI input, which must be a hex-encoded string, into binary, + and performs the reverse when reading the response. + When reading the response, it reads until the end of message byte, if this is defined + by setting the property + +tcp.BinaryTCPClient.eomByte + +, otherwise until the end of the input stream. + +

+ + +

+ +LengthPrefixedBinaryTCPClientImpl + +
+ + + This implementation extends BinaryTCPClientImpl by prefixing the binary message data with a binary length byte. + The length prefix defaults to 2 bytes. + This can be changed by setting the property + +tcp.binarylength.prefix.length + +. + +

+ + +

+ +Timeout handling + + + If the timeout is set, the read will be terminated when this expires. + So if you are using an eolByte/eomByte, make sure the timeout is sufficiently long, + otherwise the read will be terminated early. + +

+ + +

+ +Response handling + + + +
+ + + If tcp.status.prefix is defined, then the response message is searched for the text following + that up to the suffix. If any such text is found, it is used to set the response code. + The response message is then fetched from the properties file (if provided). + +
+ + + For example, if the prefix = "[" and the suffix = "]", then the following repsonse: + +
+ + + [J28] XI123,23,GBP,CR + +
+ + + would have the response code J28. + +
+ + + Response codes in the range "400"-"499" and "500"-"599" are currently regarded as failures; + all others are successful. [This needs to be made configurable!] + +

+ + +

+ + +
The login name/password are not used by the supplied TCP implementations. +
+

+ + +
+ + + Sockets are disconnected at the end of a test run. + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
TCPClient classnameName of the TCPClient class. Defaults to the property tcp.handler, failing that TCPClientImpl. + +No +
ServerName or IPName or IP of TCP server + +Yes +
Port NumberPort to be used + +Yes +
Re-use connectionIf selected, the connection is kept open. Otherwise it is closed when the data has been read. + +Yes +
Connect TimeoutConnect Timeout (milliseconds, 0 disables). + +No +
Response TimeoutResponse Timeout (milliseconds, 0 disables). + +No +
Set NodelaySee java.net.Socket.setTcpNoDelay(). + If selected, this will disable Nagle's algorithm, otherwise Nagle's algorithm will be used. + +Yes +
Text to SendText to be sent + +Yes +
Login UserUser Name - not used by default implementation + +No +
PasswordPassword - not used by default implementation (N.B. this is stored unencrypted in the test plan) + +No +
+

+

+
+ + + + +
+ +

+18.1.13 JMS Publisher +

+
+
+

+ + +
BETA CODE - the code is still subject to change +
+

+ + +

+ + JMS Publisher will publish messages to a given destination (topic/queue). For those not + familiar with JMS, it is the J2EE specification for messaging. There are + numerous JMS servers on the market and several open source options. + +

+ + +
+ + + +

+ + +
JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
use JNDI properties fileuse jndi.properties. + Note that the file must be on the classpath - e.g. by updating the user.classpath JMeter property. + If this option is not selected, JMeter uses the "JNDI Initial Context Factory" and "Provider URL" fields + to create the connection. + + +Yes +
JNDI Initial Context FactoryName of the context factory + +No +
Provider URLThe URL for the jms provider + +Yes, unless using jndi.properties +
DestinationThe message destination (topic or queue name) + +Yes +
SetupThe destination setup type. With At startup, the destination name is static (i.e. always same name during the test), with Each sample, the destination name is dynamic and is evaluate at each sample (i.e. the destination name may be a variable) + +Yes +
AuthenticationAuthentication requirement for the JMS provider + +Yes +
UserUser Name + +No +
PasswordPassword (N.B. this is stored unencrypted in the test plan) + +No +
Number of samples to aggregateNumber of samples to aggregate + +Yes +
Message sourceWhere to obtain the message + +Yes +
Message typeText, Map or Object message + +Yes +
Use non-persistent delivery mode? + Whether to set DeliveryMode.NON_PERSISTENT (defaults to false) + + +No +
JMS Properties + The JMS Properties are properties specific for the underlying messaging system. + For example: for WebSphere 5.1 web services you will need to set the JMS Property targetService to test + webservices through JMS. + + +No +
+

+

+ +For the MapMessage type, JMeter reads the source as lines of text. +Each line must have 3 fields, delimited by commas. +The fields are: + +

    + + +
  • +Name of entry +
  • + + +
  • +Object class name, e.g. "String" (assumes java.lang package if not specified) +
  • + + +
  • +Object string value +
  • + + +
+ +For each entry, JMeter adds an Object with the given name. +The value is derived by creating an instance of the class, and using the valueOf(String) method to convert the value if necessary. +For example: + +
+
+name,String,Example
+size,Integer,1234
+
+
+ +This is a very simple implementation; it is not intended to support all possible object types. + +

+

+ + +

+ + +
+The Object message is implemented since 2.7 and works as follow: + +
    + + +
  • +Put the JAR that contain you object and its dependencies in jmeter_home/lib/ folder +
  • + + +
  • +Serialize your object as XML using XStream +
  • + + +
  • +Either put result in a file suffixed with .txt or .obj or put XML content direclty in Text Area +
  • + + +
+ +Note that if message is in an file, replacement of properties will not occur while it will happen if you use Text Area. + +
+

+ + + +

+

+ +The following table shows some values which may be useful when configuring JMS: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +Apache ActiveMQ + + + +Value(s) + + + +Comment + +
+ +Context Factory + + + +org.apache.activemq.jndi.ActiveMQInitialContextFactory + + + +. + +
+ +Provider URL + + + +vm://localhost + + + +  + +
+ +Provider URL + + + +vm:(broker:(vm://localhost)?persistent=false) + + + +Disable persistence + +
+ +Queue Reference + + + +dynamicQueues/QUEUENAME + + + +Dynamically define the QUEUENAME to JNDI + +
+ +Topic Reference + + + +dynamicTopics/TOPICNAME + + + +Dynamically define the TOPICNAME to JNDI + +
+ + +

+

+
+ + + + +
+ +

+18.1.14 JMS Subscriber +

+
+
+

+ + +
BETA CODE - the code is still subject to change +
+

+ + +

+ + JMS Publisher will subscribe to messages in a given destination (topic or queue). For those not + familiar with JMS, it is the J2EE specification for messaging. There are + numerous JMS servers on the market and several open source options. + +

+ + +
+ + + +

+ + +
JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
use JNDI properties fileuse jndi.properties. + Note that the file must be on the classpath - e.g. by updating the user.classpath JMeter property. + If this option is not selected, JMeter uses the "JNDI Initial Context Factory" and "Provider URL" fields + to create the connection. + + +Yes +
JNDI Initial Context FactoryName of the context factory + +No +
Provider URLThe URL for the jms provider + +No +
Destinationthe message destination (topic or queue name) + +Yes +
Durable Subscription IDThe ID to use for a durable subscription. On first + use the respective queue will automatically be generated by the JMS provider if it does not exist yet. + +No +
Client IDThe Client ID to use when you you use a durable subscription. + Be sure to add a variable like ${__threadNum} when you have more than one Thread. + +No +
JMS SelectorMessage Selector as defined by JMS specification to extract only + messages that respect the Selector condition. Syntax uses subpart of SQL 92. + +No +
SetupThe destination setup type. With At startup, the destination name is static (i.e. always same name during the test), with Each sample, the destination name is dynamic and is evaluate at each sample (i.e. the destination name may be a variable) + +Yes +
AuthenticationAuthentication requirement for the JMS provider + +Yes +
UserUser Name + +No +
PasswordPassword (N.B. this is stored unencrypted in the test plan) + +No +
Number of samples to aggregatenumber of samples to aggregate + +Yes +
Read responseshould the sampler read the response. If not, only the response length is returned. + +Yes +
TimeoutSpecify the timeout to be applied, in milliseconds. 0=none. + This is the overall aggregate timeout, not per sample. + +Yes +
ClientWhich client implementation to use. + Both of them create connections which can read messages. However they use a different strategy, as described below: + +
    + + +
  • +MessageConsumer.receive() - calls receive() for every requested message. + Retains the connection between samples, but does not fetch messages unless the sampler is active. + This is best suited to Queue subscriptions. + +
  • + + +
  • +MessageListener.onMessage() - establishes a Listener that stores all incoming messages on a queue. + The listener remains active after the sampler completes. + This is best suited to Topic subscriptions. +
  • + + +
+ + +
+Yes +
Stop between samples? + If selected, then JMeter calls Connection.stop() at the end of each sample (and calls start() before each sample). + This may be useful in some cases where multiple samples/threads have connections to the same queue. + If not selected, JMeter calls Connection.start() at the start of the thread, and does not call stop() until the end of the thread. + + +Yes +
Separator + Separator used to separate messages when there is more than one (related to setting Number of samples to aggregate). + Note that \n, \r, \t are accepted. + + +No +
+

+

+ + + +NOTE: + + JMeter 2.3.4 and earlier used a different strategy for the MessageConsumer.receive() client. +Previously this started a background thread which polled for messages. This thread continued when the sampler +completed, so the net effect was similar to the MessageListener.onMessage() strategy. + +

+

+
+ + + + +
+ +

+18.1.15 JMS Point-to-Point +

+
+
+

+ + +
BETA CODE - the code is still subject to change +
+

+ + +

+ + This sampler sends and optionally receives JMS Messages through point-to-point connections (queues). + It is different from pub/sub messages and is generally used for handling transactions. + +

+ + +

+ + Versions of JMeter after 2.3.2 use the properties java.naming.security.[principal|credentials] - if present - + when creating the Queue Connection. If this behaviour is not desired, set the JMeter property + + +JMSSampler.useSecurity.properties=false + + + +

+ + +
+ + + +

+ + +
JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
QueueConnection Factory + The JNDI name of the queue connection factory to use for connecting to the messaging system. + + +Yes +
JNDI Name Request queue + This is the JNDI name of the queue to which the messages are sent. + + +Yes +
JNDI Name Reply queue + The JNDI name of the receiving queue. If a value is provided here and the communication style is Request Response + this queue will be monitored for responses to the requests sent. + + +No +
JMS Selector + Message Selector as defined by JMS specification to extract only + messages that respect the Selector condition. Syntax uses subpart of SQL 92. + + +No +
Communication style + The Communication style can be Request Only (also known as Fire and Forget) or Request Reply. + Request Only will only sent messages and will not monitor replies. As such it can be used to put load on a system. + Request Reply will sent messages and monitor the replies it receives. Behaviour is depended on the value of the JNDI Name Reply Queue. + If JNDI Name Reply Queue has a value, this queue is used to monitor the results. Matching of request and reply is done with + the message id of the request with the correlation id of the reply. If the JNDI Name Reply Queue is empty, then + temporary queues will be used for the communication between the requestor and the server. This is very different from + the fixed reply queue. With temporary queues the diffent threads will block until the reply message has been received. + + +Yes +
Use alternate fields for message correlation + These check-boxes select the fields which will be used for matching the response message with the original request. + +
    + + +
  • +Use Request Message Id - if selected, the request JMSMessageID will be used, + otherwise the request JMSCorrelationID will be used. + In the latter case the correlation id must be specified in the request. +
  • + + +
  • +Use Response Message Id - if selected, the response JMSMessageID will be used, + otherwise the response JMSCorrelationID will be used. + +
  • + + +
+ + There are two frequently used JMS Correlation patterns: + +
    + + +
  • +JMS Correlation ID Pattern - + i.e. match request and response on their correlation Ids + => deselect both checkboxes, and provide a correlation id. +
  • + + +
  • +JMS Message ID Pattern - + i.e. match request message id with response correlation id + => select "Use Request Message Id" only. + +
  • + + +
+ + In both cases the JMS application is responsible for populating the correlation ID as necessary. + + +Note: + + if the same queue is used to send and receive messages, + then the response message will be the same as the request message. + In which case, either provide a correlation id and clear both checkboxes; + or select both checkboxes to use the message Id for correlation. + This can be useful for checking raw JMS throughput. + +
+Yes +
Timeout + The timeout in milliseconds for the reply-messages. If a reply has not been received within the specified + time, the specific testcase failes and the specific reply message received after the timeout is discarded. + + +Yes +
Use non-persistent delivery mode? + Whether to set DeliveryMode.NON_PERSISTENT. + + +Yes +
Content + The content of the message. + + +No +
JMS Properties + The JMS Properties are properties specific for the underlying messaging system. + For example: for WebSphere 5.1 web services you will need to set the JMS Property targetService to test + webservices through JMS. + + +No +
Initial Context Factory + The Initial Context Factory is the factory to be used to look up the JMS Resources. + + +No +
JNDI properties + The JNDI Properties are the specific properties for the underlying JNDI implementation. + + +No +
Provider URL + The URL for the jms provider. + + +No +
+

+

+
+ + + + +
+ +

+18.1.16 JUnit Request +

+
+
+ +The current implementation supports standard Junit convention and extensions. It also +includes extensions like oneTimeSetUp and oneTimeTearDown. The sampler works like the +JavaSampler with some differences. + +
+ +1. rather than use Jmeter's test interface, it scans the jar files for classes extending junit's TestCase class. That includes any class or subclass. + +
+ +2. Junit test jar files should be placed in jmeter/lib/junit instead of /lib directory. +In versions of JMeter after 2.3.1, you can also use the "user.classpath" property to specify where to look for TestCase classes. + +
+ +3. Junit sampler does not use name/value pairs for configuration like the JavaSampler. The sampler assumes setUp and tearDown will configure the test correctly. + +
+ +4. The sampler measures the elapsed time only for the test method and does not include setUp and tearDown. + +
+ +5. Each time the test method is called, Jmeter will pass the result to the listeners. + +
+ +6. Support for oneTimeSetUp and oneTimeTearDown is done as a method. Since Jmeter is multi-threaded, we cannot call oneTimeSetUp/oneTimeTearDown the same way Maven does it. + +
+ +7. The sampler reports unexpected exceptions as errors. +There are some important differences between standard JUnit test runners and JMeter's +implementation. Rather than make a new instance of the class for each test, JMeter +creates 1 instance per sampler and reuses it. +This can be changed with checkbox "Create a new instance per sample". +
+ + +The current implementation of the sampler will try to create an instance using the string constructor first. If the test class does not declare a string constructor, the sampler will look for an empty constructor. Example below:
+
+Empty Constructor:
+public class myTestCase {
+ public myTestCase() {}
+}
+
+String Constructor:
+public class myTestCase {
+ public myTestCase(String text) {
+ super(text);
+ }
+}
+By default, Jmeter will provide some default values for the success/failure code and message. Users should define a set of unique success and failure codes and use them uniformly across all tests.
+General Guidelines +
+ + +If you use setUp and tearDown, make sure the methods are declared public. If you do not, the test may not run properly. + +
+ + +Here are some general guidelines for writing Junit tests so they work well with Jmeter. Since Jmeter runs multi-threaded, it is important to keep certain things in mind.
+
+1. Write the setUp and tearDown methods so they are thread safe. This generally means avoid using static memebers.
+2. Make the test methods discrete units of work and not long sequences of actions. By keeping the test method to a descrete operation, it makes it easier to combine test methods to create new test plans.
+3. Avoid making test methods depend on each other. Since Jmeter allows arbitrary sequencing of test methods, the runtime behavior is different than the default Junit behavior.
+4. If a test method is configurable, be careful about where the properties are stored. Reading the properties from the Jar file is recommended.
+5. Each sampler creates an instance of the test class, so write your test so the setup happens in oneTimeSetUp and oneTimeTearDown. + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Search for JUnit4 annotationsSelect this to search for JUnit 4 tests (@Test annotations) + +Yes +
Package filterComma separated list of packages to show. Example, org.apache.jmeter,junit.framework. + +No +
Class nameFully qualified name of the JUnit test class. + +Yes +
Constructor stringString pass to the string constructor. If a string is set, the sampler will use the + string constructor instead of the empty constructor. + +No +
Test methodThe method to test. + +Yes +
Success messageA descriptive message indicating what success means. + +No +
Success codeAn unique code indicating the test was successful. + +No +
Failure messageA descriptive message indicating what failure means. + +No +
Failure codeAn unique code indicating the test failed. + +No +
Error messageA description for errors. + +No +
Error codeSome code for errors. Does not need to be unique. + +No +
Do not call setUp and tearDownSet the sampler not to call setUp and tearDown. + By default, setUp and tearDown should be called. Not calling those methods could affect the test and make it inaccurate. + This option should only be used with calling oneTimeSetUp and oneTimeTearDown. If the selected method is oneTimeSetUp or oneTimeTearDown, + this option should be checked. + +Yes +
Append assertion errorsWhether or not to append assertion errors to the response message. + +Yes +
Append runtime exceptionsWhether or not to append runtime exceptions to the response message. Only applies if "Append assertion errors" is not selected. + +Yes +
Create a new Instance per sampleWhether or not to create a new JUnit instance for each sample. Defaults to false, meaning JUnit TestCase is created one and reused. + +Yes +
+

+

+ +The following JUnit4 annotations are recognised: + +

    + + +
  • +@Test - used to find test methods and classes. The "expected" and "timeout" attributes are supported. +
  • + + +
  • +@Before - treated the same as setUp() in JUnit3 +
  • + + +
  • +@After - treated the same as tearDown() in JUnit3 +
  • + + +
  • +@BeforeClass, @AfterClass - treated as test methods so they can be run independently as required +
  • + + +
+ + +

+

+ +Note that JMeter currently runs the test methods directly, rather than leaving it to JUnit. +This is to allow the setUp/tearDown methods to be excluded from the sample time. + +

+

+
+ + + + +
+ +

+18.1.17 Mail Reader Sampler +

+
+
+ + +

+ +The Mail Reader Sampler can read (and optionally delete) mail messages using POP3(S) or IMAP(S) protocols. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Server TypeThe protocol used by the provider: e.g. pop3, pop3s, imap, imaps. +or another string representing the server protocol. +For example + +file + + for use with the read-only mail file provider. +The actual provider names for POP3 and IMAP are + +pop3 + + and + +imap + + + + +Yes +
ServerHostname or IP address of the server. See below for use with + +file + + protocol. + +Yes +
PortPort to be used to connect to the server (optional) + +No +
UsernameUser login name + +No +
PasswordUser login password (N.B. this is stored unencrypted in the test plan) + +No +
FolderThe IMAP(S) folder to use. See below for use with + +file + + protocol. + +Yes, if using IMAP(S) +
Number of messages to retrieveSet this to retrieve all or some messages + +Yes +
Delete messages from the serverIf set, messages will be deleted after retrieval + +Yes +
Store the message using MIMEWhether to store the message as MIME. +If so, then the entire raw message is stored in the Response Data; the headers are not stored as they are available in the data. +If not, the message headers are stored as Response Headers. +A few headers are stored (Date, To, From, Subject) in the body. + + +Yes +
Use no security featuresIndicates that the connection to the server does not use any security protocol. + +No +
Use SSLIndicates that the connection to the server must use the SSL protocol. + +No +
Use StartTLSIndicates that the connection to the server should attempt to start the TLS protocol. + +No +
Enforce StartTLSIf the server does not start the TLS protocol the connection will be terminated. + +No +
Trust All CertificatesWhen selected it will accept all certificates independent of the CA. + +No +
Use local truststoreWhen selected it will only accept certificates that are locally trusted. + +No +
Local truststorePath to file containing the trusted certificates. +Relative paths are resolved against the current directory. + +
+ +Failing that, against the directory containing the test script (JMX file). + +
+No +
+

+

+ +Messages are stored as subsamples of the main sampler. +In versions of JMeter after 2.3.4, multipart message parts are stored as subsamples of the message. + +

+

+ + + +Special handling for "file" protocol: + +
+ + +The + +file + + JavaMail provider can be used to read raw messages from files. +The + +server + + field is used to specify the path to the parent of the + +folder + +. +Individual message files should be stored with the name + +n.msg + +, +where + +n + + is the message number. +Alternatively, the + +server + + field can be the name of a file which contains a single message. +The current implementation is quite basic, and is mainly intended for debugging purposes. + +

+

+
+ + + + +
+ +

+18.1.18 Test Action +

+
+
+ +The Test Action sampler is a sampler that is intended for use in a conditional controller. +Rather than generate a sample, the test element eithers pauses or stops the selected target. + +

+This sampler can also be useful in conjunction with the Transaction Controller, as it allows +pauses to be included without needing to generate a sample. +For variable delays, set the pause time to zero, and add a Timer as a child. +

+ + +

+ +The "Stop" action stops the thread or test after completing any samples that are in progress. +The "Stop Now" action stops the test without waiting for samples to complete; it will interrupt any active samples. +If some threads fail to stop within the 5 second time-limit, a message will be displayed in GUI mode. +You can try using the Stop command to see if this will stop the threads, but if not, you should exit JMeter. +In non-GUI mode, JMeter will exit if some threads fail to stop within the 5 second time limit. +[This can be changed using the JMeter property + +jmeterengine.threadstop.wait + +] + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
TargetCurrent Thread / All Threads (ignored for Pause) + +Yes +
ActionPause / Stop / Stop Now + +Yes +
DurationHow long to pause for (milliseconds) + +Yes, if Pause is selected +
+

+

+
+ + + + +
+ +

+18.1.19 SMTP Sampler +

+
+
+ + +

+ +The SMTP Sampler can send mail messages using SMTP/SMTPS protocol. +It is possible to set security propocols for the connection (SSL and TLS), as well as user authentication. +If a security protocol is used a verification on the server certificate will occur. +
+ + +Two alternatives to handle this verification are available: +
+ + + +

    + + +
  • +Trust all certificates. This will ignore certificate chain verification +
  • + + +
  • +Use a local truststore. With this option the certificate chain will be validated against the local truststore file. +
  • + + +
+ + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
ServerHostname or IP address of the server. See below for use with + +file + + protocol. + +Yes +
PortPort to be used to connect to the server. +Defaults are: SMTP=25, SSL=465, StartTLS=587 + + +No +
Address FromThe from address that will appear in the e-mail + +Yes +
Address ToThe destination e-mail address (multiple values separated by ";") + +Yes, unless CC or BCC is specified +
Address To CCCarbon copy destinations e-mail address (multiple values separated by ";") + +No +
Address To BCCBlind carbon copy destinations e-mail address (multiple values separated by ";") + +No +
Address Reply-ToAlternate Reply-To address (multiple values separated by ";") + +No +
Use AuthIndicates if the SMTP server requires user authentication + +No +
UsernameUser login name + +No +
PasswordUser login password (N.B. this is stored unencrypted in the test plan) + +No +
Use no security featuresIndicates that the connection to the SMTP server does not use any security protocol. + +No +
Use SSLIndicates that the connection to the SMTP server must use the SSL protocol. + +No +
Use StartTLSIndicates that the connection to the SMTP server should attempt to start the TLS protocol. + +No +
Enforce StartTLSIf the server does not start the TLS protocol the connection will be terminated. + +No +
Trust All CertificatesWhen selected it will accept all certificates independent of the CA. + +No +
Use local truststoreWhen selected it will only accept certificates that are locally trusted. + +No +
Local truststorePath to file containing the trusted certificates. +Relative paths are resolved against the current directory. + +
+ +Failing that, against the directory containing the test script (JMX file). + +
+No +
SubjectThe e-mail message subject. + +No +
Suppress Subject HeaderIf selected, the "Subject:" header is omitted from the mail that is sent. +This is different from sending an empty "Subject:" header, though some e-mail clients may display it identically. + +No +
Include timestamp in subjectIncludes the System.currentTimemilis() in the subject line. + +No +
Add HeaderAdditional headers can be defined using this button. + +No +
MessageThe message body. + +No +
Send plain body (i.e. not multipart/mixed) +If selected, then send the body as a plain message, i.e. not multipart/mixed, if possible. +If the message body is empty and there is a single file, then send the file contents as the message body. +Note: If the message body is not empty, and there is at least one attached file, then the body is sent as multipart/mixed. + + +No +
Attach filesFiles to be attached to the message. + +No +
Send .emlIf set, the .eml file will be sent instead of the entries in the Subject, Message, and Attached files + +No +
Calculate message sizeCalculates the message size and stores it in the sample result. + +No +
Enable debug logging?If set, then the "mail.debug" property is set to "true" + +No +
+

+

+
+ + + + +
+ +

+18.1.20 OS Process Sampler +

+
+
+ + +

+ +The OS Process Sampler is a sampler that can be used to execute commands on the local machine. +
+ + +It should allow execution of any command that can be run from the command line. +
+ + +Validation of the return code can be enabled, and the expected return code can be specified. +
+ + + +

+ + +

+ +Note that OS shells generally provide command-line parsing. +This varies between OSes, but generally the shell will split parameters on white-space. +Some shells expand wild-card file names; some don't. +The quoting mechanism also varies between OSes. +The sampler deliberately does not do any parsing or quote handling. +The command and its parameters must be provided in the form expected by the executable. +This means that the sampler settings will not be portable between OSes. + +

+ + +

+ +Many OSes have some built-in commands which are not provided as separate executables. +For example the Windows DIR command is part of the command interpreter (CMD.EXE). +These built-ins cannot be run as independent programs, but have to be provided as arguments to the appropriate command interpreter. + +
+ + +For example, the Windows command-line: + + +DIR C:\TEMP + + + needs to be specified as follows: + +

+
+command:   CMD
+Param 1:   /C
+Param 2:   DIR
+Param 3:   C:\TEMP
+
+
+ + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
Check Return CodeIf checked, sampler will compare return code with Expected Return Code. + +No +
Expected Return CodeExpected return code for System Call, required if "Check Return Code" is checked. + +No +
DirectoryDirectory from which command will be executed, defaults to folder referenced by "user.dir" System property + +No +
CommandThe System command or shell to execute. + +Yes +
OS Process ParametersParameters passed to process. + +No +
Environment ParametersKey/Value pairs added to environment when running command. + +No +
+

+

+
+ +^ + +
+

+

+ + + + +
+ +18.2 Logic Controllers +
+
+ + + +
+Logic Controllers determine the order in which Samplers are processed. + + + +
+ + + + +
+ +

+18.2.1 Simple Controller +

+
+
+ + +

+The Simple Logic Controller lets you organize your Samplers and other +Logic Controllers. Unlike other Logic Controllers, this controller provides no functionality beyond that of a +storage device. +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
+

+ +

Using the Simple Controller

+ + +

+ +Download + + this example (see Figure 6). +In this example, we created a Test Plan that sends two Ant HTTP requests and two +Log4J HTTP requests. We grouped the Ant and Log4J requests by placing them inside +Simple Logic Controllers. Remember, the Simple Logic Controller has no effect on how JMeter +processes the controller(s) you add to it. So, in this example, JMeter sends the requests in the +following order: Ant Home Page, Ant News Page, Log4J Home Page, Log4J History Page. +Note, the File Reporter +is configured to store the results in a file named "simple-test.dat" in the current directory. +

+ + +


+Figure 6 Simple Controller Example +

+ + + +

+
+ + + + +
+ +

+18.2.2 Loop Controller +

+
+
+

+If you add Generative or Logic Controllers to a Loop Controller, JMeter will +loop through them a certain number of times, in addition to the loop value you +specified for the Thread Group. For example, if you add one HTTP Request to a +Loop Controller with a loop count of two, and configure the Thread Group loop +count to three, JMeter will send a total of 2 * 3 = 6 HTTP Requests. + +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
Loop Count + The number of times the subelements of this controller will be iterated each time + through a test run. + +

+ +Special Case: + + The Loop Controller embedded in the + +Thread Group + + + element behaves slightly differently. Unless set to forever, it stops the test after + the given number of iterations have been done. +

+
+Yes, unless "Forever" is checked +
+

+ +

Looping Example

+ + + +

+ +Download + + this example (see Figure 4). +In this example, we created a Test Plan that sends a particular HTTP Request +only once and sends another HTTP Request five times. +

+ + + +


+Figure 4 - Loop Controller Example +

+ + + +

+We configured the Thread Group for a single thread and a loop count value of +one. Instead of letting the Thread Group control the looping, we used a Loop +Controller. You can see that we added one HTTP Request to the Thread Group and +another HTTP Request to a Loop Controller. We configured the Loop Controller +with a loop count value of five. +

+ + +

+JMeter will send the requests in the following order: Home Page, News Page, +News Page, News Page, News Page, and News Page. Note, the File Reporter +is configured to store the results in a file named "loop-test.dat" in the current directory. +

+ + + +

+
+ + + + +
+ +

+18.2.3 Once Only Controller +

+
+
+ + +

+The Once Only Logic Controller tells JMeter to process the controller(s) inside it only once, and pass over any requests under it +during further iterations through the test plan. +

+ + + +

+The Once Only Controller will now execute always during the first iteration of any looping parent controller. Thus, if the Once Only Controller is placed under a Loop Controller specified to loop 5 times, then the Once Only Controller will execute only on the first iteration through the Loop Controller (ie, every 5 times). Note this means the Once Only Controller will still behave as previously expected if put under a Thread Group (runs only once per test), but now the user has more flexibility in the use of the Once Only Controller. +

+ + + +

+For testing that requires a login, consider placing the login request in this controller since each thread only needs +to login once to establish a session. +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
+

+ +

Once Only Example

+ + +

+ +Download + + this example (see Figure 5). +In this example, we created a Test Plan that has two threads that send HTTP request. +Each thread sends one request to the Home Page, followed by three requests to the Bug Page. +Although we configured the Thread Group to iterate three times, each JMeter thread only +sends one request to the Home Page because this request lives inside a Once Only Controller. +

+ + +


+Figure 5. Once Only Controller Example +

+ + +

+Each JMeter thread will send the requests in the following order: Home Page, Bug Page, +Bug Page, Bug Page. Note, the File Reporter is configured to store the results in a file named "loop-test.dat" in the current directory. +

+ + + +

+ + +
The behaviour of the Once Only controller under anything other than the +Thread Group or a Loop Controller is not currently defined. Odd things may happen. +
+

+

+
+ + + + +
+ +

+18.2.4 Interleave Controller +

+
+
+

+If you add Generative or Logic Controllers to an Interleave Controller, JMeter will alternate among each of the +other controllers for each loop iteration. +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
nameDescriptive name for this controller that is shown in the tree. + +No +
ignore sub-controller blocksIf checked, the interleave controller will treat sub-controllers like single request elements and only allow one request per controller at a time. + +No +
+

+ +

Simple Interleave Example

+ + + +

+ +Download + + this example (see Figure 1). In this example, +we configured the Thread Group to have two threads and a loop count of five, for a total of ten +requests per thread. See the table below for the sequence JMeter sends the HTTP Requests. +

+ + + +


+Figure 1 - Interleave Controller Example 1 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +Loop Iteration + + + +Each JMeter Thread Sends These HTTP Requests + +
+ +1 + + + +News Page + +
+ +1 + + + +Log Page + +
+ +2 + + + +FAQ Page + +
+ +2 + + + +Log Page + +
+ +3 + + + +Gump Page + +
+ +3 + + + +Log Page + +
+ +4 + + + +Because there are no more requests in the controller,
JMeter starts over and sends the first HTTP Request, which is the News Page. +
+
+ +4 + + + +Log Page + +
+ +5 + + + +FAQ Page + +
+ +5 + + + +Log Page + +
+ + + + + +

Useful Interleave Example

+ + + +

+ +Download + + another example (see Figure 2). In this +example, we configured the Thread Group +to have a single thread and a loop count of eight. Notice that the Test Plan has an outer Interleave Controller with +two Interleave Controllers inside of it. +

+ + + +


+ + Figure 2 - Interleave Controller Example 2 + +

+ + + +

+The outer Interleave Controller alternates between the +two inner ones. Then, each inner Interleave Controller alternates between each of the HTTP Requests. Each JMeter +thread will send the requests in the following order: Home Page, Interleaved, Bug Page, Interleaved, CVS Page, Interleaved, and FAQ Page, Interleaved. +Note, the File Reporter is configured to store the results in a file named "interleave-test2.dat" in the current directory. +

+ + + +


+ + Figure 3 - Interleave Controller Example 3 + +

+ + +

+If the two interleave controllers under the main interleave controller were instead simple controllers, then the order would be: Home Page, CVS Page, Interleaved, Bug Page, FAQ Page, Interleaved. However, if "ignore sub-controller blocks" was checked on the main interleave controller, then the order would be: Home Page, Interleaved, Bug Page, Interleaved, CVS Page, Interleaved, and FAQ Page, Interleaved. +

+ + +

+
+ + + + +
+ +

+18.2.5 Random Controller +

+
+
+ + +

+The Random Logic Controller acts similarly to the Interleave Controller, except that +instead of going in order through its sub-controllers and samplers, it picks one +at random at each pass. +

+ + +

+ + +
Interactions between multiple controllers can yield complex behavior. +This is particularly true of the Random Controller. Experiment before you assume +what results any given interaction will give +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
+

+

+
+ + + + +
+ +

+18.2.6 Random Order Controller +

+
+
+ + +

+The Random Order Controller is much like a Simple Controller in that it will execute each child + element at most once, but the order of execution of the nodes will be random. +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
+

+

+
+ + + + +
+ +

+18.2.7 Throughput Controller +

+
+
+ + +

+ + + +This controller is badly named, as it does not control throughput. + + +Please refer to the +Constant Throughput Timer + for an element that can be used to adjust the throughput. + +

+ + +

+The Throughput Controller allows the user to control how often it is executed. There are two modes - percent execution and total executions. Percent executions causes the controller to execute a certain percentage of the iterations through the test plan. Total +executions causes the controller to stop executing after a certain number of executions have occurred. Like the Once Only Controller, this +setting is reset when a parent Loop Controller restarts. + +

+ + +

Control Panel

+
+

+ + +
The Throughput Controller can yield very complex behavior when combined with other controllers - in particular with interleave or random controllers as parents (also very useful). +
+

+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
Execution StyleWhether the controller will run in percent executions or total executions mode. + +Yes +
ThroughputA number. for percent execution mode, a number from 0-100 that indicates the percentage of times the controller will execute. "50" means the controller will execute during half the iterations throught the test plan. for total execution mode, the number indicates the total number of times the controller will execute. + +Yes +
Per UserIf checked, per user will cause the controller to calculate whether it should execute on a per user (per thread) basis. if unchecked, then the calculation will be global for all users. for example, if using total execution mode, and uncheck "per user", then the number given for throughput will be the total number of executions made. if "per user" is checked, then the total number of executions would be the number of users times the number given for throughput. + +No +
+

+

+
+ + + + +
+ +

+18.2.8 Runtime Controller +

+
+
+ + +

+The Runtime Controller controls how long its children are allowed to run. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree, and used to name the transaction. + +Yes +
Runtime (seconds)Desired runtime in seconds + +Yes +
+

+

+
+ + + + +
+ +

+18.2.9 If Controller +

+
+
+ + +

+The If Controller allows the user to control whether the test elements below it (its children) are run or not. +

+ + +

+ + Prior to JMeter 2.3RC3, the condition was evaluated for every runnable element contained in the controller. + This sometimes caused unexpected behaviour, so 2.3RC3 was changed to evaluate the condition only once on initial entry. + However, the original behaviour is also useful, so versions of JMeter after 2.3RC4 have an additional + option to select the original behaviour. + +

+ + +

+ + Versions of JMeter after 2.3.2 allow the script to be processed as a variable expression, rather than requiring Javascript. + It was always possible to use functions and variables in the Javascript condition, so long as they evaluated to "true" or "false"; + now this can be done without the overhead of using Javascript as well. For example, previously one could use the condition: + + +${__jexl(${VAR} == 23)} + + and this would be evaluated as true/false, the result would then be passed to Javascript + which would then return true/false. If the Variable Expression option is selected, then the expression is evaluated + and compared with "true", without needing to use Javascript. + Also, variable expressions can return any value, whereas the + Javascript condition must return "true"/"false" or an error is logged. + +

+ + +

+ + +
+ No variables are made available to the script when the condition is interpreted as Javascript. + If you need access to such variables, then select "Interpret Condition as Variable Expression?" and use + a __javaScript() function call. You can then use the objects "vars", "log", "ctx" etc. in the script. + +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
Condition (default Javascript)By default the condition is interpreted as + +Javascript + + code that returns "true" or "false", + but this can be overriden (see below) + +Yes +
Interpret Condition as Variable Expression?If this is selected, then the condition must be an expression that evaluates to "true" (case is ignored). + For example, + +${FOUND} + + or + +${__jexl(${VAR} > 100)} + +. + Unlike the Javascript case, the condition is only checked to see if it matches "true" (case is ignored). + + +Yes +
Evaluate for all children + Should condition be evaluated for all children? + If not checked, then the condition is only evaluated on entry. + + +Yes +
+

+

+ +Examples (Javascript): + + + +

    + + +
  • +${COUNT} < 10 +
  • + + +
  • +"${VAR}" == "abcd" +
  • + + +
  • +${JMeterThread.last_sample_ok} (check if last sample succeeded) +
  • + + +
+ + If there is an error interpreting the code, the condition is assumed to be false, and a message is logged in jmeter.log. + +

+

+ +Examples (Variable Expression): + + + +

    + + +
  • +${__jexl(${COUNT} < 10)} +
  • + + +
  • +${RESULT} +
  • + + +
+ + +

+

+
+ + + + +
+ +

+18.2.10 While Controller +

+
+
+ + +

+ +The While Controller runs its children until the condition is "false". + +

+ + + +

+Possible condition values: +

+ + +
    + + +
  • +blank - exit loop when last sample in loop fails +
  • + + +
  • +LAST - exit loop when last sample in loop fails. +If the last sample just before the loop failed, don't enter loop. +
  • + + +
  • +Otherwise - exit (or don't enter) the loop when the condition is equal to the string "false" +
  • + + +
+ + +

+ + +
+The condition can be any variable or function that eventually evaluates to the string "false". +This allows the use of JavaScript, BeanShell, properties or variables as needed. + +
+

+ + +
+ + + +

+ + +
+Note that the is evaluated twice, once before starting sampling children and once at end of children sampling, so putting +non idempotent functions in Condition (like __counter) can introduce issues. + +
+

+ + +
+ + +For example: + +
    + + +
  • +${VAR} - where VAR is set to false by some other test element +
  • + + +
  • +${__javaScript(${C}==10)} +
  • + + +
  • +${__javaScript("${VAR2}"=="abcd")} +
  • + + +
  • +${_P(property)} - where property is set to "false" somewhere else +
  • + + +
+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree, and used to name the transaction. + +Yes +
Conditionblank, LAST, or variable/function + +Yes +
+

+

+
+ + + + +
+ +

+18.2.11 Switch Controller +

+
+
+ + +

+ +The Switch Controller acts like the +Interleave Controller + +in that it runs one of the subordinate elements on each iteration, but rather than +run them in sequence, the controller runs the element defined by the switch value. + +

+ + +

+ +Note: In versions of JMeter after 2.3.1, the switch value can also be a name. + +

+ + +

+If the switch value is out of range, it will run the zeroth element, +which therefore acts as the default for the numeric case. +It also runs the zeroth element if the value is the empty string. +

+ + +

+ +If the value is non-numeric (and non-empty), then the Switch Controller looks for the +element with the same name (case is significant). +If none of the names match, then the element named "default" (case not significant) is selected. +If there is no default, then no element is selected, and the controller will not run anything. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree, and used to name the transaction. + +Yes +
Switch ValueThe number (or name) of the subordinate element to be invoked. Elements are numbered from 0. + +Yes +
+

+

+
+ + + + +
+ +

+18.2.12 ForEach Controller +

+
+
+

+A ForEach controller loops through the values of a set of related variables. +When you add samplers (or controllers) to a ForEach controller, every sample sample (or controller) +is executed one or more times, where during every loop the variable has a new value. +The input should consist of several variables, each extended with an underscore and a number. +Each such variable must have a value. +So for example when the input variable has the name inputVar, the following variables should have been defined: + +

    + + +
  • +inputVar_1 = wendy +
  • + + +
  • +inputVar_2 = charles +
  • + + +
  • +inputVar_3 = peter +
  • + + +
  • +inputVar_4 = john +
  • + + +
+ + +

+Note: the "_" separator is now optional. +

+ +When the return variable is given as "returnVar", the collection of samplers and controllers under the ForEach controller will be executed 4 consecutive times, +with the return variable having the respective above values, which can then be used in the samplers. + +

+ + +

+ +It is especially suited for running with the regular expression post-processor. +This can "create" the necessary input variables out of the result data of a previous request. +By omitting the "_" separator, the ForEach Controller can be used to loop through the groups by using +the input variable refName_g, and can also loop through all the groups in all the matches +by using an input variable of the form refName_${C}_g, where C is a counter variable. + +

+ + +

+ + +
The ForEach Controller does not run any samples if inputVar_1 is null. +This would be the case if the Regular Expression returned no matches. +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
Input variable prefixPrefix for the variable names to be used as input. + +Yes +
Output variable + The name of the variable which can be used in the loop for replacement in the samplers + +Yes +
Use SeparatorIf not checked, the "_" separator is omitted. + +Yes +
+

+ +

ForEach Example

+ + + +

+ +Download + + this example (see Figure 7). +In this example, we created a Test Plan that sends a particular HTTP Request +only once and sends another HTTP Request to every link that can be found on the page. +

+ + + +


+Figure 7 - ForEach Controller Example +

+ + + +

+We configured the Thread Group for a single thread and a loop count value of +one. You can see that we added one HTTP Request to the Thread Group and +another HTTP Request to the ForEach Controller. +

+ + +

+After the first HTTP request, a regular expression extractor is added, which extracts all the html links +out of the return page and puts them in the inputVar variable +

+ + +

+In the ForEach loop, a HTTP sampler is added which requests all the links that were extracted from the first returned HTML page. + +

+ +

ForEach Example

+ + +

+Here is + +another example + + you can download. +This has two Regular Expressions and ForEach Controllers. +The first RE matches, but the second does not match, +so no samples are run by the second ForEach Controller +

+ + +


+Figure 8 - ForEach Controller Example 2 +

+ + +

+The Thread Group has a single thread and a loop count of two. + +

+

+ +Sample 1 uses the JavaTest Sampler to return the string "a b c d". + +

+

+The Regex Extractor uses the expression + +(\w)\s + + which matches a letter followed by a space, +and returns the letter (not the space). Any matches are prefixed with the string "inputVar". + +

+

+The ForEach Controller extracts all variables with the prefix "inputVar_", and executes its +sample, passing the value in the variable "returnVar". In this case it will set the variable to the values "a" "b" and "c" in turn. + +

+

+The For 1 Sampler is another Java Sampler which uses the return variable "returnVar" as part of the sample Label +and as the sampler Data. + +

+

+Sample 2, Regex 2 and For 2 are almost identical, except that the Regex has been changed to "(\w)\sx", +which clearly won't match. Thus the For 2 Sampler will not be run. + +

+ + +

+
+ + + + +
+ +

+18.2.13 Module Controller +

+
+
+ + +

+ +The Module Controller provides a mechanism for substituting test plan fragments into the current test plan at run-time. + +

+ + +

+ +A test plan fragment consists of a Controller and all the test elements (samplers etc) contained in it. +The fragment can be located in any Thread Group, or on the +WorkBench +. +If the fragment is located in a Thread Group, then its Controller can be disabled to prevent the fragment being run +except by the Module Controller. +Or you can store the fragments in a dummy Thread Group, and disable the entire Thread Group. + +

+ + +

+ +There can be multiple fragments, each with a different series of +samplers under them. The module controller can then be used to easily switch between these multiple test cases simply by choosing +the appropriate controller in its drop down box. This provides convenience for running many alternate test plans quickly and easily. + +

+ + +

+ +A fragment name is made up of the Controller name and all its parent names. +For example: + +

+
+Test Plan / Protocol: JDBC / Control / Interleave Controller (Module1)
+
+
+ +Any + +fragments used by the Module Controller must have a unique name + +, +as the name is used to find the target controller when a test plan is reloaded. +For this reason it is best to ensure that the Controller name is changed from the default +- as shown in the example above - +otherwise a duplicate may be accidentally created when new elements are added to the test plan. + +

+ + +

Control Panel

+
+

+ + +
The Module Controller should not be used with remote testing or non-gui testing in conjunction with Workbench components since the Workbench test elements are not part of test plan .jmx files. Any such test will fail. +
+

+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
Module to RunThe module controller provides a list of all controllers loaded into the gui. Select + the one you want to substitute in at runtime. + +Yes +
+

+

+
+ + + + +
+ +

+18.2.14 Include Controller +

+
+
+ + +

+ +The include controller is designed to use an external jmx file. To use it, create a Test Fragment +underneath the Test Plan and add any desired samplers, controllers etc. below it. +Then save the Test Plan. The file is now ready to be included as part of other Test Plans. + +

+ + +

+ +For convenience, a Thread Group can also be added in the external JMX file for debugging purposes. +A Module Controller can be used to reference the Test Fragment. The Thread Group will be ignored during the +include process. + +

+ + +

+ +If the test uses a Cookie Manager or User Defined Variables, these should be placed in the top-level +test plan, not the included file, otherwise they are not guaranteed to work. + +

+ + +

+ + +
+This element does not support variables/functions in the filename field. +
+ + +However, if the property + +includecontroller.prefix + + is defined, +the contents are used to prefix the pathname. + +
+

+ + +

+ + +
+When using IncludeController and including the same JMX file, ensure you name the IncludeController differently to avoid facing known issue 50898. + +
+

+ + +

+ +If the file cannot be found at the location given by prefix+filename, then the controller +attempts to open the fileName relative to the JMX launch directory (versions of JMeter after 2.3.4). + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
FilenameThe file to include. + +Yes +
+

+

+
+ + + + +
+ +

+18.2.15 Transaction Controller +

+
+
+ + +

+ + The Transaction Controller generates an additional + sample which measures the overall time taken to perform the nested test elements. + Note that this time by default includes all processing within the controller scope, not just + the samples, this can be changed by unchecking "Include duration of timer and pre-post processors in generated sample". + +

+ + +

+ + For JMeter versions after 2.3, there are two modes of operation + +

    + + +
  • +additional sample is added after the nested samples +
  • + + +
  • +additional sample is added as a parent of the nested samples +
  • + + +
+ + +

+ + +

+ + The generated sample time includes all the times for the nested samplers, + +and any timers etc. + + + Depending on the clock resolution, it may be slightly longer than the sum of the individual samplers plus timers. + The clock might tick after the controller recorded the start time but before the first sample starts. + Similarly at the end. + +

+ + +

+The generated sample is only regarded as successful if all its sub-samples are successful. +

+ + +

+ + In parent mode, the individual samples can still be seen in the Tree View Listener, + but no longer appear as separate entries in other Listeners. + Also, the sub-samples do not appear in CSV log files, but they can be saved to XML files. + +

+ + +

+ + +
+ In parent mode, Assertions (etc) can be added to the Transaction Controller. + However by default they will be applied to both the individual samples and the overall transaction sample. + To limit the scope of the Assertions, use a Simple Controller to contain the samples, and add the Assertions + to the Simple Controller. + Parent mode controllers do not currently properly support nested transaction controllers of either type. + +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree, and used to name the transaction. + +Yes +
Generate Parent Sample + If checked, then the sample is generated as a parent of the other samples, + otherwise the sample is generated as an independent sample. + + +Yes +
Include duration of timer and pre-post processors in generated sample + Whether to include timer, pre- and post-processing delays in the generated sample. + Default is true to be compatible with the behaviour in previous versions of JMeter. + Setting it to false is a better option to get only response time of the sample. + + +Yes +
+

+

+
+ + + + +
+ +

+18.2.16 Recording Controller +

+
+
+ + +

+The Recording Controller is a place holder indicating where the proxy server should +record samples to. During test run, it has no effect, similar to the Simple Controller. But during +recording using the +HTTP Proxy Server +, all recorded samples will by default +be saved under the Recording Controller. +

+ + + +

Control Panel

+
+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
+

+

+
+ +^ + +
+

+

+ + + + +
+ +18.3 Listeners +
+
+ + + +
+ + +Most of the listeners perform several roles in addition to "listening" +to the test results. +They also provide means to view, save, and read saved test results. + +

+Note that Listeners are processed at the end of the scope in which they are found. +

+ + +

+ +The saving and reading of test results is generic. The various +listeners have a panel whereby one can specify the file to +which the results will be written (or read from). +By default, the results are stored as XML +files, typically with a ".jtl" extension. +Storing as CSV is the most efficient option, but is less detailed than XML (the other available option). + +

+ + +

+ + + +Listeners do + +not + + process sample data in non-GUI mode, but the raw data will be saved if an output +file has been configured. + + +In order to analyse the data generated by a non-GUI test run, you need to load the file into the appropriate +Listener. + +

+ + +

+ + +
+To read existing results and display them, use the file panel Browse button to open the file. + +
+

+ + +

+ +Versions of JMeter up to 2.3.2 + +used to clear any current data + + before loading the new file. +
+ + +This is no longer done, thus + +allowing files to be merged + +. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. + +

+ + +

+Results can be read from XML or CSV format files. +When reading from CSV results files, the header (if present) is used to determine which fields are present. + + +In order to interpret a header-less CSV file correctly, the appropriate properties must be set in jmeter.properties. + + + +

+ + +

+ + +
+The file name can contain function and/or variable references. +However variable references do not work in client-server mode (functions work OK). + +
+

+ + +

+ +Listeners can use a lot of memory if there are a lot of samples. + + +Most of the listeners currently keep a copy of every sample in their scope, apart from: + +

+ + +
    + + +
  • +Simple Data Writer +
  • + + +
  • +BeanShell/BSF Listener +
  • + + +
  • +Mailer Visualizer +
  • + + +
  • +Monitor Results +
  • + + +
  • +Summary Report +
  • + + +
+ + +

+ +The following Listeners no longer need to keep copies of every single sample. +Instead, samples with the same elapsed time are aggregated. +Less memory is now needed, especially if most samples only take a second or two at most. + +

+ + +
    + + +
  • +Aggregate Report +
  • + + +
  • +Aggregate Graph +
  • + + +
  • +Distribution Graph +
  • + + +
+ + +

+To minimise the amount of memory needed, use the Simple Data Writer, and use the CSV format. +

+ + +

+ + +

+ + +
+Versions of JMeter after 2.3.1 allow JMeter variables to be saved to the output files. +This can only be specified using a property. +See the + +Listener Sample Variables + + for details + +
+

+ +For full details on setting up the default items to be saved +see the + +Listener Default Configuration + + documentation. +For details of the contents of the output files, +see the + +CSV log + + format or +the + +XML log + + format. + +

+ + +

+ + +
The entries in jmeter.properties are used to define the defaults; +these can be overriden for individual listeners by using the Configure button, +as shown below. +The settings in jmeter.properties also apply to the listener that is added +by using the -l command-line flag. + +
+

+ + +

+ + The figure below shows an example of the result file configuration panel + +


+Result file configuration panel +

+ + +

+ + +

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
FilenameName of the file containing sample results. + The file name can be specified using either a relative or an absolute path name. + Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). + Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). + If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), + then the path is assumed to be relative to the JMX file location. + + +No +
Browse...File Browse Button + +No +
ErrorsSelect this to write/read only results with errors + +No +
SuccessesSelect this to write/read only results without errors. + If neither Errors nor Successes is selected, then all results are processed. + +No +
ConfigureConfigure Button, see below + +No +
+

+ + +
+ + + + +
+ +

+18.3.1 Sample Result Save Configuration +

+
+
+ + +

+ +Listeners can be configured to save different items to the result log files (JTL) by using the Config popup as shown below. +The defaults are defined as described in the + +Listener Default Configuration + + documentation. +Items with (CSV) after the name only apply to the CSV format; items with (XML) only apply to XML format. +CSV format cannot currently be used to save any items that include line-breaks. + +

+ + +

+ +Note that cookies, method and the query string are saved as part of the "Sampler Data" option. + +

+ + +

Control Panel

+
+

+
+ + + + +
+ +

+18.3.3 Graph Results +

+
+
+

+The Graph Results listener generates a simple graph that plots all sample times. Along +the bottom of the graph, the current sample (black), the current average of all samples(blue), the +current standard deviation (red), and the current throughput rate (green) are displayed in milliseconds. +

+ + +

+The throughput number represents the actual number of requests/minute the server handled. This calculation +includes any delays you added to your test and JMeter's own internal processing time. The advantage +of doing the calculation like this is that this number represents something +real - your server in fact handled that many requests per minute, and you can increase the number of threads +and/or decrease the delays to discover your server's maximum throughput. Whereas if you made calculations +that factored out delays and JMeter's processing, it would be unclear what you could conclude from that +number. +

+

Control Panel

+
+

+The following table briefly describes the items on the graph. +Further details on the precise meaning of the statistical terms can be found on the web + - e.g. Wikipedia - or by consulting a book on statistics. + +

+
    + + +
  • +Data - plot the actual data values +
  • + + +
  • +Average - plot the Average +
  • + + +
  • +Median - plot the + +Median + + (midway value) +
  • + + +
  • +Deviation - plot the + +Standard Deviation + + (a measure of the variation) +
  • + + +
  • +Throughput - plot the number of samples per unit of time +
  • + + +
+

+The individual figures at the bottom of the display are the current values. + "Latest Sample" is the current elapsed sample time, shown on the graph as "Data". +

+

+
+ + + + +
+ +

+18.3.4 Spline Visualizer +

+
+
+ + +

+ +The Spline Visualizer provides a view of all sample times from the start +of the test till the end, regardless of how many samples have been taken. The spline +has 10 points, each representing 10% of the samples, and connected using spline +logic to show a single continuous line. + +

+ + +

+ +The graph is automatically scaled to fit within the window. +This needs to be borne in mind when comparing graphs. + +

+ + +

Control Panel

+
+

+
+ + + + +
+ +

+18.3.5 Assertion Results +

+
+
+

+The Assertion Results visualizer shows the Label of each sample taken. +It also reports failures of any + +Assertions + + that +are part of the test plan. +

+

Control Panel

+
+

See Also: +

+

+

+
+ + + + +
+ +

+18.3.6 View Results Tree +

+
+
+The View Results Tree shows a tree of all sample responses, allowing you to view the +response for any sample. In addition to showing the response, you can see the time it took to get +this response, and some response codes. +Note that the Request panel only shows the headers added by JMeter. +It does not show any headers (such as Host) that may be added by the HTTP protocol implementation. + +

+ +There are several ways to view the response, selectable by a drop-down box at the bottom of the left hand panel. +

+ + +
    + + +
  • +HTML +
  • + + +
  • +HTML (download resources) +
  • + + +
  • +JSON +
  • + + +
  • +Regexp Tester +
  • + + +
  • +Text +
  • + + +
  • +XML +
  • + + +
+ + +

+Scroll automatically? option permit to have last node display in tree selection +

+ + +

+ +Additional renderers can be created. +The class must implement the interface + +org.apache.jmeter.visualizers.ResultRenderer + + +and/or extend the abstract class + +org.apache.jmeter.visualizers.SamplerResultTab + +, and the +compiled code must be available to JMeter (e.g. by adding it to the lib/ext directory). + +

+ + +

+ +The default "Text" view shows all of the text contained in the response. +Note that this will only work if the response content-type is considered to be text. +If the content-type begins with any of the following, it is considered as binary, +otherwise it is considered to be text. + +

+
+image/
+audio/
+video/
+
+
+ +If there is no content-type provided, then the content +will not be displayed in the any of the Response Data panels. +You can use +Save Responses to a file + to save the data in this case. +Note that the response data will still be available in the sample result, +so can still be accessed using Post-Processors. + +

+ + +

+If the response data is larger than 200K, then it won't be displayed. +To change this limit, set the JMeter property + +view.results.tree.max_size + +. +You can also use save the entire response to a file using + +Save Responses to a file +. + +

+ + +

+The HTML view attempts to render the response as +HTML. The rendered HTML is likely to compare poorly to the view one +would get in any web browser; however, it does provide a quick +approximation that is helpful for initial result evaluation. +No images etc are downloaded. +If the HTML (download embedded resources) option is selected, the renderer +may download images and style-sheets etc referenced by the HTML. + +

+ + +

+The XML view will show response in tree style. +Any DTD nodes or Prolog nodes will not show up in tree; however, response may contain those nodes. + +

+ + +

+The JSON view will show the response in tree style (also handles JSON embedded in JavaScript). +

+ + +

+ +Most of the views also allow the displayed data to be searched; the result of the search will be high-lighted +in the display above. For example the Control panel screenshot below shows one result of searching for "Java". +Note that the search operates on the visible text, so you may get different results when searching +the Text and HTML views. + +

+ + +

+The "Regexp Tester" view only works for text responses. It shows the plain text in the upper panel. +The "Test" button allows the user to apply the Regular Expression to the upper panel and the results +will be displayed in the lower panel. +For example, the RE + +(JMeter\w*).* + + applied to the current JMeter home page gives the following output: + +

+ + +
+
+Match count: 26
+Match[1][0]=JMeter - Apache JMeter</title>
+Match[1][1]=JMeter
+Match[2][0]=JMeter" title="JMeter" border="0"/></a>
+Match[2][1]=JMeter
+Match[3][0]=JMeterCommitters">Contributors</a>
+Match[3][1]=JMeterCommitters
+... and so on ...
+
+
+ + +

+ +The first number in [] is the match number; the second number is the group. +Group [0] is whatever matched the whole RE. +Group [1] is whatever matched the 1st group, i.e. (JMeter\w*) in this case. +See Figure 9b (below). + +

+ + +

Control Panel

+
+

+ + The Control Panel (above) shows an example of an HTML display. + Figure 9 (below) shows an example of an XML display. + +


+Figure 9 Sample XML display +

+ + +


+Figure 9a Sample Regexp Test display +

+ + +

+

+
+ + + + +
+ +

+18.3.7 Aggregate Report +

+
+
+The aggregate report creates a table row for each differently named request in your +test. For each request, it totals the response information and provides request count, min, max, +average, error rate, approximate throughput (request/second) and Kilobytes per second throughput. +Once the test is done, the throughput is the actual through for the duration of the entire test. + +

+ +The thoughput is calculated from the point of view of the sampler target +(e.g. the remote server in the case of HTTP samples). +JMeter takes into account the total time over which the requests have been generated. +If other samplers and timers are in the same thread, these will increase the total time, +and therefore reduce the throughput value. +So two identical samplers with different names will have half the throughput of two samplers with the same name. +It is important to choose the sampler names correctly to get the best results from +the Aggregate Report. + +

+ + +

+ +Calculation of the + +Median + + and 90% Line (90 + +th + + + +percentile + +) values requires additional memory. +For JMeter 2.3.4 and earlier, details of each sample were saved separately, which meant a lot of memory was needed. +JMeter now combines samples with the same elapsed time, so far less memory is used. +However, for samples that take more than a few seconds, the probability is that fewer samples will have identical times, +in which case more memory will be needed. +See the +Summary Report + for a similar Listener that does not store individual samples and so needs constant memory. + +

+ + +
    + + +
  • +Label - The label of the sample. +If "Include group name in label?" is selected, then the name of the thread group is added as a prefix. +This allows identical labels from different thread groups to be collated separately if required. + +
  • + + +
  • +# Samples - The number of samples with the same label +
  • + + +
  • +Average - The average time of a set of results +
  • + + +
  • +Median - The + +median + + is the time in the middle of a set of results. +50% of the samples took no more than this time; the remainder took at least as long. +
  • + + +
  • +90% Line - 90% of the samples took no more than this time. +The remaining samples at least as long as this. (90 + +th + + + +percentile + +) +
  • + + +
  • +Min - The shortest time for the samples with the same label +
  • + + +
  • +Max - The longest time for the samples with the same label +
  • + + +
  • +Error % - Percent of requests with errors +
  • + + +
  • +Throughput - the + +Throughput + + is measured in requests per second/minute/hour. +The time unit is chosen so that the displayed rate is at least 1.0. +When the throughput is saved to a CSV file, it is expressed in requests/second, +i.e. 30.0 requests/minute is saved as 0.5. + +
  • + + +
  • +Kb/sec - The throughput measured in Kilobytes per second +
  • + + +
+ + +

+Times are in milliseconds. +

+ + +

Control Panel

+
+
+ + +

+ + The figure below shows an example of selecting the "Include group name" checkbox. + +


+Sample "Include group name" display +

+ + +

+ + +
+

+
+ + + + +
+ +

+18.3.8 View Results in Table +

+
+
+This visualizer creates a row for every sample result. +Like the +View Results Tree +, this visualizer uses a lot of memory. + +

+ +By default, it only displays the main (parent) samples; it does not display the sub-samples (child samples). +Versions of JMeter after 2.5.1 have a "Child Samples?" check-box. +If this is selected, then the sub-samples are displayed instead of the main samples. + +

+ + +

Control Panel

+
+

+
+ + + + +
+ +

+18.3.9 Simple Data Writer +

+
+
+This listener can record results to a file +but not to the UI. It is meant to provide an efficient means of +recording data by eliminating GUI overhead. +When running in non-GUI mode, the -l flag can be used to create a data file. +The fields to save are defined by JMeter properties. +See the jmeter.properties file for details. + +

Control Panel

+
+

+
+ + + + +
+ +

+18.3.10 Monitor Results +

+
+
+ + +

+Monitor Results is a new Visualizer for displaying server +status. It is designed for Tomcat 5, but any servlet container +can port the status servlet and use this monitor. There are two primary +tabs for the monitor. The first is the "Health" tab, which will show the +status of one or more servers. The second tab labled "Performance" shows +the performance for one server for the last 1000 samples. The equations +used for the load calculation is included in the Visualizer. +

+ + +

+Currently, the primary limitation of the monitor is system memory. A +quick benchmark of memory usage indicates a buffer of 1000 data points for +100 servers would take roughly 10Mb of RAM. On a 1.4Ghz centrino +laptop with 1Gb of ram, the monitor should be able to handle several +hundred servers. +

+ + +

+As a general rule, monitoring production systems should take care to +set an appropriate interval. Intervals shorter than 5 seconds are too +aggressive and have a potential of impacting the server. With a buffer of +1000 data points at 5 second intervals, the monitor would check the server +status 12 times a minute or 720 times a hour. This means the buffer shows +the performance history of each machine for the last hour. +

+ + +

+ + +
+The monitor requires Tomcat 5 or above. +Use a browser to check that you can access the Tomcat status servlet OK. + +
+

+ + +

+ +For a detailed description of how to use the monitor, please refer to + + +Building a Monitor Test Plan + + + +

+ + +

Control Panel

+
+

+
+ + + + +
+ +

+18.3.11 Distribution Graph (alpha) +

+
+
+ + +

+The distribution graph will display a bar for every unique response time. Since the +granularity of System.currentTimeMillis() is 10 milliseconds, the 90% threshold should be +within the width of the graph. The graph will draw two threshold lines: 50% and 90%. +What this means is 50% of the response times finished between 0 and the line. The same +is true of 90% line. Several tests with Tomcat were performed using 30 threads for 600K +requests. The graph was able to display the distribution without any problems and both +the 50% and 90% line were within the width of the graph. A performant application will +generally produce results that clump together. A poorly written application that has +memory leaks may result in wild fluctuations. In those situations, the threshold lines +may be beyond the width of the graph. The recommended solution to this specific problem +is fix the webapp so it performs well. If your test plan produces distribution graphs +with no apparent clumping or pattern, it may indicate a memory leak. The only way to +know for sure is to use a profiling tool. +

+ + +

Control Panel

+
+

+
+ + + + +
+ +

+18.3.12 Aggregate Graph +

+
+
+The aggregate graph is similar to the aggregate report. The primary +difference is the aggregate graph provides an easy way to generate bar graphs and save +the graph as a PNG file. +

Control Panel

+
+
+ + +

+ + The figure below shows an example of settings to draw this graph. + +


+Aggregate graph settings +

+ + +

+ + +
+

+
+ + + + +
+ +

+18.3.13 Mailer Visualizer +

+
+
+

+The mailer visualizer can be set up to send email if a test run receives too many +failed responses from the server. +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
FromEmail address to send messages from. + +Yes +
Addressee(s)Email address to send messages to, comma-separated. + +Yes +
Success SubjectEmail subject line for success messages. + +No +
Success LimitOnce this number of successful responses is exceeded + + +after previously reaching the failure limit + +, a success email + is sent. The mailer will thus only send out messages in a sequence of failed-succeeded-failed-succeeded, etc. + +Yes +
Failure SubjectEmail subject line for fail messages. + +No +
Failure LimitOnce this number of failed responses is exceeded, a failure + email is sent - i.e. set the count to 0 to send an e-mail on the first failure. + +Yes +
HostIP address or host name of SMTP server (email redirector) + server. + +No +
PortPort of SMTP server (defaults to 25). + +No +
LoginLogin used to authenticate. + +No +
PasswordPassword used to authenticate. + +No +
Connection securityType of encryption for SMTP authentication (SSL, TLS or none). + +No +
Test MailPress this button to send a test mail + +No +
FailuresA field that keeps a running total of number + of failures so far received. + +No +
+

+

+
+ + + + +
+ +

+18.3.14 BeanShell Listener +

+
+
+ + +

+ +The BeanShell Listener allows the use of BeanShell for processing samples for saving etc. + +

+ + +

+ + + +For full details on using BeanShell, please see the + +BeanShell website. + + + + +

+ + +

+ +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + +No +
Reset bsh.Interpreter before each call + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see + +Best Practices - BeanShell scripting + +. + + +Yes +
ParametersParameters to pass to the BeanShell script. + The parameters are stored in the following variables: + +
    + + +
  • +Parameters - string containing the parameters as a single variable +
  • + + +
  • +bsh.args - String array containing parameters, split on white-space +
  • + + +
+
+No +
Script fileA file containing the BeanShell script to run. + The file name is stored in the script variable FileName + +No +
ScriptThe BeanShell script to run. The return value is ignored. + +Yes (unless script file is provided) +
+

+

+Before invoking the script, some variables are set up in the BeanShell interpreter: +

+
    + + +
  • +log - (Logger) - can be used to write to the log file +
  • + + +
  • +ctx - (JMeterContext) - gives access to the context +
  • + + +
  • +vars - ( + +JMeterVariables + +) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); +
  • + + +
  • +props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
  • +sampleResult, prev - (SampleResult) - gives access to the previous SampleResult +
  • + + +
  • +sampleEvent (SampleEvent) gives access to the current sample event +
  • + + +
+

+For details of all the methods available on each of the above variables, please check the Javadoc +

+

+If the property + +beanshell.listener.init + + is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script. +

+

+
+ + + + +
+ +

+18.3.15 Summary Report +

+
+
+The summary report creates a table row for each differently named request in your +test. This is similar to the +Aggregate Report + , except that it uses less memory. + +

+ +The thoughput is calculated from the point of view of the sampler target +(e.g. the remote server in the case of HTTP samples). +JMeter takes into account the total time over which the requests have been generated. +If other samplers and timers are in the same thread, these will increase the total time, +and therefore reduce the throughput value. +So two identical samplers with different names will have half the throughput of two samplers with the same name. +It is important to choose the sampler labels correctly to get the best results from +the Report. + +

+ + +
    + + +
  • +Label - The label of the sample. +If "Include group name in label?" is selected, then the name of the thread group is added as a prefix. +This allows identical labels from different thread groups to be collated separately if required. + +
  • + + +
  • +# Samples - The number of samples with the same label +
  • + + +
  • +Average - The average elapsed time of a set of results +
  • + + +
  • +Min - The lowest elapsed time for the samples with the same label +
  • + + +
  • +Max - The longest elapsed time for the samples with the same label +
  • + + +
  • +Std. Dev. - the + +Standard Deviation + + of the sample elapsed time +
  • + + +
  • +Error % - Percent of requests with errors +
  • + + +
  • +Throughput - the + +Throughput + + is measured in requests per second/minute/hour. +The time unit is chosen so that the displayed rate is at least 1.0. +When the throughput is saved to a CSV file, it is expressed in requests/second, +i.e. 30.0 requests/minute is saved as 0.5. + +
  • + + +
  • +Kb/sec - The throughput measured in Kilobytes per second +
  • + + +
  • +Avg. Bytes - average size of the sample response in bytes. (in JMeter 2.2 it wrongly showed the value in kB) +
  • + + +
+ + +

+Times are in milliseconds. +

+ + +

Control Panel

+
+
+ + +

+ + The figure below shows an example of selecting the "Include group name" checkbox. + +


+Sample "Include group name" display +

+ + +

+ + +
+

+
+ + + + +
+ +

+18.3.16 Save Responses to a file +

+
+
+ + +

+ + This test element can be placed anywhere in the test plan. + For each sample in its scope, it will create a file of the response Data. + The primary use for this is in creating functional tests, but it can also + be useful where the response is too large to be displayed in the + +View Results Tree + Listener. + The file name is created from the specified prefix, plus a number (unless this is disabled, see below). + The file extension is created from the document type, if known. + If not known, the file extension is set to 'unknown'. + If numbering is disabled, and adding a suffix is disabled, then the file prefix is + taken as the entire file name. This allows a fixed file name to be generated if required. + The generated file name is stored in the sample response, and can be saved + in the test log output file if required. + +

+ + +

+ + The current sample is saved first, followed by any sub-samples (child samples). + If a variable name is provided, then the names of the files are saved in the order + that the sub-samples appear. See below. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Filename PrefixPrefix for the generated file names; this can include a directory name. + Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). + Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). + If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), + then the path is assumed to be relative to the JMX file location. + + +Yes +
Variable Name + Name of a variable in which to save the generated file name (so it can be used later in the test plan). + If there are sub-samples then a numeric suffix is added to the variable name. + E.g. if the variable name is FILENAME, then the parent sample file name is saved in the variable FILENAME, + and the filenames for the child samplers are saved in FILENAME1, FILENAME2 etc. + + +No +
Save Failed Responses onlyIf selected, then only failed responses are saved + +Yes +
Save Successful Responses onlyIf selected, then only successful responses are saved + +Yes +
Don't add number to prefixIf selected, then no number is added to the prefix. If you select this option, make sure that the prefix is unique or the file may be overwritten. + +Yes +
Don't add suffixIf selected, then no suffix is added. If you select this option, make sure that the prefix is unique or the file may be overwritten. + +Yes +
+

+

+
+ + + + +
+ +

+18.3.17 BSF Listener +

+
+
+ + +

+ +The BSF Listener allows BSF script code to be applied to sample results. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
LanguageThe BSF language to be used + +Yes +
ParametersParameters to pass to the script. + The parameters are stored in the following variables: + +
    + + +
  • +Parameters - string containing the parameters as a single variable +
  • + + +
  • +args - String array containing parameters, split on white-space +
  • + + +
+
+No +
Script fileA file containing the script to run. + +No +
ScriptThe script to run. + +Yes (unless script file is provided) +
+

+

+ +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. + +

+

+ +Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. + +

+
    + + +
  • +log - (Logger) - can be used to write to the log file +
  • + + +
  • +Label - the String Label +
  • + + +
  • +Filename - the script file name (if any) +
  • + + +
  • +Parameters - the parameters (as a String) +
  • + + +
  • +args[] - the parameters as a String array (split on whitespace) +
  • + + +
  • +ctx - (JMeterContext) - gives access to the context +
  • + + +
  • +vars - ( + +JMeterVariables + +) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2"); +
  • + + +
  • +props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
  • +sampleResult, prev - (SampleResult) - gives access to the SampleResult +
  • + + +
  • +sampleEvent - (SampleEvent) - gives access to the SampleEvent +
  • + + +
  • +sampler - (Sampler)- gives access to the last sampler +
  • + + +
  • +OUT - System.out - e.g. OUT.println("message") +
  • + + +
+

+For details of all the methods available on each of the above variables, please check the Javadoc +

+

+
+ + + + +
+ +

+18.3.18.1 JSR223 Listener +

+
+
+ + +

+ +The JSR223 Listener allows JSR223 script code to be applied to sample results. +For details, see +BSF Listener +. + +

+ + +

+
+ + + + +
+ +

+18.3.18 Generate Summary Results +

+
+
+This test element can be placed anywhere in the test plan. +Generates a summary of the test run so far to the log file and/or +standard output. Both running and differential totals are shown. +Output is generated every n seconds (default 3 minutes) on the appropriate +time boundary, so that multiple test runs on the same time will be synchronised. +See jmeter.properties file for the summariser configuration items: + +
+
+# Define the following property to automatically start a summariser with that name
+# (applies to non-GUI mode only)
+#summariser.name=summary
+#
+# interval between summaries (in seconds) default 3 minutes
+#summariser.interval=180
+#
+# Write messages to log file
+#summariser.log=true
+#
+# Write messages to System.out
+#summariser.out=true
+
+
+ +This element is mainly intended for batch (non-GUI) runs. +The output looks like the following: + +
+
+label +   171 in  20.3s =    8.4/s Avg:  1129 Min:  1000 Max:  1250 Err:     0 (0.00%)
+label +   263 in  31.3s =    8.4/s Avg:  1138 Min:  1000 Max:  1250 Err:     0 (0.00%)
+label =   434 in  50.4s =    8.6/s Avg:  1135 Min:  1000 Max:  1250 Err:     0 (0.00%)
+label +   263 in  31.0s =    8.5/s Avg:  1138 Min:  1000 Max:  1250 Err:     0 (0.00%)
+label =   697 in  80.3s =    8.7/s Avg:  1136 Min:  1000 Max:  1250 Err:     0 (0.00%)
+label +   109 in  12.4s =    8.8/s Avg:  1092 Min:    47 Max:  1250 Err:     0 (0.00%)
+label =   806 in  91.6s =    8.8/s Avg:  1130 Min:    47 Max:  1250 Err:     0 (0.00%)
+
+
+ +The "label" is the the name of the element. +The "+" means that the line is a delta line, i.e. shows the changes since the last output. +The "=" means that the line is a totals line, i.e. it shows the running total. +Entries in the jmeter log file also include time-stamps. +The example "806 in 91.6s = 8.8/s" means that there were 806 samples recorded in 91.6 seconds, +and that works out at 8.8 samples per second. +The Avg (Average), Min(imum) and Max(imum) times are in milliseconds. +"Err" means number of errors (also shown as percentage). +The last two lines will appear at the end of a test. +They will not be synchronised to the appropriate time boundary. +Note that the initial and final deltas may be for less than the interval (in the example above this is 30 seconds). +The first delta will generally be lower, as JMeter synchronises to the interval boundary. +The last delta will be lower, as the test will generally not finish on an exact interval boundary. + +

+ +The label is used to group sample results together. +So if you have multiple Thread Groups and want to summarize across them all, then use the same label + - or add the summariser to the Test Plan (so all thread groups are in scope). +Different summary groupings can be implemented +by using suitable labels and adding the summarisers to appropriate parts of the test plan. + +

+ + + +

Control Panel

+
+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + It appears as the "label" in the output. Details for all elements with the same label will be added together. + + +Yes +
+

+

+
+ + + + +
+ +

+18.3.19 Comparison Assertion Visualizer +

+
+
+ +The Comparison Assertion Visualizer shows the results of any +Compare Assertion + elements. + +

Control Panel

+
+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + + +Yes +
+

+

+
+ +^ + +
+

+

+ + + + +
+ +18.4 Configuration Elements +
+
+ + + +
+ + + Configuration elements can be used to set up defaults and variables for later use by samplers. + Note that these elements are processed at the start of the scope in which they are found, + i.e. before any samplers in the same scope. + +
+ + + +
+ + + + +
+ +

+18.4.1 CSV Data Set Config +

+
+
+ + +

+ + CSV Data Set Config is used to read lines from a file, and split them into variables. + It is easier to use than the __CSVRead() and _StringFromFile() functions. + It is well suited to handling large numbers of variables, and is also useful for tesing with + "random" and unique values. + Generating unique random values at run-time is expensive in terms of CPU and memory, so just create the data + in advance of the test. If necessary, the "random" data from the file can be used in conjunction with + a run-time parameter to create different sets of values from each run - e.g. using concatenation - which is + much cheaper than generating everything at run-time. + +

+ + +

+ + Versions of JMeter after 2.3.1 allow values to be quoted; this allows the value to contain a delimiter. + Previously it was necessary to choose a delimiter that was not used in any values. + If "allow quoted data" is enabled, a value may be enclosed in double-quotes. + These are removed. To include double-quotes within a quoted field, use two double-quotes. + For example: +

+
+1,"2,3","4""5" =>
+1
+2,3
+4"5
+
+
+ + +

+ + +

+ + Versions of JMeter after 2.3.4 support CSV files which have a header line defining the column names. + To enable this, leave the "Variable Names" field empty. The correct delimiter must be provided. + +

+ + +

+ + By default, the file is only opened once, and each thread will use a different line from the file. + However the order in which lines are passed to threads depends on the order in which they execute, + which may vary between iterations. + Lines are read at the start of each test iteration. + The file name and mode are resolved in the first iteration. + +

+ + +

+ + See the description of the Share mode below for additional options (JMeter 2.3.2+). + If you want each thread to have its own set of values, then you will need to create a set of files, + one for each thread. For example test1.csv, test2.csv,... testn.csv. Use the filename + + +test${__threadNum}.csv + + and set the "Sharing mode" to "Current thread". + +

+ + +

+ + +
CSV Dataset variables are defined at the start of each test iteration. + As this is after configuration processing is completed, + they cannot be used for some configuration items - such as JDBC Config - + that process their contents at configuration time (see + +Bug 40394 + +) + However the variables do work in the HTTP Auth Manager, as the username etc are processed at run-time. + +
+

+ + +

+ + As a special case, the string "\t" (without quotes) in the delimiter field is treated as a Tab. + +

+ + +

+ + When the end of file (EOF) is reached, and the recycle option is true, reading starts again with the first line of the file. + +

+ + +

+ + If the recycle option is false, and stopThread is false, then all the variables are set to + +<EOF> + + when the end of file is reached. + This value can be changed by setting the JMeter property + +csvdataset.eofstring + +. + +

+ + +

+ + If the Recycle option is false, and Stop Thread is true, then reaching EOF will cause the thread to be stopped. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
FilenameName of the file to be read. + + +Relative file names are resolved with respect to the path of the active test plan. + + + Absolute file names are also supported, but note that they are unlikely to work in remote mode, + unless the remote server has the same directory structure. + If the same physical file is referenced in two different ways - e.g. csvdata.txt and ./csvdata.txt - + then these are treated as different files. + If the OS does not distinguish between upper and lower case, csvData.TXT would also be opened separately. + + +Yes +
File EncodingThe encoding to be used to read the file, if not the platform default. + +No +
Variable NamesList of variable names (comma-delimited). + Versions of JMeter after 2.3.4 support CSV header lines: + if the variable name field empty, then the first line of the file is read and interpreted as the list of column names. + The names must be separated by the delimiter character. They can be quoted using double-quotes. + + +Yes +
DelimiterDelimiter to be used to split the records in the file. + If there are fewer values on the line than there are variables the remaining variables are not updated - + so they will retain their previous value (if any). + +Yes +
Allow quoted data?Should the CSV file allow values to be quoted? + If enabled, then values can be enclosed in + +" + + - double-quote - allowing values to contain a delimeter. + + +Yes +
Recycle on EOF?Should the file be re-read from the beginning on reaching EOF? (default is true) + +Yes +
Stop thread on EOF?Should the thread be stopped on EOF, if Recycle is false? (default is false) + +Yes +
Sharing mode + +
    + + +
  • +All threads - (the default) the file is shared between all the threads. +
  • + + +
  • +Current thread group - each file is opened once for each thread group in which the element appears +
  • + + +
  • +Current thread - each file is opened separately for each thread +
  • + + +
  • +Identifier - all threads sharing the same identifier share the same file. + So for example if you have 4 thread groups, you could use a common id for two or more of the groups + to share the file between them. + Or you could use the thread number to share the file between the same thread numbers in different thread groups. + +
  • + + +
+ + +
+Yes +
+

+

+
+ + + + +
+ +

+18.4.2 FTP Request Defaults +

+
+
+

Control Panel

+
+

+
+ + + + +
+ +

+18.4.3 HTTP Authorization Manager +

+
+
+

+ + +
If there is more than one Authorization Manager in the scope of a Sampler, +there is currently no way to specify which one is to be used. +
+

+ + +

+The Authorization Manager lets you specify one or more user logins for web pages that are +restricted using server authentication. You see this type of authentication when you use +your browser to access a restricted page, and your browser displays a login dialog box. JMeter +transmits the login information when it encounters this type of page. +

+ + +

+ +The Authorisation headers are not shown in the Tree View Listener. + +

+ + +

+ +In versions of JMeter after 2.2, the HttpClient sampler defaults to pre-emptive authentication +if the setting has not been defined. To disable this, set the values as below, in which case +authentication will only be performed in response to a challenge. + +

+
+jmeter.properties:
+httpclient.parameters.file=httpclient.parameters
+
+httpclient.parameters:
+http.authentication.preemptive$Boolean=false
+
+
+ +Note: the above settings only apply to the HttpClient sampler (and the SOAP samplers, which use Httpclient). + +

+ + +

+ + +
+When looking for a match against a URL, JMeter checks each entry in turn, and stops when it finds the first match. +Thus the most specific URLs should appear first in the list, followed by less specific ones. +Duplicate URLs will be ignored. +If you want to use different usernames/passwords for different threads, you can use variables. +These can be set up using a +CSV Data Set Config + Element (for example). + +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Base URLA partial or complete URL that matches one or more HTTP Request URLs. As an example, +say you specify a Base URL of "http://jmeter.apache.org/restricted/" with a username of "jmeter" and +a password of "jmeter". If you send an HTTP request to the URL +"http://jmeter.apache.org/restricted/ant/myPage.html", the Authorization Manager sends the login +information for the user named, "jmeter". + +Yes +
UsernameThe username to authorize. + +Yes +
PasswordThe password for the user. (N.B. this is stored unencrypted in the test plan) + +Yes +
DomainThe domain to use for NTLM. + +No +
RealmThe realm to use for NTLM. + +No +
+

+

+ + +
+The Realm only applies to the HttpClient sampler. +In JMeter 2.2, the domain and realm did not have separate columns, and were encoded as part of +the user name in the form: [domain\]username[@realm]. +This was an experimental feature and has been removed. + +
+

+
+ + +Controls: + +
    + + +
  • +Add Button - Add an entry to the authorization table. +
  • + + +
  • +Delete Button - Delete the currently selected table entry. +
  • + + +
  • +Load Button - Load a previously saved authorization table and add the entries to the existing +authorization table entries. +
  • + + +
  • +Save As Button - Save the current authorization table to a file. +
  • + + +
+

+ + +
When you save the Test Plan, JMeter automatically saves all of the authorization +table entries - including any passwords, which are not encrypted. +
+

+ +

Authorization Example

+ + + +

+ +Download + + this example. In this example, we created a Test Plan on a local server that sends three HTTP requests, two requiring a login and the +other is open to everyone. See figure 10 to see the makeup of our Test Plan. On our server, we have a restricted +directory named, "secret", which contains two files, "index.html" and "index2.html". We created a login id named, "kevin", +which has a password of "spot". So, in our Authorization Manager, we created an entry for the restricted directory and +a username and password (see figure 11). The two HTTP requests named "SecretPage1" and "SecretPage2" make requests +to "/secret/index.html" and "/secret/index2.html". The other HTTP request, named "NoSecretPage" makes a request to +"/index.html". +

+ + + +


+Figure 10 - Test Plan +

+ + +


+Figure 11 - Authorization Manager Control Panel +

+ + + +

+When we run the Test Plan, JMeter looks in the Authorization table for the URL it is requesting. If the Base URL matches +the URL, then JMeter passes this information along with the request. +

+ + + +

+ + +
You can download the Test Plan, but since it is built as a test for our local server, you will not +be able to run it. However, you can use it as a reference in constructing your own Test Plan. +
+

+ + +

+
+ + + + +
+ +

+18.4.4 HTTP Cache Manager +

+
+
+

+ + +
This is a new element, and is liable to change +
+

+ + +

+ +The HTTP Cache Manager is used to add caching functionality to HTTP requests within its scope. + +

+ + +

+ +If a sample is successful (i.e. has response code 2xx) then the Last-Modified and Etag (and Expired if relevant) values are saved for the URL. +Before executing the next sample, the sampler checks to see if there is an entry in the cache, +and if so, the If-Last-Modified and If-None-Match conditional headers are set for the request. + +

+ + +

+ +Additionally, if the "Use Cache-Control/Expires header" option is selected, then the Cache-Control/Expires value is checked against the current time. +If the request is a GET request, and the timestamp is in the future, then the sampler returns immediately, +without requesting the URL from the remote server. This is intended to emulate browser behaviour. +Note that the Cache-Control header must be "public" and only the "max-age" expiry option is processed. + +

+ + +

+ +By default, Cache Manager will store up to 5000 items in cache using LRU algorithm. Use property to modify this value. +Note that the more you increase this value the more HTTP Cache Manager will consume memory, so be sure to adapt -Xmx option. + +

+ + +

+ +If the requested document has not changed since it was cached, then the response body will be empty. +Likewise if the Expires date is in the future. +This may cause problems for Assertions. + +

+ + + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Clear cache each iteration + If selected, then the cache is cleared at the start of the thread. + + +Yes +
Use Cache Control/Expires header when processing GET requestsSee description above. + +Yes +
Max Number of elements in cacheSee description above. + +Yes +
+

+

+
+ + + + +
+ +

+18.4.4 HTTP Cookie Manager +

+
+
+

+ + +
If there is more than one Cookie Manager in the scope of a Sampler, +there is currently no way to specify which one is to be used. +Also, a cookie stored in one cookie manager is not available to any other manager, +so use multiple Cookie Managers with care. +
+

+

+The Cookie Manager element has two functions: +
+ + +First, it stores and sends cookies just like a web browser. If you have an HTTP Request and +the response contains a cookie, the Cookie Manager automatically stores that cookie and will +use it for all future requests to that particular web site. Each JMeter thread has its own +"cookie storage area". So, if you are testing a web site that uses a cookie for storing +session information, each JMeter thread will have its own session. +Note that such cookies do not appear on the Cookie Manager display, but they can be seen using +the +View Results Tree + Listener. + +

+ + +

+ +JMeter version 2.3.2 and earlier did not check that received cookies were valid for the URL. +This meant that cross-domain cookies were stored, and might be used later. +This has been fixed in later versions. +To revert to the earlier behaviour, define the JMeter property "CookieManager.check.cookies=false". + +

+ + +

+ +Received Cookies can be stored as JMeter thread variables +(versions of JMeter after 2.3.2 no longer do this by default). +To save cookies as variables, define the property "CookieManager.save.cookies=true". +Also, cookies names are prefixed with "COOKIE_" before they are stored (this avoids accidental corruption of local variables) +To revert to the original behaviour, define the property "CookieManager.name.prefix= " (one or more spaces). +If enabled, the value of a cookie with the name TEST can be referred to as ${COOKIE_TEST}. + +

+ + +

+Second, you can manually add a cookie to the Cookie Manager. However, if you do this, +the cookie will be shared by all JMeter threads. +

+ + +

+Note that such Cookies are created with an Expiration time far in the future +

+ + +

+ +Since version 2.0.3, cookies with null values are ignored by default. +This can be changed by setting the JMeter property: CookieManager.delete_null_cookies=false. +Note that this also applies to manually defined cookies - any such cookies will be removed from the display when it is updated. +Note also that the cookie name must be unique - if a second cookie is defined with the same name, it will replace the first. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Clear Cookies each IterationIf selected, all server-defined cookies are cleared each time the main Thread Group loop is executed. + In JMeter versions after 2.3, any cookies defined in the GUI are not cleared. + +Yes +
Cookie PolicyThe cookie policy that will be used to manage the cookies. + "compatibility" is the default, and should work in most cases. + See http://hc.apache.org/httpclient-3.x/cookies.html and + http://hc.apache.org/httpclient-3.x/apidocs/org/apache/commons/httpclient/cookie/CookiePolicy.html + [Note: "ignoreCookies" is equivalent to omitting the CookieManager.] + + +Yes +
User-Defined CookiesThis + gives you the opportunity to use hardcoded cookies that will be used by all threads during the test execution. + +
+ + + The "domain" is the hostname of the server (without http://); the port is currently ignored. + +
+No (discouraged, unless you know what you're doing) +
Add ButtonAdd an entry to the cookie table. + +N/A +
Delete ButtonDelete the currently selected table entry. + +N/A +
Load ButtonLoad a previously saved cookie table and add the entries to the existing +cookie table entries. + +N/A +
Save As Button + Save the current cookie table to a file (does not save any cookies extracted from HTTP Responses). + + +N/A +
+

+

+
+ + + + +
+ +

+18.4.5 HTTP Request Defaults +

+
+
+

+This element lets you set default values that your HTTP Request controllers use. For example, if you are +creating a Test Plan with 25 HTTP Request controllers and all of the requests are being sent to the same server, +you could add a single HTTP Request Defaults element with the "Server Name or IP" field filled in. Then, when +you add the 25 HTTP Request controllers, leave the "Server Name or IP" field empty. The controllers will inherit +this field value from the HTTP Request Defaults element. +

+ + +

+ + +
+In JMeter 2.2 and earlier, port 80 was treated specially - it was ignored if the sampler used the https protocol. +JMeter 2.3 and later treat all port values equally; a sampler that does not specify a port will use the HTTP Request Defaults port, if one is provided. + +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
ServerDomain name or IP address of the web server. e.g. www.example.com. [Do not include the http:// prefix. + +No +
PortPort the web server is listening to. + +No +
Connect TimeoutConnection Timeout. Number of milliseconds to wait for a connection to open. Requires Java 1.5 or later when using the default Java HTTP implementation. + +No +
Response TimeoutResponse Timeout. Number of milliseconds to wait for a response. Requires Java 1.5 or later when using the default Java HTTP implementation. + +No +
ImplementationJava, HttpClient3.1, HttpClient4. + If not specified the default depends on the value of the JMeter property + + +jmeter.httpsampler + +, failing that, the Java implementation is used. + +No +
ProtocolHTTP or HTTPS. + +No +
MethodHTTP GET or HTTP POST. + +No +
PathThe path to resource (for example, /servlets/myServlet). If the + resource requires query string parameters, add them below in the "Send Parameters With the Request" section. + Note that the path is the default for the full path, not a prefix to be applied to paths + specified on the HTTP Request screens. + + +No +
Send Parameters With the RequestThe query string will + be generated from the list of parameters you provide. Each parameter has a + +name + + and + + +value + +. The query string will be generated in the correct fashion, depending on + the choice of "Method" you made (ie if you chose GET, the query string will be + appended to the URL, if POST, then it will be sent separately). Also, if you are + sending a file using a multipart form, the query string will be created using the + multipart form specifications. + +No +
Server (proxy)Hostname or IP address of a proxy server to perform request. [Do not include the http:// prefix.] + +No +
PortPort the proxy server is listening to. + +No, unless proxy hostname is specified +
Username(Optional) username for proxy server. + +No +
Password(Optional) password for proxy server. (N.B. this is stored unencrypted in the test plan) + +No +
Retrieve All Embedded Resources from HTML FilesTell JMeter to parse the HTML file +and send HTTP/HTTPS requests for all images, Java applets, JavaScript files, CSSs, etc. referenced in the file. + + +No +
Use concurrent poolUse a pool of concurrent connections to get embedded resources. + +No +
SizePool size for concurrent connections used to get embedded resources. + +No +
+

+

+ + +
+Note: radio buttons only have two states - on or off. +This makes it impossible to override settings consistently +- does off mean off, or does it mean use the current default? +JMeter uses the latter (otherwise defaults would not work at all). +So if the button is off, then a later element can set it on, +but if the button is on, a later element cannot set it off. + +
+

+

+
+ + + + +
+ +

+18.4.6 HTTP Header Manager +

+
+
+ + +

+The Header Manager lets you add or override HTTP request headers. +

+ + +

+ +Versions of JMeter up to 2.3.2 supported only one Header Manager per sampler; +if there were more in scope, then only the last one would be used. + +

+ + +

+ + + +JMeter now supports multiple Header Managers + +. The header entries are merged to form the list for the sampler. +If an entry to be merged matches an existing header name, it replaces the previous entry, +unless the entry value is empty, in which case any existing entry is removed. +This allows one to set up a default set of headers, and apply adjustments to particular samplers. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Name (Header)Name of the request header. + Two common request headers you may want to experiment with +are "User-Agent" and "Referer". + +No (You should have at least one, however) +
ValueRequest header value. + +No (You should have at least one, however) +
Add ButtonAdd an entry to the header table. + +N/A +
Delete ButtonDelete the currently selected table entry. + +N/A +
Load ButtonLoad a previously saved header table and add the entries to the existing +header table entries. + +N/A +
Save As ButtonSave the current header table to a file. + +N/A +
+

+ +

Header Manager example

+ + + +

+ +Download + + this example. In this example, we created a Test Plan +that tells JMeter to override the default "User-Agent" request header and use a particular Internet Explorer agent string +instead. (see figures 12 and 13). +

+ + + +


+Figure 12 - Test Plan +

+ + +


+Figure 13 - Header Manager Control Panel +

+ + +

+
+ + + + +
+ +

+18.4.7 Java Request Defaults +

+
+
+

+The Java Request Defaults component lets you set default values for Java testing. See the +Java Request +. +

+ + +

Control Panel

+
+

+
+ + + + +
+ +

+18.4.8 JDBC Connection Configuration +

+
+
+Creates a database connection (used by +JDBC Request +Sampler) + from the supplied JDBC Connection settings. The connection may be optionally pooled between threads. + Otherwise each thread gets its own connection. + The connection configuration name is used by the JDBC Sampler to select the appropriate + connection. + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for the connection configuration that is shown in the tree. + +No +
Variable NameThe name of the variable the connection is tied to. + Multiple connections can be used, each tied to a different variable, allowing JDBC Samplers + to select the appropriate connection. + + +Each name must be different. If there are two configuration elements using the same name, + only one will be saved. JMeter versions after 2.3 log a message if a duplicate name is detected. + + + + +Yes +
Max Number of Connections + Maximum number of connections allowed in the pool. + In most cases, + +set this to zero (0) + +. + This means that each thread will get its own pool with a single connection in it, i.e. + the connections are not shared betweeen threads. + +
+ + + If you really want to use shared pooling (why?), then set the max count to the same as the number of threads + to ensure threads don't wait on each other. + +
+Yes +
Pool timeoutPool throws an error if the timeout period is exceeded in the + process of trying to retrieve a connection + +Yes +
Idle Cleanup Interval (ms)Uncertain what exactly this does. + +Yes +
Auto CommitTurn auto commit on or off for the connections. + +Yes +
Keep-aliveUncertain what exactly this does. + +Yes +
Max Connection Age (ms)Uncertain what exactly this does. + +Yes +
Validation QueryA simple query used to determine if the database is still + responding. + +Yes +
Database URLJDBC Connection string for the database. + +Yes +
JDBC Driver classFully qualified name of driver class. (Must be in + JMeter's classpath - easiest to copy .jar file into JMeter's /lib directory). + +Yes +
UsernameName of user to connect as. + +No +
PasswordPassword to connect with. (N.B. this is stored unencrypted in the test plan) + +No +
+

+

+Different databases and JDBC drivers require different JDBC settings. +The Database URL and JDBC Driver class are defined by the provider of the JDBC implementation. +

+

+Some possible settings are shown below. Please check the exact details in the JDBC driver documentation. +

+

+ +If JMeter reports + +No suitable driver + +, then this could mean either: + +

    + + +
  • +The driver class was not found. In this case, there will be a log message such as + +DataSourceElement: Could not load driver: {classname} java.lang.ClassNotFoundException: {classname} + +
  • + + +
  • +The driver class was found, but the class does not support the connection string. This could be because of a syntax error in the connection string, or because the the wrong classname was used. +
  • + + +
+ +If the database server is not running or is not accessible, then JMeter will report a + +java.net.ConnectException + +. + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +Database + + + +Driver class + + + +Database URL + +
+ +MySQL + + + +com.mysql.jdbc.Driver + + + +jdbc:mysql://host[:port]/dbname + +
+ +PostgreSQL + + + +org.postgresql.Driver + + + +jdbc:postgresql:{dbname} + +
+ +Oracle + + + +oracle.jdbc.OracleDriver + + + +jdbc:oracle:thin:@//host:port/service +OR
jdbc:oracle:thin:@(description=(address=(host={mc-name})(protocol=tcp)(port={port-no}))(connect_data=(sid={sid}))) +
+
+ +Ingres (2006) + + + +ingres.jdbc.IngresDriver + + + +jdbc:ingres://host:port/db[;attr=value] + +
+ +SQL Server (MS JDBC driver) + + + +com.microsoft.sqlserver.jdbc.SQLServerDriver + + + +jdbc:sqlserver://host:port;DatabaseName=dbname + +
+ +Apache Derby + + + +org.apache.derby.jdbc.ClientDriver + + + +jdbc:derby://server[:port]/databaseName[;URLAttributes=value[;...]] + +
+

+ + +
The above may not be correct - please check the relevant JDBC driver documentation. +
+

+

+
+ + + + +
+ +

+18.4.9 Keystore Configuration +

+
+
+

+The Keystore Config Element lets you configure how Keystore will be loaded and which keys it will use. +This component is typically used in HTTPS scenarios where you don't want to take into account keystore initialization into account in response time. +

+ + +

+To use this element, you need to setup first a Java Key Store with the client certificates you want to test, to do that: + +

    + + +
  1. +Create your certificates either with Java keytool utility or through your PKI +
  2. + + +
  3. +If created by PKI, import your keys in Java Key Store by converting them to a format acceptable by JKS +
  4. + + +
  5. +Then reference the keystore file through the 2 JVM properties (or add them in system.properties): + +
      + + +
    • +-Djavax.net.ssl.keyStore=path_to_keystore +
    • + + +
    • +-Djavax.net.ssl.keyStorePassword=password_of_keystore +
    • + + +
    + + +
  6. + + +
+ + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
PreloadWether or not to preload Keystore. + +Yes +
Alias Start IndexThe index of the first key to use in Keystore. + +Yes +
Alias End IndexThe index of the last key to use in Keystore. + +Yes +
+

+

+ + +
+To make JMeter use more than one certificate you need to ensure that: + +
    + + +
  • +https.use.cached.ssl.context=false is set in jmeter.properties or user.properties +
  • + + +
  • +You use either HTTPClient 3.1 or 4 implementations for HTTP Request +
  • + + +
+ + +
+

+

+
+ + + + +
+ +

+18.4.10 Login Config Element +

+
+
+

+The Login Config Element lets you add or override username and password settings in samplers that use username and password as part of their setup. +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
UsernameThe default username to use. + +No +
PasswordThe default password to use. (N.B. this is stored unencrypted in the test plan) + +No +
+

+

+
+ + + + +
+ +

+18.4.11 LDAP Request Defaults +

+
+
+

+The LDAP Request Defaults component lets you set default values for LDAP testing. See the +LDAP Request +. +

+ + +

Control Panel

+
+

+
+ + + + +
+ +

+18.4.12 LDAP Extended Request Defaults +

+
+
+

+The LDAP Extended Request Defaults component lets you set default values for extended LDAP testing. See the +LDAP Extended Request +. +

+ + +

Control Panel

+
+

+
+ + + + +
+ +

+18.4.13 TCP Sampler Config +

+
+
+ + +

+ + The TCP Sampler Config provides default data for the TCP Sampler + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
TCPClient classnameName of the TCPClient class. Defaults to the property tcp.handler, failing that TCPClientImpl. + +No +
ServerName or IPName or IP of TCP server + +No +
Port NumberPort to be used + +No +
Re-use connectionIf selected, the connection is kept open. Otherwise it is closed when the data has been read. + +Yes +
Connect TimeoutConnect Timeout (milliseconds, 0 disables). + +No +
Response TimeoutResponse Timeout (milliseconds, 0 disables). + +No +
Set NodelayShould the nodelay property be set? + +No +
Text to SendText to be sent + +No +
+

+

+
+ + + + +
+ +

+18.4.14 User Defined Variables +

+
+
+

+The User Defined Variables element lets you define an + +initial set of variables + +, just as in the +Test Plan +. + + + +Note that all the UDV elements in a test plan - no matter where they are - are processed at the start. + + + +So you cannot reference variables which are defined as part of a test run, e.g. in a Post-Processor. + +

+ + +

+ + + + +UDVs should not be used with functions that generate different results each time they are called. +Only the result of the first function call will be saved in the variable. + + + +However, UDVs can be used with functions such as __P(), for example: + +

+
+HOST      ${__P(host,localhost)} 
+
+
+ +which would define the variable "HOST" to have the value of the JMeter property "host", defaulting to "localhost" if not defined. + +

+ + +

+ +For defining variables during a test run, see +User Parameters +. +UDVs are processed in the order they appear in the Plan, from top to bottom. + +

+ + +

+ +For simplicity, it is suggested that UDVs are placed only at the start of a Thread Group +(or perhaps under the Test Plan itself). + +

+ + +

+ +Once the Test Plan and all UDVs have been processed, the resulting set of variables is +copied to each thread to provide the initial set of variables. + +

+ + +

+ +If a runtime element such as a User Parameters Pre-Processor or Regular Expression Extractor defines a variable +with the same name as one of the UDV variables, then this will replace the initial value, and all other test +elements in the thread will see the updated value. + +

+ + +

Control Panel

+
+

+ + +
+If you have more than one Thread Group, make sure you use different names for different values, as UDVs are shared between Thread Groups. +Also, the variables are not available for use until after the element has been processed, +so you cannot reference variables that are defined in the same element. +You can reference variables defined in earlier UDVs or on the Test Plan. + +
+

+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
User Defined VariablesVariable name/value pairs. The string under the "Name" + column is what you'll need to place inside the brackets in ${...} constructs to use the variables later on. The + whole ${...} will then be replaced by the string in the "Value" column. + +No +
+

+

+
+ + + + +
+ +

+18.4.15 Random Variable +

+
+
+ + +

+ +The Random Variable Config Element is used to generate random numeric strings and store them in variable for use later. +It's simpler than using +User Defined Variables + together with the __Random() function. + +

+ + +

+ +The output variable is constructed by using the random number generator, +and then the resulting number is formatted using the format string. +The number is calculated using the formula + +minimum+Random.nextInt(maximum-minimum+1) + +. +Random.nextInt() requires a positive integer. +This means that maximum-minimum - i.e. the range - must be less than 2147483647, +however the minimum and maximum values can be any long values so long as the range is OK. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +Yes +
Variable NameThe name of the variable in which to store the random string. + +Yes +
Format StringThe java.text.DecimalFormat format string to be used. + For example "000" which will generate numbers with at least 3 digits, + or "USER_000" which will generate output of the form USER_nnn. + If not specified, the default is to generate the number using Long.toString() + +No +
Minimum ValueThe minimum value (long) of the generated random number. + +Yes +
Maximum ValueThe maximum value (long) of the generated random number. + +Yes +
Random SeedThe seed for the random number generator. Default is the current time in milliseconds. + +No +
Per Thread(User)?If False, the generator is shared between all threads in the thread group. + If True, then each thread has its own random generator. + +Yes +
+

+

+
+ + + + +
+ +

+18.4.16 Counter +

+
+
+

+Allows the user to create a counter that can be referenced anywhere +in the Thread Group. The counter config lets the user configure a starting point, a maximum, +and the increment. The counter will loop from the start to the max, and then start over +with the start, continuing on like that until the test is ended. +

+ + +

+From version 2.1.2, the counter now uses a long to store the value, so the range is from -2^63 to 2^63-1. +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
StartThe starting number for the counter. The counter will equal this + number during the first iteration. + +Yes +
IncrementHow much to increment the counter by after each + iteration. + +Yes +
MaximumIf the counter exceeds the maximum, then it is reset to the Start value. + For versions after 2.2 the default is Long.MAX_VALUE (previously it was 0). + + +No +
FormatOptional format, e.g. 000 will format as 001, 002 etc. + This is passed to DecimalFormat, so any valid formats can be used. + If there is a problem interpreting the format, then it is ignored. + [The default format is generated using Long.toString()] + + +No +
Reference NameThis controls how you refer to this value in other elements. Syntax is + as in + +user-defined values + +: + +$(reference_name} + +. + +Yes +
Track Counter Independently for each UserIn other words, is this a global counter, or does each user get their + own counter? If unchecked, the counter is global (ie, user #1 will get value "1", and user #2 will get value "2" on + the first iteration). If checked, each user has an independent counter. + +No +
Reset counter on each Thread Group IterationThis option is only available when counter is tracked per User, if checked, + counter will be reset to Start value on each Thread Group iteration. This can be useful when Counter is inside a Loop Controller. + +No +
+

+

+
+ + + + +
+ +

+18.4.17 Simple Config Element +

+
+
+

+The Simple Config Element lets you add or override arbitrary values in samplers. You can choose the name of the value +and the value itself. Although some adventurous users might find a use for this element, it's here primarily for developers as a basic +GUI that they can use while developing new JMeter components. +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +Yes +
Parameter NameThe name of each parameter. These values are internal to JMeter's workings and + are not generally documented. Only those familiar with the code will know these values. + +Yes +
Parameter ValueThe value to apply to that parameter. + +Yes +
+

+

+
+ +^ + +
+

+

+ + + + +
+ +18.5 Assertions +
+
+ + + +

+ + Assertions are used to perform additional checks on samplers, and are processed after + +every sampler + + + in the same scope. + To ensure that an Assertion is applied only to a particular sampler, add it as a child of the sampler. + +

+ + +

+ + Note: Unless documented otherwise, Assertions are not applied to sub-samples (child samples) - + only to the parent sample. + In the case of BSF and BeanShell Assertions, the script can retrieve sub-samples using the method + + +prev.getSubResults() + + which returns an array of SampleResults. + The array will be empty if there are none. + +

+ + +

+ + Versions of JMeter after 2.3.2 include the option to apply certain assertions + to either the main sample, the sub-samples or both. + The default is to apply the assertion to the main sample only. + If the Assertion supports this option, then there will be an entry on the GUI which looks like the following: + +


+Assertion Scope +

+ + or the following + +


+Assertion Scope +

+ + If a sub-sampler fails and the main sample is successful, + then the main sample will be set to failed status and an Assertion Result will be added. + If the JMeter variable option is used, it is assumed to relate to the main sample, and + any failure will be applied to the main sample only. + +

+ + +

+ + +
+ The variable + +JMeterThread.last_sample_ok + + is updated to + "true" or "false" after all assertions for a sampler have been run. + +
+

+ + +
+ + + + +
+ +

+18.5.1 Response Assertion +

+
+
+

+The response assertion control panel lets you add pattern strings to be compared against various + fields of the response. + The pattern strings are: + +

    + + +
  • +Contains, Matches: Perl5-style regular expressions +
  • + + +
  • +Equals, Substring: plain text, case-sensitive +
  • + + +
+ + +

+ + +

+ + A summary of the pattern matching characters can be found at + +http://jakarta.apache.org/oro/api/org/apache/oro/text/regex/package-summary.html + + + +

+ + +

+You can also choose whether the strings will be expected +to + +match + + the entire response, or if the response is only expected to + +contain + + the +pattern. You can attach multiple assertions to any controller for additional flexibility. +

+ + +

+Note that the pattern string should not include the enclosing delimiters, + i.e. use + +Price: \d+ + + not + +/Price: \d+/ + +. + +

+ + +

+ + By default, the pattern is in multi-line mode, which means that the "." meta-character does not match newline. + In multi-line mode, "^" and "$" match the start or end of any line anywhere within the string + - not just the start and end of the entire string. Note that \s does match new-line. + Case is also significant. To override these settings, one can use the + +extended regular expression + + syntax. + For example: + +

+ + +
+
+	(?i) - ignore case
+	(?s) - treat target as single line, i.e. "." matches new-line
+	(?is) - both the above
+    These can be used anywhere within the expression and remain in effect until overriden.  e.g.
+    (?i)apple(?-i) Pie - matches "ApPLe Pie", but not "ApPLe pIe"
+    (?s)Apple.+?Pie - matches Apple followed by Pie, which may be on a subsequent line.
+    Apple(?s).+?Pie - same as above, but it's probably clearer to use the (?s) at the start.  
+
+
+ + + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Apply to: + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. + +
    + + +
  • +Main sample only - assertion only applies to the main sample +
  • + + +
  • +Sub-samples only - assertion only applies to the sub-samples +
  • + + +
  • +Main sample and sub-samples - assertion applies to both. +
  • + + +
  • +JMeter Variable - assertion is to be applied to the contents of the named variable +
  • + + +
+ + +
+Yes +
Response Field to TestInstructs JMeter which field of the Response to test. + +
    + + +
  • +Text Response - the response text from the server, i.e. the body, excluing any HTTP headers. +
  • + + +
  • +URL sampled +
  • + + +
  • +Response Code - e.g. 200 +
  • + + +
  • +Response Message - e.g. OK +
  • + + +
  • +Response Headers, including Set-Cookie headers (if any) +
  • + + +
+ + +
+Yes +
Ignore statusInstructs JMeter to set the status to success initially. + +

+ + The overall success of the sample is determined by combining the result of the + assertion with the existing Response status. + When the Ignore Status checkbox is selected, the Response status is forced + to successful before evaluating the Assertion. + +

+ + HTTP Responses with statuses in the 4xx and 5xx ranges are normally + regarded as unsuccessful. + The "Ignore status" checkbox can be used to set the status successful before performing further checks. + Note that this will have the effect of clearing any previous assertion failures, + so make sure that this is only set on the first assertion. + +
+Yes +
Pattern Matching RulesIndicates how the text being tested + is checked against the pattern. + +
    + + +
  • +Contains - true if the text contains the regular expression pattern +
  • + + +
  • +Matches - true if the whole text matches the regular expression pattern +
  • + + +
  • +Equals - true if the whole text equals the pattern string (case-sensitive) +
  • + + +
  • +Substring - true if the text contains the pattern string (case-sensitive) +
  • + + +
+ + Equals and Substring patterns are plain strings, not regular expressions. + NOT may also be selected to invert the result of the check. +
+Yes +
Patterns to TestA list of patterns to + be tested. + Each pattern is tested separately. + If a pattern fails, then further patterns are not checked. + There is no difference between setting up + one Assertion with multiple patterns and setting up multiple Assertions with one + pattern each (assuming the other options are the same). + + +However, when the Ignore Status checkbox is selected, this has the effect of cancelling any + previous assertion failures - so make sure that the Ignore Status checkbox is only used on + the first Assertion. + + + + +Yes +
+

+

+ + The pattern is a Perl5-style regular expression, but without the enclosing brackets. + +

+ +

Assertion Examples

+ + +


+Figure 14 - Test Plan +

+ + +


+Figure 15 - Assertion Control Panel with Pattern +

+ + +


+Figure 16 - Assertion Listener Results (Pass) +

+ + +


+Figure 17 - Assertion Listener Results (Fail) +

+ + +

+
+ + + + +
+ +

+18.5.2 Duration Assertion +

+
+
+

+The Duration Assertion tests that each response was received within a given amount +of time. Any response that takes longer than the given number of milliseconds (specified by the +user) is marked as a failed response. +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Duration in MillisecondsThe maximum number of milliseconds + each response is allowed before being marked as failed. + +Yes +
+

+

+
+ + + + +
+ +

+18.5.3 Size Assertion +

+
+
+

+The Size Assertion tests that each response contains the right number of bytes in it. You can specify that +the size be equal to, greater than, less than, or not equal to a given number of bytes. +

+ + +

+ + +
Since JMeter 2.3RC3, an empty response is treated as being 0 bytes rather than reported as an error. +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Apply to: + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. + +
    + + +
  • +Main sample only - assertion only applies to the main sample +
  • + + +
  • +Sub-samples only - assertion only applies to the sub-samples +
  • + + +
  • +Main sample and sub-samples - assertion applies to both. +
  • + + +
  • +JMeter Variable - assertion is to be applied to the contents of the named variable +
  • + + +
+ + +
+Yes +
Size in bytesThe number of bytes to use in testing the size of the response (or value of the JMeter variable). + +Yes +
Type of ComparisonWhether to test that the response is equal to, greater than, less than, + or not equal to, the number of bytes specified. + +Yes +
+

+

+
+ + + + +
+ +

+18.5.4 XML Assertion +

+
+
+

+The XML Assertion tests that the response data consists of a formally correct XML document. It does not +validate the XML based on a DTD or schema or do any further validation. +

+

Control Panel

+
+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
+

+

+
+ + + + +
+ +

+18.5.5 BeanShell Assertion +

+
+
+

+The BeanShell Assertion allows the user to perform assertion checking using a BeanShell script. + +

+ + +

+ + + +For full details on using BeanShell, please see the + +BeanShell website. + + + + +

+

+ +Note that a different Interpreter is used for each independent occurence of the assertion +in each thread in a test script, but the same Interpreter is used for subsequent invocations. +This means that variables persist across calls to the assertion. + +

+ + +

+ +All Assertions are called from the same thread as the sampler. + +

+ + +

+ +If the property "beanshell.assertion.init" is defined, it is passed to the Interpreter +as the name of a sourced file. This can be used to define common methods and variables. +There is a sample init file in the bin directory: BeanShellAssertion.bshrc + +

+ + +

+ +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + +No +
Reset bsh.Interpreter before each call + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see + +Best Practices - BeanShell scripting + +. + + +Yes +
ParametersParameters to pass to the BeanShell script. + The parameters are stored in the following variables: + +
    + + +
  • +Parameters - string containing the parameters as a single variable +
  • + + +
  • +bsh.args - String array containing parameters, split on white-space +
  • + + +
+
+No +
Script fileA file containing the BeanShell script to run. This overrides the script. + The file name is stored in the script variable FileName + +No +
ScriptThe BeanShell script to run. The return value is ignored. + +Yes (unless script file is provided) +
+

+

+There's a + +sample script + + you can try. +

+

+ +Before invoking the script, some variables are set up in the BeanShell interpreter. +These are strings unless otherwise noted: + +

    + + +
  • +log - the Logger Object. (e.g.) log.warn("Message"[,Throwable]) +
  • + + +
  • +SampleResult - the SampleResult Object; read-write +
  • + + +
  • +Response - the response Object; read-write +
  • + + +
  • +Failure - boolean; read-write; used to set the Assertion status +
  • + + +
  • +FailureMessage - String; read-write; used to set the Assertion message +
  • + + +
  • +ResponseData - the response body (byte []) +
  • + + +
  • +ResponseCode - e.g. 200 +
  • + + +
  • +ResponseMessage - e.g. OK +
  • + + +
  • +ResponseHeaders - contains the HTTP headers +
  • + + +
  • +RequestHeaders - contains the HTTP headers sent to the server +
  • + + +
  • +SampleLabel +
  • + + +
  • +SamplerData - data that was sent to the server +
  • + + +
  • +ctx - JMeterContext +
  • + + +
  • +vars - + +JMeterVariables + + - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.putObject("OBJ1",new Object()); +
  • + + +
  • +props - JMeterProperties (class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
+ + +

+

+The following methods of the Response object may be useful: + +

    + + +
  • +setStopThread(boolean) +
  • + + +
  • +setStopTest(boolean) +
  • + + +
  • +String getSampleLabel() +
  • + + +
  • +setSampleLabel(String) +
  • + + +
+

+

+
+ + + + +
+ +

+18.5.6 MD5Hex Assertion +

+
+
+

+The MD5Hex Assertion allows the user to check the MD5 hash of the response data. +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
MD5 sum32 hex digits representing the MD5 hash (case not significant) + +Yes +
+

+

+
+ + + + +
+ +

+18.5.7 HTML Assertion +

+
+
+

+The HTML Assertion allows the user to check the HTML syntax of the response data using JTidy. +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
doctypeomit/auto/strict/loose + +Yes +
FormatHTML, XHTML or XML + +Yes +
Errors onlyOnly take note of errors? + +Yes +
Error thresholdNumber of errors allowed before classing the response as failed + +Yes +
Warning thresholdNumber of warnings allowed before classing the response as failed + +Yes +
FilenameName of file to which report is written + +No +
+

+

+
+ + + + +
+ +

+18.5.8 XPath Assertion +

+
+
+

+The XPath Assertion tests a document for well formedness, has the option +of validating against a DTD, or putting the document through JTidy and testing for an +XPath. If that XPath exists, the Assertion is true. Using "/" will match any well-formed +document, and is the default XPath Expression. +The assertion also supports boolean expressions, such as "count(//*error)=2". +See + +http://www.w3.org/TR/xpath + + for more information +on XPath. + +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Use Tidy (tolerant parser)Use Tidy, i.e. be tolerant of XML/HTML errors + +Yes +
QuietSets the Tidy Quiet flag + +If Tidy is selected +
Report ErrorsIf a Tidy error occurs, then set the Assertion accordingly + +If Tidy is selected +
Show warningsSets the Tidy showWarnings option + +If Tidy is selected +
Use NamespacesShould namespaces be honoured? + +If Tidy is not selected +
Validate XMLCheck the document against its schema. + +If Tidy is not selected +
Ignore WhitespaceIgnore Element Whitespace. + +If Tidy is not selected +
Fetch External DTDsIf selected, external DTDs are fetched. + +If Tidy is not selected +
XPath AssertionXPath to match in the document. + +Yes +
True if nothing matchesTrue if a XPath expression is not matched + +No +
+

+

+ + +
+The non-tolerant parser can be quite slow, as it may need to download the DTD etc. + +
+

+

+ + +
+To overcome Xalan XPath parser implementation on which JMeter is based, you can help the parsing by +providing a Properties file which will contain: + +
    + + +
  • +prefix1=Full Namespace 1 +
  • + + +
  • +prefix2=Full Namespace 2 +
  • + + +
  • +... +
  • + + +
+ + +You reference this file in jmeter.properties file using the property: + +
    + + +
  • +xpath.namespace.config +
  • + + +
+ + +
+

+

+
+ + + + +
+ +

+18.5.9 XML Schema Assertion +

+
+
+

+The XML Schema Assertion allows the user to validate a response against an XML Schema. +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
File NameSpecify XML Schema File Name + +Yes +
+

+

+
+ + + + +
+ +

+18.5.10 BSF Assertion +

+
+
+ + +

+ +The BSF Assertion allows BSF script code to be used to check the status of the previous sample. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
LanguageThe BSF language to be used + +Yes +
ParametersParameters to pass to the script. + The parameters are stored in the following variables: + +
    + + +
  • +Parameters - string containing the parameters as a single variable +
  • + + +
  • +args - String array containing parameters, split on white-space +
  • + + +
+
+No +
Script fileA file containing the script to run. + +No +
ScriptThe script to run. + +Yes (unless script file is provided) +
+

+

+ +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. + +

+

+The following variables are set up for use by the script: +

+
    + + +
  • +log - (Logger) - can be used to write to the log file +
  • + + +
  • +Label - the String Label +
  • + + +
  • +Filename - the script file name (if any) +
  • + + +
  • +Parameters - the parameters (as a String) +
  • + + +
  • +args[] - the parameters as a String array (split on whitespace) +
  • + + +
  • +ctx - (JMeterContext) - gives access to the context +
  • + + +
  • +vars - ( + +JMeterVariables + +) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2"); +
  • + + +
  • +props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
  • +SampleResult, prev - (SampleResult) - gives access to the previous SampleResult (if any) +
  • + + +
  • +sampler - (Sampler)- gives access to the current sampler +
  • + + +
  • +OUT - System.out - e.g. OUT.println("message") +
  • + + +
  • +AssertionResult - the assertion result +
  • + + +
+

+ +The script can check various aspects of the SampleResult. +If an error is detected, the script should use AssertionResult.setFailureMessage("message") and AssertionResult.setFailure(true). + +

+

+For futher details of all the methods available on each of the above variables, please check the Javadoc +

+

+
+ + + + +
+ +

+18.5.11 JSR223 Assertion +

+
+
+ + +

+ +The JSR223 Assertion allows JSR223 script code to be used to check the status of the previous sample. +For details, see +BSF Assertion +. + +

+ + +

+
+ + + + +
+ +

+18.5.12 Compare Assertion +

+
+
+ +The Compare Assertion can be used to compare sample results within its scope. +Either the contents or the elapsed time can be compared, and the contents can be filtered before comparison. +The assertion comparisons can be seen in the +Comparison Assertion Visualizer +. + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Compare ContentWhether or not to compare the content (response data) + +Yes +
Compare TimeIf the value is >=0, then check if the response time difference is no greater than the value. + I.e. if the value is 0, then the response times must be exactly equal. + +Yes +
Comparison FiltersFilters can be used to remove strings from the content comparison. + For example, if the page has a time-stamp, it might be matched with: "Time: \d\d:\d\d:\d\d" and replaced with a dummy fixed time "Time: HH:MM:SS". + + +No +
+

+

+
+ + + + +
+ +

+18.5.13 SMIME Assertion +

+
+
+ +The SMIME Assertion can be used to evaluate the sample results from the Mail Reader Sampler. +This assertion verifies if the body of a mime message is signed or not. The signature can also be verified against a specific signer certificate. +As this is a functionality that is not necessarily needed by most users, additional jars need to be downloaded and added to JMETER_HOME/lib : +
+ + + +
    + + +
  • +bcmail-xxx.jar (BouncyCastle SMIME/CMS) +
  • + + +
  • +bcprov-xxx.jar (BouncyCastle Provider) +
  • + + +
+ +These need to be + +downloaded from BouncyCastle. + + + +

+ +If using the +Mail Reader Sampler +, +please ensure that you select "Store the message using MIME (raw)" otherwise the Assertion won't be able to process the message correctly. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Verify SignatureIf selected, the asertion will verify if it is a valid signature according to the parameters defined in the Signer Certificate box. + +Yes +
Message not signedWhether or not to expect a signature in the message + +Yes +
Signer Cerificate"No Check" means that it wil not perform signature verification. "Check values" is used to verify the signature against the inputs provided. And "Certificate file" will perform the verification against a specific certificate file. + +Yes +
Message Position + The Mail sampler can retrieve multiple messages in a single sample. + Use this field to specify which message will be checked. + Messages are numbered from 0, so 0 means the first message. + Negative numbers count from the LAST message; -1 means LAST, -2 means penultimate etc. + + +Yes +
+

+

+
+ +^ + +
+

+

+ + + + +
+ +18.6 Timers +
+
+ + + +
+ + + +

+ + Note that timers are processed + +before + + each sampler in the scope in which they are found; + if there are several timers in the same scope, + +all + + the timers will be processed + +before + each + + sampler. + +
+ + + Timers are only processed in conjunction with a sampler. + A timer which is not in the same scope as a sampler will not be processed at all. + +
+ + + To apply a timer to a single sampler, add the timer as a child element of the sampler. + The timer will be applied before the sampler is executed. + To apply a timer after a sampler, either add it to the next sampler, or add it as the + child of a +Test Action + Sampler. + +

+ + +
+ + + + +
+ +

+18.6.1 Constant Timer +

+
+
+ + +

+If you want to have each thread pause for the same amount of time between +requests, use this timer. +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this timer that is shown in the tree. + +No +
Thread DelayNumber of milliseconds to pause. + +Yes +
+

+

+
+ + + + +
+ +

+18.6.2 Gaussian Random Timer +

+
+
+

+This timer pauses each thread request for a random amount of time, with most +of the time intervals ocurring near a particular value. The total delay is the +sum of the Gaussian distributed value (with mean 0.0 and standard deviation 1.0) times +the deviation value you specify, and the offset value. +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this timer that is shown in the tree + +No +
DeviationDeviation in milliseconds. + +Yes +
Constant Delay OffsetNumber of milliseconds to pause in addition +to the random delay. + +Yes +
+

+

+
+ + + + +
+ +

+18.6.3 Uniform Random Timer +

+
+
+

+This timer pauses each thread request for a random amount of time, with +each time interval having the same probability of occurring. The total delay +is the sum of the random value and the offset value. +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this timer that is shown in the tree. + +No +
Random Delay MaximumMaxium random number of milliseconds to +pause. + +Yes +
Constant Delay OffsetNumber of milliseconds to pause in addition +to the random delay. + +Yes +
+

+

+
+ + + + +
+ +

+18.6.4 Constant Throughput Timer +

+
+
+

+This timer introduces variable pauses, calculated to keep the total throughput (in terms of samples per minute) as close as possible to a give figure. Of course the throughput will be lower if the server is not capable of handling it, or if other timers or time-consuming test elements prevent it. +

+ + +

+ +N.B. although the Timer is called the Constant Throughput timer, the throughput value does not need to be constant. +It can be defined in terms of a variable or function call, and the value can be changed during a test. +The value can be changed in various ways: + +

+ + +
    + + +
  • +using a counter variable +
  • + + +
  • +using a JavaScript or BeanShell function to provide a changing value +
  • + + +
  • +using the remote BeanShell server to change a JMeter property +
  • + + +
+ + +

+See + +Best Practices + + for further details. +Note that the throughput value should not be changed too often during a test +- it will take a while for the new value to take effect. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this timer that is shown in the tree. + +No +
Target ThroughputThroughput we want the timer to try to generate. + +Yes +
Calculate Throughput based on + +
    + + +
  • +this thread only - each thread will try to maintain the target throughput. The overall throughput will be proportional to the number of active threads. +
  • + + +
  • +all active threads in current thread group - the target throughput is divided amongst all the active threads in the group. + Each thread will delay as needed, based on when it last ran. +
  • + + +
  • +all active threads - the target throughput is divided amongst all the active threads in all Thread Groups. + Each thread will delay as needed, based on when it last ran. + In this case, each other Thread Group will need a Constant Throughput timer with the same settings. +
  • + + +
  • +all active threads in current thread group (shared) - as above, but each thread is delayed based on when any thread in the group last ran. +
  • + + +
  • +all active threads (shared) - as above; each thread is delayed based on when any thread last ran. +
  • + + +
+ + +
+Yes +
+

+

+
+ + + + +
+ +

+18.6.5 Synchronizing Timer +

+
+
+ + +

+ +The purpose of the SyncTimer is to block threads until X number of threads have been blocked, and +then they are all released at once. A SyncTimer can thus create large instant loads at various +points of the test plan. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this timer that is shown in the tree. + +No +
Number of Simultaneous Users to Group byNumber of threads to release at once. Setting it to 0 is equivalent to setting it to Number of threads in Thread Group. + +Yes +
+

+

+
+ + + + +
+ +

+18.6.6 BeanShell Timer +

+
+
+ + +

+ +The BeanShell Timer can be used to generate a delay. + +

+ + +

+ + + +For full details on using BeanShell, please see the + +BeanShell website. + + + + +

+ + +

+ +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + +No +
Reset bsh.Interpreter before each call + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see + +Best Practices - BeanShell scripting + +. + + +Yes +
ParametersParameters to pass to the BeanShell script. + The parameters are stored in the following variables: + +
    + + +
  • +Parameters - string containing the parameters as a single variable +
  • + + +
  • +bsh.args - String array containing parameters, split on white-space +
  • + + +
+ + +
+No +
Script file + A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The return value is used as the number of milliseconds to wait. + + +No +
Script + The BeanShell script. The return value is used as the number of milliseconds to wait. + + +Yes (unless script file is provided) +
+

+

+Before invoking the script, some variables are set up in the BeanShell interpreter: +

+
    + + +
  • +log - (Logger) - can be used to write to the log file +
  • + + +
  • +ctx - (JMeterContext) - gives access to the context +
  • + + +
  • +vars - ( + +JMeterVariables + +) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); +
  • + + +
  • +props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
  • +prev - (SampleResult) - gives access to the previous SampleResult (if any) +
  • + + +
+

+For details of all the methods available on each of the above variables, please check the Javadoc +

+

+If the property + +beanshell.timer.init + + is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script. +

+

+
+ + + + +
+ +

+18.6.7 BSF Timer +

+
+
+ + +

+ +The BSF Timer can be used to generate a delay using a BSF scripting language. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
ScriptLanguage + The scripting language to be used. + + +Yes +
ParametersParameters to pass to the script. + The parameters are stored in the following variables: + +
    + + +
  • +Parameters - string containing the parameters as a single variable +
  • + + +
  • +args - String array containing parameters, split on white-space +
  • + + +
+ + +
+No +
Script file + A file containing the script to run. + The return value is converted to a long integer and used as the number of milliseconds to wait. + + +No +
Script + The script. The return value is used as the number of milliseconds to wait. + + +Yes (unless script file is provided) +
+

+

+Before invoking the script, some variables are set up in the script interpreter: +

+
    + + +
  • +log - (Logger) - can be used to write to the log file +
  • + + +
  • +ctx - (JMeterContext) - gives access to the context +
  • + + +
  • +vars - ( + +JMeterVariables + +) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); +
  • + + +
  • +props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
  • +sampler - the current Sampler +
  • + + +
  • +Label - the name of the Timer +
  • + + +
  • +Filename - the file name (if any) +
  • + + +
  • +OUT - System.out +
  • + + +
+

+For details of all the methods available on each of the above variables, please check the Javadoc +

+

+
+ + + + +
+ +

+18.6.8 JSR223 Timer +

+
+
+ + +

+ +The JSR223 Timer can be used to generate a delay using a JSR223 scripting language, +For details, see +BSF Timer +. + +

+ + +

+
+ + + + +
+ +

+18.6.9 Poisson Random Timer +

+
+
+

+This timer pauses each thread request for a random amount of time, with most +of the time intervals ocurring near a particular value. The total delay is the +sum of the Poisson distributed value, and the offset value. +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this timer that is shown in the tree + +No +
LambdaLambda value in milliseconds. + +Yes +
Constant Delay OffsetNumber of milliseconds to pause in addition +to the random delay. + +Yes +
+

+

+
+ +^ + +
+

+

+ + + + +
+ +18.7 Pre Processors +
+
+ + + +
+ + + Preprocessors are used to modify the Samplers in their scope. + +
+ + + +
+ + + + +
+ +

+18.7.1 HTML Link Parser +

+
+
+ + +

+This modifier parses HTML response from the server and extracts +links and forms. A URL test sample that passes through this modifier will be examined to +see if it "matches" any of the links or forms extracted +from the immediately previous response. It would then replace the values in the URL +test sample with appropriate values from the matching link or form. Perl-type regular +expressions are used to find matches. +

+ + +

Control Panel

+
+

+ + +
+Matches are performed using protocol, host, path and parameter names. +The target sampler cannot contain parameters that are not in the response links. + +
+

+ +

Spidering Example

+ + +

+Consider a simple example: let's say you wanted JMeter to "spider" through your site, +hitting link after link parsed from the HTML returned from your server (this is not +actually the most useful thing to do, but it serves as a good example). You would create +a +Simple Controller +, and add the "HTML Link Parser" to it. Then, create an +HTTP Request, and set the domain to ".*", and the path likewise. This will +cause your test sample to match with any link found on the returned pages. If you wanted to +restrict the spidering to a particular domain, then change the domain value +to the one you want. Then, only links to that domain will be followed. + +

+ + + +

Poll Example

+ + +

+A more useful example: given a web polling application, you might have a page with +several poll options as radio buttons for the user to select. Let's say the values +of the poll options are very dynamic - maybe user generated. If you wanted JMeter to +test the poll, you could either create test samples with hardcoded values chosen, or you +could let the HTML Link Parser parse the form, and insert a random poll option into +your URL test sample. To do this, follow the above example, except, when configuring +your Web Test controller's URL options, be sure to choose "POST" as the +method. Put in hard-coded values for the domain, path, and any additional form parameters. +Then, for the actual radio button parameter, put in the name (let's say it's called "poll_choice"), +and then ".*" for the value of that parameter. When the modifier examines +this URL test sample, it will find that it "matches" the poll form (and +it shouldn't match any other form, given that you've specified all the other aspects of +the URL test sample), and it will replace your form parameters with the matching +parameters from the form. Since the regular expression ".*" will match with +anything, the modifier will probably have a list of radio buttons to choose from. It +will choose at random, and replace the value in your URL test sample. Each time through +the test, a new random value will be chosen. +

+ + + +


+Figure 18 - Online Poll Example +

+ + + +

+ + +
One important thing to remember is that you must create a test sample immediately +prior that will return an HTML page with the links and forms that are relevant to +your dynamic test sample. +
+

+ + +

+
+ + + + +
+ +

+18.7.2 HTTP URL Re-writing Modifier +

+
+
+

+This modifier works similarly to the HTML Link Parser, except it has a specific purpose for which +it is easier to use than the HTML Link Parser, and more efficient. For web applications that +use URL Re-writing to store session ids instead of cookies, this element can be attached at the +ThreadGroup level, much like the +HTTP Cookie Manager +. Simply give it the name +of the session id parameter, and it will find it on the page and add the argument to every +request of that ThreadGroup. +

+ + +

+Alternatively, this modifier can be attached to select requests and it will modify only them. +Clever users will even determine that this modifier can be used to grab values that elude the + +HTML Link Parser +. +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name given to this element in the test tree. + +No +
Session Argument NameThe name of the parameter to grab from + previous response. This modifier will find the parameter anywhere it exists on the page, and + grab the value assigned to it, whether it's in an HREF or a form. + +Yes +
Path ExtensionSome web apps rewrite URLs by appending + a semi-colon plus the session id parameter. Check this box if that is so. + +No +
Do not use equals in path extensionSome web apps rewrite URLs without using an "=" sign between the parameter name and value (such as Intershop Enfinity). + +No +
Do not use questionmark in path extensionPrevents the query string to end up in the path extension (such as Intershop Enfinity). + +No +
Cache Session Id? + Should the value of the session Id be saved for later use when the session Id is not present? + + +Yes +
+

+

+
+ + + + + +
+ +

+18.7.3 HTML Parameter Mask +

+
+
*** This element is deprecated. Use Counter instead ***
+

+The HTML Parameter Mask is used to generate unique values for HTML arguments. By +specifying the name of the parameter, a value prefix and suffix, and counter parameters, this +modifier will generate values of the form " + +name=prefixcountersuffix + +". Any HTTP +Request that it modifies, it will replace any parameter with the same name or add the appropriate +parameter to the requests list of arguments. +

+ + +

+ + +
The value of the argument in your HTTP Request must be a '*' in order for the HTML Parameter Mask +Modifier to replace it. +
+

+ + +

+As an example, the username for a login script could be modified to send a series of values +such as: +
+ + +user_1 +
+ + +user_2 +
+ + +user_3 +
+ + +user_4, etc. +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name given to this element in the test tree. + +No +
Name (second appearing)The name of the parameter to + modify or add to the HTTP Request. + +Yes +
ID PrefixA string value to prefix to every generated value. + +No +
Lower BoundA number value to start the counter at. + +Yes +
Upper BoundA number value to end the counter, at which point it restarts + with the Lower Bound value. + +Yes +
IncrementValue to increment the counter by each time through. + +Yes +
ID SuffixA string value to add as suffix to every generated vaue. + +No +
+

+

+
+ + + + +
+ +

+18.7.5 User Parameters +

+
+
+

+Allows the user to specify values for User Variables specific to individual threads. +

+ + +

+User Variables can also be specified in the Test Plan but not specific to individual threads. This panel allows +you to specify a series of values for any User Variable. For each thread, the variable will be assigned one of the values from the series +in sequence. If there are more threads than values, the values get re-used. For example, this can be used to assign a distinct +user id to be used by each thread. User variables can be referenced in any field of any jMeter Component. +

+ + + +

+The variable is specified by clicking the Add Variable button in the bottom of the panel and filling in the Variable name in the 'Name:' column. +To add a new value to the series, click the 'Add User' button and fill in the desired value in the newly added column. +

+ + + +

+Values can be accessed in any test component in the same thread group, using the + +function syntax + +: ${variable}. +

+ + +

+See also the +CSV Data Set Config + element, which is more suitable for large numbers of parameters +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Update Once Per IterationA flag to indicate whether the User Paramters element + should update its variables only once per iteration. if you embed functions into the UP, then you may need greater + control over how often the values of the variables are updated. Keep this box checked to ensure the values are + updated each time through the UP's parent controller. Uncheck the box, and the UP will update the parameters for + every sample request made within its + +scope + +. + +Yes +
+

+

+
+ + + + +
+ +

+18.7.7 BeanShell PreProcessor +

+
+
+ + +

+ +The BeanShell PreProcessor allows arbitrary code to be applied before taking a sample. + +

+ + +

+ + + +For full details on using BeanShell, please see the + +BeanShell website. + + + + +

+ + +

+ +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + +No +
Reset bsh.Interpreter before each call + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see + +Best Practices - BeanShell scripting + +. + + +Yes +
ParametersParameters to pass to the BeanShell script. + The parameters are stored in the following variables: + +
    + + +
  • +Parameters - string containing the parameters as a single variable +
  • + + +
  • +bsh.args - String array containing parameters, split on white-space +
  • + + +
+
+No +
Script fileA file containing the BeanShell script to run. + The file name is stored in the script variable FileName + +No +
ScriptThe BeanShell script. The return value is ignored. + +Yes (unless script file is provided) +
+

+

+Before invoking the script, some variables are set up in the BeanShell interpreter: +

+
    + + +
  • +log - (Logger) - can be used to write to the log file +
  • + + +
  • +ctx - (JMeterContext) - gives access to the context +
  • + + +
  • +vars - ( + +JMeterVariables + +) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); +
  • + + +
  • +props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
  • +prev - (SampleResult) - gives access to the previous SampleResult (if any) +
  • + + +
  • +sampler - (Sampler)- gives access to the current sampler +
  • + + +
+

+For details of all the methods available on each of the above variables, please check the Javadoc +

+

+If the property + +beanshell.preprocessor.init + + is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script. +

+

+
+ + + + +
+ +

+18.7.8 BSF PreProcessor +

+
+
+ + +

+ +The BSF PreProcessor allows BSF script code to be applied before taking a sample. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
LanguageThe BSF language to be used + +Yes +
ParametersParameters to pass to the script. + The parameters are stored in the following variables: + +
    + + +
  • +Parameters - string containing the parameters as a single variable +
  • + + +
  • +args - String array containing parameters, split on white-space +
  • + + +
+
+No +
Script fileA file containing the script to run. + +No +
ScriptThe script to run. + +Yes (unless script file is provided) +
+

+

+ +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. + +

+

+The following BSF variables are set up for use by the script: +

+
    + + +
  • +log - (Logger) - can be used to write to the log file +
  • + + +
  • +Label - the String Label +
  • + + +
  • +Filename - the script file name (if any) +
  • + + +
  • +Parameters - the parameters (as a String) +
  • + + +
  • +args[] - the parameters as a String array (split on whitespace) +
  • + + +
  • +ctx - (JMeterContext) - gives access to the context +
  • + + +
  • +vars - ( + +JMeterVariables + +) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2"); +
  • + + +
  • +props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
  • +sampler - (Sampler)- gives access to the current sampler +
  • + + +
  • +OUT - System.out - e.g. OUT.println("message") +
  • + + +
+

+For details of all the methods available on each of the above variables, please check the Javadoc +

+

+
+ + + + +
+ +

+18.7.8 JSR223 PreProcessor +

+
+
+ + +

+ +The JSR223 PreProcessor allows JSR223 script code to be applied before taking a sample. +For details, see +BSF PreProcessor +. + +

+ + +

+
+ + + + +
+ +

+18.7.9 JDBC PreProcessor +

+
+
+ + +

+ +The JDBC PreProcessor enables you to run some SQL statement just before a sample runs. +This can be useful if your JDBC Sample requires some data to be in DataBase and you cannot compute this in a setup Thread group. + +

+ + +

+ +See the following Test plan: + +

+ + +

See Also: +

+

+ + +

+ +In the linked test plan,"Create Price Cut-Off" JDBC PreProcessor calls a stored procedure to create a Price Cut-Off in Database, +this one will be used by "Calculate Price cut off". + + +


+Create Price Cut-Off Preprocessor +

+ + + +

+ + +

+
+ +^ + +
+

+

+ + + + +
+ +18.8 Post-Processors +
+
+ + + +

+ + As the name suggests, Post-Processors are applied after samplers. Note that they are + applied to + +all + + the samplers in the same scope, so to ensure that a post-processor + is applied only to a particular sampler, add it as a child of the sampler. + +

+ + +

+ + Note: Unless documented otherwise, Post-Processors are not applied to sub-samples (child samples) - + only to the parent sample. + In the case of BSF and BeanShell post-processors, the script can retrieve sub-samples using the method + + +prev.getSubResults() + + which returns an array of SampleResults. + The array will be empty if there are none. + +

+ + +

+ + Post-Processors are run before Assertions, so they do not have access to any Assertion Results, nor will + the sample status reflect the results of any Assertions. If you require access to Assertion Results, try + using a Listener instead. Also note that the variable JMeterThread.last_sample_ok is set to "true" or "false" + after all Assertions have been run. + +

+ + +
+ + + + +
+ +

+18.8.1 Regular Expression Extractor +

+
+
+

+Allows the user to extract values from a server response using a Perl-type regular expression. As a post-processor, +this element will execute after each Sample request in its scope, applying the regular expression, extracting the requested values, +generate the template string, and store the result into the given variable name. +

+

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Apply to: + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. + +
    + + +
  • +Main sample only - only applies to the main sample +
  • + + +
  • +Sub-samples only - only applies to the sub-samples +
  • + + +
  • +Main sample and sub-samples - applies to both. +
  • + + +
  • +JMeter Variable - assertion is to be applied to the contents of the named variable +
  • + + +
+ + Matching is applied to all qualifying samples in turn. + For example if there is a main sample and 3 sub-samples, each of which contains a single match for the regex, + (i.e. 4 matches in total). + For match number = 3, Sub-samples only, the extractor will match the 3rd sub-sample. + For match number = 3, Main sample and sub-samples, the extractor will match the 2nd sub-sample (1st match is main sample). + For match number = 0 or negative, all qualifying samples will be processed. + For match number > 0, matching will stop as soon as enough matches have been found. + +
+Yes +
Response Field to check + The following response fields can be checked: + +
    + + +
  • +Body - the body of the response, e.g. the content of a web-page (excluding headers) +
  • + + +
  • +Body (unescaped) - the body of the response, with all Html escape codes replaced. + Note that Html escapes are processed without regard to context, so some incorrect substitutions + may be made. + +
  • + + +
  • +Headers - may not be present for non-HTTP samples +
  • + + +
  • +URL +
  • + + +
  • +Response Code - e.g. 200 +
  • + + +
  • +Response Message - e.g. OK +
  • + + +
+ + Headers can be useful for HTTP samples; it may not be present for other sample types. +
+Yes +
Reference NameThe name of the JMeter variable in which to store the result. Also note that each group is stored as [refname]_g#, where [refname] is the string you entered as the reference name, and # is the group number, where group 0 is the entire match, group 1 is the match from the first set of parentheses, etc. + +Yes +
Regular ExpressionThe regular expression used to parse the response data. + This must contain at least one set of parentheses "()" to capture a portion of the string, unless using the group $0$. + Do not enclose the expression in / / - unless of course you want to match these characters as well. + + +Yes +
TemplateThe template used to create a string from the matches found. This is an arbitrary string + with special elements to refer to groups within the regular expression. The syntax to refer to a group is: '$1$' to refer to + group 1, '$2$' to refer to group 2, etc. $0$ refers to whatever the entire expression matches. + +Yes +
Match No.Indicates which match to use. The regular expression may match multiple times. + +
    + + +
  • +Use a value of zero to indicate JMeter should choose a match at random. +
  • + + +
  • +A positive number N means to select the nth match. +
  • + + +
  • + Negative numbers are used in conjunction with the ForEach controller - see below. +
  • + + +
+ + +
+Yes +
Default Value + If the regular expression does not match, then the reference variable will be set to the default value. + This is particularly useful for debugging tests. If no default is provided, then it is difficult to tell + whether the regular expression did not match, or the RE element was not processed or maybe the wrong variable + is being used. + +

+ + However, if you have several test elements that set the same variable, + you may wish to leave the variable unchanged if the expression does not match. + In this case, remove the default value once debugging is complete. + +

+ + +
+No, but recommended +
+

+

+ + If the match number is set to a non-negative number, and a match occurs, the variables are set as follows: + +

    + + +
  • +refName - the value of the template +
  • + + +
  • +refName_gn, where n=0,1,2 - the groups for the match +
  • + + +
  • +refName_g - the number of groups in the Regex (excluding 0) +
  • + + +
+ + If no match occurs, then the refName variable is set to the default (unless this is absent). + Also, the following variables are removed: + +
    + + +
  • +refName_g0 +
  • + + +
  • +refName_g1 +
  • + + +
  • +refName_g +
  • + + +
+ + +

+

+ + If the match number is set to a negative number, then all the possible matches in the sampler data are processed. + The variables are set as follows: + +

    + + +
  • +refName_matchNr - the number of matches found; could be 0 +
  • + + +
  • +refName_n, where n = 1,2,3 etc - the strings as generated by the template +
  • + + +
  • +refName_n_gm, where m=0,1,2 - the groups for match n +
  • + + +
  • +refName - always set to the default value +
  • + + +
  • +refName_gn - not set +
  • + + +
+ + Note that the refName variable is always set to the default value in this case, + and the associated group variables are not set. + +

+See also +Response Assertion + for some examples of how to specify modifiers, + and + + for further information on JMeter regular expressions. + +

+ + +

+

+
+ + + + +
+ +

+18.8.2 XPath Extractor +

+
+
+This test element allows the user to extract value(s) from + structured response - XML or (X)HTML - using XPath + query language. + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Apply to: + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. + +
    + + +
  • +Main sample only - only applies to the main sample +
  • + + +
  • +Sub-samples only - only applies to the sub-samples +
  • + + +
  • +Main sample and sub-samples - applies to both. +
  • + + +
  • +JMeter Variable - assertion is to be applied to the contents of the named variable +
  • + + +
+ + XPath matching is applied to all qualifying samples in turn, and all the matching results will be returned. + +
+Yes +
Use Tidy (tolerant parser)If checked use Tidy to parse HTML response into XHTML. + +
    + + +
  • +"Use Tidy" should be checked on for HTML response. Such response is converted to valid XHTML (XML compatible HTML) using Tidy +
  • + + +
  • +"Use Tidy" should be unchecked for both XHTML or XML response (for example RSS) +
  • + + +
+ + +
+Yes +
QuietSets the Tidy Quiet flag + +If Tidy is selected +
Report ErrorsIf a Tidy error occurs, then set the Assertion accordingly + +If Tidy is selected +
Show warningsSets the Tidy showWarnings option + +If Tidy is selected +
Use Namespaces + If checked, then the XML parser will use namespace resolution. + Note that currently only namespaces declared on the root element will be recognised. + A later version of JMeter may support user-definition of additional workspace names. + Meanwhile, a work-round is to replace: + +
+ + + //mynamespace:tagname + +
+ + + by + +
+ + + //*[local-name()='tagname' and namespace-uri()='uri-for-namespace'] + +
+ + + where "uri-for-namespace" is the uri for the "mynamespace" namespace. + + (not applicable if Tidy is selected) + +
+If Tidy is not selected +
Validate XMLCheck the document against its schema. + +If Tidy is not selected +
Ignore WhitespaceIgnore Element Whitespace. + +If Tidy is not selected +
Fetch External DTDsIf selected, external DTDs are fetched. + +If Tidy is not selected +
Return entire XPath fragment instead of text content? + If selected, the fragment will be returned rather than the text content. +
+ + + For example //title would return "<title>Apache JMeter</title>" rather than "Apache JMeter". +
+ + + In this case, //title/text() would return "Apache JMeter". + +
+Yes +
Reference NameThe name of the JMeter variable in which to store the result. + +Yes +
XPath QueryElement query in XPath language. Can return more than one match. + +Yes +
Default ValueDefault value returned when no match found. + It is also returned if the node has no value and the fragment option is not selected. + +No +
+

+

+To allow for use in a ForEach Controller, the following variables are set on return: +

+
    + + +
  • +refName - set to first (or only) match; if no match, then set to default +
  • + + +
  • +refName_matchNr - set to number of matches (may be 0) +
  • + + +
  • +refName_n - n=1,2,3 etc. Set to the 1st, 2nd 3rd match etc. + +
  • + + +
+

+Note: The next refName_n variable is set to null - e.g. if there are 2 matches, then refName_3 is set to null, + and if there are no matches, then refName_1 is set to null. + +

+

+XPath is query language targeted primarily for XSLT transformations. However it is usefull as generic query language for structured data too. See + + +XPath Reference + + or + +XPath specification + + for more information. Here are few examples: + +

+
+ + +
+/html/head/title +
+ + +
+extracts title element from HTML response +
+ + +
+/book/page[2] +
+ + +
+extracts 2nd page from a book +
+ + +
+/book/page +
+ + +
+extracts all pages from a book +
+ + +
+//form[@name='countryForm']//select[@name='country']/option[text()='Czech Republic'])/@value +
+ + +
+extracts value attribute of option element that match text 'Czech Republic' + inside of select element with name attribute 'country' inside of + form with name attribute 'countryForm' +
+ + +
+

+ + +
When "Use Tidy" is checked on - resulting XML document may slightly differ from original HTML response: + +
    + + +
  • +All elements and attribute names are converted to lowercase +
  • + + +
  • +Tidy attempts to correct improperly nested elements. For example - original (incorrect) + +ul/font/li + + becomes correct + +ul/li/font + +
  • + + +
+ + See + +Tidy homepage + + for more information. + +
+

+

+
+ + + + +
+ +

+18.8.3 Result Status Action Handler +

+
+
+This test element allows the user to stop the thread or the whole test if the relevant sampler failed. + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Action to be taken after a Sampler error + Determines what happens if a sampler error occurs, either because the sample itself failed or an assertion failed. + The possible choices are: + +
    + + +
  • +Continue - ignore the error and continue with the test +
  • + + +
  • +Stop Thread - current thread exits +
  • + + +
  • +Stop Test - the entire test is stopped at the end of any current samples. +
  • + + +
  • +Stop Test Now - the entire test is stopped abruptly. Any current samplers are interrupted if possible. +
  • + + +
+ + +
+No +
+

+

+
+ + + + +
+ +

+18.8.4 BeanShell PostProcessor +

+
+
+ + +

+ +The BeanShell PreProcessor allows arbitrary code to be applied after taking a sample. + +

+ + +

+For JMeter versions after 2.2 the BeanShell Post-Processor no longer ignores samples with zero-length result data +

+ + +

+ + + +For full details on using BeanShell, please see the + +BeanShell website. + + + + +

+ + +

+ +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + +No +
Reset bsh.Interpreter before each call + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see + +Best Practices - BeanShell scripting + +. + + +Yes +
ParametersParameters to pass to the BeanShell script. + The parameters are stored in the following variables: + +
    + + +
  • +Parameters - string containing the parameters as a single variable +
  • + + +
  • +bsh.args - String array containing parameters, split on white-space +
  • + + +
+
+No +
Script fileA file containing the BeanShell script to run. + The file name is stored in the script variable FileName + +No +
ScriptThe BeanShell script. The return value is ignored. + +Yes (unless script file is provided) +
+

+

+The following BeanShell variables are set up for use by the script: +

+
    + + +
  • +log - (Logger) - can be used to write to the log file +
  • + + +
  • +ctx - (JMeterContext) - gives access to the context +
  • + + +
  • +vars - ( + +JMeterVariables + +) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); +
  • + + +
  • +props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
  • +prev - (SampleResult) - gives access to the previous SampleResult +
  • + + +
  • +data - (byte [])- gives access to the current sample data +
  • + + +
+

+For details of all the methods available on each of the above variables, please check the Javadoc +

+

+If the property + +beanshell.postprocessor.init + + is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script. +

+

+
+ + + + +
+ +

+18.8.5 BSF PostProcessor +

+
+
+ + +

+ +The BSF PostProcessor allows BSF script code to be applied after taking a sample. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
LanguageThe BSF language to be used + +Yes +
ParametersParameters to pass to the script. + The parameters are stored in the following variables: + +
    + + +
  • +Parameters - string containing the parameters as a single variable +
  • + + +
  • +args - String array containing parameters, split on white-space +
  • + + +
+
+No +
Script fileA file containing the script to run. + +No +
ScriptThe script to run. + +Yes (unless script file is provided) +
+

+

+ +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. + +

+

+ +Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. + +

+
    + + +
  • +log - (Logger) - can be used to write to the log file +
  • + + +
  • +Label - the String Label +
  • + + +
  • +Filename - the script file name (if any) +
  • + + +
  • +Parameters - the parameters (as a String) +
  • + + +
  • +args[] - the parameters as a String array (split on whitespace) +
  • + + +
  • +ctx - (JMeterContext) - gives access to the context +
  • + + +
  • +vars - ( + +JMeterVariables + +) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2"); +
  • + + +
  • +props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234"); +
  • + + +
  • +prev - (SampleResult) - gives access to the previous SampleResult (if any) +
  • + + +
  • +sampler - (Sampler)- gives access to the current sampler +
  • + + +
  • +OUT - System.out - e.g. OUT.println("message") +
  • + + +
+

+For details of all the methods available on each of the above variables, please check the Javadoc +

+

+
+ + + + +
+ +

+18.8.6 JSR223 PostProcessor +

+
+
+ + +

+ +The JSR223 PostProcessor allows JSR223 script code to be applied after taking a sample. +For details, see the +BSF PostProcessor +. + +

+ + +

+
+ + + + +
+ +

+18.8.7 JDBC PostProcessor +

+
+
+ + +

+ +The JDBC PostProcessor enables you to run some SQL statement just after a sample has run. +This can be useful if your JDBC Sample changes some data and you want to reset state to what it was before the JDBC sample run. + +

+ + +

+
+

See Also: +

+

+

+ +In the linked test plan,"JDBC PostProcessor" JDBC PostProcessor calls a stored procedure to delete from Database the Price Cut-Off that was created by PreProcessor. + +


+JDBC PostProcessor +

+ + +

+
+

+

+ + + + +
+ +18.9 Miscellaneous Features +
+
+ + + +
+ + + +
+ + + + +
+ +

+18.9.1 Test Plan +

+
+
+ + +

+ +The Test Plan is where the overall settings for a test are specified. + +

+ + +

+ +Static variables can be defined for values that are repeated throughout a test, such as server names. +For example the variable SERVER could be defined as www.example.com, and the rest of the test plan +could refer to it as ${SERVER}. This simplifies changing the name later. + +

+ + +

+ +If the same variable name is reused on one of more + +User Defined Variables + Configuration elements, +the value is set to the last definition in the test plan (reading from top to bottom). +Such variables should be used for items that may change between test runs, +but which remain the same during a test run. + +

+ + +

+ + + +Note that the Test Plan cannot refer to variables it defines. + + +If you need to construct other variables from the Test Plan variables, +use a +User Defined Variables + test element. + +

+ + +

+ +Selecting Functional Testing instructs JMeter to save the additional sample information +- Response Data and Sampler Data - to all result files. +This increases the resources needed to run a test, and may adversely impact JMeter performance. +If more data is required for a particular sampler only, then add a Listener to it, and configure the fields as required. +[The option does not affect CSV result files, which cannot currently store such information.] + +

+ + +

+Also, an option exists here to instruct JMeter to run the +Thread Group + serially rather than in parallel. +

+ + +

+ +Test plan now provides an easy way to add classpath setting to a specific test plan. +The feature is additive, meaning that you can add jar files or directories, but removing an entry requires restarting JMeter. +Note that this cannot be used to add JMeter GUI plugins, because they are processed earlier. +However it can be useful for utility jars such as JDBC drivers. + +

+ + +

+ +JMeter properties also provides an entry for loading additional classpaths. +In jmeter.properties, edit "user.classpath" to include additional libraries. + +

+ + +

Control Panel

+
+

+
+ + + + +
+ +

+18.9.2 Thread Group +

+
+
+ + +

+A Thread Group defines a pool of users that will execute a particular test case against your server. In the Thread Group GUI, you can control the number of users simulated (num of threads), the ramp up time (how long it takes to start all the threads), the number of times to perform the test, and optionally, a start and stop time for the test. +

+ + +

+ +See also +tearDown Thread Group + and +setUp Thread Group +. + +

+ + +

+ +When using the scheduler, JMeter runs the thread group until either the number of loops is reached or the duration/end-time is reached - whichever occurs first. +Note that the condition is only checked between samples; when the end condition is reached, that thread will stop. +JMeter does not interrupt samplers which are waiting for a response, so the end time may be delayed arbitrarily. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
Action to be taken after a Sampler error + Determines what happens if a sampler error occurs, either because the sample itself failed or an assertion failed. + The possible choices are: + +
    + + +
  • +Continue - ignore the error and continue with the test +
  • + + +
  • +Start Next Loop - ignore the error, start next loop and continue with the test +
  • + + +
  • +Stop Thread - current thread exits +
  • + + +
  • +Stop Test - the entire test is stopped at the end of any current samples. +
  • + + +
  • +Stop Test Now - the entire test is stopped abruptly. Any current samplers are interrupted if possible. +
  • + + +
+ + +
+No +
Number of ThreadsNumber of users to simulate. + +Yes +
Ramp-up PeriodHow long JMeter should take to get all the threads started. If there are 10 threads and a ramp-up time of 100 seconds, then each thread will begin 10 seconds after the previous thread started, for a total time of 100 seconds to get the test fully up to speed. + +Yes +
Loop CountNumber of times to perform the test case. Alternatively, "forever" can be selected causing the test to run until manually stopped. + +Yes, unless forever is selected +
Start TimeIf the scheduler checkbox is selected, one can choose an absolute start time. When you start your test, JMeter will wait until the specified start time to begin testing. + Note: the Startup Delay field over-rides this - see below. + + +No +
End TimeIf the scheduler checkbox is selected, one can choose an absolute end time. When you start your test, JMeter will wait until the specified start time to begin testing, and it will stop at the specified end time. + Note: the Duration field over-rides this - see below. + + +No +
Duration (seconds) + If the scheduler checkbox is selected, one can choose a relative end time. + JMeter will use this to calculate the End Time, and ignore the End Time value. + + +No +
Startup delay (seconds) + If the scheduler checkbox is selected, one can choose a relative startup delay. + JMeter will use this to calculate the Start Time, and ignore the Start Time value. + + +No +
+

+

+
+ + + + +
+ +

+18.9.3 WorkBench +

+
+
+ + +

+The WorkBench simply provides a place to temporarily store test elements while not in use, for copy/paste purposes, or any other purpose you desire. +When you save your test plan, WorkBench items are not saved with it. +Your WorkBench can be saved independently, if you like (right-click on WorkBench and choose Save). +

+ + +

+Certain test elements are only available on the WorkBench: +

+ + + + + +

Control Panel

+
+

+
+ + + + +
+ +

+18.9.4 SSL Manager +

+
+
+

+ + The SSL Manager is a way to select a client certificate so that you can test + applications that use Public Key Infrastructure (PKI). + It is only needed if you have not set up the appropriate System properties. + +

+ +Choosing a Client Certificate + +

+ + You may either use a Java Key Store (JKS) format key store, or a Public Key + Certificate Standard #12 (PKCS12) file for your client certificates. There + is a feature of the JSSE libraries that require you to have at least a six character + password on your key (at least for the keytool utility that comes with your + JDK). + +

+

+ + To select the client certificate, choose Options->SSL Manager from the menu bar. + You will be presented with a file finder that looks for PKCS12 files by default. + Your PKCS12 file must have the extension '.p12' for SSL Manager to recognize it + as a PKCS12 file. Any other file will be treated like an average JKS key store. + If JSSE is correctly installed, you will be prompted for the password. The text + box does not hide the characters you type at this point--so make sure no one is + looking over your shoulder. The current implementation assumes that the password + for the keystore is also the password for the private key of the client you want + to authenticate as. + +

+

+Or you can set the appropriate System properties - see the system.properties file. +

+

+ + The next time you run your test, the SSL Manager will examine your key store to + see if it has at least one key available to it. If there is only one key, SSL + Manager will select it for you. If there is more than one key, it currently selects the first key. + There is currently no way to select other entries in the keystore, so the desired key must be the first. + +

+ +Things to Look Out For + +

+ + You must have your Certificate Authority (CA) certificate installed properly + if it is not signed by one of the five CA certificates that ships with your + JDK. One method to install it is to import your CA certificate into a JKS + file, and name the JKS file "jssecacerts". Place the file in your JRE's + lib/security folder. This file will be read before the "cacerts" file in + the same directory. Keep in mind that as long as the "jssecacerts" file + exists, the certificates installed in "cacerts" will not be used. This may + cause problems for you. If you don't mind importing your CA certificate into + the "cacerts" file, then you can authenticate against all of the CA certificates + installed. + +

+

+
+ + + + +
+ +

+18.9.5 HTTP Proxy Server +

+
+
+

+The Proxy Server allows JMeter to watch and record your actions while you browse your web application +with your normal browser. JMeter will create test sample objects and store them +directly into your test plan as you go (so you can view samples interactively while you make them). +

+ + + +

+To use the proxy server, + +add + + the HTTP Proxy Server element to the workbench. +Select the WorkBench element in the tree, and right-click on this element to get the +Add menu (Add --> Non-Test Elements --> HTTP Proxy Server). +

+ + +

+ +You also need to set up your browser to use the JMeter proxy port as the proxy for HTTP and HTTPS requests. +Do not use JMeter as the proxy for any other request types - FTP, etc. - as the JMeter proxy cannot handle them. + +

+ + +

+ +When recording HTTPS, the JMeter proxy server uses a dummy certificate to enable it to accept the SSL connection from +the browser. This certificate is not one of the certificates that browsers normally trust, and will not be for the +correct host, so the browser should display a dialogue asking if you want to accept the certificate or not. For example: + + + +1) The server's name "www.example.com" does not match the certificate's name + "JMeter Proxy". Somebody may be trying to eavesdrop on you. +2) The certificate for "JMeter Proxy" is signed by the unknown Certificate Authority + "JMeter Proxy". It is not possible to verify that this is a valid certificate. + + + +You will need to accept the certificate in order to allow the JMeter Proxy to intercept the SSL traffic in order to +record it. You should only accept the certificate temporarily. + +

+ + +

+ +The following properties can be used to change the certificate that is used: + +

    + + +
  • +proxy.cert.directory - the directory in which to find the certificate (default = JMeter bin/) +
  • + + +
  • +proxy.cert.file - name of the keystore file (default "proxyserver.jks") +
  • + + +
  • +proxy.cert.keystorepass - keystore password (default "password") +
  • + + +
  • +proxy.cert.keypassword - certificate key password (default "password") +
  • + + +
  • +proxy.cert.type - the certificate type (default "JKS") +
  • + + +
  • +proxy.cert.factory - the factory (default "SunX509") +
  • + + +
  • +proxy.ssl.protocol - the protocol to be used (default "SSLv3") +
  • + + +
+ + +

+ + +

+ + +
+If your browser currently uses a proxy (e.g. a company intranet may route all external requests via a proxy), +then you need to + +tell JMeter to use that proxy + + before starting JMeter, +using the + +command-line options + + -H and -P. +This setting will also be needed when running the generated test plan. + +
+

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this controller that is shown in the tree. + +No +
PortThe port that the Proxy Server listens to. 8080 is the default, but you can change it. + +Yes +
Attempt HTTPS Spoofing + [Note: HTTPS spoofing should no longer be required] + When you enable HTTPS spoofing, the following happens: + +
    + + +
  • +All matching (see below) http requests from the client are turned into https (between the proxy + and the web server). +
  • + + +
  • +All text response data is scanned and any occurrence of the string "https://" + is replaced with "http://"; the default HTTPS port (443) is also removed if present. +
  • + + +
+ + So if you want to use this feature, while you are browsing in your client, + instead of typing "https://..." into the browser, type "http://...". + JMeter will request and record + +everything that matches + + as https, whether it should be or not. + +
+Yes +
Optional URL match string + If this is specified, it must be a regular expression (java.util.regex) which matches the + HTTP URL(s) to be spoofed. + For example, if you want to spoof http://a.b.c/service/ but not http://a.b.c/images, + then you could use the expression "http://a.b.c/service/.*". + Note that the expression ends in ".*" because it must match the whole URL. + + +No +
Target ControllerThe controller where the proxy will store the generated samples. By default, it will look for a Recording Controller and store them there wherever it is. + +Yes +
GroupingWhether to group samplers for requests from a single "click" (requests received without significant time separation), and how to represent that grouping in the recording: + +
    + + +
  • +Do not group samplers: store all recorded samplers sequentially, without any grouping. +
  • + + +
  • +Add separators between groups: add a controller named "--------------" to create a visual separation between the groups. Otherwise the samplers are all stored sequentially. +
  • + + +
  • +Put each group in a new controller: create a new +Simple Controller + for each group, and store all samplers for that group in it. +
  • + + +
  • +Store 1st sampler of each group only: only the first request in each group will be recorded. The "Follow Redirects" and "Retrieve All Embedded Resources..." flags will be turned on in those samplers. +
  • + + +
  • +Put each group in a new transaction controller: create a new +Transaction Controller + for each group, and store all samplers for that group in it. +
  • + + +
+ + The property + +proxy.pause + + determines the minimum gap that JMeter needs between requests + to treat them as separate "clicks". The default is 1000 (milliseconds) i.e. 1 second. + If you are using grouping, please ensure that you leave the required gap between clicks. + +
+Yes +
Capture HTTP HeadersShould headers be added to the plan? + If specified, a Header Manager will be added to each HTTP Sampler. + The Proxy server always removes Cookie and Authorization headers from the generated Header Managers. + By default it also removes If-Modified-Since and If-None-Match headers. + These are used to determine if the browser cache items are up to date; + when recording one normally wants to download all the content. + To change which additional headers are removed, define the JMeter property + +proxy.headers.remove + + + as a comma-separated list of headers. + + +Yes +
Add AssertionsAdd a blank assertion to each sampler? + +Yes +
Regex MatchingUse Regex Matching when replacing variables? If checked replacement will use word boundaries, ie it will only replace word matching values of variable, not part of a word. A word boundary follows Perl5 definition and is equivalent to \b. + +Yes +
TypeWhich type of sampler to generate (the Java default or HTTPClient) + +Yes +
Redirect AutomaticallySet Redirect Automatically in the generated samplers? + +Yes +
Follow RedirectsSet Follow Redirects in the generated samplers? + +Yes +
Use Keep-AliveSet Use Keep-Alive in the generated samplers? + +Yes +
Retrieve all Embedded ResourcesSet Retrieve all Embedded Resources in the generated samplers? + +Yes +
Content Type filter + Filter the requests based on the content-type - e.g. "text/html [;charset=utf-8 ]". + The fields are regular expressions which are checked to see if they are contained in the content-type. + [Does not have to match the entire field]. + The include filter is checked first, then the exclude filter. + Samples which are filtered out will not be stored. + + +No +
Patterns to IncludeRegular expressions that are matched against the full URL that is sampled. Allows filtering of requests that are recorded. All requests pass through, but only + those that meet the requirements of the Include/Exclude fields are + +recorded + +. If both Include and Exclude are + left empty, then everything is recorded (which can result in dozens of samples recorded for each page, as images, stylesheets, + etc are recorded). + +If there is at least one entry in the Include field, then only requests that match one or more Include patterns are + recorded + +. + +No +
Patterns to ExcludeRegular expressions that are matched against the URL that is sampled. + + +Any requests that match one or more Exclude pattern are + +not + + recorded + +. + +No +
Start ButtonStart the proxy server. JMeter writes the following message to the console once the proxy server has started up and is ready to take requests: "Proxy up and running!". + +N/A +
Stop ButtonStop the proxy server. + +N/A +
Restart ButtonStops and restarts the proxy server. This is + useful when you change/add/delete an include/exclude filter expression. + +N/A +
+

+

+The + +include and exclude patterns + + are treated as regular expressions (using Jakarta ORO). +They will be matched against the host name, port (actual or implied) path and query (if any) of each browser request. +If the URL you are browsing is +
+ + + + +"http://jmeter.apache.org/jmeter/index.html?username=xxxx" + +, +
+ + +then the regular expression will be tested against the string: +
+ + + + +"jmeter.apache.org:80/jmeter/index.html?username=xxxx" + +. +
+ + +Thus, if you want to include all .html files, your regular expression might look like: +
+ + + + +".*\.html(\?.*)?" + + - or + +".*\.html" + + +if you know that there is no query string or you only want html pages without query strings. + +

+

+ +If there are any include patterns, then the URL + +must match at least one + + of the patterns +, otherwise it will not be recorded. +If there are any exclude patterns, then the URL + +must not match any + + of the patterns +, otherwise it will not be recorded. +Using a combination of includes and excludes, +you should be able to record what you are interested in and skip what you are not. + +

+

+ +N.B. the string that is matched by the regular expression must be the same as the + +whole + + host+path string. +
+ +Thus + +"\.html" + + will + +not + + match + +j.a.o/index.html + + + +

+

+ +Versions of JMeter from 2.3.2 are able to capture binary POST data. +To configure which content-types are treated as binary, update the JMeter property proxy.binary.types. +The default settings are as follows: + +

+
+# These content-types will be handled by saving the request in a file:
+proxy.binary.types=application/x-amf,application/x-java-serialized-object
+# The files will be saved in this directory:
+proxy.binary.directory=user.dir
+# The files will be created with this file filesuffix:
+proxy.binary.filesuffix=.binary
+
+
+ + +

+

+It is also possible to have the proxy add timers to the recorded script. To +do this, create a timer directly within the HTTP Proxy Server component. +The proxy will place a copy of this timer into each sample it records, or into +the first sample of each group if you're using grouping. This copy will then be +scanned for occurences of variable ${T} in its properties, and any such +occurences will be replaced by the time gap from the previous sampler +recorded (in milliseconds). +

+

+When you are ready to begin, hit "start". +

+

+ + +
You will need to edit the proxy settings of your browser to point at the +appropriate server and port, where the server is the machine JMeter is running on, and +the port # is from the Proxy Control Panel shown above. +
+

+

+ +Where Do Samples Get Recorded? + +

+

+JMeter places the recorded samples in the Target Controller you choose. If you choose the default option +"Use Recording Controller", they will be stored in the first Recording Controller found in the test object tree (so be +sure to add a Recording Controller before you start recording). +

+

+ +If the Proxy does not seem to record any samples, this could be because the browser is not actually using the proxy. +To check if this is the case, try stopping the proxy. +If the browser still downloads pages, then it was not sending requests via the proxy. +Double-check the browser options. +If you are trying to record from a server running on the same host, +then check that the browser is not set to "Bypass proxy server for local addresses" +(this example is from IE7, but there will be similar options for other browsers). +If JMeter does not record browser URLs such as http://localhost/ or http://127.0.0.1/, +try using the non-loopback hostname or IP address, e.g. http://myhost/ or http://192.168.0.2/. + +

+

+ +Handling of HTTP Request Defaults + +

+

+If the HTTP Proxy Server finds enabled +HTTP Request Defaults + directly within the +controller where samples are being stored, or directly within any of its parent controllers, the recorded samples +will have empty fields for the default values you specified. You may further control this behaviour by placing an +HTTP Request Defaults element directly within the HTTP Proxy Server, whose non-blank values will override +those in the other HTTP Request Defaults. See + + Best +Practices with the Proxy Server + + for more info. +

+

+ +User Defined Variable replacement + +

+

+Similarly, if the HTTP Proxy Server finds +User Defined Variables + (UDV) directly within the +controller where samples are being stored, or directly within any of its parent controllers, the recorded samples +will have any occurences of the values of those variables replaced by the corresponding variable. Again, you can +place User Defined Variables directly within the HTTP Proxy Server to override the values to be replaced. See + + + Best Practices with the Proxy Server + + for more info. +

+

+ + +
Please note that matching is case-sensitive. +
+

+

+Replacement by Variables: by default, the Proxy server looks for all occurences of UDV values. +If you define the variable "WEB" with the value "www", for example, +the string "www" will be replaced by ${WEB} wherever it is found. +To avoid this happening everywhere, set the "Regex Matching" check-box. +This tells the proxy server to treat values as Regexes (using ORO). + +
+ + +If you want to match a whole string only, enclose it in ^$, e.g. "^thus$". + +
+ + +If you want to match /images at the start of a string only, use the value "^/images". +Jakarta ORO also supports zero-width look-ahead, so one can match /images/... +but retain the trailing / in the output by using "^/images(?=/)". +Note that the current version of Jakara ORO does not support look-behind - i.e. "(?<=...) or (?<!...)". + +
+ + +If there are any problems interpreting any variables as patterns, these are reported in jmeter.log, +so be sure to check this if UDVs are not working as expected. + +

+

+When you are done recording your test samples, stop the proxy server (hit the "stop" button). Remember to reset +your browser's proxy settings. Now, you may want to sort and re-order the test script, add timers, listeners, a +cookie manager, etc. +

+

+ +How can I record the server's responses too? + +

+

+Just place a +View Results Tree + listener as a child of the Proxy Server and the responses will be displayed. +You can also add a +Save Responses to a file + Post-Processor which will save the responses to files. + +

+

+ +Cookie Manager + +

+

+ +If the server you are testing against uses cookies, remember to add an +HTTP Cookie Manager + to the test plan +when you have finished recording it. +During recording, the browser handles any cookies, but JMeter needs a Cookie Manager +to do the cookie handling during a test run. +The JMeter Proxy server passes on all cookies sent by the browser during recording, but does not save them to the test +plan because they are likely to change between runs. + +

+

+ +Authorization Manager + +

+

+ +The Proxy server passes on any Authorization headers sent by the browser, but does not save them in the test plan. +If the site requires Authorization, you will need to add an Authorization Manager and fill it in with the necessary entries. + +

+

+ +Uploading files + +

+

+ +Some browsers (e.g. Firefox and Opera) don't include the full name of a file when uploading files. +This can cause the JMeter proxy server to fail. +One solution is to ensure that any files to be uploaded are in the JMeter working directory, +either by copying the files there or by starting JMeter in the directory containing the files. + +

+

+ +Recording HTTP Based Non Textual Protocols not natively available in JMeter + +

+

+ +You may have to record an HTTP protocol that is not handled by default by JMeter (Custom Binary Protocol, Adobe Flex, Microsoft Silverlight... ). +Although JMeter does not provide a native proxy implementation to record these protocols, you have the ability to +record these protocols by implementing a custom SamplerCreator. This Sampler Creator will translate the binary format into a HTTPSamplerBase subclass +that can be added to the JMeter Test Case. +For more details see "Extending JMeter". + +

+

+
+ + + + +
+ +

+18.9.6 HTTP Mirror Server +

+
+
+ + +

+ +The HTTP Mirrror Server is a very simple HTTP server - it simply mirrors the data sent to it. +This is useful for checking the content of HTTP requests. + +

+ + +

+ +It uses default port 8081 since 2.6. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
PortPort on which Mirror server listens, defaults to 8081. + +Yes +
Max Number of threadsIf set to a value > 0, number of threads serving requests will be limited to the configured number, if set to a value <=0 + a new thread will be created to serve each incoming request. Defaults to 0 + +No +
Max Queue sizeSize of queue used for holding tasks before they are executed by Thread Pool, when Thread pool is exceeded, incoming requests will + be held in this queue and discarded when this queue is full. This parameter is only used if Max Number of Threads is greater than 0. Defaults to 25 + +No +
+

+

+ + +
+Note that you can make simulate requests response time by adding an HTTP Header Manager with the following name/value pair: + +
    + + +
  • +X-Sleep=Time to sleep in ms +
  • + + +
+ + +
+

+

+
+ + + + +
+ +

+18.9.7 Property Display +

+
+
+ + +

+ +The Property Display shows the values of System or JMeter properties. +Values can be changed by entering new text in the Value column. +It is available only on the WorkBench. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
+

+

+
+ + + + +
+ +

+18.9.8 Debug Sampler +

+
+
+ + +

+ +The Debug Sampler generates a sample containing the values of all JMeter variables and/or properties. + +

+ + +

+ +The values can be seen in the +View Results Tree + Listener Response Data pane. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
JMeter PropertiesInclude JMeter properties ? + +Yes +
JMeter VariablesInclude JMeter variables ? + +Yes +
System PropertiesInclude System properties ? + +Yes +
+

+

+
+ + + + +
+ +

+18.9.8 Debug PostProcessor +

+
+
+ + +

+ +The Debug PostProcessor creates a subSample with the details of the previous Sampler properties, +JMeter variables, properties and/or System Properties. + +

+ + +

+ +The values can be seen in the +View Results Tree + Listener Response Data pane. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +No +
JMeter PropertiesWhether to show JMeter properties (default false). + +Yes +
JMeter VariablesWhether to show JMeter variables (default false). + +Yes +
Sampler PropertiesWhether to show Sampler properties (default true). + +Yes +
System PropertiesWhether to show System properties (default false). + +Yes +
+

+

+
+ + + + +
+ +

+18.9.9 Test Fragment +

+
+
+ + +

+ +The Test Fragment is used in conjunction with the +Include Controller + and +Module Controller +. + +

+ + +

Control Panel

+
+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
NameDescriptive name for this element that is shown in the tree. + +Yes +
+

+

+
+ + + + +
+ +

+18.9.10 setUp Thread Group +

+
+
+ + +

+ + A special type of ThreadGroup that can be utilized to perform Pre-Test Actions. The behavior of these threads + is exactly like a normal +Thread Group + element. The difference is that these type of threads + execute before the test proceeds to the executing of regular Thread Groups. + +

+ + +

Control Panel

+
+

+
+ + + + +
+ +

+18.9.11 tearDown Thread Group +

+
+
+ + +

+ + A special type of ThreadGroup that can be utilized to perform Post-Test Actions. The behavior of these threads + is exactly like a normal +Thread Group + element. The difference is that these type of threads + execute after the test has finished executing its regular Thread Groups. + +

+ + +

Control Panel

+
+

+
+ +^ + +
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/functions.html b/ApacheJmeter/docs/usermanual/functions.html new file mode 100644 index 0000000..4b85a5e --- /dev/null +++ b/ApacheJmeter/docs/usermanual/functions.html @@ -0,0 +1,4230 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Functions and Variables + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +19. Functions and Variables +
+
+

+ +JMeter functions are special values that can populate fields of any Sampler or other +element in a test tree. A function call looks like this: +

+

+ +${__functionName(var1,var2,var3)} + +

+

+ +Where "__functionName" matches the name of a function. + +
+ + +Parentheses surround the parameters sent to the function, for example ${__time(YMD)} +The actual parameters vary from function to function. +Functions that require no parameters can leave off the parentheses, for example ${__threadNum}. + +

+

+ +If a function parameter contains a comma, then be sure to escape this with "\", otherwise JMeter will treat it as a parameter delimiter. +For example: + +

+
+${__time(EEE\, d MMM yyyy)}
+
+
+ + +

+

+Variables are referenced as follows: + +

+
+${VARIABLE}
+
+
+ + +

+

+ + + + +If an undefined function or variable is referenced, JMeter does not report/log an error - the reference is returned unchanged. +For example if UNDEF is not defined as a variable, then the value of ${UNDEF} is ${UNDEF}. + + + +Variables, functions (and properties) are all case-sensitive. + + + +Versions of JMeter after 2.3.1 trim spaces from variable names before use, so for example +${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY '. + + + + +

+

+ + +
+Properties are not the same as variables. +Variables are local to a thread; properties are common to all threads, +and need to be referenced using the __P or __property function. + +
+

+

+List of functions, loosely grouped into types. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +Type of function + + + +Name + + + +Comment + + + +Since + +
+ +Information + + + + threadNum + + + +get thread number + + + +1.X + +
+ +Information + + + + samplerName + + + +get the sampler name (label) + + + +2.5 + +
+ +Information + + + + machineIP + + + +get the local machine IP address + + + +2.6 + +
+ +Information + + + + machineName + + + +get the local machine name + + + +1.X + +
+ +Information + + + + time + + + +return current time in various formats + + + +2.2 + +
+ +Information + + + + log + + + +log (or display) a message (and return the value) + + + +2.2 + +
+ +Information + + + + logn + + + +log (or display) a message (empty return value) + + + +2.2 + +
+ +Input + + + + StringFromFile + + + +read a line from a file + + + +1.9 + +
+ +Input + + + + FileToString + + + +read an entire file + + + +2.4 + +
+ +Input + + + + CSVRead + + + +read from CSV delimited file + + + +1.9 + +
+ +Input + + + + XPath + + + +Use an XPath expression to read from a file + + + +2.0.3 + +
+ +Calculation + + + + counter + + + +generate an incrementing number + + + +1.X + +
+ +Calculation + + + + intSum + + + +add int numbers + + + +1.8.1 + +
+ +Calculation + + + + longSum + + + +add long numbers + + + +2.3.2 + +
+ +Calculation + + + + Random + + + +generate a random number + + + +1.9 + +
+ +Calculation + + + + RandomString + + + +generate a random string + + + +2.6 + +
+ +Scripting + + + + BeanShell + + + +run a BeanShell script + + + +1.X + +
+ +Scripting + + + + javaScript + + + +process JavaScript (Mozilla Rhino) + + + +1.9 + +
+ +Scripting + + + + jexl, jexl2 + + + +evaluate a Commons Jexl expression + + + +jexl(2.2), jexl2(2.6) + +
+ +Properties + + + + property + + + +read a property + + + +2.0 + +
+ +Properties + + + + P + + + +read a property (shorthand method) + + + +2.0 + +
+ +Properties + + + + setProperty + + + +set a JMeter property + + + +2.1 + +
+ +Variables + + + + split + + + +Split a string into variables + + + +2.0.2 + +
+ +Variables + + + + V + + + +evaluate a variable name + + + +2.3RC3 + +
+ +Variables + + + + eval + + + +evaluate a variable expression + + + +2.3.1 + +
+ +Variables + + + + evalVar + + + +evaluate an expression stored in a variable + + + +2.3.1 + +
+ +String + + + + regexFunction + + + +parse previous response using a regular expression + + + +1.X + +
+ +String + + + + char + + + +generate Unicode char values from a list of numbers + + + +2.3.3 + +
+ +String + + + + unescape + + + +Process strings containing Java escapes (e.g. \n & \t) + + + +2.3.3 + +
+ +String + + + + unescapeHtml + + + +Decode HTML-encoded strings + + + +2.3.3 + +
+ +String + + + + escapeHtml + + + +Encode strings using HTML encoding + + + +2.3.3 + +
+ +String + + + + TestPlanName + + + +Return name of current test plan + + + +2.6 + +
+

+

+ + + + +
+ +19.1 What can functions do + +
+
+

+There are two kinds of functions: user-defined static values (or variables), and built-in functions. +
+ + +User-defined static values allow the user to define variables to be replaced with their static value when +a test tree is compiled and submitted to be run. This replacement happens once at the beginning of the test +run. This could be used to replace the DOMAIN field of all HTTP requests, for example - making it a simple +matter to change a test to target a different server with the same test. + +

+

+ +Note that variables cannot currently be nested; i.e ${Var${N}} does not work. +The __V (variable) function (versions after 2.2) can be used to do this: ${__V(Var${N})}. +In earlier JMeter versions one can use ${__BeanShell(vars.get("Var${N}")}. + +

+

+This type of replacement is possible without functions, but was less convenient and less intuitive. +It required users to create default config elements that would fill in blank values of Samplers. +Variables allow one to replace only part of any given value, not just filling in blank values. +

+

+ +With built-in functions users can compute new values at run-time based on previous response data, which +thread the function is in, the time, and many other sources. These values are generated fresh for every +request throughout the course of the test. +

+

+ + +
Functions are shared between threads. +Each occurrence of a function call in a test plan is handled by a separate function instance. +
+

+
+

+ + + + +
+ +19.2 Where can functions and variables be used? + +
+
+

+ +Functions and variables can be written into any field of any test component (apart from the TestPlan - see below). +Some fields do not allow random strings +because they are expecting numbers, and thus will not accept a function. However, most fields will allow +functions. + +

+

+ +Functions which are used on the Test Plan have some restrictions. +JMeter thread variables will have not been fully set up when the functions are processed, +so variable names passed as parameters will not be set up, and variable references will not work, +so split() and regex() and the variable evaluation functions won't work. +The threadNum() function won't work (and does not make sense at test plan level). +The following functions should work OK on the test plan: + +

    + + +
  • +intSum +
  • + + +
  • +longSum +
  • + + +
  • +machineName +
  • + + +
  • +BeanShell +
  • + + +
  • +javaScript +
  • + + +
  • +jexl +
  • + + +
  • +random +
  • + + +
  • +time +
  • + + +
  • +property functions +
  • + + +
  • +log functions +
  • + + +
+ + +

+

+ +Configuration elements are processed by a separate thread. +Therefore functions such as __threadNum do not work properly in elements such as User Defined Variables. +Also note that variables defined in a UDV element are not available until the element has been processed. + +

+

+ + +
When using variable/function references in SQL code (etc), +remember to include any necessary quotes for text strings, +i.e. use +
+ + +SELECT item from table where name='${VAR}' + +
+ + +not + + + +
+ + +SELECT item from table where name=${VAR} + +
+ +(unless VAR itself contains the quotes) + +
+

+
+

+ + + + +
+ +19.3 How to reference variables and functions + +
+
+

+Referencing a variable in a test element is done by bracketing the variable name with '${' and '}'. +

+

+Functions are referenced in the same manner, but by convention, the names of +functions begin with "__" to avoid conflict with user value names + +* + +. Some functions take arguments to +configure them, and these go in parentheses, comma-delimited. If the function takes no arguments, the parentheses can +be omitted. +

+

+ +Argument values that themselves contain commas should be escaped as necessary. +If you need to include a comma in your parameter value, escape it like so: '\,'. + + +This applies for example to the scripting functions - Javascript, Beanshell, Jexl - where it is necessary to escape any commas +that may be needed in script method calls - e.g. + +

+
+
+    ${__BeanShell(vars.put("name"\,"value"))}
+
+
+

+ +Alternatively, you can define your script as a variable, e.g. on the Test Plan: + +

+SCRIPT          vars.put("name","value")
+
+ +The script can then be referenced as follows: + +
+${__BeanShell(${SCRIPT})}
+
+ +There is no need to escape commas in the SCRIPT variable because the function call is parsed before the variable is replaced with its value. +This works well in conjunction with the BSF or BeanShell Samplers, as these can be used to test Javascript, Jexl and BeanShell scripts. + +

+

+ +Functions can reference variables and other functions, for example + + +${__XPath(${__P(xpath.file),${XPATH})} + + +will use the property "xpath.file" as the file name +and the contents of the variable XPATH as the expression to search for. + +

+

+ +JMeter provides a tool to help you construct +function calls for various built-in functions, which you can then copy-paste. +It will not automatically escape values for you, since functions can be parameters to other functions, and you should only escape values you intend as literal. + +

+

+ + +
+If a string contains a backslash('\') and also contains a function or variable reference, the backslash will be removed if +it appears before '$' or ',' or '\'. +This behaviour is necessary to allow for nested functions that include commas or the string ${. +Backslashes before '$' or ',' or '\' are not removed if the string does not contain a function or variable reference. + +
+

+

+ + + +The value of a variable or function can be reported + + using the + +__logn() + + function. +The __logn() function reference can be used anywhere in the test plan after the variable has been defined. +Alternatively, the Java Request sampler can be used to create a sample containing variable references; +the output will be shown in the appropriate Listener. +For versions of JMeter later than 2.3, there is a +Debug Sampler + +that can be used to display the values of variables etc in the Tree View Listener. + +

+

+ + +
+* + +If you define a user-defined static variable with the same name as a built-in function, your static +variable will override the built-in function. +
+

+
+

+ + + + +
+ +19.4 The Function Helper Dialog + +
+
+

+The Function Helper dialog is available from JMeter's Tools menu. +

+


+Function Helper Dialog +

+

+Using the Function Helper, you can select a function from the pull down, and assign +values for its arguments. The left column in the table provides a brief description of the +argument, and the right column is where you write in the value for that argument. Different +functions take different arguments. +

+

+Once you have done this, click the "generate" button, and the appropriate string is generated +for you to copy-paste into your test plan wherever you like. +

+
+

+ + + + +
+ +19.5 Functions + +
+
+ + + + +
+ +

+19.5.1 __regexFunction +

+
+
+

+The Regex Function is used to parse the previous response (or the value of a variable) using any regular +expression (provided by user). The function returns the template string with variable values filled +in. +

+ + +

+The __regexFunction can also store values for future use. In the sixth parameter, you can specify +a reference name. After this function executes, the same values can be retrieved at later times +using the syntax for user-defined values. For instance, if you enter "refName" as the sixth +parameter you will be able to use: + +

    + + +
  • +${refName} to refer to the computed result of the second parameter ("Template for the +replacement string") parsed by this function +
  • + + +
  • +${refName_g0} to refer to the entire match parsed by this function. +
  • + + +
  • +${refName_g1} to refer to the first group parsed by this function. +
  • + + +
  • +${refName_g#} to refer to the n + +th + + group parsed by this function. +
  • + + +
  • +${refName_matchNr} to refer to the number of groups found by this function. +
  • + + +
+ + +

+ + +

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
First argumentThe first argument is the regular expression + to be applied to the response data. It will grab all matches. Any parts of this expression + that you wish to use in your template string, be sure to surround in parentheses. Example: + <a href="(.*)">. This will grab the value of the link and store it as the first group (there is + only 1 group). Another example: <input type="hidden" name="(.*)" value="(.*)">. This will + grab the name as the first group, and the value as the second group. These values can be used + in your template string + +Yes +
Second argumentThis is the template string that will replace + the function at run-time. To refer to a group captured in the regular expression, use the syntax: + $[group_number]$. Ie: $1$, or $2$. Your template can be any string. + +Yes +
Third argumentThe third argument tells JMeter which match + to use. Your regular expression might find numerous matches. You have four choices: + +
    +
  • +An integer - Tells JMeter to use that match. '1' for the first found match, '2' for the + second, and so on +
  • + + +
  • +RAND - Tells JMeter to choose a match at random. +
  • + + +
  • +ALL - Tells JMeter to use all matches, and create a template string for each one and then + append them all together. This option is little used. +
  • + + +
  • +A float number between 0 and 1 - tells JMeter to find the Xth match using the formula: + (number_of_matches_found * float_number) rounded to nearest integer. +
  • + + +
+
+No, default=1 +
Fourth argumentIf 'ALL' was selected for the above argument + value, then this argument will be inserted between each appended copy of the template value. + +No +
Fifth argumentDefault value returned if no match is found + +No +
Sixth argumentA reference name for reusing the values parsed by this function. +
+ + + Stored values are ${refName} (the replacement template string) and ${refName_g#} where "#" is the + group number from the regular expression ("0" can be used to refer to the entire match). +
+No +
Seventh argumentInput variable name. + If specified, then the value of the variable is used as the input instead of using the previous sample result. + + +No +
+

+

+
+ + + + +
+ +

+19.5.2 __counter +

+
+
+

+The counter generates a new number each time it is called, starting with 1 +and incrementing by +1 each time. The counter can be configured to keep each simulated user's values +separate, or to use the same counter for all users. If each user's values is incremented separately, +that is like counting the number of iterations through the test plan. A global counter is like +counting how many times that request was run. + +

+ + +

+The counter uses an integer variable to hold the count, which therefore has a maximum of 2,147,483,647. +

+ + +

+The counter function instances are now completely independent. +[JMeter 2.1.1 and earlier used a fixed thread variable to keep track of the per-user count, +so multiple counter functions operated on the same value.] +The global counter - "FALSE" - is separately maintained by each counter instance. + +

+ + +

+ + + + +Multiple __counter function calls in the same iteration won't increment the value further. + + + + +
+ + +If you want to have a count that increments for each sample, use the function in a Pre-Processor such as +User Parameters +. + +

+ + +

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
First argumentTRUE if you wish each simulated user's counter + to be kept independent and separate from the other users. FALSE for a global counter. + +Yes +
Second argumentA reference name for reusing the value created by this function. +
+ + + Stored values are of the form ${refName}. This allows you to keep one counter and refer to its value in + multiple places. [For JMeter 2.1.1 and earlier this parameter was required.] +
+No +
+

+

+
+ + + + +
+ +

+19.5.3 __threadNum +

+
+
+

+The thread number function simply returns the number of the thread currently +being executed. These numbers are independent of ThreadGroup, meaning thread #1 in one threadgroup +is indistinguishable from thread #1 in another threadgroup, from the point of view of this function. +

+ + + +

+There are no arguments for this function. +

+ + +

+ + +
+This function does not work in any Configuration elements (e.g. User Defined Variables) as these are run from a separate thread. +Nor does it make sense to use it on the Test Plan. + +
+

+

+
+ + + + +
+ +

+19.5.4a __intSum +

+
+
+ + +

+ +The intSum function can be used to compute the sum of two or more integer values. + +

+ + +

+ + +
+JMeter Versions 2.3.1 and earlier required the reference name to be present. +The reference name is now optional, but it must not be a valid integer. + +
+

+ + +

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
First argumentThe first int value. + +Yes +
Second argumentThe second int value. + +Yes +
nth argumentThe nth int value. + +No +
last argumentA reference name for reusing the value computed by this function. + If specified, the reference name must contain at least one non-numeric character otherwise it will + be treated as another int value to be added. + + +No +
+

+

+
+ + + + +
+ +

+19.5.4b __longSum +

+
+
+

+The longSum function can be used to compute the sum of two or more long values. + +

+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
First argumentThe first long value. + +Yes +
Second argumentThe second long value. + +Yes +
nth argumentThe nth long value. + +No +
last argumentA reference name for reusing the value computed by this function. + If specified, the reference name must contain at least one non-numeric character otherwise it will + be treated as another long value to be added. + + +No +
+

+

+
+ + + + + + +
+ +

+19.5.5 __StringFromFile +

+
+
+ + +

+ + The StringFromFile function can be used to read strings from a text file. + This is useful for running tests that require lots of variable data. + For example when testing a banking application, 100s or 1000s of different account numbers might be required. + +

+ + +

+ + See also the + + +CSV Data Set Config test element + + + which may be easier to use. However, that does not currently support multiple input files. + +

+ + + +

+ + Each time it is called it reads the next line from the file. + All threads share the same instance, so different threads will get different lines. + When the end of the file is reached, it will start reading again from the beginning, + unless the maximum loop count has been reached. + If there are multiple references to the function in a test script, each will open the file independently, + even if the file names are the same. + [If the value is to be used again elsewhere, use different variable names for each function call.] + +

+ + +

+ + +
+ Function instances are shared between threads, and the file is (re-)opened by whatever thread + happens to need the next line of input, so using the threadNumber as part of the file name + will result in unpredictable behaviour. + +
+

+ + +

+If an error occurs opening or reading the file, then the function returns the string "**ERR**" +

+ + +

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
File NamePath to the file name. + (The path can be relative to the JMeter launch directory) + If using optional sequence numbers, the path name should be suitable for passing to DecimalFormat. + See below for examples. + + +Yes +
Variable Name +A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. +Defaults to "StringFromFile_". + + +No +
Start sequence numberInitial Sequence number (if omitted, the End sequence number is treated as a loop count) + +No +
End sequence numberFinal sequence number (if omitted, seqence numbers can increase without limit) + +No +
+

+

+The file name parameter is resolved when the file is opened or re-opened. +

+

+The reference name parameter (if supplied) is resolved every time the function is executed. +

+

+ +Using sequence numbers: + +

+

+When using the optional sequence numbers, the path name is used as the format string for java.text.DecimalFormat. + The current sequence number is passed in as the only parameter. + If the optional start number is not specified, the path name is used as is. + Useful formatting sequences are: + +

+

+ + + + +# - insert the number, with no leading zeros or spaces +
+ + +000 - insert the number packed out to 3 digits with leading zeros if necessary + +

+

+ +Examples: +
+ + + pin#'.'dat -> pin1.dat, ... pin9.dat, pin10.dat, ... pin9999.dat +
+ + + pin000'.'dat -> pin001.dat ... pin099.dat ... pin999.dat ... pin9999.dat +
+ + + pin'.'dat# -> pin.dat1, ... pin.dat9 ... pin.dat999 + +
+

+

+ + If more digits are required than there are formatting characters, the number will be + expanded as necessary. +
+ + + + +To prevent a formatting character from being interpreted, + enclose it in single quotes. Note that "." is a formatting character, + and must be enclosed in single quotes + + + (though #. and 000. work as expected in locales where the decimal point is also ".") + +
+ + + In other locales (e.g. fr), the decimal point is "," - which means that "#." + becomes "nnn,". +
+ + + See the documentation for DecimalFormat for full details. +
+ + + If the path name does not contain any special formatting characters, + the current sequence number will be appended to the name, otherwise + the number will be inserted aaccording to the fomatting instructions. +
+ + + If the start sequence number is omitted, and the end sequence number is specified, + the sequence number is interpreted as a loop count, and the file will be used at most "end" times. + In this case the filename is not formatted. + + + + +
+ + + ${_StringFromFile(PIN#'.'DAT,,1,2)} - reads PIN1.DAT, PIN2.DAT +
+ + + ${_StringFromFile(PIN.DAT,,,2)} - reads PIN.DAT twice +
+ + + +
+ + Note that the "." in PIN.DAT above should + +not + + be quoted. + In this case the start number is omitted, so the file name is used exactly as is. + +

+

+
+ + + + +
+ +

+19.5.6a __machineName +

+
+
+

+The machineName function returns the local host name +

+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
Variable NameA reference name for reusing the value + computed by this function. + +No +
+

+

+
+ + + + +
+ +

+19.5.6b __machineIP +

+
+
+

+The machineIP function returns the local IP address +

+

+Parameters + + + + + + + + +
AttributeDescriptionRequired
Variable NameA reference name for reusing the value + computed by this function. + +No +
+

+

+
+ + + + +
+ +

+19.5.7 __javaScript +

+
+
+ + +

+ +The javaScript function executes a piece of JavaScript (not Java!) code and returns its value + +

+ + +

+ +The JMeter Javascript function calls a standalone JavaScript interpreter. +Javascript is used as a scripting language, so you can do calculations etc. +

+ + +

+ +For details of the language, please see + +Mozilla Rhino Overview + + + +

+ + +

+ +The following variables are made available to the script: + +

+ + +
    + + +
  • +log - the logger for the function +
  • + + +
  • +ctx - JMeterContext object +
  • + + +
  • +vars - JMeterVariables object +
  • + + +
  • +threadName - String containing the current thread name (in 2.3.2 it was misspelt as "theadName") +
  • + + +
  • +sampler - current Sampler object (if any) +
  • + + +
  • +sampleResult - previous SampleResult object (if any) +
  • + + +
  • +props - JMeter Properties object +
  • + + +
+ + +

+ +Rhinoscript allows access to static methods via its Packages object. +See the + +Scripting Java + + documentation. +For example one can access the JMeterContextService static methods thus: + + +Packages.org.apache.jmeter.threads.JMeterContextService.getTotalThreads() + + + +

+ + +

+ + +
+JMeter is not a browser, and does not interpret the JavaScript in downloaded pages. + +
+

+ + +

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
ExpressionThe JavaScript expression to be executed. For example: + +
    + + +
  • +new Date() - return the current date and time +
  • + + +
  • +Math.floor(Math.random()*(${maxRandom}+1)) + - a random number between 0 and the variable maxRandom +
  • + + +
  • +${minRandom}+Math.floor(Math.random()*(${maxRandom}-${minRandom}+1)) + - a random number between the variables minRandom and maxRandom +
  • + + +
  • +"${VAR}"=="abcd" +
  • + + +
+ + +
+Yes +
Variable NameA reference name for reusing the value + computed by this function. + +No +
+

+

+ + +
Remember to include any necessary quotes for text strings and JMeter variables. Also, if +the expression has commas, please make sure to escape them. For example in: + +
+ + +${__javaScript('${sp}'.slice(7\,99999))} + +
+ + +the comma after 7 is escaped. +
+

+

+
+ + + + +
+ +

+19.5.8 __Random +

+
+
+

+The random function returns a random number that lies between the given min and max values. +

+

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
Minimum valueA number + +Yes +
Maximum valueA bigger number + +Yes +
Variable NameA reference name for reusing the value + computed by this function. + +No +
+

+

+
+ + + + +
+ +

+19.5.8 __RandomString +

+
+
+

+The RandomString function returns a random String of length using characters in chars to use. +

+

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
LengthA number length of generated String + +Yes +
Characters to useChars used to generate String + +No +
Variable NameA reference name for reusing the value + computed by this function. + +No +
+

+

+
+ + + + +
+ +

+19.5.8 __CSVRead +

+
+
+

+The CSVRead function returns a string from a CSV file (c.f. + +StringFromFile + +) +

+ + +

+NOTE: versions up to 1.9.1 only supported a single file. + JMeter versions since 1.9.1 support multiple file names. + +

+ + +

+ +In most cases, the newer + + +CSV Data Set Config element + + + is easier to use. + +

+ + +

+ + When a filename is first encountered, the file is opened and read into an internal + array. If a blank line is detected, this is treated as end of file - this allows + trailing comments to be used (N.B. this feature was introduced in versions after 1.9.1) + +

+ + +

+All subsequent references to the same file name use the same internal array. + N.B. the filename case is significant to the function, even if the OS doesn't care, + so CSVRead(abc.txt,0) and CSVRead(aBc.txt,0) would refer to different internal arrays. + +

+ + +

+ + The *ALIAS feature allows the same file to be opened more than once, + and also allows for shorter file names. + +

+ + +

+ + Each thread has its own internal pointer to its current row in the file array. + When a thread first refers to the file it will be allocated the next free row in + the array, so each thread will access a different row from all other threads. + [Unless there are more threads than there are rows in the array.] + +

+ + +

+ + Note: the function splits the line at every comma by default. + If you want to enter columns containing commas, then you will need + to change the delimiter to a character that does not appear in any + column data, by setting the property: csvread.delimiter + +

+ + +

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
File NameThe file (or *ALIAS) to read from + +Yes +
Column number + The column number in the file. + 0 = first column, 1 = second etc. + "next" - go to next line of file. + *ALIAS - open a file and assign it to the alias + + +Yes +
+

+

+For example, you could set up some variables as follows: + +

    + + +
  • +COL1a ${__CSVRead(random.txt,0)} +
  • + + +
  • +COL2a ${__CSVRead(random.txt,1)}${__CSVRead(random.txt,next)} +
  • + + +
  • +COL1b ${__CSVRead(random.txt,0)} +
  • + + +
  • +COL2b ${__CSVRead(random.txt,1)}${__CSVRead(random.txt,next)} +
  • + + +
+ +This would read two columns from one line, and two columns from the next available line. +If all the variables are defined on the same User Parameters Pre-Processor, then the lines +will be consecutive. Otherwise, a different thread may grab the next line. + +

+

+ + +
+The function is not suitable for use with large files, as the entire file is stored in memory. +For larger files, use + +CSV Data Set Config element + + +or + +StringFromFile + +. + +
+

+

+
+ + + + +
+ +

+19.5.9 __property +

+
+
+

+The property function returns the value of a JMeter property. + If the property value cannot be found, and no default has been supplied, it returns the property name. + When supplying a default value, there is no need to provide a function name - the parameter can be set to null, and it will be ignored. + +

+For example: +

+ + +

    + + +
  • +${__property(user.dir)} - return value of user.dir +
  • + + +
  • +${__property(user.dir,UDIR)} - return value of user.dir and save in UDIR +
  • + + +
  • +${__property(abcd,ABCD,atod)} - return value of property abcd (or "atod" if not defined) and save in ABCD +
  • + + +
  • +${__property(abcd,,atod)} - return value of property abcd (or "atod" if not defined) but don't save it +
  • + + +
+ + +

+ + +

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
Property NameThe property name to be retrieved. + +Yes +
Variable NameA reference name for reusing the value + computed by this function. + +No +
Default ValueThe default value for the property. + +No +
+

+

+
+ + + + +
+ +

+19.5.10 __P +

+
+
+

+This is a simplified property function which is + intended for use with properties defined on the command line. + Unlike the __property function, there is no option to save the value in a variable, + and if no default value is supplied, it is assumed to be 1. + The value of 1 was chosen because it is valid for common test variables such + as loops, thread count, ramp up etc. + +

+For example: +

+ + + + +Define the property value: + +
+ + +jmeter -Jgroup1.threads=7 -Jhostname1=www.realhost.edu + +
+ + + +Fetch the values: + +
+ + +${__P(group1.threads)} - return the value of group1.threads + +
+ + +${__P(group1.loops)} - return the value of group1.loops + +
+ + +${__P(hostname,www.dummy.org)} - return value of property hostname or www.dummy.org if not defined + +
+ + + +
+ +In the examples above, the first function call would return 7, +the second would return 1 and the last would return www.dummy.org +(unless those properties were defined elsewhere!) + +

+ + +

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
Property NameThe property name to be retrieved. + +Yes +
Default ValueThe default value for the property. + If omitted, the default is set to "1". + +No +
+

+

+
+ + + + +
+ +

+19.5.11 __log +

+
+
+ + +

+ + The log function logs a message, and returns its input string + +

+ + +

+Parameters + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
String to be loggedA string + +Yes +
Log LevelOUT, ERR, DEBUG, INFO (default), WARN or ERROR + +No +
Throwable textIf non-empty, creates a Throwable to pass to the logger + +No +
CommentIf present, it is displayed in the string. + Useful for identifying what is being logged. + +No +
+

+

+The OUT and ERR log level names are used to direct the output to System.out and System.err respectively. + In this case, the output is always printed - it does not depend on the current log setting. + +

+
+
+For example:
+     ${__log(Message)} - written to the log file as   "...thread Name : Message"
+     ${__log(Message,OUT)} - written to console window
+     ${__log(${VAR},,,VAR=)} - written to log file as "...thread Name VAR=value"
+
+
+

+
+ + + + +
+ +

+19.5.12 __logn +

+
+
+ + +

+ + The logn function logs a message, and returns the empty string + +

+ + +

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
String to be loggedA string + +Yes +
Log LevelOUT, ERR, DEBUG, INFO (default), WARN or ERROR + +No +
Throwable textIf non-empty, creates a Throwable to pass to the logger + +No +
+

+

+The OUT and ERR log level names are used to direct the output to System.out and System.err respectively. + In this case, the output is always printed - it does not depend on the current log setting. + +

+
+
+For example:
+     ${__logn(VAR1=${VAR1},OUT)} - write the value of the variable to the console window
+
+
+

+
+ + + + +
+ +

+19.5.13 __BeanShell +

+
+
+ + +

+ + The BeanShell function evaluates the script passed to it, and returns the result. + +

+ + +

+ + + +For full details on using BeanShell, please see the BeanShell web-site at + +http://www.beanshell.org/ + + + + + + +

+ + +

+ +Note that a different Interpreter is used for each independent occurence of the function +in a test script, but the same Interpreter is used for subsequent invocations. +This means that variables persist across calls to the function. + +

+ + +

+ +A single instance of a function may be called from multiple threads. +However the function execute() method is synchronised. + +

+ + +

+ +If the property "beanshell.function.init" is defined, it is passed to the Interpreter +as the name of a sourced file. This can be used to define common methods and variables. There is a +sample init file in the bin directory: BeanShellFunction.bshrc. + +

+ + +

+ +The following variables are set before the script is executed: + +

    + + +
  • +log - the logger for the BeanShell function (*) +
  • + + +
  • +ctx - the current JMeter context variable +
  • + + +
  • +vars - the current JMeter variables +
  • + + +
  • +props - JMeter Properties object +
  • + + +
  • +threadName - the threadName (String) +
  • + + +
  • +Sampler the current Sampler, if any +
  • + + +
  • +SampleResult - the current SampleResult, if any +
  • + + +
+ +(*) means that this is set before the init file, if any, is processed. +Other variables vary from invocation to invocation. + +

+ + +

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
BeanShell scriptA beanshell script (not a file name) + +Yes +
Name of variableA reference name for reusing the value + computed by this function. + +No +
+

+

+ +Example: + +

+
+${__BeanShell(123*456)} - returns 56088
+${__BeanShell(source("function.bsh"))} - processes the script in function.bsh
+
+
+ + +

+

+ + +
+Remember to include any necessary quotes for text strings and JMeter variables that represent text strings. + +
+

+

+
+ + + + +
+ +

+19.5.14 __split +

+
+
+ + +

+ + The split function splits the string passed to it according to the delimiter, + and returns the original string. If any delimiters are adjacent, "?" is returned as the value. + The split strings are returned in the variables ${VAR_1}, ${VAR_2} etc. + The count of variables is returned in ${VAR_n}. + From JMeter 2.1.2 onwards, a trailing delimiter is treated as a missing variable, and "?" is returned. + Also, to allow it to work better with the ForEach controller, + __split now deletes the first unused variable in case it was set by a previous split. + +

+ + +

+

+ + Example: + + + + +
+ + + Define VAR="a||c|" in the test plan. + +
+ + + ${__split(${VAR},VAR,|)} + +
+ + + This will return the contents of VAR, i.e. "a||c|" and set the following variables: + +
+ + + VAR_n=4 (3 in JMeter 2.1.1 and earlier) + +
+ + + VAR_1=a + +
+ + + VAR_2=? + +
+ + + VAR_3=c + +
+ + + VAR_4=? (null in JMeter 2.1.1 and earlier) + +
+ + + VAR_5=null (in JMeter 2.1.2 and later) + +
+ + +

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
String to splitA delimited string, e.g. "a|b|c" + +Yes +
Name of variableA reference name for reusing the value + computed by this function. + +Yes +
DelimiterThe delimiter character, e.g. + +| + +. +If omitted, + +, + + is used. Note that + +, + + would need to be specified as + +\, + +. + + +No +
+

+

+
+ + + + +
+ +

+19.5.15 __XPath +

+
+
+ + +

+ + The XPath function reads an XML file and matches the XPath. + Each time the function is called, the next match will be returned. + At end of file, it will wrap around to the start. + If no nodes matched, then the function will return the empty string, + and a warning message will be written to the JMeter log file. + +

+ + +
Note that the entire NodeList is held in memory. +
+

+ + +

+ + +

+

+ + Example: + + + + +
+ + + + +
+ + + ${__XPath(/path/to/build.xml, //target/@name)} + +
+ + + This will match all targets in build.xml and return the contents of the next name attribute + +
+ + +

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
XML file to parse a XML file to parse + +Yes +
XPath a XPath expression to match nodes in the XML file + +Yes +
+

+

+
+ + + + +
+ +

+19.5.16 __setProperty +

+
+
+ + +

+The setProperty function sets the value of a JMeter property. + The default return value from the function is the empty string, + so the function call can be used anywhere functions are valid. +

+ + +

+The original value can be returned by setting the optional 3rd parameter to "true". +

+ + +

+Properties are global to JMeter, + so can be used to communicate between threads and thread groups +

+ + +

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
Property NameThe property name to be set. + +Yes +
Property ValueThe value for the property. + +Yes +
True/FalseShould the original value be returned? + +No +
+

+

+
+ + + + +
+ +

+19.5.17 __time +

+
+
+ + +

+The time function returns the current time in various formats. +

+ + +

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
Format + The format to be passed to SimpleDateFormat. + The function supports various shorthand aliases, see below. + + +No +
Name of variableThe name of the variable to set. + +No +
+

+

+If the format string is omitted, then the function returns the current time in milliseconds. +Otherwise, the current time is passed to SimpleDateFormat. +The following shorthand aliases are provided: + +

+
    + + +
  • +YMD = yyyyMMdd +
  • + + +
  • +HMS = HHmmss +
  • + + +
  • +YMDHMS = yyyyMMdd-HHmmss +
  • + + +
  • +USER1 = whatever is in the Jmeter property time.USER1 +
  • + + +
  • +USER2 = whatever is in the Jmeter property time.USER2 +
  • + + +
+

+The defaults can be changed by setting the appropriate JMeter property, e.g. +time.YMD=yyMMdd + +

+

+
+ + + + +
+ +

+19.5.18 __jexl +

+
+
+ + +

+The jexl function returns the result of evaluating a + + +Commons JEXL expression + +. + See links below for more information on JEXL expressions. + +

+ + +

+The __jexl function uses Commons JEXL 1, and the __jexl2 function uses Commons JEXL 2 +

+ + + + + +

+Parameters + + + + + + + + + + + + + +
AttributeDescriptionRequired
Expression + The expression to be evaluated. For example, 6*(5+2) + + +Yes +
Name of variableThe name of the variable to set. + +No +
+

+

+ +The following variables are made available to the script: + +

+
    + + +
  • +log - the logger for the function +
  • + + +
  • +ctx - JMeterContext object +
  • + + +
  • +vars - JMeterVariables object +
  • + + +
  • +props - JMeter Properties object +
  • + + +
  • +threadName - String containing the current thread name (in 2.3.2 it was misspelt as "theadName") +
  • + + +
  • +sampler - current Sampler object (if any) +
  • + + +
  • +sampleResult - previous SampleResult object (if any) +
  • + + +
  • +OUT - System.out - e.g. OUT.println("message") +
  • + + +
+

+ + Jexl can also create classes and call methods on them, for example: + +

+

+ + + + + Systemclass=log.class.forName("java.lang.System"); +
+ + + now=Systemclass.currentTimeMillis(); + +
+ + Note that the Jexl documentation on the web-site wrongly suggests that "div" does integer division. + In fact "div" and "/" both perform normal division. One can get the same effect + as follows: + + + + i= 5 / 2; + i.intValue(); // or use i.longValue() + + + + +

+

+ + +
Versions of JMeter after 2.3.2 allow the expression to contain multiple statements. + JMeter 2.3.2 and earlier only processed the first statement (if there were multiple statements a warning was logged). + +
+

+

+
+ + + + +
+ +

+19.5.19 __V +

+
+
+ + +

+The V (variable) function returns the result of evaluating a variable name expression. + This can be used to evaluate nested variable references (which are not currently supported). + +

+ + +

+For example, if one has variables A1,A2 and N=1: +

+ + +
    + + +
  • +${A1} - works OK +
  • + + +
  • +${A${N}} - does not work (nested variable reference) +
  • + + +
  • +${__V(A${N})} - works OK. A${N} becomes A1, and the __V function returns the value of A1 +
  • + + +
+ + +

+Parameters + + + + + + + + +
AttributeDescriptionRequired
Variable name + The variable to be evaluated. + + +Yes +
+

+

+
+ + + + +
+ +

+19.5.20 __evalVar +

+
+
+ + +

+The eval function returns the result of evaluating an expression stored in a variable. + +

+ + +

+ + This allows one to read a string from a file, and process any variable references in it. + For example, if the variable "query" contains "select ${column} from ${table}" + and "column" and "table" contain "name" and "customers", then ${__evalVar(query)} + will evaluate as "select name from customers". + +

+ + +

+Parameters + + + + + + + + +
AttributeDescriptionRequired
Variable name + The variable to be evaluated. + + +Yes +
+

+

+
+ + + + +
+ +

+19.5.21 __eval +

+
+
+ + +

+The eval function returns the result of evaluating a string expression. + +

+ + +

+ + This allows one to interpolate variable and function references in a string + which is stored in a variable. For example, given the following variables: + +

    + + +
  • +name=Smith +
  • + + +
  • +column=age +
  • + + +
  • +table=birthdays +
  • + + +
  • +SQL=select ${column} from ${table} where name='${name}' +
  • + + +
+ + then ${__eval(${SQL})} will evaluate as "select age from birthdays where name='Smith'". + +

+ + +

+ + This can be used in conjunction with CSV Dataset, for example + where the both SQL statements and the values are defined in the data file. + +

+ + +

+Parameters + + + + + + + + +
AttributeDescriptionRequired
Variable name + The variable to be evaluated. + + +Yes +
+

+

+
+ + + + +
+ +

+19.5.22 __char +

+
+
+ + +

+ + The char function returns the result of evaluating a list of numbers as Unicode characters. + See also __unescape(), below. + +

+ + +

+ + This allows one to add arbitrary character values into fields. + +

+ + +

+Parameters + + + + + + + + +
AttributeDescriptionRequired
Unicode character number (decimal or 0xhex) + The decimal number (or hex number, if prefixed by 0x, or octal, if prefixed by 0) to be converted to a Unicode character. + + +Yes +
+

+

+Examples: + +
+ + +${__char(13,10)} = ${__char(0xD,0xA)} = ${__char(015,012)} = CRLF + +
+ + +${__char(165)} = ¥ (yen) + +

+

+
+ + + + +
+ +

+19.5.23 __unescape +

+
+
+ + +

+ + The unescape function returns the result of evaluating a Java-escaped string. See also __char() above. + +

+ + +

+ + This allows one to add characters to fields which are otherwise tricky (or impossible) to define via the GUI. + +

+ + +

+Parameters + + + + + + + + +
AttributeDescriptionRequired
String to unescape + The string to be unescaped. + + +Yes +
+

+

+Examples: + +
+ + +${__unescape(\r\n)} = CRLF + +
+ + +${__unescape(1\t2)} = 1[tab]2 + +

+

+
+ + + + +
+ +

+19.5.24 __unescapeHtml +

+
+
+ + +

+ +Function to unescape a string containing HTML entity escapes +to a string containing the actual Unicode characters corresponding to the escapes. +Supports HTML 4.0 entities. + +

+ + +

+ +For example, the string + +"&lt;Fran&ccedil;ais&gt;" + + +will become + +"<Français>" + +. + +

+ + +

+ +If an entity is unrecognized, it is left alone, and inserted verbatim into the result string. +e.g. + +">&zzzz;x" + + will become + +">&zzzz;x" + +. + +

+ + +

+ + Uses StringEscapeUtils#unescapeHtml(String) from Commons Lang. + +

+ + +

+Parameters + + + + + + + + +
AttributeDescriptionRequired
String to unescape + The string to be unescaped. + + +Yes +
+

+

+
+ + + + +
+ +

+19.5.25 __escapeHtml +

+
+
+ + +

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

+ + +

+ +For example, + +"bread" & "butter" + + +becomes: + + +&quot;bread&quot; &amp; &quot;butter&quot; + +. + +

+ + +

+ + Uses StringEscapeUtils#escapeHtml(String) from Commons Lang. + +

+ + +

+Parameters + + + + + + + + +
AttributeDescriptionRequired
String to escape + The string to be escaped. + + +Yes +
+

+

+
+ + + + +
+ +

+19.5.26 __FileToString +

+
+
+ + +

+ + The FileToString function can be used to read an entire file. + Each time it is called it reads the entire file. + +

+ + +

+If an error occurs opening or reading the file, then the function returns the string "**ERR**" +

+ + +

+Parameters + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
File NamePath to the file name. + (The path can be relative to the JMeter launch directory) + + +Yes +
File encoding if not the platform default + The encoding to be used to read the file. If not specified, the platform default is used. + + +No +
Variable Name +A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. + + +No +
+

+

+The file name, encoding and reference name parameters are resolved every time the function is executed. +

+

+
+ + + + +
+ +

+19.5.27 __samplerName +

+
+
+ + +

+ + The samplerName function returns the name (i.e. label) of the current sampler. + +

+ + +

+ + The function does not work in Test elements that don't have an associated sampler. + For example the Test Plan. + Configuration elements also don't have an associated sampler. + However some Configuration elements are referenced directly by samplers, such as the HTTP Header Manager + and Http Cookie Manager, and in this case the functions are resolved in the context of the Http Sampler. + Pre-Processors, Post-Processors and Assertions always have an associated Sampler. + +

+ + +

+Parameters + + + + + + + + +
AttributeDescriptionRequired
Variable Name +A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. + + +No +
+

+

+
+ + + + +
+ +

+19.5.28 __TestPlanName +

+
+
+ + +

+ + The TestPlanName function returns the name of the current test plan (can be used in Including Plans to know the name of the calling test plan). + +

+ + +

+
+
+

+ + + + +
+ +19.6 Pre-defined Variables + +
+
+

+ +Most variables are set by calling functions or by test elements such as User Defined Variables; +in which case the user has full control over the variable name that is used. +However some variables are defined internally by JMeter. These are listed below. + +

+
    + + +
  • +COOKIE_cookiename - contains the cookie value (see +HTTP Cookie Manager +) +
  • + + +
  • +JMeterThread.last_sample_ok - whether or not the last sample was OK - true/false. +Note: this is updated after PostProcessors and Assertions have been run. + +
  • + + +
  • +START variables (see next section) +
  • + + +
+
+

+ + + + +
+ +19.6 Pre-defined Properties + +
+
+

+ +The set of JMeter properties is initialised from the system properties defined when JMeter starts; +additional JMeter properties are defined in jmeter.properties, user.properties or on the command line. + +

+

+ +Some built-in properties are defined by JMeter. These are listed below. +For convenience, the START properties are also copied to variables with the same names. + +

+
    + + +
  • +START.MS - JMeter start time in milliseconds +
  • + + +
  • +START.YMD - JMeter start time as yyyyMMdd +
  • + + +
  • +START.HMS - JMeter start time as HHmmss +
  • + + +
  • +TESTSTART.MS - test start time in milliseconds +
  • + + +
+

+ +Please note that the START variables / properties represent JMeter startup time, not the test start time. +They are mainly intended for use in file names etc. + +

+
+

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/get-started.html b/ApacheJmeter/docs/usermanual/get-started.html new file mode 100644 index 0000000..fda0193 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/get-started.html @@ -0,0 +1,1570 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Getting Started + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +2. Getting Started +
+
+

+The easiest way to begin using JMeter is to first + + +download the latest production release + + and install it. +The release contains all of the files you need to build and run most types of tests, +e.g. Web (HTTP/HTTPS), FTP, JDBC, LDAP, Java, and JUnit. +

+

+If you want to perform JDBC testing, +then you will, of course, need the appropriate JDBC driver from your vendor. JMeter does not come with +any JDBC drivers. +

+

+ +JMeter includes the JMS API jar, but does not include a JMS client implementation. +If you want to run JMS tests, you will need to download the appropriate jars from the JMS provider. + +

+

+ + +
+See the + +JMeter Classpath + + section for details on installing additional jars. + +
+

+

+Next, start JMeter and go through the + +Building a Test Plan + + section +of the User Guide to familiarize yourself with JMeter basics (for example, adding and removing elements). +

+

+Finally, go through the appropriate section on how to build a specific type of Test Plan. +For example, if you are interested in testing a Web application, then see the section + + +Building a Web Test Plan + +. +The other specific Test Plan sections are: + +

+ + +

+

+Once you are comfortable with building and running JMeter Test Plans, you can look into the +various configuration elements (timers, listeners, assertions, and others) which give you more control +over your Test Plans. +

+
+

+

+ + + + +
+ +2.1 Requirements +
+
+

+JMeter requires your computing environment meets some minimum requirements. +

+ + + + +
+ +2.1.1 Java Version + +
+
+

+ + +
JMeter requires a fully compliant JVM 1.5 or higher. + +
+

+

+Because JMeter uses only standard Java APIs, please do not file bug reports if your JRE fails to run +JMeter because of JRE implementation issues. +

+
+

+ + + + +
+ +2.1.2 Operating Systems + +
+
+

+JMeter is a 100% Java application and should run correctly on any system +that has a compliant Java implementation. +

+

+Operating systems tested with JMeter can be view on + + +this page + + +on JMeter wiki. +

+

+Even if your OS is not listed on the wiki page, JMeter should run on it provided that the JVM is compliant. +

+
+

+
+

+

+ + + + +
+ +2.2 Optional +
+
+

+If you plan on doing JMeter development, then you will need one or more optional packages listed below. +

+ + + + +
+ +2.2.1 Java Compiler + +
+
+

+If you want to build the JMeter source or develop JMeter plugins, then you will need a fully compliant JDK 1.5 or higher. +

+
+

+ + + + +
+ +2.2.2 SAX XML Parser + +
+
+

+JMeter comes with Apache's + +Xerces XML parser + +. You have the option of telling JMeter +to use a different XML parser. To do so, include the classes for the third-party parser in JMeter's + +classpath + +, +and update the + +jmeter.properties + + file with the full classname of the parser +implementation. +

+
+

+ + + + +
+ +2.2.3 Email Support + +
+
+

+JMeter has extensive Email capabilities. +It can send email based on test results, and has a POP3(S)/IMAP(S) sampler. +It also has an SMTP sampler. + +

+
+

+ + + + +
+ +2.2.4 SSL Encryption + +
+
+

+To test a web server using SSL encryption (HTTPS), JMeter requires that an +implementation of SSL be provided, as is the case with Sun Java 1.4 and above. +If your version of Java does not include SSL support, then it is possible to add an external implementation. +Include the necessary encryption packages in JMeter's + +classpath + +. +Also, update + +system.properties + + to register the SSL Provider. +

+

+ +JMeter HTTP defaults to protocol level TLS. This can be changed by editting the JMeter property +"https.default.protocol" in jmeter.properties or user.properties. + +

+

+ +The JMeter HTTP samplers are configured to accept all certificates, +whether trusted or not, regardless of validity periods etc. + + +This is to allow the maximum flexibility in testing servers. +

+

+If the server requires a client certificate, this can be provided. +

+

+There is also the +SSL Manager +, for greater control of certificates. +

+

+ + +
The JMeter proxy server (see below) supports recording HTTPS (SSL) in versions after 2.3.4 +
+

+

+ +The SMTP sampler can optionally use a local trust store or trust all certificates. + +

+
+

+ + + + +
+ +2.2.5 JDBC Driver + +
+
+

+You will need to add your database vendor's JDBC driver to the + +classpath + + if you want to do JDBC testing. +Make sure the file is a jar file, not a zip. + +

+
+

+ + + + +
+ +2.2.6 JMS client + +
+
+

+ +JMeter now includes the JMS API from Apache Geronimo, so you just need to add the appropriate JMS Client implementation +jar(s) from the JMS provider. Please refer to their documentation for details. +There may also be some information on the + +JMeter Wiki + +. + +

+
+

+ + + + +
+ +2.2.7 Libraries for ActiveMQ JMS + +
+
+

+ +At the time of writing, the current version of ActiveMQ is 5.3.2. +You will need to add the jar activemq-all-5.3.2.jar to your classpath, e.g. by storing it in the lib/ directory. + +

+

+ +Alternatively, add the jar activemq-core-5.3.2.jar to the classpath; +this requires the javax/management/j2ee classes which can be found in the +Apache Geronimo jar geronimo-j2ee-management_1.0_spec-1.0.jar. +The other required jars (such as commons-logging) are already included with JMeter. + +

+

+ +See + +http://activemq.apache.org/initial-configuration.html + + +for details. + +

+
+

+

+ + +
+See the + +JMeter Classpath + + section for more details on installing additional jars. + +
+

+
+

+

+ + + + +
+ +2.3 Installation +
+
+

+We recommend that most users run the + +latest release + +. +

+

+To install a release build, simply unzip the zip/tar file into the directory +where you want JMeter to be installed. Provided that you have a JRE/JDK correctly installed +and the JAVA_HOME environment variable set, there is nothing more for you to do. +

+

+ +Note: there can be problems (especially with client-server mode) if the directory path contains any spaces. + +

+

+ +The installation directory structure should look something like this (for version 2.7): + +

+
+apache-jmeter-2.7
+apache-jmeter-2.7/bin
+apache-jmeter-2.7/docs
+apache-jmeter-2.7/extras
+apache-jmeter-2.7/lib/
+apache-jmeter-2.7/lib/ext
+apache-jmeter-2.7/lib/junit
+apache-jmeter-2.7/printable_docs
+
+
+ +You can rename the parent directory (i.e. apache-jmeter-2.7) if you want, but do not change any of the sub-directory names. + +

+
+

+

+ + + + +
+ +2.4 Running JMeter +
+
+
+ +

+To run JMeter, run the jmeter.bat (for Windows) or jmeter (for Unix) file. +These files are found in the bin directory. +After a short pause, the JMeter GUI should appear. + +

+

+ +There are some additional scripts in the bin directory that you may find useful. +Windows script files (the .CMD files require Win2K or later): + +

    + + +
  • +jmeter.bat - run JMeter (in GUI mode by default) +
  • + + +
  • +jmeter-n.cmd - drop a JMX file on this to run a non-GUI test +
  • + + +
  • +jmeter-n-r.cmd - drop a JMX file on this to run a non-GUI test remotely +
  • + + +
  • +jmeter-t.cmd - drop a JMX file on this to load it in GUI mode +
  • + + +
  • +jmeter-server.bat - start JMeter in server mode +
  • + + +
  • +mirror-server.cmd - runs the JMeter Mirror Server in non-GUI mode +
  • + + +
  • +shutdown.cmd - Run the Shutdown client to stop a non-GUI instance gracefully +
  • + + +
  • +stoptest.cmd - Run the Shutdown client to stop a non-GUI instance abruptly +
  • + + +
+ +Note: the special name LAST can be used with jmeter-n.cmd, jmeter-t.cmd and jmeter-n-r.cmd +and means the last test plan that was run interactively. + +

+

+ +The environment variable JVM_ARGS can be used to override JVM settings in the jmeter.bat script. +For example: + +

+
+set JVM_ARGS="-Xms1024m -Xmx1024m -Dpropname=propvalue"
+jmeter -t test.jmx ...
+
+
+ + +

+

+ +Un*x script files; should work on most Linux/Unix systems: + +

    + + +
  • +jmeter - run JMeter (in GUI mode by default). Defines some JVM settings which may not work for all JVMs. +
  • + + +
  • +jmeter-server - start JMeter in server mode (calls jmeter script with appropriate parameters) +
  • + + +
  • +jmeter.sh - very basic JMeter script with no JVM options specified. +
  • + + +
  • +mirror-server.sh - runs the JMeter Mirror Server in non-GUI mode +
  • + + +
  • +shutdown.sh - Run the Shutdown client to stop a non-GUI instance gracefully +
  • + + +
  • +stoptest.sh - Run the Shutdown client to stop a non-GUI instance abruptly +
  • + + +
+ + +

+

+ +It may be necessary to edit the jmeter shell script if some of the JVM options are not supported +by the JVM you are using. +The JVM_ARGS environment variable can be used to override or set additional JVM options, for example: + +

+
+JVM_ARGS="-Xms1024m -Xmx1024m" jmeter -t test.jmx [etc.]
+
+
+ +will override the HEAP settings in the script. + +

+ + + + +
+ +2.4.1 JMeter's Classpath + +
+
+

+JMeter automatically finds classes from jars in the following directories: +

+
    + + +
  • +JMETER_HOME/lib - used for utility jars +
  • + + +
  • +JMETER_HOME/lib/ext - used for JMeter components and add-ons +
  • + + +
+

+If you have developed new JMeter components, +then you should jar them and copy the jar into JMeter's + +lib/ext + + directory. +JMeter will automatically find JMeter components in any jars found here. + +

+

+Support jars (libraries etc) should be placed in the + +lib + + directory. +

+

+If you don't want to put JMeter extension jars in the + +lib/ext + + directory, +then define the property + +search_paths + + in jmeter.properties. +Do not use lib/ext for utility jars; it is only intended for JMeter components. + +

+

+ +Other jars (such as JDBC, JMS implementations and any other support libaries needed by the JMeter code) + should be placed in the + +lib + + directory - not the + +lib/ext + + directory +

+

+Note: JMeter will only find .jar files, not .zip. +

+

+You can also install utility Jar files in $JAVA_HOME/jre/lib/ext, or (since 2.1.1) you can set the property + +user.classpath + + in jmeter.properties +

+

+Note that setting the CLASSPATH environment variable will have no effect. +This is because JMeter is started with "java -jar", +and the java command silently ignores the CLASSPATH variable, and the -classpath/-cp options when -jar is used. +[This occurs with all Java programs, not just JMeter.] +

+
+

+ + + + +
+ +2.4.2 Using a Proxy Server + +
+
+

+If you are testing from behind a firewall/proxy server, you may need to provide JMeter with +the firewall/proxy server hostname and port number. To do so, run the jmeter.bat/jmeter file +from a command line with the following parameters: +

+

+ +-H [proxy server hostname or ip address] +
+ + +-P [proxy server port] +
+ + +-N [nonproxy hosts] (e.g. *.apache.org|localhost) +
+ + +-u [username for proxy authentication - if required] +
+ + +-a [password for proxy authentication - if required] +
+ + + +

+

+ +Example + +: jmeter -H my.proxy.server -P 8000 -u username -a password -N localhost +

+

+Alternatively, you can use --proxyHost, --proxyPort, --username, and --password +

+

+ + +
JMeter also has its own in-built +HTTP Proxy Server +, +which can be used for recording HTTP or HTTPS browser sessions. +This is not to be confused with the proxy settings described above, which are used when JMeter makes HTTP or HTTPS requests itself. +
+

+
+

+ + + + +
+ +2.4.3 Non-GUI Mode (Command Line mode) + +
+
+

+For non-interactive testing, you may choose to run JMeter without the GUI. To do so, use +the following command options +

+

+-n This specifies JMeter is to run in non-gui mode +

+

+-t [name of JMX file that contains the Test Plan]. +

+

+-l [name of JTL file to log sample results to]. +

+

+-j [name of JMeter run log file]. +

+

+-r Run the test in the servers specified by the JMeter property "remote_hosts" +

+

+-R [list of remote servers] Run the test in the specified remote servers +

+

+The script also lets you specify the optional firewall/proxy server information: +

+

+-H [proxy server hostname or ip address] +
+ + +-P [proxy server port] +

+

+ +Example + +: jmeter -n -t my_test.jmx -l log.jtl -H my.proxy.server -P 8000 +

+

+ +If the property + +jmeterengine.stopfail.system.exit + + is set to true (default is false), +then JMeter will invoke System.exit(1) if it cannot stop all threads. +Normally this is not necessary. + +

+
+

+ + + + +
+ +2.4.4 Server Mode + +
+
+

+For + +distributed testing + +, run JMeter in server mode on the remote node(s), and then control the server(s) from the GUI. +You can also use non-GUI mode to run remote tests. +To start the server(s), run jmeter-server/jmeter-server.bat on each server host. +

+

+The script also lets you specify the optional firewall/proxy server information: +

+

+-H [proxy server hostname or ip address] +
+ + +-P [proxy server port] +

+

+ +Example + +: jmeter-server -H my.proxy.server -P 8000 +

+

+If you want the server to exit after a single test has been run, then define the JMeter property server.exitaftertest=true. + +

+

+To run the test from the client in non-GUI mode, use the following command: +

+
+
+jmeter -n -t testplan.jmx -r [-Gprop=val] [-Gglobal.properties] [-Z]
+where:
+-G is used to define JMeter properties to be set in the servers
+-X means exit the servers at the end of the test
+-Rserver1,server2 - can be used instead of -r to provide a list of servers to start
+  Overrides remote_hosts, but does not define the property.
+
+
+

+ +If the property + +jmeterengine.remote.system.exit + + is set to true (default is false), +then JMeter will invoke System.exit(0) after stopping RMI at the end of a test. +Normally this is not necessary. + +

+
+

+ + + + +
+ +2.4.5 Overriding Properties Via The Command Line + +
+
+

+Java system properties, JMeter properties, and logging properties can be overriden directly on the command line (instead of modifying jmeter.properties). +To do so, use the following options: +

+

+-D[prop_name]=[value] - defines a java system property value. +

+

+-J[prop name]=[value] - defines a local JMeter property. +

+

+-G[prop name]=[value] - defines a JMeter property to be sent to all remote servers. +

+

+-G[propertyfile] - defines a file containing JMeter properties to be sent to all remote servers. +

+

+-L[category]=[priority] - overrides a logging setting, setting a particular category to the given priority level. +

+

+The -L flag can also be used without the category name to set the root logging level. +

+

+ +Examples + +: + +

+
+jmeter -Duser.dir=/home/mstover/jmeter_stuff \
+    -Jremote_hosts=127.0.0.1 -Ljmeter.engine=DEBUG
+
+jmeter -LDEBUG
+
+ + +

+

+ + + +N.B. +
+ + + The command line properties are processed early in startup, but after the logging system has been set up. + Attempts to use the -J flag to update log_level or log_file properties will have no effect. +
+ + +

+
+

+ + + + +
+ +2.4.6 Logging and error messages + +
+
+

+ + +
+ JMeter does not generally use pop-up dialog boxes for errors, as these would interfere with + running tests. Nor does it report any error for a mis-spelt variable or function; instead the + reference is just used as is. See + +Functions and Variables for more information + +. + +
+

+

+If JMeter detects an error during a test, a message will be written to the log file. + The log file name is defined in the jmeter.properties file (or using the -j option, see below). + It defaults to + +jmeter.log + +, and will be found in the directory from which JMeter was launched. + +

+

+ + Since JMeter 2.6, menu + +Options > Log Viewer + + displays the log file in a bottom pane on main JMeter window. + +

+

+ + Since JMeter 2.7 (GUI mode), the number of error/fatal messages logged in the log file is displayed at top-right. + +

+


+Error/fatal counter +

+

+ + JMeter versions after 2.2 added a new command-line option, -j jmeterlogfile. + This is processed after the initial properties file is read, + and before any further properties are processed. + It therefore allows the default of jmeter.log to be overridden. + The jmeter scripts that take a test plan name as a parameter (e.g. jmeter-n.cmd) have been updated + to define the log file using the test plan name, + e.g. for the test plan Test27.jmx the log file is set to Test27.log. + +

+

+When running on Windows, the file may appear as just + +jmeter + + unless you have set Windows to show file extensions. + [Which you should do anyway, to make it easier to detect viruses and other nasties that pretend to be text files...] + +

+

+As well as recording errors, the jmeter.log file records some information about the test run. For example: +

+
+ + +
+
+10/17/2003 12:19:20 PM INFO  - jmeter.JMeter: Version 1.9.20031002 
+10/17/2003 12:19:45 PM INFO  - jmeter.gui.action.Load: Loading file: c:\mytestfiles\BSH.jmx 
+10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Running the test! 
+10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Starting 1 threads for group BSH. Ramp up = 1. 
+10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Continue on error 
+10/17/2003 12:19:52 PM INFO  - jmeter.threads.JMeterThread: Thread BSH1-1 started 
+10/17/2003 12:19:52 PM INFO  - jmeter.threads.JMeterThread: Thread BSH1-1 is done 
+10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Test has ended 	
+
+
+ + +
+

+The log file can be helpful in determining the cause of an error, + as JMeter does not interrupt a test to display an error dialogue. +

+
+

+ + + + +
+ +2.4.7 Full list of command-line options + +
+
+

+Invoking JMeter as "jmeter -?" will print a list of all the command-line options. +These are shown below. +

+
+
+        -h, --help
+                print usage information and exit
+        -v, --version
+                print the version information and exit
+        -p, --propfile {argument}
+                the jmeter property file to use
+        -q, --addprop {argument}
+                additional property file(s)
+        -t, --testfile {argument}
+                the jmeter test(.jmx) file to run
+        -j, --jmeterlogfile {argument}
+                the jmeter log file
+        -l, --logfile {argument}
+                the file to log samples to
+        -n, --nongui
+                run JMeter in nongui mode
+        -s, --server
+                run the JMeter server
+        -H, --proxyHost {argument}
+                Set a proxy server for JMeter to use
+        -P, --proxyPort {argument}
+                Set proxy server port for JMeter to use
+        -u, --username {argument}
+                Set username for proxy server that JMeter is to use
+        -a, --password {argument}
+                Set password for proxy server that JMeter is to use
+        -J, --jmeterproperty {argument}={value}
+                Define additional JMeter properties
+        -G, --globalproperty (argument)[=(value)]
+                Define Global properties (sent to servers)
+                e.g. -Gport=123
+                 or -Gglobal.properties
+        -D, --systemproperty {argument}={value}
+                Define additional System properties
+        -S, --systemPropertyFile {filename}
+                a property file to be added as System properties
+        -L, --loglevel {argument}={value}
+                Define loglevel: [category=]level 
+                e.g. jorphan=INFO or jmeter.util=DEBUG
+        -r, --runremote (non-GUI only)
+                Start remote servers (as defined by the jmeter property remote_hosts)
+        -R, --remotestart  server1,... (non-GUI only)
+                Start these remote servers (overrides remote_hosts)
+        -d, --homedir {argument}
+                the jmeter home directory to use
+        -X, --remoteexit
+                Exit the remote servers at end of test (non-GUI)
+
+
+

+ +Note: the JMeter log file name is formatted as a SimpleDateFormat (applied to the current date) +if it contains paired single-quotes, .e.g. 'jmeter_'yyyyMMddHHmmss'.log' + +

+

+ +If the special name LAST is used for the -t, -j or -l flags, then JMeter takes that to mean the last test plan +that was run in interactive mode. + +

+
+

+ + + + +
+ +2.4.8 non-GUI shutdown + +
+
+

+ +Prior to version 2.5.1, JMeter invoked System.exit() when a non-GUI test completed. +This caused problems for applications that invoke JMeter directly, so JMeter no longer invokes System.exit() +for a normal test completion. [Some fatal errors may still invoke System.exit()] +JMeter will exit all the non-daemon threads it starts, but it is possible that some non-daemon threads +may still remain; these will prevent the JVM from exitting. +To detect this situation, JMeter starts a new daemon thread just before it exits. +This daemon thread waits a short while; if it returns from the wait, then clearly the +JVM has not been able to exit, and the thread prints a message to say why. + +

+

+ +The property + +jmeter.exit.check.pause + + can be used to override the default pause of 2000ms (2secs). +If set to 0, then JMeter does not start the daemon thread. + +

+
+

+
+

+

+ + + + +
+ +2.5 Configuring JMeter +
+
+

+If you wish to modify the properties with which JMeter runs you need to + either modify the jmeter.properties in the /bin directory or create + your own copy of the jmeter.properties and specify it in the command line. + +

+

+ + +
+ Note: since 2.2, you can define additional JMeter properties in the file defined by the + JMeter property + +user.properties + + which has the default value + +user.properties + +. + The file will be automatically loaded if it is found in the current directory + or if it is found in the JMeter bin directory. + Similarly, + +system.properties + + is used to update system properties. + +
+

+

+Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
ssl.providerYou can specify the class for your SSL + implementation if you don't want to use the built-in Java implementation. + + +No +
xml.parserYou can specify an implementation as your XML + parser. The default value is: org.apache.xerces.parsers.SAXParser + +No +
remote_hostsComma-delimited list of remote JMeter hosts (or host:port if required). + If you are running JMeter in a distributed environment, list the machines where + you have JMeter remote servers running. This will allow you to control those + servers from this machine's GUI + +No +
not_in_menuA list of components you do not want to see in + JMeter's menus. As JMeter has more and more components added, you may wish to + customize your JMeter to show only those components you are interested in. + You may list their classname or their class label (the string that appears + in JMeter's UI) here, and they will no longer appear in the menus. + +No +
search_paths + List of paths (separated by ;) that JMeter will search for JMeter add-on classes; + for example additional samplers. + This is in addition to any jars found in the lib/ext directory. + + +No +
user.classpath + List of paths that JMeter will search for utility classes. + This is in addition to any jars found in the lib directory. + + +No +
user.properties + Name of file containing additional JMeter properties. + These are added after the initial property file, but before the -q and -J options are processed. + + +No +
system.properties + Name of file containing additional system properties. + These are added before the -S and -D options are processed. + + +No +
+

+

+ + The command line options and properties files are processed in the following order: + +

    + + +
  • +-p propfile +
  • + + +
  • +jmeter.properties (or the file from the -p option) is then loaded +
  • + + +
  • +-j logfile +
  • + + +
  • +Logging is initialised +
  • + + +
  • +user.properties is loaded +
  • + + +
  • +system.properties is loaded +
  • + + +
  • +all other command-line options are processed +
  • + + +
+ + +

+

+ + +See also the comments in the jmeter.properties, user.properties and system.properties files for further information on other settings you can change. + + +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/glossary.html b/ApacheJmeter/docs/usermanual/glossary.html new file mode 100644 index 0000000..c479352 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/glossary.html @@ -0,0 +1,386 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Glossary + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + +
+ + + +
+
+ + + + +
+ +22. Glossary +
+
+

+ + + + +Elapsed time + + +. JMeter measures the elapsed time from just before sending the request to +just after the last response has been received. +JMeter does not include the time needed to render the response, nor does JMeter process any client code, for example +Javascript. + +

+

+ + + + +Latency + + +. JMeter measures the latency from just before sending the request to +just after the first response has been received. Thus the time +includes all the processing needed to assemble the request as well as +assembling the first part of the response, which in general will be longer than one +byte. +Protocol analysers (such as Wireshark) measure the time when bytes are actually sent/received over the interface. +The JMeter time should be closer to that which is experienced by a +browser or other application client. + +

+

+ + + + +Median + + + is a number which divides the samples into two equal halves. +Half of the samples are smaller than the median, and half are larger. +[Some samples may equal the median.] +This is a standard statistical measure. +See, for example: + +Median + + entry at Wikipedia. +The Median is the same as the 50 + +th + + Percentile + +

+

+ + + + +90% Line (90 + +th + + Percentile) + + + is the value below which 90% of the samples fall. +The remaining samples too at least as long as the value. +This is a standard statistical measure. +See, for example: + +Percentile + + entry at Wikipedia. + +

+

+ + + + +Standard Deviation + + + is a measure of the variability +of a data set. This is a standard statistical measure. +See, for example: + +Standard Deviation + + entry at Wikipedia. +JMeter calculates the population standard deviation (e.g. STDEVP function in spreadheets), not the sample standard deviation (e.g. STDEV). + +

+

+ + + +The + +Thread Name + + + as it appears in Listeners and logfiles +is derived from the Thread Group name and the thread within the group. +
+ + +The name has the format + + +groupName + " " + groupIndex + "-" + threadIndex + + +where: + +

    + + +
  • +groupName - name of the Thread Group element +
  • + + +
  • +groupIndex - number of the Thread Group in the Test Plan, starting from 1 +
  • + + +
  • +threadIndex - number of the thread within the Thread Group, starting from 1 +
  • + + +
+ +A test plan with two Thread Groups each with two threads would use the names: + +
+
+Thread Group 1-1
+Thread Group 1-2
+Thread Group 2-1
+Thread Group 2-2
+
+
+ + +

+

+ + + + +Throughput + + + is calculated as requests/unit of time. +The time is calculated from the start of the first sample to the end of the last sample. +This includes any intervals between samples, as it is supposed to represent the load on the server. +
+ + +The formula is: Throughput = (number of requests) / (total time). + +

+
+

+

+ + + + + +
+ + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/hints_and_tips.html b/ApacheJmeter/docs/usermanual/hints_and_tips.html new file mode 100644 index 0000000..cd20f11 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/hints_and_tips.html @@ -0,0 +1,374 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Hints and Tips + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +21. Hints and Tips +
+
+

+ +This section is a collection of various hints and tips that have been suggested by various questions on the JMeter User list. +If you don't find what you are looking for here, please check the + +JMeter Wiki + +. +Also, try search the JMeter User list; someone may well have already provided a solution. + +

+ + + + +
+ +21.1 Passing variables between threads + +
+
+

+ +JMeter variables have thread scope. This is deliberate, so that threads can act indepently. +However sometimes there is a need to pass variables between different threads, in the same or different Thread Groups. + +

+

+ +One way to do this is to use a property instead. +Properties are shared between all JMeter threads, so if one thread + +sets a property + +, +another thread can + +read + + the updated value. + +

+

+ +If there is a lot of information that needs to be passed between threads, then consider using a file. +For example you could use the + +Save Responses to a file + + +listener or perhaps a BeanShell PostProcessor in one thread, and read the file using the HTTP Sampler "file:" protocol, +and extract the information using a PostProcessor or BeanShell element. + +

+

+ +If you can derive the data before starting the test, then it may well be better to store it in a file, +read it using CSV Dataset. + +

+
+

+ + + + +
+ +21.2 Enabling Debug logging + +
+
+

+ +Most test elements include debug logging. If running a test plan from the GUI, +select the test element and use the Help Menu to enable or disable logging. +The Help Menu also has an option to display the GUI and test element class names. +You can use these to determine the correct property setting to change the logging level. + +

+

+ +It is sometimes very useful to see Log messages to debug dynamic scripting languages like BeanShell or +groovy used in JMeter. +Since 2.6, you can view log messages directly in JMeter GUI, to do so, use menu Options > Log Viewer, +a log console will appear at the bottom of the interface. +Note that log messages are cleared each time you disable this option. +By default this log console is disabled, you can enable it by changing in jmeter.properties: + +

    + + +
  • +jmeter.loggerpanel.display=true +
  • + + +
+ + +

+
+

+ + + + +
+ +21.3 Searching + +
+
+

+ +It is sometimes hard to find in a Test Plan tree and elements using a variable or containing a certain URL or parameter. +A new feature is now available since 2.6, you can access it in Menu Search. +It provides search with following options: + +

    + + +
  • +Case Sensitive : Makes search case sensitive +
  • + + +
  • +Regexp : Is text to search a regexp, if so Regexp will be searched in Tree of components, example "\btest\b" will match any component that contains test in searchable elements of the component +
  • + + +
+ + +

+


+Figure 1 - Search raw text in TreeView +

+


+Figure 2 - Result in TreeView +

+


+Figure 3 - Search Regexp in TreeView (in this example we search whole word) +

+


+Figure 4 - Result in TreeView +

+
+

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/index.html b/ApacheJmeter/docs/usermanual/index.html new file mode 100644 index 0000000..8dd00f1 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/index.html @@ -0,0 +1,1379 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + +
+ + + +
+
+ + + + +
+ +User's Manual +
+
+

+Click on the section name to go straight to the section. + Click on the "+" to go to the relevant section of the detailed section list, + where you can select individual subsections. +

+ + + + +
+ +Section Summary + +
+
+ +
+

+ + + + +
+ +Detailed Section List + +
+
+ +
+

+
+

+

+ + + + + +
+ + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/intro.html b/ApacheJmeter/docs/usermanual/intro.html new file mode 100644 index 0000000..cf00003 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/intro.html @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Introduction + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + +
+ + + +
+
+ + + + +
+ +1. Introduction +
+
+

+ + + +Apache JMeter + + is a 100% pure Java desktop application designed +to load test client/server software +(such as a + +web application + +). It may be used +to test performance both on static and dynamic +resources such as static files, Java Servlets, CGI scripts, Java objects, + +databases + +, + + +FTP servers + +, and more. JMeter can be used to simulate a heavy +load on a server, network or object to test its strength or to analyze +overall performance under different load types. +

+

+ +Additionally, JMeter can help you regression test your application by letting you create +test scripts with + +assertions + + to validate that your application is returning the +results you expect. For maximum flexibility, JMeter lets you create these assertions using +regular expressions. +

+

+But please note that JMeter is not a browser. +

+ + + + +
+ +1.1 History + +
+
+

+Stefano Mazzocchi of the Apache Software Foundation was the original developer of JMeter. +He wrote it primarily to test the performance of Apache JServ (a project that has +since been replaced by the Apache Tomcat project). We redesigned JMeter to enhance the GUI +and to add functional-testing capabilities. + +

+
+

+ + + + +
+ +1.2 The Future + +
+
+

+We hope to see JMeter's capabilities rapidly expand as developers take advantage of its +pluggable architecture. The primary goal of further development is to make JMeter the most +useful regression testing tool as possible, without compromising JMeter's load-testing +capabilities. +

+
+

+
+

+

+ + + + + +
+ + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/ldapanswer_xml.html b/ApacheJmeter/docs/usermanual/ldapanswer_xml.html new file mode 100644 index 0000000..53fe998 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/ldapanswer_xml.html @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: LDAP answer XML description + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +description of the ldapanswer XML definition +
+
+

+ + The extended LDAP sampler was built to support testing for very complex testpurposes. + It was aimed at supporting the LDAP operations as close as possible. + As the results are not passed back in a user-readable form, I invented my own xml definition to + construct an answer in xml encoding, so the results may be parsed with regextracter or alike functions. + +

+ + + + +
+ +1 Global overview + +
+
+

+ + The global structure is as follows: +
+ + + + +

    +
  1. + <ldapanswer> and <ldapanswer/> are the begin and endtags which are used to define the beginning ands end of the answer. + +
  2. +
  3. + each ldapanswer contains 4 sections, an "operation" section (enclosed by + <operation> tags) a respons code section (enclosed by <responsecode> tags), a + response message (enclosed by <responsemessage> tags and (only with a search + request) a searchresult section (enclosed with <searchresult> tags> +
  4. +
+ + + +

+ + + + +
+ +1.1 The operation section + +
+
+

+ +The operation section defines the operation as it is sent to the LDAP Server. The +following tags (with a short explanation) are used + + +

    +
  1. + <opertype>
    + Thise describes which kind of operation was sent, it can have the following values: +
      +
    1. + bind
      + (this code is used for both a thread bind as a single bind/unbind operation) +
    2. +
    3. + unbind +
    4. +
    5. + compare +
    6. +
    7. + add +
    8. +
    9. + delete +
    10. +
    11. + modfy +
    12. +
    13. + rename +
    14. +
    15. + search +
    16. +
    +
  2. +
  3. +<attributes> +This will name all attributes that will be added or modified during an "add" or a "modify" +operation. +
  4. +
  5. +<baseobject> +This will hold the value of the root or context prefix that will be used in the current session. +This value is given for any "thread bind" and "thread unbind" operation +
  6. +
  7. +<binddn> +This is the DN of the user that is binding to the directory, it is used in the +"thread bind", "thread unbind" and "single bind" operations. +
  8. +
  9. +<comparedn> +This is the DN of the object from which an attribute is compared to a given attributevalue +It is only used in the "compare" operation. +
  10. +
  11. +<comparefilter> +This is the filter, in the form (attribute= value) that is used in the "compare" operation. +
  12. +
  13. +<countlimit> +This is the maximum number of searchresults that will be returned, as it is requested by the client. +The actual number might be smaller as the server can have its own countlimit configured. +This will be used only in search requests.
  14. +
  15. +<dn> +This is the distinguished name of the object on which the actions are performed. +It is used in Add, Delete, Modify and ModifyDN operations +
  16. +
  17. +<newdn> +This is the new distinghuised name of an object to which the object will be renamed +or moved, it is used in de ModifyDN operation +
  18. +
  19. +<scope> +This is used in search operations, it can be 0 (object), 1 (onelevel) or 2 (subtree). +It defines the scope of the search which can be limited to the above settings. +
  20. +
  21. +<searchfilter> +The searchfilter that will be used in a search request, it should be in proper LDAP syntax. +
  22. +
  23. +<searchbase> +The searchbase where a search is started, this is given, relative to the baseobject of the current connection. +
  24. +
  25. +<timelimit> +This is the maximum timethe server will use in finding the requested objects.as it is requested by the client. +The actual number might be smaller as the server can have its own countlimit configured. +This will be used only in search requests.
  26. +
+ + + +

+
+

+ + + + +
+ +1.2 Response message section + +
+
+

+ +As the response code, the official LDAP error definitions are used, so this section +contains the error message as returned from the server. +A succesfull request always returns "Success" as the responsmessage. +The following tag is used: + + +<responsemessage>
+ +

+
+

+ + + + +
+ +1.3 Response code section + +
+
+

+ +As the response code, the official LDAP error definitions are used, so this section +contains the error number as returned from the server. +A succesfull request always returns 0 (zero) as the responscode. +The following tag is used: + + +<responsecode>
+ + + +

+
+

+ + + + +
+ +1.4 Search Result section + +
+
+

+ +The following tag is used: + + +<searchresult>
+This gives the results from a serachrequest, as they are returned by the server. +It contains the following tags: +

    +
  1. +<dn> +This contains the complete distinghuised name of the object which is represented here +
  2. +
  3. +<returnedattr> +This contains the number of returned attributes. +
  4. +
  5. +<attribute> +Each returned attribute is given between these tags, it always consists of an +attribute name and attribute value pair +
      +
    1. +<attributename> +This contains the official attributename as returned by the server. +
    2. +
    3. +<attributevalue> +This contains the attribute value as returned by the server. +
    4. +
    +
  6. +
+ + +

+
+

+
+

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/ldapops_tutor.html b/ApacheJmeter/docs/usermanual/ldapops_tutor.html new file mode 100644 index 0000000..0cea7c7 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/ldapops_tutor.html @@ -0,0 +1,457 @@ + + + + + + + + + + + + + + + + + + + +Apache JMeter - JMeter - User's Manual: LDAP Operations + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+
+ + + + +
+ +A short LDAP Operations tutorial +
+
+

+ + The extended LDAP sampler was built to support testing for very complex testpurposes. + It was aimed at supporting the LDAP operations as close as possible. + In this short tutorial, I will explain which LDAP operations exist and what they do. + Per operation, I will shortly explain how these operations are implemented. +
+ + + LDAP servers are some kind of hierarchical database, they store objects (entries) in a tree. The uppermost part of a tree is called the ROOT of the tree. +
+ + + eg. When a tree starts with dc=com, the root equals dc=com. +
+ + + The next level can exist under the root, eg dc=Siemens. The full name of this object (the "distinghuised name") is "dc=siemens,dc=com. +
+ + + Again, a following level can be made, by adding the user "cn=admin" under dc=siemens,dc=com. This object has a DN (distinguished name) of "cn=admin,dc=siemens,dc=com". +
+ + + The relative distinguished name (RDN) is the last part of the DN, eg. cn=admin. +
+ + + The characteristics of an object are determined by the objectClasses, which can be seen as a collection of attributes. +
+ + + The type of an object is determined by the "structural objectClass" eg person, organizationalUnit or country. +
+ + + The attributes contain the data of an object, eg mailadress, name, streetadress etc. Each attribute can have 0, 1 or more values. + +

+ + + + +
+ +1 Bind operation + +
+
+

+ + Any contact with an LDAP server MUST start with a bind request. LDAP is a state dependent protocol. Without opening a session to + a LDAP server, no additional request can be made. + Due to some peculiarities in the JAVA libraries, 2 different bind operations are implemented. + +

+ + + + +
+ +1.1 Thread Bind + +
+
+

+ + This bind is meant to open a session to a LDAP server. Any testplan should use this operation as the starting point from a session. + For each Thread (each virtual user) a seperate connection with the LDAP server is build, and so a seperate Thread bind is performed. + +

+
+

+ + + + +
+ +1.2 Single bind/unbind + +
+
+

+ + This bind is used for user authentication verification. + A proper developed LDAP client, who needs an authenticated user, perform a bind with a given distinguished name and password. + This Single bind/unbind operation is for this purpose. It builds it own seperate connection to the LDAP server, performs a + bind operation, and ends the connection again (by sending an unbind). + +

+
+

+
+

+ + + + +
+ +2 Unbind + +
+
+

+ + To close a connection to a LDAP server, an unbind operation is needed. + As the Single bind/unbind operation already (implicitly) performs an unbind, only a Thread unbind operation is needed. + This Thread unbind just closes the connection and cleans up any resources it has used. + +

+
+

+ + + + +
+ +3 Compare + +
+
+

+ + The compare operation needs the full distinguished name from a LDAP object, as well as a attribute and a value for the attribute. + It will simply check: "Has this object really this attribute with this value?". + Typical use is checking the membership of a certain user with a given group. + +

+
+

+ + + + +
+ +4 Search + +
+
+

+ + The search test simply searches for all objects which comply with a given search filter, eg. + all persons with a "employeeType=inactive" or "all persons with a userID equals user1" + + +

+
+

+ + + + +
+ +5 Add + +
+
+

+ + This simply add an object to the LDAP directory. + Off course the combination of attributes and distinguishedName must be valid! + +

+
+

+ + + + +
+ +6 Modify + +
+
+

+ + This operation modifies one or more attributes from a given object. + It needs the distinghised name from the object, as well as the attributes and the new values for this attribute. +
+ + + Three versions are available, add, for adding an attribute value +
+ + + replace, for overwriting the old attribute value with a new value +
+ + + delete, to delete a value form an attribute, or to delete all the values of an attribute +
+ + + +

+
+

+ + + + +
+ +7 Delete + +
+
+

+ + This operation deletes an object from the LDAP server. + It needs the distinghised name from the object. + +

+
+

+ + + + +
+ +8 modDN + +
+
+

+ + This operation modifies the distinguished name from an object (it "moves" the object). +
+ + + It comes in two flavours, just renaming an entry, then you specify a new RDN (relative distinguished name, this is the lowest part of the DN) +
+ + + eg, you can rename "cn=admin,dc=siemens,dc=com" to cn=administrator,dc=Siemens,dc=com" +
+ + + The second flavour is renaming (moving) a complete subtree by specifying a "new superior" +
+ + + eg you can move a complete subtree "ou=retired,ou=people,dc=siemens,dc=com" to a new subtree "ou=retired people,dc=siemens,dc=com" by specifying + a new rdn "ou=retired people" and a new superior of "dc=siemens,dc=com" + +

+
+

+
+

+

+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/listeners.html b/ApacheJmeter/docs/usermanual/listeners.html new file mode 100644 index 0000000..6be0e43 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/listeners.html @@ -0,0 +1,1276 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Listeners + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +14. Introduction to listeners +
+
+

+A listener is a component that shows the results of the +samples. The results can be shown in a tree, tables, graphs or simply written to a log +file. To view the contents of a response from any given sampler, add either of the Listeners "View +Results Tree" or "View Results in table" to a test plan. To view the response time graphically, add +graph results, spline results or distribution graph. +The +listeners + +section of the components page has full descriptions of all the listeners. +

+

+ + +
+Different listeners display the response information in different ways. +However, they all write the same raw data to the output file - if one is specified. + +
+

+

+ +The "Configure" button can be used to specify which fields to write to the file, and whether to +write it as CSV or XML. +CSV files are much smaller than XML files, so use CSV if you are generating lots of samples. + +

+

+ +The file name can be specified using either a relative or an absolute path name. +Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). +Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). +If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), +then the path is assumed to be relative to the JMX file location. + +

+

+ +If you only wish to record certain samples, add the Listener as a child of the sampler. +Or you can use a Simple Controller to group a set of samplers, and add the Listener to that. +The same filename can be used by multiple samplers - but make sure they all use the same configuration! + +

+
+

+

+ + + + +
+ +14.1 Default Configuration +
+
+

+ +The default items to be saved can be defined in the jmeter.properties (or user.properties) file. +The properties are used as the initial settings for the Listener Config pop-up, and are also +used for the log file specified by the -l command-line flag (commonly used for non-GUI test runs). + +

+

+To change the default format, find the following line in jmeter.properties: +

+

+jmeter.save.saveservice.output_format= +

+

+ +The information to be saved is configurable. For maximum information, choose "xml" as the format and specify "Functional Test Mode" on the Test Plan element. If this box is not checked, the default saved +data includes a time stamp (the number of milliseconds since midnight, +January 1, 1970 UTC), the data type, the thread name, the label, the +response time, message, and code, and a success indicator. If checked, all information, including the full response data will be logged. +

+

+ +The following example indicates how to set +properties to get a vertical bar ("|") delimited format that will +output results like:. +

+

+ + + + + +

+
+timeStamp|time|label|responseCode|threadName|dataType|success|failureMessage
+02/06/03 08:21:42|1187|Home|200|Thread Group-1|text|true|
+02/06/03 08:21:42|47|Login|200|Thread Group-1|text|false|Test Failed: 
+	expected to contain: password etc.
+
+
+ + + +

+

+ +The corresponding jmeter.properties that need to be set are shown below. One oddity +in this example is that the output_format is set to csv, which +typically +indicates comma-separated values. However, the default_delimiter was +set to be a vertical bar instead of a comma, so the csv tag is a +misnomer in this case. (Think of CSV as meaning character separated values) +

+

+ + + + + +

+
+jmeter.save.saveservice.output_format=csv
+jmeter.save.saveservice.assertion_results_failure_message=true
+jmeter.save.saveservice.default_delimiter=|
+
+
+ + + + + +

+ +The full set of properties that affect result file output is shown below. + +

+ + + + + +
+
+#---------------------------------------------------------------------------
+# Results file configuration
+#---------------------------------------------------------------------------
+
+# This section helps determine how result data will be saved.
+# The commented out values are the defaults.
+
+# legitimate values: xml, csv, db.  Only xml and csv are currently supported.
+#jmeter.save.saveservice.output_format=xml
+
+
+# true when field should be saved; false otherwise
+
+# assertion_results_failure_message only affects CSV output
+#jmeter.save.saveservice.assertion_results_failure_message=false
+#
+#jmeter.save.saveservice.data_type=true
+#jmeter.save.saveservice.label=true
+#jmeter.save.saveservice.response_code=true
+# response_data is not currently supported for CSV output
+#jmeter.save.saveservice.response_data=false
+# Save ResponseData for failed samples
+#jmeter.save.saveservice.response_data.on_error=false
+#jmeter.save.saveservice.response_message=true
+#jmeter.save.saveservice.successful=true
+#jmeter.save.saveservice.thread_name=true
+#jmeter.save.saveservice.time=true
+#jmeter.save.saveservice.subresults=true
+#jmeter.save.saveservice.assertions=true
+#jmeter.save.saveservice.latency=true
+#jmeter.save.saveservice.samplerData=false
+#jmeter.save.saveservice.responseHeaders=false
+#jmeter.save.saveservice.requestHeaders=false
+#jmeter.save.saveservice.encoding=false
+#jmeter.save.saveservice.bytes=true
+#jmeter.save.saveservice.url=false
+#jmeter.save.saveservice.filename=false
+#jmeter.save.saveservice.hostname=false
+#jmeter.save.saveservice.thread_counts=false
+#jmeter.save.saveservice.sample_count=false
+#jmeter.save.saveservice.idle_time=false
+
+# Timestamp format
+# legitimate values: none, ms, or a format suitable for SimpleDateFormat
+#jmeter.save.saveservice.timestamp_format=ms
+#jmeter.save.saveservice.timestamp_format=MM/dd/yy HH:mm:ss
+
+# Put the start time stamp in logs instead of the end
+sampleresult.timestamp.start=true
+
+# Whether to use System.nanoTime() - otherwise only use System.currentTimeMillis()
+#sampleresult.useNanoTime=true
+
+# Use a background thread to calculate the nanoTime offset
+# Set this to <= 0 to disable the background thread
+#sampleresult.nanoThreadSleep=5000
+
+# legitimate values: none, first, all
+#jmeter.save.saveservice.assertion_results=none
+
+# For use with Comma-separated value (CSV) files or other formats
+# where the fields' values are separated by specified delimiters.
+# Default:
+#jmeter.save.saveservice.default_delimiter=,
+# For TAB, since JMeter 2.3 one can use:
+#jmeter.save.saveservice.default_delimiter=\t
+
+#jmeter.save.saveservice.print_field_names=false
+
+# Optional list of JMeter variable names whose values are to be saved in the result data files.
+# Use commas to separate the names. For example:
+#sample_variables=SESSION_ID,REFERENCE
+# N.B. The current implementation saves the values in XML as attributes,
+# so the names must be valid XML names.
+# Versions of JMeter after 2.3.2 send the variable to all servers
+# to ensure that the correct data is available at the client.
+
+# Optional xml processing instruction for line 2 of the file:
+#jmeter.save.saveservice.xml_pi=<?xml-stylesheet type="text/xsl" href="sample.xsl"?>
+
+# Prefix used to identify filenames that are relative to the current base
+#jmeter.save.saveservice.base_prefix=~/
+
+
+ + +
+

+

+ +The date format to be used for the timestamp_format is described in + + + + +SimpleDateFormat + + +. +Bear in mind that choosing a date format other than "ms" is likely to +make it impossible for JMeter to interpret the value when it is read +in later for viewing purposes. +

+ + + + +
+ +14.1.1 Sample Variables + +
+
+

+ +Versions of JMeter after 2.3.1 allow one to use the + +sample_variables + + +property to define a list of additional JMeter variables which are to be saved with +each sample in the JTL files. The values are written to CSV files as additional columns, +and as additional attributes in XML files. See above for an example. + +

+
+

+ + + + +
+ +14.1.2 Sample Result Save Configuration + +
+
+

+ +Listeners can be configured to save different items to the result log files (JTL) by using the Config popup as shown below. +The defaults are defined as described in the + +Listener Default Configuration + + section above. +Items with (CSV) after the name only apply to the CSV format; items with (XML) only apply to XML format. +CSV format cannot currently be used to save any items that include line-breaks. + +

+


+
+ + +Configuration dialogue + +

+
+

+

+ +Note that cookies, method and the query string are saved as part of the "Sampler Data" option. + +

+
+

+

+ + + + +
+ +14.2 non-GUI (batch) test runs +
+
+

+ +When running in non-GUI mode, the -l flag can be used to create a top-level listener for the test run. +This is in addition to any Listeners defined in the test plan. +The configuration of this listener is controlled by entries in the file jmeter.properties +as described in the previous section. + +

+

+ +This feature can be used to specify different data and log files for each test run, for example: + +

+
+jmeter -n -t testplan.jmx -l testplan_01.jtl -j testplan_01.log
+jmeter -n -t testplan.jmx -l testplan_02.jtl -j testplan_02.log
+
+
+ + +

+

+ +Note that JMeter logging messages are written to the file + +jmeter.log + + by default. +This file is recreated each time, so if you want to keep the log files for each run, +you will need to rename it using the -j option as above. The -j option was added in version 2.3. + +

+

+Versions of JMeter after 2.3.1 support variables in the log file name. +If the filename contains paired single-quotes, then the name is processed +as a SimpleDateFormat format applied to the current date, for example: + + +log_file='jmeter_'yyyyMMddHHmmss'.tmp' + +. +This can be used to generate a unique name for each test run. + +

+
+

+

+ + + + +
+ +14.3 Resource usage +
+
+

+ +Listeners can use a lot of memory if there are a lot of samples. + + +Most of the listeners currently keep a copy of every sample they display, apart from: + +

+
    + + +
  • +Simple Data Writer +
  • + + +
  • +BeanShell/BSF Listener +
  • + + +
  • +Mailer Visualizer +
  • + + +
  • +Monitor Results +
  • + + +
  • +Summary Report +
  • + + +
+

+ +The following Listeners no longer need to keep copies of every single sample. +Instead, samples with the same elapsed time are aggregated. +Less memory is now needed, especially if most samples only take a second or two at most. + +

+
    + + +
  • +Aggregate Report +
  • + + +
  • +Aggregate Graph +
  • + + +
  • +Distribution Graph +
  • + + +
+

+To minimise the amount of memory needed, use the Simple Data Writer, and use the CSV format. +

+
+

+

+ + + + +
+ +14.4 CSV Log format +
+
+

+ +The CSV log format depends on which data items are selected in the configuration. +Only the specified data items are recorded in the file. +The order of appearance of columns is fixed, and is as follows: + +

+
    + + +
  • +timeStamp - in milliseconds since 1/1/1970 +
  • + + +
  • +elapsed - in milliseconds +
  • + + +
  • +label - sampler label +
  • + + +
  • +responseCode - e.g. 200, 404 +
  • + + +
  • +responseMessage - e.g. OK +
  • + + +
  • +threadName +
  • + + +
  • +dataType - e.g. text +
  • + + +
  • +success - true or false +
  • + + +
  • +failureMessage - if any +
  • + + +
  • +bytes - number of bytes in the sample +
  • + + +
  • +grpThreads - number of active threads in this thread group +
  • + + +
  • +allThreads - total number of active threads in all groups +
  • + + +
  • +URL +
  • + + +
  • +Filename - if Save Response to File was used +
  • + + +
  • +latency - time to first response +
  • + + +
  • +encoding +
  • + + +
  • +SampleCount - number of samples (1, unless multiple samples are aggregated) +
  • + + +
  • +ErrorCount - number of errors (0 or 1, unless multiple samples are aggregated) +
  • + + +
  • +Hostname where the sample was generated +
  • + + +
  • +IdleTime - number of milliseconds of 'Idle' time (normally 0) +
  • + + +
  • +Variables, if specified +
  • + + +
+
+

+

+ + + + +
+ +14.5 XML Log format 2.1 +
+
+

+ +The format of the updated XML (2.1) is as follows (line breaks will be different): + +

+
+
+<?xml version="1.0" encoding="UTF-8"?>
+<testResults version="1.2">
+
+-- HTTP Sample, with nested samples 
+
+<httpSample t="1392" lt="351" ts="1144371014619" s="true" 
+     lb="HTTP Request" rc="200" rm="OK" 
+     tn="Listen 1-1" dt="text" de="iso-8859-1" by="12407">
+  <httpSample t="170" lt="170" ts="1144371015471" s="true" 
+        lb="http://www.apache.org/style/style.css" rc="200" rm="OK" 
+        tn="Listen 1-1" dt="text" de="ISO-8859-1" by="1002">
+    <responseHeader class="java.lang.String">HTTP/1.1 200 OK
+Date: Fri, 07 Apr 2006 00:50:14 GMT
+...
+Content-Type: text/css
+</responseHeader>
+    <requestHeader class="java.lang.String">MyHeader: MyValue</requestHeader>
+    <responseData class="java.lang.String">body, td, th {
+    font-size: 95%;
+    font-family: Arial, Geneva, Helvetica, sans-serif;
+    color: black;
+    background-color: white;
+}
+...
+</responseData>
+    <cookies class="java.lang.String"></cookies>
+    <method class="java.lang.String">GET</method>
+    <queryString class="java.lang.String"></queryString>
+    <url>http://www.apache.org/style/style.css</url>
+  </httpSample>
+  <httpSample t="200" lt="180" ts="1144371015641" s="true" 
+     lb="http://www.apache.org/images/asf_logo_wide.gif" 
+     rc="200" rm="OK" tn="Listen 1-1" dt="bin" de="ISO-8859-1" by="5866">
+    <responseHeader class="java.lang.String">HTTP/1.1 200 OK
+Date: Fri, 07 Apr 2006 00:50:14 GMT
+...
+Content-Type: image/gif
+</responseHeader>
+    <requestHeader class="java.lang.String">MyHeader: MyValue</requestHeader>
+    <responseData class="java.lang.String">http://www.apache.org/asf.gif</responseData>
+      <responseFile class="java.lang.String">Mixed1.html</responseFile>
+    <cookies class="java.lang.String"></cookies>
+    <method class="java.lang.String">GET</method>
+    <queryString class="java.lang.String"></queryString>
+    <url>http://www.apache.org/asf.gif</url>
+  </httpSample>
+  <responseHeader class="java.lang.String">HTTP/1.1 200 OK
+Date: Fri, 07 Apr 2006 00:50:13 GMT
+...
+Content-Type: text/html; charset=ISO-8859-1
+</responseHeader>
+  <requestHeader class="java.lang.String">MyHeader: MyValue</requestHeader>
+  <responseData class="java.lang.String">
+...
+&lt;html&gt;
+ &lt;head&gt;
+...
+ &lt;/head&gt;
+ &lt;body&gt;        
+...
+ &lt;/body&gt;
+&lt;/html&gt;
+</responseData>
+  <cookies class="java.lang.String"></cookies>
+  <method class="java.lang.String">GET</method>
+  <queryString class="java.lang.String"></queryString>
+  <url>http://www.apache.org/</url>
+</httpSample>
+
+-- nonHTTPP Sample
+
+<sample t="0" lt="0" ts="1144372616082" s="true" lb="Example Sampler"
+    rc="200" rm="OK" tn="Listen 1-1" dt="text" de="ISO-8859-1" by="10">
+  <responseHeader class="java.lang.String"></responseHeader>
+  <requestHeader class="java.lang.String"></requestHeader>
+  <responseData class="java.lang.String">Listen 1-1</responseData>
+  <responseFile class="java.lang.String">Mixed2.unknown</responseFile>
+  <samplerData class="java.lang.String">ssssss</samplerData>
+</sample>
+
+</testResults>
+
+
+

+ +Note that the sample node name may be either "sample" or "httpSample". + +

+
+

+

+ + + + +
+ +14.6 XML Log format 2.2 +
+
+

+ +The format of the JTL files is identical for 2.2 and 2.1. Format 2.2 only affects JMX files. + +

+
+

+

+ + + + +
+ +14.7 Sample Attributes +
+
+

+ +The sample attributes have the following meaning: + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +Attribute + + + +Content + +
+ +by + + + +Bytes + +
+ +de + + + +Data encoding + +
+ +dt + + + +Data type + +
+ +ec + + + +Error count (0 or 1, unless multiple samples are aggregated) + +
+ +hn + + + +Hostname where the sample was generated + +
+ +it + + + +Idle Time = time not spent sampling (milliseconds) (generally 0) + +
+ +lb + + + +Label + +
+ +lt + + + +Latency = time to initial response (milliseconds) - not all samplers support this + +
+ +na + + + +Number of active threads for all thread groups + +
+ +ng + + + +Number of active threads in this group + +
+ +rc + + + +Response Code (e.g. 200) + +
+ +rm + + + +Response Message (e.g. OK) + +
+ + s + + + +Success flag (true/false) + +
+ +sc + + + +Sample count (1, unless multiple samples are aggregated) + +
+ + t + + + +Elapsed time (milliseconds) + +
+ +tn + + + +Thread Name + +
+ +ts + + + +timeStamp (milliseconds since midnight Jan 1, 1970 UTC) + +
+ +varname + + + +Value of the named variable (versions of JMeter after 2.3.1) + +
+

+ +Versions 2.1 and 2.1.1 of JMeter saved the Response Code as "rs", but read it back expecting to find "rc". +This has been corrected so that it is always saved as "rc"; either "rc" or "rs" can be read. + +

+

+ + +
+Versions of JMeter after 2.3.1 allow additional variables to be saved with the test plan. +Currently, the variables are saved as additional attributes. +The testplan variable name is used as the attribute name. +See + +Sample variables + + (above) for more information. + +
+

+
+

+

+ + + + +
+ +14.8 Saving response data +
+
+

+ +As shown above, the response data can be saved in the XML log file if required. +However, this can make the file rather large, and the text has to be encoded so +that it is still valid XML. Also, images cannot be included. + +
+ + +Another solution is to use the Post-Processor +Save_Responses_to_a_file +. +This generates a new file for each sample, and saves the file name with the sample. +The file name can then be included in the sample log output. +The data will be retrieved from the file if necessary when the sample log file is reloaded. + +

+
+

+

+ + + + +
+ +14.9 Loading (reading) response data +
+
+

+To view an existing results file, you can use the File "Browse..." button to select a file. +If necessary, just create a dummy testplan with the appropriate Listener in it. + +

+

+Results can be read from XML or CSV format files. +When reading from CSV results files, the header (if present) is used to determine which fields were saved. + + +In order to interpret a header-less CSV file correctly, the appropriate JMeter properties must be set. + + + +

+

+ + +
+Versions of JMeter up to 2.3.2 used to clear any current data before loading the new file. +This is no longer done, thus allowing files to be merged. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. + +
+

+
+

+

+ + + + +
+ +14.10 Saving Listener GUI data +
+
+

+JMeter is capable of saving any listener as a PNG file. To do so, select the +listener in the left panel. Click edit -> Save As Image. A file dialog will +appear. Enter the desired name and save the listener. + +

+

+ +The Listeners which generate output as tables can also be saved using Copy/Paste. +Select the desired cells in the table, and use the OS Copy short-cut (normally Control+C). +The data will be saved to the clipboard, from where it can be pasted into another application, +e.g. a spreadsheet or text editor. + +

+


+Figure 1 - Edit -> Save As Image +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/regular_expressions.html b/ApacheJmeter/docs/usermanual/regular_expressions.html new file mode 100644 index 0000000..3958395 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/regular_expressions.html @@ -0,0 +1,855 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Regular Expressions + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +20. Regular Expressions +
+
+ + + + +
+ +20.1 Overview + +
+
+

+ +JMeter includes the pattern matching software + +Apache Jakarta ORO + + + +
+ + +There is some documentation for this on the Jakarta web-site, for example + + + +a summary of the pattern matching characters + + + +

+

+ +There is also documentation on an older incarnation of the product at + + +OROMatcher User's guide + +, which might prove useful. + +

+

+ +The pattern matching is very similar to the pattern matching in Perl. +A full installation of Perl will include plenty of documentation on regular expressions - look for perlrequick, perlretut, perlre, perlreref. + +

+

+ +It is worth stressing the difference between "contains" and "matches", as used on the Response Assertion test element: + +

+
    + + +
  • + +"contains" means that the regular expression matched at least some part of the target, +so 'alphabet' "contains" 'ph.b.' because the regular expression matches the substring 'phabe'. + +
  • + + +
  • + +"matches" means that the regular expression matched the whole target. +So 'alphabet' is "matched" by 'al.*t'. + +
  • + + +
+

+In this case, it is equivalent to wrapping the regular expression in ^ and $, viz '^al.*t$'. + +

+

+However, this is not always the case. +For example, the regular expression 'alp|.lp.*' is "contained" in 'alphabet', but does not match 'alphabet'. + +

+

+Why? Because when the pattern matcher finds the sequence 'alp' in 'alphabet', it stops trying any other combinations - and 'alp' is not the same as 'alphabet', as it does not include 'habet'. + +

+

+ +Note: unlike Perl, there is no need to (i.e. do not) enclose the regular expression in //. + +

+

+ +So how does one use the modifiers ismx etc if there is no trailing /? +The solution is to use + +extended regular expressions + +, i.e. /abc/i becomes (?i)abc. +See also + +Placement of modifiers + + below. + +

+
+

+ + + + +
+ +20.2 Examples + +
+
+

+Extract single string +

+

+ +Suppose you want to match the following portion of a web-page: + +
+ + + + +name="file" value="readme.txt"> + + + +
+ + +and you want to extract + +readme.txt + +. + +
+ + +A suitable regular expression would be: + +
+ + + + +name="file" value="(.+?)"> + + + +

+ +The special characters above are: + +

+ + +
    + + +
  • +( and ) - these enclose the portion of the match string to be returned +
  • + + +
  • +. - match any character +
  • + + +
  • ++ - one or more times +
  • + + +
  • +? - don't be greedy, i.e. stop when first match succeeds +
  • + + +
+ + +

+ +Note: without the ?, the .+ would continue past the first + +"> + + +until it found the last possible + +"> + + - which is probably not what was intended. + +

+ + +

+ +Note: although the above expression works, it's more efficient to use the following expression: + +
+ + + + +name="file" value="([^"]+)"> + + +where +
+ + +[^"] - means match anything except " +
+ + +In this case, the matching engine can stop looking as soon as it sees the first + +" + +, +whereas in the previous case the engine has to check that it has found + +"> + + rather than say + +" > + +. + +

+ + +

+Extract multiple strings +

+ + +

+ +Suppose you want to match the following portion of a web-page: +
+ + + + +name="file.name" value="readme.txt" + + +and you want to extract both + +file.name + + and + +readme.txt + +. + +
+ + +A suitable reqular expression would be: + +
+ + + + +name="([^"]+)" value="([^"]+)" + + + +
+ + +This would create 2 groups, which could be used in the JMeter Regular Expression Extractor template as $1$ and $2$. + +

+ + +

+ +The JMeter Regex Extractor saves the values of the groups in additional variables. + +

+ + +

+ +For example, assume: + +

+ + +
    + + +
  • +Reference Name: MYREF +
  • + + +
  • +Regex: name="(.+?)" value="(.+?)" +
  • + + +
  • +Template: $1$$2$ +
  • + + +
+ + +

+ + +
Do not enclose the regular expression in / / +
+

+ + +

+ +The following variables would be set: + +

+ + +
    + + +
  • +MYREF: file.namereadme.txt +
  • + + +
  • +MYREF_g0: name="file.name" value="readme.txt" +
  • + + +
  • +MYREF_g1: file.name +
  • + + +
  • +MYREF_g2: readme.txt +
  • + + +
+ +These variables can be referred to later on in the JMeter test plan, as ${MYREF}, ${MYREF_g1} etc + +

+
+

+ + + + +
+ +20.3 Line mode + +
+
+

+The pattern matching behaves in various slightly different ways, +depending on the setting of the multi-line and single-line modifiers. +Note that the single-line and multi-line operators have nothing to do with each other; +they can be specified independently. + +

+

+Single-line mode +

+

+ +Single-line mode only affects how the '.' meta-character is interpreted. + +

+

+ +Default behaviour is that '.' matches any character except newline. +In single-line mode, '.' also matches newline. + +

+

+Multi-line mode +

+

+ +Multi-line mode only affects how the meta-characters '^' and '$' are interpreted. + +

+

+ +Default behaviour is that '^' and '$' only match at the very beginning and end of the string. +When Multi-line mode is used, the '^' metacharacter matches at the beginning of every line, +and the '$' metacharacter matches at the end of every line. +

+
+

+ + + + +
+ +20.4 Meta characters + +
+
+

+ +Regular expressions use certain characters as meta characters - these characters have a special meaning to the RE engine. +Such characters must be escaped by preceeding them with \ (backslash) in order to treat them as ordinary characters. +Here is a list of the meta characters and their meaning (please check the ORO documentation if in doubt). + +

+
    + + +
  • +( ) - grouping +
  • + + +
  • +[ ] - character classes +
  • + + +
  • +{ } - repetition +
  • + + +
  • +* + ? - repetition +
  • + + +
  • +. - wild-card character +
  • + + +
  • +\ - escape character +
  • + + +
  • +| - alternatives +
  • + + +
  • +^ $ - start and end of string or line +
  • + + +
+

+Please note that ORO does not support the \Q and \E meta-characters. +[In other RE engines, these can be used to quote a portion of an RE so that the meta-characters stand for themselves.] +

+

+ +The following Perl5 extended regular expressions are supported by ORO. + + +

+ + +
+(?#text) +
+ + +
+An embedded comment causing text to be ignored. +
+ + +
+(?:regexp) +
+ + +
+Groups things like "()" but doesn't cause the group match to be saved. +
+ + +
+(?=regexp) +
+ + +
+A zero-width positive lookahead assertion. For example, \w+(?=\s) matches a word followed by whitespace, without including whitespace in the MatchResult. +
+ + +
+(?!regexp) +
+ + +
+A zero-width negative lookahead assertion. For example foo(?!bar) matches any occurrence of "foo" that isn't followed by "bar". Remember that this is a zero-width assertion, which means that a(?!b)d will match ad because a is followed by a character that is not b (the d) and a d follows the zero-width assertion. +
+ + +
+(?imsx) +
+ + +
+One or more embedded pattern-match modifiers. i enables case insensitivity, m enables multiline treatment of the input, s enables single line treatment of the input, and x enables extended whitespace comments. +
+ + +
+ + + +Note that + +(?<=regexp) + + - lookbehind - is not supported. + + + +

+
+

+ + + + +
+ +20.5 Placement of modifiers + +
+
+

+ +Modifiers can be placed anywhere in the regex, and apply from that point onwards. +[A bug in ORO means that they cannot be used at the very end of the regex. +However they would have no effect there anyway.] + +

+

+ +The single-line (?s) and multi-line (?m) modifiers are normally placed at the start of the regex. + +

+

+ +The ignore-case modifier (?i) may be usefully applied to just part of a regex, +for example: + +

+
+Match ExAct case or (?i)ArBiTrARY(?-i) case
+
+
+ + +

+
+

+
+

+

+ + + + +
+ +20.6 Testing Regular Expressions +
+
+

+ +Since JMeter 2.4, the listener + +View Results Tree + + +include a RegExp Tester to test regular expressions directly on sampler response data. + +

+

+ +There is a + +demo + + applet for Apache JMeter ORO. + +

+

+ +Another approach is to use a simple test plan to test the regular expressions. +The Java Request sampler can be used to generate a sample, or the HTTP Sampler can be used to load a file. +Add a Debug Sampler and a Tree View Listener and changes to the regular expression can be tested quickly, +without needing to access any external servers. + +

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/remote-test.html b/ApacheJmeter/docs/usermanual/remote-test.html new file mode 100644 index 0000000..7d585e6 --- /dev/null +++ b/ApacheJmeter/docs/usermanual/remote-test.html @@ -0,0 +1,817 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Remote (Distributed) Testing + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +15. Remote Testing +
+
+

+In the event that your JMeter client machine is unable, performance-wise, to simulate +enough users to stress your server, an option exists to control multiple, remote JMeter +engines from a single JMeter GUI client. By running JMeter remotely, you can replicate +a test across many low-end computers and thus simulate a larger load on the server. One +instance of the JMeter GUI client can control any number of remote JMeter instances, and collect +all the data from them. This offers the following features: + + + +

    + + +
  • +Saving of test samples to the local machine +
  • + + +
  • +Managment of multiple JMeterEngines from a single machine +
  • + + +
  • +No need to copy the test plan to each server - the client sends it to all the servers +
  • + + +
+ + +

+

+ + +
+Note: The same test plan is run by all the servers. +JMeter does not distribute the load between servers, each runs the full test plan. + +
+

+

+ +However, remote mode does use more resources than running the same number of non-GUI tests independently. +If many server instances are used, the client JMeter can become overloaded, as can the client network connection. + +

+

+Note that while you can execute the JMeterEngine on your application +server, you need to be mindful of the fact that this will be adding processing +overhead on the application server and thus your testing results will be +somewhat tainted. The recommended approach is to have one or more machines on +the same Ethernet segment as your application server that you configure to run +the JMeter Engine. This will minimize the impact of the network on the test +results without impacting the performance of the application serer +itself. + +

+

+ +Step 0: Configure the nodes + +

+

+ +Make sure that all the nodes (client and servers) are running exactly the same version of JMeter. +As far as possible, also use the same version of Java on all systems. +Using different versions of Java may work - but is best avoided. + +

+

+ +If the test uses any data files, note that these are not sent across by the client so +make sure that these are available in the appropriate directory on each server. +If necessary you can define different values for properties by editting the user.properties or system.properties +files on each server. These properties will be picked up when the server is started and may be +used in the test plan to affect its behaviour (e.g. connecting to a different remote server). +Alternatively use different content in any datafiles used by the test +(e.g. if each server must use unique ids, divide these between the data files) + +

+

+ +Step 1: Start the servers + +

+

+To run JMeter in remote node, start the JMeter server component on all machines you wish to run on by running the + +JMETER_HOME/bin/jmeter-server + + (unix) or + +JMETER_HOME/bin/jmeter-server.bat + + (windows) script. +

+

+Note that there can only be one JMeter server on each node unless different RMI ports are used. +

+

+Since JMeter 2.3.1, the JMeter server application starts the RMI registry itself; +there is no need to start RMI registry separately. +To revert to the previous behaviour, define the JMeter property server.rmi.create=false on the server host systems. + +

+

+ +By default, RMI uses a dynamic port for the JMeter server engine. This can cause problems for firewalls, +so versions of JMeter after 2.3.2 will check for the JMeter property + +server.rmi.localport + +. +If this is non-zero, it will be used as the local port number for the server engine. + +

+

+ +Step 2: Add the server IP to your client's Properties File + +

+

+Edit the properties file + +on the controlling JMeter machine + +. In /bin/jmeter.properties, find the property named, "remote_hosts", and +add the value of your running JMeter server's IP address. Multiple such servers can be added, comma-delimited. +

+

+Note that you can use the -R + +command line option + + +instead to specify the remote host(s) to use. This has the same effect as using -r and -Jremote_hosts={serverlist}. + E.g. jmeter -Rhost1,127.0.0.1,host2 +

+

+If you define the JMeter property server.exitaftertest=true, then the server will exit after it runs a single test. +See also the -X flag (described below) + +

+

+ +Step 3a: Start the JMeter Client from a GUI client + +

+

+Now you are ready to start the controlling JMeter client. For MS-Windows, start the client with the script "bin/jmeter.bat". For UNIX, +use the script "bin/jmeter". You will notice that the Run menu contains two new sub-menus: "Remote Start" and "Remote Stop" +(see figure 1). These menus contain the client that you set in the properties file. Use the remote start and stop instead of the +normal JMeter start and stop menu items. +

+


+Figure 1 - Run Menu +

+

+ +Step 3b: Start the JMeter from a non-GUI Client + +

+

+ +As an alternative, you can start the remote server(s) from a non-GUI (command-line) client. +The command to do this is: + +

+
+jmeter -n -t script.jmx -r
+or
+jmeter -n -t script.jmx -R server1,server2...
+
+Other flags that may be useful:
+-Gproperty=value - define a property in all the servers (may appear more than once)
+-Z - Exit remote servers at the end of the test.
+
+
+ +The first example will start whatever servers are defined in the JMeter property remote_hosts; +the second example will define remote_hosts from the list of servers and then run the remote servers. + +
+ + +The command-line client will exit when all the remote servers have stopped. + +

+ + + + +
+ +15.1 Doing it Manually + +
+
+

+In some cases, the jmeter-server script may not work for you (if you are using an OS platform not anticipated by the JMeter developers). Here is how to start the JMeter servers (step 1 above) with a more manual process: +

+

+ +Step 1a: Start the RMI Registry + +

+

+ +Since JMeter 2.3.1, the RMI registry is started by the JMeter server, so this section does not apply in the normal case. +To revert to the previous behaviour, define the JMeter property server.rmi.create=false on the server host systems +and follow the instructions below. + +

+

+JMeter uses Remote Method Invocation (RMI) as the remote communication mechanism. Therefore, you need +to run the RMI Registry application (which is named, "rmiregistry") that comes with the JDK and is located in the "bin" +directory. Before running rmiregistry, make sure that the following jars are in your system claspath: + +

    + + +
  • +JMETER_HOME/lib/ext/ApacheJMeter_core.jar +
  • + + +
  • +JMETER_HOME/lib/jorphan.jar +
  • + + +
  • +JMETER_HOME/lib/logkit-1.2.jar +
  • + + +
+ +The +rmiregistry application needs access to certain JMeter classes. Run rmiregistry with no parameters. By default the +application listens to port 1099. +

+

+ +Step 1b: Start the JMeter Server + +

+

+Once the RMI Registry application is running, start the JMeter Server. +Use the "-s" option with the jmeter startup script ("jmeter -s"). +

+

+Steps 2 and 3 remain the same. +

+
+

+ + + + +
+ +15.2 Tips + +
+
+

+ +JMeter/RMI requires a connection from the client to the server. This will use the port you chose, default 1099. +
+ + +JMeter/RMI also requires a reverse connection in order to return sample results from the server to the client. +
+ + +This will use a high-numbered port. +
+ + +This port can be controlled by jmeter property called client.rmi.localport in jmeter.properties. +
+ + +If there are any firewalls or other network filters between JMeter client and server, +you will need to make sure that they are set up to allow the connections through. +If necessary, use monitoring software to show what traffic is being generated. + +

+

+If you're running Suse Linux, these tips may help. The default installation may enable the firewall. In that case, remote testing will not work properly. The following tips were contributed by Sergey Ten. +

+

+If you see connections refused, turn on debugging by passing the following options. +

+

+Since JMeter 2.3.1, the RMI registry is started by the server; however the options can still be passed in from the JMeter command line. +For example: "jmeter -s -Dsun.rmi.loader.logLevel=verbose" (i.e. omit the -J prefixes). +Alternatively the properties can be defined in the system.properties file. + +

+

+The solution to the problem is to remove the loopbacks 127.0.0.1 and 127.0.0.2 from etc/hosts. What happens is jmeter-server can't connect to rmiregistry if 127.0.0.2 loopback is not available. Use the following settings to fix the problem. +

+

+ Replace +

+
    + + +
  • + `dirname $0`/jmeter -s "$@" +
  • + + +
+

+ With +

+
    + + +
  • + HOST="-Djava.rmi.server.hostname=[computer_name][computer_domain] +
  • + + +
  • + -Djava.security.policy=`dirname $0`/[policy_file]" +
  • + + +
  • + `dirname $0`/jmeter $HOST -s "$@" +
  • + + +
+

+Also create a policy file and add [computer_name][computer_domain] line to /etc/hosts. +

+

+In order to better support SSH-tunneling of the RMI communication channels used +in remote testing, since JMeter 2.6: +

+
    + + +
  • +a new property "client.rmi.localport" can be set to control the RMI port used by the RemoteSampleListenerImpl +
  • + + +
  • +To support tunneling RMI traffic over an SSH tunnel as the remote endpoint using a port on the local machine, + loopback interface is now allowed to be used if it has been specified directly using the Java System Property "java.rmi.server.hostname" parameter. +
  • + + +
+
+

+ + + + +
+ +15.3 Using a different port + +
+
+

+By default, JMeter uses the standard RMI port 1099. It is possible to change this. For this to work successfully, all the following need to agree: +

+
    + + +
  • +On the server, start rmiregistry using the new port number +
  • + + +
  • +On the server, start JMeter with the property server_port defined +
  • + + +
  • +On the client, update the remote_hosts property to include the new remote host:port settings +
  • + + +
+

+Since Jmeter 2.1.1, the jmeter-server scripts provide support for changing the port. +For example, assume you want to use port 1664 (perhaps 1099 is already used). +

+
+
+On Windows (in a DOS box)
+C:\JMETER> SET SERVER_PORT=1664
+C:\JMETER> JMETER-SERVER [other options]
+
+On Unix:
+$ SERVER_PORT=1664 jmeter-server [other options]
+[N.B. use upper case for the environment variable]
+
+
+

+ +In both cases, the script starts rmiregistry on the specified port, +and then starts JMeter in server mode, having defined the "server_port" property. + +

+

+ +The chosen port will be logged in the server jmeter.log file (rmiregistry does not create a log file). + +

+
+

+ + + + +
+ +15.4 Using a different sample sender + +
+
+

+ +Listeners in the test plan send their results back to the client JMeter which writes the results to the specified files +By default, samples are sent back synchronously as they are generated. +This can affect the maximum throughput of the server test; the sample result has to be sent back before the thread can +continue. +There are some JMeter properties that can be set to alter this behaviour. + +

+
    + + +
  • +mode - sample sending mode - default is Standard. This should be set on the client node. +
  • + + +
      + + +
    • +Standard - send samples synchronously as soon as they are generated +
    • + + +
    • +Hold - hold samples in an array until the end of a run. This may use a lot of memory on the server. +
    • + + +
    • +DiskStore - store samples in a disk file (under java.io.temp) until the end of a run. + The serialised data file is deleted on JVM exit. +
    • + + +
    • +Batch - send saved samples when either the count or time exceeds a threshold, + at which point the samples are sent synchronously. + The thresholds can be configured on the server using the following properties: + +
        + + +
      • +num_sample_threshold - number of samples to accumulate, default 100 +
      • + + +
      • +time_threshold - time threshold, default 60000 ms = 60 seconds +
      • + + +
      + + +
    • + + See also the Asynch mode, described below. + +
    • +Statistical - send a summary sample when either the count or time exceeds a threshold. + The samples are summarised by thread group name and sample label. + The following fields are accumulated: + +
        + + +
      • +elapsed time +
      • + + +
      • +latency +
      • + + +
      • +bytes +
      • + + +
      • +sample count +
      • + + +
      • +error count +
      • + + +
      + + Other fields that vary between samples are lost. + +
    • + + +
    • +Stripped - remove responseData from succesful samples +
    • + + +
    • +StrippedBatch - remove responseData from succesful samples, and use Batch sender to send them. +
    • + + +
    • +Asynch - samples are temporarily stored in a local queue. A separate worker thread sends the samples. + This allows the test thread to continue without waiting for the result to be sent back to the client. + However, if samples are being created faster than they can be sent, the queue will eventually fill up, + and the sampler thread will block until some samples can be drained from the queue. + This mode is useful for smoothing out peaks in sample generation. + The queue size can be adjusted by setting the JMeter property + + + +asynch.batch.queue.size + + + (default 100) on the server node. + +
    • + + +
    • +Custom implementation : set the mode parameter to your custom sample sender class name. + This must implement the interface SampleSender and have a constructor which takes a single + parameter of type RemoteSampleListener. + +
    • + + +
    + + +
+

+The following properties apply to the Batch and Statistical modes: +

+
    + + +
  • +num_sample_threshold - number of samples in a batch (default 100) +
  • + + +
  • +time_threshold - number of milliseconds to wait (default 60 seconds) +
  • + + +
+
+

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/docs/usermanual/test_plan.html b/ApacheJmeter/docs/usermanual/test_plan.html new file mode 100644 index 0000000..e254ddb --- /dev/null +++ b/ApacheJmeter/docs/usermanual/test_plan.html @@ -0,0 +1,1269 @@ + + + + + + + + + + + + + + + + + +Apache JMeter - User's Manual: Elements of a Test Plan + + + + + + + + + + +
+ + + + + +Apache JMeter +
+ + + + + + + + + + +
+
+
+

About

+ +

Download

+ +

Documentation

+ +

Tutorials (PDF format)

+ +

Community

+ +

Foundation

+ +
+ + + + + + +
+ + + + + +
+
+ + + + +
+ +4. Elements of a Test Plan +
+
+

+The Test Plan object has a checkbox called "Functional Testing". If selected, it +will cause JMeter to record the data returned from the server for each sample. If you have +selected a file in your test listeners, this data will be written to file. This can be useful if +you are doing a small run to ensure that JMeter is configured correctly, and that your server +is returning the expected results. The consequence is that the file will grow huge quickly, and +JMeter's performance will suffer. This option should be off if you are doing stress-testing (it +is off by default). +

+

+If you are not recording the data to file, this option makes no difference. +

+

+You can also use the Configuration button on a listener to decide what fields to save. +

+ + + + +
+ +4.1 ThreadGroup + +
+
+

+Thread group elements are the beginning points of any test plan. +All controllers and samplers must be under a thread group. +Other elements, e.g. Listeners, may be placed directly under the test plan, +in which case they will apply to all the thread groups. +As the name implies, the thread group +element controls the number of threads JMeter will use to execute your test. The +controls for a thread group allow you to: + +

    +
  • +Set the number of threads +
  • + + +
  • +Set the ramp-up period +
  • + + +
  • +Set the number of times to execute the test +
  • + + +
+

+

+Each thread will execute the test plan in its entirety and completely independently +of other test threads. Multiple threads are used to simulate concurrent connections +to your server application. +

+

+The ramp-up period tells JMeter how long to take to "ramp-up" to the full number of +threads chosen. If 10 threads are used, and the ramp-up period is 100 seconds, then +JMeter will take 100 seconds to get all 10 threads up and running. Each thread will +start 10 (100/10) seconds after the previous thread was begun. If there are 30 threads +and a ramp-up period of 120 seconds, then each successive thread will be delayed by 4 seconds. +

+

+Ramp-up needs to be long enough to avoid too large a work-load at the start +of a test, and short enough that the last threads start running before +the first ones finish (unless one wants that to happen). + +

+

+ +Start with Ramp-up = number of threads and adjust up or down as needed. + +

+

+By default, the thread group is configured to loop once through its elements. +

+

+Version 1.9 introduces a test run + +scheduler + +. + Click the checkbox at the bottom of the Thread Group panel to reveal extra fields + in which you can enter the start and end times of the run. + When the test is started, JMeter will wait if necessary until the start-time has been reached. + At the end of each cycle, JMeter checks if the end-time has been reached, and if so, the run is stopped, + otherwise the test is allowed to continue until the iteration limit is reached. +

+

+Alternatively, one can use the relative delay and duration fields. + Note that delay overrides start-time, and duration over-rides end-time. +

+
+

+ + + + +
+ +4.2 Controllers + +
+
+

+ +JMeter has two types of Controllers: Samplers and Logical Controllers. +These drive the processing of a test. + +

+

+Samplers tell JMeter to send requests to a server. For +example, add an HTTP Request Sampler if you want JMeter +to send an HTTP request. You can also customize a request by adding one +or more Configuration Elements to a Sampler. For more +information, see + + +Samplers + +. +

+

+Logical Controllers let you customize the logic that JMeter uses to +decide when to send requests. For example, you can add an Interleave +Logic Controller to alternate between two HTTP Request Samplers. +For more information, see + +Logical Controllers + +. +

+
+

+ + + + +
+ +4.2.1 Samplers + +
+
+

+ +Samplers tell JMeter to send requests to a server and wait for a response. +They are processed in the order they appear in the tree. +Controllers can be used to modify the number of repetitions of a sampler. + +

+

+ +JMeter samplers include: + +

    + + +
  • +FTP Request +
  • + + +
  • +HTTP Request +
  • + + +
  • +JDBC Request +
  • + + +
  • +Java object request +
  • + + +
  • +LDAP Request +
  • + + +
  • +SOAP/XML-RPC Request +
  • + + +
  • +WebService (SOAP) Request +
  • + + +
+ +Each sampler has several properties you can set. +You can further customize a sampler by adding one or more Configuration Elements to the Test Plan. + +

+

+If you are going to send multiple requests of the same type (for example, +HTTP Request) to the same server, consider using a Defaults Configuration +Element. Each controller has one or more Defaults elements (see below). +

+

+Remember to add a Listener to your test plan to view and/or store the +results of your requests to disk. +

+

+If you are interested in having JMeter perform basic validation on +the response of your request, add an + +Assertion + + to +the sampler. For example, in stress testing a web application, the server +may return a successful "HTTP Response" code, but the page may have errors on it or +may be missing sections. You could add assertions to check for certain HTML tags, +common error strings, and so on. JMeter lets you create these assertions using regular +expressions. +

+

+ +JMeter's built-in samplers + +

+
+

+ + + + +
+ +4.2.2 Logic Controllers + +
+
+

+Logic Controllers let you customize the logic that JMeter uses to +decide when to send requests. +Logic Controllers can change the order of requests coming from their +child elements. They can modify the requests themselves, cause JMeter to repeat +requests, etc. + +

+

+To understand the effect of Logic Controllers on a test plan, consider the +following test tree: +

+

+ + +

    + + +
  • +Test Plan +
  • + + +
      + + +
    • +Thread Group +
    • + + +
        + + +
      • +Once Only Controller +
      • + + + + + +
      • +Load Search Page (HTTP Sampler) +
      • + + +
      • +Interleave Controller +
      • + + +
          + + +
        • +Search "A" (HTTP Sampler) +
        • + + +
        • +Search "B" (HTTP Sampler) +
        • + + +
        • +HTTP default request (Configuration Element) +
        • + + +
        + + +
      • +HTTP default request (Configuration Element) +
      • + + +
      • +Cookie Manager (Configuration Element) +
      • + + +
      + + +
    + + +
+ + +

+

+The first thing about this test is that the login request will be executed only +the first time through. Subsequent iterations will skip it. This is due to the +effects of the +Once Only Controller +. +

+

+After the login, the next Sampler loads the search page (imagine a +web application where the user logs in, and then goes to a search page to do a search). This +is just a simple request, not filtered through any Logic Controller. +

+

+After loading the search page, we want to do a search. Actually, we want to do +two different searches. However, we want to re-load the search page itself between +each search. We could do this by having 4 simple HTTP request elements (load search, +search "A", load search, search "B"). Instead, we use the +Interleave Controller + which passes on one child request each time through the test. It keeps the +ordering (ie - it doesn't pass one on at random, but "remembers" its place) of its +child elements. Interleaving 2 child requests may be overkill, but there could easily have +been 8, or 20 child requests. +

+

+Note the +HTTP Request Defaults + that +belongs to the Interleave Controller. Imagine that "Search A" and "Search B" share +the same PATH info (an HTTP request specification includes domain, port, method, protocol, +path, and arguments, plus other optional items). This makes sense - both are search requests, + hitting the same back-end search engine (a servlet or cgi-script, let's say). Rather than + configure both HTTP Samplers with the same information in their PATH field, we + can abstract that information out to a single Configuration Element. When the Interleave + Controller "passes on" requests from "Search A" or "Search B", it will fill in the blanks with + values from the HTTP default request Configuration Element. So, we leave the PATH field + blank for those requests, and put that information into the Configuration Element. In this +case, this is a minor benefit at best, but it demonstrates the feature. +

+

+The next element in the tree is another HTTP default request, this time added to the +Thread Group itself. The Thread Group has a built-in Logic Controller, and thus, it uses +this Configuration Element exactly as described above. It fills in the blanks of any +Request that passes through. It is extremely useful in web testing to leave the DOMAIN +field blank in all your HTTP Sampler elements, and instead, put that information +into an HTTP default request element, added to the Thread Group. By doing so, you can +test your application on a different server simply by changing one field in your Test Plan. +Otherwise, you'd have to edit each and every Sampler. +

+

+The last element is a +HTTP Cookie Manager +. A Cookie Manager should be added to all web tests - otherwise JMeter will +ignore cookies. By adding it at the Thread Group level, we ensure that all HTTP requests +will share the same cookies. +

+

+Logic Controllers can be combined to achieve various results. See the list of + +built-in +Logic Controllers + +. +

+
+

+ + + + +
+ +4.2.3 Test Fragments + +
+
+

+The Test Fragment element is a special type of + +controller + + that +exists on the Test Plan tree at the same level as the Thread Group element. It is distinguished +from a Thread Group in that it is not executed unless it is +referenced by either a +Module Controller + or an +Include_Controller +. + +

+

+This element is purely for code re-use within Test Plans and was introduced in Version 2.5 +

+
+

+ + + + +
+ +4.3 Listeners + +
+
+

+Listeners provide access to the information JMeter gathers about the test cases while +JMeter runs. The +Graph Results + listener plots the response times on a graph. +The "View Results Tree" Listener shows details of sampler requests and responses, and can display basic HTML and XML representations of the response. +Other listeners provide summary or aggregation information. + +

+

+ +Additionally, listeners can direct the data to a file for later use. +Every listener in JMeter provides a field to indicate the file to store data to. +There is also a Configuration button which can be used to choose which fields to save, and whether to use CSV or XML format. + + +Note that all Listeners save the same data; the only difference is in the way the data is presented on the screen. + + + +

+

+ +Listeners can be added anywhere in the test, including directly under the test plan. +They will collect data only from elements at or below their level. + +

+

+There are several + +listeners + + +that come with JMeter. +

+
+

+ + + + +
+ +4.4 Timers + +
+
+

+By default, a JMeter thread sends requests without pausing between each request. +We recommend that you specify a delay by adding one of the available timers to +your Thread Group. If you do not add a delay, JMeter could overwhelm your server by +making too many requests in a very short amount of time. +

+

+The timer will cause JMeter to delay a certain amount of time + +before + + each +sampler which is in its + +scope + +. +

+

+ +If you choose to add more than one timer to a Thread Group, JMeter takes the sum of +the timers and pauses for that amount of time before executing the samplers to which the timers apply. +Timers can be added as children of samplers or controllers in order to restrict the samplers to which they are applied. + +

+

+ +To provide a pause at a single place in a test plan, one can use the +Test Action + Sampler. + +

+
+

+ + + + +
+ +4.5 Assertions + +
+
+

+Assertions allow you to assert facts about responses received from the +server being tested. Using an assertion, you can essentially "test" that your +application is returning the results you expect it to. +

+

+For instance, you can assert that the response to a query will contain some +particular text. The text you specify can be a Perl-style regular expression, and +you can indicate that the response is to contain the text, or that it should match +the whole response. +

+

+You can add an assertion to any Sampler. For example, you can +add an assertion to a HTTP Request that checks for the text, "</HTML>". JMeter +will then check that the text is present in the HTTP response. If JMeter cannot find the +text, then it will mark this as a failed request. +

+

+ +Note that assertions apply to all samplers which are in its + +scope + +. +To restrict the assertion to a single sampler, add the assertion as a child of the sampler. + +

+

+To view the assertion results, add an Assertion Listener to the Thread Group. +Failed Assertions will also show up in the Tree View and Table Listeners, +and will count towards the error %age for example in the Aggregate and Summary reports. + +

+
+

+ + + + +
+ +4.6 Configuration Elements + +
+
+

+A configuration element works closely with a Sampler. Although it does not send requests +(except for +HTTP Proxy Server +), it can add to or modify requests. +

+

+A configuration element is accessible from only inside the tree branch where you place the element. +For example, if you place an HTTP Cookie Manager inside a Simple Logic Controller, the Cookie Manager will +only be accessible to HTTP Request Controllers you place inside the Simple Logic Controller (see figure 1). +The Cookie Manager is accessible to the HTTP requests "Web Page 1" and "Web Page 2", but not "Web Page 3". +

+

+Also, a configuration element inside a tree branch has higher precedence than the same element in a "parent" +branch. For example, we defined two HTTP Request Defaults elements, "Web Defaults 1" and "Web Defaults 2". +Since we placed "Web Defaults 1" inside a Loop Controller, only "Web Page 2" can access it. The other HTTP +requests will use "Web Defaults 2", since we placed it in the Thread Group (the "parent" of all other branches). +

+


+Figure 1 - + Test Plan Showing Accessability of Configuration Elements +

+

+ + +
+The +User Defined Variables + Configuration element is different. +It is processed at the start of a test, no matter where it is placed. +For simplicity, it is suggested that the element is placed only at the start of a Thread Group. + +
+

+
+

+ + + + +
+ +4.7 Pre-Processor Elements + +
+
+

+A Pre-Processor executes some action prior to a Sampler Request being made. +If a Pre-Processor is attached to a Sampler element, then it will execute just prior to that sampler element running. +A Pre-Processor is most often used to modify the settings of a Sample Request just before it runs, or to update variables that aren't extracted from response text. +See the + + +scoping rules + + + for more details on when Pre-Processors are executed. +

+
+

+ + + + +
+ +4.8 Post-Processor Elements + +
+
+

+A Post-Processor executes some action after a Sampler Request has been made. +If a Post-Processor is attached to a Sampler element, then it will execute just after that sampler element runs. +A Post-Processor is most often used to process the response data, often to extract values from it. +See the + + +scoping rules + + + for more details on when Post-Processors are executed. +

+
+

+ + + + +
+ +4.9 Execution order + +
+
+
    + + +
  1. +Configuration elements +
  2. + + +
  3. +Pre-Processors +
  4. + + +
  5. +Timers +
  6. + + +
  7. +Sampler +
  8. + + +
  9. +Post-Processors (unless SampleResult is null) +
  10. + + +
  11. +Assertions (unless SampleResult is null) +
  12. + + +
  13. +Listeners (unless SampleResult is null) +
  14. + + +
+

+ + +
+Please note that Timers, Assertions, Pre- and Post-Processors are only processed if there is a sampler to which they apply. +Logic Controllers and Samplers are processed in the order in which they appear in the tree. +Other test elements are processed according to the scope in which they are found, and the type of test element. +[Within a type, elements are processed in the order in which they appear in the tree]. + +
+

+

+ +For example, in the following test plan: + +

    + + +
  • +Controller +
  • + + +
      + + +
    • +Post-Processor 1 +
    • + + +
    • +Sampler 1 +
    • + + +
    • +Sampler 2 +
    • + + +
    • +Timer 1 +
    • + + +
    • +Assertion 1 +
    • + + +
    • +Pre-Processor 1 +
    • + + +
    • +Timer 2 +
    • + + +
    • +Post-Processor 2 +
    • + + +
    + + +
+ +The order of execution would be: + +
+
+Pre-Processor 1
+Timer 1
+Timer 2
+Sampler 1
+Post-Processor 1
+Post-Processor 2
+Assertion 1
+
+Pre-Processor 1
+Timer 1
+Timer 2
+Sampler 2
+Post-Processor 1
+Post-Processor 2
+Assertion 1
+
+
+ + +

+
+

+ + + + +
+ +4.10 Scoping Rules + +
+
+

+ +The JMeter test tree contains elements that are both hierarchical and ordered. Some elements in the test trees are strictly hierarchical (Listeners, Config Elements, Post-Procesors, Pre-Processors, Assertions, Timers), and some are primarily ordered (controllers, samplers). When you create your test plan, you will create an ordered list of sample request (via Samplers) that represent a set of steps to be executed. These requests are often organized within controllers that are also ordered. Given the following test tree: +

+


+Example test tree +

+

+The order of requests will be, One, Two, Three, Four. +

+

+Some controllers affect the order of their subelements, and you can read about these specific controllers in + +the component reference + +. +

+

+Other elements are hierarchical. An Assertion, for instance, is hierarchical in the test tree. +If its parent is a request, then it is applied to that request. If its +parent is a Controller, then it affects all requests that are descendants of +that Controller. In the following test tree: +

+


+Hierarchy example +

+

+Assertion #1 is applied only to Request One, while Assertion #2 is applied to Requests Two and Three. +

+

+Another example, this time using Timers: +

+


+complex example +

+

+In this example, the requests are named to reflect the order in which they will be executed. Timer #1 will apply to Requests Two, Three, and Four (notice how order is irrelevant for hierarchical elements). Assertion #1 will apply only to Request Three. Timer #2 will affect all the requests. +

+

+Hopefully these examples make it clear how configuration (hierarchical) elements are applied. If you imagine each Request being passed up the tree branches, to its parent, then to its parent's parent, etc, and each time collecting all the configuration elements of that parent, then you will see how it works. +

+ + +The Configuration elements Header Manager, Cookie Manager and Authorization manager are +treated differently from the Configuration Default elements. +The settings from the Configuration Default elements are merged into a set of values that the Sampler has access to. +However, the settings from the Managers are not merged. +If more than one Manager is in the scope of a Sampler, +only one Manager is used, but there is currently no way to specify + +which + + is used. + + +
+

+ + + + +
+ +4.11 Properties and Variables + +
+
+

+ +JMeter + +properties + + are defined in jmeter.properties (see + +Gettting Started - Configuring JMeter + + for more details). + +
+ + +Properties are global to jmeter, and are mostly used to define some of the defaults JMeter uses. +For example the property remote_hosts defines the servers that JMeter will try to run remotely. +Properties can be referenced in test plans +- see + +Functions - read a property + + - +but cannot be used for thread-specific values. + +

+

+ +JMeter + +variables + + are local to each thread. The values may be the same for each thread, or they may be different. + +
+ + +If a variable is updated by a thread, only the thread copy of the variable is changed. +For example the +Regular Expression Extractor + Post-Processor +will set its variables according to the sample that its thread has read, and these can be used later +by the same thread. +For details of how to reference variables and functions, see + +Functions and Variables + + + +

+

+ +Note that the values defined by the +Test Plan + and the +User Defined Variables + configuration element +are made available to the whole test plan at startup. +If the same variable is defined by multiple UDV elements, then the last one takes effect. +Once a thread has started, the initial set of variables is copied to each thread. +Other elements such as the + +User Parameters + Pre-Processor or +Regular Expression Extractor + Post-Processor +may be used to redefine the same variables (or create new ones). These redefinitions only apply to the current thread. + +

+

+ +The + +setProperty + + function can be used to define a JMeter property. +These are global to the test plan, so can be used to pass information between threads - should that be needed. + +

+

+ + +
Both variables and properties are case-sensitive. +
+

+
+

+ + + + +
+ +4.12 Using Variables to parameterise tests + +
+
+

+ +Variables don't have to vary - they can be defined once, and if left alone, will not change value. +So you can use them as short-hand for expressions that appear frequently in a test plan. +Or for items which are constant during a run, but which may vary between runs. +For example, the name of a host, or the number of threads in a thread group. + +

+

+ +When deciding how to structure a Test Plan, +make a note of which items are constant for the run, but which may change between runs. +Decide on some variable names for these - +perhaps use a naming convention such as prefixing them with C_ or K_ or using uppercase only +to distinguish them from variables that need to change during the test. +Also consider which items need to be local to a thread - +for example counters or values extracted with the Regular Expression Post-Processor. +You may wish to use a different naming convention for these. + +

+

+ +For example, you might define the following on the Test Plan: + +

+
+HOST             www.example.com
+THREADS          10
+LOOPS            20
+
+
+ +You can refer to these in the test plan as ${HOST} ${THREADS} etc. +If you later want to change the host, just change the value of the HOST variable. +This works fine for small numbers of tests, but becomes tedious when testing lots of different combinations. +One solution is to use a property to define the value of the variables, for example: + +
+
+HOST             ${__P(host,www.example.com)}
+THREADS          ${__P(threads,10)}
+LOOPS            ${__P(loops,20)}
+
+
+ +You can then change some or all of the values on the command-line as follows: + +
+
+jmeter ... -Jhost=www3.example.org -Jloops=13
+
+
+ + +

+
+

+
+

+

+ + + + + + +
+ + + + + +
+
+
+
+
+
+Copyright © 1999-2012, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/eclipse.classpath b/ApacheJmeter/eclipse.classpath new file mode 100644 index 0000000..1556258 --- /dev/null +++ b/ApacheJmeter/eclipse.classpath @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/eclipse.readme b/ApacheJmeter/eclipse.readme new file mode 100644 index 0000000..c670d4c --- /dev/null +++ b/ApacheJmeter/eclipse.readme @@ -0,0 +1,105 @@ + 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. + + + +Eclipse settings +---------------- +The following files should be excluded from build output: +*.metaprops +See Preferences/Java/Building/Output Folder/Filtered Resources + + +Eclipse.classpath +----------------- +[This has been tested with Eclipse 3.2. It may not work with other versions.] + +The file eclipse.classpath is intended as a starter .classpath file +for building JMeter using Eclipse version 3. Make sure to execute +the ant download_jars task to download and install the jars referred +to in the classpath before creating the Eclipse project. + +Note that Eclipse does not handle RMI compilations, +nor is it easy to use for creating jar files. + +However, it is easy to use Eclipse to run Ant. + +The following targets may prove useful: + + compile-rmi - compiles the RMI files that Eclipse ignores + package-only - creates the jars + package - compiles everything and then packages it + run_gui - compiles, packages, and then start the JMeter GUI from the jars + +Invoking Ant targets inside Eclipse +---------------------------------- +You can use the "Run As --> Ant Build" and select target, or you can use +the "Windows->Show View->Ant View". Then select the "build.xml" file and +drag and drop to the "Ant View". +Now you can invoke targets by clicking on them. +Note that if you invoke for example the "compile" target, and get error +messages about +" +Unable to find a javac compiler; +com.sun.tools.javac.Main is not on the classpath. +Perhaps JAVA_HOME does not point to the JDK +" +it just means that your Eclipse project is set up with JRE libraries instead of JDK libraries. +The suggested fix is to add a JDK in "Window->Preferences->Java->Installed JREs". +Then do a "Project->Properties" and select "Java Build Path" in the left pane, and then +select the "Libraries" tab in the right pane. Scroll to the bottom, select the "JRE System Library", +and click "Remove". Then click "Add library..." , select "JRE System Library", and then select +the JDK. Now it should work when you invoke the "compile" target. + + +Finishing the build using Ant +----------------------------- + +Find the build.xml file in the project, +right click on it, and click "Run As --> Ant Build". + +Make sure you select the "package" target. + +This will compile any remaining classes (e.g. the RMI ones), +and then create all the jars. + +Now refresh the project (you should add this to the Ant build properties) + +Launching from Eclipse +---------------------- + +You can use the Ant target run_gui to run the JMeter GUI, or you can follow the instructions +below to add a Java Application launch, which will for example, allow you to use the debugger to +run JMeter. + +These instructions assume you have configured Eclipse to use the classpath +as suggested in eclipse.classpath, and have run "ant package" to compile +the RMI classes and build the jars. + +Create a new Java Application launch configuration. + +On the Main tab, enter the following as the main class: + + org.apache.jmeter.NewDriver + +On the Arguments tab, in the Working Directory area, pick the radio +button next to "Other" and enter the following in the text box: + + ${workspace_loc}/jmeterproject/bin + + where "jmeterproject" is the name of the JMeter project. + + [It would be nicer to use ${project_loc}/bin + but unfortunately the Eclipse Debug view does not seem to preserve any of the project variables] diff --git a/ApacheJmeter/extras/ConvertHTTPSampler.txt b/ApacheJmeter/extras/ConvertHTTPSampler.txt new file mode 100644 index 0000000..5d11605 --- /dev/null +++ b/ApacheJmeter/extras/ConvertHTTPSampler.txt @@ -0,0 +1,16 @@ +=== HTTPSampler to HTTPSampler2 convertion === + +If the testcase was created with an old version, load it into 2.1.1 and save it. +Edit the testcase and replace the following: + +Old +=== + +... + + +New +=== + +... + \ No newline at end of file diff --git a/ApacheJmeter/extras/Test.jmx b/ApacheJmeter/extras/Test.jmx new file mode 100644 index 0000000..96f1e06 --- /dev/null +++ b/ApacheJmeter/extras/Test.jmx @@ -0,0 +1,145 @@ + + + + + + + + false + false + Sample test for demonstrating JMeter Ant build script and Schematic stylesheet + + + + 1143889321000 + + + 3 + false + + 5 + false + + 1143889321000 + continue + 1 + + + + 1 + 1000 + C + false + 1000000 + + + + + + + = + 100 + Sleep_Time + + + = + 0xFF + Sleep_Mask + + + = + + Label + + + = + 200 + ResponseCode + + + = + OK + ResponseMessage + + + = + OK + Status + + + = + Request + SamplerData + + + = + Response C=${C} + ResultData + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + 3 + + Assertion.response_data + 6 + false + + + + + + + + = + 100 + Sleep_Time + + + = + 0xFF + Sleep_Mask + + + = + + Label + + + = + 200 + ResponseCode + + + = + OK + ResponseMessage + + + = + OK + Status + + + = + Request + SamplerData + + + = + Response C=${C} Tn=${__threadNum} + ResultData + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + diff --git a/ApacheJmeter/extras/addons.txt b/ApacheJmeter/extras/addons.txt new file mode 100644 index 0000000..4e39760 --- /dev/null +++ b/ApacheJmeter/extras/addons.txt @@ -0,0 +1,35 @@ +This file describes how to create local additions to JMeter. + +Create a new directory for the sources: + +cd JMETER_HOME + +mkdir addons + +Copy addons.xml into JMETER_HOME + +To build the addons, run ant as follows: + +ant -buildfile=addons.xml + +This will compile the sources to build/addons/... + +If successful, it will also create the jar file: + +JMETER_HOME/lib/ext/ApacheJmeter_addons.jar + +As the filename of this jar is alphabetically earlier, +any classes in it will be used in preference to existing JMeter classes. + +This allows the addons to be used to supply new functionality as well +as overriding existing functionality, without needing to rebuild JMeter. + +Such addons are intended mainly to be used locally - for example if you +have developed any new code that is only relevant to your organisation. + +It can also be useful for developing general purpose add-ons that are +intended for general release. Once tested, these can be moved into one of +the normal JMeter source directories. + +N.B. The build file assumes that JMeter has been built separately, as +JMeter classes are resolved from jars in the lib and lib/ext directories. diff --git a/ApacheJmeter/extras/addons.xml b/ApacheJmeter/extras/addons.xml new file mode 100644 index 0000000..85e714a --- /dev/null +++ b/ApacheJmeter/extras/addons.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApacheJmeter/extras/build.xml b/ApacheJmeter/extras/build.xml new file mode 100644 index 0000000..f71c60f --- /dev/null +++ b/ApacheJmeter/extras/build.xml @@ -0,0 +1,167 @@ + + + + + + Sample build file for use with ant-jmeter.jar + See http://www.programmerplanet.org/pages/projects/jmeter-ant-task.php + + To run a test and create the output report: + ant -Dtest=script + + To run a test only: + ant -Dtest=script run + + To run report on existing test output + ant -Dtest=script report + + The "script" parameter is the name of the script without the .jmx suffix. + + Additional options: + -Dshow-data=y - include response data in Failure Details + -Dtestpath=xyz - path to test file(s) (default user.dir). + N.B. Ant interprets relative paths against the build file + -Djmeter.home=.. - path to JMeter home directory (defaults to parent of this build file) + -Dreport.title="My Report" - title for html report (default is 'Load Test Results') + + Deprecated: + -Dformat=2.0 - use version 2.0 JTL files rather than 2.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + funcMode = ${funcMode} + + + + + + + + + + + + + + + + + + + + + + Report generated at ${report.datestamp} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cannot find all xalan and/or serialiser jars + The XSLT formatting may not work correctly. + Check you have xalan and serializer jars in ${lib.dir} + + + + diff --git a/ApacheJmeter/extras/convertjmx.fdl b/ApacheJmeter/extras/convertjmx.fdl new file mode 100644 index 0000000..f17cfc3 --- /dev/null +++ b/ApacheJmeter/extras/convertjmx.fdl @@ -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. +! +! Convert JMX files so Java can read them on OpenVMS +! ================================================== +! +! This will be needed if the JMX is in VARIABLE format. +! +! Usage: +! CONVERT/FDL=CONVERTJMX input.jmx output.jmx +! +! +RECORD + CARRIAGE_CONTROL carriage_return + FORMAT stream_lf \ No newline at end of file diff --git a/ApacheJmeter/extras/execcode.bsh b/ApacheJmeter/extras/execcode.bsh new file mode 100644 index 0000000..3b0ab3b --- /dev/null +++ b/ApacheJmeter/extras/execcode.bsh @@ -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. + * + */ + +/** + Start an external application using the Java Runtime exec() method. + Display any output to the standard BeanShell output using print(). + Return the process exit code. + Note: does not display stderr. +*/ + +bsh.help.execcode = "usage: execcode( String arg )"; + +int execcode( String arg ) +{ + this.proc = Runtime.getRuntime().exec(arg); + this.din = new DataInputStream( proc.getInputStream() ); + while( (line=din.readLine()) != null ) { + print(line); + } + return this.proc.waitFor(); +} diff --git a/ApacheJmeter/extras/jmeter-results-detail-report.xsl b/ApacheJmeter/extras/jmeter-results-detail-report.xsl new file mode 100644 index 0000000..52e676f --- /dev/null +++ b/ApacheJmeter/extras/jmeter-results-detail-report.xsl @@ -0,0 +1,407 @@ + + + + + + + + + + + + + Load Test Results + + + + + + + + +
+ + +
+ + + + + +
+ + +

Load Test Results

+ + + + + +
Designed for use with JMeter and Ant.
+
+
+ + +

Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + +
TestsFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + +
+
+ + +

Pages

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + + + + + + page_details_ + + + + +
URLTestsFailuresSuccess RateAverage TimeMin TimeMax Time
+ + # + + + + + + + + + + + + + + + + + + + + + + + + + + + + + javascript:change('page_details_') + expand/collapsepage_details__image + +
+
+ Details for Page "" + + + + + + + + + + + + + + + + + + +
ThreadIterationTimeSuccess
ms
+
+
+
+ + + + + +

Failure Detail

+ + + + + + +

+ + + + + + + + + + + + + + + + + + + + +
ResponseFailure MessageResponse Data
-
+
+ +
+
+
+ + + + + NaN + + + + + + + + + + + + + + + NaN + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/ApacheJmeter/extras/jmeter-results-detail-report_21.xsl b/ApacheJmeter/extras/jmeter-results-detail-report_21.xsl new file mode 100644 index 0000000..8a77a95 --- /dev/null +++ b/ApacheJmeter/extras/jmeter-results-detail-report_21.xsl @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + <xsl:value-of select="$titleReport" /> + + + + + + + + +
+ + +
+ + + + + +
+ + +

+ + + + + +
Date report: Designed for use with JMeter and Ant.
+
+
+ + +

Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + +
# SamplesFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + +
+
+ + +

Pages

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + + + + + + page_details_ + + + + +
URL# SamplesFailuresSuccess RateAverage TimeMin TimeMax Time
+ + # + + + + + + + + + + + + + + + + + + + + + + + + + + + + + javascript:change('page_details_') + expand/collapsepage_details__image + +
+
+ Details for Page "" + + + + + + + + + + + + + + + + + + + + +
ThreadIterationTime (milliseconds)BytesSuccess
+
+
+
+ + + + + +

Failure Detail

+ + + + + + +

+ + + + + + + + + + + + + + + + + + + + +
ResponseFailure MessageResponse Data
-
+
+ +
+
+
+ + + + + NaN + + + + + + + + + + + + + + + NaN + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ApacheJmeter/extras/jmeter-results-report.xsl b/ApacheJmeter/extras/jmeter-results-report.xsl new file mode 100644 index 0000000..7fc9976 --- /dev/null +++ b/ApacheJmeter/extras/jmeter-results-report.xsl @@ -0,0 +1,291 @@ + + + + + + + + + + Load Test Results + + + + + + + +
+ + +
+ + + + + +
+ + +

Load Test Results

+ + + + + +
Designed for use with JMeter and Ant.
+
+
+ + +

Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + +
TestsFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + +
+
+ + +

Pages

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + + + +
URLTestsFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +

Failure Detail

+ + + + + + +

+ + + + + + + + + + + + + + +
ResponseFailure Message
-
+
+ +
+
+
+ + + + + NaN + + + + + + + + + + + + + + + NaN + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/ApacheJmeter/extras/jmeter-results-report_21.xsl b/ApacheJmeter/extras/jmeter-results-report_21.xsl new file mode 100644 index 0000000..7b6cda2 --- /dev/null +++ b/ApacheJmeter/extras/jmeter-results-report_21.xsl @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + <xsl:value-of select="$titleReport" /> + + + + + + + +
+ + +
+ + + + + +
+ + +

+ + + + + +
Date report: Designed for use with JMeter and Ant.
+
+
+ + +

Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + +
# SamplesFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + +
+
+ + +

Pages

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + + + + + + + + + + + +
URL# SamplesFailuresSuccess RateAverage TimeMin TimeMax Time
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +

Failure Detail

+ + + + + + +

+ + + + + + + + + + + + + + +
ResponseFailure Message
-
+
+ +
+
+
+ + + + + NaN + + + + + + + + + + + + + + + NaN + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/ApacheJmeter/extras/jmeter.fb b/ApacheJmeter/extras/jmeter.fb new file mode 100644 index 0000000..161f2fb --- /dev/null +++ b/ApacheJmeter/extras/jmeter.fb @@ -0,0 +1,62 @@ +[Jar files] +bin/apachejmeter.jar +lib/ext/apachejmeter_components.jar +lib/ext/apachejmeter_core.jar +lib/ext/apachejmeter_ftp.jar +lib/ext/apachejmeter_functions.jar +lib/ext/apachejmeter_http.jar +lib/ext/apachejmeter_java.jar +lib/ext/apachejmeter_jdbc.jar +lib/ext/apachejmeter_ldap.jar +lib/ext/apachejmeter_mail.jar +lib/ext/apachejmeter_monitors.jar +lib/ext/apachejmeter_tcp.jar +lib/jorphan.jar +[Source dirs] +src/components +src/core +src/examples +src/functions +src/htmlparser +src/jorphan +src/monitor/components +src/monitor/model +src/protocol/ftp +src/protocol/html +src/protocol/java +src/protocol/jdbc +src/protocol/ldap +src/protocol/mail +src/protocol/tcp +[Aux classpath entries] +lib/avalon-framework-4.1.4.jar +lib/batik-awt-util.jar +lib/commons-collections.jar +lib/commons-httpclient-2.0.jar +lib/commons-logging.jar +lib/excalibur-compatibility-1.1.jar +lib/excalibur-datasource-1.1.1.jar +lib/excalibur-i18n-1.1.jar +lib/excalibur-instrument-1.0.jar +lib/excalibur-logger-1.1.jar +lib/excalibur-pool-1.2.jar +lib/htmlparser.jar +lib/jakarta-oro-2.0.8.jar +lib/jdom-b9.jar +lib/js.jar +lib/junit.jar +lib/logkit-1.2.jar +lib/soap.jar +lib/tidy.jar +lib/velocity-1.4-dev.jar +lib/xalan.jar +lib/xercesimpl.jar +lib/xml-apis.jar +lib/xpp3-1.1.3.4.d.jar +lib/xstream-1.0.1.jar +lib/opt/activation.jar +lib/opt/bsf.jar +lib/opt/bsh-2.0b1.jar +lib/opt/mail.jar +[Options] +relative_paths=true diff --git a/ApacheJmeter/extras/printvars.bsh b/ApacheJmeter/extras/printvars.bsh new file mode 100644 index 0000000..ccfc8c2 --- /dev/null +++ b/ApacheJmeter/extras/printvars.bsh @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// Sample script to print JMeter variables +print(">>>>"); +Iterator i = vars.getIterator(); +while(i.hasNext()) +{ + Map.Entry me = i.next(); + if(String.class.equals(me.getValue().getClass())){ + print(me); + } +} +print("<<<<"); diff --git a/ApacheJmeter/extras/proxycert.cmd b/ApacheJmeter/extras/proxycert.cmd new file mode 100644 index 0000000..47de5ef --- /dev/null +++ b/ApacheJmeter/extras/proxycert.cmd @@ -0,0 +1,27 @@ +@echo off + + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem Generate proxyserver certificate for JMeter + +set DNAME="cn=JMeter Proxy, ou=JMeter, o=Apache Software Foundation, c=US" + +rem generate the keystore with the certificate +keytool -genkey -alias jmeter -keystore proxyserver.jks -keypass password -storepass password -validity 1825 -keyalg RSA -dname %DNAME% + +rem show the contents +keytool -list -v -keystore proxyserver.jks -storepass password diff --git a/ApacheJmeter/extras/proxycert.sh b/ApacheJmeter/extras/proxycert.sh new file mode 100644 index 0000000..d3053fd --- /dev/null +++ b/ApacheJmeter/extras/proxycert.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +## 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. + + +## Generate proxyserver certificate for JMeter + +DNAME="cn=JMeter Proxy, ou=JMeter, o=Apache Software Foundation, c=US" + +## generate the keystore with the certificate +keytool -genkey -alias jmeter -keystore proxyserver.jks -keypass password -storepass password -validity 1825 -keyalg RSA -dname ${DNAME} + +## show the contents +keytool -list -v -keystore proxyserver.jks -storepass password diff --git a/ApacheJmeter/extras/remote.bsh b/ApacheJmeter/extras/remote.bsh new file mode 100644 index 0000000..e088e8e --- /dev/null +++ b/ApacheJmeter/extras/remote.bsh @@ -0,0 +1,48 @@ +/* + * 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. + * + */ + +// remote.bsh +// Sample remote file for use with bshclient +// +// Usage: +// java -jar ../lib/bshclent.jar localhost 9000 ../extras/bsh.remote [arg1 arg2 ...] +// Note: port 9000 is specified, but the jar actually uses 9001 (telnet) +// + +print("remote.bsh starting"); + +if (args.length > 0){ +print("Arguments:"); +print(args); +} + +printsysprop("user.home"); +printsysprop("user.dir"); + +printprop("log_level.jmeter"); +printprop("log_level.jorphan"); + +// loglevel("DEBUG","jmeter"); + +for(i=0;i<10;i++){ + setprop("EXAMPLE",i.toString()); + Thread.sleep(1000); +} +printprop("EXAMPLE"); + +print("remote.bsh ended"); \ No newline at end of file diff --git a/ApacheJmeter/extras/schematic.cmd b/ApacheJmeter/extras/schematic.cmd new file mode 100644 index 0000000..d796a17 --- /dev/null +++ b/ApacheJmeter/extras/schematic.cmd @@ -0,0 +1,24 @@ +@echo off + +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed with +rem this work for additional information regarding copyright ownership. +rem The ASF licenses this file to You under the Apache License, Version 2.0 +rem (the "License"); you may not use this file except in compliance with +rem the License. You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem Drop a JMX file on this script to create a schematic of the test plan as an HTML file + +cd /d %~dp0 +set name=%~n1 +if .%1 ==. set name=Test +call ant -f schematic.xml -Dtest=%name% +pause \ No newline at end of file diff --git a/ApacheJmeter/extras/schematic.xml b/ApacheJmeter/extras/schematic.xml new file mode 100644 index 0000000..d45a961 --- /dev/null +++ b/ApacheJmeter/extras/schematic.xml @@ -0,0 +1,39 @@ + + + + + To create the schematic report: + ant -Dtest=script + + + + + + + + + + + + + + diff --git a/ApacheJmeter/extras/schematic.xsl b/ApacheJmeter/extras/schematic.xsl new file mode 100644 index 0000000..81c1b01 --- /dev/null +++ b/ApacheJmeter/extras/schematic.xsl @@ -0,0 +1,119 @@ + + + + + + + + Test Plan Schematic + + + + + + + +
    + +
+
+ + + + + +
+ + + +
+
+ + + + +
+ Threads: + + Loops: + + Ramp up: + +
+ + + + +
+ + + + :// + + : + + / + +
+ + + + + +
+ Output: + XML: +
+
+ + + + + + + + +
+ + + +
+
+ + + +( + + + + + SimpleController + + + + + + : + +) + + + +
\ No newline at end of file diff --git a/ApacheJmeter/extras/startup.bsh b/ApacheJmeter/extras/startup.bsh new file mode 100644 index 0000000..48ea775 --- /dev/null +++ b/ApacheJmeter/extras/startup.bsh @@ -0,0 +1,99 @@ +/* + * 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. + * + */ + +// +// Sample BeanShell Server Startup file +// +// Use as follows: +// -Jbeanshell.server.port=nnnn +// -Jbeanshell.server.file=../extras/startup.bsh +// +// Defines various utility routines for properties and logging +// +// + +// Stop exit() from calling System.exit(); +bsh.system.shutdownOnExit = false; + +print("Startup script running"); + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; + +getprop(p){// get a JMeter property +return JMeterUtils.getPropDefault(p,""); +} + +setprop(p,v){// set a JMeter property +print("Setting property '"+p+"' to '"+v+"'."); +JMeterUtils.getJMeterProperties().setProperty(p, v); +} + +printprop(p){// print a JMeter property +print(p + " = " + getprop(p)); +} + +loglevel(String priority, String category){ +LoggingManager.setPriority(priority, category); +} + +logdebug(String text){ +loglevel("DEBUG",text); +} + +loginfo(String text){ +loglevel("INFO",text); +} + +// Define routines to stop the test or a thread +stopEngine(){// Stop the JMeter test +print("Stop Engine called"); +org.apache.jmeter.engine.StandardJMeterEngine.stopEngine(); +} + +stopEngineNow(){// Stop the JMeter test now +print("Stop Engine NOW called"); +org.apache.jmeter.engine.StandardJMeterEngine.stopEngineNow(); +} + +stopThread(t){// Stop a JMeter thread +print("Stop Thread "+t+" called"); +ok=org.apache.jmeter.engine.StandardJMeterEngine.stopThread(t); +if (ok){print("Thread requested to stop");} else { print("Thread not found");} +} + +stopThreadNow(t){// Stop a JMeter thread +print("Stop Thread Now "+t+" called"); +ok=org.apache.jmeter.engine.StandardJMeterEngine.stopThreadNow(t); +if (ok){print("Thread stopped");} else { print("Thread not found");} +} + +getsysprop(p){// get a system property +return System.getProperty(p,""); +} + +setsysprop(p,v){// set a system property +print("Setting property '"+p+"' to '"+v+"'."); +System.setProperty(p, v); +} + +printsysprop(p){// print a system property +print(p + " = " + getsysprop(p)); +} + +print("Startup script completed"); \ No newline at end of file diff --git a/ApacheJmeter/fb-csv.xsl b/ApacheJmeter/fb-csv.xsl new file mode 100644 index 0000000..358c528 --- /dev/null +++ b/ApacheJmeter/fb-csv.xsl @@ -0,0 +1,59 @@ + + + + + + + + + Priority,Type,Classname,Method,Field,SourceLine + + + + , + + + , + + + + + , + + + + + + , + + + + + , + + + + (start:) + + + + + + + + diff --git a/ApacheJmeter/fb-excludes.xml b/ApacheJmeter/fb-excludes.xml new file mode 100644 index 0000000..abfb4bf --- /dev/null +++ b/ApacheJmeter/fb-excludes.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --> + diff --git a/ApacheJmeter/lib/aareadme.txt b/ApacheJmeter/lib/aareadme.txt new file mode 100644 index 0000000..c35155c --- /dev/null +++ b/ApacheJmeter/lib/aareadme.txt @@ -0,0 +1,194 @@ +Directories +=========== +lib - utility jars +lib/api - Directory where API spec libraries live. +lib/doc - jars needed for generating documentation. Not included with JMeter releases. +lib/ext - JMeter jars only +lib/junit - test jar for JUnit sampler +lib/opt - Directory where Optional 3rd party libraries live +lib/src - storage area for source and javadoc jars, e.g. for use in IDEs + Excluded from SVN, not included in classpath + +Which jars are used by which modules? +==================================== +[not exhaustive] + +avalon-framework-4.1.4 (org.apache.avalon.framework) +---------------------- +- LogKit (LoggingManager) +- Configuration (DataSourceElement) +- OldSaveService + +bsf-2.4.0.jar (org.apache.bsf) +------------- +http://jakarta.apache.org/site/downloads/downloads_bsf.cgi +- BSF test elements (sampler etc.) + +bsh-2.0b5.jar (org.bsh) +------------- +- BeanShell test elements + +commons-codec-1.6 +----------------- +http://commons.apache.org/downloads/download_codec.cgi +- used by commons-httpclient-3.1 +- also HtmlParserTester for Base64 + +commons-collections-3.2.1 +------------------------- +http://commons.apache.org/downloads/download_collections.cgi +- ListenerNotifier +- Anakia + +commons-httpclient-3.1 +---------------------- +http://hc.apache.org/downloads.cgi +- httpclient version of HTTP sampler +- Cookie manager implementation + +commons-io-2.2 +-------------- +http://commons.apache.org/downloads/download_io.cgi +- FTPSampler + +commons-jexl-1.1 +---------------- +http://commons.apache.org/downloads/download_jexl.cgi +- Jexl function and BSF test elements + +commons-lang-2.6 +---------------- +http://commons.apache.org/downloads/download_lang.cgi +- velocity (Anakia) +- URLCollection (unescapeXml) + +commons-logging-1.1.1 +--------------------- +http://commons.apache.org/downloads/download_logging.cgi +- httpclient + +commons-net-3.1 +----------------- +http://commons.apache.org/downloads/download_net.cgi +- FTPSampler + +excalibur-datasource-1.1.1 (org.apache.avalon.excalibur.datasource) +-------------------------- +- DataSourceElement (JDBC) + +excalibur-instrument-1.0 (org.apache.excalibur.instrument) +------------------------ +- used by excalibur-datasource + +excalibur-logger-1.1 (org.apache.avalon.excalibur.logger) +-------------------- +- LoggingManager + +excalibur-pool-1.2 (org.apache.avalon.excalibur.pool) +------------------ +- used by excalibur-datasource + +htmlparser-2.1 +htmllexer-2.1 +---------------------- +http://htmlparser.sourceforge.net/ +- http: parsing html + +jCharts-0.7.5 (org.jCharts) +------------- +http://jcharts.sourceforge.net/downloads.html +- AxisGraph,LineGraph,LineChart + +jdom-1.1.2 +-------- +http://www.jdom.org/downloads/index.html +- XMLAssertion, JMeterTest ONLY +- Anakia + +rhino-1.7R3 +-------- +http://www.mozilla.org/rhino/download.html +- javascript function +- BSF (Javascript) + +jTidy-r938 +---- +- http: various modules for parsing html +- org.xml.sax - various +- XPathUtil (XPath assertion) + +junit 4.10 +----------- +- unit tests, JUnit sampler + +HttpComponents (HttpComponents Core 4.x and HttpComponents Client 4.x) +----------- +http://hc.apache.org/ +- httpclient 4 implementation for HTTP sampler + +logkit-2.0 +---------- +- logging +- Anakia + +oro-2.0.8 +--------- +http://jakarta.apache.org/site/downloads/downloads_oro.cgi +- regular expressions: various + +serialiser-2.7.1 +---------------- +http://www.apache.org/dyn/closer.cgi/xml/xalan-j +- xalan + +soap-2.3.1 +---------- +- WebServiceSampler ONLY + +velocity-1.7 +-------------- +http://velocity.apache.org/download.cgi +- Anakia (create documentation) Not used by JMeter runtime + +xalan_2.7.1 +----------- +http://www.apache.org/dyn/closer.cgi/xml/xalan-j ++org.apache.xalan|xml|xpath + +xercesimpl-2.9.1 +---------------- +http://xerces.apache.org/xerces2-j/download.cgi ++org.apache.html.dom|org.apache.wml|org.apache.xerces|org.apache.xml.serialize ++org.w3c.dom.html|ls + +xml-apis-1.3.04 +-------------- +http://xerces.apache.org/xerces2-j/download.cgi ++javax.xml ++org.w3c.dom ++org.xml.sax + +The x* jars above are used for XML handling + +xmlgraphics-commons-1.3.1 (org.apache.xmlgraphics.image.codec) +------------------ +http://xmlgraphics.apache.org/commons/download.html +- SaveGraphicsService + +xmlpull-1.1.3.1 +--------------- +http://www.xmlpull.org/impls.shtml +- xstream + + +xpp3_min-1.1.4c +--------------- +http://xstream.codehaus.org/download.html +or +http://www.extreme.indiana.edu/dist/java-repository/xpp3/distributions/ +- xstream + +xstream-1.4.2 +------------- +http://xstream.codehaus.org/download.html +- SaveService \ No newline at end of file diff --git a/ApacheJmeter/lib/opt/README.txt b/ApacheJmeter/lib/opt/README.txt new file mode 100644 index 0000000..cfccbde --- /dev/null +++ b/ApacheJmeter/lib/opt/README.txt @@ -0,0 +1,8 @@ +lib/opt +======= + +This directory is included in the Ant build classpath, +and is used for optional jars that may be needed to build JMeter, +but which are not included in the distribution. + +For example: Doccheck and svnant. \ No newline at end of file diff --git a/ApacheJmeter/org/apache/commons/cli/avalon/ClutilTestCase.java b/ApacheJmeter/org/apache/commons/cli/avalon/ClutilTestCase.java new file mode 100644 index 0000000..8910e56 --- /dev/null +++ b/ApacheJmeter/org/apache/commons/cli/avalon/ClutilTestCase.java @@ -0,0 +1,967 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +import java.util.List; + +import junit.framework.TestCase; + +/** + * + */ +public final class ClutilTestCase extends TestCase { + private static final String[] ARGLIST1 = new String[] { "--you", "are", "--all", "-cler", "kid" }; + + private static final String[] ARGLIST2 = new String[] { "-Dstupid=idiot", "are", "--all", "here", "-d" }; + + private static final String[] ARGLIST3 = new String[] { + // duplicates + "-Dstupid=idiot", "are", "--all", "--all", "here" }; + + private static final String[] ARGLIST4 = new String[] { + // incompatable (blee/all) + "-Dstupid", "idiot", "are", "--all", "--blee", "here" }; + + private static final String[] ARGLIST5 = new String[] { "-f", "myfile.txt" }; + + private static final int DEFINE_OPT = 'D'; + + private static final int CASE_CHECK_OPT = 'd'; + + private static final int YOU_OPT = 'y'; + + private static final int ALL_OPT = 'a'; + + private static final int CLEAR1_OPT = 'c'; + + private static final int CLEAR2_OPT = 'l'; + + private static final int CLEAR3_OPT = 'e'; + + private static final int CLEAR5_OPT = 'r'; + + private static final int BLEE_OPT = 'b'; + + private static final int FILE_OPT = 'f'; + + private static final int TAINT_OPT = 'T'; + + private static final CLOptionDescriptor DEFINE = new CLOptionDescriptor("define", + CLOptionDescriptor.ARGUMENTS_REQUIRED_2, DEFINE_OPT, "define"); + + private static final CLOptionDescriptor DEFINE_MANY = new CLOptionDescriptor("define", + CLOptionDescriptor.ARGUMENTS_REQUIRED_2 | CLOptionDescriptor.DUPLICATES_ALLOWED, DEFINE_OPT, "define"); + + private static final CLOptionDescriptor CASE_CHECK = new CLOptionDescriptor("charCheck", + CLOptionDescriptor.ARGUMENT_DISALLOWED, CASE_CHECK_OPT, "check character case sensitivity"); + + private static final CLOptionDescriptor YOU = new CLOptionDescriptor("you", CLOptionDescriptor.ARGUMENT_DISALLOWED, + YOU_OPT, "you"); + + private static final CLOptionDescriptor CLEAR1 = new CLOptionDescriptor("c", + CLOptionDescriptor.ARGUMENT_DISALLOWED, CLEAR1_OPT, "c"); + + private static final CLOptionDescriptor CLEAR2 = new CLOptionDescriptor("l", + CLOptionDescriptor.ARGUMENT_DISALLOWED, CLEAR2_OPT, "l"); + + private static final CLOptionDescriptor CLEAR3 = new CLOptionDescriptor("e", + CLOptionDescriptor.ARGUMENT_DISALLOWED, CLEAR3_OPT, "e"); + + private static final CLOptionDescriptor CLEAR5 = new CLOptionDescriptor("r", + CLOptionDescriptor.ARGUMENT_DISALLOWED, CLEAR5_OPT, "r"); + + private static final CLOptionDescriptor BLEE = new CLOptionDescriptor("blee", + CLOptionDescriptor.ARGUMENT_DISALLOWED, BLEE_OPT, "blee"); + + private static final CLOptionDescriptor ALL = new CLOptionDescriptor("all", + CLOptionDescriptor.ARGUMENT_DISALLOWED, + ALL_OPT, "all", new CLOptionDescriptor[] { BLEE }); + + private static final CLOptionDescriptor FILE = new CLOptionDescriptor("file", + CLOptionDescriptor.ARGUMENT_REQUIRED, FILE_OPT, "the build file."); + + private static final CLOptionDescriptor TAINT = new CLOptionDescriptor("taint", + CLOptionDescriptor.ARGUMENT_OPTIONAL, TAINT_OPT, "turn on tainting checks (optional level)."); + + private static final CLOptionDescriptor [] OPTIONS = new CLOptionDescriptor [] { + new CLOptionDescriptor("none", + CLOptionDescriptor.ARGUMENT_DISALLOWED | CLOptionDescriptor.DUPLICATES_ALLOWED, + '0', "no parameter"), + + new CLOptionDescriptor("optional", + CLOptionDescriptor.ARGUMENT_OPTIONAL | CLOptionDescriptor.DUPLICATES_ALLOWED, + '?', "optional parameter"), + + new CLOptionDescriptor("one", + CLOptionDescriptor.ARGUMENT_REQUIRED | CLOptionDescriptor.DUPLICATES_ALLOWED, + '1', "one parameter"), + + new CLOptionDescriptor("two", + CLOptionDescriptor.ARGUMENTS_REQUIRED_2 | CLOptionDescriptor.DUPLICATES_ALLOWED, + '2', "two parameters") + }; + + + + + public ClutilTestCase() { + this("Command Line Interpreter Test Case"); + } + + public ClutilTestCase(String name) { + super(name); + } + + public void testOptionalArgWithSpace() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + final String[] args = new String[] { "-T", "param", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals("Option count", 3, size); + + final CLOption option0 = clOptions.get(0); + assertEquals("Option Code: " + option0.getDescriptor().getId(), TAINT_OPT, option0.getDescriptor().getId()); + assertEquals("Option Arg: " + option0.getArgument(0), null, option0.getArgument(0)); + + final CLOption option1 = clOptions.get(1); + assertEquals(option1.getDescriptor().getId(), CLOption.TEXT_ARGUMENT); + assertEquals(option1.getArgument(0), "param"); + + final CLOption option2 = clOptions.get(2); + assertEquals(option2.getDescriptor().getId(), ALL_OPT); + assertEquals(option2.getArgument(0), null); + } + + public void testOptionalArgLong() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + // Check that optional args work woth long options + final String[] args = new String[] { "--taint", "param", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals("Option count", 3, size); + + final CLOption option0 = clOptions.get(0); + assertEquals("Option Code: " + option0.getDescriptor().getId(), TAINT_OPT, option0.getDescriptor().getId()); + assertEquals("Option Arg: " + option0.getArgument(0), null, option0.getArgument(0)); + + final CLOption option1 = clOptions.get(1); + assertEquals(CLOption.TEXT_ARGUMENT, option1.getDescriptor().getId()); + assertEquals("param", option1.getArgument(0)); + + final CLOption option2 = clOptions.get(2); + assertEquals(option2.getDescriptor().getId(), ALL_OPT); + assertEquals(option2.getArgument(0), null); + } + + public void testOptionalArgLongEquals() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + // Check that optional args work woth long options + final String[] args = new String[] { "--taint=param", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals("Option count", 2, size); + + final CLOption option0 = clOptions.get(0); + assertEquals("Option Code: " + option0.getDescriptor().getId(), TAINT_OPT, option0.getDescriptor().getId()); + assertEquals("Option Arg: " + option0.getArgument(0), "param", option0.getArgument(0)); + + final CLOption option2 = clOptions.get(1); + assertEquals(option2.getDescriptor().getId(), ALL_OPT); + assertEquals(option2.getArgument(0), null); + } + + public void testShortOptArgUnenteredBeforeOtherOpt() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + final String[] args = new String[] { "-T", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals("Option count", 2, size); + + final CLOption option0 = clOptions.get(0); + assertEquals("Option Code: " + option0.getDescriptor().getId(), TAINT_OPT, option0.getDescriptor().getId()); + assertEquals("Option Arg: " + option0.getArgument(0), null, option0.getArgument(0)); + + final CLOption option1 = clOptions.get(1); + assertEquals(option1.getDescriptor().getId(), ALL_OPT); + assertEquals(option1.getArgument(0), null); + } + + public void testOptionalArgsWithArgShortBeforeOtherOpt() { + // "-T3","-a" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + final String[] args = new String[] { "-T3", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 2); + final CLOption option0 = clOptions.get(0); + assertEquals(option0.getDescriptor().getId(), TAINT_OPT); + assertEquals(option0.getArgument(0), "3"); + + final CLOption option1 = clOptions.get(1); + assertEquals(ALL_OPT, option1.getDescriptor().getId()); + assertEquals(null, option1.getArgument(0)); + } + + public void testOptionalArgsWithArgShortEqualsBeforeOtherOpt() { + // "-T3","-a" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + final String[] args = new String[] { "-T=3", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 2); + final CLOption option0 = clOptions.get(0); + assertEquals(option0.getDescriptor().getId(), TAINT_OPT); + assertEquals(option0.getArgument(0), "3"); + + final CLOption option1 = clOptions.get(1); + assertEquals(ALL_OPT, option1.getDescriptor().getId()); + assertEquals(null, option1.getArgument(0)); + } + + public void testOptionalArgsNoArgShortBeforeOtherOpt() { + // "-T","-a" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { ALL, TAINT }; + + final String[] args = new String[] { "-T", "-a" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 2); + final CLOption option0 = clOptions.get(0); + assertEquals(TAINT_OPT, option0.getDescriptor().getId()); + assertEquals(null, option0.getArgument(0)); + + final CLOption option1 = clOptions.get(1); + assertEquals(ALL_OPT, option1.getDescriptor().getId()); + assertEquals(null, option1.getArgument(0)); + } + + public void testFullParse() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { YOU, ALL, CLEAR1, CLEAR2, CLEAR3, CLEAR5 }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST1, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 8); + assertEquals((clOptions.get(0)).getDescriptor().getId(), YOU_OPT); + assertEquals((clOptions.get(1)).getDescriptor().getId(), 0); + assertEquals((clOptions.get(2)).getDescriptor().getId(), ALL_OPT); + assertEquals((clOptions.get(3)).getDescriptor().getId(), CLEAR1_OPT); + assertEquals((clOptions.get(4)).getDescriptor().getId(), CLEAR2_OPT); + assertEquals((clOptions.get(5)).getDescriptor().getId(), CLEAR3_OPT); + assertEquals((clOptions.get(6)).getDescriptor().getId(), CLEAR5_OPT); + assertEquals((clOptions.get(7)).getDescriptor().getId(), 0); + } + + public void testDuplicateOptions() { + // "-Dstupid=idiot","are","--all","--all","here" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE, ALL, CLEAR1 }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST3, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 5); + assertEquals((clOptions.get(0)).getDescriptor().getId(), DEFINE_OPT); + assertEquals((clOptions.get(1)).getDescriptor().getId(), 0); + assertEquals((clOptions.get(2)).getDescriptor().getId(), ALL_OPT); + assertEquals((clOptions.get(3)).getDescriptor().getId(), ALL_OPT); + assertEquals((clOptions.get(4)).getDescriptor().getId(), 0); + } + + public void testIncompatableOptions() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE, ALL, CLEAR1, BLEE }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST4, options); + + assertNotNull(parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 5); + assertEquals((clOptions.get(0)).getDescriptor().getId(), DEFINE_OPT); + assertEquals((clOptions.get(1)).getDescriptor().getId(), 0); + assertEquals((clOptions.get(2)).getDescriptor().getId(), ALL_OPT); + assertEquals((clOptions.get(3)).getDescriptor().getId(), BLEE_OPT); + assertEquals((clOptions.get(4)).getDescriptor().getId(), 0); + } + + public void testSingleArg() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST5, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 1); + assertEquals((clOptions.get(0)).getDescriptor().getId(), FILE_OPT); + assertEquals((clOptions.get(0)).getArgument(), "myfile.txt"); + } + + public void testSingleArg2() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-f-=,=-" } // Check + // delimiters + // are + // allowed + , options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals("-=,=-", (clOptions.get(0)).getArgument()); + } + + public void testSingleArg3() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--file=-=,-" } // Check + // delimiters + // are + // allowed + , options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals("-=,-", (clOptions.get(0)).getArgument()); + } + + public void testSingleArg4() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--file", "myfile.txt" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals("myfile.txt", (clOptions.get(0)).getArgument()); + } + + public void testSingleArg5() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-f", "myfile.txt" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals("myfile.txt", (clOptions.get(0)).getArgument()); + } + + public void testSingleArg6() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-f", "-=-" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals("-=-", (clOptions.get(0)).getArgument()); + } + + public void testSingleArg7() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--file=-=-" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals("-=-", (clOptions.get(0)).getArgument()); + } + + public void testSingleArg8() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--file", "-=-" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals("-=-", (clOptions.get(0)).getArgument()); + } + + public void testSingleArg9() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--file", "-=-" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(FILE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals("-=-", (clOptions.get(0)).getArgument()); + } + + public void testCombinedArgs1() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { BLEE, TAINT }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-bT", "rest" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + assertEquals(3, size); + assertEquals(BLEE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals(TAINT_OPT, (clOptions.get(1)).getDescriptor().getId()); + assertEquals(0, (clOptions.get(2)).getDescriptor().getId()); + assertEquals("rest", (clOptions.get(2)).getArgument()); + } + + public void testCombinedArgs2() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { BLEE, TAINT, FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-bT", "-fa" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + assertEquals(3, size); + assertEquals(BLEE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals(TAINT_OPT, (clOptions.get(1)).getDescriptor().getId()); + assertEquals(FILE_OPT, (clOptions.get(2)).getDescriptor().getId()); + assertEquals("a", (clOptions.get(2)).getArgument()); + } + + public void testCombinedArgs3() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { BLEE, TAINT, FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-bT", "--", "-fa" }// Should + // not + // detect + // trailing + // option + , options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + assertEquals(3, size); + assertEquals(BLEE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals(TAINT_OPT, (clOptions.get(1)).getDescriptor().getId()); + assertEquals(0, (clOptions.get(2)).getDescriptor().getId()); + assertEquals("-fa", (clOptions.get(2)).getArgument()); + } + + public void testCombinedArgs4() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { BLEE, TAINT, FILE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-bT", "rest", "-fa" } // should + // detect + // trailing + // option + , options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + assertEquals(4, size); + assertEquals(BLEE_OPT, (clOptions.get(0)).getDescriptor().getId()); + assertEquals(TAINT_OPT, (clOptions.get(1)).getDescriptor().getId()); + assertEquals(0, (clOptions.get(2)).getDescriptor().getId()); + assertEquals("rest", (clOptions.get(2)).getArgument()); + assertEquals(FILE_OPT, (clOptions.get(3)).getDescriptor().getId()); + assertEquals("a", (clOptions.get(3)).getArgument()); + } + + public void test2ArgsParse() { + // "-Dstupid=idiot","are","--all","here" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE, ALL, CLEAR1, CASE_CHECK }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST2, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 5); + assertEquals((clOptions.get(0)).getDescriptor().getId(), DEFINE_OPT); + assertEquals((clOptions.get(1)).getDescriptor().getId(), 0); + assertEquals((clOptions.get(2)).getDescriptor().getId(), ALL_OPT); + assertEquals((clOptions.get(3)).getDescriptor().getId(), 0); + assertEquals((clOptions.get(4)).getDescriptor().getId(), CASE_CHECK_OPT); + + final CLOption option = clOptions.get(0); + assertEquals("stupid", option.getArgument(0)); + assertEquals("idiot", option.getArgument(1)); + } + + public void test2ArgsParse2() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "--define", "a-b,c=d-e,f" }, // Check + // "-" + // is + // allowed + // in + // arg2 + options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(DEFINE_OPT, (clOptions.get(0)).getDescriptor().getId()); + + final CLOption option = clOptions.get(0); + assertEquals("a-b,c", option.getArgument(0)); + assertEquals("d-e,f", option.getArgument(1)); + } + + public void test2ArgsParse3() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-D", "A-b,c", "G-e,f" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(1, size); + assertEquals(DEFINE_OPT, (clOptions.get(0)).getDescriptor().getId()); + + final CLOption option = clOptions.get(0); + assertEquals("A-b,c", option.getArgument(0)); + assertEquals("G-e,f", option.getArgument(1)); + } + + public void test2ArgsParse4() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE_MANY }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-Dval1=-1", "-D", "val2=-2", "--define=val-3=-3", + "--define", "val4-=-4" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(4, size); + for (int i = 0; i < size; i++) { + assertEquals(DEFINE_OPT, (clOptions.get(i)).getDescriptor().getId()); + } + + CLOption option; + option = clOptions.get(0); + assertEquals("val1", option.getArgument(0)); + assertEquals("-1", option.getArgument(1)); + + option = clOptions.get(1); + assertEquals("val2", option.getArgument(0)); + assertEquals("-2", option.getArgument(1)); + + option = clOptions.get(2); + assertEquals("val-3", option.getArgument(0)); + assertEquals("-3", option.getArgument(1)); + + option = clOptions.get(3); + assertEquals("val4-", option.getArgument(0)); + assertEquals("-4", option.getArgument(1)); + } + + public void testPartParse() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { YOU }; + + final ParserControl control = new AbstractParserControl() { + @Override + public boolean isFinished(int lastOptionCode) { + return (lastOptionCode == YOU_OPT); + } + }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST1, options, control); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 1); + assertEquals((clOptions.get(0)).getDescriptor().getId(), YOU_OPT); + } + + public void test2PartParse() { + final CLOptionDescriptor[] options1 = new CLOptionDescriptor[] { YOU }; + + final CLOptionDescriptor[] options2 = new CLOptionDescriptor[] { ALL, CLEAR1, CLEAR2, CLEAR3, CLEAR5 }; + + final ParserControl control1 = new AbstractParserControl() { + @Override + public boolean isFinished(int lastOptionCode) { + return (lastOptionCode == YOU_OPT); + } + }; + + final CLArgsParser parser1 = new CLArgsParser(ARGLIST1, options1, control1); + + assertNull(parser1.getErrorString(), parser1.getErrorString()); + + final List clOptions1 = parser1.getArguments(); + final int size1 = clOptions1.size(); + + assertEquals(size1, 1); + assertEquals((clOptions1.get(0)).getDescriptor().getId(), YOU_OPT); + + final CLArgsParser parser2 = new CLArgsParser(parser1.getUnparsedArgs(), options2); + + assertNull(parser2.getErrorString(), parser2.getErrorString()); + + final List clOptions2 = parser2.getArguments(); + final int size2 = clOptions2.size(); + + assertEquals(size2, 7); + assertEquals((clOptions2.get(0)).getDescriptor().getId(), 0); + assertEquals((clOptions2.get(1)).getDescriptor().getId(), ALL_OPT); + assertEquals((clOptions2.get(2)).getDescriptor().getId(), CLEAR1_OPT); + assertEquals((clOptions2.get(3)).getDescriptor().getId(), CLEAR2_OPT); + assertEquals((clOptions2.get(4)).getDescriptor().getId(), CLEAR3_OPT); + assertEquals((clOptions2.get(5)).getDescriptor().getId(), CLEAR5_OPT); + assertEquals((clOptions2.get(6)).getDescriptor().getId(), 0); + } + + public void test2PartPartialParse() { + final CLOptionDescriptor[] options1 = new CLOptionDescriptor[] { YOU, ALL, CLEAR1 }; + + final CLOptionDescriptor[] options2 = new CLOptionDescriptor[] {}; + + final ParserControl control1 = new AbstractParserControl() { + @Override + public boolean isFinished(final int lastOptionCode) { + return (lastOptionCode == CLEAR1_OPT); + } + }; + + final CLArgsParser parser1 = new CLArgsParser(ARGLIST1, options1, control1); + + assertNull(parser1.getErrorString(), parser1.getErrorString()); + + final List clOptions1 = parser1.getArguments(); + final int size1 = clOptions1.size(); + + assertEquals(size1, 4); + assertEquals((clOptions1.get(0)).getDescriptor().getId(), YOU_OPT); + assertEquals((clOptions1.get(1)).getDescriptor().getId(), 0); + assertEquals((clOptions1.get(2)).getDescriptor().getId(), ALL_OPT); + assertEquals((clOptions1.get(3)).getDescriptor().getId(), CLEAR1_OPT); + + assertEquals("ler",parser1.getUnparsedArgs()[0]); + + final CLArgsParser parser2 = new CLArgsParser(parser1.getUnparsedArgs(), options2); + + assertNull(parser2.getErrorString(), parser2.getErrorString()); + + final List clOptions2 = parser2.getArguments(); + final int size2 = clOptions2.size(); + + assertEquals(size2, 2); + assertEquals((clOptions2.get(0)).getDescriptor().getId(), 0); + assertEquals((clOptions2.get(1)).getDescriptor().getId(), 0); + } + + public void testDuplicatesFail() { + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { YOU, ALL, CLEAR1, CLEAR2, CLEAR3, CLEAR5 }; + + final CLArgsParser parser = new CLArgsParser(ARGLIST1, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + } + + public void testIncomplete2Args() { + // "-Dstupid=" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE }; + + final CLArgsParser parser = new CLArgsParser(new String[] { "-Dstupid=" }, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 1); + final CLOption option = clOptions.get(0); + assertEquals(option.getDescriptor().getId(), DEFINE_OPT); + assertEquals(option.getArgument(0), "stupid"); + assertEquals(option.getArgument(1), ""); + } + + public void testIncomplete2ArgsMixed() { + // "-Dstupid=","-c" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE, CLEAR1 }; + + final String[] args = new String[] { "-Dstupid=", "-c" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 2); + assertEquals(clOptions.get(1).getDescriptor().getId(), CLEAR1_OPT); + final CLOption option = clOptions.get(0); + assertEquals(option.getDescriptor().getId(), DEFINE_OPT); + assertEquals(option.getArgument(0), "stupid"); + assertEquals(option.getArgument(1), ""); + } + + public void testIncomplete2ArgsMixedNoEq() { + // "-Dstupid","-c" + final CLOptionDescriptor[] options = new CLOptionDescriptor[] { DEFINE, CLEAR1 }; + + final String[] args = new String[] { "-DStupid", "-c" }; + + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + + assertEquals(size, 2); + assertEquals(clOptions.get(1).getDescriptor().getId(), CLEAR1_OPT); + final CLOption option = clOptions.get(0); + assertEquals(option.getDescriptor().getId(), DEFINE_OPT); + assertEquals(option.getArgument(0), "Stupid"); + assertEquals(option.getArgument(1), ""); + } + + /** + * Test the getArgumentById and getArgumentByName lookup methods. + */ + public void testArgumentLookup() { + final String[] args = { "-f", "testarg" }; + final CLOptionDescriptor[] options = { FILE }; + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + CLOption optionById = parser.getArgumentById(FILE_OPT); + assertNotNull(optionById); + assertEquals(FILE_OPT, optionById.getDescriptor().getId()); + assertEquals("testarg", optionById.getArgument()); + + CLOption optionByName = parser.getArgumentByName(FILE.getName()); + assertNotNull(optionByName); + assertEquals(FILE_OPT, optionByName.getDescriptor().getId()); + assertEquals("testarg", optionByName.getArgument()); + } + + /** + * Test that you can have null long forms. + */ + public void testNullLongForm() { + final CLOptionDescriptor test = new CLOptionDescriptor(null, CLOptionDescriptor.ARGUMENT_DISALLOWED, 'n', + "test null long form"); + + final String[] args = { "-n", "testarg" }; + final CLOptionDescriptor[] options = { test }; + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final CLOption optionByID = parser.getArgumentById('n'); + assertNotNull(optionByID); + assertEquals('n', optionByID.getDescriptor().getId()); + + final CLOption optionByName = parser.getArgumentByName(FILE.getName()); + assertNull("Looking for non-existent option by name", optionByName); + } + + /** + * Test that you can have null descriptions. + */ + public void testNullDescription() { + final CLOptionDescriptor test = new CLOptionDescriptor("nulltest", CLOptionDescriptor.ARGUMENT_DISALLOWED, 'n', + null); + + final String[] args = { "-n", "testarg" }; + final CLOptionDescriptor[] options = { test }; + final CLArgsParser parser = new CLArgsParser(args, options); + + assertNull(parser.getErrorString(), parser.getErrorString()); + + final CLOption optionByID = parser.getArgumentById('n'); + assertNotNull(optionByID); + assertEquals('n', optionByID.getDescriptor().getId()); + + final StringBuilder sb = CLUtil.describeOptions(options); + final String lineSeparator = System.getProperty("line.separator"); + assertEquals("Testing display of null description", "\t-n, --nulltest" + lineSeparator, sb.toString()); + } + + public void testCombinations() throws Exception { + check(new String [] {},""); + check(new String [] {"--none", + "-0" + }, + "-0 -0"); // Canonical form + check(new String [] {"--one=a", + "--one","A", + "-1b", + "-1=c", + "-1","d" + }, + "-1=[a] -1=[A] -1=[b] -1=[c] -1=[d]"); + check(new String [] {"-2n=v", + "-2","N=V" + }, + "-2=[n, v] -2=[N, V]"); + check(new String [] {"--two=n=v", + "--two","N=V" + }, + "-2=[n, v] -2=[N, V]"); + // Test optional arguments + check(new String [] {"-?", + "A", // Separate argument + "-?=B", + "-?C", + "-?" + }, + "-? [A] -?=[B] -?=[C] -?"); + check(new String [] {"--optional=A", // OK + "--optional","B", // should treat B as separate + "--optional" // Should have no arg + }, + "-?=[A] -? [B] -?"); + } + + private void check(String args[], String canon){ + final CLArgsParser parser = new CLArgsParser(args, OPTIONS); + + assertNull(parser.getErrorString(),parser.getErrorString()); + + final List clOptions = parser.getArguments(); + final int size = clOptions.size(); + StringBuilder sb = new StringBuilder(); + for (int i=0; i< size; i++){ + if (i>0) { + sb.append(" "); + } + sb.append(clOptions.get(i).toShortString()); + } + assertEquals("Canonical form ("+size+")",canon,sb.toString()); + } + /* + * TODO add tests to check for: - name clash - long option abbreviations + * (match shortest unique abbreviation) + */ + +} diff --git a/ApacheJmeter/org/apache/jmeter/assertions/MD5HexAssertionTest.java b/ApacheJmeter/org/apache/jmeter/assertions/MD5HexAssertionTest.java new file mode 100644 index 0000000..f38e9e4 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/assertions/MD5HexAssertionTest.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.jmeter.assertions; + +import junit.framework.TestCase; +public class MD5HexAssertionTest extends TestCase { + + public MD5HexAssertionTest() { + super(); + } + + public MD5HexAssertionTest(String arg0) { + super(arg0); + } + + public void testMD5() throws Exception { + assertEquals("D41D8CD98F00B204E9800998ECF8427E", MD5HexAssertion.baMD5Hex(new byte[] {}).toUpperCase(java.util.Locale.ENGLISH)); + } + +} diff --git a/ApacheJmeter/org/apache/jmeter/assertions/ResponseAssertionTest.java b/ApacheJmeter/org/apache/jmeter/assertions/ResponseAssertionTest.java new file mode 100644 index 0000000..761b4f2 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/assertions/ResponseAssertionTest.java @@ -0,0 +1,253 @@ +/* + * 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.assertions; + +import java.net.MalformedURLException; +import java.net.URL; + +import junit.framework.TestCase; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class ResponseAssertionTest extends TestCase { + + public ResponseAssertionTest() { + } + + private JMeterContext jmctx; + private ResponseAssertion assertion; + private SampleResult sample; + private JMeterVariables vars; + private AssertionResult result; + + @Override + public void setUp() throws MalformedURLException { + jmctx = JMeterContextService.getContext(); + assertion = new ResponseAssertion(); + assertion.setThreadContext(jmctx); + sample = new SampleResult(); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(sample); + sample.setResponseData("response Data\nline 2\n\nEOF", null); + sample.setURL(new URL("http://localhost/Sampler/Data/")); + sample.setResponseCode("401"); + sample.setResponseHeaders("X-Header: abcd"); + } + + public void testResponseAssertionEquals() throws Exception{ + assertion.unsetNotType(); + assertion.setToEqualsType(); + assertion.setTestFieldURL(); + assertion.addTestString("Sampler Label"); + assertion.addTestString("Sampler labelx"); + result = assertion.getResult(sample); + assertFailed(); + + assertion.setToNotType(); + assertion.clearTestStrings(); + assertion.addTestString("Sampler LabeL"); + assertion.addTestString("Sampler Labelx"); + result = assertion.getResult(sample); + assertPassed(); + } + + public void testResponseAssertionHeaders() throws Exception{ + assertion.unsetNotType(); + assertion.setToEqualsType(); + assertion.setTestFieldResponseHeaders(); + assertion.addTestString("X-Header: abcd"); + assertion.addTestString("X-Header: abcdx"); + result = assertion.getResult(sample); + assertFailed(); + + assertion.clearTestStrings(); + assertion.addTestString("X-Header: abcd"); + result = assertion.getResult(sample); + assertPassed(); + } + + public void testResponseAssertionContains() throws Exception{ + assertion.unsetNotType(); + assertion.setToContainsType(); + assertion.setTestFieldURL(); + assertion.addTestString("Sampler"); + assertion.addTestString("Label"); + assertion.addTestString(" x"); + + result = assertion.getResult(sample); + assertFailed(); + + assertion.setToNotType(); + + result = assertion.getResult(sample); + assertFailed(); + + assertion.clearTestStrings(); + assertion.addTestString("r l"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.unsetNotType(); + assertion.setTestFieldResponseData(); + + assertion.clearTestStrings(); + assertion.addTestString("line 2"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.clearTestStrings(); + assertion.addTestString("(?s)line \\d+.*EOF"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.setTestFieldResponseCode(); + + assertion.clearTestStrings(); + assertion.addTestString("401"); + result = assertion.getResult(sample); + assertPassed(); + + } + + // Bug 46831 - check can match dollars + public void testResponseAssertionContainsDollar() throws Exception { + sample.setResponseData("value=\"${ID}\" Group$ctl00$drpEmails", null); + assertion.unsetNotType(); + assertion.setToContainsType(); + assertion.setTestFieldResponseData(); + assertion.addTestString("value=\"\\${ID}\" Group\\$ctl00\\$drpEmails"); + + result = assertion.getResult(sample); + assertPassed(); + } + + public void testResponseAssertionSubstring() throws Exception{ + assertion.unsetNotType(); + assertion.setToSubstringType(); + assertion.setTestFieldURL(); + assertion.addTestString("Sampler"); + assertion.addTestString("Label"); + assertion.addTestString("+("); + + result = assertion.getResult(sample); + assertFailed(); + + assertion.setToNotType(); + + result = assertion.getResult(sample); + assertFailed(); + + assertion.clearTestStrings(); + assertion.addTestString("r l"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.unsetNotType(); + assertion.setTestFieldResponseData(); + + assertion.clearTestStrings(); + assertion.addTestString("line 2"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.clearTestStrings(); + assertion.addTestString("line 2\n\nEOF"); + result = assertion.getResult(sample); + assertPassed(); + + assertion.setTestFieldResponseCode(); + + assertion.clearTestStrings(); + assertion.addTestString("401"); + result = assertion.getResult(sample); + assertPassed(); + + } + +//TODO - need a lot more tests + + private void assertPassed() throws Exception{ + assertNull(result.getFailureMessage(),result.getFailureMessage()); + assertFalse("Not expecting error: "+result.getFailureMessage(),result.isError()); + assertFalse("Not expecting error",result.isError()); + assertFalse("Not expecting failure",result.isFailure()); + } + + private void assertFailed() throws Exception{ + assertNotNull(result.getFailureMessage()); + assertFalse("Should not be: Response was null","Response was null".equals(result.getFailureMessage())); + assertFalse("Not expecting error: "+result.getFailureMessage(),result.isError()); + assertTrue("Expecting failure",result.isFailure()); + + } + private volatile int threadsRunning; + + private volatile int failed; + + public void testThreadSafety() throws Exception { + Thread[] threads = new Thread[100]; + for (int i = 0; i < threads.length; i++) { + threads[i] = new TestThread(); + } + failed = 0; + for (int i = 0; i < threads.length; i++) { + threads[i].start(); + threadsRunning++; + } + synchronized (this) { + while (threadsRunning > 0) { + wait(); + } + } + assertEquals(failed, 0); + } + + class TestThread extends Thread { + static final String TEST_STRING = "DAbale arroz a la zorra el abad."; + + // Used to be 'dábale', but caused trouble on Gump. Reasons + // unknown. + static final String TEST_PATTERN = ".*A.*\\."; + + @Override + public void run() { + ResponseAssertion assertion = new ResponseAssertion(); + assertion.setTestFieldResponseData(); + assertion.setToContainsType(); + assertion.addTestString(TEST_PATTERN); + SampleResult response = new SampleResult(); + response.setResponseData(TEST_STRING, null); + for (int i = 0; i < 100; i++) { + AssertionResult result; + result = assertion.getResult(response); + if (result.isFailure() || result.isError()) { + failed++; + } + } + synchronized (ResponseAssertionTest.this) { + threadsRunning--; + ResponseAssertionTest.this.notifyAll(); + } + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/assertions/SizeAssertionTest.java b/ApacheJmeter/org/apache/jmeter/assertions/SizeAssertionTest.java new file mode 100644 index 0000000..7697ecb --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/assertions/SizeAssertionTest.java @@ -0,0 +1,170 @@ +/* + * 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.assertions; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class SizeAssertionTest extends JMeterTestCase{ + + private JMeterContext jmctx; + private SizeAssertion assertion; + private SampleResult sample1,sample0; + private JMeterVariables vars; + private AssertionResult result; + private String data1 = "response Data\n" + "line 2\n\nEOF"; + private int data1Len=data1.length(); + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + assertion = new SizeAssertion(); + assertion.setThreadContext(jmctx); + assertion.setTestFieldResponseBody(); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + sample0 = new SampleResult(); + sample1 = new SampleResult(); + sample1.setResponseData(data1, null); + } + + public void testSizeAssertionEquals() throws Exception{ + assertion.setCompOper(SizeAssertion.EQUAL); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertFailed(); + + result = assertion.getResult(sample0); + assertPassed(); + + assertion.setAllowedSize(data1Len); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertFailed(); + } + + public void testSizeAssertionNotEquals() throws Exception{ + assertion.setCompOper(SizeAssertion.NOTEQUAL); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertFailed(); + + assertion.setAllowedSize(data1Len); + result = assertion.getResult(sample1); + assertFailed(); + + result = assertion.getResult(sample0); + assertPassed(); + } + + public void testSizeAssertionGreaterThan() throws Exception{ + assertion.setCompOper(SizeAssertion.GREATERTHAN); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertFailed(); + + assertion.setAllowedSize(data1Len); + result = assertion.getResult(sample1); + assertFailed(); + + result = assertion.getResult(sample0); + assertFailed(); + } + + public void testSizeAssertionGreaterThanEqual() throws Exception{ + assertion.setCompOper(SizeAssertion.GREATERTHANEQUAL); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertPassed(); + + assertion.setAllowedSize(data1Len); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertFailed(); + } + + public void testSizeAssertionLessThan() throws Exception{ + assertion.setCompOper(SizeAssertion.LESSTHAN); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertFailed(); + + result = assertion.getResult(sample0); + assertFailed(); + + assertion.setAllowedSize(data1Len+1); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertPassed(); + } + + public void testSizeAssertionLessThanEqual() throws Exception{ + assertion.setCompOper(SizeAssertion.LESSTHANEQUAL); + assertion.setAllowedSize(0); + result = assertion.getResult(sample1); + assertFailed(); + + result = assertion.getResult(sample0); + assertPassed(); + + assertion.setAllowedSize(data1Len+1); + result = assertion.getResult(sample1); + assertPassed(); + + result = assertion.getResult(sample0); + assertPassed(); + } +// TODO - need a lot more tests + + private void assertPassed() throws Exception{ + if (null != result.getFailureMessage()){ + //System.out.println(result.getFailureMessage());// debug + } + assertNull("Failure message should be null",result.getFailureMessage()); + assertFalse(result.isError()); + assertFalse(result.isFailure()); + } + + private void assertFailed() throws Exception{ + assertNotNull("Failure nessage should not be null",result.getFailureMessage()); + //System.out.println(result.getFailureMessage()); + assertFalse("Should not be: Response was null","Response was null".equals(result.getFailureMessage())); + assertFalse(result.isError()); + assertTrue(result.isFailure()); + + } +} diff --git a/ApacheJmeter/org/apache/jmeter/assertions/XMLSchemaAssertionTest.java b/ApacheJmeter/org/apache/jmeter/assertions/XMLSchemaAssertionTest.java new file mode 100644 index 0000000..59c9ca2 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/assertions/XMLSchemaAssertionTest.java @@ -0,0 +1,182 @@ +/* + * 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.assertions; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +//import org.apache.jorphan.logging.LoggingManager; + +public class XMLSchemaAssertionTest extends JMeterTestCase { + + private XMLSchemaAssertion assertion; + + private SampleResult result; + + private JMeterVariables vars; + + private JMeterContext jmctx; + + public XMLSchemaAssertionTest(String arg0) { + super(arg0); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + jmctx = JMeterContextService.getContext(); + assertion = new XMLSchemaAssertion(); + assertion.setThreadContext(jmctx);// This would be done by the run + // command + result = new SampleResult(); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + // LoggingManager.setPriority("DEBUG","jmeter"); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + private ByteArrayOutputStream readBA(String name) throws IOException { + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(findTestFile(name))); + ByteArrayOutputStream baos = new ByteArrayOutputStream(1000); + int len = 0; + byte[] data = new byte[512]; + while ((len = bis.read(data)) >= 0) { + baos.write(data, 0, len); + } + bis.close(); + return baos; + } + + private byte[] readFile(String name) throws IOException { + return readBA(name).toByteArray(); + } + + public void testAssertionOK() throws Exception { + result.setResponseData(readFile("testfiles/XMLSchematest.xml")); + assertion.setXsdFileName(findTestPath("testfiles/XMLSchema-pass.xsd")); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertFalse("Should not be a failure", res.isFailure()); + } + + public void testAssertionFail() throws Exception { + result.setResponseData(readFile("testfiles/XMLSchematest.xml")); + assertion.setXsdFileName("testfiles/XMLSchema-fail.xsd"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testAssertionBadXSDFile() throws Exception { + result.setResponseData(readFile("testfiles/XMLSchematest.xml")); + assertion.setXsdFileName("xtestfiles/XMLSchema-fail.xsd"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertTrue(res.getFailureMessage().indexOf("Failed to read schema document") > 0); + assertTrue(res.isError());// TODO - should this be a failure? + assertFalse(res.isFailure()); + } + + public void testAssertionNoFile() throws Exception { + result.setResponseData(readFile("testfiles/XMLSchematest.xml")); + assertion.setXsdFileName(""); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertEquals(XMLSchemaAssertion.FILE_NAME_IS_REQUIRED, res.getFailureMessage()); + assertFalse(res.isError()); + assertTrue(res.isFailure()); + } + + public void testAssertionNoResult() throws Exception { + // result.setResponseData - not set + assertion.setXsdFileName("testfiles/XMLSchema-fail.xsd"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertEquals(AssertionResult.RESPONSE_WAS_NULL, res.getFailureMessage()); + assertFalse(res.isError()); + assertTrue(res.isFailure()); + } + + public void testAssertionEmptyResult() throws Exception { + result.setResponseData("", null); + assertion.setXsdFileName("testfiles/XMLSchema-fail.xsd"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertEquals(AssertionResult.RESPONSE_WAS_NULL, res.getFailureMessage()); + assertFalse(res.isError()); + assertTrue(res.isFailure()); + } + + public void testAssertionBlankResult() throws Exception { + result.setResponseData(" ", null); + assertion.setXsdFileName("testfiles/XMLSchema-fail.xsd"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertTrue(res.getFailureMessage().indexOf("Premature end of file") > 0); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testXMLTrailingcontent() throws Exception { + ByteArrayOutputStream baos = readBA("testfiles/XMLSchematest.xml"); + baos.write("extra".getBytes()); // TODO - charset? + result.setResponseData(baos.toByteArray()); + assertion.setXsdFileName(findTestPath("testfiles/XMLSchema-pass.xsd")); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertTrue(res.getFailureMessage().indexOf("Content is not allowed in trailing section") > 0); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testXMLTrailingwhitespace() throws Exception { + ByteArrayOutputStream baos = readBA("testfiles/XMLSchematest.xml"); + baos.write(" \t\n".getBytes()); // TODO - charset? + result.setResponseData(baos.toByteArray()); + assertion.setXsdFileName(findTestPath("testfiles/XMLSchema-pass.xsd")); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + testLog.debug("xisError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure " + res.getFailureMessage()); + assertFalse(res.isError()); + assertFalse(res.isFailure()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/assertions/XPathAssertionTest.java b/ApacheJmeter/org/apache/jmeter/assertions/XPathAssertionTest.java new file mode 100644 index 0000000..16c1629 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/assertions/XPathAssertionTest.java @@ -0,0 +1,322 @@ +/* + * 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.assertions; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class XPathAssertionTest extends JMeterTestCase { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private XPathAssertion assertion; + + private SampleResult result; + + private JMeterVariables vars; + + private JMeterContext jmctx; + + public XPathAssertionTest(String arg0) { + super(arg0); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + jmctx = JMeterContextService.getContext(); + assertion = new XPathAssertion(); + assertion.setThreadContext(jmctx);// This would be done by the run command + result = new SampleResult(); + result.setResponseData(readFile("testfiles/XPathAssertionTest.xml")); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + //testLog.setPriority(org.apache.log.Priority.DEBUG); + } + + private void setAlternateResponseData(){ + String data = "" + "" + "LIS_OK" + + "" + "" + + "" + "0" + + "1" + "" + + "5" + "" + + "6" + "" + + "" + ""; + result.setResponseData(data, null); + } + + private ByteArrayOutputStream readBA(String name) throws IOException { + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(findTestFile(name))); + ByteArrayOutputStream baos = new ByteArrayOutputStream(1000); + int len = 0; + byte[] data = new byte[512]; + while ((len = bis.read(data)) >= 0) { + baos.write(data, 0, len); + } + bis.close(); + return baos; + } + + private byte[] readFile(String name) throws IOException { + return readBA(name).toByteArray(); + } + + public void testAssertionOK() throws Exception { + assertion.setXPathString("/"); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertFalse("Should not be a failure", res.isFailure()); + } + + public void testAssertionFail() throws Exception { + assertion.setXPathString("//x"); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionPath1() throws Exception { + assertion.setXPathString("//*[code=1]"); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertFalse("Should not be a failure",res.isFailure()); + } + + public void testAssertionPath2() throws Exception { + assertion.setXPathString("//*[code=2]"); // Not present + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionBool1() throws Exception { + assertion.setXPathString("count(//error)=2"); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertFalse("Should not be a failure",res.isFailure()); + } + + public void testAssertionBool2() throws Exception { + assertion.setXPathString("count(//*[code=1])=1"); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertFalse("Should not be a failure",res.isFailure()); + } + + public void testAssertionBool3() throws Exception { + assertion.setXPathString("count(//error)=1"); // wrong + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionBool4() throws Exception { + assertion.setXPathString("count(//*[code=2])=1"); //Wrong + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionNumber() throws Exception { + assertion.setXPathString("count(//error)");// not yet handled + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionNoResult() throws Exception { + // result.setResponseData - not set + result = new SampleResult(); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertEquals(AssertionResult.RESPONSE_WAS_NULL, res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionEmptyResult() throws Exception { + result.setResponseData("", null); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertEquals(AssertionResult.RESPONSE_WAS_NULL, res.getFailureMessage()); + assertFalse("Should not be an error", res.isError()); + assertTrue("Should be a failure",res.isFailure()); + } + + public void testAssertionBlankResult() throws Exception { + result.setResponseData(" ", null); + AssertionResult res = assertion.getResult(result); + testLog.debug("isError() " + res.isError() + " isFailure() " + res.isFailure()); + testLog.debug("failure message: " + res.getFailureMessage()); + assertTrue(res.getFailureMessage().indexOf("Premature end of file") > 0); + assertTrue("Should be an error",res.isError()); + assertFalse("Should not be a failure", res.isFailure()); + } + + public void testNoTolerance() throws Exception { + String data = "testtitle" + "" + + "

invalid tag nesting


" + ""; + + result.setResponseData(data, null); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + assertion.setXPathString("/html/head/title"); + assertion.setValidating(false); + assertion.setTolerant(false); + AssertionResult res = assertion.getResult(result); + log.debug("failureMessage: " + res.getFailureMessage()); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testAssertion() throws Exception { + setAlternateResponseData(); + assertion.setXPathString("//row/value[@field = 'alias']"); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + log.debug(" res " + res.isError()); + log.debug(" failure " + res.getFailureMessage()); + assertFalse(res.isError()); + assertFalse(res.isFailure()); + } + + public void testNegateAssertion() throws Exception { + setAlternateResponseData(); + assertion.setXPathString("//row/value[@field = 'noalias']"); + assertion.setNegated(true); + + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + log.debug(" res " + res.isError()); + log.debug(" failure " + res.getFailureMessage()); + assertFalse(res.isError()); + assertFalse(res.isFailure()); + } + + public void testValidationFailure() throws Exception { + setAlternateResponseData(); + assertion.setXPathString("//row/value[@field = 'alias']"); + assertion.setNegated(false); + assertion.setValidating(true); + AssertionResult res = assertion.getResult(jmctx.getPreviousResult()); + log.debug(res.getFailureMessage() + " error: " + res.isError() + " failure: " + res.isFailure()); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testValidationSuccess() throws Exception { + String data = "" + "" + + "" + + "" + "" + + "" + "" + + "" + "" + + "" + "" + + "" + "" + "]>" + "" + + "" + "All About Me" + "" + "" + + "
Welcome To My Book
" + "" + + "CHAPTER 1" + "" + + "

Glad you want to hear about me.

" + "

There's so much to say!

" + + "

Where should we start?

" + "

How about more about me?

" + "
" + + "
" + "
" + "
"; + + result.setResponseData(data, null); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + assertion.setXPathString("/"); + assertion.setValidating(true); + AssertionResult res = assertion.getResult(result); + assertFalse(res.isError()); + assertFalse(res.isFailure()); + } + + public void testValidationFailureWithDTD() throws Exception { + String data = "" + "" + + "" + + "" + "" + + "" + "" + + "" + "" + + "" + "" + + "" + "" + "]>" + "" + + "" + "All About Me" + "" + "" + + "
Welcome To My Book
" + "" + + "CHAPTER 1" + "" + + "

Glad you want to hear about me.

" + "

There's so much to say!

" + + "

Where should we start?

" + "

How about more about me?

" + "
" + + "
" + "not defined in dtd" + "
" + "
"; + + result.setResponseData(data, null); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + assertion.setXPathString("/"); + assertion.setValidating(true); + AssertionResult res = assertion.getResult(result); + log.debug("failureMessage: " + res.getFailureMessage()); + assertTrue(res.isError()); + assertFalse(res.isFailure()); + } + + public void testTolerance() throws Exception { + String data = "testtitle" + "" + + "

invalid tag nesting


" + ""; + + result.setResponseData(data, null); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + assertion.setXPathString("/html/head/title"); + assertion.setValidating(true); + assertion.setTolerant(true); + AssertionResult res = assertion.getResult(result); + log.debug("failureMessage: " + res.getFailureMessage()); + assertFalse(res.isFailure()); + assertFalse(res.isError()); + } + +} diff --git a/ApacheJmeter/org/apache/jmeter/config/TestCVSDataSet.java b/ApacheJmeter/org/apache/jmeter/config/TestCVSDataSet.java new file mode 100644 index 0000000..eb524a4 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/config/TestCVSDataSet.java @@ -0,0 +1,199 @@ +/* + * 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 to test FileServer methods + */ + +package org.apache.jmeter.config; + +import java.io.IOException; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestCVSDataSet extends JMeterTestCase { + + private JMeterVariables threadVars; + + public TestCVSDataSet(String arg0) { + super(arg0); + } + + @Override + public void setUp(){ + JMeterContext jmcx = JMeterContextService.getContext(); + jmcx.setVariables(new JMeterVariables()); + threadVars = jmcx.getVariables(); + threadVars.put("b", "value"); + } + + @Override + public void tearDown() throws IOException{ + FileServer.getFileServer().closeFiles(); + } + + public void testopen() throws Exception { + CSVDataSet csv = new CSVDataSet(); + csv.setFilename("No.such.filename"); + csv.setVariableNames("a,b,c"); + csv.setDelimiter(","); + csv.iterationStart(null); + assertEquals("",threadVars.get("a")); + assertEquals("",threadVars.get("b")); + assertEquals("",threadVars.get("c")); + + csv = new CSVDataSet(); + csv.setFilename(findTestPath("testfiles/testempty.csv")); + csv.setVariableNames("a,b,c"); + csv.setDelimiter(","); + + csv.iterationStart(null); + assertEquals("",threadVars.get("a")); + assertEquals("b1",threadVars.get("b")); + assertEquals("c1",threadVars.get("c")); + + csv.iterationStart(null); + assertEquals("a2",threadVars.get("a")); + assertEquals("",threadVars.get("b")); + assertEquals("c2",threadVars.get("c")); + + csv.iterationStart(null); + assertEquals("a3",threadVars.get("a")); + assertEquals("b3",threadVars.get("b")); + assertEquals("",threadVars.get("c")); + + + csv.iterationStart(null); + assertEquals("a4",threadVars.get("a")); + assertEquals("b4",threadVars.get("b")); + assertEquals("c4",threadVars.get("c")); + + csv.iterationStart(null); // Restart file + assertEquals("",threadVars.get("a")); + assertEquals("b1",threadVars.get("b")); + assertEquals("c1",threadVars.get("c")); + } + + public void testutf8() throws Exception { + + CSVDataSet csv = new CSVDataSet(); + csv.setFilename(findTestPath("testfiles/testutf8.csv")); + csv.setVariableNames("a,b,c,d"); + csv.setDelimiter(","); + csv.setQuotedData( true ); + csv.setFileEncoding( "UTF-8" ); + + csv.iterationStart(null); + assertEquals("a1",threadVars.get("a")); + assertEquals("b1",threadVars.get("b")); + assertEquals("\u00e71",threadVars.get("c")); + assertEquals("d1",threadVars.get("d")); + + csv.iterationStart(null); + assertEquals("a2",threadVars.get("a")); + assertEquals("b2",threadVars.get("b")); + assertEquals("\u00e72",threadVars.get("c")); + assertEquals("d2",threadVars.get("d")); + + csv.iterationStart(null); + assertEquals("a3",threadVars.get("a")); + assertEquals("b3",threadVars.get("b")); + assertEquals("\u00e73",threadVars.get("c")); + assertEquals("d3",threadVars.get("d")); + + csv.iterationStart(null); + assertEquals("a4",threadVars.get("a")); + assertEquals("b4",threadVars.get("b")); + assertEquals("\u00e74",threadVars.get("c")); + assertEquals("d4",threadVars.get("d")); + } + + // Test CSV file with a header line + public void testHeaderOpen(){ + CSVDataSet csv = new CSVDataSet(); + csv.setFilename(findTestPath("testfiles/testheader.csv")); + csv.setDelimiter("|"); + assertNull(csv.getVariableNames()); + csv.iterationStart(null); + assertNull(threadVars.get("a")); + assertEquals("a1",threadVars.get("A")); + assertEquals("b1",threadVars.get("B")); + assertEquals("c1",threadVars.get("C")); + assertEquals("d1",threadVars.get("D|1")); + csv.iterationStart(null); + assertNull(threadVars.get("a")); + assertEquals("a2",threadVars.get("A")); + assertEquals("b2",threadVars.get("B")); + assertEquals("c2",threadVars.get("C")); + assertEquals("d2",threadVars.get("D|1")); + } + + // Test CSV file with a header line and recycle is true + public void testHeaderOpenAndRecycle(){ + CSVDataSet csv = new CSVDataSet(); + csv.setFilename(findTestPath("testfiles/testheader.csv")); + csv.setDelimiter("|"); + csv.setRecycle(true); + assertNull(csv.getVariableNames()); // read 1st line + // read 5 lines + restart to file begin + csv.iterationStart(null); // line 2 + csv.iterationStart(null); // line 3 + csv.iterationStart(null); // line 4 + csv.iterationStart(null); // line 5 + csv.iterationStart(null); // return to 2nd line (first line is names) + assertEquals("a1",threadVars.get("A")); + assertEquals("b1",threadVars.get("B")); + assertEquals("c1",threadVars.get("C")); + assertEquals("d1",threadVars.get("D|1")); + } + + private CSVDataSet initCSV(){ + CSVDataSet csv = new CSVDataSet(); + csv.setFilename(findTestPath("testfiles/test.csv")); + csv.setVariableNames("a,b,c"); + csv.setDelimiter(","); + return csv; + } + + public void testShareMode(){ + + new CSVDataSetBeanInfo(); // needs to be initialised + CSVDataSet csv0 = initCSV(); + CSVDataSet csv1 = initCSV(); + assertNull(csv1.getShareMode()); + csv1.setShareMode("abc"); + assertEquals("abc",csv1.getShareMode()); + csv1.iterationStart(null); + assertEquals("a1",threadVars.get("a")); + csv1.iterationStart(null); + assertEquals("a2",threadVars.get("a")); + CSVDataSet csv2 = initCSV(); + csv2.setShareMode("abc"); + assertEquals("abc",csv2.getShareMode()); + csv2.iterationStart(null); + assertEquals("a3",threadVars.get("a")); + csv0.iterationStart(null); + assertEquals("a1",threadVars.get("a")); + csv1.iterationStart(null); + assertEquals("a4",threadVars.get("a")); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/config/gui/TestArgumentsPanel.java b/ApacheJmeter/org/apache/jmeter/config/gui/TestArgumentsPanel.java new file mode 100644 index 0000000..4d83bd2 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/config/gui/TestArgumentsPanel.java @@ -0,0 +1,60 @@ +/* + * 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.config.gui; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; + +/** + * A GUI panel allowing the user to enter name-value argument pairs. These + * arguments (or parameters) are usually used to provide configuration values + * for some other component. + * + */ +public class TestArgumentsPanel extends TestCase { + /** + * Create a new test. + * + * @param name + * the name of the test + */ + public TestArgumentsPanel(String name) { + super(name); + } + + /** + * Test that adding an argument to the table results in an appropriate + * TestElement being created. + * + * @throws Exception + * if an exception occurred during the test + */ + public void testArgumentCreation() throws Exception { + ArgumentsPanel gui = new ArgumentsPanel(); + gui.tableModel.addRow(new Argument()); + gui.tableModel.setValueAt("howdy", 0, 0); + gui.tableModel.addRow(new Argument()); + gui.tableModel.setValueAt("doody", 0, 1); + + assertEquals("=", ((Argument) ((Arguments) gui.createTestElement()).getArguments().get(0).getObjectValue()) + .getMetaData()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/control/TestGenericController.java b/ApacheJmeter/org/apache/jmeter/control/TestGenericController.java new file mode 100644 index 0000000..6a3f1c6 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/control/TestGenericController.java @@ -0,0 +1,58 @@ +/* + * 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.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.testelement.TestElement; + +public class TestGenericController extends JMeterTestCase { + public TestGenericController(String name) { + super(name); + } + + public void testProcessing() throws Exception { + testLog.debug("Testing Generic Controller"); + GenericController controller = new GenericController(); + GenericController sub_1 = new GenericController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + GenericController sub_2 = new GenericController(); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + String[] order = new String[] { "one", "two", "three", "four", "five", "six", "seven" }; + int counter = 7; + controller.initialize(); + for (int i = 0; i < 2; i++) { + assertEquals(7, counter); + counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals(order[counter++], sampler.getName()); + } + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/control/TestIfController.java b/ApacheJmeter/org/apache/jmeter/control/TestIfController.java new file mode 100644 index 0000000..2a896eb --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/control/TestIfController.java @@ -0,0 +1,176 @@ +/* + * 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.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.samplers.Sampler; + +public class TestIfController extends JMeterTestCase { + public TestIfController(String name) { + super(name); + } + + public void testProcessing() throws Exception { + + GenericController controller = new GenericController(); + + controller.addTestElement(new IfController("false==false")); + controller.addTestElement(new IfController(" \"a\".equals(\"a\")")); + controller.addTestElement(new IfController("2<100")); + + //TODO enable some proper tests!! + + /* + * GenericController sub_1 = new GenericController(); + * sub_1.addTestElement(new IfController("3==3")); + * controller.addTestElement(sub_1); controller.addTestElement(new + * IfController("false==true")); + */ + + /* + * GenericController controller = new GenericController(); + * GenericController sub_1 = new GenericController(); + * sub_1.addTestElement(new IfController("10<100")); + * sub_1.addTestElement(new IfController("true==false")); + * controller.addTestElement(sub_1); controller.addTestElement(new + * IfController("false==false")); + * + * IfController sub_2 = new IfController(); sub_2.setCondition( "10<10000"); + * GenericController sub_3 = new GenericController(); + * + * sub_2.addTestElement(new IfController( " \"a\".equals(\"a\")" ) ); + * sub_3.addTestElement(new IfController("2>100")); + * sub_3.addTestElement(new IfController("false==true")); + * sub_2.addTestElement(sub_3); sub_2.addTestElement(new + * IfController("2==3")); controller.addTestElement(sub_2); + */ + + /* + * IfController controller = new IfController("12==12"); + * controller.initialize(); + */ +// TestElement sampler = null; +// while ((sampler = controller.next()) != null) { +// logger.debug(" ->>> Gonna assertTrue :" + sampler.getClass().getName() + " Property is ---->>>" +// + sampler.getName()); +// } + } + + public void testProcessingTrue() throws Exception { + LoopController controller = new LoopController(); + controller.setLoops(2); + controller.addTestElement(new TestSampler("Sample1")); + IfController ifCont = new IfController("true==true"); + ifCont.setEvaluateAll(true); + ifCont.addTestElement(new TestSampler("Sample2")); + TestSampler sample3 = new TestSampler("Sample3"); + ifCont.addTestElement(sample3); + controller.addTestElement(ifCont); + + String[] order = new String[] { "Sample1", "Sample2", "Sample3", + "Sample1", "Sample2", "Sample3" }; + int counter = 0; + controller.setRunningVersion(true); + ifCont.setRunningVersion(true); + + Sampler sampler = null; + while ((sampler = controller.next()) != null) { + sampler.sample(null); + assertEquals(order[counter], sampler.getName()); + counter++; + } + assertEquals(counter, 6); + } + + /** + * Test false return on sample3 (sample4 doesn't execute) + * @throws Exception + */ + public void testEvaluateAllChildrenWithoutSubController() throws Exception { + LoopController controller = new LoopController(); + controller.setLoops(2); + controller.addTestElement(new TestSampler("Sample1")); + IfController ifCont = new IfController("true==true"); + ifCont.setEvaluateAll(true); + controller.addTestElement(ifCont); + + ifCont.addTestElement(new TestSampler("Sample2")); + TestSampler sample3 = new TestSampler("Sample3"); + ifCont.addTestElement(sample3); + TestSampler sample4 = new TestSampler("Sample4"); + ifCont.addTestElement(sample4); + + String[] order = new String[] { "Sample1", "Sample2", "Sample3", + "Sample1", "Sample2", "Sample3" }; + int counter = 0; + controller.setRunningVersion(true); + ifCont.setRunningVersion(true); + + Sampler sampler = null; + while ((sampler = controller.next()) != null) { + sampler.sample(null); + if (sampler.getName().equals("Sample3")) { + ifCont.setCondition("true==false"); + } + assertEquals(order[counter], sampler.getName()); + counter++; + } + assertEquals(counter, 6); + } + + /** + * test 2 loops with a sub generic controller (sample4 doesn't execute) + * @throws Exception + */ + public void testEvaluateAllChildrenWithSubController() throws Exception { + LoopController controller = new LoopController(); + controller.setLoops(2); + controller.addTestElement(new TestSampler("Sample1")); + IfController ifCont = new IfController("true==true"); + ifCont.setEvaluateAll(true); + controller.addTestElement(ifCont); + ifCont.addTestElement(new TestSampler("Sample2")); + + GenericController genericCont = new GenericController(); + TestSampler sample3 = new TestSampler("Sample3"); + genericCont.addTestElement(sample3); + TestSampler sample4 = new TestSampler("Sample4"); + genericCont.addTestElement(sample4); + ifCont.addTestElement(genericCont); + + String[] order = new String[] { "Sample1", "Sample2", "Sample3", + "Sample1", "Sample2", "Sample3" }; + int counter = 0; + controller.setRunningVersion(true); + ifCont.setRunningVersion(true); + genericCont.setRunningVersion(true); + + Sampler sampler = null; + while ((sampler = controller.next()) != null) { + sampler.sample(null); + if (sampler.getName().equals("Sample3")) { + ifCont.setCondition("true==false"); + } + assertEquals(order[counter], sampler.getName()); + counter++; + } + assertEquals(counter, 6); + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/control/TestInterleaveControl.java b/ApacheJmeter/org/apache/jmeter/control/TestInterleaveControl.java new file mode 100644 index 0000000..e75e6c2 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/control/TestInterleaveControl.java @@ -0,0 +1,227 @@ +/* + * 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.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.testelement.TestElement; + +public class TestInterleaveControl extends JMeterTestCase { + public TestInterleaveControl(String name) { + super(name); + } + + public void testProcessing() throws Exception { + testLog.debug("Testing Interleave Controller 1"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + sub_1.setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + String[] interleaveOrder = new String[] { "one", "two" }; + String[] order = new String[] { "dummy", "three", "four", "five", "six", "seven", "four", "five", "six", + "seven", "four", "five", "six", "seven" }; + int counter = 14; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + for (int i = 0; i < 4; i++) { + assertEquals(14, counter); + counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + if (counter == 0) { + assertEquals(interleaveOrder[i % 2], sampler.getName()); + } else { + assertEquals(order[counter], sampler.getName()); + } + counter++; + } + } + } + + public void testProcessing6() throws Exception { + testLog.debug("Testing Interleave Controller 6"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + controller.addTestElement(new TestSampler("one")); + sub_1.setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + controller.addTestElement(sub_1); + LoopController sub_2 = new LoopController(); + sub_1.addTestElement(sub_2); + sub_2.setLoops(3); + int counter = 1; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + controller.initialize(); + for (int i = 0; i < 4; i++) { + assertEquals(1, counter); + counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("one", sampler.getName()); + counter++; + } + } + } + + public void testProcessing2() throws Exception { + testLog.debug("Testing Interleave Controller 2"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + sub_1.setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + sub_1.addTestElement(sub_2); + String[] order = new String[] { "one", "three", "two", "three", "four", "three", "one", "three", "two", + "three", "five", "three", "one", "three", "two", "three", "six", "three", "one", "three" }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + while (counter < order.length) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("failed on " + counter, order[counter], sampler.getName()); + counter++; + } + } + } + + public void testProcessing3() throws Exception { + testLog.debug("Testing Interleave Controller 3"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + sub_1.setStyle(InterleaveControl.USE_SUB_CONTROLLERS); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + sub_1.addTestElement(sub_2); + String[] order = new String[] { "one", "three", "two", "three", "four", "five", "six", "seven", "four", + "five", "six", "seven", "four", "five", "six", "seven", "three", "one", "three", "two", "three" }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + while (counter < order.length) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("failed on" + counter, order[counter], sampler.getName()); + counter++; + } + } + } + + public void testProcessing4() throws Exception { + testLog.debug("Testing Interleave Controller 4"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + sub_1.setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + controller.addTestElement(sub_1); + GenericController sub_2 = new GenericController(); + sub_2.addTestElement(new TestSampler("one")); + sub_2.addTestElement(new TestSampler("two")); + sub_1.addTestElement(sub_2); + GenericController sub_3 = new GenericController(); + sub_3.addTestElement(new TestSampler("three")); + sub_3.addTestElement(new TestSampler("four")); + sub_1.addTestElement(sub_3); + String[] order = new String[] { "one", "three", "two", "four" }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + while (counter < order.length) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("failed on" + counter, order[counter], sampler.getName()); + counter++; + } + } + } + + public void testProcessing5() throws Exception { + testLog.debug("Testing Interleave Controller 5"); + GenericController controller = new GenericController(); + InterleaveControl sub_1 = new InterleaveControl(); + sub_1.setStyle(InterleaveControl.USE_SUB_CONTROLLERS); + controller.addTestElement(sub_1); + GenericController sub_2 = new GenericController(); + sub_2.addTestElement(new TestSampler("one")); + sub_2.addTestElement(new TestSampler("two")); + sub_1.addTestElement(sub_2); + GenericController sub_3 = new GenericController(); + sub_3.addTestElement(new TestSampler("three")); + sub_3.addTestElement(new TestSampler("four")); + sub_1.addTestElement(sub_3); + String[] order = new String[] { "one", "two", "three", "four" }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + while (counter < order.length) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("failed on" + counter, order[counter], sampler.getName()); + counter++; + } + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/control/TestLoopController.java b/ApacheJmeter/org/apache/jmeter/control/TestLoopController.java new file mode 100644 index 0000000..d0824ea --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/control/TestLoopController.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.testelement.TestElement; + +public class TestLoopController extends JMeterTestCase { + public TestLoopController(String name) { + super(name); + } + + public void testProcessing() throws Exception { + GenericController controller = new GenericController(); + GenericController sub_1 = new GenericController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + String[] order = new String[] { "one", "two", "three", "four", "five", "six", "seven", "four", "five", + "six", "seven", "four", "five", "six", "seven" }; + int counter = 15; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + for (int i = 0; i < 2; i++) { + assertEquals(15, counter); + counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals(order[counter++], sampler.getName()); + } + } + } + + public void testLoopZeroTimes() throws Exception { + LoopController loop = new LoopController(); + loop.setLoops(0); + loop.addTestElement(new TestSampler("never run")); + loop.initialize(); + assertNull(loop.next()); + } + + public void testInfiniteLoop() throws Exception { + LoopController loop = new LoopController(); + loop.setLoops(-1); + loop.addTestElement(new TestSampler("never run")); + loop.setRunningVersion(true); + loop.initialize(); + for (int i = 0; i < 42; i++) { + assertNotNull(loop.next()); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/control/TestOnceOnlyController.java b/ApacheJmeter/org/apache/jmeter/control/TestOnceOnlyController.java new file mode 100644 index 0000000..ce19c33 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/control/TestOnceOnlyController.java @@ -0,0 +1,331 @@ +/* + * 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.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.testelement.TestElement; + +public class TestOnceOnlyController extends JMeterTestCase { + public TestOnceOnlyController(String name) { + super(name); + } + + public void testProcessing() throws Exception { + GenericController controller = new GenericController(); + GenericController sub_1 = new OnceOnlyController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + String[] interleaveOrder = new String[] { "one", "two" }; + String[] order = new String[] { "", "", "three", "four", "five", "six", "seven", "four", "five", "six", + "seven", "four", "five", "six", "seven" }; + int counter = 15; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + for (int i = 0; i < 4; i++) { + assertEquals(15, counter); + counter = 0; + if (i > 0) { + counter = 2; + } + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + if (i == 0 && counter < 2) { + assertEquals(interleaveOrder[counter], sampler.getName()); + } else { + assertEquals(order[counter], sampler.getName()); + } + counter++; + } + } + } + + public void testProcessing2() throws Exception { + GenericController controller = new GenericController(); + GenericController sub_1 = new OnceOnlyController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addTestElement(new TestSampler("three")); + LoopController sub_2 = new LoopController(); + sub_2.setLoops(3); + OnceOnlyController sub_3 = new OnceOnlyController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addIterationListener(sub_3); + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + String[] interleaveOrder = new String[] { "one", "two" }; + String[] order = new String[] { "", "", "three", "four", "five", "six", "seven", "four", "seven", "four", + "seven" }; + int counter = 11; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + for (int i = 0; i < 4; i++) { + assertEquals(11, counter); + counter = 0; + if (i > 0) { + counter = 2; + } + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + if (i == 0 && counter < 2) { + assertEquals(interleaveOrder[counter], sampler.getName()); + } else { + assertEquals(order[counter], sampler.getName()); + } + counter++; + } + } + } + + public void testInOuterLoop() throws Exception { + // Set up the test plan + LoopController controller = new LoopController(); + final int outerLoopCount = 4; + controller.setLoops(outerLoopCount); + // OnlyOnce samples + OnceOnlyController sub_1 = new OnceOnlyController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + // Outer sample + controller.addTestElement(new TestSampler("three")); + // Inner loop + LoopController sub_2 = new LoopController(); + final int innerLoopCount = 3; + sub_2.setLoops(innerLoopCount); + GenericController sub_3 = new GenericController(); + sub_2.addTestElement(new TestSampler("four")); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + // Sample in inner loop + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + + // Compute the expected sample names + String[] onlyOnceOrder = new String[] { "one", "two" }; + String[] order = new String[] { "three", "four", "five", "six", "seven", "four", "five", "six", + "seven", "four", "five", "six", "seven" }; + // Outer only once + ("three" + ("four" + "five" + "six" + "seven") * innerLoopCount) * outerLoopCount; + int expectedNoSamples = 2 + (1 + (3 + 1) * innerLoopCount) * outerLoopCount; + String[] expectedSamples = new String[expectedNoSamples]; + // The only once samples + for(int i = 0; i < onlyOnceOrder.length; i++) { + expectedSamples[i] = onlyOnceOrder[i]; + } + // The outer sample and the inner loop samples + final int onceOnlySamples = onlyOnceOrder.length; + for(int i = 0; i < order.length * outerLoopCount; i++) { + expectedSamples[onceOnlySamples + i] = order[i % order.length]; + } + + // Execute the test pan + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + + int counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals(expectedSamples[counter], sampler.getPropertyAsString(TestElement.NAME)); + + counter++; + } + assertEquals(expectedNoSamples, counter); + } + + public void testInsideInnerLoop() throws Exception { + // Test plan with OnlyOnceController inside inner loop + // Set up the test plan + LoopController controller = new LoopController(); + final int outerLoopCount = 4; + controller.setLoops(outerLoopCount); + // OnlyOnce samples + OnceOnlyController sub_1 = new OnceOnlyController(); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + // Outer sample + controller.addTestElement(new TestSampler("three")); + // Inner loop + LoopController sub_2 = new LoopController(); + final int innerLoopCount = 3; + sub_2.setLoops(innerLoopCount); + // Sample in inner loop + sub_2.addTestElement(new TestSampler("four")); + // OnlyOnce inside inner loop + OnceOnlyController sub_3 = new OnceOnlyController(); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addIterationListener(sub_3); + // Sample in inner loop + sub_2.addTestElement(new TestSampler("seven")); + controller.addTestElement(sub_2); + + // Compute the expected sample names + String[] onlyOnceOrder = new String[] { "one", "two" }; + String[] order = new String[] { "three", "four", "five", "six", "seven", "four", "seven", "four", "seven" }; + // Outer only once + ("three" + "only once five and six" + ("four" + "seven") * innerLoopCount) * outerLoopCount; + int expectedNoSamples = 2 + (1 + 2 + (1 + 1) * innerLoopCount) * outerLoopCount; + String[] expectedSamples = new String[expectedNoSamples]; + // The only once samples + for(int i = 0; i < onlyOnceOrder.length; i++) { + expectedSamples[i] = onlyOnceOrder[i]; + } + // The outer sample and the inner loop samples + final int onceOnlySamples = onlyOnceOrder.length; + for(int i = 0; i < order.length * outerLoopCount; i++) { + expectedSamples[onceOnlySamples + i] = order[i % order.length]; + } + + // Execute the test pan + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + controller.initialize(); + + int counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals(expectedSamples[counter], sampler.getPropertyAsString(TestElement.NAME)); + + counter++; + } + assertEquals(expectedNoSamples, counter); + } + + // Test skipped for now as behaviour is not yet properly defined + public void notestInsideInterleave() throws Exception { + // Test to show current problem with InterleaveController + // I am not sure if the expected order of the samples + // below are correct, because I am not sure if it is + // properly defined how the InterleaveController and + // OnlyOnceController should function. + + // Test plan with OnlyOnceController inside inner loop + // Set up the test plan + LoopController controller = new LoopController(); + final int outerLoopCount = 4; + controller.setLoops(outerLoopCount); + // OnlyOnce samples + OnceOnlyController sub_1 = new OnceOnlyController(); + sub_1.setName("outer OnlyOnce"); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + // Outer sample + controller.addTestElement(new TestSampler("three")); + // Inner loop + LoopController sub_2 = new LoopController(); + final int innerLoopCount = 5; + sub_2.setLoops(innerLoopCount); + sub_2.addTestElement(new TestSampler("four")); + // OnlyOnce inside inner loop + OnceOnlyController sub_3 = new OnceOnlyController(); + sub_3.setName("In loop OnlyOnce"); + sub_3.addTestElement(new TestSampler("five")); + sub_3.addTestElement(new TestSampler("six")); + sub_2.addTestElement(sub_3); + sub_2.addIterationListener(sub_3); + // InterleaveController in inner loop + InterleaveControl sub_4 = new InterleaveControl(); + sub_4.setStyle(InterleaveControl.USE_SUB_CONTROLLERS); + // OnlyOnce inside InterleaveController + OnceOnlyController sub_5 = new OnceOnlyController(); + sub_5.addTestElement(new TestSampler("seven")); + sub_5.addTestElement(new TestSampler("eight")); + sub_5.setName("Inside InterleaveController OnlyOnce"); + sub_4.addTestElement(sub_5); + sub_4.addIterationListener(sub_5); + // Samples inside InterleaveController + sub_4.addTestElement(new TestSampler("nine")); + sub_4.addTestElement(new TestSampler("ten")); + sub_2.addTestElement(sub_4); + // Sample in inner loop + sub_2.addTestElement(new TestSampler("eleven")); + controller.addTestElement(sub_2); + + // Compute the expected sample names + String[] onlyOnceOrder = new String[] { "one", "two" }; + String[] order = new String[] { "three", "four", "five", "six", "seven", "eight", "eleven", + "four", "nine", "eleven", + "four", "ten", "eleven", + "four", "nine", "eleven", + "four", "ten", "eleven" }; + // Outer only once + ("three" + "only once five and six" + "eight in interleave only once" + ("four" + "interleave" + "eleven") * innerLoopCount) * outerLoopCount; + int expectedNoSamples = 2 + (1 + 2 + 1 + (1 + 1 + 1) * innerLoopCount) * outerLoopCount; + String[] expectedSamples = new String[expectedNoSamples]; + // The only once samples + for (int i = 0; i < onlyOnceOrder.length; i++) { + expectedSamples[i] = onlyOnceOrder[i]; + } + // The outer sample and the inner loop samples + final int onceOnlySamples = onlyOnceOrder.length; + for (int i = 0; i < order.length * outerLoopCount; i++) { + expectedSamples[onceOnlySamples + i] = order[i % order.length]; + } + + // Execute the test pan + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + sub_3.setRunningVersion(true); + sub_4.setRunningVersion(true); + sub_5.setRunningVersion(true); + controller.initialize(); + + int counter = 0; + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + System.out.println("ex: " + expectedSamples[counter] + " ac: " + sampler.getPropertyAsString(TestElement.NAME)); + assertEquals(expectedSamples[counter], sampler.getPropertyAsString(TestElement.NAME)); + + counter++; + } + assertEquals(expectedNoSamples, counter); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/control/TestRandomOrderController.java b/ApacheJmeter/org/apache/jmeter/control/TestRandomOrderController.java new file mode 100644 index 0000000..26634ad --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/control/TestRandomOrderController.java @@ -0,0 +1,73 @@ +/* + * 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.control; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.testelement.TestElement; + +public class TestRandomOrderController extends JMeterTestCase { + + public TestRandomOrderController(String name) { + super(name); + } + + public void testRandomOrder() { + testLog.debug("Testing RandomOrderController"); + RandomOrderController roc = new RandomOrderController(); + roc.addTestElement(new TestSampler("zero")); + roc.addTestElement(new TestSampler("one")); + roc.addTestElement(new TestSampler("two")); + roc.addTestElement(new TestSampler("three")); + TestElement sampler = null; + List usedSamplers = new ArrayList(); + roc.initialize(); + while ((sampler = roc.next()) != null) { + String samplerName = sampler.getName(); + if (usedSamplers.contains(samplerName)) { + assertTrue("Duplicate sampler returned from next()", false); + } + usedSamplers.add(samplerName); + } + assertEquals("All samplers were returned", 4, usedSamplers.size()); + } + + public void testRandomOrderNoElements() { + RandomOrderController roc = new RandomOrderController(); + roc.initialize(); + assertNull(roc.next()); + } + + public void testRandomOrderOneElement() { + RandomOrderController roc = new RandomOrderController(); + roc.addTestElement(new TestSampler("zero")); + TestElement sampler = null; + List usedSamplers = new ArrayList(); + roc.initialize(); + while ((sampler = roc.next()) != null) { + String samplerName = sampler.getName(); + if (usedSamplers.contains(samplerName)) { + assertTrue("Duplicate sampler returned from next()", false); + } + usedSamplers.add(samplerName); + } + assertEquals("All samplers were returned", 1, usedSamplers.size()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/control/TestRunTime.java b/ApacheJmeter/org/apache/jmeter/control/TestRunTime.java new file mode 100644 index 0000000..efc45a2 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/control/TestRunTime.java @@ -0,0 +1,70 @@ +/* + * 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.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.samplers.Sampler; + +/** + * @version $Revision: 773380 $ + */ +public class TestRunTime extends JMeterTestCase { + public TestRunTime(String name) { + super(name); + } + + public void testProcessing() throws Exception { + + RunTime controller = new RunTime(); + controller.setRuntime(10); + TestSampler samp1 = new TestSampler("Sample 1", 500); + TestSampler samp2 = new TestSampler("Sample 2", 490); + + LoopController sub1 = new LoopController(); + sub1.setLoops(2); + sub1.setContinueForever(false); + sub1.addTestElement(samp1); + + LoopController sub2 = new LoopController(); + sub2.setLoops(40); + sub2.setContinueForever(false); + sub2.addTestElement(samp2); + controller.addTestElement(sub1); + controller.addTestElement(sub2); + controller.setRunningVersion(true); + sub1.setRunningVersion(true); + sub2.setRunningVersion(true); + controller.initialize(); + Sampler sampler = null; + int loops = 0; + long now = System.currentTimeMillis(); + while ((sampler = controller.next()) != null) { + loops++; + sampler.sample(null); + } + long elapsed = System.currentTimeMillis() - now; + assertTrue("Should be at least 20 loops "+loops, loops >= 20); + assertTrue("Should be fewer than 30 loops "+loops, loops < 30); + assertTrue("Should take at least 10 seconds "+elapsed, elapsed >= 10000); + assertTrue("Should take less than 12 seconds "+elapsed, elapsed <= 12000); + assertEquals("Sampler 1 should run 2 times", 2, samp1.getSamples()); + assertTrue("Sampler 2 should run >= 18 times", samp2.getSamples() >= 18); + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/control/TestSwitchController.java b/ApacheJmeter/org/apache/jmeter/control/TestSwitchController.java new file mode 100644 index 0000000..8d0bd0d --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/control/TestSwitchController.java @@ -0,0 +1,298 @@ +/* + * 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.control; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.engine.util.ReplaceStringWithFunctions; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestSwitchController extends JMeterTestCase { +// static { +// LoggingManager.setPriority("DEBUG","jmeter"); +// LoggingManager.setTarget(new java.io.PrintWriter(System.out)); +// } + + public TestSwitchController(String name) { + super(name); + } + + // Get next sample and its name + private String nextName(GenericController c) { + Sampler s = c.next(); + String n; + if (s == null) { + return null; + } + n = s.getName(); + return n; + } + + public void test() throws Exception { + runSimpleTests("", "zero"); + } + + public void test0() throws Exception { + runSimpleTests("0", "zero"); + } + + public void test1() throws Exception { + runSimpleTests("1", "one"); + runSimpleTests("one", "one"); // Match by name + } + + public void test2() throws Exception { + runSimpleTests("2", "two"); + runSimpleTests("two", "two"); // Match by name + } + + public void test3() throws Exception { + runSimpleTests("3", "three"); + runSimpleTests("three", "three"); // Match by name + } + + public void test4() throws Exception { + runSimpleTests("4", "zero"); + } + + public void testX() throws Exception { + runSimpleTests("X", null); // should not run any children + runSimpleTest2("X", "one", "Default"); // should match the default entry + } + + private void runSimpleTests(String cond, String exp) throws Exception { + runSimpleTest(cond, exp); + runSimpleTest2(cond, exp, "one"); + } + + /* + * Simple test with single Selection controller + * Generic Controller + * + Sampler "before" + * + Switch Controller + * + + Sampler "zero" + * + + Sampler "one" + * + + Sampler "two" + * + + Sampler "three" + * + Sampler "after" + */ + private void runSimpleTest(String cond, String exp) throws Exception { + GenericController controller = new GenericController(); + + SwitchController switch_cont = new SwitchController(); + switch_cont.setSelection(cond); + + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(switch_cont); + + switch_cont.addTestElement(new TestSampler("zero")); + switch_cont.addTestElement(new TestSampler("one")); + switch_cont.addTestElement(new TestSampler("two")); + switch_cont.addTestElement(new TestSampler("three")); + + controller.addTestElement(new TestSampler("after")); + + controller.initialize(); + + for (int i = 1; i <= 3; i++) { + assertEquals("Loop " + i, "before", nextName(controller)); + if (exp!=null){ + assertEquals("Loop " + i, exp, nextName(controller)); + } + assertEquals("Loop " + i, "after", nextName(controller)); + assertNull(nextName(controller)); + } + } + + // Selection controller with two sub-controllers, but each has only 1 + // child + /* + * Controller + * + Before + * + Switch (cond) + * + + zero + * + + Controller sub_1 + * + + + one + * + + two + * + + Controller sub_2 + * + + + three + * + After + */ + private void runSimpleTest2(String cond, String exp, String sub1Name) throws Exception { + GenericController controller = new GenericController(); + GenericController sub_1 = new GenericController(); + GenericController sub_2 = new GenericController(); + + SwitchController switch_cont = new SwitchController(); + switch_cont.setSelection(cond); + + switch_cont.addTestElement(new TestSampler("zero")); + switch_cont.addTestElement(sub_1); + sub_1.addTestElement(new TestSampler("one")); + sub_1.setName(sub1Name); + + switch_cont.addTestElement(new TestSampler("two")); + + switch_cont.addTestElement(sub_2); + sub_2.addTestElement(new TestSampler("three")); + sub_2.setName("three"); + + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(switch_cont); + controller.addTestElement(new TestSampler("after")); + controller.initialize(); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop="+i,"before", nextName(controller)); + if (exp!=null){ + assertEquals("Loop="+i,exp, nextName(controller)); + } + assertEquals("Loop="+i,"after", nextName(controller)); + assertNull("Loop="+i,nextName(controller)); + } + } + + public void testTest2() throws Exception { + runTest2("", new String[] { "zero" }); + runTest2("0", new String[] { "zero" }); + runTest2("7", new String[] { "zero" }); + runTest2("5", new String[] { "zero" }); + runTest2("4", new String[] { "six" }); + runTest2("3", new String[] { "five" }); + runTest2("1", new String[] { "one", "two" }); + runTest2("2", new String[] { "three", "four" }); + } + + /* + * Test: + * Before + * Selection Controller + * - zero (default) + * - simple controller 1 + * - - one + * - - two + * - simple controller 2 + * - - three + * - - four + * - five + * - six + * After + * + * cond = Switch condition + * exp[] = expected results + */ + private void runTest2(String cond, String exp[]) throws Exception { + int loops = 3; + LoopController controller = new LoopController(); + controller.setLoops(loops); + controller.setContinueForever(false); + GenericController sub_1 = new GenericController(); + GenericController sub_2 = new GenericController(); + + SwitchController switch_cont = new SwitchController(); + switch_cont.setSelection(cond); + + switch_cont.addTestElement(new TestSampler("zero")); + switch_cont.addTestElement(sub_1); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + switch_cont.addTestElement(sub_2); + sub_2.addTestElement(new TestSampler("three")); + sub_2.addTestElement(new TestSampler("four")); + + switch_cont.addTestElement(new TestSampler("five")); + switch_cont.addTestElement(new TestSampler("six")); + + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(switch_cont); + controller.addTestElement(new TestSampler("after")); + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_2.setRunningVersion(true); + switch_cont.setRunningVersion(true); + controller.initialize(); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop:" + i, "before", nextName(controller)); + for (int j = 0; j < exp.length; j++) { + assertEquals("Loop:" + i, exp[j], nextName(controller)); + } + assertEquals("Loop:" + i, "after", nextName(controller)); + } + assertNull("Loops:" + loops, nextName(controller)); + } + + /* + * N.B. Requires ApacheJMeter_functions.jar to be on the classpath, + * otherwise the function cannot be resolved. + */ + public void testFunction() throws Exception { + JMeterContext jmctx = JMeterContextService.getContext(); + Map variables = new HashMap(); + ReplaceStringWithFunctions transformer = new ReplaceStringWithFunctions(new CompoundVariable(), variables); + jmctx.setVariables(new JMeterVariables()); + JMeterVariables jmvars = jmctx.getVariables(); + jmvars.put("VAR", "100"); + StringProperty prop = new StringProperty(SwitchController.SWITCH_VALUE,"${__counter(TRUE,VAR)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + + GenericController controller = new GenericController(); + + SwitchController switch_cont = new SwitchController(); + switch_cont.setProperty(newProp); + + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(switch_cont); + + switch_cont.addTestElement(new TestSampler("0")); + switch_cont.addTestElement(new TestSampler("1")); + switch_cont.addTestElement(new TestSampler("2")); + switch_cont.addTestElement(new TestSampler("3")); + + controller.addTestElement(new TestSampler("after")); + + controller.initialize(); + + assertEquals("100",jmvars.get("VAR")); + + for (int i = 1; i <= 3; i++) { + assertEquals("Loop " + i, "before", nextName(controller)); + assertEquals("Loop " + i, ""+i, nextName(controller)); + assertEquals("Loop " + i, ""+i, jmvars.get("VAR")); + assertEquals("Loop " + i, "after", nextName(controller)); + assertNull(nextName(controller)); + } + int i = 4; + assertEquals("Loop " + i, "before", nextName(controller)); + assertEquals("Loop " + i, "0", nextName(controller)); + assertEquals("Loop " + i, ""+i, jmvars.get("VAR")); + assertEquals("Loop " + i, "after", nextName(controller)); + assertNull(nextName(controller)); + assertEquals("4",jmvars.get("VAR")); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/control/TestThroughputController.java b/ApacheJmeter/org/apache/jmeter/control/TestThroughputController.java new file mode 100644 index 0000000..73d7748 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/control/TestThroughputController.java @@ -0,0 +1,216 @@ +/* + * 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.control; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.testelement.TestElement; + +/** + * This class represents a controller that can controll the number of times that + * it is executed, either by the total number of times the user wants the + * controller executed (BYNUMBER) or by the percentage of time it is called + * (BYPERCENT) + * + */ +public class TestThroughputController extends JMeterTestCase { + public TestThroughputController(String name) { + super(name); + } + + public void testByNumber() throws Exception { + ThroughputController sub_1 = new ThroughputController(); + sub_1.setStyle(ThroughputController.BYNUMBER); + sub_1.setMaxThroughput(2); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + LoopController loop = new LoopController(); + loop.setLoops(5); + loop.addTestElement(new TestSampler("zero")); + loop.addTestElement(sub_1); + loop.addIterationListener(sub_1); + loop.addTestElement(new TestSampler("three")); + + LoopController test = new LoopController(); + test.setLoops(2); + test.addTestElement(loop); + + String[] order = new String[] { "zero", "one", "two", "three", "zero", "one", "two", "three", "zero", + "three", "zero", "three", "zero", "three", "zero", "three", "zero", "three", "zero", "three", + "zero", "three", "zero", "three", }; + sub_1.testStarted(); + test.setRunningVersion(true); + sub_1.setRunningVersion(true); + loop.setRunningVersion(true); + test.initialize(); + for (int counter = 0; counter < order.length; counter++) { + TestElement sampler = test.next(); + assertNotNull(sampler); + assertEquals("Counter: " + counter, order[counter], sampler.getName()); + } + assertNull(test.next()); + sub_1.testEnded(); + } + + public void testByNumberZero() throws Exception { + ThroughputController sub_1 = new ThroughputController(); + sub_1.setStyle(ThroughputController.BYNUMBER); + sub_1.setMaxThroughput(0); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + LoopController controller = new LoopController(); + controller.setLoops(5); + controller.addTestElement(new TestSampler("zero")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + controller.addTestElement(new TestSampler("three")); + + String[] order = new String[] { "zero", "three", "zero", "three", "zero", "three", "zero", "three", "zero", + "three", }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_1.testStarted(); + controller.initialize(); + for (int i = 0; i < 3; i++) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("Counter: " + counter + ", i: " + i, order[counter], sampler.getName()); + counter++; + } + assertEquals(counter, order.length); + counter = 0; + } + sub_1.testEnded(); + } + + public void testByPercent33() throws Exception { + ThroughputController sub_1 = new ThroughputController(); + sub_1.setStyle(ThroughputController.BYPERCENT); + sub_1.setPercentThroughput(33.33f); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + LoopController controller = new LoopController(); + controller.setLoops(6); + controller.addTestElement(new TestSampler("zero")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + controller.addTestElement(new TestSampler("three")); + // Expected results established using the DDA + // algorithm (see + // http://www.siggraph.org/education/materials/HyperGraph/scanline/outprims/drawline.htm): + String[] order = new String[] { "zero", // 0/1 vs. 1/1 -> 0 is + // closer to 33.33 + "three", "zero", // 0/2 vs. 1/2 -> 50.0 is closer to + // 33.33 + "one", "two", "three", "zero", // 1/3 vs. 2/3 -> 33.33 is + // closer to 33.33 + "three", "zero", // 1/4 vs. 2/4 -> 25.0 is closer to + // 33.33 + "three", "zero", // 1/5 vs. 2/5 -> 40.0 is closer to + // 33.33 + "one", "two", "three", "zero", // 2/6 vs. 3/6 -> 33.33 is + // closer to 33.33 + "three", + // etc... + }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_1.testStarted(); + controller.initialize(); + for (int i = 0; i < 3; i++) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("Counter: " + counter + ", i: " + i, order[counter], sampler.getName()); + counter++; + } + assertEquals(counter, order.length); + counter = 0; + } + sub_1.testEnded(); + } + + public void testByPercentZero() throws Exception { + ThroughputController sub_1 = new ThroughputController(); + sub_1.setStyle(ThroughputController.BYPERCENT); + sub_1.setPercentThroughput(0.0f); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + LoopController controller = new LoopController(); + controller.setLoops(150); + controller.addTestElement(new TestSampler("zero")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + controller.addTestElement(new TestSampler("three")); + + String[] order = new String[] { "zero", "three", }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_1.testStarted(); + controller.initialize(); + for (int i = 0; i < 3; i++) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("Counter: " + counter + ", i: " + i, order[counter % order.length], sampler.getName()); + counter++; + } + assertEquals(counter, 150 * order.length); + counter = 0; + } + sub_1.testEnded(); + } + + public void testByPercent100() throws Exception { + ThroughputController sub_1 = new ThroughputController(); + sub_1.setStyle(ThroughputController.BYPERCENT); + sub_1.setPercentThroughput(100.0f); + sub_1.addTestElement(new TestSampler("one")); + sub_1.addTestElement(new TestSampler("two")); + + LoopController controller = new LoopController(); + controller.setLoops(150); + controller.addTestElement(new TestSampler("zero")); + controller.addTestElement(sub_1); + controller.addIterationListener(sub_1); + controller.addTestElement(new TestSampler("three")); + + String[] order = new String[] { "zero", "one", "two", "three", }; + int counter = 0; + controller.setRunningVersion(true); + sub_1.setRunningVersion(true); + sub_1.testStarted(); + controller.initialize(); + for (int i = 0; i < 3; i++) { + TestElement sampler = null; + while ((sampler = controller.next()) != null) { + assertEquals("Counter: " + counter + ", i: " + i, order[counter % order.length], sampler.getName()); + counter++; + } + assertEquals(counter, 150 * order.length); + counter = 0; + } + sub_1.testEnded(); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/control/TestWhileController.java b/ApacheJmeter/org/apache/jmeter/control/TestWhileController.java new file mode 100644 index 0000000..4b5de76 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/control/TestWhileController.java @@ -0,0 +1,365 @@ +/* + * 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.control; + +import org.apache.jmeter.engine.util.ValueReplacer; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.junit.stubs.TestSampler; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterThread; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestWhileController extends JMeterTestCase { +// static { +// LoggingManager.setPriority("DEBUG","jmeter"); +// LoggingManager.setTarget(new java.io.PrintWriter(System.out)); +// } + + public TestWhileController(String name) { + super(name); + } + + private JMeterContext jmctx; + private JMeterVariables jmvars; + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + jmctx.setVariables(new JMeterVariables()); + jmvars = jmctx.getVariables(); + } + + private void setLastSampleStatus(boolean status){ + jmvars.put(JMeterThread.LAST_SAMPLE_OK,Boolean.toString(status)); + } + + private void setRunning(TestElement el){ + PropertyIterator pi = el.propertyIterator(); + while(pi.hasNext()){ + pi.next().setRunningVersion(true); + } + } + + // Get next sample and its name + private String nextName(GenericController c) { + Sampler s = c.next(); + if (s == null) { + return null; + } + return s.getName(); + } + + // While (blank), previous sample OK - should loop until false + public void testBlankPrevOK() throws Exception { +// log.info("testBlankPrevOK"); + runtestPrevOK(""); + } + + // While (LAST), previous sample OK - should loop until false + public void testLastPrevOK() throws Exception { +// log.info("testLASTPrevOK"); + runtestPrevOK("LAST"); + } + + private static final String OTHER = "X"; // Dummy for testing functions + + // While (LAST), previous sample OK - should loop until false + public void testOtherPrevOK() throws Exception { +// log.info("testOtherPrevOK"); + runtestPrevOK(OTHER); + } + + private void runtestPrevOK(String type) throws Exception { + GenericController controller = new GenericController(); + WhileController while_cont = new WhileController(); + setLastSampleStatus(true); + while_cont.setCondition(type); + while_cont.addTestElement(new TestSampler("one")); + while_cont.addTestElement(new TestSampler("two")); + while_cont.addTestElement(new TestSampler("three")); + controller.addTestElement(while_cont); + controller.addTestElement(new TestSampler("four")); + controller.initialize(); + assertEquals("one", nextName(controller)); + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + assertEquals("one", nextName(controller)); + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + assertEquals("one", nextName(controller)); + setLastSampleStatus(false); + if (type.equals(OTHER)){ + while_cont.setCondition("false"); + } + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + setLastSampleStatus(true); + if (type.equals(OTHER)) { + while_cont.setCondition(OTHER); + } + assertEquals("one", nextName(controller)); + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + setLastSampleStatus(false); + if (type.equals(OTHER)) { + while_cont.setCondition("false"); + } + assertEquals("four", nextName(controller)); + assertNull(nextName(controller)); + setLastSampleStatus(true); + if (type.equals(OTHER)) { + while_cont.setCondition(OTHER); + } + assertEquals("one", nextName(controller)); + } + + // While (blank), previous sample failed - should run once + public void testBlankPrevFailed() throws Exception { +// log.info("testBlankPrevFailed"); + GenericController controller = new GenericController(); + controller.setRunningVersion(true); + WhileController while_cont = new WhileController(); + setLastSampleStatus(false); + while_cont.setCondition(""); + while_cont.addTestElement(new TestSampler("one")); + while_cont.addTestElement(new TestSampler("two")); + controller.addTestElement(while_cont); + controller.addTestElement(new TestSampler("three")); + controller.initialize(); + assertEquals("one", nextName(controller)); + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + assertNull(nextName(controller)); + // Run entire test again + assertEquals("one", nextName(controller)); + assertEquals("two", nextName(controller)); + assertEquals("three", nextName(controller)); + assertNull(nextName(controller)); + } + + /* + * Generic Controller + * - before + * - While Controller ${VAR} + * - - one + * - - two + * - - Simple Controller + * - - - three + * - - - four + * - after + */ + public void testVariable1() throws Exception { + GenericController controller = new GenericController(); + WhileController while_cont = new WhileController(); + setLastSampleStatus(false); + while_cont.setCondition("${VAR}"); + jmvars.put("VAR", ""); + ValueReplacer vr = new ValueReplacer(); + vr.replaceValues(while_cont); + setRunning(while_cont); + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(while_cont); + while_cont.addTestElement(new TestSampler("one")); + while_cont.addTestElement(new TestSampler("two")); + GenericController simple = new GenericController(); + while_cont.addTestElement(simple); + simple.addTestElement(new TestSampler("three")); + simple.addTestElement(new TestSampler("four")); + controller.addTestElement(new TestSampler("after")); + controller.initialize(); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + assertEquals("Loop: "+i,"one", nextName(controller)); + assertEquals("Loop: "+i,"two", nextName(controller)); + assertEquals("Loop: "+i,"three", nextName(controller)); + assertEquals("Loop: "+i,"four", nextName(controller)); + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + jmvars.put("VAR", "LAST"); // Should not enter the loop + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + jmvars.put("VAR", ""); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + if (i==1) { + assertEquals("Loop: "+i,"one", nextName(controller)); + assertEquals("Loop: "+i,"two", nextName(controller)); + assertEquals("Loop: "+i,"three", nextName(controller)); + jmvars.put("VAR", "LAST"); // Should not enter the loop next time + assertEquals("Loop: "+i,"four", nextName(controller)); + } + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + } + + // Test with SimpleController as first item + public void testVariable2() throws Exception { + GenericController controller = new GenericController(); + WhileController while_cont = new WhileController(); + setLastSampleStatus(false); + while_cont.setCondition("${VAR}"); + jmvars.put("VAR", ""); + ValueReplacer vr = new ValueReplacer(); + vr.replaceValues(while_cont); + setRunning(while_cont); + controller.addTestElement(new TestSampler("before")); + controller.addTestElement(while_cont); + GenericController simple = new GenericController(); + while_cont.addTestElement(simple); + simple.addTestElement(new TestSampler("one")); + simple.addTestElement(new TestSampler("two")); + while_cont.addTestElement(new TestSampler("three")); + while_cont.addTestElement(new TestSampler("four")); + controller.addTestElement(new TestSampler("after")); + controller.initialize(); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + assertEquals("Loop: "+i,"one", nextName(controller)); + assertEquals("Loop: "+i,"two", nextName(controller)); + assertEquals("Loop: "+i,"three", nextName(controller)); + assertEquals("Loop: "+i,"four", nextName(controller)); + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + jmvars.put("VAR", "LAST"); // Should not enter the loop + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + jmvars.put("VAR", ""); + for (int i = 1; i <= 3; i++) { + assertEquals("Loop: "+i,"before", nextName(controller)); + if (i==1){ + assertEquals("Loop: "+i,"one", nextName(controller)); + assertEquals("Loop: "+i,"two", nextName(controller)); + jmvars.put("VAR", "LAST"); // Should not enter the loop next time + // But should continue to the end of the loop + assertEquals("Loop: "+i,"three", nextName(controller)); + assertEquals("Loop: "+i,"four", nextName(controller)); + } + assertEquals("Loop: "+i,"after", nextName(controller)); + assertNull("Loop: "+i,nextName(controller)); + } + } + + // While LAST, previous sample failed - should not run + public void testLASTPrevFailed() throws Exception { +// log.info("testLastPrevFailed"); + runTestPrevFailed("LAST"); + } + + // While False, previous sample failed - should not run + public void testfalsePrevFailed() throws Exception { +// log.info("testFalsePrevFailed"); + runTestPrevFailed("False"); + } + + private void runTestPrevFailed(String s) throws Exception { + GenericController controller = new GenericController(); + WhileController while_cont = new WhileController(); + setLastSampleStatus(false); + while_cont.setCondition(s); + while_cont.addTestElement(new TestSampler("one")); + while_cont.addTestElement(new TestSampler("two")); + controller.addTestElement(while_cont); + controller.addTestElement(new TestSampler("three")); + controller.initialize(); + assertEquals("three", nextName(controller)); + assertNull(nextName(controller)); + assertEquals("three", nextName(controller)); + assertNull(nextName(controller)); + } + + public void testLastFailedBlank() throws Exception{ + runTestLastFailed(""); + } + + public void testLastFailedLast() throws Exception{ + runTestLastFailed("LAST"); + } + + // Should behave the same for blank and LAST because success on input + private void runTestLastFailed(String s) throws Exception { + GenericController controller = new GenericController(); + controller.addTestElement(new TestSampler("1")); + WhileController while_cont = new WhileController(); + controller.addTestElement(while_cont); + while_cont.setCondition(s); + GenericController sub = new GenericController(); + while_cont.addTestElement(sub); + sub.addTestElement(new TestSampler("2")); + sub.addTestElement(new TestSampler("3")); + + controller.addTestElement(new TestSampler("4")); + + setLastSampleStatus(true); + controller.initialize(); + assertEquals("1", nextName(controller)); + assertEquals("2", nextName(controller)); + setLastSampleStatus(false); + assertEquals("3", nextName(controller)); + assertEquals("4", nextName(controller)); + assertNull(nextName(controller)); + } + + // Tests for Stack Overflow (bug 33954) + public void testAlwaysFailOK() throws Exception { + runTestAlwaysFail(true); // Should be OK + } + + public void testAlwaysFailBAD() throws Exception { + runTestAlwaysFail(false); + } + + private void runTestAlwaysFail(boolean other) { + LoopController controller = new LoopController(); + controller.setContinueForever(true); + controller.setLoops(-1); + WhileController while_cont = new WhileController(); + setLastSampleStatus(false); + while_cont.setCondition("false"); + while_cont.addTestElement(new TestSampler("one")); + while_cont.addTestElement(new TestSampler("two")); + controller.addTestElement(while_cont); + if (other) { + controller.addTestElement(new TestSampler("three")); + } + controller.initialize(); + try { + if (other) { + assertEquals("three", nextName(controller)); + } else { + assertNull(nextName(controller)); + } + } catch (StackOverflowError e) { + // e.printStackTrace(); + fail(e.toString()); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/engine/TestTreeCloner.java b/ApacheJmeter/org/apache/jmeter/engine/TestTreeCloner.java new file mode 100644 index 0000000..01ecc36 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/engine/TestTreeCloner.java @@ -0,0 +1,81 @@ +/* + * 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.engine; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.control.GenericController; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jorphan.collections.ListedHashTree; + +public class TestTreeCloner extends junit.framework.TestCase { + public TestTreeCloner(String name) { + super(name); + } + + public void testCloning() throws Exception { + ListedHashTree original = new ListedHashTree(); + GenericController controller = new GenericController(); + controller.setName("controller"); + Arguments args = new Arguments(); + args.setName("args"); + TestPlan plan = new TestPlan(); + plan.addParameter("server", "jakarta"); + original.add(controller, args); + original.add(plan); + ResultCollector listener = new ResultCollector(); + listener.setName("Collector"); + original.add(controller, listener); + TreeCloner cloner = new TreeCloner(); + original.traverse(cloner); + ListedHashTree newTree = cloner.getClonedTree(); + assertTrue(original != newTree); + assertEquals(original.size(), newTree.size()); + assertEquals(original.getTree(original.getArray()[0]).size(), newTree.getTree(newTree.getArray()[0]).size()); + assertTrue(original.getArray()[0] != newTree.getArray()[0]); + assertEquals(((GenericController) original.getArray()[0]).getName(), ((GenericController) newTree + .getArray()[0]).getName()); + assertSame(original.getTree(original.getArray()[0]).getArray()[1], newTree.getTree(newTree.getArray()[0]) + .getArray()[1]); + TestPlan clonedTestPlan = (TestPlan) newTree.getArray()[1]; + clonedTestPlan.setRunningVersion(true); + clonedTestPlan.recoverRunningVersion(); + assertTrue(!plan.getUserDefinedVariablesAsProperty().isRunningVersion()); + assertTrue(clonedTestPlan.getUserDefinedVariablesAsProperty().isRunningVersion()); + Arguments vars = (Arguments) plan.getUserDefinedVariablesAsProperty().getObjectValue(); + PropertyIterator iter = ((CollectionProperty) vars.getProperty(Arguments.ARGUMENTS)).iterator(); + while (iter.hasNext()) { + JMeterProperty argProp = iter.next(); + assertTrue(!argProp.isRunningVersion()); + assertTrue(argProp.getObjectValue() instanceof Argument); + Argument arg = (Argument) argProp.getObjectValue(); + arg.setValue("yahoo"); + assertEquals("yahoo", arg.getValue()); + } + vars = (Arguments) clonedTestPlan.getUserDefinedVariablesAsProperty().getObjectValue(); + iter = vars.propertyIterator(); + while (iter.hasNext()) { + assertTrue(iter.next().isRunningVersion()); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/engine/util/PackageTest.java b/ApacheJmeter/org/apache/jmeter/engine/util/PackageTest.java new file mode 100644 index 0000000..8b995ea --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/engine/util/PackageTest.java @@ -0,0 +1,218 @@ +/* + * 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. + * + */ + +/* + * Created on Jul 25, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +/* + * To run this test stand-alone, ensure that ApacheJMeter_functions.jar is on the classpath, + * as it is needed to resolve the functions. + */ +public class PackageTest extends JMeterTestCase { + private Map variables; + + private SampleResult result; + + private ReplaceStringWithFunctions transformer; + + public PackageTest(String arg0) { + super(arg0); + } + + private JMeterContext jmctx = null; + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + variables = new HashMap(); + variables.put("my_regex", ".*"); + variables.put("server", "jakarta.apache.org"); + result = new SampleResult(); + result.setResponseData("hello world costs: $3.47,$5.67", null); + transformer = new ReplaceStringWithFunctions(new CompoundVariable(), variables); + jmctx.setVariables(new JMeterVariables()); + jmctx.setSamplingStarted(true); + jmctx.setPreviousResult(result); + jmctx.getVariables().put("server", "jakarta.apache.org"); + jmctx.getVariables().put("my_regex", ".*"); + } + + public void testFunctionParse1() throws Exception { + StringProperty prop = new StringProperty("date", "${__javaScript((new Date().getDate() / 100).toString()." + + "substr(${__javaScript(1+1,d\\,ay)}\\,2),heute)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + newProp.recoverRunningVersion(null); + assertTrue(Integer.parseInt(newProp.getStringValue()) > -1); + assertEquals("2", jmctx.getVariables().getObject("d,ay")); + } + + public void testParseExample1() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction((.*),$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("hello world", newProp.getStringValue()); + } + + public void testParseExample2() throws Exception { + StringProperty prop = new StringProperty("html", "It should say:\\${${__regexFunction((.*),$1$)}}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("It should say:${hello world}", newProp.getStringValue()); + } + + public void testParseExample3() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction((.*),$1$)}" + + "${__regexFunction((.*o)(.*o)(.*)," + "$1$$3$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("hello worldhellorld", newProp.getStringValue()); + } + + public void testParseExample4() throws Exception { + StringProperty prop = new StringProperty("html", "${non-existing function}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("${non-existing function}", newProp.getStringValue()); + } + + public void testParseExample6() throws Exception { + StringProperty prop = new StringProperty("html", "${server}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("jakarta.apache.org", newProp.getStringValue()); + } + + public void testParseExample5() throws Exception { + StringProperty prop = new StringProperty("html", ""); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.StringProperty", newProp.getClass().getName()); + assertEquals("", newProp.getStringValue()); + } + + public void testParseExample7() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction(\\<([a-z]*)\\>,$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("html", newProp.getStringValue()); + } + + public void testParseExample8() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction((\\\\$\\d+\\.\\d+),$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("$3.47", newProp.getStringValue()); + } + + public void testParseExample9() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction(([$]\\d+\\.\\d+),$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("$3.47", newProp.getStringValue()); + } + + public void testParseExample10() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction(\\ " + + "(\\\\\\$\\d+\\.\\d+\\,\\\\$\\d+\\.\\d+),$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("$3.47,$5.67", newProp.getStringValue()); + } + + // Escaped dollar commma and backslash with no variable reference + public void testParseExample11() throws Exception { + StringProperty prop = new StringProperty("html", "\\$a \\, \\\\ \\x \\ jakarta.apache.org"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.StringProperty", newProp.getClass().getName()); + assertEquals("\\$a \\, \\\\ \\x \\ jakarta.apache.org", newProp.getStringValue()); + } + + // N.B. See Bug 46831 which wanted to changed the behaviour of \$ + // It's too late now, as this would invalidate some existing test plans, + // so document the current behaviour with some more tests. + + // Escaped dollar commma and backslash with variable reference + public void testParseExample12() throws Exception { + StringProperty prop = new StringProperty("html", "\\$a \\, \\\\ \\x \\ ${server} \\$b \\, \\\\ cd"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + // N.B. Backslashes are removed before dollar, comma and backslash + assertEquals("$a , \\ \\x \\ jakarta.apache.org $b , \\ cd", newProp.getStringValue()); + } + + // Escaped dollar commma and backslash with missing variable reference + public void testParseExample13() throws Exception { + StringProperty prop = new StringProperty("html", "\\$a \\, \\\\ \\x \\ ${missing} \\$b \\, \\\\ cd"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + // N.B. Backslashes are removed before dollar, comma and backslash + assertEquals("$a , \\ \\x \\ ${missing} $b , \\ cd", newProp.getStringValue()); + } + + // Escaped dollar commma and backslash with missing function reference + public void testParseExample14() throws Exception { + StringProperty prop = new StringProperty("html", "\\$a \\, \\\\ \\x \\ ${__missing(a)} \\$b \\, \\\\ cd"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + // N.B. Backslashes are removed before dollar, comma and backslash + assertEquals("$a , \\ \\x \\ ${__missing(a)} $b , \\ cd", newProp.getStringValue()); + } + + public void testNestedExample1() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction((${my_regex})," + + "$1$)}${__regexFunction((.*o)(.*o)(.*)" + ",$1$$3$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("hello worldhellorld", newProp.getStringValue()); + } + + public void testNestedExample2() throws Exception { + StringProperty prop = new StringProperty("html", "${__regexFunction((${my_regex}),$1$)}"); + JMeterProperty newProp = transformer.transformValue(prop); + newProp.setRunningVersion(true); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", newProp.getClass().getName()); + assertEquals("hello world", newProp.getStringValue()); + } + +} diff --git a/ApacheJmeter/org/apache/jmeter/engine/util/TestValueReplacer.java b/ApacheJmeter/org/apache/jmeter/engine/util/TestValueReplacer.java new file mode 100644 index 0000000..22184f3 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/engine/util/TestValueReplacer.java @@ -0,0 +1,91 @@ +/* + * 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.engine.util; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestValueReplacer extends JMeterTestCase { + private TestPlan variables; + + public TestValueReplacer(String name) { + super(name); + } + + /** {@inheritDoc} */ + @Override + public void setUp() { + variables = new TestPlan(); + variables.addParameter("server", "jakarta.apache.org"); + variables.addParameter("username", "jack"); + // The following used to be jacks_password, but the Arguments class uses + // HashMap for which the order is not defined. + variables.addParameter("password", "his_password"); + variables.addParameter("regex", ".*"); + JMeterVariables vars = new JMeterVariables(); + vars.put("server", "jakarta.apache.org"); + JMeterContextService.getContext().setVariables(vars); + JMeterContextService.getContext().setSamplingStarted(true); + } + + public void testReverseReplacement() throws Exception { + ValueReplacer replacer = new ValueReplacer(variables); + assertTrue(variables.getUserDefinedVariables().containsKey("server")); + assertTrue(replacer.containsKey("server")); + TestElement element = new TestPlan(); + element.setProperty(new StringProperty("domain", "jakarta.apache.org")); + List argsin = new ArrayList(); + argsin.add("username is jack"); + argsin.add("his_password"); + element.setProperty(new CollectionProperty("args", argsin)); + replacer.reverseReplace(element); + assertEquals("${server}", element.getPropertyAsString("domain")); + @SuppressWarnings("unchecked") + List args = (List) element.getProperty("args").getObjectValue(); + assertEquals("username is ${username}", args.get(0).getStringValue()); + assertEquals("${password}", args.get(1).getStringValue()); + } + + public void testReplace() throws Exception { + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(variables.getUserDefinedVariables()); + TestElement element = new ConfigTestElement(); + element.setProperty(new StringProperty("domain", "${server}")); + replacer.replaceValues(element); + //log.debug("domain property = " + element.getProperty("domain")); + element.setRunningVersion(true); + assertEquals("jakarta.apache.org", element.getPropertyAsString("domain")); + } + + /** {@inheritDoc} */ + @Override + protected void tearDown() throws Exception { + JMeterContextService.getContext().setSamplingStarted(false); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/extractor/TestRegexExtractor.java b/ApacheJmeter/org/apache/jmeter/extractor/TestRegexExtractor.java new file mode 100644 index 0000000..4594a42 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/extractor/TestRegexExtractor.java @@ -0,0 +1,412 @@ +/* + * 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.extractor; + + +import java.net.URL; + +import junit.framework.TestCase; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestRegexExtractor extends TestCase { + private RegexExtractor extractor; + + private SampleResult result; + + private JMeterVariables vars; + + public TestRegexExtractor(String name) { + super(name); + } + + private JMeterContext jmctx; + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + extractor = new RegexExtractor(); + extractor.setThreadContext(jmctx);// This would be done by the run + // command + extractor.setRefName("regVal"); + result = new SampleResult(); + String data = "" + "" + "LIS_OK" + + "" + "" + + "" + "0" + + "1" + "" + + "5" + "" + + "6" + "" + + "" + ""; + result.setResponseData(data, null); + result.setResponseHeaders("Header1: Value1\nHeader2: Value2"); + result.setResponseCode("abcd"); + result.setResponseMessage("The quick brown fox"); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + } + + public void testVariableExtraction0() throws Exception { + extractor.setRegex("<(value) field=\""); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(0); + extractor.process(); + assertEquals("value", vars.get("regVal")); + } + + public void testVariableExtraction() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$2$"); + extractor.setMatchNumber(2); + extractor.process(); + assertEquals("5", vars.get("regVal")); + assertEquals("pinposition2", vars.get("regVal_g1")); + assertEquals("5", vars.get("regVal_g2")); + assertEquals("5", vars.get("regVal_g0")); + assertNull(vars.get("regVal_g3")); + assertEquals("2",vars.get("regVal_g")); + } + + private static void templateSetup(RegexExtractor rex, String tmp) { + rex.setRegex(""); + rex.setMatchNumber(1); + rex.setTemplate(tmp); + rex.process(); + } + + public void testTemplate1() throws Exception { + templateSetup(extractor, ""); + assertEquals("", vars.get("regVal_g0")); + assertEquals("xmlext", vars.get("regVal_g1")); + assertEquals("query", vars.get("regVal_g2")); + assertEquals("ret", vars.get("regVal_g3")); + assertEquals("", vars.get("regVal")); + assertEquals("3",vars.get("regVal_g")); + } + + public void testTemplate2() throws Exception { + templateSetup(extractor, "ABC"); + assertEquals("ABC", vars.get("regVal")); + } + + public void testTemplate3() throws Exception { + templateSetup(extractor, "$2$"); + assertEquals("query", vars.get("regVal")); + } + + public void testTemplate4() throws Exception { + templateSetup(extractor, "PRE$2$"); + assertEquals("PREquery", vars.get("regVal")); + } + + public void testTemplate5() throws Exception { + templateSetup(extractor, "$2$POST"); + assertEquals("queryPOST", vars.get("regVal")); + } + + public void testTemplate6() throws Exception { + templateSetup(extractor, "$2$$1$"); + assertEquals("queryxmlext", vars.get("regVal")); + } + + public void testTemplate7() throws Exception { + templateSetup(extractor, "$2$MID$1$"); + assertEquals("queryMIDxmlext", vars.get("regVal")); + } + + public void testVariableExtraction2() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(3); + extractor.process(); + assertEquals("pinposition3", vars.get("regVal")); + } + + public void testVariableExtraction6() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$2$"); + extractor.setMatchNumber(4); + extractor.setDefaultValue("default"); + extractor.process(); + assertEquals("default", vars.get("regVal")); + } + + public void testVariableExtraction3() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("_$1$"); + extractor.setMatchNumber(2); + extractor.process(); + assertEquals("_pinposition2", vars.get("regVal")); + } + + public void testVariableExtraction5() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1);// Set up the non-wild variables + extractor.process(); + assertNotNull(vars.get("regVal")); + assertEquals("2",vars.get("regVal_g")); + assertNotNull(vars.get("regVal_g0")); + assertNotNull(vars.get("regVal_g1")); + assertNotNull(vars.get("regVal_g2")); + + extractor.setMatchNumber(-1); + extractor.process(); + assertNotNull(vars.get("regVal"));// Should not clear this? + assertNull(vars.get("regVal_g")); + assertNull(vars.get("regVal_g1")); + assertNull(vars.get("regVal_g2")); + assertEquals("3", vars.get("regVal_matchNr")); + assertEquals("pinposition1", vars.get("regVal_1")); + assertEquals("pinposition2", vars.get("regVal_2")); + assertEquals("pinposition3", vars.get("regVal_3")); + assertEquals("2", vars.get("regVal_1_g")); + assertEquals("pinposition1", vars.get("regVal_1_g1")); + assertEquals("1", vars.get("regVal_1_g2")); + assertEquals("6", vars.get("regVal_3_g2")); + assertEquals("1", vars.get("regVal_1_g0")); + assertNull(vars.get("regVal_4")); + + // Check old values don't hang around: + extractor.setRegex("(\\w+)count"); // fewer matches + extractor.process(); + assertEquals("2", vars.get("regVal_matchNr")); + assertEquals("position", vars.get("regVal_1")); + assertEquals("1", vars.get("regVal_1_g")); + assertEquals("position", vars.get("regVal_1_g1")); + assertNull("Unused variables should be null", vars.get("regVal_1_g2")); + assertEquals("invalidpin", vars.get("regVal_2")); + assertEquals("1", vars.get("regVal_2_g")); + assertEquals("invalidpin", vars.get("regVal_2_g1")); + assertNull("Unused variables should be null", vars.get("regVal_2_g2")); + assertEquals("1", vars.get("regVal_1_g")); + assertNull("Unused variables should be null", vars.get("regVal_3")); + assertNull("Unused variables should be null", vars.get("regVal_3_g")); + assertNull("Unused variables should be null", vars.get("regVal_3_g0")); + assertNull("Unused variables should be null", vars.get("regVal_3_g1")); + assertNull("Unused variables should be null", vars.get("regVal_3_g2")); + + // Check when match fails + extractor.setRegex("xxxx(.)(.)"); + extractor.process(); + assertEquals("0", vars.get("regVal_matchNr")); + assertNull("Unused variables should be null", vars.get("regVal_1")); + assertNull("Unused variables should be null", vars.get("regVal_1_g0")); + assertNull("Unused variables should be null", vars.get("regVal_1_g1")); + assertNull("Unused variables should be null", vars.get("regVal_1_g2")); + } + + public void testVariableExtraction7() throws Exception { + extractor.setRegex("Header1: (\\S+)"); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1); + assertTrue("useBody should be true", extractor.useBody()); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useURL should be false", extractor.useUrl()); + extractor.setUseField(RegexExtractor.USE_BODY); + assertTrue("useBody should be true", extractor.useBody()); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useURL should be false", extractor.useUrl()); + extractor.setUseField(RegexExtractor.USE_HDRS); + assertTrue("useHdrs should be true", extractor.useHeaders()); + assertFalse("useBody should be false", extractor.useBody()); + assertFalse("useURL should be false", extractor.useUrl()); + extractor.process(); + assertEquals("Value1", vars.get("regVal")); + extractor.setUseField(RegexExtractor.USE_URL); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useBody should be false", extractor.useBody()); + assertTrue("useURL should be true", extractor.useUrl()); + } + + public void testVariableExtraction8() throws Exception { + extractor.setRegex("http://jakarta\\.apache\\.org/(\\w+)"); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1); + extractor.setUseField(RegexExtractor.USE_URL); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useBody should be false", extractor.useBody()); + assertTrue("useURL should be true", extractor.useUrl()); + extractor.process(); + assertNull(vars.get("regVal")); + result.setURL(new URL("http://jakarta.apache.org/index.html?abcd")); + extractor.process(); + assertEquals("index",vars.get("regVal")); + } + + public void testVariableExtraction9() throws Exception { + extractor.setRegex("(\\w+)"); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1); + extractor.setUseField(RegexExtractor.USE_CODE); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useBody should be false", extractor.useBody()); + assertFalse("useURL should be false", extractor.useUrl()); + assertFalse("useMessage should be false", extractor.useMessage()); + assertTrue("useCode should be true", extractor.useCode()); + extractor.process(); + assertEquals("abcd",vars.get("regVal")); + extractor.setUseField(RegexExtractor.USE_MESSAGE); + assertFalse("useHdrs should be false", extractor.useHeaders()); + assertFalse("useBody should be false", extractor.useBody()); + assertFalse("useURL should be false", extractor.useUrl()); + assertTrue("useMessage should be true", extractor.useMessage()); + assertFalse("useCode should be falsee", extractor.useCode()); + extractor.setMatchNumber(3); + extractor.process(); + assertEquals("brown",vars.get("regVal")); + } + + public void testNoDefault() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$2$"); + extractor.setMatchNumber(4); + //extractor.setDefaultValue("default"); + vars.put("regVal", "initial"); + assertEquals("initial", vars.get("regVal")); + extractor.process(); + assertEquals("initial", vars.get("regVal")); + } + + public void testDefault() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$2$"); + extractor.setMatchNumber(999); + extractor.setDefaultValue("default"); + vars.put("regVal", "initial"); + assertEquals("initial", vars.get("regVal")); + extractor.process(); + assertEquals("default", vars.get("regVal")); + assertNull(vars.get("regVal_g0")); + assertNull(vars.get("regVal_g1")); + } + + public void testStaleVariables() throws Exception { + extractor.setRegex("(\\d+)"); + extractor.setTemplate("$2$"); + extractor.setMatchNumber(1); + extractor.setDefaultValue("default"); + extractor.process(); + assertEquals("1", vars.get("regVal")); + assertEquals("1", vars.get("regVal_g2")); + assertEquals("2", vars.get("regVal_g")); + assertNotNull(vars.get("regVal_g0")); + assertNotNull(vars.get("regVal_g1")); + // Now rerun with match fail + extractor.setMatchNumber(10); + extractor.process(); + assertEquals("default", vars.get("regVal")); + assertNull(vars.get("regVal_g0")); + assertNull(vars.get("regVal_g1")); + assertNull(vars.get("regVal_g")); + } + + public void testScope1() throws Exception { + result.setResponseData("ONE", "ISO-8859-1"); + extractor.setScopeParent(); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1); + extractor.setRegex("([^<]+)<"); + extractor.setDefaultValue("NOTFOUND"); + extractor.process(); + assertEquals("ONE", vars.get("regVal")); + extractor.setScopeAll(); + extractor.process(); + assertEquals("ONE", vars.get("regVal")); + extractor.setScopeChildren(); + extractor.process(); + assertEquals("NOTFOUND", vars.get("regVal")); + } + + public void testScope2() throws Exception { + result.sampleStart(); + result.setResponseData("<title>PARENT", "ISO-8859-1"); + result.sampleEnd(); + SampleResult child1 = new SampleResult(); + child1.sampleStart(); + child1.setResponseData("ONE", "ISO-8859-1"); + child1.sampleEnd(); + result.addSubResult(child1); + SampleResult child2 = new SampleResult(); + child2.sampleStart(); + child2.setResponseData("TWO", "ISO-8859-1"); + child2.sampleEnd(); + result.addSubResult(child2); + SampleResult child3 = new SampleResult(); + child3.sampleStart(); + child3.setResponseData("THREE", "ISO-8859-1"); + child3.sampleEnd(); + result.addSubResult(child3); + extractor.setScopeParent(); + extractor.setTemplate("$1$"); + extractor.setMatchNumber(1); + extractor.setRegex("([^<]+)<"); + extractor.setDefaultValue("NOTFOUND"); + extractor.process(); + assertEquals("PARENT", vars.get("regVal")); + extractor.setScopeAll(); + extractor.setMatchNumber(3); + extractor.process(); + assertEquals("TWO", vars.get("regVal")); + extractor.setScopeChildren(); + extractor.process(); + assertEquals("THREE", vars.get("regVal")); + extractor.setRegex(">(...)<"); + extractor.setScopeAll(); + extractor.setMatchNumber(2); + extractor.process(); + assertEquals("TWO", vars.get("regVal")); + + // Match all + extractor.setRegex("<title>([^<]+)<"); + extractor.setMatchNumber(-1); + + extractor.setScopeParent(); + extractor.process(); + assertEquals("1", vars.get("regVal_matchNr")); + extractor.setScopeAll(); + extractor.process(); + assertEquals("4", vars.get("regVal_matchNr")); + extractor.setScopeChildren(); + extractor.process(); + assertEquals("3", vars.get("regVal_matchNr")); + + // Check random number + extractor.setMatchNumber(0); + extractor.setScopeParent(); + extractor.process(); + assertEquals("PARENT", vars.get("regVal")); + extractor.setRegex("(<title>)"); + extractor.setScopeAll(); + extractor.process(); + assertEquals("<title>", vars.get("regVal")); + extractor.setScopeChildren(); + extractor.process(); + assertEquals("<title>", vars.get("regVal")); + extractor.setRegex("<title>(...)<"); + extractor.setScopeAll(); + extractor.process(); + final String found = vars.get("regVal"); + assertTrue(found.equals("ONE") || found.equals("TWO")); + } + +} diff --git a/ApacheJmeter/org/apache/jmeter/extractor/TestXPathExtractor.java b/ApacheJmeter/org/apache/jmeter/extractor/TestXPathExtractor.java new file mode 100644 index 0000000..2e5a650 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/extractor/TestXPathExtractor.java @@ -0,0 +1,221 @@ +/* + * 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.extractor; + + +import java.io.UnsupportedEncodingException; + +import junit.framework.TestCase; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestXPathExtractor extends TestCase { + private XPathExtractor extractor; + + private SampleResult result; + + private String data; + + private JMeterVariables vars; + + public TestXPathExtractor(String name) { + super(name); + } + + private JMeterContext jmctx; + + private final static String VAL_NAME = "value"; + private final static String VAL_NAME_NR = "value_matchNr"; + @Override + public void setUp() throws UnsupportedEncodingException { + jmctx = JMeterContextService.getContext(); + extractor = new XPathExtractor(); + extractor.setThreadContext(jmctx);// This would be done by the run command + extractor.setRefName(VAL_NAME); + extractor.setDefaultValue("Default"); + result = new SampleResult(); + data = "<book><preface title='Intro'>zero</preface><page>one</page><page>two</page><empty></empty><a><b></b></a></book>"; + result.setResponseData(data.getBytes("UTF-8")); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + } + + public void testAttributeExtraction() throws Exception { + extractor.setXPathQuery("/book/preface/@title"); + extractor.process(); + assertEquals("Intro", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("Intro", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + extractor.setXPathQuery("/book/preface[@title]"); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + extractor.setXPathQuery("/book/preface[@title='Intro']"); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + extractor.setXPathQuery("/book/preface[@title='xyz']"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("0", vars.get(VAL_NAME_NR)); + assertNull(vars.get(VAL_NAME+"_1")); + } + + public void testVariableExtraction() throws Exception { + extractor.setXPathQuery("/book/preface"); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + extractor.setXPathQuery("/book/page"); + extractor.process(); + assertEquals("one", vars.get(VAL_NAME)); + assertEquals("2", vars.get(VAL_NAME_NR)); + assertEquals("one", vars.get(VAL_NAME+"_1")); + assertEquals("two", vars.get(VAL_NAME+"_2")); + assertNull(vars.get(VAL_NAME+"_3")); + + extractor.setXPathQuery("/book/page[2]"); + extractor.process(); + assertEquals("two", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("two", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + assertNull(vars.get(VAL_NAME+"_3")); + + extractor.setXPathQuery("/book/index"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("0", vars.get(VAL_NAME_NR)); + assertNull(vars.get(VAL_NAME+"_1")); + + // Has child, but child is empty + extractor.setXPathQuery("/book/a"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertNull(vars.get(VAL_NAME+"_1")); + + // Has no child + extractor.setXPathQuery("/book/empty"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertNull(vars.get(VAL_NAME+"_1")); + + // No text + extractor.setXPathQuery("//a"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + + // Test fragment + extractor.setXPathQuery("/book/page[2]"); + extractor.setFragment(true); + extractor.process(); + assertEquals("<page>two</page>", vars.get(VAL_NAME)); + // Now get its text + extractor.setXPathQuery("/book/page[2]/text()"); + extractor.process(); + assertEquals("two", vars.get(VAL_NAME)); + + // No text, but using fragment mode + extractor.setXPathQuery("//a"); + extractor.process(); + assertEquals("<a><b/></a>", vars.get(VAL_NAME)); + } + + public void testScope(){ + extractor.setXPathQuery("/book/preface"); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + extractor.setScopeChildren(); // There aren't any + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("0", vars.get(VAL_NAME_NR)); + assertNull(vars.get(VAL_NAME+"_1")); + + extractor.setScopeAll(); // same as Parent + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + // Try to get data from subresult + result.sampleStart(); // Needed for addSubResult() + result.sampleEnd(); + SampleResult subResult = new SampleResult(); + subResult.sampleStart(); + subResult.setResponseData(result.getResponseData()); + subResult.sampleEnd(); + result.addSubResult(subResult); + + + // Get data from both + extractor.setScopeAll(); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("2", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertEquals("zero", vars.get(VAL_NAME+"_2")); + assertNull(vars.get(VAL_NAME+"_3")); + + // get data from child + extractor.setScopeChildren(); + extractor.process(); + assertEquals("zero", vars.get(VAL_NAME)); + assertEquals("1", vars.get(VAL_NAME_NR)); + assertEquals("zero", vars.get(VAL_NAME+"_1")); + assertNull(vars.get(VAL_NAME+"_2")); + + } + + public void testInvalidXpath() throws Exception { + extractor.setXPathQuery("<"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("0", vars.get(VAL_NAME_NR)); + } + + public void testInvalidDocument() throws Exception { + result.setResponseData("<z>", null); + extractor.setXPathQuery("<"); + extractor.process(); + assertEquals("Default", vars.get(VAL_NAME)); + assertEquals("0", vars.get(VAL_NAME_NR)); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/functions/PackageTest.java b/ApacheJmeter/org/apache/jmeter/functions/PackageTest.java new file mode 100644 index 0000000..f04fdc4 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/functions/PackageTest.java @@ -0,0 +1,969 @@ +/* + * 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 to test functions + * + * Functions are created and parameters set up in one thread. + * + * They are then tested in another thread, or two threads running in parallel + * + */ +package org.apache.jmeter.functions; + +import java.io.FileNotFoundException; +import java.util.Collection; +import java.util.LinkedList; + +import junit.extensions.ActiveTestSuite; +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.log.Logger; + +/** + * Test cases for Functions + */ +public class PackageTest extends JMeterTestCase { + + private static final Logger log = LoggingManager.getLoggerForClass(); + +// static { +// LoggingManager.setPriority("DEBUG","jmeter"); +// LoggingManager.setTarget(new java.io.PrintWriter(System.out)); +// } + + public PackageTest(String arg0) { + super(arg0); + } + + // Create the CSVRead function and set its parameters. + private static CSVRead setCSVReadParams(String p1, String p2) throws Exception { + CSVRead cr = new CSVRead(); + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + if (p1 != null) { + parms.add(new CompoundVariable(p1)); + } + if (p2 != null) { + parms.add(new CompoundVariable(p2)); + } + cr.setParameters(parms); + return cr; + } + + // Create the StringFromFile function and set its parameters. + private static StringFromFile SFFParams(String p1, String p2, String p3, String p4) throws Exception { + StringFromFile sff = new StringFromFile(); + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + if (p1 != null) { + parms.add(new CompoundVariable(p1)); + } + if (p2 != null) { + parms.add(new CompoundVariable(p2)); + } + if (p3 != null) { + parms.add(new CompoundVariable(p3)); + } + if (p4 != null) { + parms.add(new CompoundVariable(p4)); + } + sff.setParameters(parms); + return sff; + } + + // Create the SplitFile function and set its parameters. + private static SplitFunction splitParams(String p1, String p2, String p3) throws Exception { + SplitFunction split = new SplitFunction(); + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + parms.add(new CompoundVariable(p1)); + if (p2 != null) { + parms.add(new CompoundVariable(p2)); + } + if (p3 != null) { + parms.add(new CompoundVariable(p3)); + } + split.setParameters(parms); + return split; + } + + // Create the BeanShell function and set its parameters. + private static BeanShell BSHFParams(String p1, String p2, String p3) throws Exception { + BeanShell bsh = new BeanShell(); + bsh.setParameters(makeParams(p1, p2, p3)); + return bsh; + } + + private static Collection<CompoundVariable> makeParams(String p1, String p2, String p3) { + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + if (p1 != null) { + parms.add(new CompoundVariable(p1)); + } + if (p2 != null) { + parms.add(new CompoundVariable(p2)); + } + if (p3 != null) { + parms.add(new CompoundVariable(p3)); + } + return parms; + } + + public static Test suite() throws Exception { + TestSuite allsuites = new TestSuite("Function PackageTest"); + + if (!BeanShellInterpreter.isInterpreterPresent()){ + final String msg = "BeanShell jar not present, tests ignored"; + log.warn(msg); + } else { + TestSuite bsh = new TestSuite("BeanShell"); + bsh.addTest(new PackageTest("BSH1")); + allsuites.addTest(bsh); + } + + TestSuite suite = new TestSuite("SingleThreaded"); + suite.addTest(new PackageTest("CSVParams")); + suite.addTest(new PackageTest("CSVNoFile")); + suite.addTest(new PackageTest("CSVSetup")); + suite.addTest(new PackageTest("CSVRun")); + + suite.addTest(new PackageTest("CSValias")); + suite.addTest(new PackageTest("CSVBlankLine")); + allsuites.addTest(suite); + + // Reset files + suite.addTest(new PackageTest("CSVSetup")); + TestSuite par = new ActiveTestSuite("Parallel"); + par.addTest(new PackageTest("CSVThread1")); + par.addTest(new PackageTest("CSVThread2")); + allsuites.addTest(par); + + TestSuite sff = new TestSuite("StringFromFile"); + sff.addTest(new PackageTest("SFFTest1")); + sff.addTest(new PackageTest("SFFTest2")); + sff.addTest(new PackageTest("SFFTest3")); + sff.addTest(new PackageTest("SFFTest4")); + sff.addTest(new PackageTest("SFFTest5")); + allsuites.addTest(sff); + + TestSuite split = new TestSuite("SplitFunction"); + split.addTest(new PackageTest("splitTest1")); + allsuites.addTest(split); + + TestSuite xpath = new TestSuite("XPath"); + xpath.addTest(new PackageTest("XPathtestColumns")); + xpath.addTest(new PackageTest("XPathtestDefault")); + xpath.addTest(new PackageTest("XPathtestNull")); + xpath.addTest(new PackageTest("XPathtestrowNum")); + xpath.addTest(new PackageTest("XPathEmpty")); + xpath.addTest(new PackageTest("XPathFile1")); + xpath.addTest(new PackageTest("XPathFile2")); + xpath.addTest(new PackageTest("XPathNoFile")); + + allsuites.addTest(xpath); + + TestSuite random = new TestSuite("Random"); + random.addTest(new PackageTest("randomTest1")); + allsuites.addTest(random); + + allsuites.addTest(new PackageTest("XPathSetup1")); + TestSuite par2 = new ActiveTestSuite("ParallelXPath1"); + par2.addTest(new PackageTest("XPathThread1")); + par2.addTest(new PackageTest("XPathThread2")); + allsuites.addTest(par2); + + allsuites.addTest(new PackageTest("XPathSetup2")); + TestSuite par3 = new ActiveTestSuite("ParallelXPath2"); + par3.addTest(new PackageTest("XPathThread1")); + par3.addTest(new PackageTest("XPathThread2")); + allsuites.addTest(par3); + + TestSuite variable = new TestSuite("Variable"); + variable.addTest(new PackageTest("variableTest1")); + allsuites.addTest(variable); + + TestSuite eval = new TestSuite("Eval"); + eval.addTest(new PackageTest("evalTest1")); + eval.addTest(new PackageTest("evalTest2")); + allsuites.addTest(eval); + + TestSuite intSum = new TestSuite("Sums"); + intSum.addTest(new PackageTest("sumTest")); + allsuites.addTest(intSum); + + return allsuites; + } + + private JMeterContext jmctx = null; + + private JMeterVariables vars = null; + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + jmctx.setVariables(new JMeterVariables()); + vars = jmctx.getVariables(); + } + + public void BSH1() throws Exception { + String fn = "testfiles/BeanShellTest.bsh"; + try { + BSHFParams(null, null, null); + fail("Expected InvalidVariableException"); + } catch (InvalidVariableException e) { + } + + try { + BSHFParams("", "", ""); + fail("Expected InvalidVariableException"); + } catch (InvalidVariableException e) { + } + + BeanShell bsh; + try { + bsh = BSHFParams("", "", null); + assertEquals("", bsh.execute()); + } catch (InvalidVariableException e) { + fail("BeanShell not present"); + } + + bsh = BSHFParams("1", null, null); + assertEquals("1", bsh.execute()); + + bsh = BSHFParams("1+1", "VAR", null); + assertEquals("2", bsh.execute()); + assertEquals("2", vars.get("VAR")); + + // Check some initial variables + bsh = BSHFParams("return threadName", null, null); + assertEquals(Thread.currentThread().getName(), bsh.execute()); + bsh = BSHFParams("return log.getClass().getName()", null, null); + assertEquals(log.getClass().getName(), bsh.execute()); + + // Check source works + bsh = BSHFParams("source (\"testfiles/BeanShellTest.bsh\")", null, null); + assertEquals("9876", bsh.execute()); + + // Check persistence + bsh = BSHFParams("${SCR1}", null, null); + + vars.put("SCR1", "var1=11"); + assertEquals("11", bsh.execute()); + + vars.put("SCR1", "var2=22"); + assertEquals("22", bsh.execute()); + + vars.put("SCR1", "x=var1"); + assertEquals("11", bsh.execute()); + + vars.put("SCR1", "++x"); + assertEquals("12", bsh.execute()); + + vars.put("VAR1", "test"); + vars.put("SCR1", "vars.get(\"VAR1\")"); + assertEquals("test", bsh.execute()); + + // Check init file functioning + JMeterUtils.getJMeterProperties().setProperty(BeanShell.INIT_FILE, fn); + bsh = BSHFParams("${SCR2}", null, null); + vars.put("SCR2", "getprop(\"" + BeanShell.INIT_FILE + "\")"); + assertEquals(fn, bsh.execute());// Check that bsh has read the file + vars.put("SCR2", "getprop(\"avavaav\",\"default\")"); + assertEquals("default", bsh.execute()); + vars.put("SCR2", "++i"); + assertEquals("1", bsh.execute()); + vars.put("SCR2", "++i"); + assertEquals("2", bsh.execute()); + + } + + public void splitTest1() throws Exception { + String src = ""; + + try { + splitParams("a,b,c", null, null); + fail("Expected InvalidVariableException (wrong number of parameters)"); + } catch (InvalidVariableException e) { + // OK + } + src = "a,b,c"; + SplitFunction split; + split = splitParams(src, "VAR1", null); + assertEquals(src, split.execute()); + assertEquals(src, vars.get("VAR1")); + assertEquals("3", vars.get("VAR1_n")); + assertEquals("a", vars.get("VAR1_1")); + assertEquals("b", vars.get("VAR1_2")); + assertEquals("c", vars.get("VAR1_3")); + assertNull(vars.get("VAR1_4")); + + split = splitParams(src, "VAR2", ","); + assertEquals(src, split.execute()); + assertEquals(src, vars.get("VAR2")); + assertEquals("3", vars.get("VAR2_n")); + assertEquals("a", vars.get("VAR2_1")); + assertEquals("b", vars.get("VAR2_2")); + assertEquals("c", vars.get("VAR2_3")); + assertNull(vars.get("VAR2_4")); + + src = "a|b|c"; + split = splitParams(src, "VAR3", "|"); + assertEquals(src, split.execute()); + assertEquals(src, vars.get("VAR3")); + assertEquals("3", vars.get("VAR3_n")); + assertEquals("a", vars.get("VAR3_1")); + assertEquals("b", vars.get("VAR3_2")); + assertEquals("c", vars.get("VAR3_3")); + assertNull(vars.get("VAR3_4")); + + src = "a|b||"; + split = splitParams(src, "VAR4", "|"); + assertEquals(src, split.execute()); + assertEquals(src, vars.get("VAR4")); + assertEquals("4", vars.get("VAR4_n")); + assertEquals("a", vars.get("VAR4_1")); + assertEquals("b", vars.get("VAR4_2")); + assertEquals("?", vars.get("VAR4_3")); + assertNull(vars.get("VAR4_5")); + + src = "a,,c"; + vars.put("VAR", src); + split = splitParams("${VAR}", "VAR", null); + assertEquals(src, split.execute()); + assertEquals("3", vars.get("VAR_n")); + assertEquals("a", vars.get("VAR_1")); + assertEquals("?", vars.get("VAR_2")); + assertEquals("c", vars.get("VAR_3")); + assertNull(vars.get("VAR_4")); + + src = "a,b"; + vars.put("VAR", src); + split = splitParams("${VAR}", "VAR", null); + assertEquals(src, split.execute()); + assertEquals("2", vars.get("VAR_n")); + assertEquals("a", vars.get("VAR_1")); + assertEquals("b", vars.get("VAR_2")); + assertNull(vars.get("VAR_3")); + + src = "a,,c,"; + vars.put("VAR", src); + split = splitParams("${VAR}", "VAR5", null); + assertEquals(src, split.execute()); + assertEquals("4", vars.get("VAR5_n")); + assertEquals("a", vars.get("VAR5_1")); + assertEquals("?", vars.get("VAR5_2")); + assertEquals("c", vars.get("VAR5_3")); + assertEquals("?", vars.get("VAR5_4")); + assertNull(vars.get("VAR5_5")); + +} + + public void SFFTest1() throws Exception { + StringFromFile sff1 = SFFParams("testfiles/SFFTest#'.'txt", "", "1", "3"); + assertEquals("uno", sff1.execute()); + assertEquals("dos", sff1.execute()); + assertEquals("tres", sff1.execute()); + assertEquals("cuatro", sff1.execute()); + assertEquals("cinco", sff1.execute()); + assertEquals("one", sff1.execute()); + assertEquals("two", sff1.execute()); + sff1.execute(); + sff1.execute(); + assertEquals("five", sff1.execute()); + assertEquals("eins", sff1.execute()); + sff1.execute(); + sff1.execute(); + sff1.execute(); + assertEquals("fuenf", sff1.execute()); + try { + sff1.execute(); + fail("Should have thrown JMeterStopThreadException"); + } catch (JMeterStopThreadException e) { + // expected + } + } + + public void SFFTest2() throws Exception { + StringFromFile sff = SFFParams("testfiles/SFFTest1.txt", "", null, null); + assertEquals("uno", sff.execute()); + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + assertEquals("uno", sff.execute()); // Restarts + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + } + + public void SFFTest3() throws Exception { + StringFromFile sff = SFFParams("testfiles/SFFTest1.txt", "", "", ""); + assertEquals("uno", sff.execute()); + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + assertEquals("uno", sff.execute()); // Restarts + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + } + + public void SFFTest4() throws Exception { + StringFromFile sff = SFFParams("xxtestfiles/SFFTest1.txt", "", "", ""); + assertEquals(StringFromFile.ERR_IND, sff.execute()); + assertEquals(StringFromFile.ERR_IND, sff.execute()); + } + + // Test that only loops twice + public void SFFTest5() throws Exception { + StringFromFile sff = SFFParams("testfiles/SFFTest1.txt", "", "", "2"); + assertEquals("uno", sff.execute()); + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + assertEquals("uno", sff.execute()); + assertEquals("dos", sff.execute()); + assertEquals("tres", sff.execute()); + assertEquals("cuatro", sff.execute()); + assertEquals("cinco", sff.execute()); + try { + sff.execute(); + fail("Should have thrown JMeterStopThreadException"); + } catch (JMeterStopThreadException e) { + // expected + } + } + + // Function objects to be tested + private static CSVRead cr1, cr2, cr3, cr4, cr5, cr6; + + // Helper class used to implement co-routine between two threads + private static class Baton { + void pass() { + done(); + try { + // System.out.println(">wait:"+Thread.currentThread().getName()); + wait(1000); + } catch (InterruptedException e) { + System.out.println(e); + } + // System.out.println("<wait:"+Thread.currentThread().getName()); + + } + + void done() { + // System.out.println(">done:"+Thread.currentThread().getName()); + notifyAll(); + } + + } + + private static final Baton baton = new Baton(); + + public void CSVThread1() throws Exception { + Thread.currentThread().setName("One"); + synchronized (baton) { + + assertEquals("b1", cr1.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + + assertEquals("b2", cr1.execute(null, null)); + + baton.pass(); + + assertEquals("", cr4.execute(null, null)); + + assertEquals("b4", cr1.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + + baton.pass(); + + assertEquals("b3", cr1.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + + baton.done(); + } + } + + public void CSVThread2() throws Exception { + Thread.currentThread().setName("Two"); + Thread.sleep(500);// Allow other thread to start + synchronized (baton) { + + assertEquals("b3", cr1.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + + baton.pass(); + + assertEquals("b1", cr1.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + + assertEquals("b2", cr1.execute(null, null)); + + baton.pass(); + + assertEquals("", cr4.execute(null, null)); + + assertEquals("b4", cr1.execute(null, null)); + + baton.done(); + } + } + + public void CSVRun() throws Exception { + assertEquals("b1", cr1.execute(null, null)); + assertEquals("c1", cr2.execute(null, null)); + assertEquals("d1", cr3.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + assertEquals("b2", cr1.execute(null, null)); + assertEquals("c2", cr2.execute(null, null)); + assertEquals("d2", cr3.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + assertEquals("b3", cr1.execute(null, null)); + assertEquals("c3", cr2.execute(null, null)); + assertEquals("d3", cr3.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + assertEquals("b4", cr1.execute(null, null)); + assertEquals("c4", cr2.execute(null, null)); + assertEquals("d4", cr3.execute(null, null)); + + assertEquals("", cr4.execute(null, null)); + assertEquals("b1", cr1.execute(null, null)); + assertEquals("c1", cr2.execute(null, null)); + assertEquals("d1", cr3.execute(null, null)); + + assertEquals("a1", cr5.execute(null, null)); + assertEquals("", cr6.execute(null, null)); + assertEquals("a2", cr5.execute(null, null)); + + } + + public void CSVParams() throws Exception { + try { + setCSVReadParams(null, null); + fail("Should have failed"); + } catch (InvalidVariableException e) { + } + try { + setCSVReadParams(null, ""); + fail("Should have failed"); + } catch (InvalidVariableException e) { + } + try { + setCSVReadParams("", null); + fail("Should have failed"); + } catch (InvalidVariableException e) { + } + } + + public void CSVSetup() throws Exception { + cr1 = setCSVReadParams("testfiles/test.csv", "1"); + cr2 = setCSVReadParams("testfiles/test.csv", "2"); + cr3 = setCSVReadParams("testfiles/test.csv", "3"); + cr4 = setCSVReadParams("testfiles/test.csv", "next"); + cr5 = setCSVReadParams("", "0"); + cr6 = setCSVReadParams("", "next"); + } + + public void CSValias() throws Exception { + cr1 = setCSVReadParams("testfiles/test.csv", "*A"); + cr2 = setCSVReadParams("*A", "1"); + cr3 = setCSVReadParams("*A", "next"); + + cr4 = setCSVReadParams("testfiles/test.csv", "*B"); + cr5 = setCSVReadParams("*B", "2"); + cr6 = setCSVReadParams("*B", "next"); + + String s; + + s = cr1.execute(null, null); // open as *A + assertEquals("", s); + s = cr2.execute(null, null); // col 1, line 1, *A + assertEquals("b1", s); + + s = cr4.execute(null, null);// open as *B + assertEquals("", s); + s = cr5.execute(null, null);// col2 line 1 + assertEquals("c1", s); + + s = cr3.execute(null, null);// *A next + assertEquals("", s); + s = cr2.execute(null, null);// col 1, line 2, *A + assertEquals("b2", s); + + s = cr5.execute(null, null);// col2, line 1, *B + assertEquals("c1", s); + + s = cr6.execute(null, null);// *B next + assertEquals("", s); + + s = cr5.execute(null, null);// col2, line 2, *B + assertEquals("c2", s); + + } + + public void CSVNoFile() throws Exception { + String s; + + cr1 = setCSVReadParams("xtestfiles/test.csv", "1"); + log.info("Expecting file not found"); + s = cr1.execute(null, null); + assertEquals("", s); + + cr2 = setCSVReadParams("xtestfiles/test.csv", "next"); + log.info("Expecting no entry for file"); + s = cr2.execute(null, null); + assertEquals("", s); + + cr3 = setCSVReadParams("xtestfiles/test.csv", "*ABC"); + log.info("Expecting file not found"); + s = cr3.execute(null, null); + assertEquals("", s); + + cr4 = setCSVReadParams("*ABC", "1"); + log.info("Expecting cannot open file"); + s = cr4.execute(null, null); + assertEquals("", s); + } + + // Check blank lines are treated as EOF + public void CSVBlankLine() throws Exception { + CSVRead csv1 = setCSVReadParams("testfiles/testblank.csv", "1"); + CSVRead csv2 = setCSVReadParams("testfiles/testblank.csv", "next"); + + String s; + + for (int i = 1; i <= 2; i++) { + s = csv1.execute(null, null); + assertEquals("b1", s); + + s = csv2.execute(null, null); + assertEquals("", s); + + s = csv1.execute(null, null); + assertEquals("b2", s); + + s = csv2.execute(null, null); + assertEquals("", s); + } + + } + + // XPathFileContainer tests + + public void XPathtestNull() throws Exception { + try { + new XPathFileContainer("nosuch.xml", "/"); + fail("Should not find the file"); + } catch (FileNotFoundException e) { + } + } + + public void XPathtestrowNum() throws Exception { + XPathFileContainer f = new XPathFileContainer("../build.xml", "/project/target/@name"); + assertNotNull(f); + // assertEquals("Expected 4 lines",4,f.size()); + + int myRow = f.nextRow(); + assertEquals(0, myRow); + assertEquals(1, f.getNextRow()); + + myRow = f.nextRow(); + assertEquals(1, myRow); + assertEquals(2, f.getNextRow()); + + myRow = f.nextRow(); + assertEquals(2, myRow); + assertEquals(3, f.getNextRow()); + + // myRow = f.nextRow(); + // assertEquals(3,myRow); + // assertEquals(0,f.getNextRow()); + + // myRow = f.nextRow(); + // assertEquals(0,myRow); + // assertEquals(1,f.getNextRow()); + + } + + public void XPathtestColumns() throws Exception { + XPathFileContainer f = new XPathFileContainer("../build.xml", "/project/target/@name"); + assertNotNull(f); + assertTrue("Not empty", f.size() > 0); + int last = 0; + for (int i = 0; i < f.size(); i++) { + last = f.nextRow(); + log.debug("found [" + i + "]" + f.getXPathString(last)); + } + assertEquals(last + 1, f.size()); + + } + + public void XPathtestDefault() throws Exception { + XPathFileContainer f = new XPathFileContainer("../build.xml", "/project/@default"); + assertNotNull(f); + assertTrue("Not empty", f.size() > 0); + assertEquals("install", f.getXPathString(0)); + + } + + public void XPathEmpty() throws Exception{ + XPath xp = setupXPath("",""); + String val=xp.execute(); + assertEquals("",val); + val=xp.execute(); + assertEquals("",val); + val=xp.execute(); + assertEquals("",val); + } + + public void XPathNoFile() throws Exception{ + XPath xp = setupXPath("no-such-file",""); + String val=xp.execute(); + assertEquals("",val); // TODO - should check that error has been logged... + } + + public void XPathFile1() throws Exception{ + XPath xp = setupXPath("testfiles/XPathTest.xml","//user/@username"); + assertEquals("u1",xp.execute()); + assertEquals("u2",xp.execute()); + assertEquals("u3",xp.execute()); + assertEquals("u4",xp.execute()); + assertEquals("u5",xp.execute()); + assertEquals("u1",xp.execute()); + } + + public void XPathFile2() throws Exception{ + XPath xp1 = setupXPath("testfiles/XPathTest.xml","//user/@username"); + XPath xp1a = setupXPath("testfiles/XPathTest.xml","//user/@username"); + XPath xp2 = setupXPath("testfiles/XPathTest.xml","//user/@password"); + XPath xp2a = setupXPath("testfiles/XPathTest.xml","//user/@password"); + assertEquals("u1",xp1.execute()); + assertEquals("p1",xp2.execute()); + assertEquals("p2",xp2.execute()); + assertEquals("u2",xp1a.execute()); + assertEquals("u3",xp1.execute()); + assertEquals("u4",xp1.execute()); + assertEquals("p3",xp2a.execute()); + + } + + private static XPath sxp1,sxp2; + // Use same XPath for both threads + public void XPathSetup1() throws Exception{ + sxp1 = setupXPath("testfiles/XPathTest.xml","//user/@username"); + sxp2=sxp1; + } + + // Use different XPath for both threads + public void XPathSetup2() throws Exception{ + sxp1 = setupXPath("testfiles/XPathTest.xml","//user/@username"); + sxp2 = setupXPath("testfiles/XPathTest.xml","//user/@username"); + } + + public void XPathThread1() throws Exception { + Thread.currentThread().setName("XPathOne"); + synchronized (baton) { + assertEquals("u1",sxp1.execute()); + assertEquals("u2",sxp1.execute()); + baton.pass(); + assertEquals("u5",sxp1.execute()); + baton.pass(); + assertEquals("u2",sxp1.execute()); + baton.done(); + } + } + + public void XPathThread2() throws Exception { + Thread.currentThread().setName("XPathTwo"); + Thread.sleep(500); + synchronized (baton) { + assertEquals("u3",sxp2.execute()); + assertEquals("u4",sxp2.execute()); + baton.pass(); + assertEquals("u1",sxp2.execute()); + baton.pass(); + assertEquals("u3",sxp2.execute()); + baton.done(); + } + } + + private XPath setupXPath(String file, String expr) throws Exception{ + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + parms.add(new CompoundVariable(file)); + parms.add(new CompoundVariable(expr)); + XPath xp = new XPath(); + xp.setParameters(parms); + return xp; + } + + + + public void randomTest1() throws Exception { + Random r = new Random(); + Collection<CompoundVariable> parms = makeParams("0","10000000000","VAR"); + r.setParameters(parms); + //String s = + r.execute(null,null); + } + + public void variableTest1() throws Exception { + Variable r = new Variable(); + vars.put("A_1","a1"); + vars.put("A_2","a2"); + vars.put("one","1"); + vars.put("two","2"); + vars.put("V","A"); + Collection<CompoundVariable> parms; + String s; + + parms = makeParams("V",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("A",s); + + parms = makeParams("X",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("X",s); + + parms = makeParams("A${X}",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("A${X}",s); + + parms = makeParams("A_1",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("a1",s); + + parms = makeParams("A_2",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("a2",s); + + parms = makeParams("A_${two}",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("a2",s); + + parms = makeParams("${V}_${one}",null,null); + r.setParameters(parms); + s = r.execute(null,null); + assertEquals("a1",s); + } + + public void evalTest1() throws Exception { + EvalFunction eval = new EvalFunction(); + vars.put("query","select ${column} from ${table}"); + vars.put("column","name"); + vars.put("table","customers"); + Collection<CompoundVariable> parms; + String s; + + parms = makeParams("${query}",null,null); + eval.setParameters(parms); + s = eval.execute(null,null); + assertEquals("select name from customers",s); + + } + + public void evalTest2() throws Exception { + EvalVarFunction evalVar = new EvalVarFunction(); + vars.put("query","select ${column} from ${table}"); + vars.put("column","name"); + vars.put("table","customers"); + Collection<CompoundVariable> parms; + String s; + + parms = makeParams("query",null,null); + evalVar.setParameters(parms); + s = evalVar.execute(null,null); + assertEquals("select name from customers",s); + } + + public void sumTest() throws Exception { + String maxIntVal = Integer.toString(Integer.MAX_VALUE); + String minIntVal = Integer.toString(Integer.MIN_VALUE); + + { // prevent accidental use of is below + IntSum is = new IntSum(); + checkInvalidParameterCounts(is,2); + checkSum(is,"3", new String[]{"1","2"}); + checkSumNoVar(is,"3", new String[]{"1","2"}); + checkSum(is,"1", new String[]{"-1","1","1","1","-2","1"}); + checkSumNoVar(is,"1", new String[]{"-1","1","1","1","-2","1"}); + checkSum(is,maxIntVal, new String[]{maxIntVal,"0"}); + checkSum(is,minIntVal, new String[]{maxIntVal,"1"}); // wrap-round check + } + + LongSum ls = new LongSum(); + checkInvalidParameterCounts(ls,2); + checkSum(ls,"3", new String[]{"1","2"}); + checkSum(ls,"1", new String[]{"-1","1","1","1","-1","0"}); + checkSumNoVar(ls,"3", new String[]{"1","2"}); + checkSumNoVar(ls,"1", new String[]{"-1","1","1","1","-1","0"}); + String maxIntVal_1 = Long.toString(1+(long)Integer.MAX_VALUE); + checkSum(ls,maxIntVal, new String[]{maxIntVal,"0"}); + checkSum(ls,maxIntVal_1, new String[]{maxIntVal,"1"}); // no wrap-round check + String maxLongVal = Long.toString(Long.MAX_VALUE); + String minLongVal = Long.toString(Long.MIN_VALUE); + checkSum(ls,maxLongVal, new String[]{maxLongVal,"0"}); + checkSum(ls,minLongVal, new String[]{maxLongVal,"1"}); // wrap-round check + } + + // Perform a sum and check the results + private void checkSum(AbstractFunction func, String value, String [] addends) throws Exception { + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + for (int i=0; i< addends.length; i++){ + parms.add(new CompoundVariable(addends[i])); + } + parms.add(new CompoundVariable("Result")); + func.setParameters(parms); + assertEquals(value,func.execute(null,null)); + assertEquals(value,vars.getObject("Result")); + } + // Perform a sum and check the results + private void checkSumNoVar(AbstractFunction func, String value, String [] addends) throws Exception { + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + for (int i=0; i< addends.length; i++){ + parms.add(new CompoundVariable(addends[i])); + } + func.setParameters(parms); + assertEquals(value,func.execute(null,null)); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/functions/TestFileRowColContainer.java b/ApacheJmeter/org/apache/jmeter/functions/TestFileRowColContainer.java new file mode 100644 index 0000000..04b62f1 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/functions/TestFileRowColContainer.java @@ -0,0 +1,143 @@ +/* + * 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 java.io.FileNotFoundException; + +import org.apache.jmeter.junit.JMeterTestCase; + +/** + * File data container for CSV (and similar delimited) files Data is accessible + * via row and column number + * + * @version $Revision: 1028573 $ + */ +public class TestFileRowColContainer extends JMeterTestCase { + + public void testNull() throws Exception { + try { + new FileRowColContainer(findTestPath("testfiles/xyzxyz")); + fail("Should not find the file"); + } catch (FileNotFoundException e) { + } + } + + public void testrowNum() throws Exception { + FileRowColContainer f = new FileRowColContainer(findTestPath("testfiles/test.csv")); + assertNotNull(f); + assertEquals("Expected 4 lines", 4, f.getSize()); + + assertEquals(0, f.nextRow()); + assertEquals(1, f.nextRow()); + assertEquals(2, f.nextRow()); + assertEquals(3, f.nextRow()); + assertEquals(0, f.nextRow()); + + } + + public void testColumns() throws Exception { + FileRowColContainer f = new FileRowColContainer(findTestPath("testfiles/test.csv")); + assertNotNull(f); + assertTrue("Not empty", f.getSize() > 0); + + int myRow = f.nextRow(); + assertEquals(0, myRow); + assertEquals("a1", f.getColumn(myRow, 0)); + assertEquals("d1", f.getColumn(myRow, 3)); + + try { + f.getColumn(myRow, 4); + fail("Expected out of bounds"); + } catch (IndexOutOfBoundsException e) { + } + myRow = f.nextRow(); + assertEquals(1, myRow); + assertEquals("b2", f.getColumn(myRow, 1)); + assertEquals("c2", f.getColumn(myRow, 2)); + } + + public void testColumnsComma() throws Exception { + FileRowColContainer f = new FileRowColContainer(findTestPath("testfiles/test.csv"), ","); + assertNotNull(f); + assertTrue("Not empty", f.getSize() > 0); + + int myRow = f.nextRow(); + assertEquals(0, myRow); + assertEquals("a1", f.getColumn(myRow, 0)); + assertEquals("d1", f.getColumn(myRow, 3)); + + try { + f.getColumn(myRow, 4); + fail("Expected out of bounds"); + } catch (IndexOutOfBoundsException e) { + } + myRow = f.nextRow(); + assertEquals(1, myRow); + assertEquals("b2", f.getColumn(myRow, 1)); + assertEquals("c2", f.getColumn(myRow, 2)); + } + + public void testColumnsTab() throws Exception { + FileRowColContainer f = new FileRowColContainer(findTestPath("testfiles/test.tsv"), "\t"); + assertNotNull(f); + assertTrue("Not empty", f.getSize() > 0); + + int myRow = f.nextRow(); + assertEquals(0, myRow); + assertEquals("a1", f.getColumn(myRow, 0)); + assertEquals("d1", f.getColumn(myRow, 3)); + + try { + f.getColumn(myRow, 4); + fail("Expected out of bounds"); + } catch (IndexOutOfBoundsException e) { + } + myRow = f.nextRow(); + assertEquals(1, myRow); + assertEquals("b2", f.getColumn(myRow, 1)); + assertEquals("c2", f.getColumn(myRow, 2)); + } + + public void testEmptyCols() throws Exception { + FileRowColContainer f = new FileRowColContainer(findTestPath("testfiles/testempty.csv")); + assertNotNull(f); + assertEquals("Expected 4 lines", 4, f.getSize()); + + int myRow = f.nextRow(); + assertEquals(0, myRow); + assertEquals("", f.getColumn(myRow, 0)); + assertEquals("d1", f.getColumn(myRow, 3)); + + myRow = f.nextRow(); + assertEquals(1, myRow); + assertEquals("", f.getColumn(myRow, 1)); + assertEquals("c2", f.getColumn(myRow, 2)); + + myRow = f.nextRow(); + assertEquals(2, myRow); + assertEquals("b3", f.getColumn(myRow, 1)); + assertEquals("", f.getColumn(myRow, 2)); + + myRow = f.nextRow(); + assertEquals(3, myRow); + assertEquals("b4", f.getColumn(myRow, 1)); + assertEquals("c4", f.getColumn(myRow, 2)); + assertEquals("", f.getColumn(myRow, 3)); + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/functions/TestJexlFunction.java b/ApacheJmeter/org/apache/jmeter/functions/TestJexlFunction.java new file mode 100644 index 0000000..a7fd3ef --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/functions/TestJexlFunction.java @@ -0,0 +1,96 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestJexlFunction extends JMeterTestCase { + private JexlFunction function; + + private SampleResult result; + + private Collection<CompoundVariable> params; + + private JMeterVariables vars; + + private JMeterContext jmctx; + + public TestJexlFunction(String name) { + super(name); + } + + @Override + public void setUp() { + function = new JexlFunction(); + result = new SampleResult(); + jmctx = JMeterContextService.getContext(); + String data = "The quick brown fox"; + result.setResponseData(data, null); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + params = new LinkedList<CompoundVariable>(); + } + + public void testParameterCount() throws Exception { + checkInvalidParameterCounts(function, 1, 2); + } + + public void testSum() throws Exception { + params.add(new CompoundVariable("1+2+3")); + function.setParameters(params); + String ret = function.execute(result, null); + assertEquals("6", ret); + } + + public void testSumVar() throws Exception { + params.add(new CompoundVariable("1+2+3")); + params.add(new CompoundVariable("TOTAL")); + function.setParameters(params); + String ret = function.execute(result, null); + assertEquals("6", ret); + assertEquals("6", vars.get("TOTAL")); + } + + public void testReplace1() throws Exception { + params.add(new CompoundVariable( + "sampleResult.getResponseDataAsString().replaceAll('T','t')")); + function.setParameters(params); + String ret = function.execute(result, null); + assertEquals("the quick brown fox", ret); + } + + public void testReplace2() throws Exception { + vars.put("URL", "/query.cgi?s1=1&s2=2&s3=3"); + params.add(new CompoundVariable("vars.get('URL').replaceAll('&','&')")); + params.add(new CompoundVariable("URL")); + function.setParameters(params); + String ret = function.execute(result, null); + assertEquals("/query.cgi?s1=1&s2=2&s3=3", ret); + assertEquals(ret,vars.getObject("URL")); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/functions/TestRegexFunction.java b/ApacheJmeter/org/apache/jmeter/functions/TestRegexFunction.java new file mode 100644 index 0000000..57fbd52 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/functions/TestRegexFunction.java @@ -0,0 +1,357 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestRegexFunction extends JMeterTestCase { + private static final String INPUT_VARIABLE_NAME = "INVAR"; + + private RegexFunction variable; + + private SampleResult result; + + private Collection<CompoundVariable> params; + + private JMeterVariables vars; + + private JMeterContext jmctx; + + public TestRegexFunction(String name) { + super(name); + } + + @Override + public void setUp() { + variable = new RegexFunction(); + result = new SampleResult(); + jmctx = JMeterContextService.getContext(); + String data = "<company-xmlext-query-ret><row>" + "<value field=\"RetCode\">" + "LIS_OK</value><value" + + " field=\"RetCodeExtension\"></value>" + "<value field=\"alias\"></value><value" + + " field=\"positioncount\"></value>" + "<value field=\"invalidpincount\">0</value><value" + + " field=\"pinposition1\">1</value><value" + " field=\"pinpositionvalue1\"></value><value" + + " field=\"pinposition2\">5</value><value" + " field=\"pinpositionvalue2\"></value><value" + + " field=\"pinposition3\">6</value><value" + " field=\"pinpositionvalue3\"></value>" + + "</row></company-xmlext-query-ret>"; + result.setResponseData(data, null); + vars = new JMeterVariables(); + String data2 = "The quick brown fox jumped over the lazy dog 123 times"; + vars.put(INPUT_VARIABLE_NAME, data2); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + } + + public void testVariableExtraction() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$")); + params.add(new CompoundVariable("2")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("5", match); + } + + // Test with output variable name + public void testVariableExtraction1a() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$")); // template + params.add(new CompoundVariable("2")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("3", vars.getObject("OUTVAR_matchNr")); + assertEquals("5", match); + assertEquals("5", vars.getObject("OUTVAR")); + assertEquals("<value field=\"pinposition2\">5</value>", vars.getObject("OUTVAR_g0")); + assertEquals("pinposition2", vars.getObject("OUTVAR_g1")); + assertEquals("5", vars.getObject("OUTVAR_g2")); + } + + // Test with empty output variable name + public void testVariableExtraction1b() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$")); // template + params.add(new CompoundVariable("2")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("5", match); + assertNull(vars.getObject("OUTVAR")); + } + + public void testVariableExtractionFromVariable() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("$2$")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("times", match); + assertEquals("times", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable2() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("$1$$2$")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("123times", match); + assertEquals("123times", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable3() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("pre$2$post")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("pretimespost", match); + assertEquals("pretimespost", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable4() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("pre$2$")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("pretimes", match); + assertEquals("pretimes", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable5() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("$2$post")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("timespost", match); + assertEquals("timespost", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable6() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("$2$$2$")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("timestimes", match); + assertEquals("timestimes", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable7() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("pre$1$mid$2$post")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("pre123midtimespost", match); + assertEquals("pre123midtimespost", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable8() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("pre$1$mid$2$")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("pre123midtimes", match); + assertEquals("pre123midtimes", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtractionFromVariable9() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("(\\d+)\\s+(\\w+)")); + params.add(new CompoundVariable("$1$mid$2$post")); // template + params.add(new CompoundVariable("1")); // match number + params.add(new CompoundVariable("-")); // ALL separator + params.add(new CompoundVariable("default")); + params.add(new CompoundVariable("OUTVAR")); + params.add(new CompoundVariable(INPUT_VARIABLE_NAME)); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1", vars.getObject("OUTVAR_matchNr")); + assertEquals("123midtimespost", match); + assertEquals("123midtimespost", vars.getObject("OUTVAR")); + assertEquals("123 times", vars.getObject("OUTVAR_g0")); + assertEquals("123", vars.getObject("OUTVAR_g1")); + assertEquals("times", vars.getObject("OUTVAR_g2")); + } + + public void testVariableExtraction2() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$1$")); + params.add(new CompoundVariable("3")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("pinposition3", match); + } + + public void testVariableExtraction5() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$1$")); + params.add(new CompoundVariable("ALL")); + params.add(new CompoundVariable("_")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("pinposition1_pinposition2_pinposition3", match); + } + + public void testVariableExtraction6() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$")); + params.add(new CompoundVariable("4")); + params.add(new CompoundVariable("")); + params.add(new CompoundVariable("default")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("default", match); + } + + public void testComma() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value,? field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$1$")); + params.add(new CompoundVariable("3")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("pinposition3", match); + } + + public void testVariableExtraction3() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("_$1$")); + params.add(new CompoundVariable("2")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("_pinposition2", match); + } + + public void testVariableExtraction4() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$, ")); + params.add(new CompoundVariable(".333")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("1, ", match); + } + + public void testDefaultValue() throws Exception { + params = new LinkedList<CompoundVariable>(); + params.add(new CompoundVariable("<value,, field=\"(pinposition\\d+)\">(\\d+)</value>")); + params.add(new CompoundVariable("$2$, ")); + params.add(new CompoundVariable(".333")); + params.add(new CompoundVariable("")); + params.add(new CompoundVariable("No Value Found")); + variable.setParameters(params); + String match = variable.execute(result, null); + assertEquals("No Value Found", match); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/functions/TestTimeFunction.java b/ApacheJmeter/org/apache/jmeter/functions/TestTimeFunction.java new file mode 100644 index 0000000..46584b6 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/functions/TestTimeFunction.java @@ -0,0 +1,170 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.Locale; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class TestTimeFunction extends JMeterTestCase { + private Function variable; + + private SampleResult result; + + private Collection<CompoundVariable> params; + + private JMeterVariables vars; + + private JMeterContext jmctx = null; + + private String value; + + public TestTimeFunction(String name) { + super(name); + } + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + params = new LinkedList<CompoundVariable>(); + result = new SampleResult(); + variable = new TimeFunction(); + } + + public void testDefault() throws Exception { + variable.setParameters(params); + long before = System.currentTimeMillis(); + value = variable.execute(result, null); + long now= Long.parseLong(value); + long after = System.currentTimeMillis(); + assertTrue(now >= before && now <= after); + } + + public void testDefault1() throws Exception { + params.add(new CompoundVariable()); + variable.setParameters(params); + long before = System.currentTimeMillis(); + value = variable.execute(result, null); + long now= Long.parseLong(value); + long after = System.currentTimeMillis(); + assertTrue(now >= before && now <= after); + } + + public void testDefault2() throws Exception { + params.add(new CompoundVariable()); + params.add(new CompoundVariable()); + variable.setParameters(params); + long before = System.currentTimeMillis(); + value = variable.execute(result, null); + long now= Long.parseLong(value); + long after = System.currentTimeMillis(); + assertTrue(now >= before && now <= after); + } + + public void testDefaultNone() throws Exception { + long before = System.currentTimeMillis(); + value = variable.execute(result, null); + long now= Long.parseLong(value); + long after = System.currentTimeMillis(); + assertTrue(now >= before && now <= after); + } + + public void testTooMany() throws Exception { + params.add(new CompoundVariable("YMD")); + params.add(new CompoundVariable("NAME")); + params.add(new CompoundVariable("YMD")); + try { + variable.setParameters(params); + fail("Should have raised InvalidVariableException"); + } catch (InvalidVariableException ignored){ + } + } + + public void testYMD() throws Exception { + params.add(new CompoundVariable("YMD")); + params.add(new CompoundVariable("NAME")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(8,value.length()); + assertEquals(value,vars.get("NAME")); + } + + public void testYMDnoV() throws Exception { + params.add(new CompoundVariable("YMD")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(8,value.length()); + assertNull(vars.get("NAME")); + } + + public void testHMS() throws Exception { + params.add(new CompoundVariable("HMS")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(6,value.length()); + } + + public void testYMDHMS() throws Exception { + params.add(new CompoundVariable("YMDHMS")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(15,value.length()); + } + + public void testUSER1() throws Exception { + params.add(new CompoundVariable("USER1")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(0,value.length()); + } + + public void testUSER2() throws Exception { + params.add(new CompoundVariable("USER2")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals(0,value.length()); + } + + public void testFixed() throws Exception { + params.add(new CompoundVariable("'Fixed text'")); + variable.setParameters(params); + value = variable.execute(result, null); + assertEquals("Fixed text",value); + } + + public void testMixed() throws Exception { + params.add(new CompoundVariable("G")); + variable.setParameters(params); + Locale locale = Locale.getDefault(); + Locale.setDefault(Locale.ENGLISH); + value = variable.execute(result, null); + Locale.setDefault(locale); + assertEquals("AD",value); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/gui/action/PackageTest.java b/ApacheJmeter/org/apache/jmeter/gui/action/PackageTest.java new file mode 100644 index 0000000..fac5578 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/gui/action/PackageTest.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import junit.framework.TestCase; + +public class PackageTest extends TestCase { + + public PackageTest(String arg0) { + super(arg0); + } + + //TODO add tests for SaveGraphics + public void testSaveGraphics() throws Exception { + } + + //TODO add tests for ReportSaveGraphics + public void testReportSaveGraphics() throws Exception { + } + +} diff --git a/ApacheJmeter/org/apache/jmeter/gui/action/TestLoad.java b/ApacheJmeter/org/apache/jmeter/gui/action/TestLoad.java new file mode 100644 index 0000000..e66476b --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/gui/action/TestLoad.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestSuite; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.save.SaveService; +import org.apache.jorphan.collections.HashTree; + +/** + * + * Test JMX files to check that they can be loaded OK. + */ +public class TestLoad extends JMeterTestCase { + + private static final String basedir = new File(System.getProperty("user.dir")).getParent(); + private static final File testfiledir = new File(basedir,"bin/testfiles"); + private static final File demofiledir = new File(basedir,"xdocs/demos"); + + private static final Set<String> notTestPlan = new HashSet<String>();// not full test plans + + static{ + notTestPlan.add("load_bug_list.jmx");// used by TestAnchorModifier + notTestPlan.add("Load_JMeter_Page.jmx");// used by TestAnchorModifier + notTestPlan.add("ProxyServerTestPlan.jmx");// used by TestSaveService + } + + private static final FilenameFilter jmxFilter = new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(".jmx"); + } + }; + + private final File testFile; + private final String parent; + + public TestLoad(String name) { + super(name); + testFile=null; + parent=null; + } + + public TestLoad(String name, File file, String dir) { + super(name); + testFile=file; + parent=dir; + } + + public static TestSuite suite(){ + TestSuite suite=new TestSuite("Load Test"); + //suite.addTest(new TestLoad("checkGuiPackage")); + scanFiles(suite,testfiledir); + scanFiles(suite,demofiledir); + return suite; + } + + private static void scanFiles(TestSuite suite, File parent) { + File testFiles[]=parent.listFiles(jmxFilter); + String dir = parent.getName(); + for (int i=0; i<testFiles.length; i++){ + suite.addTest(new TestLoad("checkTestFile",testFiles[i],dir)); + } + } + + public void checkTestFile() throws Exception{ + HashTree tree = null; + try { + tree =getTree(testFile); + } catch (Exception e) { + fail(parent+": "+ testFile.getName()+" caused "+e); + } + assertTree(tree); + } + + private void assertTree(HashTree tree) throws Exception { + assertNotNull(parent+": "+ testFile.getName()+" caused null tree: ",tree); + final Object object = tree.getArray()[0]; + final String name = testFile.getName(); + + if (! (object instanceof org.apache.jmeter.testelement.TestPlan) && !notTestPlan.contains(name)){ + fail(parent+ ": " +name+" tree should be TestPlan, but is "+object.getClass().getName()); + } + } + + private HashTree getTree(File f) throws Exception { + FileInputStream fis = new FileInputStream(f); + HashTree tree = null; + try { + tree = SaveService.loadTree(fis); + } finally { + fis.close(); + } + return tree; + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/gui/action/TestSave.java b/ApacheJmeter/org/apache/jmeter/gui/action/TestSave.java new file mode 100644 index 0000000..0c69b67 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/gui/action/TestSave.java @@ -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. + * + */ + +package org.apache.jmeter.gui.action; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; + +public class TestSave extends junit.framework.TestCase { + private Save save; + + public TestSave(String name) { + super(name); + } + + @Override + public void setUp() { + save = new Save(); + } + + public void testTreeConversion() throws Exception { + HashTree tree = new ListedHashTree(); + JMeterTreeNode root = new JMeterTreeNode(new Arguments(), null); + tree.add(root, root); + tree.getTree(root).add(root, root); + save.convertSubTree(tree); + assertEquals(tree.getArray()[0].getClass().getName(), root.getTestElement().getClass().getName()); + tree = tree.getTree(tree.getArray()[0]); + assertEquals(tree.getArray()[0].getClass().getName(), root.getTestElement().getClass().getName()); + assertEquals(tree.getTree(tree.getArray()[0]).getArray()[0].getClass().getName(), root.getTestElement() + .getClass().getName()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/gui/util/TestMenuFactory.java b/ApacheJmeter/org/apache/jmeter/gui/util/TestMenuFactory.java new file mode 100644 index 0000000..4daa494 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/gui/util/TestMenuFactory.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import org.apache.jmeter.junit.JMeterTestCase; + +public final class TestMenuFactory extends JMeterTestCase { + + public TestMenuFactory() { + super(); + } + + public TestMenuFactory(String name) { + super(name); + } + + private static void check(String s, int i) throws Exception { + assertFalse("The number of " + s + " should not be 0", 0 == i); + } + + public void testMenu() throws Exception { + check("menumap", MenuFactory.menuMap_size()); + + check("assertions", MenuFactory.assertions_size()); + check("configElements", MenuFactory.configElements_size()); + check("controllers", MenuFactory.controllers_size()); + check("listeners", MenuFactory.listeners_size()); + check("nonTestElements", MenuFactory.nonTestElements_size()); + check("postProcessors", MenuFactory.postProcessors_size()); + check("preProcessors", MenuFactory.preProcessors_size()); + check("samplers", MenuFactory.samplers_size()); + check("timers", MenuFactory.timers_size()); + + check("elementstoskip", MenuFactory.elementsToSkip_size()); + + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/junit/JMeterTest.java b/ApacheJmeter/org/apache/jmeter/junit/JMeterTest.java new file mode 100644 index 0000000..f617d57 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/junit/JMeterTest.java @@ -0,0 +1,657 @@ +/* + * 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.junit; + +import java.awt.Component; +import java.awt.HeadlessException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.rmi.RemoteException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.jmeter.config.gui.ObsoleteGui; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.functions.Function; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.TestBeanGUI; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.input.SAXBuilder; + +public class JMeterTest extends JMeterTestCase { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static Map<String, Boolean> guiTitles; + + private static Map<String, Boolean> guiTags; + + private static Map<String, Boolean> funcTitles; + + private static Properties nameMap; + + private static final Locale TEST_LOCALE = Locale.ENGLISH; + + private static final Locale DEFAULT_LOCALE = Locale.getDefault(); + + public JMeterTest(String name) { + super(name); + } + + /* + * The suite() method creates separate test suites for each of the types of + * test. The suitexxx() methods create a list of items to be tested, and + * create a new test instance for each. + * + * Each test type has its own constructor, which saves the item to be tested + * + * Note that the suite() method must be static, and the methods to run the + * tests must be instance methods so that they can pick up the item value + * which was saved by the constructor. + * + */ + // Constructor for TestElement tests + private TestElement testItem; + + public JMeterTest(String testName, TestElement te) { + super(testName);// Save the method name + testItem = te; + } + + // Constructor for Serializable tests + private Serializable serObj; + + public JMeterTest(String testName, Serializable ser) { + super(testName);// Save the method name + serObj = ser; + } + + // Constructor for GUI tests + private JMeterGUIComponent guiItem; + + public JMeterTest(String testName, JMeterGUIComponent gc) { + super(testName);// Save the method name + guiItem = gc; + } + + // Constructor for Function tests + private Function funcItem; + + private static volatile boolean classPathShown = false;// Only show classpath once + + public JMeterTest(String testName, Function fi) { + super(testName);// Save the method name + funcItem = fi; + } + + /* + * Use a suite to allow the tests to be generated at run-time + */ + public static Test suite() throws Exception { + // The Locale used to instantiate the GUI objects + JMeterUtils.setLocale(TEST_LOCALE); + Locale.setDefault(TEST_LOCALE); + // Needs to be done before any GUI classes are instantiated + + TestSuite suite = new TestSuite("JMeterTest"); + suite.addTest(new JMeterTest("readAliases")); + suite.addTest(new JMeterTest("createTitleSet")); + suite.addTest(new JMeterTest("createTagSet")); + suite.addTest(suiteGUIComponents()); + suite.addTest(suiteSerializableElements()); + suite.addTest(suiteTestElements()); + suite.addTest(suiteBeanComponents()); + suite.addTest(new JMeterTest("createFunctionSet")); + suite.addTest(suiteFunctions()); + suite.addTest(new JMeterTest("checkGuiSet")); + suite.addTest(new JMeterTest("checkFunctionSet")); + + suite.addTest(new JMeterTest("resetLocale")); // revert + return suite; + } + + // Restore the original Locale + public void resetLocale(){ + JMeterUtils.setLocale(DEFAULT_LOCALE); + Locale.setDefault(DEFAULT_LOCALE); + } + + /* + * Extract titles from component_reference.xml + */ + public void createTitleSet() throws Exception { + guiTitles = new HashMap<String, Boolean>(90); + + String compref = "../xdocs/usermanual/component_reference.xml"; + SAXBuilder bldr = new SAXBuilder(); + Document doc; + doc = bldr.build(compref); + Element root = doc.getRootElement(); + Element body = root.getChild("body"); + @SuppressWarnings("unchecked") + List<Element> sections = body.getChildren("section"); + for (int i = 0; i < sections.size(); i++) { + @SuppressWarnings("unchecked") + List<Element> components = sections.get(i).getChildren("component"); + for (int j = 0; j < components.size(); j++) { + Element comp = components.get(j); + String nm=comp.getAttributeValue("name"); + if (!nm.equals("SSL Manager")){// Not a true GUI component + guiTitles.put(nm.replace(' ','_'), Boolean.FALSE); + } + } + } + // Add titles that don't need to be documented + //guiTitles.put("Root", Boolean.FALSE); + guiTitles.put("Example Sampler", Boolean.FALSE); + } + + /* + * Extract titles from component_reference.xml + */ + public void createTagSet() throws Exception { + guiTags = new HashMap<String, Boolean>(90); + + String compref = "../xdocs/usermanual/component_reference.xml"; + SAXBuilder bldr = new SAXBuilder(); + Document doc; + doc = bldr.build(compref); + Element root = doc.getRootElement(); + Element body = root.getChild("body"); + @SuppressWarnings("unchecked") + List<Element> sections = body.getChildren("section"); + for (int i = 0; i < sections.size(); i++) { + @SuppressWarnings("unchecked") + List<Element> components = sections.get(i).getChildren("component"); + for (int j = 0; j < components.size(); j++) { + Element comp = components.get(j); + guiTags.put(comp.getAttributeValue("tag"), Boolean.FALSE); + } + } + } + + /* + * Extract titles from functions.xml + */ + public void createFunctionSet() throws Exception { + funcTitles = new HashMap<String, Boolean>(20); + + String compref = "../xdocs/usermanual/functions.xml"; + SAXBuilder bldr = new SAXBuilder(); + Document doc; + doc = bldr.build(compref); + Element root = doc.getRootElement(); + Element body = root.getChild("body"); + Element section = body.getChild("section"); + @SuppressWarnings("unchecked") + List<Element> sections = section.getChildren("subsection"); + for (int i = 0; i < sections.size(); i++) { + @SuppressWarnings("unchecked") + List<Element> components = sections.get(i).getChildren("component"); + for (int j = 0; j < components.size(); j++) { + Element comp = components.get(j); + funcTitles.put(comp.getAttributeValue("name"), Boolean.FALSE); + String tag = comp.getAttributeValue("tag"); + if (tag != null){ + funcTitles.put(tag, Boolean.FALSE); + } + } + } + } + + private int scanprintMap(Map<String, Boolean> m, String t) { + Set<String> s = m.keySet(); + int unseen = 0; + if (s.size() == 0) { + return 0; + } + Iterator<String> i = s.iterator(); + while (i.hasNext()) { + String key = i.next(); + if (!m.get(key).equals(Boolean.TRUE)) { + if (unseen == 0)// first time + { + System.out.println("\nNames remaining in " + t + " Map:"); + } + unseen++; + System.out.println(key); + } + } + return unseen; + } + + public void checkGuiSet() throws Exception { + guiTitles.remove("Example Sampler");// We don't mind if this is left over + guiTitles.remove("Sample_Result_Save_Configuration");// Ditto, not a sampler + assertEquals("Should not have any names left over", 0, scanprintMap(guiTitles, "GUI")); + } + + public void checkFunctionSet() throws Exception { + assertEquals("Should not have any names left over", 0, scanprintMap(funcTitles, "Function")); + } + + /* + * Test GUI elements - create the suite of tests + */ + private static Test suiteGUIComponents() throws Exception { + TestSuite suite = new TestSuite("GuiComponents"); + Iterator<Object> iter = getObjects(JMeterGUIComponent.class).iterator(); + while (iter.hasNext()) { + JMeterGUIComponent item = (JMeterGUIComponent) iter.next(); + if (item instanceof JMeterTreeNode) { + System.out.println("o.a.j.junit.JMeterTest INFO: JMeterGUIComponent: skipping all tests " + item.getClass().getName()); + continue; + } + if (item instanceof ObsoleteGui){ + continue; + } + TestSuite ts = new TestSuite(item.getClass().getName()); + ts.addTest(new JMeterTest("GUIComponents1", item)); + if (item instanceof TestBeanGUI) { + System.out.println("o.a.j.junit.JMeterTest INFO: JMeterGUIComponent: skipping some tests " + item.getClass().getName()); + } else { + ts.addTest(new JMeterTest("GUIComponents2", item)); + ts.addTest(new JMeterTest("runGUITitle", item)); + } + suite.addTest(ts); + } + return suite; + } + + /* + * Test Functions - create the suite of tests + */ + private static Test suiteFunctions() throws Exception { + TestSuite suite = new TestSuite("Functions"); + Iterator<Object> iter = getObjects(Function.class).iterator(); + while (iter.hasNext()) { + Object item = iter.next(); + if (item.getClass().equals(CompoundVariable.class)) { + continue; + } + TestSuite ts = new TestSuite(item.getClass().getName()); + ts.addTest(new JMeterTest("runFunction", (Function) item)); + ts.addTest(new JMeterTest("runFunction2", (Function) item)); + suite.addTest(ts); + } + return suite; + } + + /* + * Test GUI elements - create the suite of tests + */ + private static Test suiteBeanComponents() throws Exception { + TestSuite suite = new TestSuite("BeanComponents"); + Iterator<Object> iter = getObjects(TestBean.class).iterator(); + while (iter.hasNext()) { + Class<? extends Object> c = iter.next().getClass(); + try { + JMeterGUIComponent item = new TestBeanGUI(c); + // JMeterGUIComponent item = (JMeterGUIComponent) iter.next(); + TestSuite ts = new TestSuite(item.getClass().getName()); + ts.addTest(new JMeterTest("GUIComponents2", item)); + ts.addTest(new JMeterTest("runGUITitle", item)); + suite.addTest(ts); + } catch (IllegalArgumentException e) { + System.out.println("o.a.j.junit.JMeterTest Cannot create test for " + c.getName() + " " + e); + e.printStackTrace(System.out); + } + } + return suite; + } + + /* + * Test GUI elements - run the test + */ + public void runGUITitle() throws Exception { + if (guiTitles.size() > 0) { + String title = guiItem.getDocAnchor(); + boolean ct = guiTitles.containsKey(title); + if (ct) { + guiTitles.put(title, Boolean.TRUE);// So we can detect extra entries + } + String name = guiItem.getClass().getName(); + if (// Is this a work in progress or an internal GUI component? + (title != null && title.length() > 0) // Will be "" for internal components + && (title.toUpperCase(java.util.Locale.ENGLISH).indexOf("(ALPHA") == -1) + && (title.toUpperCase(java.util.Locale.ENGLISH).indexOf("(BETA") == -1) + && (!title.matches("Example\\d+")) // Skip the example samplers ... + && (!name.startsWith("org.apache.jmeter.examples.")) + && (!name.startsWith("org.apache.jmeter.report.")) // Skip report packages as implementation is incomplete + && (!name.equals("org.apache.jmeter.control.gui.ReportGui"))) // Skip report GUI as implementation is incomplete + {// No, not a work in progress ... + String s = "component_reference.xml needs '" + title + "' anchor for " + name; + if (!ct) { + log.warn(s); // Record in log as well + } + assertTrue(s, ct); + } + } + } + + /* + * run the function test + */ + public void runFunction() throws Exception { + if (funcTitles.size() > 0) { + String title = funcItem.getReferenceKey(); + boolean ct = funcTitles.containsKey(title); + if (ct) { + funcTitles.put(title, Boolean.TRUE);// For detecting extra entries + } + if (// Is this a work in progress ? + title.indexOf("(ALPHA") == -1 && title.indexOf("(EXPERIMENTAL") == -1) {// No, + // not + // a + // work + // in + // progress + // ... + String s = "function.xml needs '" + title + "' entry for " + funcItem.getClass().getName(); + if (!ct) { + log.warn(s); // Record in log as well + } + assertTrue(s, ct); + } + } + } + + + /* + * Check that function descriptions are OK + */ + public void runFunction2() throws Exception { + Iterator<?> i = funcItem.getArgumentDesc().iterator(); + while (i.hasNext()) { + Object o = i.next(); + assertTrue("Description must be a String", o instanceof String); + assertFalse("Description must not start with [refkey", ((String) o).startsWith("[refkey")); + } + } + + /* + * Test GUI elements - run for all components + */ + public void GUIComponents1() throws Exception { + String name = guiItem.getClass().getName(); + + assertEquals("Name should be same as static label for " + name, guiItem.getStaticLabel(), guiItem.getName()); + if (name.startsWith("org.apache.jmeter.examples.")){ + return; + } + if (!name.endsWith("TestBeanGUI")) { + try { + String label = guiItem.getLabelResource(); + assertNotNull("Label should not be null for "+name, label); + assertTrue("Label should not be empty for "+name, label.length() > 0); + assertFalse("'" + label + "' should be in resource file for " + name, JMeterUtils.getResString( + label).startsWith(JMeterUtils.RES_KEY_PFX)); + } catch (UnsupportedOperationException uoe) { + log.warn("Class has not yet implemented getLabelResource " + name); + } + } + checkElementAlias(guiItem); + } + + /* + * Test GUI elements - not run for TestBeanGui items + */ + public void GUIComponents2() throws Exception { + String name = guiItem.getClass().getName(); + + // TODO these assertions should be separate tests + + TestElement el = guiItem.createTestElement(); + assertNotNull(name + ".createTestElement should be non-null ", el); + assertEquals("GUI-CLASS: Failed on " + name, name, el.getPropertyAsString(TestElement.GUI_CLASS)); + + assertEquals("NAME: Failed on " + name, guiItem.getName(), el.getName()); + assertEquals("TEST-CLASS: Failed on " + name, el.getClass().getName(), el + .getPropertyAsString(TestElement.TEST_CLASS)); + TestElement el2 = guiItem.createTestElement(); + el.setName("hey, new name!:"); + el.setProperty("NOT", "Shouldn't be here"); + if (!(guiItem instanceof UnsharedComponent)) { + assertEquals("SHARED: Failed on " + name, "", el2.getPropertyAsString("NOT")); + } + log.debug("Saving element: " + el.getClass()); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + SaveService.saveElement(el, bos); + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + bos.close(); + el = (TestElement) SaveService.loadElement(bis); + bis.close(); + assertNotNull("Load element failed on: "+name,el); + guiItem.configure(el); + assertEquals("CONFIGURE-TEST: Failed on " + name, el.getName(), guiItem.getName()); + guiItem.modifyTestElement(el2); + assertEquals("Modify Test: Failed on " + name, "hey, new name!:", el2.getName()); + } + + /* + * Test serializable elements - create the suite of tests + */ + private static Test suiteSerializableElements() throws Exception { + TestSuite suite = new TestSuite("SerializableElements"); + Iterator<Object> iter = getObjects(Serializable.class).iterator(); + while (iter.hasNext()) { + Serializable serObj = (Serializable) iter.next(); + if (serObj.getClass().getName().endsWith("_Stub")) { + continue; + } + TestSuite ts = new TestSuite(serObj.getClass().getName()); + ts.addTest(new JMeterTest("runSerialTest", serObj)); + suite.addTest(ts); + } + return suite; + } + + /* + * Test serializable elements - test the object + */ + public void runSerialTest() throws Exception { + if (!(serObj instanceof Component)) {// + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(bytes); + out.writeObject(serObj); + out.close(); + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray())); + Object readObject = in.readObject(); + in.close(); + assertEquals("deserializing class: " + serObj.getClass().getName(), serObj.getClass(), readObject + .getClass()); + } catch (Exception e) { + fail("serialization of " + serObj.getClass().getName() + " failed: " + e); + } + } + } + + /* + * Test TestElements - create the suite + */ + private static Test suiteTestElements() throws Exception { + TestSuite suite = new TestSuite("TestElements"); + Iterator<Object> iter = getObjects(TestElement.class).iterator(); + while (iter.hasNext()) { + TestElement item = (TestElement) iter.next(); + TestSuite ts = new TestSuite(item.getClass().getName()); + ts.addTest(new JMeterTest("runTestElement", item)); + suite.addTest(ts); + } + return suite; + } + + /* + * Test TestElements - implement the test case + */ + public void runTestElement() throws Exception { + checkElementCloning(testItem); + String name = testItem.getClass().getName(); + assertTrue(name + " must implement Serializable", testItem instanceof Serializable); + if (name.startsWith("org.apache.jmeter.examples.")){ + return; + } + if (name.equals("org.apache.jmeter.control.TransactionSampler")){ + return; // Not a real sampler + } + + checkElementAlias(testItem); + } + + public void readAliases() throws Exception { + nameMap = SaveService.loadProperties(); + assertNotNull("SaveService nameMap (saveservice.properties) should not be null",nameMap); + } + + private void checkElementAlias(Object item) { + String name=item.getClass().getName(); + boolean contains = nameMap.values().contains(name); + if (!contains){ + //System.out.println(name.substring(name.lastIndexOf('.')+1)+"="+name); + fail("SaveService nameMap (saveservice.properties) should contain "+name); + } + } + + private static Collection<Object> getObjects(Class<?> extendsClass) throws Exception { + String exName = extendsClass.getName(); + Object myThis = ""; + Iterator<String> classes = ClassFinder + .findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { extendsClass }).iterator(); + List<Object> objects = new LinkedList<Object>(); + String n = ""; + boolean caughtError = true; + Throwable caught = null; + try { + while (classes.hasNext()) { + n = classes.next(); + // TODO - improve this check + if (n.endsWith("RemoteJMeterEngineImpl")) { + continue; // Don't try to instantiate remote server + } + Class<?> c = null; + try { + c = Class.forName(n); + try { + // Try with a parameter-less constructor first + objects.add(c.newInstance()); + } catch (InstantiationException e) { + caught = e; + // System.out.println(e.toString()); + try { + // Events often have this constructor + objects.add(c.getConstructor(new Class[] { Object.class }).newInstance( + new Object[] { myThis })); + } catch (NoSuchMethodException f) { + // no luck. Ignore this class + System.out.println("o.a.j.junit.JMeterTest WARN: " + exName + ": NoSuchMethodException " + n + ", missing empty Constructor or Constructor with Object parameter"); + } + } + } catch (NoClassDefFoundError e) { + // no luck. Ignore this class + System.out.println("o.a.j.junit.JMeterTest WARN: " + exName + ": NoClassDefFoundError " + n); + } catch (IllegalAccessException e) { + caught = e; + System.out.println("o.a.j.junit.JMeterTest WARN: " + exName + ": IllegalAccessException " + n); + // We won't test restricted-access classes. + } catch (HeadlessException e) { + caught = e; + System.out.println("o.a.j.junit.JMeterTest Error creating "+n+" "+e.toString()); + } catch (Exception e) { + caught = e; + if (e instanceof RemoteException) { // not thrown, so need to check here + System.out.println("o.a.j.junit.JMeterTest WARN: " + "Error creating " + n + " " + e.toString()); + } else { + throw new Exception("Error creating " + n, e); + } + } + } + caughtError = false; + } finally { + if (caughtError) { + System.out.println("Last class=" + n); + System.out.println("objects.size=" + objects.size()); + System.out.println("Last error=" + caught); + } + } + + if (objects.size() == 0) { + System.out.println("No classes found that extend " + exName + ". Check the following:"); + System.out.println("Search paths are:"); + String ss[] = JMeterUtils.getSearchPaths(); + for (int i = 0; i < ss.length; i++) { + System.out.println(ss[i]); + } + if (!classPathShown) {// Only dump it once + System.out.println("Class path is:"); + String cp = System.getProperty("java.class.path"); + String cpe[] = JOrphanUtils.split(cp, java.io.File.pathSeparator); + for (int i = 0; i < cpe.length; i++) { + System.out.println(cpe[i]); + } + classPathShown = true; + } + } + return objects; + } + + private static void cloneTesting(TestElement item, TestElement clonedItem) { + assertTrue(item != clonedItem); + assertEquals("CLONE-SAME-CLASS: testing " + item.getClass().getName(), item.getClass().getName(), clonedItem + .getClass().getName()); + } + + private static void checkElementCloning(TestElement item) { + TestElement clonedItem = (TestElement) item.clone(); + cloneTesting(item, clonedItem); + PropertyIterator iter2 = item.propertyIterator(); + while (iter2.hasNext()) { + JMeterProperty item2 = iter2.next(); + // [sebb] assertEquals(item2, + // clonedItem.getProperty(item2.getName())); + assertEquals(item2.getStringValue(), clonedItem.getProperty(item2.getName()).getStringValue()); + assertTrue(item2 != clonedItem.getProperty(item2.getName())); + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/junit/JMeterTestCase.java b/ApacheJmeter/org/apache/jmeter/junit/JMeterTestCase.java new file mode 100644 index 0000000..9081d96 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/junit/JMeterTestCase.java @@ -0,0 +1,177 @@ +/* + * 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.junit; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Locale; +import java.util.MissingResourceException; + +import junit.framework.TestCase; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.functions.AbstractFunction; +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/* + * Extend JUnit TestCase to provide common setup + */ +public abstract class JMeterTestCase extends TestCase { + // Used by findTestFile + private static final String filePrefix; + + public JMeterTestCase() { + super(); + } + + public JMeterTestCase(String name) { + super(name); + } + + /* + * If not running under AllTests.java, make sure that the properties (and + * log file) are set up correctly. + * + * N.B. In order for this to work correctly, the JUnit test must be started + * in the bin directory, and all the JMeter jars (plus any others needed at + * run-time) need to be on the classpath. + * + */ + static { + if (JMeterUtils.getJMeterProperties() == null) { + String file = "testfiles/jmetertest.properties"; + File f = new File(file); + if (!f.canRead()) { + System.out.println("Can't find " + file + " - trying bin directory"); + file = "bin/" + file;// JMeterUtils assumes Unix-style separators + filePrefix = "bin/"; + } else { + filePrefix = ""; + } + // Used to be done in initializeProperties + String home=new File(System.getProperty("user.dir"),filePrefix).getParent(); + System.out.println("Setting JMeterHome: "+home); + JMeterUtils.setJMeterHome(home); + System.setProperty("jmeter.home", home); // needed for scripts + JMeterUtils jmu = new JMeterUtils(); + try { + jmu.initializeProperties(file); + } catch (MissingResourceException e) { + System.out.println("** Can't find resources - continuing anyway **"); + } + System.out.println("JMeterVersion="+JMeterUtils.getJMeterVersion()); + logprop("java.version"); + logprop("java.vm.name"); + logprop("java.vendor"); + logprop("java.home"); + logprop("file.encoding"); + // Display actual encoding used (will differ if file.encoding is not recognised) + System.out.println("default encoding="+Charset.defaultCharset()); + logprop("user.home"); + logprop("user.dir"); + logprop("user.language"); + logprop("user.region"); + logprop("user.country"); + logprop("user.variant"); + System.out.println("Locale="+Locale.getDefault().toString()); + logprop("java.class.version"); + logprop("os.name"); + logprop("os.version"); + logprop("os.arch"); + logprop("java.class.path"); + // String cp = System.getProperty("java.class.path"); + // String cpe[]= JOrphanUtils.split(cp,File.pathSeparator); + // System.out.println("java.class.path="); + // for (int i=0;i<cpe.length;i++){ + // System.out.println(cpe[i]); + // } + } else { + filePrefix = ""; + } + } + + private static void logprop(String prop) { + System.out.println(prop + "=" + System.getProperty(prop)); + } + + // Helper method to find a file + protected static File findTestFile(String file) { + File f = new File(file); + if (filePrefix.length() > 0 && !f.isAbsolute()) { + f = new File(filePrefix, file);// Add the offset + } + return f; + } + + // Helper method to find a test path + protected static String findTestPath(String file) { + File f = new File(file); + if (filePrefix.length() > 0 && !f.isAbsolute()) { + return filePrefix + file;// Add the offset + } + return file; + } + + protected static final Logger testLog = LoggingManager.getLoggerForClass(); + + protected void checkInvalidParameterCounts(AbstractFunction func, int minimum) + throws Exception { + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + for (int c = 0; c < minimum; c++) { + try { + func.setParameters(parms); + fail("Should have generated InvalidVariableException for " + parms.size() + + " parameters"); + } catch (InvalidVariableException ignored) { + } + parms.add(new CompoundVariable()); + } + func.setParameters(parms); + } + + protected void checkInvalidParameterCounts(AbstractFunction func, int min, + int max) throws Exception { + Collection<CompoundVariable> parms = new LinkedList<CompoundVariable>(); + for (int count = 0; count < min; count++) { + try { + func.setParameters(parms); + fail("Should have generated InvalidVariableException for " + parms.size() + + " parameters"); + } catch (InvalidVariableException ignored) { + } + parms.add(new CompoundVariable()); + } + for (int count = min; count <= max; count++) { + func.setParameters(parms); + parms.add(new CompoundVariable()); + } + parms.add(new CompoundVariable()); + try { + func.setParameters(parms); + fail("Should have generated InvalidVariableException for " + parms.size() + + " parameters"); + } catch (InvalidVariableException ignored) { + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/junit/stubs/TestSampler.java b/ApacheJmeter/org/apache/jmeter/junit/stubs/TestSampler.java new file mode 100644 index 0000000..33e53d3 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/junit/stubs/TestSampler.java @@ -0,0 +1,74 @@ +/* + * 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. + * + */ + +/* + * Created on Apr 30, 2003 + * + * To change the template for this generated file go to + * Window>Preferences>Java>Code Generation>Code and Comments + */ +package org.apache.jmeter.junit.stubs; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; + +public class TestSampler extends AbstractSampler { + + private static final long serialVersionUID = 240L; + + private long wait = 0; + + private long samples = 0; // number of samples taken + + /** + * {@inheritDoc} + */ + public SampleResult sample(Entry e) { + if (wait > 0) { + try { + Thread.sleep(wait); + } catch (InterruptedException e1) { + // ignore + } + } + samples++; + return null; + } + + public TestSampler(String name, long wait) { + setName(name); + this.wait = wait; + } + + public TestSampler(String name) { + setName(name); + } + + public TestSampler() { + } + + @Override + public String toString() { + return getName(); + } + + public long getSamples() { + return samples; + } +} diff --git a/ApacheJmeter/org/apache/jmeter/monitor/model/TestObjectFactory.java b/ApacheJmeter/org/apache/jmeter/monitor/model/TestObjectFactory.java new file mode 100644 index 0000000..9461d9f --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/monitor/model/TestObjectFactory.java @@ -0,0 +1,73 @@ +/* + * 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.monitor.model; + +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.jmeter.junit.JMeterTestCase; + +public class TestObjectFactory extends JMeterTestCase { + + private ObjectFactory of; + + private Status status; + + @Override + public void setUp(){ + of = ObjectFactory.getInstance(); + } + + + public void testStatus() throws Exception { + status = of.parseString("<status></status>"); + assertNotNull(status); + } + + public void testNoStatus() throws Exception { + status = of.parseString("<a></a>"); + assertNull(status); + } + + public void testFileData() throws Exception { + byte[] bytes= FileUtils.readFileToByteArray(findTestFile("testfiles/monitorStatus.xml")); + status = of.parseBytes(bytes); + checkResult(); + } + + public void testStringData() throws Exception { + String content = FileUtils.readFileToString(findTestFile("testfiles/monitorStatus.xml")); + status = of.parseString(content); + checkResult(); + } + + private void checkResult(){ + assertNotNull(status); + final Jvm jvm = status.getJvm(); + assertNotNull(jvm); + final Memory memory = jvm.getMemory(); + assertNotNull(memory); + assertEquals(10807352, memory.getFree()); + assertEquals(16318464, memory.getTotal()); + assertEquals(259522560, memory.getMax()); + final List<Connector> connector = status.getConnector(); + assertNotNull(connector); + assertEquals(2, connector.size()); + Connector conn = connector.get(0); + assertEquals(200, conn.getThreadInfo().getMaxThreads()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/monitor/model/benchmark/ParseBenchmark.java b/ApacheJmeter/org/apache/jmeter/monitor/model/benchmark/ParseBenchmark.java new file mode 100644 index 0000000..a97b031 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/monitor/model/benchmark/ParseBenchmark.java @@ -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. + */ +package org.apache.jmeter.monitor.model.benchmark; + +public class ParseBenchmark { + + /** + * + */ + public ParseBenchmark() { + super(); + } + + public static void main(String[] args) { + if (args.length == 3) { + int parser = 0; + String file = null; + int loops = 1000; + if (args[0] != null) { + if (!args[0].equals("jaxb")) { + parser = 1; + } + } + if (args[1] != null) { + file = args[1]; + } + if (args[2] != null) { + loops = Integer.parseInt(args[2]); + } + + java.io.File infile = new java.io.File(file); + java.io.FileInputStream fis = null; + java.io.InputStreamReader isr = null; + StringBuilder buf = new StringBuilder(); + try { + fis = new java.io.FileInputStream(infile); + isr = new java.io.InputStreamReader(fis); + java.io.BufferedReader br = new java.io.BufferedReader(isr); + String line = null; + while ((line = br.readLine()) != null) { + buf.append(line); + } + } catch (Exception e) { + e.printStackTrace(); + } + long start = 0; + long end = 0; + String contents = buf.toString().trim(); + System.out.println("start test: " + loops + " iterations"); + System.out.println("content:"); + System.out.println(contents); + + if (parser == 0) { + /** + * try { JAXBContext jxbc = new + * org.apache.jorphan.tomcat.manager.ObjectFactory(); + * Unmarshaller mar = jxbc.createUnmarshaller(); + * + * start = System.currentTimeMillis(); for (int idx=0; idx < + * loops; idx++){ StreamSource ss = new StreamSource( new + * ByteArrayInputStream(contents.getBytes())); Object ld = + * mar.unmarshal(ss); } end = System.currentTimeMillis(); + * System.out.println("elapsed Time: " + (end - start)); } catch + * (JAXBException e){ } + */ + } else { + org.apache.jmeter.monitor.model.ObjectFactory of = org.apache.jmeter.monitor.model.ObjectFactory + .getInstance(); + start = System.currentTimeMillis(); + for (int idx = 0; idx < loops; idx++) { + // NOTUSED org.apache.jmeter.monitor.model.Status st = + of.parseBytes(contents.getBytes()); // TODO - charset? + } + end = System.currentTimeMillis(); + System.out.println("elapsed Time: " + (end - start)); + } + + } else { + System.out.println("missing paramters:"); + System.out.println("parser file iterations"); + System.out.println("example: jaxb status.xml 1000"); + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/config/MultipartUrlConfigTest.java b/ApacheJmeter/org/apache/jmeter/protocol/http/config/MultipartUrlConfigTest.java new file mode 100644 index 0000000..b55d129 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/config/MultipartUrlConfigTest.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.config; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.protocol.http.util.HTTPFileArgs; + +public class MultipartUrlConfigTest extends TestCase { + + public MultipartUrlConfigTest(String name) { + super(name); + } + + @SuppressWarnings("deprecation") + public void testConstructors() { + MultipartUrlConfig muc = new MultipartUrlConfig(); + assertEquals(0, muc.getArguments().getArgumentCount()); + assertEquals(0, muc.getHTTPFileArgs().getHTTPFileArgCount()); + muc = new MultipartUrlConfig("boundary"); + assertEquals(0, muc.getArguments().getArgumentCount()); + assertEquals(0, muc.getHTTPFileArgs().getHTTPFileArgCount()); + assertEquals("boundary", muc.getBoundary()); + } + + // TODO - should LF-only EOL be allowed? + public void testParseArgumentsLF() { + String queryString + = "Content-Disposition: form-data; name=\"aa\"\n" + + "Content-Type: text/plain; charset=ISO-8859-1\n" + + "Content-Transfer-Encoding: 8bit\n" + + "\n" + + "bb\n" + + "--7d159c1302d0y0\n" + + "Content-Disposition: form-data; name=\"xx\"\n" + + "Content-Type: text/plain; charset=ISO-8859-1\n" + + "Content-Transfer-Encoding: 8bit\n" + + "\n" + + "yy\n" + + "--7d159c1302d0y0\n" + + "Content-Disposition: form-data; name=\"abc\"\n" + + "Content-Type: text/plain; charset=ISO-8859-1\n" + + "Content-Transfer-Encoding: 8bit\n" + + "\n" + + "xyz \n" + + "xyz \n" + + "--7d159c1302d0y0\n" + + "Content-Disposition: form-data; name=\"param1\"; filename=\"file1\"\n" + + "Content-Type: text/plain\n" + + "Content-Transfer-Encoding: binary\n" + + "\n" + + "file content\n" + + "\n"; + MultipartUrlConfig muc = new MultipartUrlConfig("7d159c1302d0y0"); + muc.parseArguments(queryString); + HTTPFileArgs files = muc.getHTTPFileArgs(); + assertEquals(1, files.getHTTPFileArgCount()); + HTTPFileArg file = (HTTPFileArg) files.iterator().next().getObjectValue(); + assertEquals("file1", file.getPath()); + assertEquals("param1", file.getParamName()); + assertEquals("text/plain", file.getMimeType()); + Arguments args = muc.getArguments(); + assertEquals(3, args.getArgumentCount()); + Argument arg = args.getArgument(0); + assertEquals("aa", arg.getName()); + assertEquals("bb", arg.getValue()); + arg = args.getArgument(1); + assertEquals("xx", arg.getName()); + assertEquals("yy", arg.getValue()); + arg = args.getArgument(2); + assertEquals("abc", arg.getName()); + assertEquals("xyz \nxyz ", arg.getValue()); + } + + public void testParseArgumentsCRLF() { + String queryString + = "Content-Disposition: form-data; name=\"aa\"\r\n" + + "Content-Type: text/plain; charset=ISO-8859-1\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "bb\r\n" + + "--7d159c1302d0y0\r\n" + + "Content-Disposition: form-data; name=\"xx\"\r\n" + + "Content-Type: text/plain; charset=ISO-8859-1\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "yy\r\n" + + "--7d159c1302d0y0\r\n" + + "Content-Disposition: form-data; name=\"abc\"\r\n" + + "Content-Type: text/plain; charset=ISO-8859-1\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "xyz \r\n" + + "xyz \r\n" + + "--7d159c1302d0y0\r\n" + + "Content-Disposition: form-data; name=\"param1\"; filename=\"file1\"\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Transfer-Encoding: binary\r\n" + + "\r\n" + + "file content\r\n" + + "\r\n"; + MultipartUrlConfig muc = new MultipartUrlConfig("7d159c1302d0y0"); + muc.parseArguments(queryString); + HTTPFileArgs files = muc.getHTTPFileArgs(); + assertEquals(1, files.getHTTPFileArgCount()); + HTTPFileArg file = (HTTPFileArg) files.iterator().next().getObjectValue(); + assertEquals("file1", file.getPath()); + assertEquals("param1", file.getParamName()); + assertEquals("text/plain", file.getMimeType()); + Arguments args = muc.getArguments(); + assertEquals(3, args.getArgumentCount()); + Argument arg = args.getArgument(0); + assertEquals("aa", arg.getName()); + assertEquals("bb", arg.getValue()); + arg = args.getArgument(1); + assertEquals("xx", arg.getName()); + assertEquals("yy", arg.getValue()); + arg = args.getArgument(2); + assertEquals("abc", arg.getName()); + assertEquals("xyz \r\nxyz ", arg.getValue()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/config/UrlConfigTest.java b/ApacheJmeter/org/apache/jmeter/protocol/http/config/UrlConfigTest.java new file mode 100644 index 0000000..c763754 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/config/UrlConfigTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.config; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; + +public class UrlConfigTest extends JMeterTestCase { + private HTTPSamplerBase config; + + private HTTPSamplerBase defaultConfig; + + private HTTPSamplerBase partialConfig; + + public UrlConfigTest(String name) { + super(name); + } + + @Override + protected void setUp() { + Arguments args = new Arguments(); + args.addArgument("username", "mstover"); + args.addArgument("password", "pass"); + args.addArgument("action", "login"); + config = new HTTPNullSampler(); + config.setName("Full Config"); + config.setProperty(HTTPSamplerBase.DOMAIN, "www.lazer.com"); + config.setProperty(HTTPSamplerBase.PATH, "login.jsp"); + config.setProperty(HTTPSamplerBase.METHOD, HTTPSamplerBase.POST); + config.setProperty(new TestElementProperty(HTTPSamplerBase.ARGUMENTS, args)); + defaultConfig = new HTTPNullSampler(); + defaultConfig.setName("default"); + defaultConfig.setProperty(HTTPSamplerBase.DOMAIN, "www.xerox.com"); + defaultConfig.setProperty(HTTPSamplerBase.PATH, "default.html"); + partialConfig = new HTTPNullSampler(); + partialConfig.setProperty(HTTPSamplerBase.PATH, "main.jsp"); + partialConfig.setProperty(HTTPSamplerBase.METHOD, HTTPSamplerBase.GET); + } + + public void testSimpleConfig() { + assertEquals("Full Config", config.getName()); + assertEquals("www.lazer.com", config.getDomain()); + } + + public void testOverRide() { + JMeterProperty jmp = partialConfig.getProperty(HTTPSamplerBase.DOMAIN); + assertTrue(jmp instanceof NullProperty); + assertEquals(jmp, new NullProperty(HTTPSamplerBase.DOMAIN)); + partialConfig.addTestElement(defaultConfig); + assertEquals(partialConfig.getPropertyAsString(HTTPSamplerBase.DOMAIN), "www.xerox.com"); + assertEquals(partialConfig.getPropertyAsString(HTTPSamplerBase.PATH), "main.jsp"); + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestAuthManager.java b/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestAuthManager.java new file mode 100644 index 0000000..4bfab43 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestAuthManager.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.net.URL; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.testelement.property.CollectionProperty; + +public class TestAuthManager extends JMeterTestCase { + public TestAuthManager(String name) { + super(name); + } + + public void testHttp() throws Exception { + assertTrue(AuthManager.isSupportedProtocol(new URL("http:"))); + } + + public void testHttps() throws Exception { + assertTrue(AuthManager.isSupportedProtocol(new URL("https:"))); + } + + public void testFile() throws Exception { + AuthManager am = new AuthManager(); + CollectionProperty ao = am.getAuthObjects(); + assertEquals(0, ao.size()); + am.addFile(findTestPath("testfiles/TestAuth.txt")); + assertEquals(9, ao.size()); + Authorization at; + at = am.getAuthForURL(new URL("http://a.b.c/")); + assertEquals("login", at.getUser()); + assertEquals("password", at.getPass()); + at = am.getAuthForURL(new URL("http://a.b.c:80/")); // same as above + assertEquals("login", at.getUser()); + assertEquals("password", at.getPass()); + at = am.getAuthForURL(new URL("http://a.b.c:443/"));// not same + assertNull(at); + at = am.getAuthForURL(new URL("http://a.b.c/1")); + assertEquals("login1", at.getUser()); + assertEquals("password1", at.getPass()); + assertEquals("", at.getDomain()); + assertEquals("", at.getRealm()); + at = am.getAuthForURL(new URL("http://d.e.f/")); + assertEquals("user", at.getUser()); + assertEquals("pass", at.getPass()); + assertEquals("domain", at.getDomain()); + assertEquals("realm", at.getRealm()); + at = am.getAuthForURL(new URL("https://j.k.l/")); + assertEquals("jkl", at.getUser()); + assertEquals("pass", at.getPass()); + at = am.getAuthForURL(new URL("https://j.k.l:443/")); + assertEquals("jkl", at.getUser()); + assertEquals("pass", at.getPass()); + at = am.getAuthForURL(new URL("https://l.m.n/")); + assertEquals("lmn443", at.getUser()); + assertEquals("pass", at.getPass()); + at = am.getAuthForURL(new URL("https://l.m.n:443/")); + assertEquals("lmn443", at.getUser()); + assertEquals("pass", at.getPass()); + at = am.getAuthForURL(new URL("https://l.m.n:8443/")); + assertEquals("lmn8443", at.getUser()); + assertEquals("pass", at.getPass()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestCacheManager.java b/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestCacheManager.java new file mode 100644 index 0000000..73323b6 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestCacheManager.java @@ -0,0 +1,381 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLConnection; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.URI; +import org.apache.commons.httpclient.URIException; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.util.HttpURLConnection; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.control.CacheManager.CacheEntry; +import org.apache.jmeter.protocol.http.util.HTTPConstantsInterface; +import org.apache.jmeter.samplers.SampleResult; + +public class TestCacheManager extends JMeterTestCase { + + private class URLConnectionStub extends URLConnection { + + protected URLConnectionStub(URL url) { + super(url); + } + + private URLConnectionStub(URLConnection urlConnection) { + super(urlConnection.getURL()); + } + + @Override + public void connect() throws IOException { + } + + private String expires = null; + private String cacheControl = null; + + @Override + public String getHeaderField(String name) { + if (HTTPConstantsInterface.LAST_MODIFIED.equals(name)) { + return currentTimeInGMT; + } else if (HTTPConstantsInterface.ETAG.equals(name)) { + return EXPECTED_ETAG; + } else if (HTTPConstantsInterface.EXPIRES.equals(name)){ + return expires; + } else if (HTTPConstantsInterface.CACHE_CONTROL.equals(name)){ + return cacheControl; + } + return super.getHeaderField(name); + } + @Override + public URL getURL() { + return url; + } + } + + private class HttpMethodStub extends PostMethod { + private Header lastModifiedHeader; + private Header etagHeader; + private String expires; + private String cacheControl; + + HttpMethodStub() { + this.lastModifiedHeader = new Header(HTTPConstantsInterface.LAST_MODIFIED, currentTimeInGMT); + this.etagHeader = new Header(HTTPConstantsInterface.ETAG, EXPECTED_ETAG); + } + + @Override + public Header getResponseHeader(String headerName) { + if (HTTPConstantsInterface.LAST_MODIFIED.equals(headerName)) { + return this.lastModifiedHeader; + } else if (HTTPConstantsInterface.ETAG.equals(headerName)) { + return this.etagHeader; + } else if (HTTPConstantsInterface.EXPIRES.equals(headerName)) { + return expires == null ? null : new Header(HTTPConstantsInterface.EXPIRES, expires); + } else if (HTTPConstantsInterface.CACHE_CONTROL.equals(headerName)) { + return cacheControl == null ? null : new Header(HTTPConstantsInterface.CACHE_CONTROL, cacheControl); + } + return null; + } + + @Override + public URI getURI() throws URIException { + return uri; + } + } + + private static class HttpURLConnectionStub extends HttpURLConnection { + private Map<String, List<String>> properties; + + public HttpURLConnectionStub(HttpMethod method, URL url) { + super(method, url); + this.properties = new HashMap<String, List<String>>(); + } + + @Override + public void addRequestProperty(String key, String value) { + List<String> list = new ArrayList<String>(); + list.add(value); + this.properties.put(key, list); + } + + @Override + public Map<String, List<String>> getRequestProperties() { + return this.properties; + } + + } + + private static final String LOCAL_HOST = "http://localhost/"; + private static final String EXPECTED_ETAG = "0xCAFEBABEDEADBEEF"; + private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + private CacheManager cacheManager; + private String currentTimeInGMT; + private URL url; + private URI uri; + private URLConnection urlConnection; + private HttpMethod httpMethod; + private HttpURLConnection httpUrlConnection; + private SampleResult sampleResultOK; + + public TestCacheManager(String name) { + super(name); + } + + private String makeDate(Date d){ + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); + simpleDateFormat.setTimeZone(GMT); + return simpleDateFormat.format(d); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + this.cacheManager = new CacheManager(); + this.currentTimeInGMT = makeDate(new Date()); + this.uri = new URI(LOCAL_HOST, false); + this.url = new URL(LOCAL_HOST); + this.urlConnection = new URLConnectionStub(this.url.openConnection()); + this.httpMethod = new HttpMethodStub(); + this.httpUrlConnection = new HttpURLConnectionStub(this.httpMethod, this.url); + this.sampleResultOK = getSampleResultWithSpecifiedResponseCode("200"); + } + + @Override + protected void tearDown() throws Exception { + this.httpUrlConnection = null; + this.httpMethod = null; + this.urlConnection = null; + this.url = null; + this.uri = null; + this.cacheManager = null; + this.currentTimeInGMT = null; + this.sampleResultOK = null; + super.tearDown(); + } + + public void testExpiresJava() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((URLConnectionStub)urlConnection).expires=makeDate(new Date(System.currentTimeMillis()+2000)); + this.cacheManager.saveDetails(this.urlConnection, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + } + + public void testNoExpiresJava() throws Exception{ + this.cacheManager.setUseExpires(false); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((URLConnectionStub)urlConnection).expires=makeDate(new Date(System.currentTimeMillis()+2000)); + this.cacheManager.saveDetails(this.urlConnection, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + } + + public void testCacheJava() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((URLConnectionStub)urlConnection).expires=makeDate(new Date(System.currentTimeMillis())); + ((URLConnectionStub)urlConnection).cacheControl="public, max-age=10"; + this.cacheManager.saveDetails(this.urlConnection, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + } + + public void testExpiresHttpClient() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).expires=makeDate(new Date(System.currentTimeMillis()+2000)); + this.cacheManager.saveDetails(httpMethod, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + } + + + public void testCacheHttpClient() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).expires=makeDate(new Date(System.currentTimeMillis())); + ((HttpMethodStub)httpMethod).cacheControl="public, max-age=10"; + this.cacheManager.saveDetails(httpMethod, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + } + + public void testCacheHttpClientBug51932() throws Exception{ + this.cacheManager.setUseExpires(true); + this.cacheManager.testIterationStart(null); + assertNull("Should not find entry",getThreadCacheEntry(LOCAL_HOST)); + assertFalse("Should not find valid entry",this.cacheManager.inCache(url)); + ((HttpMethodStub)httpMethod).expires=makeDate(new Date(System.currentTimeMillis())); + ((HttpMethodStub)httpMethod).cacheControl="public, max-age=10, no-transform"; + this.cacheManager.saveDetails(httpMethod, sampleResultOK); + assertNotNull("Should find entry",getThreadCacheEntry(LOCAL_HOST)); + assertTrue("Should find valid entry",this.cacheManager.inCache(url)); + } + + public void testGetClearEachIteration() throws Exception { + assertFalse("Should default not to clear after each iteration.", this.cacheManager.getClearEachIteration()); + this.cacheManager.setClearEachIteration(true); + assertTrue("Should be settable to clear after each iteration.", this.cacheManager.getClearEachIteration()); + this.cacheManager.setClearEachIteration(false); + assertFalse("Should be settable not to clear after each iteration.", this.cacheManager.getClearEachIteration()); + } + + public void testSaveDetailsWithEmptySampleResultGivesNoCacheEntry() throws Exception { + saveDetailsWithConnectionAndSampleResultWithResponseCode(""); + assertTrue("Saving details with empty SampleResult should not make cache entry.", getThreadCache().isEmpty()); + } + + public void testSaveDetailsURLConnectionWithSampleResultWithResponseCode200GivesCacheEntry() throws Exception { + saveDetailsWithConnectionAndSampleResultWithResponseCode("200"); + CacheManager.CacheEntry cacheEntry = getThreadCacheEntry(this.url.toString()); + assertNotNull("Saving details with SampleResult & connection with 200 response should make cache entry.", cacheEntry); + assertEquals("Saving details with SampleResult & connection with 200 response should make cache entry with an etag.", EXPECTED_ETAG, cacheEntry.getEtag()); + assertEquals("Saving details with SampleResult & connection with 200 response should make cache entry with last modified date.", this.currentTimeInGMT, cacheEntry.getLastModified()); + } + + public void testSaveDetailsHttpMethodWithSampleResultWithResponseCode200GivesCacheEntry() throws Exception { + saveDetailsWithHttpMethodAndSampleResultWithResponseCode("200"); + CacheManager.CacheEntry cacheEntry = getThreadCacheEntry(this.httpMethod.getURI().toString()); + assertNotNull("Saving SampleResult with HttpMethod & 200 response should make cache entry.", cacheEntry); + assertEquals("Saving details with SampleResult & HttpMethod with 200 response should make cache entry with no etag.", EXPECTED_ETAG, cacheEntry.getEtag()); + assertEquals("Saving details with SampleResult & HttpMethod with 200 response should make cache entry with no last modified date.", this.currentTimeInGMT, cacheEntry.getLastModified()); + } + + public void testSaveDetailsURLConnectionWithSampleResultWithResponseCode404GivesNoCacheEntry() throws Exception { + saveDetailsWithConnectionAndSampleResultWithResponseCode("404"); + assertNull("Saving details with SampleResult & connection with 404 response should not make cache entry.", getThreadCacheEntry(url.toString())); + } + + public void testSaveDetailsHttpMethodWithSampleResultWithResponseCode404GivesNoCacheEntry() throws Exception { + saveDetailsWithHttpMethodAndSampleResultWithResponseCode("404"); + assertNull("Saving SampleResult with HttpMethod & 404 response should not make cache entry.", getThreadCacheEntry(this.httpMethod.getPath())); + } + + public void testSetHeadersHttpMethodWithSampleResultWithResponseCode200GivesCacheEntry() throws Exception { + this.httpMethod.setURI(this.uri); + this.httpMethod.addRequestHeader(new Header(HTTPConstantsInterface.IF_MODIFIED_SINCE, this.currentTimeInGMT, false)); + this.httpMethod.addRequestHeader(new Header(HTTPConstantsInterface.ETAG, EXPECTED_ETAG, false)); + saveDetailsWithHttpMethodAndSampleResultWithResponseCode("200"); + setHeadersWithUrlAndHttpMethod(); + checkRequestHeader(HTTPConstantsInterface.IF_NONE_MATCH, EXPECTED_ETAG); + checkRequestHeader(HTTPConstantsInterface.IF_MODIFIED_SINCE, this.currentTimeInGMT); + } + + public void testSetHeadersHttpMethodWithSampleResultWithResponseCode404GivesNoCacheEntry() throws Exception { + this.httpMethod.setURI(this.uri); + saveDetailsWithHttpMethodAndSampleResultWithResponseCode("404"); + setHeadersWithUrlAndHttpMethod(); + assertNull("Saving SampleResult with HttpMethod & 404 response should not make cache entry.", getThreadCacheEntry(this.httpMethod.getPath())); + } + + public void testSetHeadersHttpURLConnectionWithSampleResultWithResponseCode200GivesCacheEntry() throws Exception { + saveDetailsWithConnectionAndSampleResultWithResponseCode("200"); + setHeadersWithHttpUrlConnectionAndUrl(); + Map<String, List<String>> properties = this.httpUrlConnection.getRequestProperties(); + checkProperty(properties, HTTPConstantsInterface.IF_NONE_MATCH, EXPECTED_ETAG); + checkProperty(properties, HTTPConstantsInterface.IF_MODIFIED_SINCE, this.currentTimeInGMT); + } + + public void testSetHeadersHttpURLConnectionWithSampleResultWithResponseCode404GivesNoCacheEntry() throws Exception { + saveDetailsWithConnectionAndSampleResultWithResponseCode("404"); + setHeadersWithHttpUrlConnectionAndUrl(); + assertNull("Saving SampleResult with HttpMethod & 404 response should not make cache entry.", getThreadCacheEntry(this.url.toString())); + } + + public void testClearCache() throws Exception { + assertTrue("ThreadCache should be empty initially.", getThreadCache().isEmpty()); + saveDetailsWithHttpMethodAndSampleResultWithResponseCode("200"); + assertFalse("ThreadCache should be populated after saving details for HttpMethod with SampleResult with response code 200.", getThreadCache().isEmpty()); + this.cacheManager.clear(); + assertTrue("ThreadCache should be emptied by call to clear.", getThreadCache().isEmpty()); + } + + private void checkRequestHeader(String requestHeader, String expectedValue) { + Header header = this.httpMethod.getRequestHeader(requestHeader); + assertEquals("Wrong name in header for " + requestHeader, requestHeader, header.getName()); + assertEquals("Wrong value for header " + header, expectedValue, header.getValue()); + } + + private static void checkProperty(Map<String, List<String>> properties, String property, String expectedPropertyValue) { + assertNotNull("Properties should not be null. Expected to find within it property = " + property + " with expected value = " + expectedPropertyValue, properties); + List<String> listOfPropertyValues = properties.get(property); + assertNotNull("No property entry found for property " + property, listOfPropertyValues); + assertEquals("Did not find single property for property " + property, 1, listOfPropertyValues.size()); + assertEquals("Unexpected value for property " + property, expectedPropertyValue, listOfPropertyValues.get(0)); + } + + private SampleResult getSampleResultWithSpecifiedResponseCode(String code) { + SampleResult sampleResult = new SampleResult(); + sampleResult.setResponseCode(code); + return sampleResult; + } + + private Map<String, CacheManager.CacheEntry> getThreadCache() throws Exception { + Field threadLocalfield = CacheManager.class.getDeclaredField("threadCache"); + threadLocalfield.setAccessible(true); + @SuppressWarnings("unchecked") + ThreadLocal<Map<String, CacheEntry>> threadLocal = (ThreadLocal<Map<String, CacheManager.CacheEntry>>) threadLocalfield.get(this.cacheManager); + return threadLocal.get(); + } + + private CacheManager.CacheEntry getThreadCacheEntry(String url) throws Exception { + return getThreadCache().get(url); + } + + private void saveDetailsWithHttpMethodAndSampleResultWithResponseCode(String responseCode) throws Exception { + SampleResult sampleResult = getSampleResultWithSpecifiedResponseCode(responseCode); + this.cacheManager.saveDetails(this.httpMethod, sampleResult); + } + + private void saveDetailsWithConnectionAndSampleResultWithResponseCode(String responseCode) { + SampleResult sampleResult = getSampleResultWithSpecifiedResponseCode(responseCode); + this.cacheManager.saveDetails(this.urlConnection, sampleResult); + } + + private void setHeadersWithHttpUrlConnectionAndUrl() { + this.cacheManager.setHeaders(this.httpUrlConnection, this.url); + } + + private void setHeadersWithUrlAndHttpMethod() { + this.cacheManager.setHeaders(this.url, this.httpMethod); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestCookieManager.java b/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestCookieManager.java new file mode 100644 index 0000000..68b3322 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestCookieManager.java @@ -0,0 +1,408 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.net.URL; + +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; + +public class TestCookieManager extends JMeterTestCase { + private CookieManager man = null; + + public TestCookieManager(String name) { + super(name); + } + + private JMeterContext jmctx = null; + + @Override + public void setUp() throws Exception { + super.setUp(); + jmctx = JMeterContextService.getContext(); + man = new CookieManager(); + man.setThreadContext(jmctx); + man.testStarted();// This is needed in order to set up the cookie policy + } + + public void testRemoveCookie() throws Exception { + man.setThreadContext(jmctx); + Cookie c = new Cookie("id", "me", "127.0.0.1", "/", false, 0); + man.add(c); + assertEquals(1, man.getCookieCount()); + // This should be ignored, as there is no value + Cookie d = new Cookie("id", "", "127.0.0.1", "/", false, 0); + man.add(d); + assertEquals(0, man.getCookieCount()); + man.add(c); + man.add(c); + assertEquals(1, man.getCookieCount()); + Cookie e = new Cookie("id", "me2", "127.0.0.1", "/", false, 0); + man.add(e); + assertEquals(1, man.getCookieCount()); + } + + public void testSendCookie() throws Exception { + man.add(new Cookie("id", "value", "jakarta.apache.org", "/", false, 9999999999L)); + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setDomain("jakarta.apache.org"); + sampler.setPath("/index.html"); + sampler.setMethod(HTTPSamplerBase.GET); + assertNotNull(man.getCookieHeaderForURL(sampler.getUrl())); + } + + public void testSendCookie2() throws Exception { + man.add(new Cookie("id", "value", ".apache.org", "/", false, 9999999999L)); + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setDomain("jakarta.apache.org"); + sampler.setPath("/index.html"); + sampler.setMethod(HTTPSamplerBase.GET); + assertNotNull(man.getCookieHeaderForURL(sampler.getUrl())); + } + + /** + * Test that the cookie domain field is actually handled as browsers do + * (i.e.: host X matches domain .X): + */ + public void testDomainHandling() throws Exception { + URL url = new URL("http://jakarta.apache.org/"); + man.addCookieFromHeader("test=1;domain=.jakarta.apache.org", url); + assertNotNull(man.getCookieHeaderForURL(url)); + } + + public void testCrossDomainHandling() throws Exception { + URL url = new URL("http://jakarta.apache.org/"); + assertEquals(0,man.getCookieCount()); // starts empty + man.addCookieFromHeader("test=2;domain=.hc.apache.org", url); + assertEquals(0,man.getCookieCount()); // should not be stored + man.addCookieFromHeader("test=1;domain=.jakarta.apache.org", url); + assertEquals(1,man.getCookieCount()); // OK + } + + /** + * Test that we won't be tricked by similar host names (this was a past + * bug, although it never got reported in the bug database): + */ + public void testSimilarHostNames() throws Exception { + URL url = new URL("http://ache.org/"); + man.addCookieFromHeader("test=1", url); + url = new URL("http://jakarta.apache.org/"); + assertNull(man.getCookieHeaderForURL(url)); + } + + // Test session cookie is returned + public void testSessionCookie() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("test=1", url); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test=1", s); + } + + // Bug 2063 + public void testCookieWithEquals() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("NSCP_USER_LOGIN1_NEW=SHA=xxxxx", url); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("NSCP_USER_LOGIN1_NEW=SHA=xxxxx", s); + Cookie c=man.get(0); + assertEquals("NSCP_USER_LOGIN1_NEW",c.getName()); + assertEquals("SHA=xxxxx",c.getValue()); + } + + // Test Old cookie is not returned + public void testOldCookie() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("test=1; expires=Mon, 01-Jan-1990 00:00:00 GMT", url); + String s = man.getCookieHeaderForURL(url); + assertNull(s); + } + + // Test New cookie is returned + public void testNewCookie() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("test=1; expires=Mon, 01-Jan-2990 00:00:00 GMT", url); + assertEquals(1,man.getCookieCount()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test=1", s); + } + + // Test multi-cookie header handling + public void testCookies1() throws Exception { + URL url = new URL("http://a.b.c.d/testCookies1"); + man.addCookieFromHeader("test1=1; comment=\"how,now\", test2=2; version=1", url); + assertEquals(2,man.getCookieCount()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test1=1; test2=2", s); + } + + public void testCookies2() throws Exception { + URL url = new URL("https://a.b.c.d/testCookies2"); + man.addCookieFromHeader("test1=1;secure, test2=2;secure", url); + assertEquals(2,man.getCookieCount()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test1=1; test2=2", s); + } + + // Test duplicate cookie handling + public void testDuplicateCookie() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("test=1", url); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test=1", s); + man.addCookieFromHeader("test=2", url); + s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test=2", s); + } + public void testDuplicateCookie2() throws Exception { + URL url = new URL("http://a.b.c/"); + man.addCookieFromHeader("test=1", url); + man.addCookieFromHeader("test2=a", url); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test=1; test2=a", s); // Assumes some kind of list is used + man.addCookieFromHeader("test=2", url); + man.addCookieFromHeader("test3=b", url); + s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test2=a; test=2; test3=b", s);// Assumes some kind of list is use + // If not using a list that retains the order, then the asserts would need to change + } + + + /** Tests missing cookie path for a trivial URL fetch from the domain + * Note that this fails prior to a fix for BUG 38256 + */ + public void testMissingPath0() throws Exception { + URL url = new URL("http://d.e.f/goo.html"); + man.addCookieFromHeader("test=moo", url); + String s = man.getCookieHeaderForURL(new URL("http://d.e.f/")); + assertNotNull(s); + assertEquals("test=moo", s); + } + + /** Tests missing cookie path for a non-trivial URL fetch from the + * domain. Note that this fails prior to a fix for BUG 38256 + */ + public void testMissingPath1() throws Exception { + URL url = new URL("http://d.e.f/moo.html"); + man.addCookieFromHeader("test=moo", url); + String s = man.getCookieHeaderForURL(new URL("http://d.e.f/goo.html")); + assertNotNull(s); + assertEquals("test=moo", s); + } + + /** Tests explicit root path with a trivial URL fetch from the domain */ + public void testRootPath0() throws Exception { + URL url = new URL("http://d.e.f/goo.html"); + man.addCookieFromHeader("test=moo;path=/", url); + String s = man.getCookieHeaderForURL(new URL("http://d.e.f/")); + assertNotNull(s); + assertEquals("test=moo", s); + } + + /** Tests explicit root path with a non-trivial URL fetch from the domain */ + public void testRootPath1() throws Exception { + URL url = new URL("http://d.e.f/moo.html"); + man.addCookieFromHeader("test=moo;path=/", url); + String s = man.getCookieHeaderForURL(new URL("http://d.e.f/goo.html")); + assertNotNull(s); + assertEquals("test=moo", s); + } + + // Test cookie matching + public void testCookieMatching() throws Exception { + URL url = new URL("http://a.b.c:8080/TopDir/fred.jsp"); + man.addCookieFromHeader("ID=abcd; Path=/TopDir", url); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("ID=abcd", s); + + url = new URL("http://a.b.c:8080/other.jsp"); + s=man.getCookieHeaderForURL(url); + assertNull(s); + + url = new URL("http://a.b.c:8080/TopDir/suub/another.jsp"); + s=man.getCookieHeaderForURL(url); + assertNotNull(s); + + url = new URL("http://a.b.c:8080/TopDir"); + s=man.getCookieHeaderForURL(url); + assertNotNull(s); + + url = new URL("http://a.b.d/"); + s=man.getCookieHeaderForURL(url); + assertNull(s); + } + + public void testCookieOrdering1() throws Exception { + URL url = new URL("http://order.now/sub1/moo.html"); + man.addCookieFromHeader("test1=moo1;path=/", url); + man.addCookieFromHeader("test2=moo2;path=/sub1", url); + man.addCookieFromHeader("test2=moo3;path=/", url); + assertEquals(3,man.getCookieCount()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + assertEquals("test2=moo2; test1=moo1; test2=moo3", s); + } + + public void testCookieOrdering2() throws Exception { + URL url = new URL("http://order.now/sub1/moo.html"); + man.addCookieFromHeader("test1=moo1;", url); + man.addCookieFromHeader("test2=moo2;path=/sub1", url); + man.addCookieFromHeader("test2=moo3;path=/", url); + assertEquals(3,man.getCookieCount()); + assertEquals("/sub1",man.get(0).getPath()); // Defaults to caller URL + assertEquals("/sub1",man.get(1).getPath()); + assertEquals("/",man.get(2).getPath()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + HC3CookieHandler hc3CookieHandler = (HC3CookieHandler) man.getCookieHandler(); + org.apache.commons.httpclient.Cookie[] c = + hc3CookieHandler.getCookiesForUrl(man.getCookies(), url, + CookieManager.ALLOW_VARIABLE_COOKIES); + assertEquals("/sub1",c[0].getPath()); + assertFalse(c[0].isPathAttributeSpecified()); + assertEquals("/sub1",c[1].getPath()); + assertTrue(c[1].isPathAttributeSpecified()); + assertEquals("/",c[2].getPath()); + assertEquals("test1=moo1; test2=moo2; test2=moo3", s); + } + + public void testCookiePolicy2109() throws Exception { + man.setCookiePolicy(CookiePolicy.RFC_2109); + man.testStarted(); // ensure policy is picked up + URL url = new URL("http://order.now/sub1/moo.html"); + man.addCookieFromHeader("test1=moo1;", url); + man.addCookieFromHeader("test2=moo2;path=/sub1", url); + man.addCookieFromHeader("test2=moo3;path=/", url); + assertEquals(3,man.getCookieCount()); + //assertEquals("/",man.get(0).getPath()); + assertEquals("/sub1",man.get(1).getPath()); + assertEquals("/",man.get(2).getPath()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + HC3CookieHandler hc3CookieHandler = (HC3CookieHandler) man.getCookieHandler(); + org.apache.commons.httpclient.Cookie[] c = + hc3CookieHandler.getCookiesForUrl(man.getCookies(), url, + CookieManager.ALLOW_VARIABLE_COOKIES); + assertEquals("/sub1",c[0].getPath()); + assertFalse(c[0].isPathAttributeSpecified()); + assertEquals("/sub1",c[1].getPath()); + assertTrue(c[1].isPathAttributeSpecified()); + assertEquals("/",c[2].getPath()); + assertTrue(c[2].isPathAttributeSpecified()); + assertEquals("$Version=0; test1=moo1; test2=moo2; $Path=/sub1; test2=moo3; $Path=/", s); + } + + public void testCookiePolicyNetscape() throws Exception { + man.setCookiePolicy(CookiePolicy.NETSCAPE); + man.testStarted(); // ensure policy is picked up + URL url = new URL("http://www.order.now/sub1/moo.html"); + man.addCookieFromHeader("test1=moo1;", url); + man.addCookieFromHeader("test2=moo2;path=/sub1", url); + man.addCookieFromHeader("test2=moo3;path=/", url); + assertEquals(3,man.getCookieCount()); + assertEquals("/sub1",man.get(0).getPath()); + assertEquals("/sub1",man.get(1).getPath()); + assertEquals("/",man.get(2).getPath()); + String s = man.getCookieHeaderForURL(url); + assertNotNull(s); + HC3CookieHandler hc3CookieHandler = (HC3CookieHandler) man.getCookieHandler(); + + org.apache.commons.httpclient.Cookie[] c = + hc3CookieHandler.getCookiesForUrl(man.getCookies(), url, + CookieManager.ALLOW_VARIABLE_COOKIES); + assertEquals("/sub1",c[0].getPath()); + assertFalse(c[0].isPathAttributeSpecified()); + assertEquals("/sub1",c[1].getPath()); + assertTrue(c[1].isPathAttributeSpecified()); + assertEquals("/",c[2].getPath()); + assertTrue(c[2].isPathAttributeSpecified()); + assertEquals("test1=moo1; test2=moo2; test2=moo3", s); + } + + public void testCookiePolicyIgnore() throws Exception { + man.setCookiePolicy(CookiePolicy.IGNORE_COOKIES); + man.testStarted(); // ensure policy is picked up + URL url = new URL("http://order.now/sub1/moo.html"); + man.addCookieFromHeader("test1=moo1;", url); + man.addCookieFromHeader("test2=moo2;path=/sub1", url); + man.addCookieFromHeader("test2=moo3;path=/", url); + assertEquals(0,man.getCookieCount());// Cookies are ignored + Cookie cc; + cc=new Cookie("test1","moo1",null,"/sub1",false,0,false,false); + man.add(cc); + cc=new Cookie("test2","moo2",null,"/sub1",false,0,true,false); + man.add(cc); + cc=new Cookie("test3","moo3",null,"/",false,0,false,false); + man.add(cc); + assertEquals(3,man.getCookieCount()); + assertEquals("/sub1",man.get(0).getPath()); + assertEquals("/sub1",man.get(1).getPath()); + assertEquals("/",man.get(2).getPath()); + String s = man.getCookieHeaderForURL(url); + assertNull(s); + HC3CookieHandler hc3CookieHandler = (HC3CookieHandler) man.getCookieHandler(); + org.apache.commons.httpclient.Cookie[] c = + hc3CookieHandler.getCookiesForUrl(man.getCookies(), url, + CookieManager.ALLOW_VARIABLE_COOKIES); + assertEquals(0,c.length); // Cookies again ignored + } + + public void testLoad() throws Exception{ + assertEquals(0,man.getCookieCount()); + man.addFile(findTestPath("testfiles/cookies.txt")); + assertEquals(3,man.getCookieCount()); + + int num = 0; + assertEquals("name",man.get(num).getName()); + assertEquals("value",man.get(num).getValue()); + assertEquals("path",man.get(num).getPath()); + assertEquals("domain",man.get(num).getDomain()); + assertTrue(man.get(num).getSecure()); + assertEquals(num,man.get(num).getExpires()); + + num++; + assertEquals("name2",man.get(num).getName()); + assertEquals("value2",man.get(num).getValue()); + assertEquals("/",man.get(num).getPath()); + assertEquals("",man.get(num).getDomain()); + assertFalse(man.get(num).getSecure()); + assertEquals(0,man.get(num).getExpires()); + + num++; + assertEquals("a",man.get(num).getName()); + assertEquals("b",man.get(num).getValue()); + assertEquals("d",man.get(num).getPath()); + assertEquals("c",man.get(num).getDomain()); + assertTrue(man.get(num).getSecure()); + assertEquals(0,man.get(num).getExpires()); // Show that maxlong now saved as 0 + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestHTTPMirrorThread.java b/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestHTTPMirrorThread.java new file mode 100644 index 0000000..f233b6a --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/control/TestHTTPMirrorThread.java @@ -0,0 +1,399 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.Socket; +/* +import org.apache.jmeter.engine.util.ValueReplacer; +import org.apache.jmeter.protocol.http.control.HttpMirrorControl; +import org.apache.jmeter.protocol.http.sampler.HTTPSampler2; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.EncoderCache; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +*/ +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import junit.extensions.TestSetup; + +/** + * Class for testing the HTTPMirrorThread, which is handling the + * incoming requests for the HTTPMirrorServer + */ +public class TestHTTPMirrorThread extends TestCase { + /** The encodings used for http headers and control information */ + private final static String ISO_8859_1 = "ISO-8859-1"; // $NON-NLS-1$ + private final static String UTF_8 = "UTF-8"; // $NON-NLS-1$ + + private static final byte[] CRLF = { 0x0d, 0x0a }; + private final static int HTTP_SERVER_PORT = 8181; + + public TestHTTPMirrorThread(String arg0) { + super(arg0); + } + + public static Test suite(){ + TestSetup setup = new TestSetup(new TestSuite(TestHTTPMirrorThread.class)){ + private HttpMirrorServer httpServer; + + @Override + protected void setUp() throws Exception { + httpServer = startHttpMirror(HTTP_SERVER_PORT); + } + + @Override + protected void tearDown() throws Exception { + // Shutdown the http server + httpServer.stopServer(); + httpServer = null; + } + }; + return setup; + } + + /** + * Utility method to handle starting the HttpMirrorServer for testing. + * Also used by TestHTTPSamplersAgainstHttpMirrorServer + */ + public static HttpMirrorServer startHttpMirror(int port) throws Exception { + HttpMirrorServer server = null; + server = new HttpMirrorServer(port); + server.start(); + Exception e = null; + for (int i=0; i < 10; i++) {// Wait up to 1 second + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + e = server.getException(); + if (e != null) {// Already failed + throw new Exception("Could not start mirror server on port: "+port+". "+e); + } + if (server.isAlive()) { + break; // succeeded + } + } + + if (!server.isAlive()){ + throw new Exception("Could not start mirror server on port: "+port); + } + return server; + } + + public void testGetRequest() throws Exception { + // Connect to the http server, and do a simple http get + Socket clientSocket = new Socket("localhost", HTTP_SERVER_PORT); + OutputStream outputStream = clientSocket.getOutputStream(); + InputStream inputStream = clientSocket.getInputStream(); + + // Write to the socket + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + // Headers + bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write("Host: localhost".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(CRLF); + bos.close(); + outputStream.write(bos.toByteArray()); + + // Read the response + ByteArrayOutputStream response = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length = 0; + while(( length = inputStream.read(buffer)) != -1) { + response.write(buffer, 0, length); + } + response.close(); + byte[] mirroredResponse = getMirroredResponse(response.toByteArray()); + // Check that the request and response matches + checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse); + // Close the connection + clientSocket.close(); + + // Connect to the http server, and do a simple http get, with + // a pause in the middle of transmitting the header + clientSocket = new Socket("localhost", HTTP_SERVER_PORT); + outputStream = clientSocket.getOutputStream(); + inputStream = clientSocket.getInputStream(); + + // Write to the socket + bos = new ByteArrayOutputStream(); + // Headers + bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1)); + bos.write(CRLF); + // Write the start of the headers, and then sleep, so that the mirror + // thread will have to block to wait for more data to appear + bos.close(); + byte[] firstChunk = bos.toByteArray(); + outputStream.write(firstChunk); + Thread.sleep(300); + // Write the rest of the headers + bos = new ByteArrayOutputStream(); + bos.write("Host: localhost".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(CRLF); + bos.close(); + byte[] secondChunk = bos.toByteArray(); + outputStream.write(secondChunk); + // Read the response + response = new ByteArrayOutputStream(); + buffer = new byte[1024]; + length = 0; + while((length = inputStream.read(buffer)) != -1) { + response.write(buffer, 0, length); + } + response.close(); + mirroredResponse = getMirroredResponse(response.toByteArray()); + // The content sent + bos = new ByteArrayOutputStream(); + bos.write(firstChunk); + bos.write(secondChunk); + bos.close(); + // Check that the request and response matches + checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse); + // Close the connection + clientSocket.close(); + } + + public void testPostRequest() throws Exception { + // Connect to the http server, and do a simple http post + Socket clientSocket = new Socket("localhost", HTTP_SERVER_PORT); + OutputStream outputStream = clientSocket.getOutputStream(); + InputStream inputStream = clientSocket.getInputStream(); + // Construct body + StringBuilder postBodyBuffer = new StringBuilder(); + for(int i = 0; i < 1000; i++) { + postBodyBuffer.append("abc"); + } + byte[] postBody = postBodyBuffer.toString().getBytes(ISO_8859_1); + + // Write to the socket + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + // Headers + bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write("Host: localhost".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-type: text/plain; charset=" + ISO_8859_1).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-length: " + postBody.length).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(CRLF); + bos.write(postBody); + bos.close(); + // Write the headers and body + outputStream.write(bos.toByteArray()); + // Read the response + ByteArrayOutputStream response = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length = 0; + while((length = inputStream.read(buffer)) != -1) { + response.write(buffer, 0, length); + } + response.close(); + byte[] mirroredResponse = getMirroredResponse(response.toByteArray()); + // Check that the request and response matches + checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse); + // Close the connection + clientSocket.close(); + + // Connect to the http server, and do a simple http post, with + // a pause after transmitting the headers + clientSocket = new Socket("localhost", HTTP_SERVER_PORT); + outputStream = clientSocket.getOutputStream(); + inputStream = clientSocket.getInputStream(); + + // Write to the socket + bos = new ByteArrayOutputStream(); + // Headers + bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write("Host: localhost".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-type: text/plain; charset=" + ISO_8859_1).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-length: " + postBody.length).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(CRLF); + bos.close(); + // Write the headers, and then sleep + bos.close(); + byte[] firstChunk = bos.toByteArray(); + outputStream.write(firstChunk); + Thread.sleep(300); + + // Write the body + byte[] secondChunk = postBody; + outputStream.write(secondChunk); + // Read the response + response = new ByteArrayOutputStream(); + buffer = new byte[1024]; + length = 0; + while((length = inputStream.read(buffer)) != -1) { + response.write(buffer, 0, length); + } + response.close(); + mirroredResponse = getMirroredResponse(response.toByteArray()); + // The content sent + bos = new ByteArrayOutputStream(); + bos.write(firstChunk); + bos.write(secondChunk); + bos.close(); + // Check that the request and response matches + checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse); + // Close the connection + clientSocket.close(); + + // Connect to the http server, and do a simple http post with utf-8 + // encoding of the body, which caused problems when reader/writer + // classes were used in the HttpMirrorThread + clientSocket = new Socket("localhost", HTTP_SERVER_PORT); + outputStream = clientSocket.getOutputStream(); + inputStream = clientSocket.getInputStream(); + // Construct body + postBodyBuffer = new StringBuilder(); + for(int i = 0; i < 1000; i++) { + postBodyBuffer.append("\u0364\u00c5\u2052"); + } + postBody = postBodyBuffer.toString().getBytes(UTF_8); + + // Write to the socket + bos = new ByteArrayOutputStream(); + // Headers + bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write("Host: localhost".getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-type: text/plain; charset=" + UTF_8).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(("Content-length: " + postBody.length).getBytes(ISO_8859_1)); + bos.write(CRLF); + bos.write(CRLF); + bos.close(); + // Write the headers, and then sleep + bos.close(); + firstChunk = bos.toByteArray(); + outputStream.write(firstChunk); + Thread.sleep(300); + + // Write the body + secondChunk = postBody; + outputStream.write(secondChunk); + // Read the response + response = new ByteArrayOutputStream(); + buffer = new byte[1024]; + length = 0; + while((length = inputStream.read(buffer)) != -1) { + response.write(buffer, 0, length); + } + response.close(); + mirroredResponse = getMirroredResponse(response.toByteArray()); + // The content sent + bos = new ByteArrayOutputStream(); + bos.write(firstChunk); + bos.write(secondChunk); + bos.close(); + // Check that the request and response matches + checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse); + // Close the connection + clientSocket.close(); + } +/* + public void testPostRequestChunked() throws Exception { + // TODO - implement testing of chunked post request + } +*/ + + /** + * Check that the the two byte arrays have identical content + * + * @param expected + * @param actual + * @throws UnsupportedEncodingException + */ + private void checkArraysHaveSameContent(byte[] expected, byte[] actual) throws UnsupportedEncodingException { + if(expected != null && actual != null) { + if(expected.length != actual.length) { + System.out.println(">>>>>>>>>>>>>>>>>>>> (expected) : length " + expected.length); + System.out.println(new String(expected,"UTF-8")); + System.out.println("==================== (actual) : length " + actual.length); + System.out.println(new String(actual,"UTF-8")); + System.out.println("<<<<<<<<<<<<<<<<<<<<"); + fail("arrays have different length, expected is " + expected.length + ", actual is " + actual.length); + } + else { + for(int i = 0; i < expected.length; i++) { + if(expected[i] != actual[i]) { + System.out.println(">>>>>>>>>>>>>>>>>>>> (expected) : length " + expected.length); + System.out.println(new String(expected,0,i+1, ISO_8859_1)); + System.out.println("==================== (actual) : length " + actual.length); + System.out.println(new String(actual,0,i+1, ISO_8859_1)); + System.out.println("<<<<<<<<<<<<<<<<<<<<"); +/* + // Useful to when debugging + for(int j = 0; j < expected.length; j++) { + System.out.print(expected[j] + " "); + } + System.out.println(); + for(int j = 0; j < actual.length; j++) { + System.out.print(actual[j] + " "); + } + System.out.println(); +*/ + fail("byte at position " + i + " is different, expected is " + expected[i] + ", actual is " + actual[i]); + } + } + } + } + else { + fail("expected or actual byte arrays were null"); + } + } + + private byte[] getMirroredResponse(byte[] allResponse) { + // The response includes the headers from the mirror server, + // we want to skip those, to only keep the content mirrored. + // Look for the first CRLFCRLF section + int startOfMirrorResponse = 0; + for(int i = 0; i < allResponse.length; i++) { + // TODO : This is a bit fragile + if(allResponse[i] == 0x0d && allResponse[i+1] == 0x0a && allResponse[i+2] == 0x0d && allResponse[i+3] == 0x0a) { + startOfMirrorResponse = i + 4; + break; + } + } + byte[] mirrorResponse = new byte[allResponse.length - startOfMirrorResponse]; + System.arraycopy(allResponse, startOfMirrorResponse, mirrorResponse, 0, mirrorResponse.length); + return mirrorResponse; + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/control/gui/TestHttpTestSampleGui.java b/ApacheJmeter/org/apache/jmeter/protocol/http/control/gui/TestHttpTestSampleGui.java new file mode 100644 index 0000000..e7e2f98 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/control/gui/TestHttpTestSampleGui.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control.gui; + +import junit.framework.TestCase; + +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; + +public class TestHttpTestSampleGui extends TestCase { + private HttpTestSampleGui gui; + + public TestHttpTestSampleGui(String name) { + super(name); + } + + @Override + public void setUp() { + gui = new HttpTestSampleGui(); + } + + public void testCloneSampler() throws Exception { + HTTPSamplerBase sampler = (HTTPSamplerBase) gui.createTestElement(); + sampler.addArgument("param", "value"); + HTTPSamplerBase clonedSampler = (HTTPSamplerBase) sampler.clone(); + clonedSampler.setRunningVersion(true); + sampler.getArguments().getArgument(0).setValue("new value"); + assertEquals("Sampler didn't clone correctly", "new value", sampler.getArguments().getArgument(0) + .getValue()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/modifier/TestAnchorModifier.java b/ApacheJmeter/org/apache/jmeter/protocol/http/modifier/TestAnchorModifier.java new file mode 100644 index 0000000..50573c2 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/modifier/TestAnchorModifier.java @@ -0,0 +1,342 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier; + +import java.io.FileInputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.io.TextFile; + +public class TestAnchorModifier extends JMeterTestCase { + private AnchorModifier parser = new AnchorModifier(); + public TestAnchorModifier(String name) { + super(name); + } + + private JMeterContext jmctx = null; + + @Override + public void setUp() { + jmctx = JMeterContextService.getContext(); + parser.setThreadContext(jmctx); + } + + public void testProcessingHTMLFile(String HTMLFileName) throws Exception { + HTTPSamplerBase config = (HTTPSamplerBase) SaveService.loadTree( + new FileInputStream(System.getProperty("user.dir") + "/testfiles/load_bug_list.jmx")).getArray()[0]; + config.setRunningVersion(true); + HTTPSampleResult result = new HTTPSampleResult(); + HTTPSamplerBase context = (HTTPSamplerBase) SaveService.loadTree( + new FileInputStream(System.getProperty("user.dir") + "/testfiles/Load_JMeter_Page.jmx")).getArray()[0]; + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + result.setResponseData(new TextFile(System.getProperty("user.dir") + HTMLFileName).getText(), null); + result.setSampleLabel(context.toString()); + result.setSamplerData(context.toString()); + result.setURL(new URL("http://issues.apache.org/fakepage.html")); + jmctx.setPreviousResult(result); + AnchorModifier modifier = new AnchorModifier(); + modifier.setThreadContext(jmctx); + modifier.process(); + assertEquals("http://issues.apache.org/bugzilla/buglist.cgi?" + + "bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED" + + "&email1=&emailtype1=substring&emailassigned_to1=1" + + "&email2=&emailtype2=substring&emailreporter2=1" + "&bugidtype=include&bug_id=&changedin=&votes=" + + "&chfieldfrom=&chfieldto=Now&chfieldvalue=" + + "&product=JMeter&short_desc=&short_desc_type=substring" + + "&long_desc=&long_desc_type=substring&bug_file_loc=" + "&bug_file_loc_type=substring&keywords=" + + "&keywords_type=anywords" + "&field0-0-0=noop&type0-0-0=noop&value0-0-0=" + + "&cmdtype=doit&order=Reuse+same+sort+as+last+time", config.toString()); + config.recoverRunningVersion(); + assertEquals("http://issues.apache.org/bugzilla/buglist.cgi?" + + "bug_status=.*&bug_status=.*&bug_status=.*&email1=" + + "&emailtype1=substring&emailassigned_to1=1&email2=" + "&emailtype2=substring&emailreporter2=1" + + "&bugidtype=include&bug_id=&changedin=&votes=" + "&chfieldfrom=&chfieldto=Now&chfieldvalue=" + + "&product=JMeter&short_desc=&short_desc_type=substring" + + "&long_desc=&long_desc_type=substring&bug_file_loc=" + "&bug_file_loc_type=substring&keywords=" + + "&keywords_type=anywords&field0-0-0=noop" + "&type0-0-0=noop&value0-0-0=&cmdtype=doit" + + "&order=Reuse+same+sort+as+last+time", config.toString()); + } + + public void testModifySampler() throws Exception { + testProcessingHTMLFile("/testfiles/jmeter_home_page.html"); + } + + public void testModifySamplerWithRelativeLink() throws Exception { + testProcessingHTMLFile("/testfiles/jmeter_home_page_with_relative_links.html"); + } + + public void testModifySamplerWithBaseHRef() throws Exception { + testProcessingHTMLFile("/testfiles/jmeter_home_page_with_base_href.html"); + } + + public void testSimpleParse() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*/index\\.html"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "<html><head><title>Test page" + + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setSamplerData(context.toString()); + result.setURL(context.getUrl()); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals("http://www.apache.org/subdir/index.html", config.getUrl().toString()); + } + + // Test https works too + public void testSimpleParse1() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*/index\\.html"); + config.setProtocol(HTTPSamplerBase.PROTOCOL_HTTPS); + config.setPort(HTTPSamplerBase.DEFAULT_HTTPS_PORT); + HTTPSamplerBase context = makeContext("https://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setSamplerData(context.toString()); + result.setURL(context.getUrl()); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals("https://www.apache.org/subdir/index.html", config.getUrl().toString()); + } + + public void testSimpleParse2() throws Exception { + HTTPSamplerBase config = makeUrlConfig("/index\\.html"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "Goto index page" + "hfdfjiudfjdfjkjfkdjf" + + "bold textlower" + ""; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + String newUrl = config.getUrl().toString(); + assertTrue("http://www.apache.org/index.html".equals(newUrl) + || "http://www.apache.org/subdir/lowerdir/index.html".equals(newUrl)); + } + + public void testSimpleParse3() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*index.*"); + config.getArguments().addArgument("param1", "value1"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "" + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + String newUrl = config.getUrl().toString(); + assertEquals("http://www.apache.org/home/index.html?param1=value1", newUrl); + } + + public void testSimpleParse4() throws Exception { + HTTPSamplerBase config = makeUrlConfig("/subdir/index\\..*"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + String newUrl = config.getUrl().toString(); + assertEquals("http://www.apache.org/subdir/index.html", newUrl); + } + + public void testSimpleParse5() throws Exception { + HTTPSamplerBase config = makeUrlConfig("/subdir/index\\.h.*"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/one/previous.html"); + String responseText = "Test page" + + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + String newUrl = config.getUrl().toString(); + assertEquals("http://www.apache.org/subdir/index.html", newUrl); + } + + public void testFailSimpleParse1() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*index.*?param2=.+1"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "" + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + String newUrl = config.getUrl().toString(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals(newUrl, config.getUrl().toString()); + } + + public void testFailSimpleParse3() throws Exception { + HTTPSamplerBase config = makeUrlConfig("/home/index.html"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "" + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + String newUrl = config.getUrl().toString(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals(newUrl + "?param1=value1", config.getUrl().toString()); + } + + public void testFailSimpleParse2() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*login\\.html"); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "" + "Goto index page"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setPreviousResult(result); + parser.process(); + String newUrl = config.getUrl().toString(); + assertTrue(!"http://www.apache.org/home/index.html?param1=value1".equals(newUrl)); + assertEquals(config.getUrl().toString(), newUrl); + } + + public void testSimpleFormParse() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*index.html"); + config.addArgument("test", "g.*"); + config.setMethod(HTTPSamplerBase.POST); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "
" + "Goto index page
"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals("http://www.apache.org/subdir/index.html", config.getUrl().toString()); + assertEquals("test=goto", config.getQueryString()); + } + + public void testBadCharParse() throws Exception { + HTTPSamplerBase config = makeUrlConfig(".*index.html"); + config.addArgument("te$st", "g.*"); + config.setMethod(HTTPSamplerBase.POST); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "
" + "Goto index page
"; + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals("http://www.apache.org/subdir/index.html", config.getUrl().toString()); + assertEquals("te%24st=goto", config.getQueryString()); + } + + public void testSpecialCharParse() throws Exception { + String specialChars = "-_.!~*'()%25";// These are some of the special characters + String htmlEncodedFixture = URLEncoder.encode(specialChars, "UTF-8"); + + HTTPSamplerBase config = makeUrlConfig(".*index.html"); + config.addArgument("test", ".*"); + config.setMethod(HTTPSamplerBase.POST); + HTTPSamplerBase context = makeContext("http://www.apache.org/subdir/previous.html"); + String responseText = "Test page" + + "
" + "Goto index page
"; + + HTTPSampleResult result = new HTTPSampleResult(); + result.setResponseData(responseText, null); + result.setSampleLabel(context.toString()); + result.setURL(context.getUrl()); + jmctx.setCurrentSampler(context); + jmctx.setCurrentSampler(config); + jmctx.setPreviousResult(result); + parser.process(); + assertEquals("http://www.apache.org/subdir/index.html", config.getUrl().toString()); + assertEquals("test=" + htmlEncodedFixture, config.getQueryString()); + } + + + private HTTPSamplerBase makeContext(String url) throws MalformedURLException { + URL u = new URL(url); + HTTPSamplerBase context = new HTTPNullSampler(); + context.setDomain(u.getHost()); + context.setPath(u.getPath()); + context.setPort(u.getPort()); + context.setProtocol(u.getProtocol()); + context.parseArguments(u.getQuery()); + return context; + } + + private HTTPSamplerBase makeUrlConfig(String path) { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setDomain("www.apache.org"); + config.setMethod(HTTPSamplerBase.GET); + config.setPath(path); + config.setPort(HTTPSamplerBase.DEFAULT_HTTP_PORT); + config.setProtocol(HTTPSamplerBase.PROTOCOL_HTTP); + return config; + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/modifier/TestURLRewritingModifier.java b/ApacheJmeter/org/apache/jmeter/protocol/http/modifier/TestURLRewritingModifier.java new file mode 100644 index 0000000..e31f69a --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/modifier/TestURLRewritingModifier.java @@ -0,0 +1,267 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.samplers.NullSampler; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; + +public class TestURLRewritingModifier extends JMeterTestCase { + private SampleResult response = null; + + private JMeterContext context = null; + + private URLRewritingModifier mod = null; + + public TestURLRewritingModifier(String name) { + super(name); + } + + @Override + public void setUp() { + context = JMeterContextService.getContext(); + mod = new URLRewritingModifier(); + mod.setThreadContext(context); + } + + public void testNonHTTPSampler() throws Exception { + Sampler sampler = new NullSampler(); + response = new SampleResult(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + } + + public void testGrabSessionId() throws Exception { + String html = "location: http://server.com/index.html" + "?session_id=jfdkjdkf%20jddkfdfjkdjfdf%22;"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session_id"); + HTTPSamplerBase sampler = createSampler(); + sampler.addArgument("session_id", "adfasdfdsafasdfasd"); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("jfdkjdkf jddkfdfjkdjfdf\"", ((Argument) args.getArguments().get(0).getObjectValue()) + .getValue()); + assertEquals("http://server.com/index.html?" + "session_id=jfdkjdkf+jddkfdfjkdjfdf%22", sampler.toString()); + } + + public void testGrabSessionId2() throws Exception { + String html = ""; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session_id"); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("jfdkjdkfjddkfdfjkdjfdf", ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + + private HTTPSamplerBase createSampler() { + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setDomain("server.com"); + sampler.setPath("index.html"); + sampler.setMethod(HTTPSamplerBase.GET); + sampler.setProtocol("http"); + return sampler; + } + + public void testGrabSessionId3() throws Exception { + String html = "href='index.html?session_id=jfdkjdkfjddkfdfjkdjfdf'"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session_id"); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("jfdkjdkfjddkfdfjkdjfdf", ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + + public void testGrabSessionIdFromXMLNonPatExtension() throws Exception { // Bug 50286 + String html = "/some/path;jsessionid=123456789"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("jsessionid"); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("123456789", ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + + public void testGrabSessionIdFromXMLPatExtension() throws Exception { // Bug 50286 + String html = "/some/path;jsessionid=123456789"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("jsessionid"); + mod.setPathExtension(true); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + assertEquals("index.html;jsessionid=123456789",sampler.getPath()); + } + + public void testGrabSessionIdEndedInTab() throws Exception { + String html = "href='index.html?session_id=jfdkjdkfjddkfdfjkdjfdf\t"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session_id"); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("jfdkjdkfjddkfdfjkdjfdf", ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + + public void testGrabSessionId4() throws Exception { + String html = "href='index.html;%24sid%24KQNq3AAADQZoEQAxlkX8uQV5bjqVBPbT'"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("%24sid%24"); // $sid$ + mod.setPathExtension(true); + mod.setPathExtensionNoEquals(true); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + // Arguments args = sampler.getArguments(); + assertEquals("index.html;%24sid%24KQNq3AAADQZoEQAxlkX8uQV5bjqVBPbT", sampler.getPath()); + } + + public void testGrabSessionId5() throws Exception { + String html = "location: http://server.com/index.html" + "?session[33]=jfdkjdkf%20jddkfdfjkdjfdf%22;"; + response = new SampleResult(); + response.setResponseData(html, null); + mod.setArgumentName("session[33]"); + HTTPSamplerBase sampler = createSampler(); + sampler.addArgument("session[33]", "adfasdfdsafasdfasd"); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + mod.process(); + Arguments args = sampler.getArguments(); + assertEquals("jfdkjdkf jddkfdfjkdjfdf\"", ((Argument) args.getArguments().get(0).getObjectValue()) + .getValue()); + assertEquals("http://server.com/index.html?session%5B33%5D=jfdkjdkf+jddkfdfjkdjfdf%22", sampler.toString()); + } + + + public void testGrabSessionIdFromForm() throws Exception { + String[] html = new String[] { + "", + "", + "", + "", + "", + "", + "", + }; + for (int i = 0; i < html.length; i++) { + response = new SampleResult(); + response.setResponseData(html[i], null); + URLRewritingModifier newMod = new URLRewritingModifier(); + newMod.setThreadContext(context); + newMod.setArgumentName("sid"); + newMod.setPathExtension(false); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + newMod.process(); + Arguments args = sampler.getArguments(); + assertEquals("For case i=" + i, "myId", + ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + } + + public void testGrabSessionIdURLinJSON() throws Exception { + String html = + "", + "", // No entry; check it is still present + }; + URLRewritingModifier newMod = new URLRewritingModifier(); + newMod.setShouldCache(true); + newMod.setThreadContext(context); + newMod.setArgumentName("sid"); + newMod.setPathExtension(false); + for (int i = 0; i < html.length; i++) { + response = new SampleResult(); + response.setResponseData(html[i], null); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + newMod.process(); + Arguments args = sampler.getArguments(); + assertEquals("For case i=" + i, "myId", + ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + } + public void testNoCache() throws Exception { + String[] html = new String[] { + "", "myId", + "", "", + }; + URLRewritingModifier newMod = new URLRewritingModifier(); + newMod.setThreadContext(context); + newMod.setArgumentName("sid"); + newMod.setPathExtension(false); + newMod.setShouldCache(false); + for (int i = 0; i < html.length/2; i++) { + response = new SampleResult(); + response.setResponseData(html[i*2], null); + HTTPSamplerBase sampler = createSampler(); + context.setCurrentSampler(sampler); + context.setPreviousResult(response); + newMod.process(); + Arguments args = sampler.getArguments(); + assertEquals("For case i=" + i, html[i*2+1], + ((Argument) args.getArguments().get(0).getObjectValue()).getValue()); + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/parser/TestHTMLParser.java b/ApacheJmeter/org/apache/jmeter/protocol/http/parser/TestHTMLParser.java new file mode 100644 index 0000000..32422bc --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/parser/TestHTMLParser.java @@ -0,0 +1,318 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.parser; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.TreeSet; +import java.util.Vector; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import junit.framework.TestSuite; + +public class TestHTMLParser extends JMeterTestCase { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public TestHTMLParser(String arg0) { + super(arg0); + } + private String parserName; + + private int testNumber = 0; + + public TestHTMLParser(String name, int test) { + super(name); + testNumber = test; + } + + public TestHTMLParser(String name, String parser, int test) { + super(name); + testNumber = test; + parserName = parser; + } + + private static class StaticTestClass // Can't instantiate + { + private StaticTestClass() { + } + } + + private class TestClass // Can't instantiate + { + private TestClass() { + } + } + + private static class TestData { + private String fileName; + + private String baseURL; + + private String expectedSet; + + private String expectedList; + + private TestData(String f, String b, String s, String l) { + fileName = f; + baseURL = b; + expectedSet = s; + expectedList = l; + } + +// private TestData(String f, String b, String s) { +// this(f, b, s, null); +// } + } + + // List of parsers to test. Should probably be derived automatically + private static final String[] PARSERS = { + "org.apache.jmeter.protocol.http.parser.HtmlParserHTMLParser", + "org.apache.jmeter.protocol.http.parser.JTidyHTMLParser", + "org.apache.jmeter.protocol.http.parser.RegexpHTMLParser" + }; + + private static final TestData[] TESTS = new TestData[] { + new TestData("testfiles/HTMLParserTestCase.html", + "http://localhost/mydir/myfile.html", + "testfiles/HTMLParserTestCase.set", + "testfiles/HTMLParserTestCase.all"), + new TestData("testfiles/HTMLParserTestCaseWithBaseHRef.html", + "http://localhost/mydir/myfile.html", + "testfiles/HTMLParserTestCaseBase.set", + "testfiles/HTMLParserTestCaseBase.all"), + new TestData("testfiles/HTMLParserTestCaseWithBaseHRef2.html", + "http://localhost/mydir/myfile.html", + "testfiles/HTMLParserTestCaseBase.set", + "testfiles/HTMLParserTestCaseBase.all"), + new TestData("testfiles/HTMLParserTestCaseWithMissingBaseHRef.html", + "http://localhost/mydir/images/myfile.html", + "testfiles/HTMLParserTestCaseBase.set", + "testfiles/HTMLParserTestCaseBase.all"), + new TestData("testfiles/HTMLParserTestCase2.html", + "http:", "", ""), // Dummy as the file has no entries + new TestData("testfiles/HTMLParserTestCase3.html", + "http:", "", ""), // Dummy as the file has no entries + new TestData("testfiles/HTMLParserTestCaseWithComments.html", + "http://localhost/mydir/myfile.html", + "testfiles/HTMLParserTestCaseBase.set", + "testfiles/HTMLParserTestCaseBase.all"), + new TestData("testfiles/HTMLScript.html", + "http://localhost/", + "testfiles/HTMLScript.set", + "testfiles/HTMLScript.all"), + new TestData("testfiles/HTMLParserTestFrames.html", + "http://localhost/", + "testfiles/HTMLParserTestFrames.all", + "testfiles/HTMLParserTestFrames.all"), + // Relative filenames + new TestData("testfiles/HTMLParserTestFile_2.html", + "file:HTMLParserTestFile_2.html", + "testfiles/HTMLParserTestFile_2.all", + "testfiles/HTMLParserTestFile_2.all"), + }; + + public static junit.framework.Test suite() { + TestSuite suite = new TestSuite("TestHTMLParser"); + suite.addTest(new TestHTMLParser("testDefaultParser")); + suite.addTest(new TestHTMLParser("testParserDefault")); + suite.addTest(new TestHTMLParser("testParserMissing")); + suite.addTest(new TestHTMLParser("testNotParser")); + suite.addTest(new TestHTMLParser("testNotCreatable")); + suite.addTest(new TestHTMLParser("testNotCreatableStatic")); + for (int i = 0; i < PARSERS.length; i++) { + TestSuite ps = new TestSuite(PARSERS[i]);// Identify subtests + ps.addTest(new TestHTMLParser("testParserProperty", PARSERS[i], 0)); + for (int j = 0; j < TESTS.length; j++) { + TestSuite ts = new TestSuite(TESTS[j].fileName); + ts.addTest(new TestHTMLParser("testParserSet", PARSERS[i], j)); + ts.addTest(new TestHTMLParser("testParserList", PARSERS[i], j)); + ps.addTest(ts); + } + suite.addTest(ps); + } + return suite; + } + + // Test if can instantiate parser using property name + public void testParserProperty() throws Exception { + Properties p = JMeterUtils.getJMeterProperties(); + if (p == null) { + p = JMeterUtils.getProperties("jmeter.properties"); + } + p.setProperty(HTMLParser.PARSER_CLASSNAME, parserName); + HTMLParser.getParser(); + } + + public void testDefaultParser() throws Exception { + HTMLParser.getParser(); + } + + public void testParserDefault() throws Exception { + HTMLParser.getParser(HTMLParser.DEFAULT_PARSER); + } + + public void testParserMissing() throws Exception { + try { + HTMLParser.getParser("no.such.parser"); + fail("Should not have been able to create the parser"); + } catch (HTMLParseError e) { + if (e.getCause() instanceof ClassNotFoundException) { + // This is OK + } else { + throw e; + } + } + } + + public void testNotParser() throws Exception { + try { + HTMLParser.getParser("java.lang.String"); + fail("Should not have been able to create the parser"); + } catch (HTMLParseError e) { + if (e.getCause() instanceof ClassCastException) { + return; + } + throw e; + } + } + + public void testNotCreatable() throws Exception { + try { + HTMLParser.getParser(TestClass.class.getName()); + fail("Should not have been able to create the parser"); + } catch (HTMLParseError e) { + if (e.getCause() instanceof InstantiationException) { + return; + } + throw e; + } + } + + public void testNotCreatableStatic() throws Exception { + try { + HTMLParser.getParser(StaticTestClass.class.getName()); + fail("Should not have been able to create the parser"); + } catch (HTMLParseError e) { + if (e.getCause() instanceof ClassCastException) { + return; + } + if (e.getCause() instanceof IllegalAccessException) { + return; + } + throw e; + } + } + + public void testParserSet() throws Exception { + HTMLParser p = HTMLParser.getParser(parserName); + filetest(p, TESTS[testNumber].fileName, TESTS[testNumber].baseURL, TESTS[testNumber].expectedSet, null, + false); + } + + public void testParserList() throws Exception { + HTMLParser p = HTMLParser.getParser(parserName); + filetest(p, TESTS[testNumber].fileName, TESTS[testNumber].baseURL, TESTS[testNumber].expectedList, + new Vector(), true); + } + + private static void filetest(HTMLParser p, String file, String url, String resultFile, Collection c, + boolean orderMatters) // Does the order matter? + throws Exception { + String parserName = p.getClass().getName().substring("org.apache.jmeter.protocol.http.parser.".length()); + String fname = file.substring(file.indexOf("/")+1); + log.debug("file " + file); + File f = findTestFile(file); + byte[] buffer = new byte[(int) f.length()]; + InputStream is = null; + try { + is = new FileInputStream(f); + int len = is.read(buffer); + assertEquals(len, buffer.length); + } finally { + IOUtils.closeQuietly(is); + } + Iterator result; + if (c == null) { + result = p.getEmbeddedResourceURLs(buffer, new URL(url), System.getProperty("file.encoding")); + } else { + result = p.getEmbeddedResourceURLs(buffer, new URL(url), c,System.getProperty("file.encoding")); + } + /* + * TODO: Exact ordering is only required for some tests; change the + * comparison to do a set compare where necessary. + */ + Iterator expected; + if (orderMatters) { + expected = getFile(resultFile).iterator(); + } else { + // Convert both to Sets + expected = new TreeSet(getFile(resultFile)).iterator(); + TreeSet temp = new TreeSet(new Comparator() { + public int compare(Object o1, Object o2) { + return (o1.toString().compareTo(o2.toString())); + } + }); + while (result.hasNext()) { + temp.add(result.next()); + } + result = temp.iterator(); + } + + while (expected.hasNext()) { + Object next = expected.next(); + assertTrue(fname+"::"+parserName + "::Expecting another result " + next, result.hasNext()); + try { + assertEquals(fname+"::"+parserName + "(next)", next, result.next().toString()); + } catch (ClassCastException e) { + fail(fname+"::"+parserName + "::Expected URL, but got " + e.toString()); + } + } + assertFalse(fname+"::"+parserName + "::Should have reached the end of the results", result.hasNext()); + } + + // Get expected results as a List + private static List getFile(String file) throws Exception { + ArrayList al = new ArrayList(); + if (file != null && file.length() > 0) { + BufferedReader br = new BufferedReader(new FileReader(findTestFile(file))); + String line = br.readLine(); + while (line != null) { + al.add(line); + line = br.readLine(); + } + br.close(); + } + return al; + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/parser/TestHtmlParsingUtils.java b/ApacheJmeter/org/apache/jmeter/protocol/http/parser/TestHtmlParsingUtils.java new file mode 100644 index 0000000..211fec1 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/parser/TestHtmlParsingUtils.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.parser; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; + +// TODO: need more tests +public final class TestHtmlParsingUtils extends JMeterTestCase { + + public TestHtmlParsingUtils(String name) { + super(name); + } + + @Override + protected void setUp() { + } + + public void testGetParser() throws Exception { + HtmlParsingUtils.getParser(); + } + + public void testGetDom() throws Exception { + HtmlParsingUtils.getDOM(""); + HtmlParsingUtils.getDOM(""); + } + + public void testIsArgumentMatched() throws Exception { + Argument arg = new Argument(); + Argument argp = new Argument(); + assertTrue(HtmlParsingUtils.isArgumentMatched(arg, argp)); + + arg = new Argument("test", "abcd"); + argp = new Argument("test", "a.*d"); + assertTrue(HtmlParsingUtils.isArgumentMatched(arg, argp)); + + arg = new Argument("test", "abcd"); + argp = new Argument("test", "a.*e"); + assertFalse(HtmlParsingUtils.isArgumentMatched(arg, argp)); + } + + public void testIsAnchorMatched() throws Exception { + HTTPSamplerBase target=new HTTPNullSampler(); + HTTPSamplerBase pattern=new HTTPNullSampler(); + + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + target.setProtocol("http:"); + assertFalse(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + pattern.setProtocol(".*"); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + target.setDomain("a.b.c"); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + pattern.setDomain(".*"); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + target.setPath("/abc"); + assertFalse(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + pattern.setPath(".*"); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + target.addArgument("param2", "value2", "="); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + pattern.addArgument("param1", ".*", "="); + assertFalse(HtmlParsingUtils.isAnchorMatched(target, pattern)); + + target.addArgument("param1", "value1", "="); + assertTrue(HtmlParsingUtils.isAnchorMatched(target, pattern)); + } + + public void testisEqualOrMatches() throws Exception { + assertTrue(HtmlParsingUtils.isEqualOrMatches("http:","http:")); + assertFalse(HtmlParsingUtils.isEqualOrMatches("http:","htTp:")); + assertTrue(HtmlParsingUtils.isEqualOrMatches("http:","ht+p:")); + assertFalse(HtmlParsingUtils.isEqualOrMatches("ht+p:","http:")); + } + + public void testisEqualOrMatchesCaseBlind() throws Exception { + assertTrue(HtmlParsingUtils.isEqualOrMatchesCaseBlind("http:","http:")); + assertTrue(HtmlParsingUtils.isEqualOrMatchesCaseBlind("http:","htTp:")); + assertTrue(HtmlParsingUtils.isEqualOrMatches("http:","ht+p:")); + assertFalse(HtmlParsingUtils.isEqualOrMatches("ht+p:","http:")); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/proxy/TestHttpRequestHdr.java b/ApacheJmeter/org/apache/jmeter/protocol/http/proxy/TestHttpRequestHdr.java new file mode 100644 index 0000000..9e171a4 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/proxy/TestHttpRequestHdr.java @@ -0,0 +1,627 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; + +public class TestHttpRequestHdr extends JMeterTestCase { + public TestHttpRequestHdr(String name) { + super(name); + } + + public void testRepeatedArguments() throws Exception { + String url = "http://localhost/matrix.html"; + // A HTTP GET request + String contentEncoding = "UTF-8"; + String testGetRequest = + "GET " + url + + "?update=yes&d=1&d=2&d=&d=&d=&d=&d=&d=1&d=2&d=1&d=&d= " + + "HTTP/1.0\r\n\r\n"; + HTTPSamplerBase s = getSamplerForRequest(url, testGetRequest, contentEncoding); + assertEquals(HTTPSamplerBase.GET, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(13, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "update", "yes", "yes", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(2), "d", "2", "2", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(3), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(4), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(5), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(6), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(7), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(8), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(9), "d", "2", "2", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(10), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(11), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(12), "d", "", "", contentEncoding, false); + + // A HTTP POST request + contentEncoding = "UTF-8"; + String postBody = "update=yes&d=1&d=2&d=&d=&d=&d=&d=&d=1&d=2&d=1&d=&d="; + String testPostRequest = "POST " + url + " HTTP/1.0\n" + + "Content-type: " + + HTTPSamplerBase.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertFalse(s.getDoMultipartPost()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + arguments = s.getArguments(); + assertEquals(13, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "update", "yes", "yes", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(2), "d", "2", "2", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(3), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(4), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(5), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(6), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(7), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(8), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(9), "d", "2", "2", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(10), "d", "1", "1", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(11), "d", "", "", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(12), "d", "", "", contentEncoding, false); + + // A HTTP POST request, with content-type text/plain + contentEncoding = "UTF-8"; + postBody = "update=yes&d=1&d=2&d=&d=&d=&d=&d=&d=1&d=2&d=1&d=\uc385&d="; + testPostRequest = "POST " + url + " HTTP/1.1\r\n" + + "Content-type: text/plain\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertFalse(s.getDoMultipartPost()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + // We should have one argument, with the value equal to the post body + arguments = s.getArguments(); + assertEquals(1, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "", postBody, postBody, contentEncoding, false); + + // A HTTP POST request, with content-type text/plain; charset=UTF-8 + // The encoding should be picked up from the header we send with the request + contentEncoding = "UTF-8"; + postBody = "update=yes&d=1&d=2&d=&d=&d=&d=&d=&d=1&d=2&d=1&d=\uc385&d="; + testPostRequest = "POST " + url + " HTTP/1.1\r\n" + + "Content-type: text/plain; charset=" + contentEncoding + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + // Use null for url to simulate that HttpRequestHdr do not + // know the encoding for the page. Specify contentEncoding, so the + // request is "sent" using that encoding + s = getSamplerForRequest(null, testPostRequest, contentEncoding); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertFalse(s.getDoMultipartPost()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + // We should have one argument, with the value equal to the post body + arguments = s.getArguments(); + assertEquals(1, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "", postBody, postBody, contentEncoding, false); + } + + public void testEncodedArguments() throws Exception { + String url = "http://localhost/matrix.html"; + testEncodedArguments(url); + } + + + public void testEncodedArgumentsIPv6() throws Exception { + String url = "http://[::1]:8080/matrix.html"; + testEncodedArguments(url); + } + + public void testEncodedArguments(String url) throws Exception { + // A HTTP GET request, with encoding not known + String contentEncoding = ""; + String queryString = "abc%3FSPACE=a+b&space=a%20b&query=What%3F"; + String testGetRequest = "GET " + url + + "?" + queryString + + " HTTP/1.1\r\n\r\n"; + // Use null for url and contentEncoding, to simulate that HttpRequestHdr do not + // know the encoding for the page + HTTPSamplerBase s = getSamplerForRequest(null, testGetRequest, null); + assertEquals(HTTPSamplerBase.GET, s.getMethod()); + assertEquals(queryString, s.getQueryString()); + assertEquals(contentEncoding, s.getContentEncoding()); + + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(3, arguments.getArgumentCount()); + // When the encoding is not known, the argument will get the encoded value, and the "encode?" set to false + checkArgument((HTTPArgument)arguments.getArgument(0), "abc%3FSPACE", "a+b", "a+b", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "space", "a%20b", "a%20b", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(2), "query", "What%3F", "What%3F", contentEncoding, false); + + // A HTTP GET request, with UTF-8 encoding + contentEncoding = "UTF-8"; + queryString = "abc%3FSPACE=a+b&space=a%20b&query=What%3F"; + testGetRequest = "GET " + url + + "?" + queryString + + " HTTP/1.1\r\n\r\n"; + s = getSamplerForRequest(url, testGetRequest, contentEncoding); + assertEquals(HTTPSamplerBase.GET, s.getMethod()); + String expectedQueryString = "abc%3FSPACE=a+b&space=a+b&query=What%3F"; + assertEquals(expectedQueryString, s.getQueryString()); + assertEquals(contentEncoding, s.getContentEncoding()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(3, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "abc?SPACE", "a b", "a+b", contentEncoding, true); + checkArgument((HTTPArgument)arguments.getArgument(1), "space", "a b", "a+b", contentEncoding, true); + checkArgument((HTTPArgument)arguments.getArgument(2), "query", "What?", "What%3F", contentEncoding, true); + + // A HTTP POST request, with unknown encoding + contentEncoding = ""; + String postBody = "abc%3FSPACE=a+b&space=a%20b&query=What%3F"; + String testPostRequest = "POST " + url + " HTTP/1.1\r\n" + + "Content-type: " + + HTTPSamplerBase.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + // Use null for url and contentEncoding, to simulate that HttpRequestHdr do not + // know the encoding for the page + s = getSamplerForRequest(null, testPostRequest, null); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertEquals(queryString, s.getQueryString()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertFalse(s.getDoMultipartPost()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(3, arguments.getArgumentCount()); + // When the encoding is not known, the argument will get the encoded value, and the "encode?" set to false + checkArgument((HTTPArgument)arguments.getArgument(0), "abc%3FSPACE", "a+b", "a+b", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "space", "a%20b", "a%20b", contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(2), "query", "What%3F", "What%3F", contentEncoding, false); + + // A HTTP POST request, with UTF-8 encoding + contentEncoding = "UTF-8"; + postBody = "abc?SPACE=a+b&space=a%20b&query=What?"; + testPostRequest = "POST " + url + " HTTP/1.1\n" + + "Content-type: " + + HTTPSamplerBase.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + expectedQueryString = "abc%3FSPACE=a+b&space=a+b&query=What%3F"; + assertEquals(expectedQueryString, s.getQueryString()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertFalse(s.getDoMultipartPost()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(3, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "abc?SPACE", "a b", "a+b", contentEncoding, true); + checkArgument((HTTPArgument)arguments.getArgument(1), "space", "a b", "a+b", contentEncoding, true); + checkArgument((HTTPArgument)arguments.getArgument(2), "query", "What?", "What%3F", contentEncoding, true); + } + + public void testGetRequestEncodings() throws Exception { + testGetRequestEncodings("http://localhost/matrix.html"); + } + + public void testGetRequestEncodingsIPv6() throws Exception { + testGetRequestEncodings("http://[::1]:8080/matrix.html"); + } + + public void testGetRequestEncodings(String url) throws Exception { + // A HTTP GET request, with encoding not known + String contentEncoding = ""; + String param1Value = "yes"; + String param2Value = "0+5 -\u00c5\uc385%C3%85"; + String param2ValueEncoded = URLEncoder.encode(param2Value,"UTF-8"); + String testGetRequest = + "GET " + url + + "?param1=" + param1Value + "¶m2=" + param2ValueEncoded + " " + + "HTTP/1.1\r\n\r\n"; + // Use null for url and contentEncoding, to simulate that HttpRequestHdr do not + // know the encoding for the page + HTTPSamplerBase s = getSamplerForRequest(null, testGetRequest, null); + assertEquals(HTTPSamplerBase.GET, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + // When the encoding is not known, the argument will get the encoded value, and the "encode?" set to false + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2ValueEncoded, param2ValueEncoded, contentEncoding, false); + + // A HTTP GET request, with UTF-8 encoding + contentEncoding = "UTF-8"; + param1Value = "yes"; + param2Value = "0+5 -\u007c\u2aa1\u266a\u0153\u20a1\u0115\u0364\u00c5\u2052\uc385%C3%85"; + param2ValueEncoded = URLEncoder.encode(param2Value, contentEncoding); + testGetRequest = + "GET " + url + + "?param1=" + param1Value + "¶m2=" + param2ValueEncoded + " " + + "HTTP/1.1\r\n\r\n"; + s = getSamplerForRequest(url, testGetRequest, contentEncoding); + assertEquals(HTTPSamplerBase.GET, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2Value, param2ValueEncoded, contentEncoding, true); + + // A HTTP GET request, with ISO-8859-1 encoding + contentEncoding = "ISO-8859-1"; + param1Value = "yes"; + param2Value = "0+5 -\u00c5%C3%85"; + param2ValueEncoded = URLEncoder.encode(param2Value, contentEncoding); + testGetRequest = + "GET " + url + + "?param1=" + param1Value + "¶m2=" + param2ValueEncoded + " " + + "HTTP/1.1\r\n\r\n"; + s = getSamplerForRequest(url, testGetRequest, contentEncoding); + assertEquals(HTTPSamplerBase.GET, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2Value, param2ValueEncoded, contentEncoding, true); + } + + public void testPostRequestEncodings() throws Exception { + String url = "http://localhost/matrix.html"; + // A HTTP POST request, with encoding not known + String contentEncoding = ""; + String param1Value = "yes"; + String param2Value = "0+5 -\u00c5%C3%85"; + String param2ValueEncoded = URLEncoder.encode(param2Value,"UTF-8"); + String postBody = "param1=" + param1Value + "¶m2=" + param2ValueEncoded + "\r\n"; + String testPostRequest = + "POST " + url + " HTTP/1.1\r\n" + + "Content-type: " + + HTTPSamplerBase.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + + // Use null for url and contentEncoding, to simulate that HttpRequestHdr do not + // know the encoding for the page + HTTPSamplerBase s = getSamplerForRequest(null, testPostRequest, null); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + // When the encoding is not known, the argument will get the encoded value, and the "encode?" set to false + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2ValueEncoded, param2ValueEncoded, contentEncoding, false); + + // A HTTP POST request, with UTF-8 encoding + contentEncoding = "UTF-8"; + param1Value = "yes"; + param2Value = "0+5 -\u007c\u2aa1\u266a\u0153\u20a1\u0115\u0364\u00c5\u2052\uc385%C3%85"; + param2ValueEncoded = URLEncoder.encode(param2Value, contentEncoding); + postBody = "param1=" + param1Value + "¶m2=" + param2ValueEncoded + "\r\n"; + testPostRequest = + "POST " + url + " HTTP/1.1\r\n" + + "Content-type: " + + HTTPSamplerBase.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2Value, param2ValueEncoded, contentEncoding, true); + + // A HTTP POST request, with ISO-8859-1 encoding + contentEncoding = "ISO-8859-1"; + param1Value = "yes"; + param2Value = "0+5 -\u00c5%C3%85"; + param2ValueEncoded = URLEncoder.encode(param2Value, contentEncoding); + postBody = "param1=" + param1Value + "¶m2=" + param2ValueEncoded + "\r\n"; + testPostRequest = + "POST " + url + " HTTP/1.1\r\n" + + "Content-type: " + + HTTPSamplerBase.APPLICATION_X_WWW_FORM_URLENCODED + "\r\n" + + "Content-length: " + getBodyLength(postBody, contentEncoding) + "\r\n" + + "\r\n" + + postBody; + + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "param1", param1Value, param1Value, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "param2", param2Value, param2ValueEncoded, contentEncoding, true); + } + + public void testPostMultipartFormData() throws Exception { + String url = "http://localhost/matrix.html"; + // A HTTP POST request, multipart/form-data, simple values, + String contentEncoding = "UTF-8"; + String boundary = "xf8SqlDNvmn6mFYwrioJaeUR2_Z4cLRXOSmB"; + String endOfLine = "\r\n"; + String titleValue = "mytitle"; + String descriptionValue = "mydescription"; + String postBody = createMultipartFormBody(titleValue, descriptionValue, contentEncoding, true, boundary, endOfLine); + String testPostRequest = createMultipartFormRequest(url, postBody, contentEncoding, boundary, endOfLine); + + HTTPSamplerBase s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertTrue(s.getDoMultipartPost()); + + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "title", titleValue, titleValue, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "description", descriptionValue, descriptionValue, contentEncoding, false); + + // A HTTP POST request, multipart/form-data, simple values, + // with \r\n as end of line, which is according to spec, + // and with more headers in each multipart + endOfLine = "\r\n"; + titleValue = "mytitle"; + descriptionValue = "mydescription"; + postBody = createMultipartFormBody(titleValue, descriptionValue, contentEncoding, true, boundary, endOfLine); + testPostRequest = createMultipartFormRequest(url, postBody, contentEncoding, boundary, endOfLine); + + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertTrue(s.getDoMultipartPost()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "title", titleValue, titleValue, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "description", descriptionValue, descriptionValue, contentEncoding, false); + + // A HTTP POST request, multipart/form-data, simple values, + // with \n as end of line, which should also be handled, + // and with more headers in each multipart + endOfLine = "\n"; + titleValue = "mytitle"; + descriptionValue = "mydescription"; + postBody = createMultipartFormBody(titleValue, descriptionValue, contentEncoding, true, boundary, endOfLine); + testPostRequest = createMultipartFormRequest(url, postBody, contentEncoding, boundary, endOfLine); + + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertTrue(s.getDoMultipartPost()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "title", titleValue, titleValue, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "description", descriptionValue, descriptionValue, contentEncoding, false); + + // A HTTP POST request, multipart/form-data, with value that will change + // if they are url encoded + // Values are similar to __VIEWSTATE parameter that .net uses + endOfLine = "\r\n"; + titleValue = "/wEPDwULLTE2MzM2OTA0NTYPZBYCAgMPZ/rA+8DZ2dnZ2dnZ2d/GNDar6OshPwdJc="; + descriptionValue = "mydescription"; + postBody = createMultipartFormBody(titleValue, descriptionValue, contentEncoding, true, boundary, endOfLine); + testPostRequest = createMultipartFormRequest(url, postBody, contentEncoding, boundary, endOfLine); + + s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertTrue(s.getDoMultipartPost()); + + // Check arguments + arguments = s.getArguments(); + assertEquals(2, arguments.getArgumentCount()); + checkArgument((HTTPArgument)arguments.getArgument(0), "title", titleValue, titleValue, contentEncoding, false); + checkArgument((HTTPArgument)arguments.getArgument(1), "description", descriptionValue, descriptionValue, contentEncoding, false); + } + + public void testParse1() throws Exception {// no space after : + HttpRequestHdr req = new HttpRequestHdr(); + ByteArrayInputStream bis = null; + bis = new ByteArrayInputStream("GET xxx HTTP/1.0\r\nname:value \r\n".getBytes("ISO-8859-1")); + req.parse(bis); + bis.close(); + HeaderManager mgr = req.getHeaderManager(); + Header header; + mgr.getHeaders(); + header = mgr.getHeader(0); + assertEquals("name",header.getName()); + assertEquals("value",header.getValue()); + } + + + public void testParse2() throws Exception {// spaces after : + HttpRequestHdr req = new HttpRequestHdr(); + ByteArrayInputStream bis = null; + bis = new ByteArrayInputStream("GET xxx HTTP/1.0\r\nname: value \r\n".getBytes("ISO-8859-1")); + req.parse(bis); + bis.close(); + HeaderManager mgr = req.getHeaderManager(); + Header header; + mgr.getHeaders(); + header = mgr.getHeader(0); + assertEquals("name",header.getName()); + assertEquals("value",header.getValue()); + } + + public void testPostMultipartFileUpload() throws Exception { + String url = "http://localhost/matrix.html"; + // A HTTP POST request, multipart/form-data, simple values, + String contentEncoding = "UTF-8"; + String boundary = "xf8SqlDNvmn6mFYwrioJaeUR2_Z4cLRXOSmB"; + String endOfLine = "\r\n"; + String fileFieldValue = "test_file"; + String fileName = "somefilename.txt"; + String mimeType = "text/plain"; + String fileContent = "somedummycontent\n\ndfgdfg\r\nfgdgdg\nContent-type:dfsfsfds"; + String postBody = createMultipartFileUploadBody(fileFieldValue, fileName, mimeType, fileContent, boundary, endOfLine); + String testPostRequest = createMultipartFormRequest(url, postBody, contentEncoding, boundary, endOfLine); + + HTTPSamplerBase s = getSamplerForRequest(url, testPostRequest, contentEncoding); + assertEquals(HTTPSamplerBase.POST, s.getMethod()); + assertEquals(contentEncoding, s.getContentEncoding()); + assertEquals("", s.getQueryString()); + assertTrue(s.getDoMultipartPost()); + + // Check arguments + Arguments arguments = s.getArguments(); + assertEquals(0, arguments.getArgumentCount()); + HTTPFileArg hfa = s.getHTTPFiles()[0]; // Assume there's at least one file + assertEquals(fileFieldValue, hfa.getParamName()); + assertEquals(fileName, hfa.getPath()); + assertEquals(mimeType, hfa.getMimeType()); + } + + private String createMultipartFormBody(String titleValue, String descriptionValue, String contentEncoding, boolean includeExtraHeaders, String boundary, String endOfLine) { + // Title multipart + String postBody = "--" + boundary + endOfLine + + "Content-Disposition: form-data; name=\"title\"" + endOfLine; + if(includeExtraHeaders) { + postBody += "Content-Type: text/plain; charset=" + contentEncoding + endOfLine + + "Content-Transfer-Encoding: 8bit" + endOfLine; + } + postBody += endOfLine + + titleValue + endOfLine + + "--" + boundary + endOfLine; + // Description multipart + postBody += "Content-Disposition: form-data; name=\"description\"" + endOfLine; + if(includeExtraHeaders) { + postBody += "Content-Type: text/plain; charset=" + contentEncoding + endOfLine + + "Content-Transfer-Encoding: 8bit" + endOfLine; + } + postBody += endOfLine + + descriptionValue + endOfLine + + "--" + boundary + "--" + endOfLine; + + return postBody; + } + + private String createMultipartFileUploadBody(String fileField, String fileName, String fileMimeType, String fileContent, String boundary, String endOfLine) { + // File upload multipart + String postBody = "--" + boundary + endOfLine + + "Content-Disposition: form-data; name=\"" + fileField + "\" filename=\"" + fileName + "\"" + endOfLine + + "Content-Type: " + fileMimeType + endOfLine + + "Content-Transfer-Encoding: binary" + endOfLine + + endOfLine + + fileContent + endOfLine + + "--" + boundary + "--" + endOfLine; + return postBody; + } + + private String createMultipartFormRequest(String url, String postBody, String contentEncoding, String boundary, String endOfLine) + throws IOException { + String postRequest = "POST " + url + " HTTP/1.1" + endOfLine + + "Content-type: " + + HTTPSamplerBase.MULTIPART_FORM_DATA + + "; boundary=" + boundary + endOfLine + + "Content-length: " + getBodyLength(postBody, contentEncoding) + endOfLine + + endOfLine + + postBody; + return postRequest; + } + + private HTTPSamplerBase getSamplerForRequest(String url, String request, String contentEncoding) + throws Exception { + HttpRequestHdr req = new HttpRequestHdr(); + ByteArrayInputStream bis = null; + if(contentEncoding != null) { + bis = new ByteArrayInputStream(request.getBytes(contentEncoding)); + + } + else { + // Most browsers use ISO-8859-1 as default encoding, even if spec says UTF-8 + bis = new ByteArrayInputStream(request.getBytes("ISO-8859-1")); + } + req.parse(bis); + bis.close(); + Map pageEncodings = Collections.synchronizedMap(new HashMap()); + Map formEncodings = Collections.synchronizedMap(new HashMap()); + if(url != null && contentEncoding != null) { + pageEncodings.put(url, contentEncoding); + } + SamplerCreatorFactory creatorFactory = new SamplerCreatorFactory(); + SamplerCreator creator = creatorFactory.getSamplerCreator(req, pageEncodings, formEncodings); + HTTPSamplerBase sampler = creator.createSampler(req, pageEncodings, formEncodings); + creator.populateSampler(sampler, req, pageEncodings, formEncodings); + return sampler; + } + + private void checkArgument( + HTTPArgument arg, + String expectedName, + String expectedValue, + String expectedEncodedValue, + String contentEncoding, + boolean expectedEncoded) throws IOException { + assertEquals(expectedName, arg.getName()); +// System.out.println("expect " + URLEncoder.encode(expectedValue, "UTF-8")); +// System.out.println("actual " + URLEncoder.encode(arg.getValue(), "UTF-8")); + assertEquals(expectedValue, arg.getValue()); + if(contentEncoding != null && contentEncoding.length() > 0) { + assertEquals(expectedEncodedValue, arg.getEncodedValue(contentEncoding)); + } + else { + // Most browsers use ISO-8859-1 as default encoding, even if spec says UTF-8 + assertEquals(expectedEncodedValue, arg.getEncodedValue("ISO-8859-1")); + } + assertEquals(expectedEncoded, arg.isAlwaysEncoded()); + } + + private int getBodyLength(String postBody, String contentEncoding) throws IOException { + if(contentEncoding != null && contentEncoding.length() > 0) { + return postBody.getBytes(contentEncoding).length; + } + else { + // Most browsers use ISO-8859-1 as default encoding, even if spec says UTF-8 + return postBody.getBytes().length; // TODO - charset? + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/proxy/TestProxyControl.java b/ApacheJmeter/org/apache/jmeter/protocol/http/proxy/TestProxyControl.java new file mode 100644 index 0000000..f35de3b --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/proxy/TestProxyControl.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy; + +import junit.framework.TestCase; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; + +public class TestProxyControl extends TestCase { + private HTTPSamplerBase sampler; + + private ProxyControl control; + + public TestProxyControl(String name) { + super(name); + } + + @Override + public void setUp() { + control = new ProxyControl(); + control.addIncludedPattern(".*\\.jsp"); + control.addExcludedPattern(".*apache.org.*"); + sampler = new HTTPNullSampler(); + } + + public void testFilter1() throws Exception { + sampler.setDomain("jakarta.org"); + sampler.setPath("index.jsp"); + assertTrue("Should find jakarta.org/index.jsp", control.filterUrl(sampler)); + } + + public void testFilter2() throws Exception { + sampler.setPath("index.jsp"); + sampler.setDomain("www.apache.org"); + assertFalse("Should not match www.apache.org", control.filterUrl(sampler)); + } + + public void testFilter3() throws Exception { + sampler.setPath("header.gif"); + sampler.setDomain("jakarta.org"); + assertFalse("Should not match header.gif", control.filterUrl(sampler)); + } + + public void testContentTypeNoFilters() throws Exception { + SampleResult result = new SampleResult(); + // No filters + control.setContentTypeInclude(null); + control.setContentTypeExclude(null); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("image/png"); + assertTrue("Should allow image/png", control.filterContentType(result)); + + // Empty filters + control.setContentTypeInclude(""); + control.setContentTypeExclude(""); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("image/png"); + assertTrue("Should allow image/png", control.filterContentType(result)); + + // Non empty filters + control.setContentTypeInclude(" "); + control.setContentTypeExclude(" "); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertFalse("Should not allow text/html", control.filterContentType(result)); + result.setContentType("image/png"); + assertFalse("Should not allow image/png", control.filterContentType(result)); + } + + public void testContentTypeInclude() throws Exception { + SampleResult result = new SampleResult(); + control.setContentTypeInclude("text/html|text/ascii"); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("text/css"); + assertFalse("Should not allow text/css", control.filterContentType(result)); + } + + public void testContentTypeExclude() throws Exception { + SampleResult result = new SampleResult(); + control.setContentTypeExclude("text/css"); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("text/css"); + assertFalse("Should not allow text/css", control.filterContentType(result)); + } + + public void testContentTypeIncludeAndExclude() throws Exception { + SampleResult result = new SampleResult(); + // Simple inclusion and exclusion filter + control.setContentTypeInclude("text/html|text/ascii"); + control.setContentTypeExclude("text/css"); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("text/css"); + assertFalse("Should not allow text/css", control.filterContentType(result)); + result.setContentType("image/png"); + assertFalse("Should not allow image/png", control.filterContentType(result)); + + // Allow all but images + control.setContentTypeInclude(null); + control.setContentTypeExclude("image/.*"); + + result.setContentType(null); + assertTrue("Should allow if no content-type present", control.filterContentType(result)); + result.setContentType("text/html; charset=utf-8"); + assertTrue("Should allow text/html", control.filterContentType(result)); + result.setContentType("text/css"); + assertTrue("Should allow text/css", control.filterContentType(result)); + result.setContentType("image/png"); + assertFalse("Should not allow image/png", control.filterContentType(result)); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/HTTPNullSampler.java b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/HTTPNullSampler.java new file mode 100644 index 0000000..07e850f --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/HTTPNullSampler.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.net.URL; + +/** + * Dummy HTTPSampler class for use by classes that need an HTTPSampler, but that + * don't need an actual sampler, e.g. for Parsing testing. + */ +public final class HTTPNullSampler extends HTTPSamplerBase { + + private static final long serialVersionUID = 240L; + + /** + * Returns a sample Result with the request fields filled in. + * + * {@inheritDoc} + */ + @Override + protected HTTPSampleResult sample(URL u, String method, boolean areFollowingRedirec, int depth) { + HTTPSampleResult res = new HTTPSampleResult(); + res.sampleStart(); + res.setURL(u); + res.sampleEnd(); + return res; +// throw new UnsupportedOperationException("For test purposes only"); + } + +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/HTTPSampler3.java b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/HTTPSampler3.java new file mode 100644 index 0000000..956621c --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/HTTPSampler3.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jmeter.protocol.http.sampler; + +import org.apache.jmeter.samplers.Interruptible; + +/** + * A sampler which understands all the parts necessary to read statistics about + * HTTP requests, including cookies and authentication. + * This sampler uses the Apache HttpClient implementation + */ +class HTTPSampler3 extends HTTPSamplerBase implements Interruptible { + + private static final long serialVersionUID = 241L; + + private final transient HTTPHC4Impl hc; + + public HTTPSampler3(){ + hc = new HTTPHC4Impl(this); + } + + public boolean interrupt() { + return hc.interrupt(); + } + + @Override + protected HTTPSampleResult sample(java.net.URL u, String method, + boolean areFollowingRedirect, int depth) { + return hc.sample(u, method, areFollowingRedirect, depth); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/NullURLConnection.java b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/NullURLConnection.java new file mode 100644 index 0000000..ee30739 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/NullURLConnection.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.net.URL; +import java.net.URLConnection; +import java.net.MalformedURLException; +import java.util.Properties; + +/** + * Dummy URLConnection class for use by classes that need an + * URLConnection for junit tests. + * + */ +public final class NullURLConnection extends URLConnection { + + private Properties data = new Properties(); + + public NullURLConnection() throws MalformedURLException { + this(new URL("http://localhost")); + } + + public NullURLConnection(URL url) { + super(url); + } + + @Override + public void connect() { + } + + @Override + public void setRequestProperty(String name, String value) { + data.put(name, value); + } + + @Override + public String getRequestProperty(String name) { + return (String) data.get(name); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PackageTest.java b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PackageTest.java new file mode 100644 index 0000000..05f263a --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PackageTest.java @@ -0,0 +1,58 @@ +/* + * 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. + * + */ + +/* + * Created on Jul 16, 2003 + */ +package org.apache.jmeter.protocol.http.sampler; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui; +import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui; +import org.apache.jmeter.protocol.http.util.HTTPArgument; + +public class PackageTest extends TestCase { + public PackageTest(String arg0) { + super(arg0); + } + + public void testConfiguring() throws Exception { + HTTPSamplerBase sampler = (HTTPSamplerBase) new HttpTestSampleGui().createTestElement(); + configure(sampler); + } + + private void configure(HTTPSamplerBase sampler) throws Exception { + sampler.addArgument("arg1", "val1"); + ConfigTestElement config = (ConfigTestElement) new HttpDefaultsGui().createTestElement(); + ((Arguments) config.getProperty(HTTPSamplerBase.ARGUMENTS).getObjectValue()).addArgument(new HTTPArgument( + "config1", "configValue")); + config.setRunningVersion(true); + sampler.setRunningVersion(true); + sampler.setRunningVersion(true); + sampler.addTestElement(config); + assertEquals("config1=configValue", sampler.getArguments().getArgument(1).toString()); + sampler.recoverRunningVersion(); + config.recoverRunningVersion(); + assertEquals(1, sampler.getArguments().getArgumentCount()); + sampler.addTestElement(config); + assertEquals("config1=configValue", sampler.getArguments().getArgument(1).toString()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PostWriterTest.java b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PostWriterTest.java new file mode 100644 index 0000000..6c146d1 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PostWriterTest.java @@ -0,0 +1,932 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class PostWriterTest extends TestCase { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String UTF_8 = "UTF-8"; + private final static String HTTP_ENCODING = "ISO-8859-1"; + private final static byte[] CRLF = { 0x0d, 0x0A }; + private static byte[] TEST_FILE_CONTENT; + + private StubURLConnection connection; + private HTTPSampler sampler; + private File temporaryFile; + + private PostWriter postWriter; + @Override + protected void setUp() throws Exception { + establishConnection(); + sampler = new HTTPSampler();// This must be the original (Java) HTTP sampler + postWriter=new PostWriter(); + + // Create the test file content + TEST_FILE_CONTENT = "foo content &?=01234+56789-\u007c\u2aa1\u266a\u0153\u20a1\u0115\u0364\u00c5\u2052".getBytes(UTF_8); + + // create a temporary file to make sure we always have a file to give to the PostWriter + // Whereever we are or Whatever the current path is. + temporaryFile = File.createTempFile("foo", "txt"); + OutputStream output = null; + try { + output = new FileOutputStream(temporaryFile); + output.write(TEST_FILE_CONTENT); + output.flush(); + } finally { + JOrphanUtils.closeQuietly(output); + } + } + + @Override + protected void tearDown() throws Exception { + // delete temporay file + if(!temporaryFile.delete()) { + fail("Could not delete file:"+temporaryFile.getAbsolutePath()); + } + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending a request which contains both formdata and file content + */ + public void testSendPostData() throws IOException { + sampler.setMethod(HTTPSamplerBase.POST); + setupFilepart(sampler); + String titleValue = "mytitle"; + String descriptionValue = "mydescription"; + setupFormData(sampler, titleValue, descriptionValue); + + // Test sending data with default encoding + String contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + byte[] expectedFormBody = createExpectedOutput(PostWriter.BOUNDARY, null, titleValue, descriptionValue, TEST_FILE_CONTENT); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedOutput(PostWriter.BOUNDARY, contentEncoding, titleValue, descriptionValue, TEST_FILE_CONTENT); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as UTF-8 + establishConnection(); + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedOutput(PostWriter.BOUNDARY, contentEncoding, titleValue, descriptionValue, TEST_FILE_CONTENT); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending UTF-8 data with ISO-8859-1 content encoding + establishConnection(); + contentEncoding = UTF_8; + sampler.setContentEncoding("ISO-8859-1"); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedOutput(PostWriter.BOUNDARY, contentEncoding, titleValue, descriptionValue, TEST_FILE_CONTENT); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveDifferentContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending a HTTPSampler with form parameters, and only + * the filename of a file. + */ + public void testSendPostData_NoFilename() throws IOException { + setupNoFilename(sampler); + String titleValue = "mytitle"; + String descriptionValue = "mydescription"; + setupFormData(sampler, titleValue, descriptionValue); + + // Test sending data with default encoding + String contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + byte[] expectedUrl = "title=mytitle&description=mydescription".getBytes(); // TODO - charset? + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + expectedUrl = "title=mytitle&description=mydescription".getBytes(UTF_8); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + expectedUrl = "title=mytitle&description=mydescription".getBytes(contentEncoding); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + expectedUrl = "title=mytitle&description=mydescription".getBytes(UTF_8); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + connection.disconnect(); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending file content as the only content of the post body + */ + public void testSendPostData_FileAsBody() throws IOException { + setupFilepart(sampler, "", temporaryFile, ""); + + // Check using default encoding + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentLength(connection, TEST_FILE_CONTENT.length); + checkArraysHaveSameContent(TEST_FILE_CONTENT, connection.getOutputStreamContent()); + connection.disconnect(); + + // Check using a different encoding + + String otherEncoding; + final String fileEncoding = System.getProperty( "file.encoding");// $NON-NLS-1$ + log.info("file.encoding: "+fileEncoding); + if (UTF_8.equalsIgnoreCase(fileEncoding) || "UTF8".equalsIgnoreCase(fileEncoding)){// $NON-NLS-1$ + otherEncoding="ISO-8859-1"; // $NON-NLS-1$ + } else { + otherEncoding=UTF_8; + } + log.info("Using other encoding: "+otherEncoding); + establishConnection(); + sampler.setContentEncoding(otherEncoding); + // File content is sent as binary, so the content encoding should not change the file data + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentLength(connection, TEST_FILE_CONTENT.length); + checkArraysHaveSameContent(TEST_FILE_CONTENT, connection.getOutputStreamContent()); + // Check that other encoding is not the current encoding + checkArraysHaveDifferentContent(new String(TEST_FILE_CONTENT) // TODO - charset? + .getBytes(otherEncoding), connection.getOutputStreamContent()); + + // If we have both file as body, and form data, then only form data will be sent + setupFormData(sampler); + establishConnection(); + sampler.setContentEncoding(""); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + byte[] expectedUrl = "title=mytitle&description=mydescription".getBytes(); // TODO - charset? + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending only a file multipart. + */ + public void testSendFileData_Multipart() throws IOException { + sampler.setMethod(HTTPSamplerBase.POST); + String fileField = "upload"; + String mimeType = "text/plain"; + File file = temporaryFile; + byte[] fileContent = TEST_FILE_CONTENT; + setupFilepart(sampler, fileField, file, mimeType); + + // Test sending data with default encoding + String contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + byte[] expectedFormBody = createExpectedFilepartOutput(PostWriter.BOUNDARY, fileField, file, mimeType, fileContent, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFilepartOutput(PostWriter.BOUNDARY, fileField, file, mimeType, fileContent, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as UTF-8 + establishConnection(); + fileField = "some_file_field"; + mimeType = "image/png"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFilepart(sampler, fileField, file, mimeType); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFilepartOutput(PostWriter.BOUNDARY, fileField, file, mimeType, fileContent, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending only a formdata, as a multipart/form-data request. + */ + public void testSendFormData_Multipart() throws IOException { + sampler.setMethod(HTTPSamplerBase.POST); + String titleField = "title"; + String titleValue = "mytitle"; + String descriptionField = "description"; + String descriptionValue = "mydescription"; + setupFormData(sampler, titleValue, descriptionValue); + // Tell sampler to do multipart, even if we have no files to upload + sampler.setDoMultipartPost(true); + + // Test sending data with default encoding + String contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + byte[] expectedFormBody = createExpectedFormdataOutput(PostWriter.BOUNDARY, null, titleField, titleValue, descriptionField, descriptionValue, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFormdataOutput(PostWriter.BOUNDARY, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as ISO-8859-1, with values that need to be urlencoded + establishConnection(); + titleValue = "mytitle+123 456&yes"; + descriptionValue = "mydescription and some spaces"; + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFormdataOutput(PostWriter.BOUNDARY, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as UTF-8 + establishConnection(); + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFormdataOutput(PostWriter.BOUNDARY, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as UTF-8, with values that would have been urlencoded + // if it was not sent as multipart + establishConnection(); + titleValue = "mytitle\u0153+\u20a1 \u0115&yes\u00c5"; + descriptionValue = "mydescription \u0153 \u20a1 \u0115 \u00c5"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + expectedFormBody = createExpectedFormdataOutput(PostWriter.BOUNDARY, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, true); + checkContentLength(connection, expectedFormBody.length); + checkArraysHaveSameContent(expectedFormBody, connection.getOutputStreamContent()); + connection.disconnect(); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.sendPostData(URLConnection, HTTPSampler)' + * This method test sending only a formdata, as urlencoded data + */ + public void testSendFormData_Urlencoded() throws IOException { + String titleValue = "mytitle"; + String descriptionValue = "mydescription"; + setupFormData(sampler, titleValue, descriptionValue); + + // Test sending data with default encoding + String contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + byte[] expectedUrl = ("title=" + titleValue + "&description=" + descriptionValue).getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), "ISO-8859-1"), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), "ISO-8859-1")); + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + expectedUrl = new StringBuilder("title=").append(titleValue).append("&description=") + .append(descriptionValue).toString().getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + connection.disconnect(); + + // Test sending data as ISO-8859-1, with values that need to be urlencoded + establishConnection(); + titleValue = "mytitle+123 456&yes"; + descriptionValue = "mydescription and some spaces"; + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + String expectedString = "title=" + URLEncoder.encode(titleValue, contentEncoding) + "&description=" + URLEncoder.encode(descriptionValue, contentEncoding); + expectedUrl = expectedString.getBytes(contentEncoding); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + String unencodedString = "title=" + titleValue + "&description=" + descriptionValue; + byte[] unexpectedUrl = unencodedString.getBytes(UTF_8); + checkArraysHaveDifferentContent(unexpectedUrl, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending data as UTF-8 + establishConnection(); + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + expectedString = "title=" + URLEncoder.encode(titleValue, contentEncoding) + "&description=" + URLEncoder.encode(descriptionValue, contentEncoding); + expectedUrl = expectedString.getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + connection.disconnect(); + + // Test sending data as UTF-8, with values that needs to be urlencoded + establishConnection(); + titleValue = "mytitle\u0153+\u20a1 \u0115&yes\u00c5"; + descriptionValue = "mydescription \u0153 \u20a1 \u0115 \u00c5"; + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + setupFormData(sampler, titleValue, descriptionValue); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + expectedString = "title=" + URLEncoder.encode(titleValue, UTF_8) + "&description=" + URLEncoder.encode(descriptionValue, UTF_8); + expectedUrl = expectedString.getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + unencodedString = "title=" + titleValue + "&description=" + descriptionValue; + unexpectedUrl = unencodedString.getBytes("US-ASCII"); + checkArraysHaveDifferentContent(unexpectedUrl, connection.getOutputStreamContent()); + connection.disconnect(); + + // Test sending parameters which are urlencoded beforehand + // The values must be URL encoded with UTF-8 encoding, because that + // is what the HTTPArgument assumes + // %C3%85 in UTF-8 is the same as %C5 in ISO-8859-1, which is the same as Å + titleValue = "mytitle%20and%20space%2Ftest%C3%85"; + descriptionValue = "mydescription+and+plus+as+space%2Ftest%C3%85"; + setupFormData(sampler, true, titleValue, descriptionValue); + + // Test sending data with default encoding + establishConnection(); + contentEncoding = ""; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + StringBuilder sb = new StringBuilder(); + expectedUrl = (sb.append("title=").append(titleValue.replaceAll("%20", "+").replaceAll("%C3%85", "%C5")) + .append("&description=").append(descriptionValue.replaceAll("%C3%85", "%C5"))).toString().getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), "ISO-8859-1"), // HTTPSampler uses ISO-8859-1 as default encoding + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), "ISO-8859-1")); // HTTPSampler uses ISO-8859-1 as default encoding + connection.disconnect(); + + // Test sending data as ISO-8859-1 + establishConnection(); + contentEncoding = "ISO-8859-1"; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + sb = new StringBuilder(); + expectedUrl = (sb.append("title=").append(titleValue.replaceAll("%20", "+").replaceAll("%C3%85", "%C5")) + .append("&description=").append(descriptionValue.replaceAll("%C3%85", "%C5"))).toString().getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + connection.disconnect(); + + // Test sending data as UTF-8 + establishConnection(); + contentEncoding = UTF_8; + sampler.setContentEncoding(contentEncoding); + postWriter.setHeaders(connection, sampler); + postWriter.sendPostData(connection, sampler); + + checkContentTypeUrlEncoded(connection); + sb = new StringBuilder(); + expectedUrl = (sb.append("title=").append(titleValue.replaceAll("%20", "+")).append("&description=").append(descriptionValue)).toString().getBytes("US-ASCII"); + checkContentLength(connection, expectedUrl.length); + checkArraysHaveSameContent(expectedUrl, connection.getOutputStreamContent()); + assertEquals( + URLDecoder.decode(new String(expectedUrl, "US-ASCII"), contentEncoding), + URLDecoder.decode(new String(connection.getOutputStreamContent(), "US-ASCII"), contentEncoding)); + connection.disconnect(); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.setHeaders(URLConnection, HTTPSampler)' + */ + public void testSetHeaders() throws IOException { + sampler.setMethod(HTTPSamplerBase.POST); + setupFilepart(sampler); + setupFormData(sampler); + + postWriter.setHeaders(connection, sampler); + checkContentTypeMultipart(connection, PostWriter.BOUNDARY); + } + + /* + * Test method for 'org.apache.jmeter.protocol.http.sampler.postWriter.setHeaders(URLConnection, HTTPSampler)' + */ + public void testSetHeaders_NoFilename() throws IOException { + setupNoFilename(sampler); + setupFormData(sampler); + + postWriter.setHeaders(connection, sampler); + checkContentTypeUrlEncoded(connection); + checkContentLength(connection, "title=mytitle&description=mydescription".length()); + } + + /** + * setup commons parts of HTTPSampler with a no filename. + * + * @param httpSampler + * @throws IOException + */ + private void setupNoFilename(HTTPSampler httpSampler) { + setupFilepart(sampler, "upload", null, "application/octet-stream"); + } + + /** + * Setup the filepart with default values + * + * @param httpSampler + */ + private void setupFilepart(HTTPSampler httpSampler) { + setupFilepart(sampler, "upload", temporaryFile, "text/plain"); + } + + /** + * Setup the filepart with specified values + * + * @param httpSampler + */ + private void setupFilepart(HTTPSampler httpSampler, String fileField, File file, String mimeType) { + HTTPFileArg[] hfa = {new HTTPFileArg(file == null ? "" : file.getAbsolutePath(), fileField, mimeType)}; + httpSampler.setHTTPFiles(hfa); + } + + /** + * Setup the form data with default values + * + * @param httpSampler + */ + private void setupFormData(HTTPSampler httpSampler) { + setupFormData(httpSampler, "mytitle", "mydescription"); + } + + /** + * Setup the form data with specified values + * + * @param httpSampler + */ + private void setupFormData(HTTPSampler httpSampler, String titleValue, String descriptionValue) { + setupFormData(sampler, false, titleValue, descriptionValue); + } + + /** + * Setup the form data with specified values + * + * @param httpSampler + */ + private void setupFormData(HTTPSampler httpSampler, boolean isEncoded, String titleValue, String descriptionValue) { + Arguments args = new Arguments(); + HTTPArgument argument1 = new HTTPArgument("title", titleValue, isEncoded); + HTTPArgument argument2 = new HTTPArgument("description", descriptionValue, isEncoded); + args.addArgument(argument1); + args.addArgument(argument2); + httpSampler.setArguments(args); + } + + private void establishConnection() throws MalformedURLException { + connection = new StubURLConnection("http://fake_url/test"); + } + + /** + * Create the expected output post body for form data and file multiparts + * with default values for field names + */ + private byte[] createExpectedOutput( + String boundaryString, + String contentEncoding, + String titleValue, + String descriptionValue, + byte[] fileContent) throws IOException { + return createExpectedOutput(boundaryString, contentEncoding, "title", titleValue, "description", descriptionValue, "upload", fileContent); + } + + /** + * Create the expected output post body for form data and file multiparts + * with specified values + */ + private byte[] createExpectedOutput( + String boundaryString, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + String fileField, + byte[] fileContent) throws IOException { + // Create the multiparts + byte[] formdataMultipart = createExpectedFormdataOutput(boundaryString, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, false); + byte[] fileMultipart = createExpectedFilepartOutput(boundaryString, fileField, temporaryFile, "text/plain", fileContent, false, true); + + // Join the two multiparts + ByteArrayOutputStream output = new ByteArrayOutputStream(); + output.write(formdataMultipart); + output.write(fileMultipart); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + /** + * Create the expected output multipart/form-data, with only form data, + * and no file multipart + * + * @param lastMultipart true if this is the last multipart in the request + */ + private byte[] createExpectedFormdataOutput( + String boundaryString, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + boolean firstMultipart, + boolean lastMultipart) throws IOException { + final byte[] DASH_DASH = "--".getBytes(HTTP_ENCODING); + // All form parameter always have text/plain as mime type + final String mimeType="text/plain";//TODO make this a parameter? + + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + if(firstMultipart) { + output.write(DASH_DASH); + output.write(boundaryString.getBytes(HTTP_ENCODING)); + output.write(CRLF); + } + output.write("Content-Disposition: form-data; name=\"".getBytes(HTTP_ENCODING)); + output.write(titleField.getBytes(HTTP_ENCODING)); + output.write("\"".getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write("Content-Type: ".getBytes(HTTP_ENCODING)); + output.write(mimeType.getBytes(HTTP_ENCODING)); + output.write("; charset=".getBytes(HTTP_ENCODING)); + output.write((contentEncoding==null ? PostWriter.ENCODING : contentEncoding).getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write("Content-Transfer-Encoding: 8bit".getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write(CRLF); + if(contentEncoding != null) { + output.write(titleValue.getBytes(contentEncoding)); + } + else { + output.write(titleValue.getBytes()); // TODO - charset? + } + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write("Content-Disposition: form-data; name=\"".getBytes(HTTP_ENCODING)); + output.write(descriptionField.getBytes(HTTP_ENCODING)); + output.write("\"".getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write("Content-Type: ".getBytes(HTTP_ENCODING)); + output.write(mimeType.getBytes(HTTP_ENCODING)); + output.write("; charset=".getBytes(HTTP_ENCODING)); + output.write((contentEncoding==null ? PostWriter.ENCODING : contentEncoding).getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write("Content-Transfer-Encoding: 8bit".getBytes(HTTP_ENCODING)); + output.write(CRLF); + output.write(CRLF); + if(contentEncoding != null) { + output.write(descriptionValue.getBytes(contentEncoding)); + } + else { + output.write(descriptionValue.getBytes()); // TODO - charset? + } + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(HTTP_ENCODING)); + if(lastMultipart) { + output.write(DASH_DASH); + } + output.write(CRLF); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + /** + * Create the expected file multipart + * + * @param lastMultipart true if this is the last multipart in the request + */ + private byte[] createExpectedFilepartOutput( + String boundaryString, + String fileField, + File file, + String mimeType, + byte[] fileContent, + boolean firstMultipart, + boolean lastMultipart) throws IOException { + // The encoding used for http headers and control information + final String httpEncoding = "ISO-8859-1"; + final byte[] DASH_DASH = "--".getBytes(httpEncoding); + + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + if(firstMultipart) { + output.write(DASH_DASH); + output.write(boundaryString.getBytes(httpEncoding)); + output.write(CRLF); + } + // replace all backslash with double backslash + String filename = file.getName(); + output.write("Content-Disposition: form-data; name=\"".getBytes(httpEncoding)); + output.write(fileField.getBytes(httpEncoding)); + output.write(("\"; filename=\"" + filename + "\"").getBytes(httpEncoding)); + output.write(CRLF); + output.write("Content-Type: ".getBytes(httpEncoding)); + output.write(mimeType.getBytes(httpEncoding)); + output.write(CRLF); + output.write("Content-Transfer-Encoding: binary".getBytes(httpEncoding)); + output.write(CRLF); + output.write(CRLF); + output.write(fileContent); + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(httpEncoding)); + if(lastMultipart) { + output.write(DASH_DASH); + } + output.write(CRLF); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + /** + * Check that the the two byte arrays have identical content + * + * @param expected + * @param actual + * @throws UnsupportedEncodingException + */ + private void checkArraysHaveSameContent(byte[] expected, byte[] actual) throws UnsupportedEncodingException { + if(expected != null && actual != null) { + if(expected.length != actual.length) { + System.out.println(new String(expected,UTF_8)); + System.out.println("--------------------"); + System.out.println(new String(actual,UTF_8)); + System.out.println("===================="); + fail("arrays have different length, expected is " + expected.length + ", actual is " + actual.length); + } + else { + for(int i = 0; i < expected.length; i++) { + if(expected[i] != actual[i]) { + System.out.println(new String(expected,0,i+1, UTF_8)); + System.out.println("--------------------"); + System.out.println(new String(actual,0,i+1, UTF_8)); + System.out.println("===================="); + fail("byte at position " + i + " is different, expected is " + expected[i] + ", actual is " + actual[i]); + } + } + } + } + else { + fail("expected or actual byte arrays were null"); + } + } + + /** + * Check that the the two byte arrays different content + * + * @param expected + * @param actual + */ + private void checkArraysHaveDifferentContent(byte[] expected, byte[] actual) { + if(expected != null && actual != null) { + if(expected.length == actual.length) { + boolean allSame = true; + for(int i = 0; i < expected.length; i++) { + if(expected[i] != actual[i]) { + allSame = false; + break; + } + } + if(allSame) { + fail("all bytes were equal"); + } + } + } + else { + fail("expected or actual byte arrays were null"); + } + } + + private void checkContentTypeMultipart(HttpURLConnection conn, String boundaryString) { + assertEquals("multipart/form-data; boundary=" + boundaryString, conn.getRequestProperty("Content-Type")); + } + + private void checkContentTypeUrlEncoded(HttpURLConnection conn) { + assertEquals(HTTPSamplerBase.APPLICATION_X_WWW_FORM_URLENCODED, conn.getRequestProperty("Content-Type")); + } + + private void checkContentLength(HttpURLConnection conn, int length) { + assertEquals(Integer.toString(length), conn.getRequestProperty("Content-Length")); + } + + /** + * Mock an HttpURLConnection. + * extends HttpURLConnection instead of just URLConnection because there is a cast in PostWriter. + */ + private static class StubURLConnection extends HttpURLConnection { + private ByteArrayOutputStream output = new ByteArrayOutputStream(); + private Map properties = new HashMap(); + + public StubURLConnection(String url) throws MalformedURLException { + super(new URL(url)); + } + + @Override + public void connect() throws IOException { + } + + @Override + public OutputStream getOutputStream() throws IOException { + return output; + } + + @Override + public void disconnect() { + } + + @Override + public boolean usingProxy() { + return false; + } + + @Override + public String getRequestProperty(String key) { + return properties.get(key); + } + + @Override + public void setRequestProperty(String key, String value) { + properties.put(key, value); + } + + public byte[] getOutputStreamContent() { + return output.toByteArray(); + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PutWriterTest.java b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PutWriterTest.java new file mode 100644 index 0000000..3520905 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/PutWriterTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.net.URLConnection; +import junit.framework.TestCase; + +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.protocol.http.util.HTTPConstants; + +public class PutWriterTest extends TestCase { + + public PutWriterTest(String name) { + super(name); + } + + public void testSetHeaders() throws Exception { + URLConnection uc = new NullURLConnection(); + HTTPSampler sampler = new HTTPSampler(); + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("file1", "", "mime1")}); + PutWriter pw = new PutWriter(); + pw.setHeaders(uc, sampler); + assertEquals("mime1", uc.getRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE)); + uc = new NullURLConnection(); + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("file2", "param2", "mime2")}); + pw.setHeaders(uc, sampler); + assertEquals("mime2", uc.getRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE)); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplers.java b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplers.java new file mode 100644 index 0000000..4036dc6 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplers.java @@ -0,0 +1,348 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import junit.framework.TestCase; + +public class TestHTTPSamplers extends TestCase { + + public TestHTTPSamplers(String arg0) { + super(arg0); + } + + // Parse arguments singly + public void testParseArguments(){ + HTTPSamplerBase sampler = new HTTPNullSampler(); + Arguments args; + Argument arg; + + args = sampler.getArguments(); + assertEquals(0,args.getArgumentCount()); + assertEquals(0,sampler.getHTTPFileCount()); + + sampler.parseArguments(""); + args = sampler.getArguments(); + assertEquals(0,args.getArgumentCount()); + assertEquals(0,sampler.getHTTPFileCount()); + + sampler.parseArguments("name1"); + args = sampler.getArguments(); + assertEquals(1,args.getArgumentCount()); + arg=args.getArgument(0); + assertEquals("name1",arg.getName()); + assertEquals("",arg.getMetaData()); + assertEquals("",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + + sampler.parseArguments("name2="); + args = sampler.getArguments(); + assertEquals(2,args.getArgumentCount()); + arg=args.getArgument(1); + assertEquals("name2",arg.getName()); + assertEquals("=",arg.getMetaData()); + assertEquals("",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + + sampler.parseArguments("name3=value3"); + args = sampler.getArguments(); + assertEquals(3,args.getArgumentCount()); + arg=args.getArgument(2); + assertEquals("name3",arg.getName()); + assertEquals("=",arg.getMetaData()); + assertEquals("value3",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + } + + // Parse arguments all at once + public void testParseArguments2(){ + HTTPSamplerBase sampler = new HTTPNullSampler(); + Arguments args; + Argument arg; + + args = sampler.getArguments(); + assertEquals(0,args.getArgumentCount()); + assertEquals(0,sampler.getHTTPFileCount()); + + sampler.parseArguments("&name1&name2=&name3=value3"); + args = sampler.getArguments(); + assertEquals(3,args.getArgumentCount()); + assertEquals(0,sampler.getHTTPFileCount()); + + arg=args.getArgument(0); + assertEquals("name1",arg.getName()); + assertEquals("",arg.getMetaData()); + assertEquals("",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + + arg=args.getArgument(1); + assertEquals("name2",arg.getName()); + assertEquals("=",arg.getMetaData()); + assertEquals("",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + + arg=args.getArgument(2); + assertEquals("name3",arg.getName()); + assertEquals("=",arg.getMetaData()); + assertEquals("value3",arg.getValue()); + assertEquals(0,sampler.getHTTPFileCount()); + } + + public void testArgumentWithoutEquals() throws Exception { + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setProtocol("http"); + sampler.setMethod(HTTPSamplerBase.GET); + sampler.setPath("/index.html?pear"); + sampler.setDomain("www.apache.org"); + assertEquals("http://www.apache.org/index.html?pear", sampler.getUrl().toString()); + } + + public void testMakingUrl() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPSamplerBase.GET); + config.addArgument("param1", "value1"); + config.setPath("/index.html"); + config.setDomain("www.apache.org"); + assertEquals("http://www.apache.org/index.html?param1=value1", config.getUrl().toString()); + } + + public void testRedirect() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPSamplerBase.GET); + config.setDomain("192.168.0.1"); + HTTPSampleResult res = new HTTPSampleResult(); + res.sampleStart(); + res.setURL(config.getUrl()); + res.setResponseCode("301"); + res.sampleEnd(); + + res.setRedirectLocation("./"); + config.followRedirects(res , 0); + assertEquals("http://192.168.0.1/", config.getUrl().toString()); + + res.setRedirectLocation("."); + config.followRedirects(res , 0); + assertEquals("http://192.168.0.1/", config.getUrl().toString()); + + res.setRedirectLocation("../"); + config.followRedirects(res , 0); + assertEquals("http://192.168.0.1/", config.getUrl().toString()); + } + + public void testMakingUrl2() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPSamplerBase.GET); + config.addArgument("param1", "value1"); + config.setPath("/index.html?p1=p2"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1=value1&p1=p2", config.getUrl().toString()); + } + + public void testMakingUrl3() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPSamplerBase.POST); + config.addArgument("param1", "value1"); + config.setPath("/index.html?p1=p2"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?p1=p2", config.getUrl().toString()); + } + + // test cases for making Url, and exercise method + // addArgument(String name,String value,String metadata) + + public void testMakingUrl4() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPSamplerBase.GET); + config.addArgument("param1", "value1", "="); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1=value1", config.getUrl().toString()); + } + + public void testMakingUrl5() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPSamplerBase.GET); + config.addArgument("param1", "", "="); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1=", config.getUrl().toString()); + } + + public void testMakingUrl6() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPSamplerBase.GET); + config.addArgument("param1", "", ""); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1", config.getUrl().toString()); + } + + // test cases for making Url, and exercise method + // parseArguments(String queryString) + + public void testMakingUrl7() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPSamplerBase.GET); + config.parseArguments("param1=value1"); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1=value1", config.getUrl().toString()); + } + + public void testMakingUrl8() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPSamplerBase.GET); + config.parseArguments("param1="); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1=", config.getUrl().toString()); + } + + public void testMakingUrl9() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPSamplerBase.GET); + config.parseArguments("param1"); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html?param1", config.getUrl().toString()); + } + + public void testMakingUrl10() throws Exception { + HTTPSamplerBase config = new HTTPNullSampler(); + config.setProtocol("http"); + config.setMethod(HTTPSamplerBase.GET); + config.parseArguments(""); + config.setPath("/index.html"); + config.setDomain("192.168.0.1"); + assertEquals("http://192.168.0.1/index.html", config.getUrl().toString()); + } + + public void testFileList(){ + HTTPSamplerBase config = new HTTPNullSampler(); + HTTPFileArg[] arg; + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(0,arg.length); + + config.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","","")}); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(0,arg.length); + + config.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","","text/plain")}); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(1,arg.length); + assertEquals("text/plain",arg[0].getMimeType()); + assertEquals("",arg[0].getPath()); + assertEquals("",arg[0].getParamName()); + + config.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("/tmp/test123.tmp","test123.tmp","text/plain")}); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(1,arg.length); + assertEquals("text/plain",arg[0].getMimeType()); + assertEquals("/tmp/test123.tmp",arg[0].getPath()); + assertEquals("test123.tmp",arg[0].getParamName()); + + HTTPFileArg[] files = {}; + + // Ignore empty file specs + config.setHTTPFiles(files); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(0,arg.length); + files = new HTTPFileArg[]{ + new HTTPFileArg(), + new HTTPFileArg(), + }; + config.setHTTPFiles(files); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(0,arg.length); + + // Ignore trailing empty spec + files = new HTTPFileArg[]{ + new HTTPFileArg("file"), + new HTTPFileArg(), + }; + config.setHTTPFiles(files); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(1,arg.length); + + // Ignore leading empty spec + files = new HTTPFileArg[]{ + new HTTPFileArg(), + new HTTPFileArg("file1"), + new HTTPFileArg(), + new HTTPFileArg("file2"), + new HTTPFileArg(), + }; + config.setHTTPFiles(files); + arg = config.getHTTPFiles(); + assertNotNull(arg); + assertEquals(2,arg.length); + } + + public void testSetAndGetFileField() { + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","param","")}); + HTTPFileArg file = sampler.getHTTPFiles()[0]; + assertEquals("param", file.getParamName()); + + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","param2","")}); + file = sampler.getHTTPFiles()[0]; + assertEquals("param2", file.getParamName()); +} + + public void testSetAndGetFilename() { + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("name","","")}); + HTTPFileArg file = sampler.getHTTPFiles()[0]; + assertEquals("name", file.getPath()); + + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("name2","","")}); + file = sampler.getHTTPFiles()[0]; + assertEquals("name2", file.getPath()); + } + + public void testSetAndGetMimetype() { + HTTPSamplerBase sampler = new HTTPNullSampler(); + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","","mime")}); + HTTPFileArg file = sampler.getHTTPFiles()[0]; + assertEquals("mime", file.getMimeType()); + + sampler.setHTTPFiles(new HTTPFileArg[]{new HTTPFileArg("","","mime2")}); + file = sampler.getHTTPFiles()[0]; + assertEquals("mime2", file.getMimeType()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplersAgainstHttpMirrorServer.java b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplersAgainstHttpMirrorServer.java new file mode 100644 index 0000000..cb348dc --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/sampler/TestHTTPSamplersAgainstHttpMirrorServer.java @@ -0,0 +1,1369 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.net.URL; +import java.util.Locale; + +import org.apache.jmeter.engine.util.ValueReplacer; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.control.HttpMirrorServer; +import org.apache.jmeter.protocol.http.control.TestHTTPMirrorThread; +import org.apache.jmeter.protocol.http.util.EncoderCache; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +import junit.framework.Test; +import junit.framework.TestSuite; +import junit.extensions.TestSetup; + +/** + * Class for performing actual samples for HTTPSampler and HTTPSampler2. + * The samples are executed against the HttpMirrorServer, which is + * started when the unit tests are executed. + */ +public class TestHTTPSamplersAgainstHttpMirrorServer extends JMeterTestCase { + private final static int HTTP_SAMPLER = 0; + private final static int HTTP_SAMPLER2 = 1; + private final static int HTTP_SAMPLER3 = 2; + + /** The encodings used for http headers and control information */ + private final static String ISO_8859_1 = "ISO-8859-1"; // $NON-NLS-1$ + private static final String US_ASCII = "US-ASCII"; // $NON-NLS-1$ + + private static final byte[] CRLF = { 0x0d, 0x0A }; + private static final int MIRROR_PORT = 8081; // Different from TestHTTPMirrorThread port + private static byte[] TEST_FILE_CONTENT; + + private static File temporaryFile; + + public TestHTTPSamplersAgainstHttpMirrorServer(String arg0) { + super(arg0); + } + + public static Test suite(){ + TestSetup setup = new TestSetup(new TestSuite(TestHTTPSamplersAgainstHttpMirrorServer.class)){ + private HttpMirrorServer httpServer; + @Override + protected void setUp() throws Exception { + httpServer = TestHTTPMirrorThread.startHttpMirror(MIRROR_PORT); + // Create the test file content + TEST_FILE_CONTENT = "some foo content &?=01234+56789-\u007c\u2aa1\u266a\u0153\u20a1\u0115\u0364\u00c5\u2052\uc385%C3%85".getBytes("UTF-8"); + + // create a temporary file to make sure we always have a file to give to the PostWriter + // Whereever we are or Whatever the current path is. + temporaryFile = File.createTempFile("TestHTTPSamplersAgainstHttpMirrorServer", "tmp"); + OutputStream output = new FileOutputStream(temporaryFile); + output.write(TEST_FILE_CONTENT); + output.flush(); + output.close(); + } + + @Override + protected void tearDown() throws Exception { + // Shutdown mirror server + httpServer.stopServer(); + httpServer = null; + // delete temporay file + if(!temporaryFile.delete()) { + fail("Could not delete file:"+temporaryFile.getAbsolutePath()); + } + } + }; + return setup; + } + + public void testPostRequest_UrlEncoded() throws Exception { + testPostRequest_UrlEncoded(HTTP_SAMPLER, ISO_8859_1); + } + + public void testPostRequest_UrlEncoded2() throws Exception { + testPostRequest_UrlEncoded(HTTP_SAMPLER2, US_ASCII); + } + + public void testPostRequest_UrlEncoded3() throws Exception { + testPostRequest_UrlEncoded(HTTP_SAMPLER3, US_ASCII); + } + + public void testPostRequest_FormMultipart() throws Exception { + testPostRequest_FormMultipart(HTTP_SAMPLER, ISO_8859_1); + } + + public void testPostRequest_FormMultipart2() throws Exception { + testPostRequest_FormMultipart(HTTP_SAMPLER2, US_ASCII); + } + + public void testPostRequest_FormMultipart3() throws Exception { + testPostRequest_FormMultipart(HTTP_SAMPLER3, US_ASCII); + } + + public void testPostRequest_FileUpload() throws Exception { + testPostRequest_FileUpload(HTTP_SAMPLER, ISO_8859_1); + } + + public void testPostRequest_FileUpload2() throws Exception { + testPostRequest_FileUpload(HTTP_SAMPLER2, US_ASCII); + } + + public void testPostRequest_FileUpload3() throws Exception { + testPostRequest_FileUpload(HTTP_SAMPLER3, US_ASCII); + } + + public void testPostRequest_BodyFromParameterValues() throws Exception { + testPostRequest_BodyFromParameterValues(HTTP_SAMPLER, ISO_8859_1); + } + + public void testPostRequest_BodyFromParameterValues2() throws Exception { + testPostRequest_BodyFromParameterValues(HTTP_SAMPLER2, US_ASCII); + } + + public void testPostRequest_BodyFromParameterValues3() throws Exception { + testPostRequest_BodyFromParameterValues(HTTP_SAMPLER3, US_ASCII); + } + + public void testGetRequest() throws Exception { + testGetRequest(HTTP_SAMPLER); + } + + public void testGetRequest2() throws Exception { + testGetRequest(HTTP_SAMPLER2); + } + + public void testGetRequest3() throws Exception { + testGetRequest(HTTP_SAMPLER3); + } + + public void testGetRequest_Parameters() throws Exception { + testGetRequest_Parameters(HTTP_SAMPLER); + } + + public void testGetRequest_Parameters2() throws Exception { + testGetRequest_Parameters(HTTP_SAMPLER2); + } + + public void testGetRequest_Parameters3() throws Exception { + testGetRequest_Parameters(HTTP_SAMPLER3); + } + + private void testPostRequest_UrlEncoded(int samplerType, String samplerDefaultEncoding) throws Exception { + String titleField = "title"; + String titleValue = "mytitle"; + String descriptionField = "description"; + String descriptionValue = "mydescription"; + + // Test sending data with default encoding + HTTPSamplerBase sampler = createHttpSampler(samplerType); + String contentEncoding = ""; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + HTTPSampleResult res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, false); + + // Test sending data as ISO-8859-1 + sampler = createHttpSampler(samplerType); + contentEncoding = ISO_8859_1; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, false); + + // Test sending data as UTF-8 + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, false); + + // Test sending data as UTF-8, with values that will change when urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle/="; + descriptionValue = "mydescription /\\"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, false); + + // Test sending data as UTF-8, with values that have been urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle%2F%3D"; + descriptionValue = "mydescription+++%2F%5C"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, true, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true); + + // Test sending data as UTF-8, with values similar to __VIEWSTATE parameter that .net uses + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "/wEPDwULLTE2MzM2OTA0NTYPZBYCAgMPZ/rA+8DZ2dnZ2dnZ2d/GNDar6OshPwdJc="; + descriptionValue = "mydescription"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, false); + + // Test sending data as UTF-8, with values similar to __VIEWSTATE parameter that .net uses, + // with values urlencoded, but the always encode set to false for the arguments + // This is how the HTTP Proxy server adds arguments to the sampler + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "%2FwEPDwULLTE2MzM2OTA0NTYPZBYCAgMPZ%2FrA%2B8DZ2dnZ2dnZ2d%2FGNDar6OshPwdJc%3D"; + descriptionValue = "mydescription"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + assertFalse(((HTTPArgument)sampler.getArguments().getArgument(0)).isAlwaysEncoded()); + assertFalse(((HTTPArgument)sampler.getArguments().getArgument(1)).isAlwaysEncoded()); + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true); + + // Test sending data as UTF-8, where user defined variables are used + // to set the value for form data + JMeterUtils.setLocale(Locale.ENGLISH); + TestPlan testPlan = new TestPlan(); + JMeterVariables vars = new JMeterVariables(); + vars.put("title_prefix", "a test\u00c5"); + vars.put("description_suffix", "the_end"); + JMeterContextService.getContext().setVariables(vars); + JMeterContextService.getContext().setSamplingStarted(true); + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(testPlan.getUserDefinedVariables()); + + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "${title_prefix}mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5${description_suffix}"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + // Replace the variables in the sampler + replacer.replaceValues(sampler); + res = executeSampler(sampler); + String expectedTitleValue = "a test\u00c5mytitle\u0153\u20a1\u0115\u00c5"; + String expectedDescriptionValue = "mydescription\u0153\u20a1\u0115\u00c5the_end"; + checkPostRequestUrlEncoded(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, expectedTitleValue, descriptionField, expectedDescriptionValue, false); + } + + private void testPostRequest_FormMultipart(int samplerType, String samplerDefaultEncoding) throws Exception { + String titleField = "title"; + String titleValue = "mytitle"; + String descriptionField = "description"; + String descriptionValue = "mydescription"; + + // Test sending data with default encoding + HTTPSamplerBase sampler = createHttpSampler(samplerType); + String contentEncoding = ""; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + HTTPSampleResult res = executeSampler(sampler); + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue); + + // Test sending data as ISO-8859-1 + sampler = createHttpSampler(samplerType); + contentEncoding = ISO_8859_1; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + res = executeSampler(sampler); + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue); + + // Test sending data as UTF-8 + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + res = executeSampler(sampler); + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue); + + // Test sending data as UTF-8, with values that would have been urlencoded + // if it was not sent as multipart + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle/="; + descriptionValue = "mydescription /\\"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + res = executeSampler(sampler); + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue); + + // Test sending data as UTF-8, with values that have been urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle%2F%3D"; + descriptionValue = "mydescription+++%2F%5C"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, true, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + res = executeSampler(sampler); + String expectedTitleValue = "mytitle/="; + String expectedDescriptionValue = "mydescription /\\"; + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, expectedTitleValue, descriptionField, expectedDescriptionValue); + + // Test sending data as UTF-8, with values similar to __VIEWSTATE parameter that .net uses + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "/wEPDwULLTE2MzM2OTA0NTYPZBYCAgMPZ/rA+8DZ2dnZ2dnZ2d/GNDar6OshPwdJc="; + descriptionValue = "mydescription"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + res = executeSampler(sampler); + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue); + + // Test sending data as UTF-8, where user defined variables are used + // to set the value for form data + JMeterUtils.setLocale(Locale.ENGLISH); + TestPlan testPlan = new TestPlan(); + JMeterVariables vars = new JMeterVariables(); + vars.put("title_prefix", "a test\u00c5"); + vars.put("description_suffix", "the_end"); + JMeterContextService.getContext().setVariables(vars); + JMeterContextService.getContext().setSamplingStarted(true); + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(testPlan.getUserDefinedVariables()); + + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "${title_prefix}mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5${description_suffix}"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + sampler.setDoMultipartPost(true); + // Replace the variables in the sampler + replacer.replaceValues(sampler); + res = executeSampler(sampler); + expectedTitleValue = "a test\u00c5mytitle\u0153\u20a1\u0115\u00c5"; + expectedDescriptionValue = "mydescription\u0153\u20a1\u0115\u00c5the_end"; + checkPostRequestFormMultipart(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, expectedTitleValue, descriptionField, expectedDescriptionValue); + } + + private void testPostRequest_FileUpload(int samplerType, String samplerDefaultEncoding) throws Exception { + String titleField = "title"; + String titleValue = "mytitle"; + String descriptionField = "description"; + String descriptionValue = "mydescription"; + String fileField = "file1"; + String fileMimeType = "text/plain"; + + // Test sending data with default encoding + HTTPSamplerBase sampler = createHttpSampler(samplerType); + String contentEncoding = ""; + setupUrl(sampler, contentEncoding); + setupFileUploadData(sampler, false, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType); + HTTPSampleResult res = executeSampler(sampler); + checkPostRequestFileUpload(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType, TEST_FILE_CONTENT); + + // Test sending data as ISO-8859-1 + sampler = createHttpSampler(samplerType); + contentEncoding = ISO_8859_1; + setupUrl(sampler, contentEncoding); + setupFileUploadData(sampler, false, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType); + res = executeSampler(sampler); + checkPostRequestFileUpload(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType, TEST_FILE_CONTENT); + + // Test sending data as UTF-8 + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + setupUrl(sampler, contentEncoding); + setupFileUploadData(sampler, false, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType); + res = executeSampler(sampler); + checkPostRequestFileUpload(sampler, res, samplerDefaultEncoding, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, fileField, temporaryFile, fileMimeType, TEST_FILE_CONTENT); + } + + private void testPostRequest_BodyFromParameterValues(int samplerType, String samplerDefaultEncoding) throws Exception { + final String titleField = ""; // ensure only values are used + String titleValue = "mytitle"; + final String descriptionField = ""; // ensure only values are used + String descriptionValue = "mydescription"; + + // Test sending data with default encoding + HTTPSamplerBase sampler = createHttpSampler(samplerType); + String contentEncoding = ""; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + HTTPSampleResult res = executeSampler(sampler); + String expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as ISO-8859-1 + sampler = createHttpSampler(samplerType); + contentEncoding = ISO_8859_1; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8 + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with values that will change when urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle/="; + descriptionValue = "mydescription /\\"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with values that will change when urlencoded, and where + // we tell the sampler to urlencode the parameter value + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle/="; + descriptionValue = "mydescription /\\"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, true, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + expectedPostBody = URLEncoder.encode(titleValue + descriptionValue, contentEncoding); + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with values that have been urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle%2F%3D"; + descriptionValue = "mydescription+++%2F%5C"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with values that have been urlencoded, and + // where we tell the sampler to urlencode the parameter values + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle%2F%3D"; + descriptionValue = "mydescription+++%2F%5C"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, true, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with values similar to __VIEWSTATE parameter that .net uses + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "/wEPDwULLTE2MzM2OTA0NTYPZBYCAgMPZ/rA+8DZ2dnZ2dnZ2d/GNDar6OshPwdJc="; + descriptionValue = "mydescription"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, with + as part of the value, + // where the value is set in sampler as not urluencoded, but the + // isalwaysencoded flag of the argument is set to false. + // This mimics the HTTPSamplerBase.addNonEncodedArgument, which the + // Proxy server calls in some cases + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle++"; + descriptionValue = "mydescription+"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + res = executeSampler(sampler); + expectedPostBody = titleValue + descriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + + // Test sending data as UTF-8, where user defined variables are used + // to set the value for form data + JMeterUtils.setLocale(Locale.ENGLISH); + TestPlan testPlan = new TestPlan(); + JMeterVariables vars = new JMeterVariables(); + vars.put("title_prefix", "a test\u00c5"); + vars.put("description_suffix", "the_end"); + JMeterContextService.getContext().setVariables(vars); + JMeterContextService.getContext().setSamplingStarted(true); + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(testPlan.getUserDefinedVariables()); + + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "${title_prefix}mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5${description_suffix}"; + setupUrl(sampler, contentEncoding); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + ((HTTPArgument)sampler.getArguments().getArgument(0)).setAlwaysEncoded(false); + ((HTTPArgument)sampler.getArguments().getArgument(1)).setAlwaysEncoded(false); + // Replace the variables in the sampler + replacer.replaceValues(sampler); + res = executeSampler(sampler); + String expectedTitleValue = "a test\u00c5mytitle\u0153\u20a1\u0115\u00c5"; + String expectedDescriptionValue = "mydescription\u0153\u20a1\u0115\u00c5the_end"; + expectedPostBody = expectedTitleValue+ expectedDescriptionValue; + checkPostRequestBody(sampler, res, samplerDefaultEncoding, contentEncoding, expectedPostBody); + } + + private void testGetRequest(int samplerType) throws Exception { + // Test sending simple HTTP get + // Test sending data with default encoding + HTTPSamplerBase sampler = createHttpSampler(samplerType); + String contentEncoding = ""; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPSamplerBase.GET); + HTTPSampleResult res = executeSampler(sampler); + checkGetRequest(sampler, res); + + // Test sending data with ISO-8859-1 encoding + sampler = createHttpSampler(samplerType); + contentEncoding = ISO_8859_1; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPSamplerBase.GET); + res = executeSampler(sampler); + checkGetRequest(sampler, res); + + // Test sending data with UTF-8 encoding + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPSamplerBase.GET); + res = executeSampler(sampler); + checkGetRequest(sampler, res); + } + + private void testGetRequest_Parameters(int samplerType) throws Exception { + String titleField = "title"; + String titleValue = "mytitle"; + String descriptionField = "description"; + String descriptionValue = "mydescription"; + + // Test sending simple HTTP get + // Test sending data with default encoding + HTTPSamplerBase sampler = createHttpSampler(samplerType); + String contentEncoding = ""; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPSamplerBase.GET); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + HTTPSampleResult res = executeSampler(sampler); + sampler.setRunningVersion(true); + URL executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, titleValue, descriptionField, descriptionValue, false); + + // Test sending data with ISO-8859-1 encoding + sampler = createHttpSampler(samplerType); + contentEncoding = ISO_8859_1; + titleValue = "mytitle\uc385"; + descriptionValue = "mydescription\uc385"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPSamplerBase.GET); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + sampler.setRunningVersion(true); + executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, titleValue, descriptionField, descriptionValue, false); + + // Test sending data with UTF-8 encoding + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPSamplerBase.GET); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + sampler.setRunningVersion(true); + executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, titleValue, descriptionField, descriptionValue, false); + + // Test sending data as UTF-8, with values that changes when urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle\u0153+\u20a1 \u0115&yes\u00c5"; + descriptionValue = "mydescription \u0153 \u20a1 \u0115 \u00c5"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPSamplerBase.GET); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + sampler.setRunningVersion(true); + executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + String expectedTitleValue = "mytitle\u0153%2B\u20a1+\u0115%26yes\u00c5"; + String expectedDescriptionValue = "mydescription+\u0153+\u20a1+\u0115+\u00c5"; + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, titleValue, descriptionField, descriptionValue, false); + + // Test sending data as UTF-8, with values that have been urlencoded + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "mytitle%2F%3D"; + descriptionValue = "mydescription+++%2F%5C"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPSamplerBase.GET); + setupFormData(sampler, true, titleField, titleValue, descriptionField, descriptionValue); + res = executeSampler(sampler); + sampler.setRunningVersion(true); + executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, titleValue, descriptionField, descriptionValue, true); + + // Test sending data as UTF-8, where user defined variables are used + // to set the value for form data + JMeterUtils.setLocale(Locale.ENGLISH); + TestPlan testPlan = new TestPlan(); + JMeterVariables vars = new JMeterVariables(); + vars.put("title_prefix", "a test\u00c5"); + vars.put("description_suffix", "the_end"); + JMeterContextService.getContext().setVariables(vars); + JMeterContextService.getContext().setSamplingStarted(true); + ValueReplacer replacer = new ValueReplacer(); + replacer.setUserDefinedVariables(testPlan.getUserDefinedVariables()); + + sampler = createHttpSampler(samplerType); + contentEncoding = "UTF-8"; + titleValue = "${title_prefix}mytitle\u0153\u20a1\u0115\u00c5"; + descriptionValue = "mydescription\u0153\u20a1\u0115\u00c5${description_suffix}"; + setupUrl(sampler, contentEncoding); + sampler.setMethod(HTTPSamplerBase.GET); + setupFormData(sampler, false, titleField, titleValue, descriptionField, descriptionValue); + // Replace the variables in the sampler + replacer.replaceValues(sampler); + res = executeSampler(sampler); + expectedTitleValue = "a test\u00c5mytitle\u0153\u20a1\u0115\u00c5"; + expectedDescriptionValue = "mydescription\u0153\u20a1\u0115\u00c5the_end"; + sampler.setRunningVersion(true); + executedUrl = sampler.getUrl(); + sampler.setRunningVersion(false); + checkGetRequest_Parameters(sampler, res, contentEncoding, executedUrl, titleField, expectedTitleValue, descriptionField, expectedDescriptionValue, false); + } + + private HTTPSampleResult executeSampler(HTTPSamplerBase sampler) { + sampler.setRunningVersion(true); + sampler.threadStarted(); + HTTPSampleResult res = (HTTPSampleResult) sampler.sample(); + sampler.threadFinished(); + sampler.setRunningVersion(false); + return res; + } + + private void checkPostRequestUrlEncoded( + HTTPSamplerBase sampler, + HTTPSampleResult res, + String samplerDefaultEncoding, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + boolean valuesAlreadyUrlEncoded) throws IOException { + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = samplerDefaultEncoding; + } + // Check URL + assertEquals(sampler.getUrl(), res.getURL()); + String expectedPostBody = null; + if(!valuesAlreadyUrlEncoded) { + String expectedTitle = URLEncoder.encode(titleValue, contentEncoding); + String expectedDescription = URLEncoder.encode(descriptionValue, contentEncoding); + expectedPostBody = titleField + "=" + expectedTitle + "&" + descriptionField + "=" + expectedDescription; + } + else { + expectedPostBody = titleField + "=" + titleValue + "&" + descriptionField + "=" + descriptionValue; + } + // Check the request + checkPostRequestBody( + sampler, + res, + samplerDefaultEncoding, + contentEncoding, + expectedPostBody + ); + } + + private void checkPostRequestFormMultipart( + HTTPSamplerBase sampler, + HTTPSampleResult res, + String samplerDefaultEncoding, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue) throws IOException { + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = samplerDefaultEncoding; + } + // Check URL + assertEquals(sampler.getUrl(), res.getURL()); + String boundaryString = getBoundaryStringFromContentType(res.getRequestHeaders()); + assertNotNull(boundaryString); + byte[] expectedPostBody = createExpectedFormdataOutput(boundaryString, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, true); + // Check request headers + checkHeaderTypeLength(res.getRequestHeaders(), "multipart/form-data" + "; boundary=" + boundaryString, expectedPostBody.length); + // Check post body from the result query string + checkArraysHaveSameContent(expectedPostBody, res.getQueryString().getBytes(contentEncoding)); + + // Find the data sent to the mirror server, which the mirror server is sending back to us + String dataSentToMirrorServer = new String(res.getResponseData(), contentEncoding); + int posDividerHeadersAndBody = getPositionOfBody(dataSentToMirrorServer); + String headersSent = null; + String bodySent = ""; + if(posDividerHeadersAndBody >= 0) { + headersSent = dataSentToMirrorServer.substring(0, posDividerHeadersAndBody); + // Skip the blank line with crlf dividing headers and body + bodySent = dataSentToMirrorServer.substring(posDividerHeadersAndBody+2); + } + else { + fail("No header and body section found"); + } + // Check response headers + checkHeaderTypeLength(headersSent, "multipart/form-data" + "; boundary=" + boundaryString, expectedPostBody.length); + // Check post body which was sent to the mirror server, and + // sent back by the mirror server + checkArraysHaveSameContent(expectedPostBody, bodySent.getBytes(contentEncoding)); + // Check method, path and query sent + checkMethodPathQuery(headersSent, sampler.getMethod(), sampler.getPath(), null); + } + + private void checkPostRequestFileUpload( + HTTPSamplerBase sampler, + HTTPSampleResult res, + String samplerDefaultEncoding, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + String fileField, + File fileValue, + String fileMimeType, + byte[] fileContent) throws IOException { + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = samplerDefaultEncoding; + } + // Check URL + assertEquals(sampler.getUrl(), res.getURL()); + String boundaryString = getBoundaryStringFromContentType(res.getRequestHeaders()); + assertNotNull(boundaryString); + byte[] expectedPostBody = createExpectedFormAndUploadOutput(boundaryString, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, fileField, fileValue, fileMimeType, fileContent); + // Check request headers + checkHeaderTypeLength(res.getRequestHeaders(), "multipart/form-data" + "; boundary=" + boundaryString, expectedPostBody.length); + // We cannot check post body from the result query string, since that will not contain + // the actual file content, but placeholder text for file content + //checkArraysHaveSameContent(expectedPostBody, res.getQueryString().getBytes(contentEncoding)); + + // Find the data sent to the mirror server, which the mirror server is sending back to us + String headersSent = getHeadersSent(res.getResponseData()); + if(headersSent == null) { + fail("No header and body section found"); + } + // Check response headers + checkHeaderTypeLength(headersSent, "multipart/form-data" + "; boundary=" + boundaryString, expectedPostBody.length); + byte[] bodySent = getBodySent(res.getResponseData()); + assertNotNull("Sent body should not be null", bodySent); + // Check post body which was sent to the mirror server, and + // sent back by the mirror server + checkArraysHaveSameContent(expectedPostBody, bodySent); + // Check method, path and query sent + checkMethodPathQuery(headersSent, sampler.getMethod(), sampler.getPath(), null); + } + + private void checkPostRequestBody( + HTTPSamplerBase sampler, + HTTPSampleResult res, + String samplerDefaultEncoding, + String contentEncoding, + String expectedPostBody) throws IOException { + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = samplerDefaultEncoding; + } + // Check URL + assertEquals(sampler.getUrl(), res.getURL()); + // Check request headers + checkHeaderTypeLength(res.getRequestHeaders(), HTTPSamplerBase.APPLICATION_X_WWW_FORM_URLENCODED, expectedPostBody.getBytes(contentEncoding).length); + // Check post body from the result query string + checkArraysHaveSameContent(expectedPostBody.getBytes(contentEncoding), res.getQueryString().getBytes(contentEncoding)); + + // Find the data sent to the mirror server, which the mirror server is sending back to us + String dataSentToMirrorServer = new String(res.getResponseData(), contentEncoding); + int posDividerHeadersAndBody = getPositionOfBody(dataSentToMirrorServer); + String headersSent = null; + String bodySent = ""; + if(posDividerHeadersAndBody >= 0) { + headersSent = dataSentToMirrorServer.substring(0, posDividerHeadersAndBody); + // Skip the blank line with crlf dividing headers and body + bodySent = dataSentToMirrorServer.substring(posDividerHeadersAndBody+2); + } + else { + fail("No header and body section found"); + } + // Check response headers + checkHeaderTypeLength(headersSent, HTTPSamplerBase.APPLICATION_X_WWW_FORM_URLENCODED, expectedPostBody.getBytes(contentEncoding).length); + // Check post body which was sent to the mirror server, and + // sent back by the mirror server + checkArraysHaveSameContent(expectedPostBody.getBytes(contentEncoding), bodySent.getBytes(contentEncoding)); + // Check method, path and query sent + checkMethodPathQuery(headersSent, sampler.getMethod(), sampler.getPath(), null); + } + + private void checkGetRequest( + HTTPSamplerBase sampler, + HTTPSampleResult res + ) throws IOException { + // Check URL + assertEquals(sampler.getUrl(), res.getURL()); + // Check method + assertEquals(sampler.getMethod(), res.getHTTPMethod()); + // Check that the query string is empty + assertEquals(0, res.getQueryString().length()); + + // Find the data sent to the mirror server, which the mirror server is sending back to us + String dataSentToMirrorServer = new String(res.getResponseData(), EncoderCache.URL_ARGUMENT_ENCODING); + int posDividerHeadersAndBody = getPositionOfBody(dataSentToMirrorServer); + String headersSent = null; + String bodySent = ""; + if(posDividerHeadersAndBody >= 0) { + headersSent = dataSentToMirrorServer.substring(0, posDividerHeadersAndBody); + // Skip the blank line with crlf dividing headers and body + bodySent = dataSentToMirrorServer.substring(posDividerHeadersAndBody+2); + } + else { + fail("No header and body section found"); + } + // No body should have been sent + assertEquals(bodySent.length(), 0); + // Check method, path and query sent + checkMethodPathQuery(headersSent, sampler.getMethod(), sampler.getPath(), null); + } + + private void checkGetRequest_Parameters( + HTTPSamplerBase sampler, + HTTPSampleResult res, + String contentEncoding, + URL executedUrl, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + boolean valuesAlreadyUrlEncoded) throws IOException { + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = EncoderCache.URL_ARGUMENT_ENCODING; + } + // Check URL + assertEquals(executedUrl, res.getURL()); + // Check method + assertEquals(sampler.getMethod(), res.getHTTPMethod()); + // Cannot check the query string of the result, because the mirror server + // replies without including query string in URL + + String expectedQueryString = null; + if(!valuesAlreadyUrlEncoded) { + String expectedTitle = URLEncoder.encode(titleValue, contentEncoding); + String expectedDescription = URLEncoder.encode(descriptionValue, contentEncoding); + expectedQueryString = titleField + "=" + expectedTitle + "&" + descriptionField + "=" + expectedDescription; + } + else { + expectedQueryString = titleField + "=" + titleValue + "&" + descriptionField + "=" + descriptionValue; + } + + // Find the data sent to the mirror server, which the mirror server is sending back to us + String dataSentToMirrorServer = new String(res.getResponseData(), EncoderCache.URL_ARGUMENT_ENCODING); + int posDividerHeadersAndBody = getPositionOfBody(dataSentToMirrorServer); + String headersSent = null; + String bodySent = ""; + if(posDividerHeadersAndBody >= 0) { + headersSent = dataSentToMirrorServer.substring(0, posDividerHeadersAndBody); + // Skip the blank line with crlf dividing headers and body + bodySent = dataSentToMirrorServer.substring(posDividerHeadersAndBody+2); + } + else { + fail("No header and body section found"); + } + // No body should have been sent + assertEquals(bodySent.length(), 0); + // Check method, path and query sent + checkMethodPathQuery(headersSent, sampler.getMethod(), sampler.getPath(), expectedQueryString); + } + + private void checkMethodPathQuery( + String headersSent, + String expectedMethod, + String expectedPath, + String expectedQueryString) + throws IOException { + // Check the Request URI sent to the mirror server, and + // sent back by the mirror server + int indexFirstSpace = headersSent.indexOf(" "); + int indexSecondSpace = headersSent.indexOf(" ", headersSent.length() > indexFirstSpace ? indexFirstSpace + 1 : indexFirstSpace); + if(indexFirstSpace <= 0 && indexSecondSpace <= 0 || indexFirstSpace == indexSecondSpace) { + fail("Could not find method and URI sent"); + } + String methodSent = headersSent.substring(0, indexFirstSpace); + assertEquals(expectedMethod, methodSent); + String uriSent = headersSent.substring(indexFirstSpace + 1, indexSecondSpace); + int indexQueryStart = uriSent.indexOf("?"); + if(expectedQueryString != null && expectedQueryString.length() > 0) { + // We should have a query string part + if(indexQueryStart <= 0 || (indexQueryStart == uriSent.length() - 1)) { + fail("Could not find query string in URI"); + } + } + else { + if(indexQueryStart > 0) { + // We should not have a query string part + fail("Query string present in URI"); + } + else { + indexQueryStart = uriSent.length(); + } + } + // Check path + String pathSent = uriSent.substring(0, indexQueryStart); + assertEquals(expectedPath, pathSent); + // Check query + if(expectedQueryString != null && expectedQueryString.length() > 0) { + String queryStringSent = uriSent.substring(indexQueryStart + 1); + // Is it only the parameter values which are encoded in the specified + // content encoding, the rest of the query is encoded in UTF-8 + // Therefore we compare the whole query using UTF-8 + checkArraysHaveSameContent(expectedQueryString.getBytes(EncoderCache.URL_ARGUMENT_ENCODING), queryStringSent.getBytes(EncoderCache.URL_ARGUMENT_ENCODING)); + } + } + + private String getHeadersSent(byte[] responseData) throws IOException { + // Find the data sent to the mirror server, which the mirror server is sending back to us + // We assume the headers are in ISO_8859_1, and the body can be in any content encoding. + String dataSentToMirrorServer = new String(responseData, ISO_8859_1); + int posDividerHeadersAndBody = getPositionOfBody(dataSentToMirrorServer); + String headersSent = null; + if(posDividerHeadersAndBody >= 0) { + headersSent = dataSentToMirrorServer.substring(0, posDividerHeadersAndBody); + } + return headersSent; + } + + private byte[] getBodySent(byte[] responseData) throws IOException { + // Find the data sent to the mirror server, which the mirror server is sending back to us + // We assume the headers are in ISO_8859_1, and the body can be in any content encoding. + // Therefore we get the data sent in ISO_8859_1, to be able to determine the end of the + // header part, and then we just construct a byte array to hold the body part, not taking + // encoding of the body into consideration, because it can contain file data, which is + // sent as raw byte data + byte[] bodySent = null; + String headersSent = getHeadersSent(responseData); + if(headersSent != null) { + // Get the content length, it tells us how much data to read + // TODO : Maybe support chunked encoding, then we cannot rely on content length + String contentLengthValue = getSentRequestHeaderValue(headersSent, HTTPSamplerBase.HEADER_CONTENT_LENGTH); + int contentLength = -1; + if(contentLengthValue != null) { + contentLength = new Integer(contentLengthValue).intValue(); + } + else { + fail("Did not receive any content-length header"); + } + bodySent = new byte[contentLength]; + System.arraycopy(responseData, responseData.length - contentLength, bodySent, 0, contentLength); + } + return bodySent; + } + + private boolean isInRequestHeaders(String requestHeaders, String headerName, String headerValue) { + return checkRegularExpression(requestHeaders, headerName + ": " + headerValue); + } + + // Java 1.6.0_22+ no longer allows Content-Length to be set, so don't check it. + // See: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6996110 + // TODO any point in checking the other headers? + private void checkHeaderTypeLength(String requestHeaders, String contentType, int contentLen) { + boolean typeOK = isInRequestHeaders(requestHeaders, HTTPSamplerBase.HEADER_CONTENT_TYPE, contentType); +// boolean lengOK = isInRequestHeaders(requestHeaders, HTTPSamplerBase.HEADER_CONTENT_LENGTH, Integer.toString(contentLen)); + if (!typeOK){ + fail("Expected type:" + contentType + " in:\n"+ requestHeaders); + } +// if (!lengOK){ +// fail("Expected & length: " +contentLen + " in:\n"+requestHeaders); +// } + } + + private String getSentRequestHeaderValue(String requestHeaders, String headerName) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + String expression = ".*" + headerName + ": (\\d*).*"; + Pattern pattern = JMeterUtils.getPattern(expression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.SINGLELINE_MASK); + if(localMatcher.matches(requestHeaders, pattern)) { + // The value is in the first group, group 0 is the whole match + return localMatcher.getMatch().group(1); + } + return null; + } + + private boolean checkRegularExpression(String stringToCheck, String regularExpression) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + Pattern pattern = JMeterUtils.getPattern(regularExpression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.SINGLELINE_MASK); + return localMatcher.contains(stringToCheck, pattern); + } + + private int getPositionOfBody(String stringToCheck) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + // The headers and body are divided by a blank line + String regularExpression = "^.$"; + Pattern pattern = JMeterUtils.getPattern(regularExpression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.MULTILINE_MASK); + + PatternMatcherInput input = new PatternMatcherInput(stringToCheck); + while(localMatcher.contains(input, pattern)) { + MatchResult match = localMatcher.getMatch(); + return match.beginOffset(0); + } + // No divider was found + return -1; + } + + private String getBoundaryStringFromContentType(String requestHeaders) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + String regularExpression = "^" + HTTPSamplerBase.HEADER_CONTENT_TYPE + ": multipart/form-data; boundary=(.+)$"; + Pattern pattern = JMeterUtils.getPattern(regularExpression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.MULTILINE_MASK); + if(localMatcher.contains(requestHeaders, pattern)) { + MatchResult match = localMatcher.getMatch(); + String matchString = match.group(1); + // Header may contain ;charset= , regexp extracts it so computed boundary is wrong + int indexOf = matchString.indexOf(";"); + if(indexOf>=0) { + return matchString.substring(0, indexOf); + } else { + return matchString; + } + } + else { + return null; + } + } + + private void setupUrl(HTTPSamplerBase sampler, String contentEncoding) { + String protocol = "http"; + // String domain = "localhost"; + String domain = "localhost"; + String path = "/test/somescript.jsp"; + sampler.setProtocol(protocol); + sampler.setMethod(HTTPSamplerBase.POST); + sampler.setPath(path); + sampler.setDomain(domain); + sampler.setPort(MIRROR_PORT); + sampler.setContentEncoding(contentEncoding); + } + + /** + * Setup the form data with specified values + * + * @param httpSampler + */ + private void setupFormData(HTTPSamplerBase httpSampler, boolean isEncoded, String titleField, String titleValue, String descriptionField, String descriptionValue) { + if(isEncoded) { + httpSampler.addEncodedArgument(titleField, titleValue); + httpSampler.addEncodedArgument(descriptionField, descriptionValue); + } + else { + httpSampler.addArgument(titleField, titleValue); + httpSampler.addArgument(descriptionField, descriptionValue); + } + } + + /** + * Setup the form data with specified values, and file to upload + * + * @param httpSampler + */ + private void setupFileUploadData( + HTTPSamplerBase httpSampler, + boolean isEncoded, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + String fileField, + File fileValue, + String fileMimeType) { + // Set the form data + setupFormData(httpSampler, isEncoded, titleField, titleValue, descriptionField, descriptionValue); + // Set the file upload data + HTTPFileArg[] hfa = {new HTTPFileArg(fileValue == null ? "" : fileValue.getAbsolutePath(), fileField, fileMimeType)}; + httpSampler.setHTTPFiles(hfa); + + } + + /** + * Check that the the two byte arrays have identical content + * + * @param expected + * @param actual + * @throws UnsupportedEncodingException + */ + private void checkArraysHaveSameContent(byte[] expected, byte[] actual) throws UnsupportedEncodingException { + if(expected != null && actual != null) { + if(expected.length != actual.length) { + System.out.println(">>>>>>>>>>>>>>>>>>>>"); + System.out.println(new String(expected,"UTF-8")); + System.out.println("===================="); + System.out.println(new String(actual,"UTF-8")); + System.out.println("<<<<<<<<<<<<<<<<<<<<"); + fail("arrays have different length, expected is " + expected.length + ", actual is " + actual.length); + } + else { + for(int i = 0; i < expected.length; i++) { + if(expected[i] != actual[i]) { + System.out.println(">>>>>>>>>>>>>>>>>>>>"); + System.out.println(new String(expected,0,i+1,"UTF-8")); + System.out.println("===================="); + System.out.println(new String(actual,0,i+1,"UTF-8")); + System.out.println("<<<<<<<<<<<<<<<<<<<<"); +/* + // Useful to when debugging + for(int j = 0; j < expected.length; j++) { + System.out.print(expected[j] + " "); + } + System.out.println(); + for(int j = 0; j < actual.length; j++) { + System.out.print(actual[j] + " "); + } + System.out.println(); +*/ + fail("byte at position " + i + " is different, expected is " + expected[i] + ", actual is " + actual[i]); + } + } + } + } + else { + fail("expected or actual byte arrays were null"); + } + } + + /** + * Create the expected output multipart/form-data, with only form data, + * and no file multipart. + * This method is copied from the PostWriterTest class + * + * @param lastMultipart true if this is the last multipart in the request + */ + private byte[] createExpectedFormdataOutput( + String boundaryString, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + boolean firstMultipart, + boolean lastMultipart) throws IOException { + // The encoding used for http headers and control information + final byte[] DASH_DASH = "--".getBytes(ISO_8859_1); + + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + if(firstMultipart) { + output.write(DASH_DASH); + output.write(boundaryString.getBytes(ISO_8859_1)); + output.write(CRLF); + } + output.write("Content-Disposition: form-data; name=\"".getBytes(ISO_8859_1)); + output.write(titleField.getBytes(ISO_8859_1)); + output.write("\"".getBytes(ISO_8859_1)); + output.write(CRLF); + output.write("Content-Type: text/plain".getBytes(ISO_8859_1)); + if(contentEncoding != null) { + output.write("; charset=".getBytes(ISO_8859_1)); + output.write(contentEncoding.getBytes(ISO_8859_1)); + } + output.write(CRLF); + output.write("Content-Transfer-Encoding: 8bit".getBytes(ISO_8859_1)); + output.write(CRLF); + output.write(CRLF); + if(contentEncoding != null) { + output.write(titleValue.getBytes(contentEncoding)); + } + else { + output.write(titleValue.getBytes()); // TODO - charset? + } + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(ISO_8859_1)); + output.write(CRLF); + output.write("Content-Disposition: form-data; name=\"".getBytes(ISO_8859_1)); + output.write(descriptionField.getBytes(ISO_8859_1)); + output.write("\"".getBytes(ISO_8859_1)); + output.write(CRLF); + output.write("Content-Type: text/plain".getBytes(ISO_8859_1)); + if(contentEncoding != null) { + output.write("; charset=".getBytes(ISO_8859_1)); + output.write(contentEncoding.getBytes(ISO_8859_1)); + } + output.write(CRLF); + output.write("Content-Transfer-Encoding: 8bit".getBytes(ISO_8859_1)); + output.write(CRLF); + output.write(CRLF); + if(contentEncoding != null) { + output.write(descriptionValue.getBytes(contentEncoding)); + } + else { + output.write(descriptionValue.getBytes()); // TODO - charset? + } + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(ISO_8859_1)); + if(lastMultipart) { + output.write(DASH_DASH); + } + output.write(CRLF); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + /** + * Create the expected file multipart + * + * @param lastMultipart true if this is the last multipart in the request + */ + private byte[] createExpectedFilepartOutput( + String boundaryString, + String fileField, + File file, + String mimeType, + byte[] fileContent, + boolean firstMultipart, + boolean lastMultipart) throws IOException { + final byte[] DASH_DASH = "--".getBytes(ISO_8859_1); + + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + if(firstMultipart) { + output.write(DASH_DASH); + output.write(boundaryString.getBytes(ISO_8859_1)); + output.write(CRLF); + } + // replace all backslash with double backslash + String filename = file.getName(); + output.write("Content-Disposition: form-data; name=\"".getBytes(ISO_8859_1)); + output.write(fileField.getBytes(ISO_8859_1)); + output.write(("\"; filename=\"" + filename + "\"").getBytes(ISO_8859_1)); + output.write(CRLF); + output.write("Content-Type: ".getBytes(ISO_8859_1)); + output.write(mimeType.getBytes(ISO_8859_1)); + output.write(CRLF); + output.write("Content-Transfer-Encoding: binary".getBytes(ISO_8859_1)); + output.write(CRLF); + output.write(CRLF); + output.write(fileContent); + output.write(CRLF); + output.write(DASH_DASH); + output.write(boundaryString.getBytes(ISO_8859_1)); + if(lastMultipart) { + output.write(DASH_DASH); + } + output.write(CRLF); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + /** + * Create the expected output post body for form data and file multiparts + * with specified values, when request is multipart + */ + private byte[] createExpectedFormAndUploadOutput( + String boundaryString, + String contentEncoding, + String titleField, + String titleValue, + String descriptionField, + String descriptionValue, + String fileField, + File fileValue, + String fileMimeType, + byte[] fileContent) throws IOException { + // Create the multiparts + byte[] formdataMultipart = createExpectedFormdataOutput(boundaryString, contentEncoding, titleField, titleValue, descriptionField, descriptionValue, true, false); + byte[] fileMultipart = createExpectedFilepartOutput(boundaryString, fileField, fileValue, fileMimeType, fileContent, false, true); + + // Join the two multiparts + ByteArrayOutputStream output = new ByteArrayOutputStream(); + output.write(formdataMultipart); + output.write(fileMultipart); + + output.flush(); + output.close(); + + return output.toByteArray(); + } + + private HTTPSamplerBase createHttpSampler(int samplerType) { + switch(samplerType) { + case HTTP_SAMPLER: + return new HTTPSampler(); + case HTTP_SAMPLER2: + return new HTTPSampler2(); + case HTTP_SAMPLER3: + return new HTTPSampler3(); + } + throw new IllegalArgumentException("Unexpected type: "+samplerType); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPArgument.java b/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPArgument.java new file mode 100644 index 0000000..169aab6 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPArgument.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.testelement.property.CollectionProperty; + +public class TestHTTPArgument extends TestCase { + public TestHTTPArgument(String name) { + super(name); + } + + public void testCloning() throws Exception { + HTTPArgument arg = new HTTPArgument("name.?", "value_ here"); + assertEquals("name.?", arg.getName()); + assertEquals("value_ here", arg.getValue()); + assertEquals("name.%3F", arg.getEncodedName()); + assertEquals("value_+here", arg.getEncodedValue()); + HTTPArgument clone = (HTTPArgument) arg.clone(); + assertEquals("name.%3F", clone.getEncodedName()); + assertEquals("value_+here", clone.getEncodedValue()); + assertEquals("name.?", clone.getName()); + assertEquals("value_ here", clone.getValue()); + } + + public void testConversion() throws Exception { + Arguments args = new Arguments(); + args.addArgument("name.?", "value_ here"); + args.addArgument("name$of property", "value_.+"); + HTTPArgument.convertArgumentsToHTTP(args); + CollectionProperty argList = args.getArguments(); + HTTPArgument httpArg = (HTTPArgument) argList.get(0).getObjectValue(); + assertEquals("name.%3F", httpArg.getEncodedName()); + assertEquals("value_+here", httpArg.getEncodedValue()); + httpArg = (HTTPArgument) argList.get(1).getObjectValue(); + assertEquals("name%24of+property", httpArg.getEncodedName()); + assertEquals("value_.%2B", httpArg.getEncodedValue()); + } + + public void testEncoding() throws Exception { + HTTPArgument arg; + arg = new HTTPArgument("name.?", "value_ here", false); + assertEquals("name.?", arg.getName()); + assertEquals("value_ here", arg.getValue()); + assertEquals("name.%3F", arg.getEncodedName()); + assertEquals("value_+here", arg.getEncodedValue()); + // Show that can bypass encoding: + arg.setAlwaysEncoded(false); + assertEquals("name.?", arg.getEncodedName()); + assertEquals("value_ here", arg.getEncodedValue()); + + // The sample does not use a valid encoding + arg = new HTTPArgument("name.?", "value_ here", true); + assertEquals("name.?", arg.getName()); + assertEquals("value_ here", arg.getValue()); + assertEquals("name.%3F", arg.getEncodedName()); + assertEquals("value_+here", arg.getEncodedValue()); + arg.setAlwaysEncoded(false); // by default, name/value are encoded on fetch + assertEquals("name.?", arg.getEncodedName()); + assertEquals("value_ here", arg.getEncodedValue()); + + // Try a real encoded argument + arg = new HTTPArgument("name.%3F", "value_+here", true); + assertEquals("name.?", arg.getName()); + assertEquals("value_ here", arg.getValue()); + assertEquals("name.%3F", arg.getEncodedName()); + assertEquals("value_+here", arg.getEncodedValue()); + // Show that can bypass encoding: + arg.setAlwaysEncoded(false); + assertEquals("name.?", arg.getEncodedName()); + assertEquals("value_ here", arg.getEncodedValue()); + + arg = new HTTPArgument("", "\00\01\07", "", false); + arg.setAlwaysEncoded(false); + assertEquals("", arg.getEncodedName()); + assertEquals("\00\01\07", arg.getEncodedValue()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPFileArg.java b/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPFileArg.java new file mode 100644 index 0000000..64ed4a1 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPFileArg.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import junit.framework.TestCase; + +public class TestHTTPFileArg extends TestCase { + public TestHTTPFileArg(String name) { + super(name); + } + + public void testConstructors() throws Exception { + HTTPFileArg file = new HTTPFileArg(); + assertEquals("no parameter failure", "", file.getPath()); + assertEquals("no parameter failure", "", file.getParamName()); + assertEquals("no parameter failure", "", file.getMimeType()); + file = new HTTPFileArg("path"); + assertEquals("single parameter failure", "path", file.getPath()); + assertEquals("single parameter failure", "", file.getParamName()); + assertEquals("single parameter failure", "", file.getMimeType()); + file = new HTTPFileArg("path", "param", "mimetype"); + assertEquals("three parameter failure", "path", file.getPath()); + assertEquals("three parameter failure", "param", file.getParamName()); + assertEquals("three parameter failure", "mimetype", file.getMimeType()); + HTTPFileArg file2 = new HTTPFileArg(file); + assertEquals("copy constructor failure", "path", file2.getPath()); + assertEquals("copy constructor failure", "param", file2.getParamName()); + assertEquals("copy constructor failure", "mimetype", file2.getMimeType()); + } + + public void testGettersSetters() throws Exception { + HTTPFileArg file = new HTTPFileArg(); + assertEquals("", file.getPath()); + assertEquals("", file.getParamName()); + assertEquals("", file.getMimeType()); + file.setPath("path"); + file.setParamName("param"); + file.setMimeType("mimetype"); + file.setHeader("header"); + assertEquals("path", file.getPath()); + assertEquals("param", file.getParamName()); + assertEquals("mimetype", file.getMimeType()); + assertEquals("header", file.getHeader()); + } + + public void testToString() throws Exception { + HTTPFileArg file = new HTTPFileArg("path1", "param1", "mimetype1"); + assertEquals("path:'path1'|param:'param1'|mimetype:'mimetype1'", file.toString()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPFileArgs.java b/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPFileArgs.java new file mode 100644 index 0000000..a1acb7e --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPFileArgs.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.util.List; +import java.util.LinkedList; +import junit.framework.TestCase; + +import org.apache.jmeter.testelement.property.PropertyIterator; + +public class TestHTTPFileArgs extends TestCase { + public TestHTTPFileArgs(String name) { + super(name); + } + + public void testConstructors() throws Exception { + HTTPFileArgs files = new HTTPFileArgs(); + assertEquals(0, files.getHTTPFileArgCount()); + } + + public void testAdding() throws Exception { + HTTPFileArgs files = new HTTPFileArgs(); + assertEquals(0, files.getHTTPFileArgCount()); + files.addHTTPFileArg("hede"); + assertEquals(1, files.getHTTPFileArgCount()); + assertEquals("hede", ((HTTPFileArg) files.iterator().next().getObjectValue()).getPath()); + HTTPFileArg file = new HTTPFileArg("hodo"); + files.addHTTPFileArg(file); + assertEquals(2, files.getHTTPFileArgCount()); + PropertyIterator iter = files.iterator(); + assertEquals("hede", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("hodo", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + files.addEmptyHTTPFileArg(); + assertEquals(3, files.getHTTPFileArgCount()); + iter = files.iterator(); + assertEquals("hede", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("hodo", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + } + + public void testSetHTTPFileArgs() throws Exception { + List newHTTPFileArgs = new LinkedList(); + newHTTPFileArgs.add(new HTTPFileArg("hede")); + HTTPFileArgs files = new HTTPFileArgs(); + files.setHTTPFileArgs(newHTTPFileArgs); + assertEquals(1, files.getHTTPFileArgCount()); + assertEquals("hede", ((HTTPFileArg) files.iterator().next().getObjectValue()).getPath()); + } + + public void testRemoving() throws Exception { + HTTPFileArgs files = new HTTPFileArgs(); + assertEquals(0, files.getHTTPFileArgCount()); + files.addHTTPFileArg("hede"); + assertEquals(1, files.getHTTPFileArgCount()); + files.clear(); + assertEquals(0, files.getHTTPFileArgCount()); + files.addHTTPFileArg("file1"); + files.addHTTPFileArg("file2"); + files.addHTTPFileArg("file3"); + HTTPFileArg file = new HTTPFileArg("file4"); + files.addHTTPFileArg(file); + files.addHTTPFileArg("file5"); + files.addHTTPFileArg("file6"); + assertEquals(6, files.getHTTPFileArgCount()); + files.removeHTTPFileArg("file3"); + assertEquals(5, files.getHTTPFileArgCount()); + PropertyIterator iter = files.iterator(); + assertEquals("file1", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file2", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file4", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file5", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file6", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + files.removeHTTPFileArg(file); + assertEquals(4, files.getHTTPFileArgCount()); + iter = files.iterator(); + assertEquals("file1", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file2", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file5", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file6", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + files.removeHTTPFileArg(new HTTPFileArg("file5")); + assertEquals(3, files.getHTTPFileArgCount()); + iter = files.iterator(); + assertEquals("file1", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file2", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file6", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + files.removeHTTPFileArg(1); + assertEquals(2, files.getHTTPFileArgCount()); + iter = files.iterator(); + assertEquals("file1", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + assertEquals("file6", ((HTTPFileArg) iter.next().getObjectValue()).getPath()); + files.removeAllHTTPFileArgs(); + assertEquals(0, files.getHTTPFileArgCount()); + } + + public void testToString() throws Exception { + HTTPFileArgs files = new HTTPFileArgs(); + files.addHTTPFileArg("file1"); + files.addHTTPFileArg("file2"); + files.addHTTPFileArg("file3"); + assertEquals("path:'file1'|param:''|mimetype:''\n" + +"path:'file2'|param:''|mimetype:''\n" + +"path:'file3'|param:''|mimetype:''", + files.toString()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPUtils.java b/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPUtils.java new file mode 100644 index 0000000..328ce13 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/util/TestHTTPUtils.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.net.URL; + +import junit.framework.TestCase; + +public class TestHTTPUtils extends TestCase { + public TestHTTPUtils(String name) { + super(name); + } + + public void testgetEncoding() throws Exception { + assertNull(ConversionUtils.getEncodingFromContentType("xyx")); + assertEquals("utf8",ConversionUtils.getEncodingFromContentType("charset=utf8")); + assertEquals("utf8",ConversionUtils.getEncodingFromContentType("charset=\"utf8\"")); + assertEquals("utf8",ConversionUtils.getEncodingFromContentType("text/plain ;charset=utf8")); + assertEquals("utf8",ConversionUtils.getEncodingFromContentType("text/html ;charset=utf8;charset=def")); + assertNull(ConversionUtils.getEncodingFromContentType("charset=")); + assertNull(ConversionUtils.getEncodingFromContentType(";charset=;")); + assertNull(ConversionUtils.getEncodingFromContentType(";charset=no-such-charset;")); + } + + public void testMakeRelativeURL() throws Exception { + URL base = new URL("http://192.168.0.1/a/b/c"); // Trailing file + assertEquals(new URL("http://192.168.0.1/a/b/d"),ConversionUtils.makeRelativeURL(base,"d")); + assertEquals(new URL("http://192.168.0.1/a/d"),ConversionUtils.makeRelativeURL(base,"../d")); + assertEquals(new URL("http://192.168.0.1/d"),ConversionUtils.makeRelativeURL(base,"../../d")); + assertEquals(new URL("http://192.168.0.1/d"),ConversionUtils.makeRelativeURL(base,"../../../d")); + assertEquals(new URL("http://192.168.0.1/d"),ConversionUtils.makeRelativeURL(base,"../../../../d")); + assertEquals(new URL("http://192.168.0.1/../d"),ConversionUtils.makeRelativeURL(base,"/../d")); + assertEquals(new URL("http://192.168.0.1/a/b/d"),ConversionUtils.makeRelativeURL(base,"./d")); + } + + public void testMakeRelativeURL2() throws Exception { + URL base = new URL("http://192.168.0.1/a/b/c/"); // Trailing directory + assertEquals(new URL("http://192.168.0.1/a/b/c/d"),ConversionUtils.makeRelativeURL(base,"d")); + assertEquals(new URL("http://192.168.0.1/a/b/d"),ConversionUtils.makeRelativeURL(base,"../d")); + assertEquals(new URL("http://192.168.0.1/a/d"),ConversionUtils.makeRelativeURL(base,"../../d")); + assertEquals(new URL("http://192.168.0.1/d"),ConversionUtils.makeRelativeURL(base,"../../../d")); + assertEquals(new URL("http://192.168.0.1/d"),ConversionUtils.makeRelativeURL(base,"../../../../d")); + assertEquals(new URL("http://192.168.0.1/../d"),ConversionUtils.makeRelativeURL(base,"/../d")); + assertEquals(new URL("http://192.168.0.1/a/b/c/d"),ConversionUtils.makeRelativeURL(base,"./d")); + } + + public void testRemoveSlashDotDot() + { + assertEquals("/path/", ConversionUtils.removeSlashDotDot("/path/")); + assertEquals("http://host/", ConversionUtils.removeSlashDotDot("http://host/")); + assertEquals("http://host/one", ConversionUtils.removeSlashDotDot("http://host/one")); + assertEquals("/two", ConversionUtils.removeSlashDotDot("/one/../two")); + assertEquals("http://host:8080/two", ConversionUtils.removeSlashDotDot("http://host:8080/one/../two")); + assertEquals("http://host:8080/two/", ConversionUtils.removeSlashDotDot("http://host:8080/one/../two/")); + assertEquals("http://usr@host:8080/two/", ConversionUtils.removeSlashDotDot("http://usr@host:8080/one/../two/")); + assertEquals("http://host:8080/two/?query#anchor", ConversionUtils.removeSlashDotDot("http://host:8080/one/../two/?query#anchor")); + assertEquals("one", ConversionUtils.removeSlashDotDot("one/two/..")); + assertEquals("../../path", ConversionUtils.removeSlashDotDot("../../path")); + assertEquals("/", ConversionUtils.removeSlashDotDot("/one/..")); + assertEquals("/", ConversionUtils.removeSlashDotDot("/one/../")); + assertEquals("/?a", ConversionUtils.removeSlashDotDot("/one/..?a")); + assertEquals("http://host/one", ConversionUtils.removeSlashDotDot("http://host/one/../one")); + assertEquals("http://host/one/two", ConversionUtils.removeSlashDotDot("http://host/one/two/../../one/two")); + assertEquals("http://host/..", ConversionUtils.removeSlashDotDot("http://host/..")); + assertEquals("http://host/../abc", ConversionUtils.removeSlashDotDot("http://host/../abc")); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/util/accesslog/TestLogFilter.java b/ApacheJmeter/org/apache/jmeter/protocol/http/util/accesslog/TestLogFilter.java new file mode 100644 index 0000000..9d281e4 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/util/accesslog/TestLogFilter.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util.accesslog; + +import org.apache.jmeter.junit.JMeterTestCase; + +public class TestLogFilter extends JMeterTestCase { + + private static final String TESTSTR = "/test/helloworld.html"; + + private static final String TESTSTROUT = "/test/helloworld.jsp"; + + private static class TestData { + private final String file; + + private final boolean exclfile; + + private final boolean inclfile; + + private final boolean exclpatt; + + private final boolean inclpatt; + + TestData(String f, boolean exf, boolean inf, boolean exp, boolean inp) { + file = f; + exclfile = exf; + inclfile = inf; + exclpatt = exp; + inclpatt = inp; + } + } + + private static final String[] INCL = { "hello.html", "index.html", "/index.jsp" }; + + private static final String[] PATTERNS = { "index", ".jtml" }; + + private static final TestData[] TESTDATA = { + // file exclf inclf exclp inclp + new TestData("/test/hello.jsp", true, false, true, false), + new TestData("/test/one/hello.html", false, true, true, false), + new TestData("hello.jsp", true, false, true, false), + new TestData("hello.htm", true, false, true, false), + new TestData("/test/open.jsp", true, false, true, false), + new TestData("/test/open.html", true, false, true, false), + new TestData("/index.jsp", false, true, false, true), + new TestData("/index.jhtml", true, false, false, true), + new TestData("newindex.jsp", true, false, false, true), + new TestData("oldindex.jsp", true, false, false, true), + new TestData("oldindex1.jsp", true, false, false, true), + new TestData("oldindex2.jsp", true, false, false, true), + new TestData("oldindex3.jsp", true, false, false, true), + new TestData("oldindex4.jsp", true, false, false, true), + new TestData("oldindex5.jsp", true, false, false, true), + new TestData("oldindex6.jsp", true, false, false, true), + new TestData("/test/index.htm", true, false, false, true) }; + + public void testConstruct() { + new LogFilter(); + } + + private LogFilter testf; + + @Override + public void setUp() { + testf = new LogFilter(); + } + + public void testReplaceExtension() { + testf.setReplaceExtension("html", "jsp"); + testf.isFiltered(TESTSTR,null);// set the required variables + assertEquals(TESTSTROUT, testf.filter(TESTSTR)); + } + + public void testExcludeFiles() { + testf.excludeFiles(INCL); + for (int idx = 0; idx < TESTDATA.length; idx++) { + TestData td = TESTDATA[idx]; + String theFile = td.file; + boolean expect = td.exclfile; + + testf.isFiltered(theFile,null); + String line = testf.filter(theFile); + if (line != null) { + assertTrue("Expect to accept " + theFile, expect); + } else { + assertFalse("Expect to reject " + theFile, expect); + } + } + } + + public void testIncludeFiles() { + testf.includeFiles(INCL); + for (int idx = 0; idx < TESTDATA.length; idx++) { + TestData td = TESTDATA[idx]; + String theFile = td.file; + boolean expect = td.inclfile; + + testf.isFiltered(theFile,null); + String line = testf.filter(theFile); + if (line != null) { + assertTrue("Expect to accept " + theFile, expect); + } else { + assertFalse("Expect to reject " + theFile, expect); + } + } + + } + + public void testExcludePattern() { + testf.excludePattern(PATTERNS); + for (int idx = 0; idx < TESTDATA.length; idx++) { + TestData td = TESTDATA[idx]; + String theFile = td.file; + boolean expect = td.exclpatt; + + assertEquals(!expect, testf.isFiltered(theFile,null)); + String line = testf.filter(theFile); + if (line != null) { + assertTrue("Expect to accept " + theFile, expect); + } else { + assertFalse("Expect to reject " + theFile, expect); + } + } + } + + public void testIncludePattern() { + testf.includePattern(PATTERNS); + for (int idx = 0; idx < TESTDATA.length; idx++) { + TestData td = TESTDATA[idx]; + String theFile = td.file; + boolean expect = td.inclpatt; + + assertEquals(!expect, testf.isFiltered(theFile,null)); + String line = testf.filter(theFile); + if (line != null) { + assertTrue("Expect to accept " + theFile, expect); + } else { + assertFalse("Expect to reject " + theFile, expect); + } + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/http/util/accesslog/TestTCLogParser.java b/ApacheJmeter/org/apache/jmeter/protocol/http/util/accesslog/TestTCLogParser.java new file mode 100644 index 0000000..3d81894 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/http/util/accesslog/TestTCLogParser.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util.accesslog; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.sampler.HTTPNullSampler; + +// TODO - more tests needed + +public class TestTCLogParser extends JMeterTestCase { + private static final TCLogParser tclp = new TCLogParser(); + + private static final String URL1 = "127.0.0.1 - - [08/Jan/2003:07:03:54 -0500] \"GET /addrbook/ HTTP/1.1\" 200 1981"; + + private static final String URL2 = "127.0.0.1 - - [08/Jan/2003:07:03:54 -0500] \"GET /addrbook?x=y HTTP/1.1\" 200 1981"; + + private static final String TEST3 = "127.0.0.1 - - [08/Jan/2003:07:03:54 -0500] \"HEAD /addrbook/ HTTP/1.1\" 200 1981"; + + public void testConstruct() throws Exception { + TCLogParser tcp; + tcp = new TCLogParser(); + assertNull("Should not have set the filename", tcp.FILENAME); + + String file = "testfiles/access.log"; + tcp = new TCLogParser(file); + assertEquals("Filename should have been saved", file, tcp.FILENAME); + } + + public void testcleanURL() throws Exception { + String res = tclp.cleanURL(URL1); + assertEquals("/addrbook/", res); + assertNull(tclp.stripFile(res, new HTTPNullSampler())); + } + + public void testcheckURL() throws Exception { + assertFalse("URL does not have a query", tclp.checkURL(URL1)); + assertTrue("URL is a query", tclp.checkURL(URL2)); + } + + public void testHEAD() throws Exception { + String res = tclp.cleanURL(TEST3); + assertEquals("/addrbook/", res); + assertNull(tclp.stripFile(res, new HTTPNullSampler())); + } + +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/protocol/ldap/config/gui/PackageTest.java b/ApacheJmeter/org/apache/jmeter/protocol/ldap/config/gui/PackageTest.java new file mode 100644 index 0000000..78401ac --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/ldap/config/gui/PackageTest.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.jmeter.protocol.ldap.config.gui; + +import junit.framework.TestCase; + +public class PackageTest extends TestCase { + /** + * Create a new test. + * + * @param name + * the name of the test + */ + public PackageTest(String name) { + super(name); + } + + /** + * Test that adding an argument to the table results in an appropriate + * TestElement being created. + * + * @throws Exception + * if an exception occurred during the test + */ + public void testLDAPArgumentCreation() throws Exception { + LDAPArgumentsPanel gui = new LDAPArgumentsPanel(); + gui.tableModel.addRow(new LDAPArgument()); + gui.tableModel.setValueAt("howdy", 0, 0); + gui.tableModel.addRow(new LDAPArgument()); + gui.tableModel.setValueAt("doody", 0, 1); + + assertEquals("=", ((LDAPArgument) ((LDAPArguments) gui.createTestElement()).getArguments().get(0) + .getObjectValue()).getMetaData()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImplTest.java b/ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImplTest.java new file mode 100644 index 0000000..a5b01f0 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImplTest.java @@ -0,0 +1,86 @@ +/* + * 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. + * + */ + +/* + * Test class for BinaryTCPClientImpl utility methods. + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.apache.jorphan.util.JOrphanUtils; + +public class BinaryTCPClientImplTest extends TestCase { + + public void testHexStringToByteArray() throws Exception { + byte [] ba; + ba = BinaryTCPClientImpl.hexStringToByteArray(""); + assertEquals(0, ba.length); + + ba = BinaryTCPClientImpl.hexStringToByteArray("00"); + assertEquals(1, ba.length); + assertEquals(0, ba[0]); + + ba = BinaryTCPClientImpl.hexStringToByteArray("0f107F8081ff"); + assertEquals(6, ba.length); + assertEquals(15, ba[0]); + assertEquals(16, ba[1]); + assertEquals(127, ba[2]); + assertEquals(-128, ba[3]); + assertEquals(-127, ba[4]); + assertEquals(-1, ba[5]); + try { + ba = BinaryTCPClientImpl.hexStringToByteArray("0f107f8081ff1");// odd chars + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + try { + BinaryTCPClientImpl.hexStringToByteArray("0f107xxf8081ff"); // invalid + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + } + + public void testLoopBack() throws Exception { + assertEquals("0f107f8081ff", JOrphanUtils.baToHexString(BinaryTCPClientImpl.hexStringToByteArray("0f107f8081ff"))); + } + + public void testRoundTrip() throws Exception { + BinaryTCPClientImpl bi = new BinaryTCPClientImpl(); + InputStream is = null; + try { + bi.write(null, is); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + // ignored + } + ByteArrayOutputStream os = new ByteArrayOutputStream(); + bi.write(os, "3132333435"); // '12345' + os.close(); + assertEquals("12345",os.toString("ISO-8859-1")); + ByteArrayInputStream bis = new ByteArrayInputStream(os.toByteArray()); + assertEquals("3132333435",bi.read(bis)); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImplTest.java b/ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImplTest.java new file mode 100644 index 0000000..d23ba45 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImplTest.java @@ -0,0 +1,55 @@ +/* + * 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. + * + */ + +/* + * Test class for BinaryTCPClientImpl utility methods. + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +import junit.framework.TestCase; + +public class LengthPrefixedBinaryTCPClientImplTest extends TestCase { + + public void testError() throws Exception { + ByteArrayOutputStream os = null; + ByteArrayInputStream is = null; + LengthPrefixedBinaryTCPClientImpl lp = new LengthPrefixedBinaryTCPClientImpl(); + try { + lp.write(os, is); + fail("Expected java.lang.UnsupportedOperationException"); + } catch (java.lang.UnsupportedOperationException expected) { + } + } + + public void testValid() throws Exception { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LengthPrefixedBinaryTCPClientImpl lp = new LengthPrefixedBinaryTCPClientImpl(); + final String DATA = "31323334353637"; + lp.write(os, DATA); + os.close(); + final byte[] byteArray = os.toByteArray(); + assertEquals(2+(DATA.length()/2), byteArray.length); + ByteArrayInputStream is = new ByteArrayInputStream(byteArray); + assertEquals(DATA, lp.read(is)); + } + +} diff --git a/ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecoratorTest.java b/ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecoratorTest.java new file mode 100644 index 0000000..8734948 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecoratorTest.java @@ -0,0 +1,247 @@ +/* + * 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. + * + */ + +/* + * Test class for TCPClientDecorator utility methods. + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import junit.framework.TestCase; + +public class TCPClientDecoratorTest extends TestCase { + + + public void testIntToByteArray() throws Exception { + byte[] ba; + int len = 2; + ba = TCPClientDecorator.intToByteArray(0, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + + ba = TCPClientDecorator.intToByteArray(15, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(15, ba[1]); + + ba = TCPClientDecorator.intToByteArray(255, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(-1, ba[1]); + + ba = TCPClientDecorator.intToByteArray(256, len); + assertEquals(len, ba.length); + assertEquals(1, ba[0]); + assertEquals(0, ba[1]); + + ba = TCPClientDecorator.intToByteArray(-1, len); + assertEquals(len, ba.length); + assertEquals(-1, ba[0]); + assertEquals(-1, ba[1]); + + ba = TCPClientDecorator.intToByteArray(Short.MAX_VALUE, len); + assertEquals(len, ba.length); + assertEquals(127, ba[0]); + assertEquals(-1, ba[1]); + + ba = TCPClientDecorator.intToByteArray(Short.MIN_VALUE, len); + assertEquals(len, ba.length); + assertEquals(-128, ba[0]); + assertEquals(0, ba[1]); + + try { + ba = TCPClientDecorator.intToByteArray(Short.MIN_VALUE-1, len); + fail(); + } catch (IllegalArgumentException iae) { + } + + try { + ba = TCPClientDecorator.intToByteArray(Short.MAX_VALUE+1, len); + fail(); + } catch (IllegalArgumentException iae) { + } + + len = 4; + ba = TCPClientDecorator.intToByteArray(0, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + assertEquals(0, ba[2]); + assertEquals(0, ba[3]); + + ba = TCPClientDecorator.intToByteArray(15, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + assertEquals(0, ba[2]); + assertEquals(15, ba[3]); + + ba = TCPClientDecorator.intToByteArray(255, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + assertEquals(0, ba[2]); + assertEquals(-1, ba[3]); + + ba = TCPClientDecorator.intToByteArray(-1, len); + assertEquals(len, ba.length); + assertEquals(-1, ba[0]); + assertEquals(-1, ba[1]); + assertEquals(-1, ba[2]); + assertEquals(-1, ba[3]); + + ba = TCPClientDecorator.intToByteArray(256, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + assertEquals(1, ba[2]); + assertEquals(0, ba[3]); + + ba = TCPClientDecorator.intToByteArray(65535, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(0, ba[1]); + assertEquals(-1, ba[2]); + assertEquals(-1, ba[3]); + + ba = TCPClientDecorator.intToByteArray(65536, len); + assertEquals(len, ba.length); + assertEquals(0, ba[0]); + assertEquals(1, ba[1]); + assertEquals(0, ba[2]); + assertEquals(0, ba[3]); + + ba = TCPClientDecorator.intToByteArray(Integer.MIN_VALUE, len); + assertEquals(len, ba.length); + assertEquals(-128, ba[0]); + assertEquals(0, ba[1]); + assertEquals(0, ba[2]); + assertEquals(0, ba[3]); + + ba = TCPClientDecorator.intToByteArray(Integer.MAX_VALUE, len); + assertEquals(len, ba.length); + assertEquals(127, ba[0]); + assertEquals(-1, ba[1]); + assertEquals(-1, ba[2]); + assertEquals(-1, ba[3]); + + // Check illegal array lengths + try { + ba = TCPClientDecorator.intToByteArray(0, 0); + fail(); + } catch (IllegalArgumentException iae) { + } + + try { + ba = TCPClientDecorator.intToByteArray(0, 1); + fail(); + } catch (IllegalArgumentException iae) { + } + + try { + ba = TCPClientDecorator.intToByteArray(0, 3); + fail(); + } catch (IllegalArgumentException iae) { + } + try { + TCPClientDecorator.intToByteArray(0, 5); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + } + } + + public void testByteArrayToInt() throws Exception { + byte[] ba; + + ba = new byte[] { 0, 0 }; + assertEquals(0, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 0, 15 }; + assertEquals(15, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 0, -1 }; + assertEquals(255, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 1, 0 }; + assertEquals(256, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { -1, -1 }; + assertEquals(-1, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 0, 0, -1, -1 }; + assertEquals(65535, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 0, 1, 0, 0 }; + assertEquals(65536, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 0, 0, 0, 0 }; + assertEquals(0, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { -128, 0, 0, 0 }; + assertEquals(Integer.MIN_VALUE, TCPClientDecorator.byteArrayToInt(ba)); + + ba = new byte[] { 127, -1, -1, -1 }; + assertEquals(Integer.MAX_VALUE, TCPClientDecorator.byteArrayToInt(ba)); + + // test invalid byte arrays + try { + TCPClientDecorator.byteArrayToInt(null); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + + try { + TCPClientDecorator.byteArrayToInt(new byte[]{}); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + + try { + TCPClientDecorator.byteArrayToInt(new byte[]{0}); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + + try { + TCPClientDecorator.byteArrayToInt(new byte[]{0,0,0}); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + + try { + TCPClientDecorator.byteArrayToInt(new byte[]{0,0,0}); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected){ + // ignored + } + + } + + + public void testLoopBack() throws Exception { + assertEquals(Short.MIN_VALUE, TCPClientDecorator.byteArrayToInt(TCPClientDecorator.intToByteArray(Short.MIN_VALUE, 2))); + assertEquals(Short.MAX_VALUE, TCPClientDecorator.byteArrayToInt(TCPClientDecorator.intToByteArray(Short.MAX_VALUE, 2))); + assertEquals(Integer.MIN_VALUE, TCPClientDecorator.byteArrayToInt(TCPClientDecorator.intToByteArray(Integer.MIN_VALUE, 4))); + assertEquals(Integer.MAX_VALUE, TCPClientDecorator.byteArrayToInt(TCPClientDecorator.intToByteArray(Integer.MAX_VALUE, 4))); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/resources/PackageTest.java b/ApacheJmeter/org/apache/jmeter/resources/PackageTest.java new file mode 100644 index 0000000..b6341c4 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/resources/PackageTest.java @@ -0,0 +1,387 @@ +/* + * 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.resources; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Properties; +import java.util.PropertyResourceBundle; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import org.apache.jmeter.gui.util.JMeterMenuBar; +import org.apache.jorphan.util.JOrphanUtils; + +/* + * Created on Nov 29, 2003 + * + * Test the composition of the messages*.properties files + * - properties files exist + * - properties files don't have duplicate keys + * - non-default properties files don't have any extra keys. + * + * N.B. If there is a default resource, ResourceBundle does not detect missing + * resources, i.e. the presence of messages.properties means that the + * ResourceBundle for Locale "XYZ" would still be found, and have the same keys + * as the default. This makes it not very useful for checking properties files. + * + * This is why the tests use Class.getResourceAsStream() etc + * + * The tests don't quite follow the normal JUnit test strategy of one test per + * possible failure. This was done in order to make it easier to report exactly + * why the tests failed. + */ + +public class PackageTest extends TestCase { + private static final String basedir = new File(System.getProperty("user.dir")).getParent(); + + private static final File srcFiledir = new File(basedir,"src"); + + private static final String MESSAGES = "messages"; + + private static PropertyResourceBundle defaultPRB; + + private static final CharsetEncoder ASCII_ENCODER = + Charset.forName("US-ASCII").newEncoder(); // Ensure properties files don't use special characters + + private static boolean isPureAscii(String v) { + return ASCII_ENCODER.canEncode(v); + } + + // Read resource into ResourceBundle and store in List + private PropertyResourceBundle getRAS(String res) throws Exception { + InputStream ras = this.getClass().getResourceAsStream(res); + if (ras == null){ + return null; + } + return new PropertyResourceBundle(ras); + } + + private static final Object[] DUMMY_PARAMS = new Object[] { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + // Read resource file saving the keys + private int readRF(String res, List l) throws Exception { + int fails = 0; + InputStream ras = this.getClass().getResourceAsStream(res); + if (ras==null){ + if (MESSAGES.equals(resourcePrefix)|| lang.length() == 0 ){ + throw new IOException("Cannot open resource file "+res); + } else { + return 0; + } + } + BufferedReader fileReader = null; + try { + fileReader = new BufferedReader(new InputStreamReader(ras)); + String s; + while ((s = fileReader.readLine()) != null) { + if (s.length() > 0 && !s.startsWith("#") && !s.startsWith("!")) { + int equ = s.indexOf('='); + String key = s.substring(0, equ); + if (resourcePrefix.equals(MESSAGES)){// Only relevant for messages + /* + * JMeterUtils.getResString() converts space to _ and lowercases + * the key, so make sure all keys pass the test + */ + if ((key.indexOf(' ') >= 0) || !key.toLowerCase(java.util.Locale.ENGLISH).equals(key)) { + System.out.println("Invalid key for JMeterUtils " + key); + fails++; + } + } + String val = s.substring(equ + 1); + l.add(key); // Store the key + /* + * Now check for invalid message format: if string contains {0} + * and ' there may be a problem, so do a format with dummy + * parameters and check if there is a { in the output. A bit + * crude, but should be enough for now. + */ + if (val.indexOf("{0}") > 0 && val.indexOf("'") > 0) { + String m = java.text.MessageFormat.format(val, DUMMY_PARAMS); + if (m.indexOf("{") > 0) { + fails++; + System.out.println("Incorrect message format ? (input/output) for: "+key); + System.out.println(val); + System.out.println(m); + } + } + + if (!isPureAscii(val)) { + fails++; + System.out.println("Incorrect char value in: "+s); + } + } + } + return fails; + } + finally { + JOrphanUtils.closeQuietly(fileReader); + } + } + + // Helper method to construct resource name + private String getResName(String lang) { + if (lang.length() == 0) { + return resourcePrefix+".properties"; + } else { + return resourcePrefix+"_" + lang + ".properties"; + } + } + + private void check(String resname) throws Exception { + check(resname, true);// check that there aren't any extra entries + } + + /* + * perform the checks on the resources + * + */ + private void check(String resname, boolean checkUnexpected) throws Exception { + ArrayList alf = new ArrayList(500);// holds keys from file + String res = getResName(resname); + subTestFailures += readRF(res, alf); + Collections.sort(alf); + + // Look for duplicate keys in the file + String last = ""; + for (int i = 0; i < alf.size(); i++) { + String curr = alf.get(i); + if (curr.equals(last)) { + subTestFailures++; + System.out.println("\nDuplicate key =" + curr + " in " + res); + } + last = curr; + } + + if (resname.length() == 0) // Must be the default resource file + { + defaultPRB = getRAS(res); + if (defaultPRB == null){ + throw new IOException("Could not find required file: "+res); + } + } else if (checkUnexpected) { + // Check all the keys are in the default props file + PropertyResourceBundle prb = getRAS(res); + if (prb == null){ + return; + } + final ArrayList list = Collections.list(prb.getKeys()); + Collections.sort(list); + final boolean mainResourceFile = resname.startsWith("messages"); + for (String key : list) { + try { + String val = defaultPRB.getString(key); // Also Check key is in default + if (mainResourceFile && val.equals(prb.getString(key))){ + System.out.println("Duplicate value? "+key+"="+val+" in "+res); + subTestFailures++; + } + } catch (MissingResourceException e) { + subTestFailures++; + System.out.println(resourcePrefix + "_" + resname + " has unexpected key: " + key); + } + } + } + + if (subTestFailures > 0) { + fail("One or more subtests failed"); + } + } + + private static final String[] prefixList = getResources(srcFiledir); + + /** + * Find I18N resources in classpath + * @param srcFiledir + * @return list of properties files subject to I18N + */ + public static final String[] getResources(File srcFiledir) { + Set set = new TreeSet(); + findFile(srcFiledir, set, new FilenameFilter() { + public boolean accept(File dir, String name) { + return new File(dir, name).isDirectory() + || ( + name.equals("messages.properties") || + (name.endsWith("Resources.properties") + && !name.matches("Example\\d+Resources\\.properties"))); + } + }); + return set.toArray(new String[set.size()]); + } + + /** + * Find resources matching filenamefiler and adds them to set removing everything before "/org" + * @param file + * @param set + * @param filenameFilter + */ + private static void findFile(File file, Set set, + FilenameFilter filenameFilter) { + File[] foundFiles = file.listFiles(filenameFilter); + for (File file2 : foundFiles) { + if(file2.isDirectory()) { + findFile(file2, set, filenameFilter); + } else { + String absPath2 = file2.getAbsolutePath().replace('\\', '/'); // Fix up Windows paths + int indexOfOrg = absPath2.indexOf("/org"); + int lastIndex = absPath2.lastIndexOf("."); + set.add(absPath2.substring(indexOfOrg, lastIndex)); + } + } + + } + + /* + * Use a suite to ensure that the default is done first + */ + public static Test suite() { + TestSuite ts = new TestSuite("Resources PackageTest"); + String languages[] = JMeterMenuBar.getLanguages(); + for(String prefix : prefixList){ + TestSuite pfx = new TestSuite(prefix) ; + pfx.addTest(new PackageTest("testLang","", prefix)); // load the default resource + for(String language : languages){ + if (!"en".equals(language)){ // Don't try to check the default language + pfx.addTest(new PackageTest("testLang", language, prefix)); + } + } + ts.addTest(pfx); + } + ts.addTest(new PackageTest("checkI18n", "fr")); + // TODO Add these some day +// ts.addTest(new PackageTest("checkI18n", "es")); +// ts.addTest(new PackageTest("checkI18n", "pl")); +// ts.addTest(new PackageTest("checkI18n", "pt_BR")); +// ts.addTest(new PackageTest("checkI18n", "tr")); +// ts.addTest(new PackageTest("checkI18n", Locale.JAPANESE.toString())); +// ts.addTest(new PackageTest("checkI18n", Locale.SIMPLIFIED_CHINESE.toString())); +// ts.addTest(new PackageTest("checkI18n", Locale.TRADITIONAL_CHINESE.toString())); + return ts; + } + + + private int subTestFailures; + + private final String lang; + + private final String resourcePrefix; // e.g. "messages" + + public PackageTest(String testName, String _lang) { + this(testName, _lang, MESSAGES); + } + + public PackageTest(String testName, String _lang, String propName) { + super(testName); + lang=_lang; + subTestFailures = 0; + resourcePrefix = propName; + } + + public void testLang() throws Exception{ + check(lang); + } + + /** + * Check all messages are available in one language + * @throws Exception + */ + public void checkI18n() throws Exception { + Map> missingLabelsPerBundle = new HashMap>(); + for (String prefix : prefixList) { + Properties messages = new Properties(); + messages.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(prefix.substring(1)+".properties")); + checkMessagesForLanguage( missingLabelsPerBundle , missingLabelsPerBundle, messages,prefix.substring(1), lang); + } + + assertEquals(missingLabelsPerBundle.size()+" missing labels, labels missing:"+printLabels(missingLabelsPerBundle), 0, missingLabelsPerBundle.size()); + } + + /** + * Check messages are available in language + * @param missingLabelsPerBundle2 + * @param missingLabelsPerBundle + * @param messages Properties messages in english + * @param language Language + * @throws IOException + */ + private void checkMessagesForLanguage(Map> missingLabelsPerBundle, Map> missingLabelsPerBundle2, Properties messages, String bundlePath,String language) + throws IOException { + Properties messagesFr = new Properties(); + String languageBundle = bundlePath+"_"+language+ ".properties"; + InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(languageBundle); + if(inputStream == null) { + Map messagesAsProperties = new HashMap(); + for (Iterator> iterator = messages.entrySet().iterator(); iterator.hasNext();) { + Map.Entry entry = iterator.next(); + messagesAsProperties.put((String) entry.getKey(), (String) entry.getValue()); + } + missingLabelsPerBundle.put(languageBundle, messagesAsProperties); + return; + } + messagesFr.load(inputStream); + + Map missingLabels = new TreeMap(); + for (Iterator> iterator = messages.entrySet().iterator(); iterator.hasNext();) { + Map.Entry entry = iterator.next(); + String key = (String)entry.getKey(); + if(!messagesFr.containsKey(key)) { + missingLabels.put(key,(String) entry.getValue()); + } + } + if(!missingLabels.isEmpty()) { + missingLabelsPerBundle.put(languageBundle, missingLabels); + } + } + + /** + * Build message with misssing labels per bundle + * @param missingLabelsPerBundle + * @return String + */ + private String printLabels(Map> missingLabelsPerBundle) { + StringBuilder builder = new StringBuilder(); + for (Iterator>> iterator = missingLabelsPerBundle.entrySet().iterator(); iterator.hasNext();) { + Map.Entry> entry = iterator.next(); + builder.append("Missing labels in bundle:"+entry.getKey()+"\r\n"); + for (Iterator> it2 = entry.getValue().entrySet().iterator(); it2.hasNext();) { + Map.Entry entry2 = it2.next(); + builder.append(entry2.getKey()+"="+entry2.getValue()+"\r\n"); + } + builder.append("======================================================\r\n"); + } + return builder.toString(); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/samplers/NullSampler.java b/ApacheJmeter/org/apache/jmeter/samplers/NullSampler.java new file mode 100644 index 0000000..49df824 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/samplers/NullSampler.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +/** + * + * Dummy class for testing purposes + * + */ +public class NullSampler extends AbstractSampler { + + private static final long serialVersionUID = 240L; + + public SampleResult sample(Entry e) { + return new SampleResult(); + } + +} diff --git a/ApacheJmeter/org/apache/jmeter/samplers/TestSampleResult.java b/ApacheJmeter/org/apache/jmeter/samplers/TestSampleResult.java new file mode 100644 index 0000000..9bafe6a --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/samplers/TestSampleResult.java @@ -0,0 +1,328 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.io.StringWriter; + +import junit.framework.TestCase; + +import org.apache.jmeter.util.Calculator; +import org.apache.log.LogTarget; +import org.apache.log.format.Formatter; +import org.apache.log.format.RawFormatter; +import org.apache.log.output.io.WriterTarget; + +// TODO need more tests - particularly for the new functions + +public class TestSampleResult extends TestCase { + public TestSampleResult(String name) { + super(name); + } + + public void testElapsedTrue() throws Exception { + SampleResult res = new SampleResult(true); + + // Check sample increments OK + res.sampleStart(); + Thread.sleep(110); // Needs to be greater than the minimum to allow for boundary errors + res.sampleEnd(); + long time = res.getTime(); + if(time < 100){ + fail("Sample time should be >=100, actual "+time); + } + } + + public void testElapsedFalse() throws Exception { + SampleResult res = new SampleResult(false); + + // Check sample increments OK + res.sampleStart(); + Thread.sleep(110); // Needs to be greater than the minimum to allow for boundary errors + res.sampleEnd(); + long time = res.getTime(); + if(time < 100){ + fail("Sample time should be >=100, actual "+time); + } + } + + public void testPauseFalse() throws Exception { + SampleResult res = new SampleResult(false); + // Check sample increments OK + res.sampleStart(); + Thread.sleep(100); + res.samplePause(); + + Thread.sleep(200); + + // Re-increment + res.sampleResume(); + Thread.sleep(100); + res.sampleEnd(); + long sampleTime = res.getTime(); + if ((sampleTime < 180) || (sampleTime > 290)) { + fail("Accumulated time (" + sampleTime + ") was not between 180 and 290 ms"); + } + } + + public void testPauseTrue() throws Exception { + SampleResult res = new SampleResult(true); + // Check sample increments OK + res.sampleStart(); + Thread.sleep(100); + res.samplePause(); + + Thread.sleep(200); + + // Re-increment + res.sampleResume(); + Thread.sleep(100); + res.sampleEnd(); + long sampleTime = res.getTime(); + if ((sampleTime < 180) || (sampleTime > 290)) { + fail("Accumulated time (" + sampleTime + ") was not between 180 and 290 ms"); + } + } + + private static final Formatter fmt = new RawFormatter(); + + private StringWriter wr = null; + + private void divertLog() {// N.B. This needs to divert the log for SampleResult + wr = new StringWriter(1000); + LogTarget[] lt = { new WriterTarget(wr, fmt) }; + SampleResult.log.setLogTargets(lt); + } + + public void testPause2True() throws Exception { + divertLog(); + SampleResult res = new SampleResult(true); + res.sampleStart(); + res.samplePause(); + assertEquals(0, wr.toString().length()); + res.samplePause(); + assertFalse(wr.toString().length() == 0); + } + + public void testPause2False() throws Exception { + divertLog(); + SampleResult res = new SampleResult(false); + res.sampleStart(); + res.samplePause(); + assertEquals(0, wr.toString().length()); + res.samplePause(); + assertFalse(wr.toString().length() == 0); + } + + public void testByteCount() throws Exception { + SampleResult res = new SampleResult(); + + res.sampleStart(); + res.setBytes(100); + res.setSampleLabel("sample of size 100 bytes"); + res.sampleEnd(); + assertEquals(100, res.getBytes()); + assertEquals("sample of size 100 bytes", res.getSampleLabel()); + } + + public void testSubResultsTrue() throws Exception { + testSubResults(true, 0); + } + + public void testSubResultsTrueThread() throws Exception { + testSubResults(true, 500L, 0); + } + + public void testSubResultsFalse() throws Exception { + testSubResults(false, 0); + } + + public void testSubResultsFalseThread() throws Exception { + testSubResults(false, 500L, 0); + } + + public void testSubResultsTruePause() throws Exception { + testSubResults(true, 100); + } + + public void testSubResultsTruePauseThread() throws Exception { + testSubResults(true, 500L, 100); + } + + public void testSubResultsFalsePause() throws Exception { + testSubResults(false, 100); + } + + public void testSubResultsFalsePauseThread() throws Exception { + testSubResults(false, 500L, 100); + } + + // temp test case for exploring settings + public void xtestUntilFail() throws Exception { + while(true) { + testSubResultsTruePause(); + testSubResultsFalsePause(); + } + } + + private void testSubResults(boolean nanoTime, long pause) throws Exception { + testSubResults(nanoTime, 0L, pause); // Don't use nanoThread + } + + private void testSubResults(boolean nanoTime, long nanoThreadSleep, long pause) throws Exception { + // This test tries to emulate a http sample, with two + // subsamples, representing images that are downloaded for the + // page representing the first sample. + + // Sample that will get two sub results, simulates a web page load + SampleResult parent = new SampleResult(nanoTime, nanoThreadSleep); + + assertEquals(nanoTime, parent.useNanoTime); + assertEquals(nanoThreadSleep, parent.nanoThreadSleep); + + long beginTest = parent.currentTimeInMillis(); + + parent.sampleStart(); + Thread.sleep(100); + parent.setBytes(300); + parent.setSampleLabel("Parent Sample"); + parent.setSuccessful(true); + parent.sampleEnd(); + long parentElapsed = parent.getTime(); + + // Sample with no sub results, simulates an image download + SampleResult child1 = new SampleResult(nanoTime); + child1.sampleStart(); + Thread.sleep(100); + child1.setBytes(100); + child1.setSampleLabel("Child1 Sample"); + child1.setSuccessful(true); + child1.sampleEnd(); + long child1Elapsed = child1.getTime(); + + assertTrue(child1.isSuccessful()); + assertEquals(100, child1.getBytes()); + assertEquals("Child1 Sample", child1.getSampleLabel()); + assertEquals(1, child1.getSampleCount()); + assertEquals(0, child1.getSubResults().length); + + long actualPause = 0; + if (pause > 0) { + long t1 = parent.currentTimeInMillis(); + Thread.sleep(pause); + actualPause = parent.currentTimeInMillis() - t1; + } + + // Sample with no sub results, simulates an image download + SampleResult child2 = new SampleResult(nanoTime); + child2.sampleStart(); + Thread.sleep(100); + child2.setBytes(200); + child2.setSampleLabel("Child2 Sample"); + child2.setSuccessful(true); + child2.sampleEnd(); + long child2Elapsed = child2.getTime(); + + assertTrue(child2.isSuccessful()); + assertEquals(200, child2.getBytes()); + assertEquals("Child2 Sample", child2.getSampleLabel()); + assertEquals(1, child2.getSampleCount()); + assertEquals(0, child2.getSubResults().length); + + // Now add the subsamples to the sample + parent.addSubResult(child1); + parent.addSubResult(child2); + assertTrue(parent.isSuccessful()); + assertEquals(600, parent.getBytes()); + assertEquals("Parent Sample", parent.getSampleLabel()); + assertEquals(1, parent.getSampleCount()); + assertEquals(2, parent.getSubResults().length); + long parentElapsedTotal = parent.getTime(); + + long overallTime = parent.currentTimeInMillis() - beginTest; + + long sumSamplesTimes = parentElapsed + child1Elapsed + actualPause + child2Elapsed; + + /* + * Parent elapsed total should be no smaller than the sum of the individual samples. + * It may be greater by the timer granularity. + */ + + long diff = parentElapsedTotal - sumSamplesTimes; + long maxDiff = nanoTime ? 2 : 16; // TimeMillis has granularity of 10-20 + if (diff < 0 || diff > maxDiff) { + fail("ParentElapsed: " + parentElapsedTotal + " - " + " sum(samples): " + sumSamplesTimes + + " = " + diff + " not in [0," + maxDiff + "]; nanotime=" + nanoTime); + } + + /** + * The overall time to run the test must be no less than, + * and may be greater (but not much greater) than the parent elapsed time + */ + + diff = overallTime - parentElapsedTotal; + if (diff < 0 || diff > maxDiff) { + fail("TestElapsed: " + overallTime + " - " + " ParentElapsed: " + parentElapsedTotal + + " = " + diff + " not in [0," + maxDiff + "]; nanotime="+nanoTime); + } + + // Check that calculator gets the correct statistics from the sample + Calculator calculator = new Calculator(); + calculator.addSample(parent); + assertEquals(600, calculator.getTotalBytes()); + assertEquals(1, calculator.getCount()); + assertEquals(1d / (parentElapsedTotal / 1000d), calculator.getRate(),0.0001d); // Allow for some margin of error + // Check that the throughput uses the time elapsed for the sub results + assertFalse(1d / (parentElapsed / 1000d) <= calculator.getRate()); + } + + // TODO some more invalid sequence tests needed + + public void testEncodingAndType() throws Exception { + // check default + SampleResult res = new SampleResult(); + assertEquals(SampleResult.DEFAULT_ENCODING,res.getDataEncodingWithDefault()); + assertEquals("DataType should be blank","",res.getDataType()); + assertNull(res.getDataEncodingNoDefault()); + + // check null changes nothing + res.setEncodingAndType(null); + assertEquals(SampleResult.DEFAULT_ENCODING,res.getDataEncodingWithDefault()); + assertEquals("DataType should be blank","",res.getDataType()); + assertNull(res.getDataEncodingNoDefault()); + + // check no charset + res.setEncodingAndType("text/html"); + assertEquals(SampleResult.DEFAULT_ENCODING,res.getDataEncodingWithDefault()); + assertEquals("text",res.getDataType()); + assertNull(res.getDataEncodingNoDefault()); + + // Check unquoted charset + res.setEncodingAndType("text/html; charset=aBcd"); + assertEquals("aBcd",res.getDataEncodingWithDefault()); + assertEquals("aBcd",res.getDataEncodingNoDefault()); + assertEquals("text",res.getDataType()); + + // Check quoted charset + res.setEncodingAndType("text/html; charset=\"aBCd\""); + assertEquals("aBCd",res.getDataEncodingWithDefault()); + assertEquals("aBCd",res.getDataEncodingNoDefault()); + assertEquals("text",res.getDataType()); + } +} + diff --git a/ApacheJmeter/org/apache/jmeter/samplers/TestSampleSaveConfiguration.java b/ApacheJmeter/org/apache/jmeter/samplers/TestSampleSaveConfiguration.java new file mode 100644 index 0000000..1dcd518 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/samplers/TestSampleSaveConfiguration.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.text.SimpleDateFormat; + +import org.apache.jmeter.junit.JMeterTestCase; + +// Extends JMeterTest case because it needs access to JMeter properties +public class TestSampleSaveConfiguration extends JMeterTestCase { + public TestSampleSaveConfiguration(String name) { + super(name); + } + + public void testClone() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(); + a.setUrl(false); + a.setAssertions(true); + a.setDefaultDelimiter(); + a.setDefaultTimeStampFormat(); + a.setDataType(true); + assertFalse(a.saveUrl()); + assertNotNull(a.getDelimiter()); + assertTrue(a.saveAssertions()); + assertTrue(a.saveDataType()); + + // Original and clone should be equal + SampleSaveConfiguration cloneA = (SampleSaveConfiguration) a.clone(); + assertNotSame(a, cloneA); + assertEquals(a, cloneA); + assertTrue(a.equals(cloneA)); + assertTrue(cloneA.equals(a)); + assertEquals(a.hashCode(), cloneA.hashCode()); + + // Change the original + a.setUrl(true); + assertFalse(a.equals(cloneA)); + assertFalse(cloneA.equals(a)); + assertFalse(a.hashCode() == cloneA.hashCode()); + + // Change the original back again + a.setUrl(false); + assertEquals(a, cloneA); + assertTrue(a.equals(cloneA)); + assertTrue(cloneA.equals(a)); + assertEquals(a.hashCode(), cloneA.hashCode()); + } + + public void testEqualsAndHashCode() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(); + a.setUrl(false); + a.setAssertions(true); + a.setDefaultDelimiter(); + a.setDefaultTimeStampFormat(); + a.setDataType(true); + SampleSaveConfiguration b = new SampleSaveConfiguration(); + b.setUrl(false); + b.setAssertions(true); + b.setDefaultDelimiter(); + b.setDefaultTimeStampFormat(); + b.setDataType(true); + + // a and b should be equal + assertEquals(a, b); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(a.saveUrl(), b.saveUrl()); + assertEquals(a.saveAssertions(), b.saveAssertions()); + assertEquals(a.getDelimiter(), b.getDelimiter()); + assertEquals(a.saveDataType(), b.saveDataType()); + + a.setAssertions(false); + // a and b should not be equal + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + assertFalse(a.hashCode() == b.hashCode()); + assertFalse(a.saveAssertions() == b.saveAssertions()); + } + + public void testFalse() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(false); + SampleSaveConfiguration b = new SampleSaveConfiguration(false); + assertEquals("Hash codes should be equal",a.hashCode(), b.hashCode()); + assertTrue("Objects should be equal",a.equals(b)); + assertTrue("Objects should be equal",b.equals(a)); + } + + public void testTrue() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(true); + SampleSaveConfiguration b = new SampleSaveConfiguration(true); + assertEquals("Hash codes should be equal",a.hashCode(), b.hashCode()); + assertTrue("Objects should be equal",a.equals(b)); + assertTrue("Objects should be equal",b.equals(a)); + } + public void testFalseTrue() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(false); + SampleSaveConfiguration b = new SampleSaveConfiguration(true); + assertFalse("Hash codes should not be equal",a.hashCode() == b.hashCode()); + assertFalse("Objects should not be equal",a.equals(b)); + assertFalse("Objects should not be equal",b.equals(a)); + } + + public void testFormatter() throws Exception { + SampleSaveConfiguration a = new SampleSaveConfiguration(false); + SampleSaveConfiguration b = new SampleSaveConfiguration(false); + a.setFormatter(null); + assertEquals("Hash codes should be equal",a.hashCode(), b.hashCode()); + assertTrue("Objects should be equal",a.equals(b)); + assertTrue("Objects should be equal",b.equals(a)); + b.setFormatter(null); + assertEquals("Hash codes should be equal",a.hashCode(), b.hashCode()); + assertTrue("Objects should be equal",a.equals(b)); + assertTrue("Objects should be equal",b.equals(a)); + a.setFormatter(new SimpleDateFormat()); + b.setFormatter(new SimpleDateFormat()); + assertEquals("Hash codes should be equal",a.hashCode(), b.hashCode()); + assertTrue("Objects should be equal",a.equals(b)); + assertTrue("Objects should be equal",b.equals(a)); + } + + } + diff --git a/ApacheJmeter/org/apache/jmeter/save/TestCSVSaveService.java b/ApacheJmeter/org/apache/jmeter/save/TestCSVSaveService.java new file mode 100644 index 0000000..df8797c --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/save/TestCSVSaveService.java @@ -0,0 +1,111 @@ +/* + * 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.save; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; + +import org.apache.jmeter.junit.JMeterTestCase; + +public class TestCSVSaveService extends JMeterTestCase { + + public TestCSVSaveService(String name) { + super(name); + } + + private void checkSplitString(String input, char delim, String []expected) throws Exception { + String out[] = CSVSaveService.csvSplitString(input, delim); + checkStrings(expected, out); + } + + private void checkStrings(String[] expected, String[] out) { + assertEquals("Incorrect number of strings returned",expected.length, out.length); + for(int i = 0; i < out.length; i++){ + assertEquals("Incorrect entry returned",expected[i], out[i]); + } + } + + // This is what JOrphanUtils.split() does + public void testSplitEmpty() throws Exception { + checkSplitString("", ',', new String[]{}); + } + + // These tests should agree with those for JOrphanUtils.split() as far as possible + + public void testSplitUnquoted() throws Exception { + checkSplitString("a", ',', new String[]{"a"}); + checkSplitString("a,bc,d,e", ',', new String[]{"a","bc","d","e"}); + checkSplitString(",bc,d,e", ',', new String[]{"","bc","d","e"}); + checkSplitString("a,,d,e", ',', new String[]{"a","","d","e"}); + checkSplitString("a,bc, ,e", ',', new String[]{"a","bc"," ","e"}); + checkSplitString("a,bc,d, ", ',', new String[]{"a","bc","d"," "}); + checkSplitString("a,bc,d,", ',', new String[]{"a","bc","d",""}); + checkSplitString("a,bc,,", ',', new String[]{"a","bc","",""}); + checkSplitString("a,,,", ',', new String[]{"a","","",""}); + checkSplitString("a,bc,d,\n",',', new String[]{"a","bc","d",""}); + + // \u00e7 = LATIN SMALL LETTER C WITH CEDILLA + // \u00e9 = LATIN SMALL LETTER E WITH ACUTE + checkSplitString("a,b\u00e7,d,\u00e9", ',', new String[]{"a","b\u00e7","d","\u00e9"}); + } + + public void testSplitQuoted() throws Exception { + checkSplitString("a,bc,d,e", ',', new String[]{"a","bc","d","e"}); + checkSplitString(",bc,d,e", ',', new String[]{"","bc","d","e"}); + checkSplitString("\"\",bc,d,e", ',', new String[]{"","bc","d","e"}); + checkSplitString("a,,d,e", ',', new String[]{"a","","d","e"}); + checkSplitString("a,\"\",d,e", ',', new String[]{"a","","d","e"}); + checkSplitString("a,bc, ,e", ',', new String[]{"a","bc"," ","e"}); + checkSplitString("a,bc,\" \",e", ',', new String[]{"a","bc"," ","e"}); + checkSplitString("a,bc,d, ", ',', new String[]{"a","bc","d"," "}); + checkSplitString("a,bc,d,\" \"", ',', new String[]{"a","bc","d"," "}); + checkSplitString("a,bc,d,", ',', new String[]{"a","bc","d",""}); + checkSplitString("a,bc,d,\"\"", ',', new String[]{"a","bc","d",""}); + checkSplitString("a,bc,d,\"\"\n",',', new String[]{"a","bc","d",""}); + + // \u00e7 = LATIN SMALL LETTER C WITH CEDILLA + // \u00e9 = LATIN SMALL LETTER E WITH ACUTE + checkSplitString("\"a\",\"b\u00e7\",\"d\",\"\u00e9\"", ',', new String[]{"a","b\u00e7","d","\u00e9"}); + } + + public void testSplitBadQuote() throws Exception { + try { + checkSplitString("a\"b",',',null); + fail("Should have generated IOException"); + } catch (IOException e) { + } + } + + public void testSplitMultiLine() throws Exception { + String line="a,,\"c\nd\",e\n,,f,g,"; + String[] out; + BufferedReader br = new BufferedReader(new StringReader(line)); + out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{"a","","c\nd","e"}, out); + out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{"","","f","g",""}, out); + assertEquals("Expected to be at EOF",-1,br.read()); + // Empty strings at EOF + out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{}, out); + out = CSVSaveService.csvReadFile(br, ','); + checkStrings(new String[]{}, out); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/save/TestSaveService.java b/ApacheJmeter/org/apache/jmeter/save/TestSaveService.java new file mode 100644 index 0000000..1126532 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/save/TestSaveService.java @@ -0,0 +1,157 @@ +/* + * 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.save; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.List; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; + +public class TestSaveService extends JMeterTestCase { + + // testLoadAndSave test files + private static final String[] FILES = new String[] { + "AssertionTestPlan.jmx", + "AuthManagerTestPlan.jmx", + "HeaderManagerTestPlan.jmx", + "InterleaveTestPlan2.jmx", + "InterleaveTestPlan.jmx", + "LoopTestPlan.jmx", + "Modification Manager.jmx", + "OnceOnlyTestPlan.jmx", + "proxy.jmx", + "ProxyServerTestPlan.jmx", + "SimpleTestPlan.jmx", + "GuiTest.jmx", + "GuiTest231.jmx", + "GenTest27.jmx", + }; + + // Test files for testLoad; output will generally be different in size + private static final String[] FILES_LOAD_ONLY = new String[] { + "GuiTest_original.jmx", + "GuiTest231_original.jmx", + "GenTest22.jmx", + "GenTest231.jmx", + "GenTest24.jmx", + "GenTest25.jmx", // GraphAccumVisualizer obsolete, BSFSamplerGui now a TestBean + "GenTest251.jmx", // GraphAccumVisualizer obsolete, BSFSamplerGui now a TestBean + "GenTest26.jmx", // GraphAccumVisualizer now obsolete + }; + + private static final boolean saveOut = JMeterUtils.getPropDefault("testsaveservice.saveout", false); + + public TestSaveService(String name) { + super(name); + } + public void testPropfile() throws Exception { + assertTrue("Property Version mismatch", SaveService.checkPropertyVersion()); + assertTrue("Property File Version mismatch", SaveService.checkFileVersion()); + } + + public void testVersions() throws Exception { + assertTrue("Unexpected version found", SaveService.checkVersions()); + } + + public void testLoadAndSave() throws Exception { + byte[] original = new byte[1000000]; + + boolean failed = false; // Did a test fail? + + for (int i = 0; i < FILES.length; i++) { + InputStream in = new FileInputStream(findTestFile("testfiles/" + FILES[i])); + int len = in.read(original); + + in.close(); + + in = new ByteArrayInputStream(original, 0, len); + HashTree tree = SaveService.loadTree(in); + + in.close(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(1000000); + + SaveService.saveTree(tree, out); + out.close(); // Make sure all the data is flushed out + + // We only check the length of the result. Comparing the + // actual result (out.toByteArray==original) will usually + // fail, because the order of the properties within each + // test element may change. Comparing the lengths should be + // enough to detect most problem cases... + int outsz=out.size(); + // Allow for input in CRLF and output in LF only + int lines=0; + byte ba[]=out.toByteArray(); + for(int j=0;j missingClasses = SaveService.checkClasses(); + if(missingClasses.size()>0) { + fail("One or more classes not found:"+missingClasses); + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/services/TestFileServer.java b/ApacheJmeter/org/apache/jmeter/services/TestFileServer.java new file mode 100644 index 0000000..e168474 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/services/TestFileServer.java @@ -0,0 +1,119 @@ +/* + * 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 to test FileServer methods + */ + +package org.apache.jmeter.services; + +import java.io.File; +import java.io.IOException; + +import org.apache.jmeter.junit.JMeterTestCase; + +public class TestFileServer extends JMeterTestCase { + + private static final FileServer FS = FileServer.getFileServer(); + + public TestFileServer() { + super(); + } + + public TestFileServer(String arg0) { + super(arg0); + } + + @Override + public void setUp() throws IOException { + FS.resetBase(); + } + + @Override + public void tearDown() throws IOException{ + FS.closeFiles(); + } + + public void testopen() throws Exception { + try { + FS.readLine("test"); + fail("Expected IOException"); + } catch (IOException ignored){ + } + try { + FS.write("test",""); + fail("Expected IOException"); + } catch (IOException ignored){ + } + assertFalse("Should not have any files open",FS.filesOpen()); + FS.closeFile("xxx"); // Unrecognised files are ignored + assertFalse("Should not have any files open",FS.filesOpen()); + String infile=findTestPath("testfiles/test.csv"); + FS.reserveFile(infile); // Does not open file + assertFalse("Should not have any files open",FS.filesOpen()); + assertEquals("a1,b1,c1,d1",FS.readLine(infile)); + assertTrue("Should have some files open",FS.filesOpen()); + assertNotNull(FS.readLine(infile)); + assertNotNull(FS.readLine(infile)); + assertNotNull(FS.readLine(infile)); + assertEquals("a1,b1,c1,d1",FS.readLine(infile));// Re-read 1st line + assertNotNull(FS.readLine(infile)); + try { + FS.write(infile,"");// should not be able to write to it ... + fail("Expected IOException"); + } catch (IOException ignored){ + } + FS.closeFile(infile); // does not remove the entry + assertFalse("Should not have any files open",FS.filesOpen()); + assertEquals("a1,b1,c1,d1",FS.readLine(infile));// Re-read 1st line + assertTrue("Should have some files open",FS.filesOpen()); + FS.closeFiles(); // removes all entries + assertFalse("Should not have any files open",FS.filesOpen()); + try { + FS.readLine(infile); + fail("Expected IOException"); + } catch (IOException ignored){ + } + infile=findTestPath("testfiles/test.csv"); + FS.reserveFile(infile); // Does not open file + assertFalse("Should not have any files open",FS.filesOpen()); + assertEquals("a1,b1,c1,d1",FS.readLine(infile)); + try { + FS.setBasedir("x"); + fail("Expected IllegalStateException"); + } catch (IllegalStateException ignored){ + } + FS.closeFile(infile); + FS.setBasedir("y"); + FS.closeFiles(); + } + + public void testRelative() throws Exception { + final String base = FileServer.getDefaultBase(); + final File basefile = new File(base); + FS.setBaseForScript(basefile); + assertEquals(".",FS.getBaseDirRelative().toString()); + FS.setBaseForScript(basefile.getParentFile()); + assertEquals(".",FS.getBaseDirRelative().toString()); + FS.setBaseForScript(new File(basefile.getParentFile(),"abcd/defg.jmx")); + assertEquals(".",FS.getBaseDirRelative().toString()); + File file = new File(basefile,"abcd/defg.jmx"); + FS.setBaseForScript(file); + assertEquals("abcd",FS.getBaseDirRelative().toString()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/testbeans/gui/PackageTest.java b/ApacheJmeter/org/apache/jmeter/testbeans/gui/PackageTest.java new file mode 100644 index 0000000..a23ee0d --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/testbeans/gui/PackageTest.java @@ -0,0 +1,220 @@ +/* + * 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.testbeans.gui; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + +import org.apache.jmeter.gui.util.JMeterMenuBar; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; + +import junit.framework.Test; +// import junit.framework.TestCase; +import junit.framework.TestSuite; + +/* + * Find all beans out there and check their resource property files: - Check + * that non-default property files don't have any extra keys. - Check all + * necessary properties are defined at least in the default property file, + * except for beans whose name contains "Experimental" or "Alpha". + * + * TODO: - Check property files don't have duplicate keys (is this important) + * + */ +public class PackageTest extends JMeterTestCase { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // ResourceBundle i18nEdit= + // ResourceBundle.getBundle("org.apache.jmeter.resources.i18nedit"); + private static final Locale defaultLocale = new Locale("en",""); // i18nEdit.getString("locale.default"); + + private final ResourceBundle defaultBundle; + + private final Class testBeanClass; + + private final Locale testLocale; + + private PackageTest(Class testBeanClass, Locale locale, ResourceBundle defaultBundle) { + super(testBeanClass.getName() + " - " + locale.getLanguage() + " - " + locale.getCountry()); + this.testBeanClass = testBeanClass; + this.testLocale = locale; + this.defaultBundle = defaultBundle; + } + + private PackageTest(String name){ + super(name); + this.testBeanClass = null; + this.testLocale = null; + this.defaultBundle = null; + } + + private BeanInfo beanInfo; + + private ResourceBundle bundle; + + @Override + public void setUp() { + if (testLocale == null) { + return;// errorDetected() + } + JMeterUtils.setLocale(testLocale); + Introspector.flushFromCaches(testBeanClass); + try { + beanInfo = Introspector.getBeanInfo(testBeanClass); + bundle = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE); + } catch (IntrospectionException e) { + log.error("Can't get beanInfo for " + testBeanClass.getName(), e); + throw new Error(e.toString()); // Programming error. Don't continue. + } + if (bundle == null) { + throw new Error("This can't happen!"); + } + } + + @Override + public void tearDown() { + JMeterUtils.setLocale(Locale.getDefault()); + } + + @Override + public void runTest() throws Throwable { + if (testLocale == null) { + super.runTest(); + return;// errorDetected() + } + if (bundle == defaultBundle) { + checkAllNecessaryKeysPresent(); + } else { + checkNoInventedKeys(); + } + } + + public void checkNoInventedKeys() { + // Check that all keys in the bundle are also in the default bundle: + for (Enumeration keys = bundle.getKeys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + defaultBundle.getString(key); + // Will throw MissingResourceException if key is not there. + } + } + + public void checkAllNecessaryKeysPresent() { + // Check that all necessary keys are there: + + // displayName is always mandatory: + String dn = defaultBundle.getString("displayName").toUpperCase(Locale.ENGLISH); + + // Skip the rest of this test for alpha/experimental beans: + if (dn.indexOf("(ALPHA") != -1 || dn.indexOf("(EXPERIMENTAL") != -1) { + return; + } + + // Check for property- and group-related texts: + PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); + for (int i = 0; i < descriptors.length; i++) { + // Skip non-editable properties, that is: + // Ignore hidden, read-only, and write-only properties + if (descriptors[i].isHidden() || descriptors[i].getReadMethod() == null + || descriptors[i].getWriteMethod() == null) { + continue; + } + // Ignore TestElement properties which don't have an explicit + // editor: + if (TestElement.class.isAssignableFrom(descriptors[i].getPropertyType()) + && descriptors[i].getPropertyEditorClass() == null) { + continue; + } + // Done -- we're working with an editable property. + + String name = descriptors[i].getName(); + + bundle.getString(name + ".displayName"); + // bundle.getString(name+".shortDescription"); NOT MANDATORY + + String group = (String) descriptors[i].getValue(GenericTestBeanCustomizer.GROUP); + if (group != null) { + bundle.getString( group + ".displayName"); + } + } + } + + public static Test suite() throws Exception { + TestSuite suite = new TestSuite("Bean Resource Test Suite"); + + List testBaeanclassNames = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { TestBean.class }); + + boolean errorDetected = false; + JMeterUtils.setLocale(defaultLocale); + for (String className : testBaeanclassNames) { + Class testBeanClass = Class.forName(className); + ResourceBundle defaultBundle = null; + try { + defaultBundle = (ResourceBundle) Introspector.getBeanInfo(testBeanClass).getBeanDescriptor().getValue( + GenericTestBeanCustomizer.RESOURCE_BUNDLE); + } catch (IntrospectionException e) { + log.error("Can't get beanInfo for " + testBeanClass.getName(), e); + throw new Error(e.toString()); // Programming error. Don't + // continue. + } + + if (defaultBundle == null) { + if (className.startsWith("org.apache.jmeter.examples.")) { + log.info("No default bundle found for " + className); + continue; + } + errorDetected=true; + log.error("No default bundle found for " + className + " using " + defaultLocale.toString()); + //throw new Error("No default bundle for class " + className); + continue; + } + + suite.addTest(new PackageTest(testBeanClass, defaultLocale, defaultBundle)); + + String [] languages = JMeterMenuBar.getLanguages(); + for (int i=0; i < languages.length; i++){ + final String[] language = languages[i].split("_"); + if (language.length == 1){ + suite.addTest(new PackageTest(testBeanClass, new Locale(language[0]), defaultBundle)); + } else if (language.length == 2){ + suite.addTest(new PackageTest(testBeanClass, new Locale(language[0], language[1]), defaultBundle)); + } + } + } + + if (errorDetected) + { + suite.addTest(new PackageTest("errorDetected")); + } + return suite; + } + + public void errorDetected(){ + fail("One or more errors detected - see log file"); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/testbeans/gui/TestBooleanPropertyEditor.java b/ApacheJmeter/org/apache/jmeter/testbeans/gui/TestBooleanPropertyEditor.java new file mode 100644 index 0000000..f2c38cc --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/testbeans/gui/TestBooleanPropertyEditor.java @@ -0,0 +1,55 @@ +/* + * 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.testbeans.gui; + +import java.beans.PropertyEditor; +import java.beans.PropertyEditorManager; + +/** + * Test class to check that the JVM provides sensible behaviour for the boolean PropertyEditor, i.e. + * that getAsText() can only return values that match getTags(). + * + */ +public class TestBooleanPropertyEditor extends junit.framework.TestCase { + + public TestBooleanPropertyEditor(String name) { + super(name); + } + + public void testBooleanEditor(){ + PropertyEditor propertyEditor = PropertyEditorManager.findEditor(boolean.class); + assertNotNull(propertyEditor); + String tags[] = propertyEditor.getTags(); + assertEquals(2,tags.length); + assertEquals("True",tags[0]); + assertEquals("False",tags[1]); + + propertyEditor.setValue(Boolean.FALSE); + assertEquals("False",propertyEditor.getAsText()); + propertyEditor.setAsText("False"); + assertEquals("False",propertyEditor.getAsText()); + propertyEditor.setAsText("false"); + assertEquals("False",propertyEditor.getAsText()); + + propertyEditor.setValue(Boolean.TRUE); + assertEquals("True",propertyEditor.getAsText()); + propertyEditor.setAsText("True"); + assertEquals("True",propertyEditor.getAsText()); + propertyEditor.setAsText("true"); + assertEquals("True",propertyEditor.getAsText()); + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/testbeans/gui/TestComboStringEditor.java b/ApacheJmeter/org/apache/jmeter/testbeans/gui/TestComboStringEditor.java new file mode 100644 index 0000000..6bc6e8b --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/testbeans/gui/TestComboStringEditor.java @@ -0,0 +1,55 @@ +/* + * 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.testbeans.gui; + +public class TestComboStringEditor extends junit.framework.TestCase { + public TestComboStringEditor(String name) { + super(name); + } + + private void testSetGet(ComboStringEditor e, Object value) throws Exception { + e.setValue(value); + assertEquals(value, e.getValue()); + } + + private void testSetGetAsText(ComboStringEditor e, String text) throws Exception { + e.setAsText(text); + assertEquals(text, e.getAsText()); + } + + public void testSetGet() throws Exception { + ComboStringEditor e = new ComboStringEditor(); + + testSetGet(e, "any string"); + testSetGet(e, ""); + testSetGet(e, null); + testSetGet(e, "${var}"); + } + + public void testSetGetAsText() throws Exception { + ComboStringEditor e = new ComboStringEditor(); + + testSetGetAsText(e, "any string"); + testSetGetAsText(e, ""); + testSetGetAsText(e, null); + testSetGetAsText(e, "${var}"); + + // Check "Undefined" does not become a "reserved word": + e.setAsText(ComboStringEditor.UNDEFINED.toString()); + assertNotNull(e.getAsText()); + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/testbeans/gui/TestFieldStringEditor.java b/ApacheJmeter/org/apache/jmeter/testbeans/gui/TestFieldStringEditor.java new file mode 100644 index 0000000..b5ed47b --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/testbeans/gui/TestFieldStringEditor.java @@ -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. + */ +package org.apache.jmeter.testbeans.gui; + + +public class TestFieldStringEditor extends junit.framework.TestCase { + public TestFieldStringEditor(String name) { + super(name); + } + + private void testSetGet(ComboStringEditor e, Object value) throws Exception { + e.setValue(value); + assertEquals(value, e.getValue()); + } + + private void testSetGetAsText(ComboStringEditor e, String text) throws Exception { + e.setAsText(text); + assertEquals(text, e.getAsText()); + } + + public void testSetGet() throws Exception { + ComboStringEditor e = new ComboStringEditor(); + + testSetGet(e, "any string"); + testSetGet(e, ""); + testSetGet(e, "${var}"); + } + + public void testSetGetAsText() throws Exception { + ComboStringEditor e = new ComboStringEditor(); + + testSetGetAsText(e, "any string"); + testSetGetAsText(e, ""); + testSetGetAsText(e, "${var}"); + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/testelement/BarChartTest.java b/ApacheJmeter/org/apache/jmeter/testelement/BarChartTest.java new file mode 100644 index 0000000..d7e72e5 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/testelement/BarChartTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.testelement; + +import java.io.File; +import java.io.IOException; + +import javax.swing.JComponent; + +import org.apache.jmeter.report.DataSet; +import org.apache.jmeter.save.SaveGraphicsService; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class BarChartTest extends JMeterTestCase { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * @param arg0 + */ + public BarChartTest(String arg0) { + super(arg0); + } + + public void testGenerateBarChart() throws IOException { + log.info("jtl version=" + JMeterUtils.getProperty("file_format.testlog")); + // String sampleLog = "C:/eclipse3/workspace/jmeter-21/bin/testfiles/sample_log1.jtl"; + String sampleLog = findTestPath("testfiles/sample_log1.jtl"); + String sampleLog2 = findTestPath("testfiles/sample_log1b.jtl"); + String sampleLog3 = findTestPath("testfiles/sample_log1c.jtl"); + JTLData input = new JTLData(); + JTLData input2 = new JTLData(); + JTLData input3 = new JTLData(); + input.setDataSource(sampleLog); + input.loadData(); + input2.setDataSource(sampleLog2); + input2.loadData(); + input3.setDataSource(sampleLog3); + input3.loadData(); + + assertTrue((input.getStartTimestamp() > 0)); + assertTrue((input.getEndTimestamp() > input.getStartTimestamp())); + assertTrue((input.getURLs().size() > 0)); + log.info("URL count=" + input.getURLs().size()); + java.util.ArrayList list = new java.util.ArrayList(); + list.add(input); + list.add(input2); + list.add(input3); + + BarChart bchart = new BarChart(); + bchart.setTitle("Sample Chart"); + bchart.setCaption("Sample"); + bchart.setName("Sample"); + bchart.setYAxis("milliseconds"); + bchart.setYLabel("Test Runs"); + bchart.setXAxis(AbstractTable.REPORT_TABLE_90_PERCENT); + bchart.setXLabel(AbstractChart.X_DATA_DATE_LABEL); + bchart.setURL("jakarta_home"); + JComponent gr = bchart.renderChart(list); + assertNotNull(gr); + SaveGraphicsService serv = new SaveGraphicsService(); + String filename = bchart.getTitle(); + filename = filename.replace(' ','_'); + if (!"true".equalsIgnoreCase(System.getProperty("java.awt.headless"))){ + String outName = File.createTempFile(filename, null).getAbsolutePath(); // tweak. + serv.saveJComponent(outName,SaveGraphicsService.PNG,gr); + assertTrue("Should have created the file",new File(outName+".png").exists()); + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/testelement/LineGraphTest.java b/ApacheJmeter/org/apache/jmeter/testelement/LineGraphTest.java new file mode 100644 index 0000000..edac071 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/testelement/LineGraphTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.testelement; + +import java.io.File; +import java.io.IOException; + +import javax.swing.JComponent; + +import org.apache.jmeter.report.DataSet; +import org.apache.jmeter.save.SaveGraphicsService; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class LineGraphTest extends JMeterTestCase { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * @param arg0 + */ + public LineGraphTest(String arg0) { + super(arg0); + } + + public void testGenerateLineChart() throws IOException { + log.info("jtl version=" + JMeterUtils.getProperty("file_format.testlog")); + // String sampleLog = "C:/eclipse3/workspace/jmeter-21/bin/testfiles/sample_log1.jtl"; + String sampleLog = findTestPath("testfiles/sample_log1.jtl"); + String sampleLog2 = findTestPath("testfiles/sample_log1b.jtl"); + String sampleLog3 = findTestPath("testfiles/sample_log1c.jtl"); + JTLData input = new JTLData(); + JTLData input2 = new JTLData(); + JTLData input3 = new JTLData(); + input.setDataSource(sampleLog); + input.loadData(); + input2.setDataSource(sampleLog2); + input2.loadData(); + input3.setDataSource(sampleLog3); + input3.loadData(); + + assertTrue((input.getStartTimestamp() > 0)); + assertTrue((input.getEndTimestamp() > input.getStartTimestamp())); + assertTrue((input.getURLs().size() > 0)); + log.info("URL count=" + input.getURLs().size()); + java.util.ArrayList list = new java.util.ArrayList(); + list.add(input); + list.add(input2); + list.add(input3); + + LineChart lgraph = new LineChart(); + lgraph.setTitle("Sample Line Graph"); + lgraph.setCaption("Sample"); + lgraph.setName("Sample"); + lgraph.setYAxis("milliseconds"); + lgraph.setYLabel("Test Runs"); + lgraph.setXAxis(AbstractTable.REPORT_TABLE_MAX); + lgraph.setXLabel(AbstractChart.X_DATA_FILENAME_LABEL); + lgraph.setURLs("jakarta_home,jmeter_home"); + JComponent gr = lgraph.renderChart(list); + assertNotNull(gr); + SaveGraphicsService serv = new SaveGraphicsService(); + String filename = lgraph.getTitle(); + filename = filename.replace(' ','_'); + if (!"true".equalsIgnoreCase(System.getProperty("java.awt.headless"))){ + String outPfx = File.createTempFile(filename, null).getAbsolutePath(); // tweak. + serv.saveJComponent(outPfx,SaveGraphicsService.PNG,gr); + assertTrue("Should have created file",new File(outPfx+".png").exists()); + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/testelement/PackageTest.java b/ApacheJmeter/org/apache/jmeter/testelement/PackageTest.java new file mode 100644 index 0000000..b146efd --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/testelement/PackageTest.java @@ -0,0 +1,111 @@ +/* + * 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. + * + */ + +/* + * Created on Jul 16, 2003 + * + */ +package org.apache.jmeter.testelement; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.LoginConfig; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.sampler.DebugSampler; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; + +public class PackageTest extends TestCase { + public PackageTest(String arg0) { + super(arg0); + } + + // Test needs to run in this package in order to give access to AbstractTestElement.addProperty() + public void DISABLEDtestBug50799() throws Exception { + HeaderManager headerManager = new HeaderManager(); + headerManager.add(new Header("1stLevelTestHeader", "testValue1")); + HeaderManager headerManager2 = new HeaderManager(); + headerManager2.add(new Header("2ndLevelTestHeader", "testValue2")); + + DebugSampler debugSampler = new DebugSampler(); + debugSampler.addProperty(new StringProperty("name", "DebugSampler_50799")); + debugSampler.setRunningVersion(true); + assertTrue(debugSampler.getProperty("HeaderManager.headers") instanceof NullProperty); + debugSampler.addTestElement(headerManager); + assertFalse(debugSampler.getProperty("HeaderManager.headers") instanceof NullProperty); + assertEquals(debugSampler.getProperty("HeaderManager.headers").getStringValue() ,"[1stLevelTestHeader testValue1]"); + + debugSampler.addTestElement(headerManager2); + assertEquals(debugSampler.getProperty("HeaderManager.headers").getStringValue() ,"[1stLevelTestHeader testValue1, 2ndLevelTestHeader testValue2]"); + assertEquals(2, ((CollectionProperty)debugSampler.getProperty("HeaderManager.headers")).size()); + + headerManager.recoverRunningVersion(); + headerManager2.recoverRunningVersion(); + debugSampler.recoverRunningVersion(); + + assertEquals(1, headerManager.size()); + assertEquals(1, headerManager2.size()); + assertEquals(0, ((CollectionProperty)debugSampler.getProperty("HeaderManager.headers")).size()); + assertEquals(new Header("1stLevelTestHeader", "testValue1"), headerManager.get(0)); + assertEquals(new Header("2ndLevelTestHeader", "testValue2"), headerManager2.get(0)); + } + + public void testRecovery() throws Exception { + ConfigTestElement config = new ConfigTestElement(); + config.addProperty(new StringProperty("name", "config")); + config.setRunningVersion(true); + LoginConfig loginConfig = new LoginConfig(); + loginConfig.setUsername("user1"); + loginConfig.setPassword("pass1"); + assertTrue(config.getProperty("login") instanceof NullProperty); + // This test should work whether or not all Nulls are equal + assertEquals(new NullProperty("login"), config.getProperty("login")); + config.addProperty(new TestElementProperty("login", loginConfig)); + assertEquals(loginConfig.toString(), config.getPropertyAsString("login")); + config.recoverRunningVersion(); + assertTrue(config.getProperty("login") instanceof NullProperty); + assertEquals(new NullProperty("login"), config.getProperty("login")); + } + + public void testArguments() throws Exception { + Arguments args = new Arguments(); + args.addArgument("arg1", "val1", "="); + TestElementProperty prop = new TestElementProperty("args", args); + ConfigTestElement te = new ConfigTestElement(); + te.addProperty(prop); + te.setRunningVersion(true); + Arguments config = new Arguments(); + config.addArgument("config1", "configValue", "="); + TestElementProperty configProp = new TestElementProperty("args", config); + ConfigTestElement te2 = new ConfigTestElement(); + te2.addProperty(configProp); + te.addTestElement(te2); + assertEquals(2, args.getArgumentCount()); + assertEquals("config1=configValue", args.getArgument(1).toString()); + te.recoverRunningVersion(); + te.addTestElement(te2); + assertEquals(2, args.getArgumentCount()); + assertEquals("config1=configValue", args.getArgument(1).toString()); + + } +} diff --git a/ApacheJmeter/org/apache/jmeter/testelement/property/PackageTest.java b/ApacheJmeter/org/apache/jmeter/testelement/property/PackageTest.java new file mode 100644 index 0000000..e7234fa --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/testelement/property/PackageTest.java @@ -0,0 +1,314 @@ +/* + * 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.testelement.property; + +import junit.framework.TestCase; + +import org.apache.jmeter.config.LoginConfig; + +/** + * Class for testing the property package. + */ +public class PackageTest extends TestCase { + + public PackageTest(String name) { + super(name); + } + + public void testStringProperty() throws Exception { + StringProperty prop = new StringProperty("name", "value"); + prop.setRunningVersion(true); + prop.setObjectValue("new Value"); + assertEquals("new Value", prop.getStringValue()); + prop.recoverRunningVersion(null); + assertEquals("value", prop.getStringValue()); + prop.setObjectValue("new Value"); + prop.setObjectValue("2nd Value"); + assertEquals("2nd Value", prop.getStringValue()); + prop.recoverRunningVersion(null); + assertEquals("value", prop.getStringValue()); + } + + public void testElementProperty() throws Exception { + LoginConfig config = new LoginConfig(); + config.setUsername("username"); + config.setPassword("password"); + TestElementProperty prop = new TestElementProperty("name", config); + prop.setRunningVersion(true); + config = new LoginConfig(); + config.setUsername("user2"); + config.setPassword("pass2"); + prop.setObjectValue(config); + assertEquals("user2=pass2", prop.getStringValue()); + prop.recoverRunningVersion(null); + assertEquals("username=password", prop.getStringValue()); + config = new LoginConfig(); + config.setUsername("user2"); + config.setPassword("pass2"); + prop.setObjectValue(config); + config = new LoginConfig(); + config.setUsername("user3"); + config.setPassword("pass3"); + prop.setObjectValue(config); + assertEquals("user3=pass3", prop.getStringValue()); + prop.recoverRunningVersion(null); + assertEquals("username=password", prop.getStringValue()); + } + + private void checkEquals(JMeterProperty jp1, JMeterProperty jp2) { + assertEquals(jp1, jp2); + assertEquals(jp2, jp1); + assertEquals(jp1, jp1); + assertEquals(jp2, jp2); + assertEquals(jp1.hashCode(), jp2.hashCode()); + + } + + private void checkNotEquals(JMeterProperty jp1, JMeterProperty jp2) { + assertEquals(jp1, jp1); + assertEquals(jp2, jp2); + assertFalse(jp1.equals(jp2)); + assertFalse(jp2.equals(jp1)); + // do not check hashcodes; unequal objects may have equal hashcodes + } + + public void testBooleanEquality() throws Exception { + BooleanProperty jpn1 = new BooleanProperty(); + BooleanProperty jpn2 = new BooleanProperty(); + BooleanProperty jp1 = new BooleanProperty("name1", true); + BooleanProperty jp2 = new BooleanProperty("name1", true); + BooleanProperty jp3 = new BooleanProperty("name2", true); + BooleanProperty jp4 = new BooleanProperty("name2", false); + checkEquals(jpn1, jpn2); + checkNotEquals(jpn1, jp1); + checkNotEquals(jpn1, jp2); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + } + + public void testDoubleEquality() throws Exception { + DoubleProperty jpn1 = new DoubleProperty(); + DoubleProperty jpn2 = new DoubleProperty(); + DoubleProperty jp1 = new DoubleProperty("name1", 123.4); + DoubleProperty jp2 = new DoubleProperty("name1", 123.4); + DoubleProperty jp3 = new DoubleProperty("name2", -123.4); + DoubleProperty jp4 = new DoubleProperty("name2", 123.4); + DoubleProperty jp5 = new DoubleProperty("name2", Double.NEGATIVE_INFINITY); + DoubleProperty jp6 = new DoubleProperty("name2", Double.NEGATIVE_INFINITY); + DoubleProperty jp7 = new DoubleProperty("name2", Double.POSITIVE_INFINITY); + DoubleProperty jp8 = new DoubleProperty("name2", Double.POSITIVE_INFINITY); + DoubleProperty jp9 = new DoubleProperty("name2", Double.NaN); + DoubleProperty jp10 = new DoubleProperty("name2", Double.NaN); + DoubleProperty jp11 = new DoubleProperty("name1", Double.NaN); + DoubleProperty jp12 = new DoubleProperty("name1", Double.MIN_VALUE); + DoubleProperty jp13 = new DoubleProperty("name2", Double.MIN_VALUE); + DoubleProperty jp14 = new DoubleProperty("name2", Double.MIN_VALUE); + DoubleProperty jp15 = new DoubleProperty("name1", Double.MAX_VALUE); + DoubleProperty jp16 = new DoubleProperty("name2", Double.MAX_VALUE); + DoubleProperty jp17 = new DoubleProperty("name2", Double.MAX_VALUE); + checkEquals(jpn1, jpn2); + checkNotEquals(jpn1, jp1); + checkNotEquals(jpn1, jp2); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + checkEquals(jp5, jp6); + checkNotEquals(jp3, jp6); + checkEquals(jp7, jp8); + checkNotEquals(jp4, jp7); + checkNotEquals(jp8, jp9); + checkEquals(jp9, jp10); + checkNotEquals(jp10, jp11); + checkNotEquals(jp5, jp10); + checkNotEquals(jp12, jp14); + checkEquals(jp13, jp14); + checkNotEquals(jp15, jp16); + checkEquals(jp16, jp17); + } + + public void testFloatEquality() throws Exception { + FloatProperty jp1 = new FloatProperty("name1", 123.4f); + FloatProperty jp2 = new FloatProperty("name1", 123.4f); + FloatProperty jp3 = new FloatProperty("name2", -123.4f); + FloatProperty jp4 = new FloatProperty("name2", 123.4f); + FloatProperty jp5 = new FloatProperty("name2", Float.NEGATIVE_INFINITY); + FloatProperty jp6 = new FloatProperty("name2", Float.NEGATIVE_INFINITY); + FloatProperty jp7 = new FloatProperty("name2", Float.POSITIVE_INFINITY); + FloatProperty jp8 = new FloatProperty("name2", Float.POSITIVE_INFINITY); + FloatProperty jp9 = new FloatProperty("name2", Float.NaN); + FloatProperty jp10 = new FloatProperty("name2", Float.NaN); + FloatProperty jp11 = new FloatProperty("name1", Float.NaN); + FloatProperty jp12 = new FloatProperty("name1", Float.MIN_VALUE); + FloatProperty jp13 = new FloatProperty("name2", Float.MIN_VALUE); + FloatProperty jp14 = new FloatProperty("name2", Float.MIN_VALUE); + FloatProperty jp15 = new FloatProperty("name1", Float.MAX_VALUE); + FloatProperty jp16 = new FloatProperty("name2", Float.MAX_VALUE); + FloatProperty jp17 = new FloatProperty("name2", Float.MAX_VALUE); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + checkEquals(jp5, jp6); + checkNotEquals(jp3, jp6); + checkEquals(jp7, jp8); + checkNotEquals(jp4, jp7); + checkNotEquals(jp8, jp9); + checkEquals(jp9, jp10); + checkNotEquals(jp10, jp11); + checkNotEquals(jp5, jp10); + checkNotEquals(jp12, jp14); + checkEquals(jp13, jp14); + checkNotEquals(jp15, jp16); + checkEquals(jp16, jp17); + } + + public void testIntegerEquality() throws Exception { + IntegerProperty jp1 = new IntegerProperty("name1", 123); + IntegerProperty jp2 = new IntegerProperty("name1", 123); + IntegerProperty jp3 = new IntegerProperty("name2", -123); + IntegerProperty jp4 = new IntegerProperty("name2", 123); + IntegerProperty jp5 = new IntegerProperty("name2", Integer.MIN_VALUE); + IntegerProperty jp6 = new IntegerProperty("name2", Integer.MIN_VALUE); + IntegerProperty jp7 = new IntegerProperty("name2", Integer.MAX_VALUE); + IntegerProperty jp8 = new IntegerProperty("name2", Integer.MAX_VALUE); + IntegerProperty jp9 = new IntegerProperty("name1", Integer.MIN_VALUE); + IntegerProperty jp10 = new IntegerProperty("name1", Integer.MAX_VALUE); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + checkEquals(jp5, jp6); + checkNotEquals(jp3, jp6); + checkEquals(jp7, jp8); + checkNotEquals(jp4, jp7); + checkNotEquals(jp9, jp5); + checkNotEquals(jp10, jp7); + checkNotEquals(jp9, jp10); + try { + new IntegerProperty(null); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + try { + new IntegerProperty(null, 0); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + } + + public void testLongEquality() throws Exception { + LongProperty jp1 = new LongProperty("name1", 123); + LongProperty jp2 = new LongProperty("name1", 123); + LongProperty jp3 = new LongProperty("name2", -123); + LongProperty jp4 = new LongProperty("name2", 123); + LongProperty jp5 = new LongProperty("name2", Long.MIN_VALUE); + LongProperty jp6 = new LongProperty("name2", Long.MIN_VALUE); + LongProperty jp7 = new LongProperty("name2", Long.MAX_VALUE); + LongProperty jp8 = new LongProperty("name2", Long.MAX_VALUE); + LongProperty jp9 = new LongProperty("name1", Long.MIN_VALUE); + LongProperty jp10 = new LongProperty("name1", Long.MAX_VALUE); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + checkEquals(jp5, jp6); + checkNotEquals(jp3, jp6); + checkEquals(jp7, jp8); + checkNotEquals(jp4, jp7); + checkNotEquals(jp9, jp5); + checkNotEquals(jp10, jp7); + checkNotEquals(jp9, jp10); + try { + new LongProperty(null, 0L); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + } + + public void testMapEquality() throws Exception { + try { + new MapProperty(null, null); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + + } + + public void testNullEquality() throws Exception { + NullProperty jpn1 = new NullProperty(); + NullProperty jpn2 = new NullProperty(); + try { + new NullProperty(null); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + NullProperty jp1 = new NullProperty("name1"); + NullProperty jp2 = new NullProperty("name1"); + NullProperty jp3 = new NullProperty("name2"); + NullProperty jp4 = new NullProperty("name2"); + checkEquals(jpn1, jpn2); + checkNotEquals(jpn1, jp1); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkEquals(jp3, jp4); + } + + public void testStringEquality() throws Exception { + StringProperty jpn1 = new StringProperty(); + StringProperty jpn2 = new StringProperty(); + StringProperty jp1 = new StringProperty("name1", "value1"); + StringProperty jp2 = new StringProperty("name1", "value1"); + StringProperty jp3 = new StringProperty("name2", "value1"); + StringProperty jp4 = new StringProperty("name2", "value2"); + StringProperty jp5 = new StringProperty("name1", null); + StringProperty jp6 = new StringProperty("name1", null); + StringProperty jp7 = new StringProperty("name2", null); + checkEquals(jpn1, jpn2); + checkNotEquals(jpn1, jp1); + checkEquals(jp1, jp2); + checkNotEquals(jp1, jp3); + checkNotEquals(jp2, jp3); + checkNotEquals(jp3, jp4); + checkEquals(jp5, jp6); + checkNotEquals(jp3, jp5); + checkNotEquals(jp6, jp7); + try { + new StringProperty(null, ""); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + try { + new StringProperty(null, null); + fail("Should have generated an Illegal Argument Exception"); + } catch (IllegalArgumentException e) { + } + + } + public void testAddingProperties() throws Exception { + CollectionProperty coll = new CollectionProperty(); + coll.addItem("joe"); + coll.addProperty(new FunctionProperty()); + assertEquals("joe", coll.get(0).getStringValue()); + assertEquals("org.apache.jmeter.testelement.property.FunctionProperty", coll.get(1).getClass().getName()); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/threads/TestJMeterContextService.java b/ApacheJmeter/org/apache/jmeter/threads/TestJMeterContextService.java new file mode 100644 index 0000000..d033b95 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/threads/TestJMeterContextService.java @@ -0,0 +1,52 @@ +/* + * 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.threads; + +import junit.framework.TestCase; + +public class TestJMeterContextService extends TestCase { + + public TestJMeterContextService(String name) { + super(name); + } + + public void testCounts(){ + assertEquals(0,JMeterContextService.getNumberOfThreads()); + assertEquals(0,JMeterContextService.getTotalThreads()); + incrNumberOfThreads(); + assertEquals(1,JMeterContextService.getNumberOfThreads()); + assertEquals(0,JMeterContextService.getTotalThreads()); + decrNumberOfThreads(); + assertEquals(0,JMeterContextService.getTotalThreads()); + assertEquals(0,JMeterContextService.getNumberOfThreads()); + JMeterContextService.addTotalThreads(27); + JMeterContextService.addTotalThreads(27); + assertEquals(54,JMeterContextService.getTotalThreads()); + assertEquals(0,JMeterContextService.getNumberOfThreads()); + } + + // Give access to the method for test code + public static void incrNumberOfThreads(){ + JMeterContextService.incrNumberOfThreads(); + } + // Give access to the method for test code + public static void decrNumberOfThreads(){ + JMeterContextService.decrNumberOfThreads(); + } +} diff --git a/ApacheJmeter/org/apache/jmeter/threads/TestTestCompiler.java b/ApacheJmeter/org/apache/jmeter/threads/TestTestCompiler.java new file mode 100644 index 0000000..d741939 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/threads/TestTestCompiler.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.threads; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.control.GenericController; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.collections.ListedHashTree; + +public class TestTestCompiler extends junit.framework.TestCase { + public TestTestCompiler(String name) { + super(name); + } + + public void testConfigGathering() throws Exception { + ListedHashTree testing = new ListedHashTree(); + GenericController controller = new GenericController(); + ConfigTestElement config1 = new ConfigTestElement(); + config1.setName("config1"); + config1.setProperty("test.property", "A test value"); + TestSampler sampler = new TestSampler(); + sampler.setName("sampler"); + testing.add(controller, config1); + testing.add(controller, sampler); + TestCompiler.initialize(); + + TestCompiler compiler = new TestCompiler(testing, new JMeterVariables()); + testing.traverse(compiler); + sampler = (TestSampler) compiler.configureSampler(sampler).getSampler(); + assertEquals("A test value", sampler.getPropertyAsString("test.property")); + } + + class TestSampler extends AbstractSampler { + private static final long serialVersionUID = 240L; + + public SampleResult sample(org.apache.jmeter.samplers.Entry e) { + return null; + } + + @Override + public Object clone() { + return new TestSampler(); + } + } +} diff --git a/ApacheJmeter/org/apache/jmeter/timers/PackageTest.java b/ApacheJmeter/org/apache/jmeter/timers/PackageTest.java new file mode 100644 index 0000000..35e0fa4 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/timers/PackageTest.java @@ -0,0 +1,102 @@ +/* + * 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.timers; + +import java.util.ResourceBundle; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.TestJMeterContextService; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class PackageTest extends JMeterTestCase { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public PackageTest(String arg0) { + super(arg0); + } + + public void testTimer1() throws Exception { + ConstantThroughputTimer timer = new ConstantThroughputTimer(); + assertEquals(0,timer.getCalcModeInt());// Assume this thread only + timer.setThroughput(60.0);// 1 per second + long delay = timer.delay(); // Initialise + assertEquals(0,delay); + Thread.sleep(500); + assertEquals("Expected delay of approx 500",500, timer.delay(), 50); + } + + public void testTimer2() throws Exception { + ConstantThroughputTimer timer = new ConstantThroughputTimer(); + assertEquals(0,timer.getCalcModeInt());// Assume this thread only + timer.setThroughput(60.0);// 1 per second + assertEquals(1000,timer.calculateCurrentTarget(0)); // Should delay for 1 second + timer.setThroughput(60000.0);// 1 per milli-second + assertEquals(1,timer.calculateCurrentTarget(0)); // Should delay for 1 milli-second + } + + public void testTimer3() throws Exception { + ConstantThroughputTimer timer = new ConstantThroughputTimer(); + ConstantThroughputTimerBeanInfo bi = new ConstantThroughputTimerBeanInfo(); + ResourceBundle rb = (ResourceBundle) bi.getBeanDescriptor().getValue(BeanInfoSupport.RESOURCE_BUNDLE); + timer.setCalcMode(rb.getString("calcMode.2")); //$NON-NLS-1$ - all threads + assertEquals(1,timer.getCalcModeInt());// All threads + for(int i=1; i<=10; i++){ + TestJMeterContextService.incrNumberOfThreads(); + } + assertEquals(10,JMeterContextService.getNumberOfThreads()); + timer.setThroughput(600.0);// 10 per second + assertEquals(1000,timer.calculateCurrentTarget(0)); // Should delay for 1 second + timer.setThroughput(600000.0);// 10 per milli-second + assertEquals(1,timer.calculateCurrentTarget(0)); // Should delay for 1 milli-second + for(int i=1; i<=990; i++){ + TestJMeterContextService.incrNumberOfThreads(); + } + assertEquals(1000,JMeterContextService.getNumberOfThreads()); + timer.setThroughput(60000000.0);// 1000 per milli-second + assertEquals(1,timer.calculateCurrentTarget(0)); // Should delay for 1 milli-second + } + + public void testTimerBSH() throws Exception { + if (!BeanShellInterpreter.isInterpreterPresent()){ + final String msg = "BeanShell jar not present, test ignored"; + log.warn(msg); + return; + } + BeanShellTimer timer = new BeanShellTimer(); + long delay; + + timer.setScript("\"60\""); + delay = timer.delay(); + assertEquals(60,delay); + + timer.setScript("60"); + delay = timer.delay(); + assertEquals(60,delay); + + timer.setScript("5*3*4"); + delay = timer.delay(); + assertEquals(60,delay); + } + +} diff --git a/ApacheJmeter/org/apache/jmeter/util/PackageTest.java b/ApacheJmeter/org/apache/jmeter/util/PackageTest.java new file mode 100644 index 0000000..bac15f0 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/util/PackageTest.java @@ -0,0 +1,66 @@ +/* + * 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; + +import junit.framework.TestCase; + +public class PackageTest extends TestCase { + + public PackageTest() { + super(); + } + + public PackageTest(String arg0) { + super(arg0); + } + + public void testServer() throws Exception { + BeanShellServer bshs = new BeanShellServer(9876, ""); + assertNotNull(bshs); + // Not sure we can test anything else here + } + public void testSub1() throws Exception { + String input = "http://jakarta.apache.org/jmeter/index.html"; + String pattern = "jakarta.apache.org"; + String sub = "${server}"; + assertEquals("http://${server}/jmeter/index.html", StringUtilities.substitute(input, pattern, sub)); + } + + public void testSub2() throws Exception { + String input = "arg1=param1;param1"; + String pattern = "param1"; + String sub = "${value}"; + assertEquals("arg1=${value};${value}", StringUtilities.substitute(input, pattern, sub)); + } + + public void testSub3() throws Exception { + String input = "jakarta.apache.org"; + String pattern = "jakarta.apache.org"; + String sub = "${server}"; + assertEquals("${server}", StringUtilities.substitute(input, pattern, sub)); + } + + public void testSub4() throws Exception { + String input = "//a///b////c"; + String pattern = "//"; + String sub = "/"; + assertEquals("/a//b//c", StringUtilities.substitute(input, pattern, sub)); + } + +} diff --git a/ApacheJmeter/org/apache/jmeter/util/TestJMeterUtils.java b/ApacheJmeter/org/apache/jmeter/util/TestJMeterUtils.java new file mode 100644 index 0000000..0ee4111 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/util/TestJMeterUtils.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 to test JMeterUtils methods + */ + +package org.apache.jmeter.util; + +import junit.framework.TestCase; + +public class TestJMeterUtils extends TestCase { + + public TestJMeterUtils() { + super(); + } + + public TestJMeterUtils(String arg0) { + super(arg0); + } + //TODO add some real tests now that split() has been removed + public void test1() throws Exception{ + + } +} diff --git a/ApacheJmeter/org/apache/jmeter/visualizers/GenerateTreeGui.java b/ApacheJmeter/org/apache/jmeter/visualizers/GenerateTreeGui.java new file mode 100644 index 0000000..9a36a46 --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/visualizers/GenerateTreeGui.java @@ -0,0 +1,255 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.control.GenericController; +import org.apache.jmeter.control.gui.LogicControllerGui; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Workbench test element to create a test plan containing samples of each test element + * (apart from Threads and Test Fragment). + * + * The user creates a Thread Group, and the elements are created as child elements of + * Simple Controllers. + * + * Note: the code currently runs on all versions of JMeter back to 2.2. + * Beware of making changes that rely on more recent APIs. + */ +public class GenerateTreeGui extends AbstractConfigGui + implements ActionListener, UnsharedComponent { + + private static final long serialVersionUID = 1L; + + private JButton generateButton = new JButton("Generate"); + + public GenerateTreeGui() { + super(); + new Throwable().printStackTrace(); + init(); + } + + public String getLabelResource() { + new Throwable().printStackTrace(); + return "test_plan"; // $NON-NLS-1$ + } + + @Override + public String getStaticLabel() { + new Throwable().printStackTrace(); + return "Test Generator"; // $NON-NLS-1$ + } + + @Override + public String getDocAnchor() { + new Throwable().printStackTrace(); + return super.getDocAnchor(); + } + + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.NON_TEST_ELEMENTS }); + } + + public void actionPerformed(ActionEvent action) { + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeModel treeModel = guiPackage.getTreeModel(); + JMeterTreeNode myTarget = findFirstNodeOfType(org.apache.jmeter.threads.ThreadGroup.class, treeModel); + if (myTarget == null) { + JMeterUtils.reportErrorToUser("Cannot find Thread Group"); + return; + } + + addElements(MenuFactory.CONTROLLERS, "Controllers", guiPackage, treeModel, myTarget); + addElements(MenuFactory.CONFIG_ELEMENTS, "Config Elements", guiPackage, treeModel, myTarget); + addElements(MenuFactory.TIMERS, "Timers", guiPackage, treeModel, myTarget); + addElements(MenuFactory.PRE_PROCESSORS, "Pre Processors", guiPackage, treeModel, myTarget); + addElements(MenuFactory.SAMPLERS, "Samplers", guiPackage, treeModel, myTarget); + addElements(MenuFactory.POST_PROCESSORS, "Post Processors", guiPackage, treeModel, myTarget); + addElements(MenuFactory.ASSERTIONS, "Assertions", guiPackage, treeModel, myTarget); + addElements(MenuFactory.LISTENERS, "Listeners", guiPackage, treeModel, myTarget); + } + + private void addElements(String menuKey, String title, GuiPackage guiPackage, JMeterTreeModel treeModel, + JMeterTreeNode myTarget) { + myTarget = addSimpleController(treeModel, myTarget, title); + JPopupMenu jp = MenuFactory.makeMenu(menuKey, "").getPopupMenu(); + for(Component comp : jp.getComponents()) { + JMenuItem jmi = (JMenuItem) comp; + try { + TestElement testElement = guiPackage.createTestElement(jmi.getName()); + addToTree(treeModel, myTarget, testElement); + } catch (Exception e) { + addSimpleController(treeModel, myTarget, jmi.getName()+" "+e.getMessage()); + } + } + } + + public TestElement createTestElement() { + TestElement el = new ConfigTestElement(); + modifyTestElement(el); + return el; + } + + public void modifyTestElement(TestElement element) { + configureTestElement(element); + } + + /** + * Create a panel containing the title label for the table. + * + * @return a panel containing the title label + */ + private Component makeLabelPanel() { + JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + ButtonGroup bg = new ButtonGroup(); + bg.add(generateButton); + generateButton.addActionListener(this); + labelPanel.add(generateButton); + return labelPanel; + } + + + /** + * Initialize the components and layout of this component. + */ + private void init() { + JPanel p = this; + + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + p = new JPanel(); + + p.setLayout(new BorderLayout()); + + p.add(makeLabelPanel(), BorderLayout.NORTH); +// p.add(makeMainPanel(), BorderLayout.CENTER); + // Force a minimum table height of 70 pixels + p.add(Box.createVerticalStrut(70), BorderLayout.WEST); + //p.add(makeButtonPanel(), BorderLayout.SOUTH); + + add(p, BorderLayout.CENTER); + } + /** + * Helper method to add a Simple Controller to contain the elements. + * Called from Application Thread that needs to update GUI (JMeterTreeModel) + * @param model + * Test component tree model + * @param node + * Node in the tree where we will add the Controller + * @param name + * A name for the Controller + * @return the new node + */ + private JMeterTreeNode addSimpleController(JMeterTreeModel model, JMeterTreeNode node, String name) { + final TestElement sc = new GenericController(); + sc.setProperty(TestElement.GUI_CLASS, LOGIC_CONTROLLER_GUI); + sc.setProperty(TestElement.NAME, name); // Use old style + return addToTree(model, node, sc); + } + + private static class RunGUI implements Runnable { + private final JMeterTreeModel model; + private final JMeterTreeNode node; + private final TestElement testElement; + RunGUI(JMeterTreeModel model, JMeterTreeNode node, TestElement testElement) { + super(); + this.model = model; + this.node = node; + this.testElement = testElement; + } + + volatile JMeterTreeNode newNode; + + public void run() { + try { + newNode = model.addComponent(testElement, node); + } catch (IllegalUserActionException e) { + throw new Error(e); + } + } + } + + private JMeterTreeNode addToTree(final JMeterTreeModel model, + final JMeterTreeNode node, final TestElement sc) { + RunGUI runnable = new RunGUI(model, node, sc); + if(SwingUtilities.isEventDispatchThread()) { + runnable.run(); + } else { + try { + SwingUtilities.invokeAndWait(runnable); + } catch (InterruptedException e) { + throw new Error(e); + } catch (InvocationTargetException e) { + throw new Error(e); + } + } + return runnable.newNode; + } + + private static final String LOGIC_CONTROLLER_GUI = LogicControllerGui.class.getName(); + + /** + * Finds the first enabled node of a given type in the tree. + * + * @param type + * class of the node to be found + * @param treeModel + * + * @return the first node of the given type in the test component tree, or + * null if none was found. + */ + private JMeterTreeNode findFirstNodeOfType(Class type, JMeterTreeModel treeModel) { + List nodes = treeModel.getNodesOfType(type); + for (JMeterTreeNode node : nodes) { + if (node.isEnabled()) { + return node; + } + } + return null; + } +} \ No newline at end of file diff --git a/ApacheJmeter/org/apache/jmeter/visualizers/TestSamplingStatCalculator.java b/ApacheJmeter/org/apache/jmeter/visualizers/TestSamplingStatCalculator.java new file mode 100644 index 0000000..3816c9c --- /dev/null +++ b/ApacheJmeter/org/apache/jmeter/visualizers/TestSamplingStatCalculator.java @@ -0,0 +1,153 @@ +/* + * 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.visualizers; + +import static org.junit.Assert.*; + +import org.apache.jmeter.samplers.SampleResult; +import org.junit.Before; +import org.junit.Test; + +public class TestSamplingStatCalculator { + + private SamplingStatCalculator ssc; + @Before + public void setUp(){ + ssc = new SamplingStatCalculator("JUnit"); + } + + @Test + public void testGetCurrentSample() { + assertNotNull(ssc.getCurrentSample()); // probably needed to avoid NPEs with GUIs + } + +// @Test +// public void testGetElapsed() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetRate() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetBytesPerSecond() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetKBPerSecond() { +// fail("Not yet implemented"); +// } + + @Test + public void testGetAvgPageBytes() { + SampleResult res = new SampleResult(); + assertEquals(0,ssc.getAvgPageBytes(),0); + res.setResponseData("abcdef", "UTF-8"); + ssc.addSample(res); + res.setResponseData("abcde", "UTF-8"); + ssc.addSample(res); + res.setResponseData("abcd", "UTF-8"); + ssc.addSample(res); + assertEquals(5,ssc.getAvgPageBytes(),0); + } + +// @Test +// public void testGetLabel() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testAddSample() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetErrorPercentage() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testToString() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetErrorCount() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMaxThroughput() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetDistribution() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetPercentPointDouble() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetCount() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMax() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMean() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMeanAsNumber() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMedian() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetMin() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetPercentPointFloat() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testGetStandardDeviation() { +// fail("Not yet implemented"); +// } + +} diff --git a/ApacheJmeter/org/apache/jorphan/TestFunctorUsers.java b/ApacheJmeter/org/apache/jorphan/TestFunctorUsers.java new file mode 100644 index 0000000..8acd44f --- /dev/null +++ b/ApacheJmeter/org/apache/jorphan/TestFunctorUsers.java @@ -0,0 +1,69 @@ +/* + * 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; + +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel; +import org.apache.jmeter.protocol.ldap.config.gui.LDAPArgumentsPanel; +import org.apache.jmeter.visualizers.StatGraphVisualizer; +import org.apache.jmeter.visualizers.StatVisualizer; +import org.apache.jmeter.visualizers.SummaryReport; +import org.apache.jmeter.visualizers.TableVisualizer; + +/* + * Unit tests for classes that use Functors + * + */ +public class TestFunctorUsers extends JMeterTestCase { + + public TestFunctorUsers(String arg0) { + super(arg0); + } + + @SuppressWarnings("deprecation") + public void testSummaryReport() throws Exception{ + assertTrue("SummaryReport Functor",SummaryReport.testFunctors()); + } + + public void testTableVisualizer() throws Exception{ + assertTrue("TableVisualizer Functor",TableVisualizer.testFunctors()); + } + + public void testStatGraphVisualizer() throws Exception{ + assertTrue("StatGraphVisualizer Functor",StatGraphVisualizer.testFunctors()); + } + + @SuppressWarnings("deprecation") + public void testStatVisualizer() throws Exception{ + assertTrue("StatVisualizer Functor",StatVisualizer.testFunctors()); + } + + public void testArgumentsPanel() throws Exception{ + assertTrue("ArgumentsPanel Functor",ArgumentsPanel.testFunctors()); + } + + public void testHTTPArgumentsPanel() throws Exception{ + assertTrue("HTTPArgumentsPanel Functor",HTTPArgumentsPanel.testFunctors()); + } + + public void testLDAPArgumentsPanel() throws Exception{ + assertTrue("LDAPArgumentsPanel Functor",LDAPArgumentsPanel.testFunctors()); + } +} diff --git a/ApacheJmeter/org/apache/jorphan/TestXMLBuffer.java b/ApacheJmeter/org/apache/jorphan/TestXMLBuffer.java new file mode 100644 index 0000000..c8dd36e --- /dev/null +++ b/ApacheJmeter/org/apache/jorphan/TestXMLBuffer.java @@ -0,0 +1,56 @@ +/* + * 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; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jorphan.util.XMLBuffer; + +public class TestXMLBuffer extends JMeterTestCase { + + public TestXMLBuffer(String arg0) { + super(arg0); + } + + public void test1() throws Exception{ + XMLBuffer xb = new XMLBuffer(); + xb.openTag("start"); + assertEquals("\n",xb.toString()); + } + + public void test2() throws Exception{ + XMLBuffer xb = new XMLBuffer(); + xb.tag("start","now"); + assertEquals("now\n",xb.toString()); + } + public void test3() throws Exception{ + XMLBuffer xb = new XMLBuffer(); + xb.openTag("abc"); + xb.closeTag("abc"); + assertEquals("\n",xb.toString()); + } + public void test4() throws Exception{ + XMLBuffer xb = new XMLBuffer(); + xb.openTag("abc"); + try { + xb.closeTag("abcd"); + fail("Should have caused IllegalArgumentException"); + } catch (IllegalArgumentException e) { + } + } +} diff --git a/ApacheJmeter/org/apache/jorphan/collections/PackageTest.java b/ApacheJmeter/org/apache/jorphan/collections/PackageTest.java new file mode 100644 index 0000000..55ed6c3 --- /dev/null +++ b/ApacheJmeter/org/apache/jorphan/collections/PackageTest.java @@ -0,0 +1,175 @@ +/* + * 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.collections; + +import java.util.Arrays; +import java.util.Collection; + +import junit.framework.TestCase; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class PackageTest extends TestCase { + public PackageTest(String name) { + super(name); + } + + public void testAdd1() throws Exception { + Logger log = LoggingManager.getLoggerForClass(); + Collection treePath = Arrays.asList(new String[] { "1", "2", "3", "4" }); + HashTree tree = new HashTree(); + log.debug("treePath = " + treePath); + tree.add(treePath, "value"); + log.debug("Now treePath = " + treePath); + log.debug(tree.toString()); + assertEquals(1, tree.list(treePath).size()); + assertEquals("value", tree.getArray(treePath)[0]); + } + + public void testEqualsAndHashCode1() throws Exception { + HashTree tree1 = new HashTree("abcd"); + HashTree tree2 = new HashTree("abcd"); + HashTree tree3 = new HashTree("abcde"); + HashTree tree4 = new HashTree("abcde"); + + assertTrue(tree1.equals(tree1)); + assertTrue(tree1.equals(tree2)); + assertTrue(tree2.equals(tree1)); + assertTrue(tree2.equals(tree2)); + assertEquals(tree1.hashCode(), tree2.hashCode()); + + assertTrue(tree3.equals(tree3)); + assertTrue(tree3.equals(tree4)); + assertTrue(tree4.equals(tree3)); + assertTrue(tree4.equals(tree4)); + assertEquals(tree3.hashCode(), tree4.hashCode()); + + assertNotSame(tree1, tree2); + assertNotSame(tree1, tree3); + assertNotSame(tree1, tree4); + assertNotSame(tree2, tree3); + assertNotSame(tree2, tree4); + + assertFalse(tree1.equals(tree3)); + assertFalse(tree1.equals(tree4)); + assertFalse(tree2.equals(tree3)); + assertFalse(tree2.equals(tree4)); + + assertNotNull(tree1); + assertNotNull(tree2); + + tree1.add("abcd", tree3); + assertFalse(tree1.equals(tree2)); + assertFalse(tree2.equals(tree1));// Check reflexive + if (tree1.hashCode() == tree2.hashCode()) { + // This is not a requirement + System.out.println("WARN: unequal HashTrees should not have equal hashCodes"); + } + tree2.add("abcd", tree4); + assertTrue(tree1.equals(tree2)); + assertTrue(tree2.equals(tree1)); + assertEquals(tree1.hashCode(), tree2.hashCode()); + } + + + public void testAddObjectAndTree() throws Exception { + ListedHashTree tree = new ListedHashTree("key"); + ListedHashTree newTree = new ListedHashTree("value"); + tree.add("key", newTree); + assertEquals(tree.list().size(), 1); + assertEquals("key", tree.getArray()[0]); + assertEquals(1, tree.getTree("key").list().size()); + assertEquals(0, tree.getTree("key").getTree("value").size()); + assertEquals(tree.getTree("key").getArray()[0], "value"); + assertNotNull(tree.getTree("key").get("value")); + } + + public void testEqualsAndHashCode2() throws Exception { + ListedHashTree tree1 = new ListedHashTree("abcd"); + ListedHashTree tree2 = new ListedHashTree("abcd"); + ListedHashTree tree3 = new ListedHashTree("abcde"); + ListedHashTree tree4 = new ListedHashTree("abcde"); + + assertTrue(tree1.equals(tree1)); + assertTrue(tree1.equals(tree2)); + assertTrue(tree2.equals(tree1)); + assertTrue(tree2.equals(tree2)); + assertEquals(tree1.hashCode(), tree2.hashCode()); + + assertTrue(tree3.equals(tree3)); + assertTrue(tree3.equals(tree4)); + assertTrue(tree4.equals(tree3)); + assertTrue(tree4.equals(tree4)); + assertEquals(tree3.hashCode(), tree4.hashCode()); + + assertNotSame(tree1, tree2); + assertNotSame(tree1, tree3); + assertFalse(tree1.equals(tree3)); + assertFalse(tree3.equals(tree1)); + assertFalse(tree1.equals(tree4)); + assertFalse(tree4.equals(tree1)); + + assertFalse(tree2.equals(tree3)); + assertFalse(tree3.equals(tree2)); + assertFalse(tree2.equals(tree4)); + assertFalse(tree4.equals(tree2)); + + tree1.add("abcd", tree3); + assertFalse(tree1.equals(tree2)); + assertFalse(tree2.equals(tree1)); + + tree2.add("abcd", tree4); + assertTrue(tree1.equals(tree2)); + assertTrue(tree2.equals(tree1)); + assertEquals(tree1.hashCode(), tree2.hashCode()); + + tree1.add("a1"); + tree1.add("a2"); + // tree1.add("a3"); + tree2.add("a2"); + tree2.add("a1"); + + assertFalse(tree1.equals(tree2)); + assertFalse(tree2.equals(tree1)); + if (tree1.hashCode() == tree2.hashCode()) { + // This is not a requirement + System.out.println("WARN: unequal ListedHashTrees should not have equal hashcodes"); + + } + + tree4.add("abcdef"); + assertFalse(tree3.equals(tree4)); + assertFalse(tree4.equals(tree3)); + } + + + public void testSearch() throws Exception { + ListedHashTree tree = new ListedHashTree(); + SearchByClass searcher = new SearchByClass(Integer.class); + String one = "one"; + String two = "two"; + Integer o = Integer.valueOf(1); + tree.add(one, o); + tree.getTree(one).add(o, two); + tree.traverse(searcher); + assertEquals(1, searcher.getSearchResults().size()); + } + +} diff --git a/ApacheJmeter/org/apache/jorphan/math/TestStatCalculator.java b/ApacheJmeter/org/apache/jorphan/math/TestStatCalculator.java new file mode 100644 index 0000000..b2ac123 --- /dev/null +++ b/ApacheJmeter/org/apache/jorphan/math/TestStatCalculator.java @@ -0,0 +1,154 @@ +/* + * 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.math; + +import java.util.Map; + +import junit.framework.TestCase; + +public class TestStatCalculator extends TestCase { + + private StatCalculatorLong calc; + + /** + * + */ + public TestStatCalculator() { + super(); + } + + public TestStatCalculator(String arg0) { + super(arg0); + } + + @Override + public void setUp() { + calc = new StatCalculatorLong(); + } + + public void testPercentagePoint() throws Exception { + calc.addValue(10); + calc.addValue(9); + calc.addValue(5); + calc.addValue(6); + calc.addValue(1); + calc.addValue(3); + calc.addValue(8); + calc.addValue(2); + calc.addValue(7); + calc.addValue(4); + assertEquals(10, calc.getCount()); + assertEquals(9, calc.getPercentPoint(0.8999999).intValue()); + } + public void testCalculation() { + assertEquals(Long.MIN_VALUE, calc.getMax().longValue()); + assertEquals(Long.MAX_VALUE, calc.getMin().longValue()); + calc.addValue(18); + calc.addValue(10); + calc.addValue(9); + calc.addValue(11); + calc.addValue(28); + calc.addValue(3); + calc.addValue(30); + calc.addValue(15); + calc.addValue(15); + calc.addValue(21); + assertEquals(16, (int) calc.getMean()); + assertEquals(8.0622577F, (float) calc.getStandardDeviation(), 0F); + assertEquals(30, calc.getMax().intValue()); + assertEquals(3, calc.getMin().intValue()); + assertEquals(15, calc.getMedian().intValue()); + } + public void testLong(){ + calc.addValue(0L); + calc.addValue(2L); + calc.addValue(2L); + final Long long0 = Long.valueOf(0); + final Long long2 = Long.valueOf(2); + assertEquals(long2,calc.getMax()); + assertEquals(long0,calc.getMin()); + Map map = calc.getDistribution(); + assertTrue(map.containsKey(long0)); + assertTrue(map.containsKey(long2)); + } + + public void testInteger(){ + StatCalculatorInteger calci = new StatCalculatorInteger(); + assertEquals(Integer.MIN_VALUE, calci.getMax().intValue()); + assertEquals(Integer.MAX_VALUE, calci.getMin().intValue()); + calci.addValue(0); + calci.addValue(2); + calci.addValue(2); + assertEquals(Integer.valueOf(2),calci.getMax()); + assertEquals(Integer.valueOf(0),calci.getMin()); + Map map = calci.getDistribution(); + assertTrue(map.containsKey(Integer.valueOf(0))); + assertTrue(map.containsKey(Integer.valueOf(2))); + } + + @SuppressWarnings("boxing") + public void testBug52125_1(){ // No duplicates when adding + calc.addValue(1L); + calc.addValue(2L); + calc.addValue(3L); + calc.addValue(2L); + calc.addValue(2L); + calc.addValue(2L); + assertEquals(6, calc.getCount()); + assertEquals(12.0, calc.getSum()); + assertEquals(0.5773502691896255, calc.getStandardDeviation()); + } + + @SuppressWarnings("boxing") + public void testBug52125_2(){ // add duplicates + calc.addValue(1L); + calc.addValue(2L); + calc.addValue(3L); + calc.addEachValue(2L, 3); + assertEquals(6, calc.getCount()); + assertEquals(12.0, calc.getSum()); + assertEquals(0.5773502691896255, calc.getStandardDeviation()); + } + + @SuppressWarnings("boxing") + public void testBug52125_2A(){ // as above, but with aggregate sample instead + calc.addValue(1L); + calc.addValue(2L); + calc.addValue(3L); + calc.addValue(6L, 3); + assertEquals(6, calc.getCount()); + assertEquals(12.0, calc.getSum()); + assertEquals(0.5773502691896255, calc.getStandardDeviation()); + } + + @SuppressWarnings("boxing") + public void testBug52125_3(){ // add duplicates as per bug + calc.addValue(1L); + calc.addValue(2L); + calc.addValue(3L); + StatCalculatorLong calc2 = new StatCalculatorLong(); + calc2.addValue(2L); + calc2.addValue(2L); + calc2.addValue(2L); + calc.addAll(calc2); + assertEquals(6, calc.getCount()); + assertEquals(12.0, calc.getSum()); + assertEquals(0.5773502691896255, calc.getStandardDeviation()); + } +} diff --git a/ApacheJmeter/org/apache/jorphan/reflect/TestFunctor.java b/ApacheJmeter/org/apache/jorphan/reflect/TestFunctor.java new file mode 100644 index 0000000..422ea50 --- /dev/null +++ b/ApacheJmeter/org/apache/jorphan/reflect/TestFunctor.java @@ -0,0 +1,225 @@ +/* + * 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.Map; +import java.util.Properties; + +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; + +/* + * Unit tests for classes that use Functors + * + */ +public class TestFunctor extends JMeterTestCase { + + interface HasName { + String getName(); + } + + interface HasString { + String getString(String s); + } + + class Test1 implements HasName { + private final String name; + public Test1(){ + this(""); + } + public Test1(String s){ + name=s; + } + public String getName(){ + return name; + } + public String getString(String s){ + return s; + } + } + class Test1a extends Test1{ + Test1a(){ + super("1a"); + } + Test1a(String s){ + super("1a:"+s); + } + @Override + public String getName(){ + return super.getName()+"."; + } + } + static class Test2 implements HasName, HasString { + private final String name; + public Test2(){ + this(""); + } + public Test2(String s){ + name=s; + } + public String getName(){ + return name; + } + public String getString(String s){ + return s; + } + } + + public TestFunctor(String arg0) { + super(arg0); + } + + @Override + public void setUp(){ + LoggingManager.setPriority("FATAL_ERROR",LoggingManager.removePrefix(Functor.class.getName())); + } + + public void testName() throws Exception{ + Functor f1 = new Functor("getName"); + Functor f2 = new Functor("getName"); + Functor f1a = new Functor("getName"); + Test1 t1 = new Test1("t1"); + Test2 t2 = new Test2("t2"); + Test1a t1a = new Test1a("aa"); + assertEquals("t1",f1.invoke(t1)); + //assertEquals("t1",f1.invoke()); + try { + f1.invoke(t2); + fail("Should have generated error"); + } catch (JMeterError e){ + + } + assertEquals("t2",f2.invoke(t2)); + //assertEquals("t2",f2.invoke()); + assertEquals("1a:aa.",f1a.invoke(t1a)); + //assertEquals("1a:aa.",f1a.invoke()); + try { + f1a.invoke(t1);// can't call invoke using super class + fail("Should have generated error"); + } catch (JMeterError e){ + + } + // OK (currently) to invoke using sub-class + assertEquals("1a:aa.",f1.invoke(t1a)); + //assertEquals("1a:aa.",f1.invoke());// N.B. returns different result from before + } + + public void testNameTypes() throws Exception{ + Functor f = new Functor("getString",new Class[]{String.class}); + Functor f2 = new Functor("getString");// Args will be provided later + Test1 t1 = new Test1("t1"); + assertEquals("x1",f.invoke(t1,new String[]{"x1"})); + try { + assertEquals("x1",f.invoke(t1)); + fail("Should have generated an Exception"); + } catch (JMeterError ok){ + } + assertEquals("x2",f2.invoke(t1,new String[]{"x2"})); + try { + assertEquals("x2",f2.invoke(t1)); + fail("Should have generated an Exception"); + } catch (JMeterError ok){ + } + } + public void testObjectName() throws Exception{ + Test1 t1 = new Test1("t1"); + Test2 t2 = new Test2("t2"); + Functor f1 = new Functor(t1,"getName"); + assertEquals("t1",f1.invoke(t1)); + assertEquals("t1",f1.invoke(t2)); // should use original object + } + + // Check how Class definition behaves + public void testClass() throws Exception{ + Test1 t1 = new Test1("t1"); + Test1 t1a = new Test1a("t1a"); + Test2 t2 = new Test2("t2"); + Functor f1 = new Functor(HasName.class,"getName"); + assertEquals("t1",f1.invoke(t1)); + assertEquals("1a:t1a.",f1.invoke(t1a)); + assertEquals("t2",f1.invoke(t2)); + try { + f1.invoke(); + fail("Should have failed"); + } catch (IllegalStateException ok){ + + } + Functor f2 = new Functor(HasString.class,"getString"); + assertEquals("xyz",f2.invoke(t2,new String[]{"xyz"})); + try { + f2.invoke(t1,new String[]{"xyz"}); + fail("Should have failed"); + } catch (JMeterError ok){ + + } + Functor f3 = new Functor(t2,"getString"); + assertEquals("xyz",f3.invoke(t2,new Object[]{"xyz"})); + + Properties p = new Properties(); + p.put("Name","Value"); + Functor fk = new Functor(Map.Entry.class,"getKey"); + Functor fv = new Functor(Map.Entry.class,"getValue"); + Object o = p.entrySet().iterator().next(); + assertEquals("Name",fk.invoke(o)); + assertEquals("Value",fv.invoke(o)); + } + + public void testBadParameters() throws Exception{ + try { + new Functor(null); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(null,new Class[]{}); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(null,new Object[]{}); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(String.class,null); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(new Object(),null); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(new Object(),null, new Class[]{}); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + try { + new Functor(new Object(),null, new Object[]{}); + fail("should have generated IllegalArgumentException;"); + } catch (IllegalArgumentException ok){} + } + public void testIllegalState() throws Exception{ + Functor f = new Functor("method"); + try { + f.invoke(); + fail("should have generated IllegalStateException;"); + } catch (IllegalStateException ok){} + try { + f.invoke(new Object[]{}); + fail("should have generated IllegalStateException;"); + } catch (IllegalStateException ok){} + } +} diff --git a/ApacheJmeter/org/apache/jorphan/test/AllTests.java b/ApacheJmeter/org/apache/jorphan/test/AllTests.java new file mode 100644 index 0000000..e52a31c --- /dev/null +++ b/ApacheJmeter/org/apache/jorphan/test/AllTests.java @@ -0,0 +1,381 @@ +/* + * 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.test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Locale; +import java.util.Properties; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import junit.textui.TestRunner; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Provides a quick and easy way to run all junit unit tests in your java + * project. It will find all unit test classes and run all their test methods. + * There is no need to configure it in any way to find these classes except to + * give it a path to search. + *

+ * Here is an example Ant target (See Ant at Apache) that runs all your unit + * tests: + * + *

+ * 
+ *       <target name="test" depends="compile">
+ *           <java classname="org.apache.jorphan.test.AllTests" fork="yes">
+ *               <classpath>
+ *                   <path refid="YOUR_CLASSPATH"/>
+ *                   <pathelement location="ROOT_DIR_OF_YOUR_COMPILED_CLASSES"/>
+ *               </classpath>
+ *               <arg value="SEARCH_PATH/"/>
+ *               <arg value="PROPERTY_FILE"/>
+ *               <arg value="NAME_OF_UNITTESTMANAGER_CLASS"/>
+ *           </java>
+ *       </target>
+ *  
+ * 
+ * + *
+ *
YOUR_CLASSPATH
+ *
Refers to the classpath that includes all jars and libraries need to run + * your unit tests
+ * + *
ROOT_DIR_OF_YOUR_COMPILED_CLASSES
+ *
The classpath should include the directory where all your project's + * classes are compiled to, if it doesn't already.
+ * + *
SEARCH_PATH
+ *
The first argument tells AllTests where to look for unit test classes to + * execute. In most cases, it is identical to ROOT_DIR_OF_YOUR_COMPILED_CLASSES. + * You can specify multiple directories or jars to search by providing a + * comma-delimited list.
+ * + *
PROPERTY_FILE
+ *
A simple property file that sets logging parameters. It is optional and + * is only relevant if you use the same logging packages that JOrphan uses.
+ * + *
NAME_OF_UNITTESTMANAGER_CLASS
+ *
If your system requires some configuration to run correctly, you can + * implement the {@link UnitTestManager} interface and be given an opportunity + * to initialize your system from a configuration file.
+ *
+ * + * @see UnitTestManager + */ +public final class AllTests { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Private constructor to prevent instantiation. + */ + private AllTests() { + } + + private static void logprop(String prop, boolean show) { + String value = System.getProperty(prop); + log.info(prop + "=" + value); + if (show) { + System.out.println(prop + "=" + value); + } + } + + private static void logprop(String prop) { + logprop(prop, false); + } + + /** + * Starts a run through all unit tests found in the specified classpaths. + * The first argument should be a list of paths to search. The second + * argument is optional and specifies a properties file used to initialize + * logging. The third argument is also optional, and specifies a class that + * implements the UnitTestManager interface. This provides a means of + * initializing your application with a configuration file prior to the + * start of any unit tests. + * + * @param args + * the command line arguments + */ + public static void main(String[] args) { + if (args.length < 1) { + System.out.println("You must specify a comma-delimited list of paths to search " + "for unit tests"); + return; + } + String home=new File(System.getProperty("user.dir")).getParent(); + System.out.println("Setting JMeterHome: "+home); + JMeterUtils.setJMeterHome(home); + initializeLogging(args); + initializeManager(args); + + String version = "JMeterVersion="+JMeterUtils.getJMeterVersion(); + log.info(version); + System.out.println(version); + logprop("java.version", true); + logprop("java.vm.name"); + logprop("java.vendor"); + logprop("java.home", true); + logprop("file.encoding", true); + // Display actual encoding used (will differ if file.encoding is not recognised) + String msg = "default encoding="+Charset.defaultCharset(); + System.out.println(msg); + log.info(msg); + logprop("user.home"); + logprop("user.dir", true); + logprop("user.language"); + logprop("user.region"); + logprop("user.country"); + logprop("user.variant"); + final String showLocale = "Locale="+Locale.getDefault().toString(); + log.info(showLocale); + System.out.println(showLocale); + logprop("os.name", true); + logprop("os.version", true); + logprop("os.arch"); + logprop("java.class.version"); + // logprop("java.class.path"); + String cp = System.getProperty("java.class.path"); + String cpe[] = JOrphanUtils.split(cp, java.io.File.pathSeparator); + StringBuilder sb = new StringBuilder(3000); + sb.append("java.class.path="); + for (int i = 0; i < cpe.length; i++) { + sb.append("\n"); + sb.append(cpe[i]); + if (new java.io.File(cpe[i]).exists()) { + sb.append(" - OK"); + } else { + sb.append(" - ??"); + } + } + log.info(sb.toString()); + + // ++ + // GUI tests throw the error + // testArgumentCreation(org.apache.jmeter.config.gui.ArgumentsPanel$Test)java.lang.NoClassDefFoundError + // at java.lang.Class.forName0(Native Method) + // at java.lang.Class.forName(Class.java:141) + // at + // java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(GraphicsEnvironment.java:62) + // + // Try to find out why this is ... + + System.out.println("+++++++++++"); + logprop("java.awt.headless", true); + logprop("java.awt.graphicsenv", true); + // + // try {// + // Class c = Class.forName(n); + // System.out.println("Found class: "+n); + // // c.newInstance(); + // // System.out.println("Instantiated: "+n); + // } catch (Exception e1) { + // System.out.println("Error finding class "+n+" "+e1); + // } catch (java.lang.InternalError e1){ + // System.out.println("Error finding class "+n+" "+e1); + // } + // + System.out.println("------------"); + // don't call isHeadless() here, as it has a side effect. + // -- + System.out.println("Creating test suite"); + TestSuite suite = suite(args[0]); + int countTestCases = suite.countTestCases(); + System.out.println("Starting test run, test count = "+countTestCases); +// for (int i=0;i= 2) { + Properties props = new Properties(); + InputStream inputStream = null; + try { + System.out.println("Setting up logging props using file: " + args[1]); + inputStream = new FileInputStream(args[1]); + props.load(inputStream); + LoggingManager.initializeLogging(props); + } catch (FileNotFoundException e) { + System.out.println(e.getLocalizedMessage()); + } catch (IOException e) { + System.out.println(e.getLocalizedMessage()); + } finally { + JOrphanUtils.closeQuietly(inputStream); + } + } + } + + /** + * An overridable method that that instantiates a UnitTestManager (if one + * was specified in the command-line arguments), and hands it the name of + * the properties file to use to configure the system. + * + * @param args + */ + protected static void initializeManager(String[] args) { + if (args.length >= 3) { + try { + System.out.println("Using initializeProperties() from " + args[2]); + UnitTestManager um = (UnitTestManager) Class.forName(args[2]).newInstance(); + System.out.println("Setting up initial properties using: " + args[1]); + um.initializeProperties(args[1]); + } catch (ClassNotFoundException e) { + System.out.println("Couldn't create: " + args[2]); + e.printStackTrace(); + } catch (InstantiationException e) { + System.out.println("Couldn't create: " + args[2]); + e.printStackTrace(); + } catch (IllegalAccessException e) { + System.out.println("Couldn't create: " + args[2]); + e.printStackTrace(); + } + } + } + + /* + * Externally callable suite() method for use by JUnit Allows tests to be + * run directly under JUnit, rather than using the startup code in the rest + * of the module. No parameters can be passed in, so it is less flexible. + */ + public static TestSuite suite() { + String args[] = { "../lib/ext", "./jmetertest.properties", "org.apache.jmeter.util.JMeterUtils" }; + + initializeManager(args); + return suite(args[0]); + } + + /** + * A unit test suite for JUnit. + * + * @return The test suite + */ + private static TestSuite suite(String searchPaths) { + TestSuite suite = new TestSuite("All Tests"); + System.out.println("Scanning "+searchPaths+ " for test cases"); + int tests=0; + int suites=0; + try { + log.info("ClassFinder(TestCase)"); + List classList = ClassFinder.findClassesThatExtend(JOrphanUtils.split(searchPaths, ","), + new Class[] { TestCase.class }, true); + int sz=classList.size(); + log.info("ClassFinder(TestCase) found: "+sz+ " TestCase classes"); + System.out.println("ClassFinder found: "+sz+ " TestCase classes"); + for (String name : classList) { + try { + /* + * TestSuite only finds testXXX() methods, and does not look + * for suite() methods. + * + * To provide more compatibilty with stand-alone tests, + * where JUnit does look for a suite() method, check for it + * first here. + * + */ + + Class clazz = Class.forName(name); + Test t = null; + try { + Method m = clazz.getMethod("suite", new Class[0]); + t = (Test) m.invoke(clazz, (Object[])null); + suites++; + } catch (NoSuchMethodException e) { + } // this is not an error, the others are + // catch (SecurityException e) {} + // catch (IllegalAccessException e) {} + // catch (IllegalArgumentException e) {} + // catch (InvocationTargetException e) {} + + if (t == null) { + t = new TestSuite(clazz); + } + + tests++; + suite.addTest(t); + } catch (Exception ex) { + System.out.println("ERROR: (see logfile) could not add test for class " + name + " " + ex.toString()); + log.error("error adding test :", ex); + } + } + } catch (IOException e) { + log.error("", e); + } + System.out.println("Created: "+tests+" tests including "+suites+" suites"); + return suite; + } +} diff --git a/ApacheJmeter/org/apache/jorphan/util/TestJorphanUtils.java b/ApacheJmeter/org/apache/jorphan/util/TestJorphanUtils.java new file mode 100644 index 0000000..f5f03d0 --- /dev/null +++ b/ApacheJmeter/org/apache/jorphan/util/TestJorphanUtils.java @@ -0,0 +1,317 @@ +/* + * 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 to test JOrphanUtils methods + */ + +package org.apache.jorphan.util; + +import junit.framework.TestCase; + +public class TestJorphanUtils extends TestCase { + + public TestJorphanUtils() { + super(); + } + + public TestJorphanUtils(String arg0) { + super(arg0); + } + + public void testReplace1() { + assertEquals("xyzdef", JOrphanUtils.replaceFirst("abcdef", "abc", "xyz")); + } + + public void testReplace2() { + assertEquals("axyzdef", JOrphanUtils.replaceFirst("abcdef", "bc", "xyz")); + } + + public void testReplace3() { + assertEquals("abcxyz", JOrphanUtils.replaceFirst("abcdef", "def", "xyz")); + } + + public void testReplace4() { + assertEquals("abcdef", JOrphanUtils.replaceFirst("abcdef", "bce", "xyz")); + } + + public void testReplace5() { + assertEquals("abcdef", JOrphanUtils.replaceFirst("abcdef", "alt=\"\" ", "")); + } + + public void testReplace6() { + assertEquals("abcdef", JOrphanUtils.replaceFirst("abcdef", "alt=\"\" ", "")); + } + + public void testReplace7() { + assertEquals("alt=\"\"", JOrphanUtils.replaceFirst("alt=\"\"", "alt=\"\" ", "")); + } + + public void testReplace8() { + assertEquals("img src=xyz ", JOrphanUtils.replaceFirst("img src=xyz alt=\"\" ", "alt=\"\" ", "")); + } + + // Note: the split tests should agree as far as possible with CSVSaveService.csvSplitString() + + // Tests for split(String,String,boolean) + public void testSplit1() { + String in = "a,bc,,"; // Test ignore trailing split characters + String out[] = JOrphanUtils.split(in, ",",true);// Ignore adjacent delimiters + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + out = JOrphanUtils.split(in, ",",false); + assertEquals("Should detect the trailing split chars; ", 4, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + assertEquals("", out[2]); + assertEquals("", out[3]); + } + + public void testSplit2() { + String in = ",,a,bc"; // Test leading split characters + String out[] = JOrphanUtils.split(in, ",",true); + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + out = JOrphanUtils.split(in, ",",false); + assertEquals("Should detect the leading split chars; ", 4, out.length); + assertEquals("", out[0]); + assertEquals("", out[1]); + assertEquals("a", out[2]); + assertEquals("bc", out[3]); + } + + public void testSplit3() { + String in = "a,bc,,"; // Test ignore trailing split characters + String out[] = JOrphanUtils.split(in, ",",true);// Ignore adjacent delimiters + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + out = JOrphanUtils.split(in, ",",false); + assertEquals("Should detect the trailing split chars; ", 4, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + assertEquals("", out[2]); + assertEquals("", out[3]); + } + + public void testSplit4() { + String in = " , ,a ,bc"; // Test leading split characters + String out[] = JOrphanUtils.split(in, " ,",true); + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + out = JOrphanUtils.split(in, " ,",false); + assertEquals("Should detect the leading split chars; ", 4, out.length); + assertEquals("", out[0]); + assertEquals("", out[1]); + assertEquals("a", out[2]); + assertEquals("bc", out[3]); + } + + public void testTruncate() throws Exception + { + String in = "a;,b;,;,;,d;,e;,;,f"; + String[] out = JOrphanUtils.split(in,";,",true); + assertEquals(5, out.length); + assertEquals("a",out[0]); + assertEquals("b",out[1]); + assertEquals("d",out[2]); + assertEquals("e",out[3]); + assertEquals("f",out[4]); + out = JOrphanUtils.split(in,";,",false); + assertEquals(8, out.length); + assertEquals("a",out[0]); + assertEquals("b",out[1]); + assertEquals("", out[2]); + assertEquals("", out[3]); + assertEquals("d",out[4]); + assertEquals("e",out[5]); + assertEquals("", out[6]); + assertEquals("f",out[7]); + + } + + public void testSplit5() throws Exception + { + String in = "a;;b;;;;;;d;;e;;;;f"; + String[] out = JOrphanUtils.split(in,";;",true); + assertEquals(5, out.length); + assertEquals("a",out[0]); + assertEquals("b",out[1]); + assertEquals("d",out[2]); + assertEquals("e",out[3]); + assertEquals("f",out[4]); + out = JOrphanUtils.split(in,";;",false); + assertEquals(8, out.length); + assertEquals("a",out[0]); + assertEquals("b",out[1]); + assertEquals("", out[2]); + assertEquals("", out[3]); + assertEquals("d",out[4]); + assertEquals("e",out[5]); + assertEquals("", out[6]); + assertEquals("f",out[7]); + + } + + // Empty string + public void testEmpty(){ + String out[] = JOrphanUtils.split("", ",",false); + assertEquals(0,out.length); + } + + // Tests for split(String,String,String) + public void testSplitSSS1() { + String in = "a,bc,,"; // Test non-empty parameters + String out[] = JOrphanUtils.split(in, ",","?"); + assertEquals(4, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + assertEquals("?", out[2]); + assertEquals("?", out[3]); + } + + public void testSplitSSS2() { + String in = "a,bc,,"; // Empty default + String out[] = JOrphanUtils.split(in, ",",""); + assertEquals(4, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + assertEquals("", out[2]); + assertEquals("", out[3]); + } + + public void testSplitSSS3() { + String in = "a,bc,,"; // Empty delimiter + String out[] = JOrphanUtils.split(in, "","?"); + assertEquals(1, out.length); + assertEquals(in, out[0]); + } + + public void testSplitSSS4() { + String in = "a,b;c,,"; // Multiple delimiters + String out[]; + out = JOrphanUtils.split(in, ",;","?"); + assertEquals(5, out.length); + assertEquals("a", out[0]); + assertEquals("b", out[1]); + assertEquals("c", out[2]); + assertEquals("?", out[3]); + assertEquals("?", out[4]); + out = JOrphanUtils.split(in, ",;",""); + assertEquals(5, out.length); + assertEquals("a", out[0]); + assertEquals("b", out[1]); + assertEquals("c", out[2]); + assertEquals("", out[3]); + assertEquals("", out[4]); + } + + public void testSplitSSS5() { + String in = "a,bc,,"; // Delimiter same as splitter + String out[] = JOrphanUtils.split(in, ",",","); + assertEquals(4, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + assertEquals(",", out[2]); + assertEquals(",", out[3]); + } + + public void testSplitSSSNulls() { + String in = "a,bc,,"; + String out[]; + try { + out = JOrphanUtils.split(null, ",","?"); + assertEquals(0, out.length); + fail("Expecting NullPointerException"); + } catch (NullPointerException ignored){ + //Ignored + } + try{ + out = JOrphanUtils.split(in, null,"?"); + assertEquals(0, out.length); + fail("Expecting NullPointerException"); + } catch (NullPointerException ignored){ + //Ignored + } + } + + public void testSplitSSSNull() { + String out[]; + out = JOrphanUtils.split("a,bc,,", ",",null); + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + + out = JOrphanUtils.split("a,;bc,;,", ",;",null); + assertEquals(2, out.length); + assertEquals("a", out[0]); + assertEquals("bc", out[1]); + } + + public void testSplitSSSNone() { + String out[]; + out = JOrphanUtils.split("", "," ,"x"); + assertEquals(0, out.length); + + out = JOrphanUtils.split("a,;bc,;,", "","x"); + assertEquals(1, out.length); + assertEquals("a,;bc,;,", out[0]); + } + + public void testreplaceAllChars(){ + assertEquals(JOrphanUtils.replaceAllChars("",' ', "+"),""); + String in,out; + in="source"; + assertEquals(JOrphanUtils.replaceAllChars(in,' ', "+"),in); + out="so+rce"; + assertEquals(JOrphanUtils.replaceAllChars(in,'u', "+"),out); + in="A B C "; out="A+B++C+"; + assertEquals(JOrphanUtils.replaceAllChars(in,' ', "+"),out); + } + + public void testTrim(){ + assertEquals("",JOrphanUtils.trim("", " ;")); + assertEquals("",JOrphanUtils.trim(" ", " ;")); + assertEquals("",JOrphanUtils.trim("; ", " ;")); + assertEquals("",JOrphanUtils.trim(";;", " ;")); + assertEquals("",JOrphanUtils.trim(" ", " ;")); + assertEquals("abc",JOrphanUtils.trim("abc ;", " ;")); + } + + public void testbaToHexString(){ + assertEquals("",JOrphanUtils.baToHexString(new byte[]{})); + assertEquals("00",JOrphanUtils.baToHexString(new byte[]{0})); + assertEquals("0f107f8081ff",JOrphanUtils.baToHexString(new byte[]{15,16,127,-128,-127,-1})); + } + + public void testbaToByte() throws Exception{ + assertEqualsArray(new byte[]{},JOrphanUtils.baToHexBytes(new byte[]{})); + assertEqualsArray(new byte[]{'0','0'},JOrphanUtils.baToHexBytes(new byte[]{0})); + assertEqualsArray("0f107f8081ff".getBytes("UTF-8"),JOrphanUtils.baToHexBytes(new byte[]{15,16,127,-128,-127,-1})); + } + + private void assertEqualsArray(byte[] expected, byte[] actual){ + assertEquals("arrays must be same length",expected.length, actual.length); + for(int i=0; i < expected.length; i++){ + assertEquals("values must be the same for index: "+i,expected[i],actual[i]); + } + } +} diff --git a/ApacheJmeter/rat-excludes.txt b/ApacheJmeter/rat-excludes.txt new file mode 100644 index 0000000..d9977c7 --- /dev/null +++ b/ApacheJmeter/rat-excludes.txt @@ -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. + + Excludes file for Apache RAT tool run by ASF Buildbot, + http://incubator.apache.org/rat/ + +**/*.jmx +bin/examples/** +bin/testfiles/** +build/** +docs/** +extras/*.txt +lib/aareadme.txt +src/core/org/apache/jmeter/help.txt +*.log +**/download_jmeter.cgi \ No newline at end of file diff --git a/ApacheJmeter/res/META-INF/default.license b/ApacheJmeter/res/META-INF/default.license new file mode 100644 index 0000000..75b5248 --- /dev/null +++ b/ApacheJmeter/res/META-INF/default.license @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/ApacheJmeter/res/META-INF/default.notice b/ApacheJmeter/res/META-INF/default.notice new file mode 100644 index 0000000..d072a7e --- /dev/null +++ b/ApacheJmeter/res/META-INF/default.notice @@ -0,0 +1,5 @@ +Apache Tomcat +Copyright 1999-@YEAR@ The Apache Software Foundation + +This product includes software developed by +The Apache Software Foundation (http://www.apache.org/). diff --git a/ApacheJmeter/res/maven/ApacheJMeter.pom b/ApacheJmeter/res/maven/ApacheJMeter.pom new file mode 100644 index 0000000..fd7cb76 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter.pom @@ -0,0 +1,29 @@ + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter + Apache JMeter launcher + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_components.pom b/ApacheJmeter/res/maven/ApacheJMeter_components.pom new file mode 100644 index 0000000..7bb32c3 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_components.pom @@ -0,0 +1,45 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_components + Apache JMeter Components + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_config.pom b/ApacheJmeter/res/maven/ApacheJMeter_config.pom new file mode 100644 index 0000000..46de923 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_config.pom @@ -0,0 +1,32 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_config + Apache JMeter Configuration + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_core.pom b/ApacheJmeter/res/maven/ApacheJMeter_core.pom new file mode 100644 index 0000000..53a45c2 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_core.pom @@ -0,0 +1,40 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_core + Apache JMeter Core + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_ftp.pom b/ApacheJmeter/res/maven/ApacheJMeter_ftp.pom new file mode 100644 index 0000000..3dcbc10 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_ftp.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_ftp + Apache JMeter FTP + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_functions.pom b/ApacheJmeter/res/maven/ApacheJMeter_functions.pom new file mode 100644 index 0000000..d2baac9 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_functions.pom @@ -0,0 +1,45 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_functions + Apache JMeter Functions + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_http.pom b/ApacheJmeter/res/maven/ApacheJMeter_http.pom new file mode 100644 index 0000000..cbf8950 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_http.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_http + Apache JMeter HTTP + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_java.pom b/ApacheJmeter/res/maven/ApacheJMeter_java.pom new file mode 100644 index 0000000..803b4f7 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_java.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_java + Apache JMeter Java + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_jdbc.pom b/ApacheJmeter/res/maven/ApacheJMeter_jdbc.pom new file mode 100644 index 0000000..3dfd559 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_jdbc.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_jdbc + Apache JMeter JDBC + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_jms.pom b/ApacheJmeter/res/maven/ApacheJMeter_jms.pom new file mode 100644 index 0000000..c5b822c --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_jms.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_jms + Apache JMeter JMS + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_junit-test.pom b/ApacheJmeter/res/maven/ApacheJMeter_junit-test.pom new file mode 100644 index 0000000..6d47cc3 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_junit-test.pom @@ -0,0 +1,31 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_junit-test + Apache JMeter JUnit sample test classes + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_junit.pom b/ApacheJmeter/res/maven/ApacheJMeter_junit.pom new file mode 100644 index 0000000..121a19f --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_junit.pom @@ -0,0 +1,44 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_junit + Apache JMeter jUnit + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_ldap.pom b/ApacheJmeter/res/maven/ApacheJMeter_ldap.pom new file mode 100644 index 0000000..9383599 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_ldap.pom @@ -0,0 +1,49 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_ldap + Apache JMeter LDAP + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_mail.pom b/ApacheJmeter/res/maven/ApacheJMeter_mail.pom new file mode 100644 index 0000000..8f68333 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_mail.pom @@ -0,0 +1,49 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_mail + Apache JMeter Mail + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_monitors.pom b/ApacheJmeter/res/maven/ApacheJMeter_monitors.pom new file mode 100644 index 0000000..f011180 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_monitors.pom @@ -0,0 +1,54 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_monitors + Apache JMeter Monitor + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_http + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_native.pom b/ApacheJmeter/res/maven/ApacheJMeter_native.pom new file mode 100644 index 0000000..0e80da7 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_native.pom @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + ApacheJMeter_native + Apache JMeter Native + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_parent.pom b/ApacheJmeter/res/maven/ApacheJMeter_parent.pom new file mode 100644 index 0000000..61f56e2 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_parent.pom @@ -0,0 +1,343 @@ + + + 4.0.0 + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + Apache JMeter parent + + pom + + Apache JMeter is open source software, a 100% pure Java desktop application designed to load test + functional behavior and measure performance. It was originally designed for testing Web Applications but has + since expanded to other test functions. + + http://jmeter.apache.org/ + 1998 + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + bugzilla + https://issues.apache.org/bugzilla/describecomponents.cgi?product=JMeter + + + http://svn.apache.org/repos/asf/jmeter/trunk/ + https://svn.apache.org/repos/asf/jmeter/trunk/ + http://svn.apache.org/viewvc/jmeter/trunk/ + + + + + + 2.4.0 + 3.1 + 4.1.4 + 2.0b5 + 1.45 + 1.45 + 1.6 + 3.2.1 + 3.1 + 2.2 + 1.1 + 2.1.1 + 2.6 + 1.1.1 + 3.1 + 1.1.1 + 1.0 + 1.1 + 1.2 + 2.1 + 4.1.3 + 4.1.4 + 2.0.8 + 0.7.5 + 1.1.2 + 1.7R3 + 4.10 + 2.0 + 2.3.1 + r938 + 1.1.3.1 + 1.4.2 + 1.1.4c + 2.7.1 + 2.7.1 + 2.9.1 + 1.3.04 + 1.3.1 + 1.1.1 + 1.4.4 + 1.1.1 + 1.7 + + + + + bsf + bsf + ${apache-bsf.version} + + + org.apache.bsf + bsf-api + ${apache-jsr223-api.version} + + + avalon-framework + avalon-framework + ${avalon-framework.version} + + + org.beanshell + bsh + ${beanshell.version} + + + org.bouncycastle + bcmail-jdk15 + ${bcmail.version} + + + org.bouncycastle + bcprov-jdk15 + ${bcprov.version} + + + commons-codec + commons-codec + ${commons-codec.version} + + + commons-collections + commons-collections + ${commons-collections.version} + + + commons-httpclient + commons-httpclient + ${commons-httpclient.version} + + + commons-io + commons-io + ${commons-io.version} + + + commons-jexl + commons-jexl + ${commons-jexl.version} + + + org.apache.commons + commons-jexl + ${commons-jexl2.version} + + + commons-lang + commons-lang + ${commons-lang.version} + + + commons-logging + commons-logging + ${commons-logging.version} + + + commons-net + commons-net + ${commons-net.version} + + + excalibur-datasource + excalibur-datasource + ${excalibur-datasource.version} + + + excalibur-instrument + excalibur-instrument + ${excalibur-instrument.version} + + + excalibur-logger + excalibur-logger + ${excalibur-logger.version} + + + excalibur-pool + excalibur-pool + ${excalibur-pool.version} + + + org.htmlparser + htmllexer + ${htmlparser.version} + + + org.htmlparser + htmlparser + ${htmlparser.version} + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + org.apache.httpcomponents + httpmime + ${httpclient.version} + + + org.apache.httpcomponents + httpcore + ${httpcore.version} + + + oro + oro + ${jakarta-oro.version} + + + jcharts + jcharts + 0.7.5 + + + org.jdom + jdom + ${jdom.version} + + + + jaxen + jaxen + + + + + org.mozilla + rhino + ${js_rhino.version} + + + junit + junit + ${junit.version} + + + logkit + logkit + ${logkit.version} + + + soap + soap + ${soap.version} + + + net.sf.jtidy + jtidy + ${tidy.version} + + + com.thoughtworks.xstream + xstream + ${xstream.version} + + + xmlpull + xmlpull + ${xmlpull.version} + + + xpp3 + xpp3_min + ${xpp3.version} + + + xalan + xalan + ${xalan.version} + + + xalan + serializer + ${serializer.version} + + + xerces + xercesImpl + ${xerces.version} + + + xml-apis + xml-apis + ${xml-apis.version} + + + org.apache.xmlgraphics + xmlgraphics-commons + ${xmlgraphics-commons.version} + + + javax.activation + activation + ${activation.version} + + + javax.mail + mail + ${javamail.version} + + + org.apache.geronimo.specs + geronimo-jms_1.1_spec + ${jms.version} + + + + + + + + MVN Search + http://www.mvnsearch.org/maven2 + + + + JBoss + https://repository.jboss.org/nexus/content/repositories/thirdparty-releases/ + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_report.pom b/ApacheJmeter/res/maven/ApacheJMeter_report.pom new file mode 100644 index 0000000..e6afb60 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_report.pom @@ -0,0 +1,49 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_report + Apache JMeter Report plugin + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/ApacheJMeter_tcp.pom b/ApacheJmeter/res/maven/ApacheJMeter_tcp.pom new file mode 100644 index 0000000..d0d5b35 --- /dev/null +++ b/ApacheJmeter/res/maven/ApacheJMeter_tcp.pom @@ -0,0 +1,49 @@ + + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + ApacheJMeter_tcp + Apache JMeter TCP + + + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_core + @MAVEN.DEPLOY.VERSION@ + + + org.apache.jmeter + ApacheJMeter_components + @MAVEN.DEPLOY.VERSION@ + + + + \ No newline at end of file diff --git a/ApacheJmeter/res/maven/jorphan.pom b/ApacheJmeter/res/maven/jorphan.pom new file mode 100644 index 0000000..68e785f --- /dev/null +++ b/ApacheJmeter/res/maven/jorphan.pom @@ -0,0 +1,32 @@ + + + 4.0.0 + + org.apache.jmeter + ApacheJMeter_parent + @MAVEN.DEPLOY.VERSION@ + . + + org.apache.jmeter + jorphan + @MAVEN.DEPLOY.VERSION@ + Apache JMeter jorphan library + + \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources.properties new file mode 100644 index 0000000..07d846b --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources.properties @@ -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. + +displayName=BSF Assertion +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props SampleResult (aka prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_fr.properties new file mode 100644 index 0000000..e9a01b0 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=Assertion BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script en langage BSF appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom de langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props SampleResult (aka prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pl.properties b/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pl.properties new file mode 100644 index 0000000..459ca25 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pl.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# Uwagi co do tlumaczenia: mr0vek@o2.pl + +displayName=Asercje BSF +filename.displayName=Nazwa pliku +filename.shortDescription=Plik ze skryptem (zast\u0119puje skrypt) +filenameGroup.displayName=Plik ze skryptem (zast\u0119puje skrypt) +parameterGroup.displayName=Parametry dla skryptu (=> String Parameters and String []args) +parameters.displayName=Parametry +parameters.shortDescription=Parametry do przekazania do pliku lub skryptu +script.displayName=Skrypt +script.shortDescription=Skrypt w j\u0119zyku zgodnym z BSF +scriptLanguage.displayName=J\u0119zyk +scriptLanguage.shortDescription=Nazwa j\u0119zyka BSF, np. beanshell, javascript, jexl +scripting.displayName=Skrypt (zmienne: ctx vars props SampleResult (aka prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=J\u0119zyk skryptu (np. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pt_BR.properties new file mode 100644 index 0000000..f27603e --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/assertions/BSFAssertionResources_pt_BR.properties @@ -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. + +displayName=Asser\u00E7\u00E3o BSF +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao script (\=> String Parameters e String []args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao arquivo ou script +script.displayName=Script +script.shortDescription=Script na linguagem BSF apropriada +scriptLanguage.displayName=Linguagem +scriptLanguage.shortDescription=Nome da linguagem BSF, ex\: beanshell, javascript, jexl +scripting.displayName=Script (vari\u00E1veis\: ctx vars props SampleResult (aka prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Linguagem do script (ex\: beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/assertions/CompareAssertionResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/assertions/CompareAssertionResources.properties new file mode 100644 index 0000000..0d17550 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/assertions/CompareAssertionResources.properties @@ -0,0 +1,24 @@ +# 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. + +displayName=Compare Assertion +compareChoices.displayName=Select Comparison Operators +compareContent.displayName=Compare Content +compareContent.shortDescription=Verify that all Samplers within the Controller return the same data +compareTime.displayName=Compare Time +compareTime.shortDescription=Verify that all Samplers' return times are within a given number of milliseconds +comparison_filters.displayName=Comparison Filters +stringsToSkip.displayName=Regular Expression Substitutions +stringsToSkip.shortDescription=Regular expressions to match elements of response data to be substituted when comparing diff --git a/ApacheJmeter/src/components/org/apache/jmeter/assertions/CompareAssertionResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/assertions/CompareAssertionResources_fr.properties new file mode 100644 index 0000000..84c01da --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/assertions/CompareAssertionResources_fr.properties @@ -0,0 +1,24 @@ +# 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. + +compareChoices.displayName=Type de comparaison +compareContent.displayName=Contenu +compareContent.shortDescription=V\u00E9rifie que tous les \u00E9chantillons fils d'un contr\u00F4leur retournent les m\u00EAmes donn\u00E9es +compareTime.displayName=Temps de r\u00E9ponse (ms) +compareTime.shortDescription=V\u00E9rifie que tous les \u00E9chantillons fils d'un contr\u00F4leur sont retourn\u00E9s dans un nombre donn\u00E9 de millisecondes +comparison_filters.displayName=Filtres de comparaison +displayName=Assertion Comparaison +stringsToSkip.displayName=Substitutions par expressions r\u00E9guli\u00E8res +stringsToSkip.shortDescription=Expressions r\u00E9guli\u00E8res pour substituer des \u00E9l\u00E9ments dans les donn\u00E9es de r\u00E9ponses avant la comparaison diff --git a/ApacheJmeter/src/components/org/apache/jmeter/assertions/JSR223AssertionResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/assertions/JSR223AssertionResources.properties new file mode 100644 index 0000000..aca3750 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/assertions/JSR223AssertionResources.properties @@ -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. + +displayName=JSR223 Assertion +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR223 language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props SampleResult (aka prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/assertions/JSR223AssertionResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/assertions/JSR223AssertionResources_fr.properties new file mode 100644 index 0000000..8f7d31c --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/assertions/JSR223AssertionResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=Assertion JSR223 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props SampleResult (avant prev) AssertionResult sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/assertions/package.html b/ApacheJmeter/src/components/org/apache/jmeter/assertions/package.html new file mode 100644 index 0000000..0d8520e --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/assertions/package.html @@ -0,0 +1,33 @@ + + + + Assertions + + +

Assertions

+

Methods to be implemented

+ getResult(SampleResult) +

Calling sequence

+

When the test plan is prepared for running, one instance of the class is created for each occurrence + of an assertion in each thread.

+

Assertions are called from the same thread as the sampler

+ + \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources.properties new file mode 100644 index 0000000..476bff3 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources.properties @@ -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. + +displayName=CSV Data Set Config +csv_data.displayName=Configure the CSV Data Source +filename.displayName=Filename +filename.shortDescription=Name of the file that holds the cvs data (relative or absolute filename) +fileEncoding.displayName=File encoding +fileEncoding.shortDescription=The character set encoding used in the file +variableNames.displayName=Variable Names (comma-delimited) +variableNames.shortDescription=List your variable names in order to match the order of columns in your csv data. Separate by commas. +delimiter.displayName=Delimiter (use '\\t' for tab) +delimiter.shortDescription=Enter the delimiter ('\\t' for tab) +quotedData.displayName=Allow quoted data? +quotedData.shortDescription=Allow CSV data values to be quoted? +recycle.displayName=Recycle on EOF ? +recycle.shortDescription=Should the file be re-read from the start on reaching EOF ? +stopThread.displayName=Stop thread on EOF ? +stopThread.shortDescription=Should the thread be stopped on reaching EOF (if Recycle is false) ? +shareMode.displayName=Sharing mode +shareMode.shortDescription=Select which threads share the same file pointer +shareMode.all=All threads +shareMode.group=Current thread group +shareMode.thread=Current thread diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_de.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_de.properties new file mode 100644 index 0000000..4daacd6 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_de.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +csv_data.displayName=Einstellungen der CSV Quellen +delimiter.displayName=Trennzeichen ('\\t' f\u00FCr Tab) +delimiter.shortDescription=Geben sie ein Trennzeichen ein ('\\t' f\u00FCr Tab) +displayName=CSV Einstellungen +fileEncoding.displayName=Zeichensatz der Datei +fileEncoding.shortDescription=Der, in der Datei, benutzte Zeichensatz +filename.displayName=Dateiname +filename.shortDescription=Name der Datei, die die CSV Daten enth\u00E4lt (relativer oder absoluter Pfadname m\u00F6glich) +recycle.displayName=Datei erneut einlesen? +recycle.shortDescription=Soll die Datei nach dem erreichen des Dateiendes erneut eingelesen werden? +stopThread.displayName=Thread stoppen? +stopThread.shortDescription=Bei erreichen des Dateiende den Thread stoppen? +variableNames.displayName=Variablenname (getrennt durch Komma) +variableNames.shortDescription=Geben sie die Variablennamen, durch Komma getrennt, ein, die den Spalten ihrer CSV Datei entsprechen. Die Variablennamen m\u00FCssen der Reihenfolge der CVS Datei entsprechen\! diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_es.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_es.properties new file mode 100644 index 0000000..1ffb0fd --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_es.properties @@ -0,0 +1,37 @@ +# 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. + +#Stored by I18NEdit, may be edited! +csv_data.displayName=Configura el Data Source de CSV +delimiter.displayName=Delimitador (utilice '\\t' para poner un tabulador) +delimiter.shortDescription=Introduzca el delimitador (utilice '\\t' para poner un tabulador) +displayName=Configuraci\u00F3n del CSV Data Set +fileEncoding.displayName=Codificaci\u00F3n del fichero +fileEncoding.shortDescription=El conjunto de caracteres usado en el fichero +filename.displayName=Nombre de Archivo +filename.shortDescription=Nombre del archivo (dentro de su directorio de archivos) que mantiene los datos CVS +quotedData.displayName=\u00BFPermitir datos entrecomillados? +quotedData.shortDescription=\u00BFPermitir que valores de datos CSV sean entrecomillados? +recycle.displayName=\u00BFReciclar en el fin de fichero (EOF)? +recycle.shortDescription=\u00BFDeber\u00EDa el fichero ser rele\u00EDdo desde el comiendo cuando se alcance el final del fichero (EOF)? +shareMode.all=Todos los hilos +shareMode.displayName=Modo compartido +shareMode.group=Actual grupo de hilos +shareMode.shortDescription=Seleccionar que hilos comparten el mismo puntero a fichero +shareMode.thread=Hilo actual +stopThread.displayName=\u00BFPara el hilo al final del fichero (EOF)? +stopThread.shortDescription=\u00BFDeber\u00EDa el hilo ser parado cuando se alcance el final del fichero(EOF) (Si 'Reciclar' es falso?) +variableNames.displayName=Nombres de Variable (delimitados por coma) +variableNames.shortDescription=Lista sus nombres de variable para ordenar las columnas en sus datos CSV.\nSeparados por comas. diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_fr.properties new file mode 100644 index 0000000..87bcb0e --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_fr.properties @@ -0,0 +1,37 @@ +# 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. + +#Stored by I18NEdit, may be edited! +csv_data.displayName=Configuration de la source de donn\u00E9es CSV +delimiter.displayName=D\u00E9limiteur (utiliser '\\t' pour la tabulation) +delimiter.shortDescription=D\u00E9limiteur (utiliser '\\t' pour la tabulation) +displayName=Source de donn\u00E9es CSV +fileEncoding.displayName=Encodage du fichier +fileEncoding.shortDescription=Encodage des caract\u00E8res utilis\u00E9s dans le fichier +filename.displayName=Nom de fichier +filename.shortDescription=Nom du fichier qui contient des donn\u00E9es CSV (chemin relatif ou absolu) +quotedData.displayName=Autoriser les donn\u00E9es avec des quotes ? +quotedData.shortDescription=Permettre aux valeurs des donn\u00E9es CSV d'\u00EAtre quot\u00E9es ? +recycle.displayName=Recycler en fin de fichier (EOF) ? +recycle.shortDescription=Voulez-vous que le fichier soit relu depuis son d\u00E9but apr\u00E8s avoir atteint la fin de fichier (EOF) ? +shareMode.all=Toutes les unit\u00E9s +shareMode.displayName=Mode de partage +shareMode.group=Groupe d'unit\u00E9s courant +shareMode.shortDescription=S\u00E9lectionner les unit\u00E9s partageant le m\u00EAme pointeur de fichier +shareMode.thread=Unit\u00E9 courante +stopThread.displayName=Arr\u00EAter l'unit\u00E9 \u00E0 la fin de fichier (EOF) ? +stopThread.shortDescription=L'unit\u00E9 sera arr\u00EAt\u00E9e en atteignant la fin de fichier (EOF) (si Recycler est \u00E0 faux) ? +variableNames.displayName=Noms des variables (s\u00E9par\u00E9s par des virgules) +variableNames.shortDescription=Liste de vos variables dans l'ordre des colonnes de vos donn\u00E9es CSV. S\u00E9par\u00E9 par des virgules.\t diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_pl.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_pl.properties new file mode 100644 index 0000000..28f7081 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_pl.properties @@ -0,0 +1,37 @@ +#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net) +# 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. + +csv_data.displayName = Konfiguracja wczytywania plik\u00F3w CSV +delimiter.displayName=Separator (np. '\\t' oznacza TABa) +delimiter.shortDescription=Wpisz separator (np. '\\t' oznacza TABa) +displayName=Konfiguracja plik\u00F3w CSV +fileEncoding.displayName=Kodowanie pliku +fileEncoding.shortDescription=Strona kodowa znak\u00F3w u\u017Cywana w pliku +filename.displayName=Nazwa pliku +filename.shortDescription=Nazwa pliku CSV (wzgl\u0119dna lub absolutna) +quotedData.displayName=Zezwala\u0107 na dane "w cudzys\u0142owiach" ? +quotedData.shortDescription=Zezwala\u0107 na "otaczanie danych" cudzys\u0142owami? +recycle.displayName=Recycle on EOF ? +recycle.shortDescription=Je\u015Bli zaczn\u0119 czyta\u0107 plik w \u015Brodku i dojd\u0119 do ko\u0144ca (EOF) to mam doczyta\u0107 brakuj\u0105c\u0105 cz\u0119\u015B\u0107 z pocz\u0105tku pliku?\r\n +shareMode.all=Wszystkie w\u0105tki +shareMode.displayName=Tryb wsp\u00F3\u0142dzielenia +shareMode.group=Bie\u017C\u0105ca grupa w\u0105tk\u00F3w +shareMode.shortDescription=Wybierz w\u0105tki, kt\u00F3re powinny dzieli\u0107 ten sam wska\u017Anik do pliku +shareMode.thread=Bie\u017C\u0105cy w\u0105tek +stopThread.displayName=Zatrzyma\u0107 w\u0105tek na EOF ? +stopThread.shortDescription=Zatrzyma\u0107 w\u0105tek gdy dojdzie do ko\u0144ca pliku (EOF, je\u015Bli nie wybra\u0142e\u015B doczytywania brakuj\u0105cego fragmentu pliku z pocz\u0105tku)? +variableNames.displayName=Nazwy zmiennych (oddzielane przecinkami) +variableNames.shortDescription=Wpisz nazwy Twoich zmiennych, w odpowieniej kolejno\u015Bci, tak by pasowa\u0142y Ci do kolumn w pliku CSV. Nazwy oddzielaj przecinkami. diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_pt_BR.properties new file mode 100644 index 0000000..a65f01d --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_pt_BR.properties @@ -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. + +csv_data.displayName=Configurar fonte de dados CSV +delimiter.displayName=Separador (usar '\\t' para tabula\u00E7\u00F5es) +delimiter.shortDescription=Informe o separador ('\\t' para tabula\u00E7\u00F5es) +displayName=Configura\u00E7\u00E3o dos dados CSV +fileEncoding.displayName=Codifica\u00E7\u00E3o do arquivo (encoding) +fileEncoding.shortDescription=O conjunto de caracteres (charset) usado no arquivo +filename.displayName=Nome do arquivo +filename.shortDescription=Nome do arquivo que cont\u00E9m os dados csv (nome do arquivo relativo ou absoluto) +quotedData.displayName=Permitir dados com cita\u00E7\u00F5es? +quotedData.shortDescription=Permitir que valores de dados CSV possuam cita\u00E7\u00F5es (aspas) +recycle.displayName=Reciclar no final do arquivo (EOF)? +recycle.shortDescription=Os dados do arquivo devem ser lidos novamente a partir do in\u00EDcio ao alcan\u00E7ar o fim do arquivo (EOF)? +shareMode.all=Todos os usu\u00E1rios virtuais +shareMode.displayName=Modo de compartilhamento +shareMode.group=Grupo de usu\u00E1rios atual +shareMode.shortDescription=Selecionar quais usu\u00E1rios virtuais compartilham o mesmo ponteiro para o arquivo +shareMode.thread=Usu\u00E1rio virtual atual +stopThread.displayName=Finalizar usu\u00E1rio virtual no final do arquivo? +stopThread.shortDescription=O usu\u00E1rio virtual dever\u00E1 ser parado quando o fim do arquivo for alcan\u00E7ado (se Reciclar n\u00E3o est\u00E1 configurado)? +variableNames.displayName=Nomes das vari\u00E1veis (separados por v\u00EDrgula) +variableNames.shortDescription=Informar os nomes das vari\u00E1veis que representam as colunas na sua fonte de dados CSV. Separe por v\u00EDrgula. diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_tr.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_tr.properties new file mode 100644 index 0000000..be85369 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_tr.properties @@ -0,0 +1,30 @@ +# 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. + +#Stored by I18NEdit, may be edited! +csv_data.displayName=CSV Veri Kayna\u011F\u0131n\u0131 Ayarla +delimiter.displayName=S\u0131n\u0131rlay\u0131c\u0131 (tab i\u00E7in '\\t' kullan) +delimiter.shortDescription=S\u0131n\u0131rlay\u0131c\u0131 gir (tab i\u00E7in '\\t') +displayName=CSV Veri K\u00FCmesi Ayar\u0131 +fileEncoding.displayName=Dosya Kodlamas\u0131 +fileEncoding.shortDescription=Dosyada kullan\u0131lan karakter kodlamas\u0131 +filename.displayName=Dosya ad\u0131 +filename.shortDescription=cvs verisini tutan dosyan\u0131n ad\u0131 (g\u00F6reli veya tam dosya yolu) +recycle.displayName=Dosya sonunda geri d\u00F6n\u00FC\u015F\u00FCm ? +recycle.shortDescription=Dosya sonundan itibaren tekrar okunsun mu? +stopThread.displayName=Dosya sonunda i\u015F par\u00E7ac\u0131\u011F\u0131n\u0131 durdur ? +stopThread.shortDescription=\u0130\u015F par\u00E7ac\u0131c\u0131\u011F\u0131 dosya sonuna var\u0131ld\u0131\u011F\u0131nda durdurulsun mu (e\u011Fer geri d\u00F6n\u00FC\u015F\u00FCm etkin de\u011Filse) ? +variableNames.displayName=De\u011Fi\u015Fken isimleri (virg\u00FClle ayr\u0131lm\u0131\u015F) +variableNames.shortDescription=De\u011Fi\u015Fken isimlerini csv verisindeki kolon s\u0131ras\u0131yla \u00F6rt\u00FC\u015Fecek \u015Fekilde listele. Virg\u00FClle ay\u0131rarak. diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_zh_TW.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_zh_TW.properties new file mode 100644 index 0000000..f254f56 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/CSVDataSetResources_zh_TW.properties @@ -0,0 +1,22 @@ +# 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. + +#Stored by I18NEdit, may be edited! +csv_data.displayName=\u8A2D\u5B9A CSV \u8CC7\u6599\u4F86\u6E90 +displayName=CSV \u8CC7\u6599\u8A2D\u5B9A +filename.displayName=\u6A94\u540D +filename.shortDescription=\u5DF2\u63D0\u4F9B\u8DEF\u5F91\u4E2D CVS \u6A94\u540D +variableNames.displayName=\u8B8A\u6578\u540D\u7A31(\u4EE5\u9017\u865F\u5206\u9694) +variableNames.shortDescription=\u8207CSV\u6A94\u6848\u8CC7\u6599\u76F8\u5C0D\u61C9\u7684\u8B8A\u6578\u540D\u7A31, \u4EE5\u9017\u865F\u5206\u9694 diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/KeystoreConfigResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/KeystoreConfigResources.properties new file mode 100644 index 0000000..b22a02f --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/KeystoreConfigResources.properties @@ -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. + +displayName=Keystore Configuration +# Groups +aliases.displayName=Aliases selection configuration +# fields +preload.displayName=Preload +preload.shortDescription=Preload Keystore before test +startIndex.displayName=Alias Start index +startIndex.shortDescription=First index of Alias in Keystore +endIndex.displayName=Alias End index +endIndex.shortDescription=Last index of Alias in Keystore diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/KeystoreConfigResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/KeystoreConfigResources_fr.properties new file mode 100644 index 0000000..cfa1e40 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/KeystoreConfigResources_fr.properties @@ -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. + +displayName=Configuration du coffre de cl\u00E9s (JKS) +# Groups +aliases.displayName=S\u00E9lection des alias +# fields +preload.displayName=Pr\u00E9chargement +preload.shortDescription=Pr\u00E9chargement du coffre de cl\u00E9s (JKS) avant le d\u00E9marrage du test +startIndex.displayName=Num\u00E9ro d'index premi\u00E8re cl\u00E9 +startIndex.shortDescription=Num\u00E9ro d'index du premier alias de cl\u00E9 dans le coffre de cl\u00E9s (JKS) +endIndex.displayName=Num\u00E9ro d'index derni\u00E8re cl\u00E9 +endIndex.shortDescription=Num\u00E9ro d'index du dernier alias de cl\u00E9 dans le coffre de cl\u00E9s (JKS) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources.properties new file mode 100644 index 0000000..55c43f6 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources.properties @@ -0,0 +1,33 @@ +# 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. + +displayName=Random Variable +# Groups +variable.displayName=Output variable +random.displayName=Configure the Random generator +options.displayName=Options +# fields +minimumValue.displayName=Minimum Value +minimumValue.shortDescription=Minimum Value +maximumValue.displayName=Maximum Value +maximumValue.shortDescription=Maximum Value +variableName.displayName=Variable Name +variableName.shortDescription=Variable Name +outputFormat.displayName=Output Format +outputFormat.shortDescription=Output Format, e.g. #### +randomSeed.displayName=Seed for Random function +randomSeed.shortDescription=Seed for Random function - long number (defaults to current time) +perThread.displayName=Per Thread(User) ? +perThread.shortDescription=Use independent random generators for each thread(user) ? diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_es.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_es.properties new file mode 100644 index 0000000..132221a --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_es.properties @@ -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. + +#Stored by I18NEdit, may be edited! +displayName=Variable aleatoria +maximumValue.displayName=Valor m\u00E1ximo +maximumValue.shortDescription=Valor m\u00E1ximo +minimumValue.displayName=Valor m\u00EDnimo +minimumValue.shortDescription=Valor m\u00EDnimo +options.displayName=Opciones +outputFormat.displayName=Formato de salida +outputFormat.shortDescription=Formato de salida, e.g. \#\#\#\# +perThread.displayName=\u00BFPor hilo(Usuario)? +perThread.shortDescription=\u00BFUsar generadores aleatorios independientes para cada hilo (usuario)? +random.displayName=Configurar el generador aleatorio +randomSeed.displayName=Semilla para la funci\u00F3n aleatoria +randomSeed.shortDescription=Semilla para la funci\u00F3n aleatoria - n\u00FAmero long (por defecto para la hora actual) +variable.displayName=Variable de salida +variableName.displayName=Nombre de variable +variableName.shortDescription=Nombre de variable diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_fr.properties new file mode 100644 index 0000000..b185db4 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_fr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +displayName=Variable al\u00E9atoire +maximumValue.displayName=Valeur maximum +maximumValue.shortDescription=Valeur maximum +minimumValue.displayName=Valeur minimum +minimumValue.shortDescription=Valeur minimum +options.displayName=Options +outputFormat.displayName=Format de sortie +outputFormat.shortDescription=Format de sortie, i.e. \#\#\#\# +perThread.displayName=Par unit\u00E9 (utilisateur) ? +perThread.shortDescription=Utiliser des g\u00E9n\u00E9rateurs al\u00E9atoires ind\u00E9pendants pour chaque unit\u00E9 (utilisateur) ? +random.displayName=Param\u00E9trer le g\u00E9n\u00E9rateur al\u00E9atoire +randomSeed.displayName=Sels pour la fonction al\u00E9atoire +randomSeed.shortDescription=Sels pour la fonction al\u00E9atoire - chiffre long (par d\u00E9faut \u00E0 l'heure courante) +variable.displayName=Variable de sortie +variableName.displayName=Nom de variable +variableName.shortDescription=Nom de variable diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pl.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pl.properties new file mode 100644 index 0000000..a38f931 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pl.properties @@ -0,0 +1,34 @@ +# 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. +# Uwagi do tlumaczenia: mr0vek@o2.pl + +displayName=Random Variable +# Groups +variable.displayName=Output variable +random.displayName=Configure the Random generator +options.displayName=Options +# fields +minimumValue.displayName=Minimum Value +minimumValue.shortDescription=Minimum Value +maximumValue.displayName=Maximum Value +maximumValue.shortDescription=Maximum Value +variableName.displayName=Variable Name +variableName.shortDescription=Variable Name +outputFormat.displayName=Output Format +outputFormat.shortDescription=Output Format, e.g. #### +randomSeed.displayName=Seed for Random function +randomSeed.shortDescription=Seed for Random function - long number (defaults to current time) +perThread.displayName=Per Thread(User) ? +perThread.shortDescription=Use independent random generators for each thread(user) ? diff --git a/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pt_BR.properties new file mode 100644 index 0000000..388f717 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/config/RandomVariableConfigResources_pt_BR.properties @@ -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. + +displayName=Vari\u00E1vel Aleat\u00F3ria +maximumValue.displayName=Valor M\u00E1ximo +maximumValue.shortDescription=Valor M\u00E1ximo +minimumValue.displayName=Valor M\u00EDnimo +minimumValue.shortDescription=Valor M\u00EDnimo +options.displayName=Op\u00E7\u00F5es +outputFormat.displayName=Formato de sa\u00EDda +outputFormat.shortDescription=Formato de sa\u00EDda, ex\: \#\#\#\# +perThread.displayName=Por usu\u00E1rio virtual (thread)? +perThread.shortDescription=Utilizar geradores aleat\u00F3rios independentes para cada usu\u00E1rio virtual (thread)? +random.displayName=Configurar o gerador aleat\u00F3rio +randomSeed.displayName=Semente para a fun\u00E7\u00E3o aleat\u00F3ria +randomSeed.shortDescription=Semente para a fun\u00E7\u00E3o aleat\u00F3ria - n\u00FAmero do tipo long (valor padr\u00E3o para o tempo atual) +variable.displayName=Vari\u00E1vel de sa\u00EDda +variableName.displayName=Nome da Vari\u00E1vel +variableName.shortDescription=Nome da Vari\u00E1vel diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources.properties new file mode 100644 index 0000000..dea72f7 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources.properties @@ -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. + +displayName=BSF PostProcessor +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props prev sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_fr.properties new file mode 100644 index 0000000..3314cdd --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=Post-Processeur BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage BSF appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables\: ctx vars props prev sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pl.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pl.properties new file mode 100644 index 0000000..e8d3061 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pl.properties @@ -0,0 +1,29 @@ +#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net) +# 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. + +displayName=Post procesor BSF +filename.displayName=Nazwa pliku +filename.shortDescription=Plik ze skryptem (zast\u0119puje skrypt) +filenameGroup.displayName=Plik ze skryptem (zast\u0119puje skrypt) +parameterGroup.displayName=Parametry do przekazania do skryptu (=> String Parameters and String []args) +parameters.displayName=Parametry +parameters.shortDescription=Parametry do przekazania do pliku lub skryptu +script.displayName=Skrypt +script.shortDescription=Skrypt w jednym z j\u0119zyk\u00F3w BSF +scriptLanguage.displayName=J\u0119zyk +scriptLanguage.shortDescription=Nazwa j\u0119zyka BSF, np. beanshell, javascript, jexl +scripting.displayName=Skrypt (zmienne: ctx vars props prev sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=J\u0119zyk skrytpu (np. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pt_BR.properties new file mode 100644 index 0000000..196e771 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BSFPostProcessorResources_pt_BR.properties @@ -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. + +displayName=P\u00F3s-Processador BeanShell +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao BeanShell (\=> String Parameters e String []bsh.args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao arquivo ou script BeanShell +script.displayName=Script +script.shortDescription=Script na linguagem BSF apropriada +scriptLanguage.displayName=Linguagem +scriptLanguage.shortDescription=Nome da linguagem BSF, ex\: beanshell, javascript, jexl +scripting.displayName=Script (vari\u00E1veis\: ctx vars props prev data log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Linguagem do script (ex\: beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources.properties new file mode 100644 index 0000000..1d02b1f --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources.properties @@ -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. + +displayName=BeanShell PostProcessor +filename.displayName=File Name +filename.shortDescription=BeanShell script file (overrides script) +filenameGroup.displayName=Script file (overrides script) +parameterGroup.displayName=Parameters to be passed to BeanShell (=> String Parameters and String []bsh.args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to BeanShell (file or script) +resetGroup.displayName=Reset bsh.Interpreter before each call +resetInterpreter.displayName=Reset Interpreter +script.displayName=Script +script.shortDescription=Beanshell script +scripting.displayName=Script (variables: ctx vars props prev data log) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_de.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_de.properties new file mode 100644 index 0000000..62b314c --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_de.properties @@ -0,0 +1,24 @@ +# 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. + +filename.displayName=Dateiname +filename.shortDescription=BeanShell Script Datei (Vorrang vor Script) +filenameGroup.displayName=Script Datei (Vorrang vor Script) +parameterGroup.displayName=Parameter die der BeanShell \u00FCbergeben werden sollen ("String parameter, String []bsh.args") +parameters.displayName=Parameter +parameters.shortDescription=Parameter die der BeanShell \u00FCbergeben werden sollen (Datei oder Script) +script.displayName=BeanShell Script +script.shortDescription=BeanShell Script +scripting.displayName=Script (Variablen\: ctx vars props prev data log) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_fr.properties new file mode 100644 index 0000000..70045f2 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_fr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +displayName=Post-Processeur BeanShell +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script BeanShell (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au BeanShell (\=> String Parameters and String []bsh.args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au BeanShell (fichier ou script) +resetGroup.displayName=R\u00E9initialiser l'interpr\u00E9teur BeanShell avant chaque appel +resetInterpreter.displayName=R\u00E9initialiser l'interpr\u00E9teur +script.displayName=Script +script.shortDescription=Script BeanShell +scripting.displayName=Script (variables\: ctx vars props prev data log) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pl.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pl.properties new file mode 100644 index 0000000..b6af2b2 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pl.properties @@ -0,0 +1,29 @@ +#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net) +# 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. +# Uwagi co do tlumaczenia: mr0vek@o2.pl + +displayName=Post-procesor BeanShell +filename.displayName=Nazwa pliku +filename.shortDescription=Plik ze skryptem BeanShell (zast\u0119puje skrypt) +filenameGroup.displayName=Plik ze skryptem (zast\u0119puje skrypt) +parameterGroup.displayName=Parametry dla BeanShella (=> String Parameters and String []bsh.args) +parameters.displayName=Parametry +parameters.shortDescription=Parametry dla BeanShell (pliku lub skryptu) +resetGroup.displayName=Resetuj interpreter bsh przed ka\u017Cdym wywo\u0142aniem +resetInterpreter.displayName=Resetuj interpreter +script.displayName=Skrypt BeanShell +script.shortDescription=Skrypt Beanshell +scripting.displayName=Skrypt (zmienne: ctx vars props prev data log) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pt_BR.properties new file mode 100644 index 0000000..5796e6d --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_pt_BR.properties @@ -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. + +displayName=P\u00F3s-Processador BeanShell +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script do BeanShell (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao BeanShell (\=> String Parameters e String []bsh.args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao BeanShell (arquivo ou script) +resetGroup.displayName=Reiniciar bsh.Interpreter antes de cada chamada +resetInterpreter.displayName=Reiniciar Interpretador +script.displayName=\ +script.shortDescription=Script BeanShell +scripting.displayName=Script (vari\u00E1veis\: ctx vars props prev data log) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_tr.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_tr.properties new file mode 100644 index 0000000..87ba215 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/BeanShellPostProcessorResources_tr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +displayName=BeanShell Son \u0130\u015Flemcisi +filename.displayName=Dosya Ad\u0131 +filename.shortDescription=BeanShell beti\u011Fi dosyas\u0131 (bu beti\u011Fin \u00FCzerine yazar) +filenameGroup.displayName=Betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +parameterGroup.displayName=BeanShell'e ge\u00E7ilecek parametreler (\=> Dizgi(String) Parametreler ve String []bsh.args) +parameters.displayName=Parametreler +parameters.shortDescription=BeanShell'e ge\u00E7ilecek parametreler (dosya ya da betik) +script.shortDescription=BeanShell beti\u011Fi +scripting.displayName=Betik (de\u011Fi\u015Fkenler\: ctx vars props prev data log) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources.properties new file mode 100644 index 0000000..00e3b92 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources.properties @@ -0,0 +1,24 @@ +# 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. + +displayJMeterProperties.displayName=JMeter properties +displayJMeterProperties.shortDescription=Display JMeter properties ? +displayJMeterVariables.displayName=JMeter variables +displayJMeterVariables.shortDescription=Display JMeter variables ? +displayName=Debug PostProcessor +displaySamplerProperties.displayName=Sampler properties +displaySamplerProperties.shortDescription=Display Sampler properties ? +displaySystemProperties.displayName=System properties +displaySystemProperties.shortDescription=Display System properties ? diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_de.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_de.properties new file mode 100644 index 0000000..7c45c1a --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_de.properties @@ -0,0 +1,24 @@ +# 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. + +displayJMeterProperties.displayName=JMeter Eigenschaften +displayJMeterProperties.shortDescription=Sollen die JMeter Eigenschaften angezeigt werden? +displayJMeterVariables.displayName=JMeter Variablen +displayJMeterVariables.shortDescription=Sollen die JMeter Variablen angezeigt werden +displayName=Debug Post-Prozessor +displaySamplerProperties.displayName=Sampler Eigenschaften +displaySamplerProperties.shortDescription=Sollen die Sampler Eigenschaften angezeigt werden +displaySystemProperties.displayName=System Eigenschaften +displaySystemProperties.shortDescription=Sollen die System Eigenschaften angezeigt werden diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_fr.properties new file mode 100644 index 0000000..5629134 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_fr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +displayJMeterProperties.displayName=Propri\u00E9t\u00E9s JMeter +displayJMeterProperties.shortDescription=Afficher les propri\u00E9t\u00E9s JMeter ? +displayJMeterVariables.displayName=Variables JMeter +displayJMeterVariables.shortDescription=Afficher les variables JMeter ? +displayName=Post-Processeur D\u00E9bogage +displaySamplerProperties.displayName=Propri\u00E9t\u00E9s Echantillon +displaySamplerProperties.shortDescription=Afficher les propri\u00E9t\u00E9s Echantillon ? +displaySystemProperties.displayName=Propri\u00E9t\u00E9s Syst\u00E8me +displaySystemProperties.shortDescription=Afficher les propri\u00E9t\u00E9s syst\u00E8mes ? diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_pt_BR.properties new file mode 100644 index 0000000..1cbfc1a --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_pt_BR.properties @@ -0,0 +1,24 @@ +# 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. + +displayJMeterProperties.displayName=Propriedades do JMeter +displayJMeterProperties.shortDescription=Exibir propriedades do JMeter? +displayJMeterVariables.displayName=Vari\u00E1veis do JMeter +displayJMeterVariables.shortDescription=Exibir vari\u00E1veis do JMeter? +displayName=Debug P\u00F3s-Processador +displaySamplerProperties.displayName=Propriedades do testador? +displaySamplerProperties.shortDescription=Exibir propriedades do testador? +displaySystemProperties.displayName=Propriedades do sistema +displaySystemProperties.shortDescription=Exibir propriedades do sistema? diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_tr.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_tr.properties new file mode 100644 index 0000000..f0769a0 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/DebugPostProcessorResources_tr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +displayJMeterProperties.displayName=JMeter ayarlar\u0131 +displayJMeterProperties.shortDescription=JMeter ayarlar\u0131n\u0131 g\u00F6ster ? +displayJMeterVariables.displayName=JMeter de\u011Fi\u015Fkenleri +displayJMeterVariables.shortDescription=JMeter de\u011Fi\u015Fkenlerini g\u00F6ster ? +displayName=Ay\u0131klama Son \u0130\u015Flemcisi +displaySamplerProperties.displayName=\u00D6rnekleyicisi ayarlar\u0131 +displaySamplerProperties.shortDescription=\u00D6rnekleyicisi ayarlar\u0131n\u0131 g\u00F6ster ? +displaySystemProperties.displayName=Sistem ayarlar\u0131 +displaySystemProperties.shortDescription=Sistem ayarlar\u0131n\u0131 g\u00F6ster ? diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources.properties new file mode 100644 index 0000000..77d0f3b --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources.properties @@ -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. + +displayName=JSR223 PostProcessor +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR223 language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props prev sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate JSR223 language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources_fr.properties new file mode 100644 index 0000000..459d776 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/extractor/JSR223PostProcessorResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=Post-Processeur JSR223 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props prev sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources.properties new file mode 100644 index 0000000..ef92050 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources.properties @@ -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. + +displayName=BSF PreProcessor +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_fr.properties new file mode 100644 index 0000000..d6979a7 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=Pr\u00E9-Processeur BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage BSF appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables\: ctx vars props sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_pt_BR.properties new file mode 100644 index 0000000..017f152 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BSFPreProcessorResources_pt_BR.properties @@ -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. + +displayName=Pr\u00E9-Processador BSF +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao script (\=> String Parameters e String []args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao arquivo ou script +script.displayName=Script +script.shortDescription=Script na linguagem BSF apropriada +scriptLanguage.displayName=Linguagem +scriptLanguage.shortDescription=Nome da linguagem BSF, ex\: beanshell, javascript, jexl +scripting.displayName=Script (vari\u00E1veis\: ctx vars props sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Linguagem de script (ex\: beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources.properties new file mode 100644 index 0000000..3278ca8 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources.properties @@ -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. + +displayName=BeanShell PreProcessor +filename.displayName=File Name +filename.shortDescription=BeanShell script file (overrides script) +filenameGroup.displayName=Script file (overrides script) +parameterGroup.displayName=Parameters to be passed to BeanShell (=> String Parameters and String []bsh.args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to BeanShell (file or script) +resetGroup.displayName=Reset bsh.Interpreter before each call +resetInterpreter.displayName=Reset Interpreter +script.displayName=Script +script.shortDescription=Beanshell script +scripting.displayName=Script (variables: ctx vars props prev sampler log) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_de.properties b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_de.properties new file mode 100644 index 0000000..779d6f3 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_de.properties @@ -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. + +displayName=BeanShell Pre-Prozessor +filename.displayName=Dateiname +filename.shortDescription=BeanShell Script Datei (Vorrang vor Script) +filenameGroup.displayName=Script Datei (Vorrang vor Script) +parameterGroup.displayName=Parameter die der BeanShell \u00FCbergeben werden (String Parameters, String []bsh.args) +parameters.displayName=Parameter +parameters.shortDescription=Parameter die der BeanShell \u00FCbergeben werden (Datei oder Script) +script.displayName=BeanShell Script +script.shortDescription=BeanShell Script +scripting.displayName=Script (Variablen\: ctx vars props prev sampler log) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_fr.properties new file mode 100644 index 0000000..a894448 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_fr.properties @@ -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. + +displayName=Pr\u00E9-Processeur BeanShell +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script BeanShell (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au BeanShell (\=> String Parameters and String []bsh.args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au BeanShell (fichier ou script) +resetGroup.displayName=R\u00E9initialiser l'interpr\u00E9teur bsh avant chaque appel +resetInterpreter.displayName=R\u00E9initialiser l'interpr\u00E9teur +script.displayName=Script +script.shortDescription=Script BeanShell +scripting.displayName=Script (variables \: ctx vars props prev sampler log) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_pt_BR.properties new file mode 100644 index 0000000..75377a5 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_pt_BR.properties @@ -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. + +displayName=Pr\u00E9-Processador BeanShell +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script do BeanShell (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao BeanShell (\=> String Parameters e String []bsh.args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao BeanShell (arquivo ou script) +resetGroup.displayName=Reiniciar bsh.Interpreter antes de cada chamada +resetInterpreter.displayName=Reiniciar interpretador +script.displayName=\ +script.shortDescription=Script Beanshell +scripting.displayName=Script (var\u00E1veis\: ctx vars props prev sampler log) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_tr.properties b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_tr.properties new file mode 100644 index 0000000..b09f489 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/BeanShellPreProcessorResources_tr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +displayName=BeanShell \u00D6n \u0130\u015Flemcisi +filename.displayName=Dosya Ad\u0131 +filename.shortDescription=BeanShell betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +filenameGroup.displayName=Betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +parameterGroup.displayName=BeanShell'e ge\u00E7ilecek parametreler (\=> Dizgi (string) parametreler ve String [] bsh.args) +parameters.displayName=Parametreler +parameters.shortDescription=BeanShell'e ge\u00E7ilecek parametreler (dosya ya da betik) +script.shortDescription=Beanshell beti\u011Fi +scripting.displayName=Betik (de\u011Fi\u015Fkenler\: ctx vars props prev sampler log) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources.properties new file mode 100644 index 0000000..34f8404 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources.properties @@ -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. + +displayName=JSR223 PreProcessor +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR223 language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate JSR223 language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources_fr.properties new file mode 100644 index 0000000..eb1654f --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/modifiers/JSR223PreProcessorResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=Pr\u00E9-Processeur JSR223 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources.properties new file mode 100644 index 0000000..17cc5aa --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources.properties @@ -0,0 +1,22 @@ +# 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. + +displayName=Debug Sampler +displayJMeterVariables.displayName=JMeter variables +displayJMeterProperties.displayName=JMeter properties +displaySystemProperties.displayName=System properties +displayJMeterVariables.shortDescription=Display JMeter variables ? +displayJMeterProperties.shortDescription=Display JMeter properties ? +displaySystemProperties.shortDescription=Display System properties ? \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_de.properties b/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_de.properties new file mode 100644 index 0000000..d604abd --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_de.properties @@ -0,0 +1,22 @@ +# 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. + +displayJMeterProperties.displayName=JMeter Eigenschaften +displayJMeterProperties.shortDescription=Sollen die JMeter Eigenschaften angezeigt werden? +displayJMeterVariables.displayName=JMeter Variablen +displayJMeterVariables.shortDescription=Sollen die JMeter Variablen angezeigt werden +displayName=Debug Sampler +displaySystemProperties.displayName=System Eigenschaften +displaySystemProperties.shortDescription=Sollen die System Eigenschaften angezeigt werden diff --git a/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_fr.properties new file mode 100644 index 0000000..c8f33e0 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_fr.properties @@ -0,0 +1,23 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayJMeterProperties.displayName=Propri\u00E9t\u00E9s JMeter +displayJMeterProperties.shortDescription=Afficher les propri\u00E9t\u00E9s JMeter ? +displayJMeterVariables.displayName=Variables JMeter +displayJMeterVariables.shortDescription=Afficher les variables JMeter ? +displayName=Echantillon D\u00E9bogage +displaySystemProperties.displayName=Propri\u00E9t\u00E9s Syst\u00E8me +displaySystemProperties.shortDescription=Afficher les propri\u00E9t\u00E9s syst\u00E8mes ? diff --git a/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_pt_BR.properties new file mode 100644 index 0000000..0c701fa --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_pt_BR.properties @@ -0,0 +1,22 @@ +# 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. + +displayJMeterProperties.displayName=Propriedades do JMeter +displayJMeterProperties.shortDescription=Exibir propriedades do JMeter? +displayJMeterVariables.displayName=Vari\u00E1veis do JMeter +displayJMeterVariables.shortDescription=Exibir vari\u00E1veis do JMeter? +displayName=Debug testador +displaySystemProperties.displayName=Propriedades do sistema +displaySystemProperties.shortDescription=Exibir propriedades do sistema? diff --git a/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_tr.properties b/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_tr.properties new file mode 100644 index 0000000..0d176ab --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/sampler/DebugSamplerResources_tr.properties @@ -0,0 +1,23 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayJMeterProperties.displayName=JMeter ayarlar\u0131 +displayJMeterProperties.shortDescription=JMeter ayarlar\u0131n\u0131 g\u00F6ster ? +displayJMeterVariables.displayName=JMeter de\u011Fi\u015Fkenleri +displayJMeterVariables.shortDescription=JMeter de\u011Fi\u015Fkenlerini g\u00F6ster ? +displayName=Ay\u0131klama \u00D6rnekleyicisi +displaySystemProperties.displayName=Sistem ayarlar\u0131 +displaySystemProperties.shortDescription=Sistem ayarlar\u0131n\u0131 g\u00F6ster ? diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/BSFTimerResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/BSFTimerResources.properties new file mode 100644 index 0000000..0e8a1c2 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/BSFTimerResources.properties @@ -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. + +displayName=BSF Timer +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/BSFTimerResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/BSFTimerResources_fr.properties new file mode 100644 index 0000000..e37dc26 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/BSFTimerResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=Compteur de temps BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script BSF (remplace le code script) +filenameGroup.displayName=Fichier script (remplace le code script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script BSF (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au BSF (fichier ou script) +script.displayName=Script +script.shortDescription=Script dans le langage BSF cible +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script BSF (ex. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources.properties new file mode 100644 index 0000000..67c072c --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources.properties @@ -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. + +displayName=BeanShell Timer +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=BeanShell script file (overrides script) +parameterGroup.displayName=Parameters to be passed to BeanShell (=> String Parameters and String []bsh.args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to BeanShell (file or script) +resetGroup.displayName=Reset bsh.Interpreter before each call +resetInterpreter.displayName=Reset Interpreter +script.displayName=Script +script.shortDescription=Beanshell script to generate delay +scripting.displayName=Script (variables: ctx vars props log prev) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_de.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_de.properties new file mode 100644 index 0000000..ff103eb --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_de.properties @@ -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. + +displayName=BeanShell Timer +filename.displayName=Dateiname +filename.shortDescription=BeanShell Script Datei (Vorrang vor Script) +filenameGroup.displayName=Script Datei (Vorrang vor Script) +parameterGroup.displayName=Parameter die der BeanShell \u00FCbergeben werden (String Parameters, String []bsh.args) +parameters.displayName=Parameter +parameters.shortDescription=Parameter die der BeanShell \u00FCbergeben werden (Datei oder Script) +script.displayName=Script +script.shortDescription=BeanShell Script zur Erzeugung der Pause +scripting.displayName=Script (Variablen\: ctv vars props log prev) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_fr.properties new file mode 100644 index 0000000..3c1c498 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_fr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +displayName=Compteur de temps BeanShell +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script BeanShell (remplace le code script) +filenameGroup.displayName=Fichier script (remplace le code script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au BeanShell (\=> String Parameters and String []bsh.args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au BeanShell (fichier ou script) +resetGroup.displayName=Reinitialiser l'interpr\u00E9teur bsh avant chaque appel +resetInterpreter.displayName=R\u00E9initialiser l'interpr\u00E9teur +script.displayName=Script +script.shortDescription=Script BeanShell pour g\u00E9n\u00E9rer le d\u00E9calage +scripting.displayName=Script (variables\: ctx vars props log prev) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_pt_BR.properties new file mode 100644 index 0000000..0597881 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_pt_BR.properties @@ -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. + +displayName=Temporizador BeanShell +filename.displayName=Nome do Arquivo +filename.shortDescription=Arquivo de script do BeanShell (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao BeanShell (\=> String Parameters e String []bsh.args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao BeanShell (arquivo ou script) +resetGroup.displayName=Reiniciar bsh.Interpreter antes de cada chamada. +resetInterpreter.displayName=Reiniciar Interpretador +script.displayName=\ +script.shortDescription=Script BeanShell que ir\u00E1 gerar o atraso +scripting.displayName=Script (vari\u00E1veis\: ctx vars props log prev) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_tr.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_tr.properties new file mode 100644 index 0000000..e46a7eb --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/BeanShellTimerResources_tr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +displayName=BeanShell Zamanlay\u0131c\u0131 +filename.displayName=Dosya Ad\u0131 +filename.shortDescription=BeanShell beti\u011Fi dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +filenameGroup.displayName=Betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +parameterGroup.displayName=BeanShell'e ge\u00E7ilecek parametreler (\=> Dizgi(string) Parametreler ve String []bsh.args) +parameters.displayName=Parametreler +parameters.shortDescription=BeanShell'e ge\u00E7ilecek parametreler (dosya ya da betik) +script.shortDescription=Gecikmeyi yaratacak BeanShell beti\u011Fi +scripting.displayName=Betik (de\u011Fi\u015Fkenler\: ctx vars props log prev) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources.properties new file mode 100644 index 0000000..1490dbd --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources.properties @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +calcMode.1=this thread only +calcMode.2=all active threads +calcMode.3=all active threads in current thread group +calcMode.4=all active threads (shared) +calcMode.5=all active threads in current thread group (shared) +calcMode.displayName=Calculate Throughput based on +calcMode.shortDescription=The Constant Throughput Timer used to delay each thread as though it was the only thread in the test. Now, it calculates the delay taking into account the number of active threads in the test or the thread group. +delay.displayName=Delay before each affected sampler +displayName=Constant Throughput Timer +throughput.displayName=Target throughput (in samples per minute) +throughput.shortDescription=Maximum number of samples you want to obtain per minute, per thread, from all affected samplers. diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_de.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_de.properties new file mode 100644 index 0000000..c86c962 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_de.properties @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +calcMode.1=Nur dieser Thread +calcMode.2=Alle aktiven Threads +calcMode.3=Alle aktiven Threads in der aktuellen Thread-Gruppe +calcMode.4=Alle aktiven Threads (Gemeinsam) +calcMode.5=Alle aktiven Threads in der aktuellen Thread-Gruppe (gemeinsam) +calcMode.displayName=Berechne Durchsatz basierend auf +calcMode.shortDescription=Es war der einzige Thread im Test. Nun wird die Pause unter Ber\u00FCcksichtigung der aktiven Threads oder der Thread-Gruppe berechnet. +delay.displayName=Pause bevor eine Probe genommen wird +displayName=Konstanter Durchsatz-Timer (Zeitgeber) +throughput.displayName=Gew\u00FCnschter Proben-Durchsatz (pro Minute) +throughput.shortDescription=Maximale Anzahl an Proben die pro Minute und pro Thread von allen betroffenen Samplern diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_es.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_es.properties new file mode 100644 index 0000000..238d7d6 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_es.properties @@ -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. + +#Stored by I18NEdit, may be edited! +calcMode.1=solamente este hilo +calcMode.2=todos los hilos activos +calcMode.3=todos los hilos activos en el grupo de hilos actual +calcMode.displayName=Calcular el rendimiento basado en +calcMode.shortDescription=El Temporizador para Rendimiento Constante introduc\u00EDa un retardo como si este fuera el unico hilo en la prueba. Ahora calcula el retardo teniendo en cuenta el n\u00FAmero de hilos activos en la prueba o el grupo de hilos. +delay.displayName=Retardo antes de cada muestreador afectado +displayName=Temporizador de Rendimiento Constante +throughput.displayName=Rendimiento objetivo (en muestras por minuto) +throughput.shortDescription=N\u00FAmero m\u00E1ximo de muestras que quiere obtener por minuto, por hilo, desde todos los muestreadores afectados. diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_fr.properties new file mode 100644 index 0000000..515c6aa --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_fr.properties @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +calcMode.1=cette unit\u00E9 seulement +calcMode.2=toutes les unit\u00E9s actives +calcMode.3=toutes les unit\u00E9s actives dans le groupe d'unit\u00E9s courant +calcMode.4=toutes les unit\u00E9s actives (partag\u00E9) +calcMode.5=toutes les unit\u00E9s actives dans le groupe d'unit\u00E9s courant (partag\u00E9) +calcMode.displayName=Calculer le d\u00E9bit sur la base de +calcMode.shortDescription=Compteur de temps utilis\u00E9 par le Compteur de d\u00E9bit constant pour d\u00E9caler chaque thread comme s'il \u00E9tait le seul dans le test. Maintenant, le d\u00E9lai est calcul\u00E9 en prenant en compte le nombre de threads actifs dans le test ou le groupe d'unit\u00E9s. +delay.displayName=D\u00E9lai avant chaque \u00E9chantillon affect\u00E9 +displayName=Compteur de d\u00E9bit constant +throughput.displayName=D\u00E9bit cibl\u00E9 (en \u00E9chantillons par minute) +throughput.shortDescription=Nombre maximum d'\u00E9chantillon \u00E0 obtenir par minute, par unit\u00E9, pour tous les \u00E9chantillons affect\u00E9s diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_ja.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_ja.properties new file mode 100644 index 0000000..849d0f6 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_ja.properties @@ -0,0 +1,18 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=\u5B9A\u6570\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8\u30BF\u30A4\u30DE +throughput.displayName=\u30BF\u30FC\u30B2\u30C3\u30C8\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8(\u30B5\u30F3\u30D7\u30EB\u6570/\u5206) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_pt_BR.properties new file mode 100644 index 0000000..335b023 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_pt_BR.properties @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +calcMode.1=este usu\u00E1rio virtual (thread) somente +calcMode.2=todos usu\u00E1rios virtuais (threads) ativos +calcMode.3=todos usu\u00E1rios virtuais ativos no grupo de usu\u00E1rios corrente +calcMode.4=todos usu\u00E1rios virtuais ativos (compartilhado) +calcMode.5=todos usu\u00E1rios virtuais no grupo de usu\u00E1rios atual (compartillhado) +calcMode.displayName=Calcular Vaz\u00E3o baseada em +calcMode.shortDescription=O Temporizador de Vaz\u00E3o Constante era usado para atrasar cada usu\u00E1rio virtual como se ele fosse o \u00FAnico usu\u00E1rio virtual no teste. Agora, ele calcula o atraso levando em considera\u00E7\u00E3o o n\u00FAmero de usu\u00E1rios virtuais ativos no teste ou no grupo de usu\u00E1rios. +delay.displayName=Atraso antes de cada testador afetado +displayName=Temporizador de Vaz\u00E3o Constante +throughput.displayName=Vaz\u00E3o alvo (em amostras por minuto) +throughput.shortDescription=N\u00FAmero m\u00E1ximo de amostras que voc\u00EA quer obter por minuto, por usu\u00E1rio virtual, de todos os testadores afetados. diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_tr.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_tr.properties new file mode 100644 index 0000000..effcfe0 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_tr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +calcMode.1=sadece bu i\u015F par\u00E7ac\u0131\u011F\u0131 +calcMode.2=b\u00FCt\u00FCn aktif i\u015F par\u00E7ac\u0131klar\u0131 +calcMode.3=bu i\u015F par\u00E7ac\u0131\u011F\u0131 grubundaki t\u00FCm aktif i\u015F par\u00E7ac\u0131klar\u0131 +calcMode.4=t\u00FCm i\u015F par\u00E7ac\u0131klar\u0131 (payla\u015F\u0131ml\u0131) +calcMode.5=bu i\u015F pa\u00E7ac\u0131\u011F\u0131 grubundaki t\u00FCm aktif i\u015F par\u00E7ac\u0131klar\u0131 (payla\u015F\u0131ml\u0131) +calcMode.displayName=transfer oran\u0131 hesab\u0131n\u0131n yap\u0131laca\u011F\u0131 temel +calcMode.shortDescription=Sabit Transfer Oran\u0131 Zamanlay\u0131c\u0131 eskiden her bir i\u015F par\u00E7ac\u0131\u011F\u0131 i\u00E7in, testteki tek i\u015F par\u00E7ac\u0131\u011F\u0131ym\u0131\u015Fcas\u0131na gecikirken; \u015Fimdi gecikme hesab\u0131 testteki veya i\u015F par\u00E7ac\u0131\u011F\u0131 grubundaki aktif i\u015F par\u00E7ac\u0131\u011F\u0131 say\u0131s\u0131na g\u00F6re yap\u0131lmakta. +delay.displayName=Etkilenen her \u00F6rnekleyiciden \u00F6nce gecikme +displayName=Sabit Transfer Oran\u0131 Zamanlay\u0131c\u0131 +throughput.displayName=Hedeflenen transfer oran\u0131 (\u00F6rnek/dak.) +throughput.shortDescription=Dakika, i\u015F par\u00E7ac\u0131\u011F\u0131 ve etkilenen \u00F6rnekleyici ba\u015F\u0131na istedi\u011Finiz en fazla \u00F6rnek say\u0131s\u0131. diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_zh_TW.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_zh_TW.properties new file mode 100644 index 0000000..bcb54b0 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/ConstantThroughputTimerResources_zh_TW.properties @@ -0,0 +1,20 @@ +# 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. + +#Stored by I18NEdit, may be edited! +delay.displayName=\u6BCF\u500B\u53D6\u6A23\u9593\u7684\u5EF6\u9072\u6642\u9593 +displayName=\u56FA\u5B9A\u6642\u9694 +throughput.displayName=\u76EE\u6A19\u8655\u7406\u91CF(\u6BCF\u5206\u9418\u53D6\u6A23\u6578) +throughput.shortDescription=\u5728\u6240\u6709\u53D6\u6A23\u4E2D\uFF0C\u6BCF\u57F7\u884C\u7DD2\u6BCF\u5206\u9418\u5167\u7684\u6700\u5927\u53D6\u6A23\u6578 diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/JSR223TimerResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/JSR223TimerResources.properties new file mode 100644 index 0000000..91d0301 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/JSR223TimerResources.properties @@ -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. + +displayName=JSR223 Timer +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR223 language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate JSR223 language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/JSR223TimerResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/JSR223TimerResources_fr.properties new file mode 100644 index 0000000..d38ca39 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/JSR223TimerResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=Compteur de temps JSR223 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le code script) +filenameGroup.displayName=Fichier script (remplace le code script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. groovy, beanshell, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources.properties new file mode 100644 index 0000000..fa02e5a --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources.properties @@ -0,0 +1,19 @@ +# 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. + +displayName=Synchronizing Timer +grouping.displayName=Grouping +groupSize.displayName=Number of Simulated Users to Group by +groupSize.shortDescription=Define how many simulated users trigger the release of the synchronizing block (default value of '0' means all users) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_de.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_de.properties new file mode 100644 index 0000000..8624f7a --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_de.properties @@ -0,0 +1,18 @@ +# 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. + +groupSize.displayName=Anzahl der gruppierten, Simulations-Benutzer +groupSize.shortDescription=Geben sie die Anzahl der Simulations-Benutzer an, die den synchronisierten Block ausl\u00F6sen (Vorgabe 0 \= alle Benutzer) +grouping.displayName=Gruppierung diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_es.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_es.properties new file mode 100644 index 0000000..d3778c8 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_es.properties @@ -0,0 +1,20 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Temporizador sincronizado +groupSize.displayName=N\u00FAmero de Usuarios Simulados para agrupar +groupSize.shortDescription=Defina cuantos usuarios simulados disparan la liberaci\u00F3n del bloque sincronizado (el valor por defecto '0' significa todos los usuarios) +grouping.displayName=Agrupaci\u00F3n diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_fr.properties new file mode 100644 index 0000000..2c9487b --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_fr.properties @@ -0,0 +1,20 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Compteur de synchronisation +groupSize.displayName=Nombre d'utilisateurs simul\u00E9s \u00E0 grouper +groupSize.shortDescription=D\u00E9fini combien d'utilisateurs simul\u00E9s d\u00E9clenchent la lib\u00E9ration synchronis\u00E9e du bloc (d\u00E9faut \: la valeur '0' signifie tous les utilisateurs) +grouping.displayName=Regroupement diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_pt_BR.properties new file mode 100644 index 0000000..e688164 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_pt_BR.properties @@ -0,0 +1,19 @@ +# 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. + +displayName=Temporizador de sincroniza\u00E7\u00E3o +groupSize.displayName=N\u00FAmero de Usu\u00E1rios Simulados por Grupo por +groupSize.shortDescription=Define quantos usu\u00E1rios simulados ativam a libera\u00E7\u00E3o do bloco de sincroniza\u00E7\u00E3o (valor padr\u00E3o de '0' significa todos usu\u00E1rios) +grouping.displayName=Agrupamento diff --git a/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_tr.properties b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_tr.properties new file mode 100644 index 0000000..509aa14 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/timers/SyncTimerResources_tr.properties @@ -0,0 +1,20 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=E\u015Fzamanl\u0131 Zamanlay\u0131c\u0131 +groupSize.displayName=Grup ba\u015F\u0131na d\u00FC\u015Fen sahte kullan\u0131c\u0131 say\u0131s\u0131 +groupSize.shortDescription=E\u015F zamanl\u0131 blo\u011Fun sal\u0131verilmesini tetikleyen sahte kullan\u0131c\u0131 say\u0131s\u0131n\u0131 belirtin (\u00F6n tan\u0131ml\u0131 olarak '0' t\u00FCm kullan\u0131c\u0131lar anlam\u0131na gelmektedir) +grouping.displayName=Gruplama diff --git a/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources.properties new file mode 100644 index 0000000..08eec12 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources.properties @@ -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. + +displayName=BSF Listener +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampleResult (aka prev) sampleEvent sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources_fr.properties new file mode 100644 index 0000000..1b6857a --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=R\u00E9cepteur BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage BSF appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props sampleResult (aka prev) sampleEvent sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources_pt_BR.properties new file mode 100644 index 0000000..e666bec --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BSFListenerResources_pt_BR.properties @@ -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. + +displayName=Ouvinte BSF +filename.displayName=Nome do arquivo +filename.shortDescription=Arquivo de script (substitui o script) +filenameGroup.displayName=Arquivo de script (substitui o script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao script (\=> String Parameters e String []args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros que ser\u00E3o passados ao arquivo ou script +script.displayName=Script +script.shortDescription=Script na linguagem BSF apropriada +scriptLanguage.displayName=Linguagem +scriptLanguage.shortDescription=Nome da linguagem BSF, ex\: beanshell, javascript, jexl +scripting.displayName=Scripts (vari\u00E1veis\: ctx vars props sampleResult (aka prev) sampleEvent sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Linguagem de script (ex\: beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources.properties new file mode 100644 index 0000000..49ba5db --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources.properties @@ -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. + +displayName=BeanShell Listener +filename.displayName=File Name +filename.shortDescription=BeanShell script file (overrides script) +filenameGroup.displayName=Script file (overrides script) +parameterGroup.displayName=Parameters to be passed to BeanShell (=> String Parameters and String []bsh.args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to BeanShell (file or script) +resetGroup.displayName=Reset bsh.Interpreter before each call +resetInterpreter.displayName=Reset Interpreter +script.displayName=Script +script.shortDescription=Beanshell script +scripting.displayName=Script (variables: ctx vars props sampleEvent sampleResult log prev) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_de.properties b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_de.properties new file mode 100644 index 0000000..e8e81f4 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_de.properties @@ -0,0 +1,24 @@ +# 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. + +displayName=BeanShell Listener (Lauscher) +filename.displayName=Dateiname +filename.shortDescription=BeanShell Script Datei (Vorrang vor Script) +filenameGroup.displayName=Script Datei (Vorrang vor Script) +parameterGroup.displayName=Parameter die der BeanShell \u00FCbergeben werden sollen (String Parameters, String []bsh.args) +parameters.displayName=Parameter +parameters.shortDescription=Parameter die der BeanShell \u00FCbergeben werden sollen (Datei oder Script) +script.shortDescription=BeanShell Script +scripting.displayName=Script (Variablen\: ctx vars props sampleEvent sampleResult log prev) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_fr.properties new file mode 100644 index 0000000..47b0867 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_fr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +displayName=R\u00E9cepteur BeanShell +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script BeanShell (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au BeanShell (\=> String Parameters and String []bsh.args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au BeanShell (fichier ou script) +resetGroup.displayName=R\u00E9initialiser l'interpr\u00E9teur BSH avant chaque appel +resetInterpreter.displayName=R\u00E9initialiser l'interpr\u00E9teur +script.displayName=Script +script.shortDescription=Script BeanShell +scripting.displayName=Script (variables \: ctx vars props sampleEvent sampleResult log prev) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_pt_BR.properties b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_pt_BR.properties new file mode 100644 index 0000000..632142b --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_pt_BR.properties @@ -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. + +displayName=Ouvinte BeanShell +filename.displayName=Nome do arquivo +filename.shortDescription=Arquivo de script do BeanShell (substitui script) +filenameGroup.displayName=Arquivo de script (substitui script) +parameterGroup.displayName=Par\u00E2metros a serem passados ao BeanShell (\=> String Parameters e String []bsh.args) +parameters.displayName=Par\u00E2metros +parameters.shortDescription=Par\u00E2metros a serem passados ao BeanShell (arquivo ou script) +resetGroup.displayName=Reiniciar bsh.Interpreter antes de cada chamada +resetInterpreter.displayName=Reiniciar Interpretador +script.displayName= +script.shortDescription=Script BeanShell +scripting.displayName=Script (vari\u00E1veis\: ctx vars props sampleEvent sampleResult log prev) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_tr.properties b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_tr.properties new file mode 100644 index 0000000..a9542a2 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/BeanShellListenerResources_tr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +displayName=BeanShell Alg\u0131lay\u0131c\u0131 +filename.displayName=Dosya Ad\u0131 +filename.shortDescription=BeanShell betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +filenameGroup.displayName=Betik dosyas\u0131 (buradaki beti\u011Fin \u00FCzerine yazar) +parameterGroup.displayName=BeanShell'e ge\u00E7ilecek parametreler (\=> Dizgi (String) Parametreler ve String []bsh.args) +parameters.displayName=Parametreler +parameters.shortDescription=BeanShell'e ge\u00E7ilecek parametreler (dosya ya da betik) +script.shortDescription=Beanshell beti\u011Fi +scripting.displayName=Betik (de\u011Fi\u015Fkenler\: ctx vars props sampleEvent sampleResult log prev) diff --git a/ApacheJmeter/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources.properties b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources.properties new file mode 100644 index 0000000..3a06747 --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources.properties @@ -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. + +displayName=JSR223 Listener +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR 223 language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props sampleResult (aka prev) sampleEvent sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate JSR 223 language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/ApacheJmeter/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources_fr.properties b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources_fr.properties new file mode 100644 index 0000000..b7c463e --- /dev/null +++ b/ApacheJmeter/src/components/org/apache/jmeter/visualizers/JSR223ListenerResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=R\u00E9cepteur JSR 223 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props sampleResult (avant prev) sampleEvent sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/core/org/apache/jmeter/help.txt b/ApacheJmeter/src/core/org/apache/jmeter/help.txt new file mode 100644 index 0000000..f6d0499 --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/help.txt @@ -0,0 +1,37 @@ + +To run Apache JMeter in GUI mode: +Double-click on the ApacheJMeter.jar file. +If this doesn't work, open a command prompt and type: + +java -jar ApacheJMeter.jar [-p property-file] + +-------------------------------------------------- + +To run Apache JMeter in NON_GUI mode: +Open a command prompt (or Unix shell) and type: + +java -jar ApacheJMeter.jar -n -t test-file [-p property-file] [-l log-file] + +-------------------------------------------------- + +To tell Apache JMeter to use a proxy server: +Open a command prompt and type: + +java -jar ApacheJMeter.jar -H [your.proxy.server] -P [your proxy server port] + +--------------------------------------------------- + +To run Apache JMeter in server mode: +Open a command prompty and type + +java -jar ApacheJMeter.jar -s + +Or, use the provided script file: jmeter-server.bat(Windows)/jmeter-server(Linux) + +--------------------------------------------------- + +Please note that a script file is provided: +jmeter.bat(Windows)/jmeter(Linux) that can be +used in place of "java -jar ApacheJMeter.jar". Example: + +jmeter -p jmeter.properties -H my.proxy.com -P 9999 diff --git a/ApacheJmeter/src/core/org/apache/jmeter/images/icon.properties b/ApacheJmeter/src/core/org/apache/jmeter/images/icon.properties new file mode 100644 index 0000000..3e7f3ae --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/images/icon.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Icon definition file. +# Key: (super) class names +# Value: icon, optionally followed by space and then the disabled icon name +org.apache.jmeter.control.gui.TestPlanGui=org/apache/jmeter/images/beaker.gif +org.apache.jmeter.timers.gui.AbstractTimerGui=org/apache/jmeter/images/timer.gif +org.apache.jmeter.threads.gui.AbstractThreadGroupGui=org/apache/jmeter/images/thread.gif +org.apache.jmeter.visualizers.gui.AbstractListenerGui=org/apache/jmeter/images/meter.png +org.apache.jmeter.config.gui.AbstractConfigGui=org/apache/jmeter/images/testtubes.png +org.apache.jmeter.processor.gui.AbstractPreProcessorGui=org/apache/jmeter/images/leafnode.gif +org.apache.jmeter.processor.gui.AbstractPostProcessorGui=org/apache/jmeter/images/leafnodeflip.gif +org.apache.jmeter.control.gui.AbstractControllerGui=org/apache/jmeter/images/knob.gif +org.apache.jmeter.samplers.gui.AbstractSamplerGui=org/apache/jmeter/images/pipet.png +org.apache.jmeter.assertions.gui.AbstractAssertionGui=org/apache/jmeter/images/question.gif +org.apache.jmeter.control.gui.WorkBenchGui=org/apache/jmeter/images/clipboard.gif \ No newline at end of file diff --git a/ApacheJmeter/src/core/org/apache/jmeter/images/icon_1.properties b/ApacheJmeter/src/core/org/apache/jmeter/images/icon_1.properties new file mode 100644 index 0000000..b15cac1 --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/images/icon_1.properties @@ -0,0 +1,35 @@ +# 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. + +# Icon definition file. +# Key: (super) class names +# Value: icon, optionally followed by space and then the disabled icon name +org.apache.jmeter.control.gui.TestPlanGui=org/apache/jmeter/images/new/book.png org/apache/jmeter/images/new/book-grey.png +org.apache.jmeter.timers.gui.AbstractTimerGui=org/apache/jmeter/images/new/clock.png org/apache/jmeter/images/new/clock-grey.png +org.apache.jmeter.threads.gui.ThreadGroupGui=org/apache/jmeter/images/new/thread.png org/apache/jmeter/images/new/thread-grey.png +org.apache.jmeter.visualizers.gui.AbstractListenerGui=org/apache/jmeter/images/new/pencil.png org/apache/jmeter/images/new/pencil-grey.png +org.apache.jmeter.config.gui.AbstractConfigGui=org/apache/jmeter/images/new/puzzle.png org/apache/jmeter/images/new/puzzle-grey.png +org.apache.jmeter.processor.gui.AbstractPreProcessorGui=org/apache/jmeter/images/new/funnel.png org/apache/jmeter/images/new/funnel-grey.png +org.apache.jmeter.processor.gui.AbstractPostProcessorGui=org/apache/jmeter/images/new/mglass.png org/apache/jmeter/images/new/mglass-grey.png +org.apache.jmeter.control.gui.AbstractControllerGui=org/apache/jmeter/images/new/remote.png org/apache/jmeter/images/new/remote-grey.png +org.apache.jmeter.samplers.gui.AbstractSamplerGui=org/apache/jmeter/images/new/glasses.png org/apache/jmeter/images/new/glasses-grey.png +org.apache.jmeter.assertions.gui.AbstractAssertionGui=org/apache/jmeter/images/new/pin.png org/apache/jmeter/images/new/pin-grey.png +org.apache.jmeter.report.writers.gui.AbstractReportWriterGui=org/apache/jmeter/images/new/pin.png org/apache/jmeter/images/new/pin-grey.png +org.apache.jmeter.report.gui.ReportPageGui=org/apache/jmeter/images/new/scroll.png org/apache/jmeter/images/new/scroll-grey.png +org.apache.jmeter.report.gui.TableGui=org/apache/jmeter/images/new/table.png org/apache/jmeter/images/new/table-grey.png +org.apache.jmeter.report.gui.BarChartGui=org/apache/jmeter/images/new/barchart.png org/apache/jmeter/images/new/barchart-grey.png +org.apache.jmeter.report.gui.LineGraphGui=org/apache/jmeter/images/new/chart.png org/apache/jmeter/images/new/chart-grey.png +org.apache.jmeter.report.writers.gui.HTMLReportWriterGui=org/apache/jmeter/images/new/typewriter.png org/apache/jmeter/images/new/typewriter-grey.png +org.apache.jmeter.control.gui.ReportGui=org/apache/jmeter/images/new/blue-quill.png \ No newline at end of file diff --git a/ApacheJmeter/src/core/org/apache/jmeter/images/toolbar/icons-toolbar.properties b/ApacheJmeter/src/core/org/apache/jmeter/images/toolbar/icons-toolbar.properties new file mode 100644 index 0000000..3d523f4 --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/images/toolbar/icons-toolbar.properties @@ -0,0 +1,45 @@ +# 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. + +# Icons order. Keys separate by comma. Use a pipe | to have a space between two icons. +toolbar=new,open,close,save,save_as_testplan,|,cut,copy,paste,|,expand,collapse,toggle,|,test_start,test_start_notimers,test_stop,test_shutdown,|,test_start_remote_all,test_stop_remote_all,test_shutdown_remote_all,|,test_clear,test_clear_all,|,search,search_reset,|,function_helper,help + +# Icon / action definition file. +# Key: button names +# Value: I18N key in messages.properties, ActionNames key field, icon path, optionally followed by comma and then the pressed icon name +new=new,CLOSE,org/apache/jmeter/images/toolbar/new.png +open=menu_open,OPEN,org/apache/jmeter/images/toolbar/open.png +close=menu_close,CLOSE,org/apache/jmeter/images/toolbar/close.png +save=save,SAVE,org/apache/jmeter/images/toolbar/save.png +save_as_testplan=save_as,SAVE_AS,org/apache/jmeter/images/toolbar/saveastp.png +cut=cut,CUT,org/apache/jmeter/images/toolbar/cut.png +copy=copy,COPY,org/apache/jmeter/images/toolbar/copy.png +paste=paste,PASTE,org/apache/jmeter/images/toolbar/paste.png +test_start=start,ACTION_START,org/apache/jmeter/images/toolbar/start.png +test_start_notimers=start_no_timers,ACTION_START_NO_TIMERS,org/apache/jmeter/images/toolbar/startnotimers.png +test_stop=stop,ACTION_STOP,org/apache/jmeter/images/toolbar/stop.png +test_shutdown=shutdown,ACTION_SHUTDOWN,org/apache/jmeter/images/toolbar/shutdown.png +test_start_remote_all=remote_start_all,REMOTE_START_ALL,org/apache/jmeter/images/toolbar/startremoteall.png +test_stop_remote_all=remote_stop_all,REMOTE_STOP_ALL,org/apache/jmeter/images/toolbar/stopremoteall.png +test_shutdown_remote_all=remote_shut_all,REMOTE_SHUT_ALL,org/apache/jmeter/images/toolbar/shutdownremoteall.png +test_clear=clear,CLEAR,org/apache/jmeter/images/toolbar/clear.png +test_clear_all=clear_all,CLEAR_ALL,org/apache/jmeter/images/toolbar/clearall.png +toggle=toggle,TOGGLE,org/apache/jmeter/images/toolbar/toggle.png +expand=menu_expand_all,EXPAND_ALL,org/apache/jmeter/images/toolbar/expand.png +collapse=menu_collapse_all,COLLAPSE_ALL,org/apache/jmeter/images/toolbar/collapse.png +search=menu_search,SEARCH_TREE,org/apache/jmeter/images/toolbar/search.png +search_reset=menu_search_reset,SEARCH_RESET,org/apache/jmeter/images/toolbar/searchreset.png +function_helper=function_dialog_menu_item,FUNCTIONS,org/apache/jmeter/images/toolbar/function.png +help=help,HELP,org/apache/jmeter/images/toolbar/help.png \ No newline at end of file diff --git a/ApacheJmeter/src/core/org/apache/jmeter/resources/messages.properties b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages.properties new file mode 100644 index 0000000..be911ca --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages.properties @@ -0,0 +1,1198 @@ +# 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. + +# Warning: JMeterUtils.getResString() replaces space with '_' +# and converts keys to lowercase before lookup +# => All keys in this file must also be lower case or they won't match +# + +# Please add new entries in alphabetical order + +about=About Apache JMeter +add=Add +add_from_clipboard=Add from Clipboard +add_as_child=Add as Child +add_parameter=Add Variable +add_pattern=Add Pattern\: +add_test=Add Test +add_user=Add User +add_value=Add Value +addtest=Add test +aggregate_graph=Statistical Graphs +aggregate_graph_choose_color=Choose color +aggregate_graph_choose_foreground_color=Foreground color +aggregate_graph_color_bar=Color\: +aggregate_graph_column=Column\: +aggregate_graph_column_selection=Column label selection\: +aggregate_graph_column_settings=Column settings +aggregate_graph_columns_to_display=Columns to display\: +aggregate_graph_dimension=Graph size +aggregate_graph_display=Display Graph +aggregate_graph_draw_outlines=Draw outlines bar? +aggregate_graph_dynamic_size=Dynamic graph size +aggregate_graph_font=Font\: +aggregate_graph_height=Height\: +aggregate_graph_legend=Legend +aggregate_graph_legend.placement.bottom=Bottom +aggregate_graph_legend.placement.left=Left +aggregate_graph_legend.placement.right=Right +aggregate_graph_legend.placement.top=Top +aggregate_graph_legend_placement=Placement\: +aggregate_graph_max_length_xaxis_label=Max length of x-axis label\: +aggregate_graph_ms=Milliseconds +aggregate_graph_no_values_to_graph=No values to graph +aggregate_graph_number_grouping=Show number grouping? +aggregate_graph_reload_data=Reload data +aggregate_graph_response_time=Response Time +aggregate_graph_save=Save Graph +aggregate_graph_save_table=Save Table Data +aggregate_graph_save_table_header=Save Table Header +aggregate_graph_size=Size\: +aggregate_graph_style=Style\: +aggregate_graph_sync_with_name=Synchronize with name +aggregate_graph_tab_graph=Graph +aggregate_graph_tab_settings=Settings +aggregate_graph_title=Aggregate Graph +aggregate_graph_title_group=Title +aggregate_graph_use_group_name=Include group name in label? +aggregate_graph_user_title=Graph title\: +aggregate_graph_value_font=Value font\: +aggregate_graph_value_labels_vertical=Value labels vertical? +aggregate_graph_width=Width\: +aggregate_graph_xaxis_group=X Axis +aggregate_graph_yaxis_group=Y Axis +aggregate_graph_yaxis_max_value=Scale maximum value\: +aggregate_report=Aggregate Report +aggregate_report_90=90% +aggregate_report_90%_line=90% Line +aggregate_report_bandwidth=KB/sec +aggregate_report_count=# Samples +aggregate_report_error=Error +aggregate_report_error%=Error % +aggregate_report_max=Max +aggregate_report_median=Median +aggregate_report_min=Min +aggregate_report_rate=Throughput +aggregate_report_stddev=Std. Dev. +aggregate_report_total_label=TOTAL +ajp_sampler_title=AJP/1.3 Sampler +als_message=Note\: The Access Log Parser is generic in design and allows you to plugin +als_message2=your own parser. To do so, implement the LogParser, add the jar to the +als_message3=/lib directory and enter the class in the sampler. +analyze=Analyze Data File... +anchor_modifier_title=HTML Link Parser +appearance=Look and Feel +argument_must_not_be_negative=The Argument must not be negative\! +arguments_panel_title=OS Process Parameters +assertion_assume_success=Ignore Status +assertion_body_resp=Response Body +assertion_code_resp=Response Code +assertion_contains=Contains +assertion_equals=Equals +assertion_headers=Response Headers +assertion_matches=Matches +assertion_message_resp=Response Message +assertion_network_size=Full Response +assertion_not=Not +assertion_pattern_match_rules=Pattern Matching Rules +assertion_patterns_to_test=Patterns to Test +assertion_resp_field=Response Field to Test +assertion_resp_size_field=Response Size Field to Test +assertion_substring=Substring +assertion_text_resp=Text Response +assertion_textarea_label=Assertions\: +assertion_title=Response Assertion +assertion_url_samp=URL Sampled +assertion_visualizer_title=Assertion Results +attribute=Attribute +attrs=Attributes +auth_base_url=Base URL +auth_manager_title=HTTP Authorization Manager +auths_stored=Authorizations Stored in the Authorization Manager +average=Average +average_bytes=Avg. Bytes +bind=Thread Bind +bouncy_castle_unavailable_message=The jars for bouncy castle are unavailable, please add them to your classpath. +browse=Browse... +bsf_sampler_title=BSF Sampler +bsf_script=Script to run (variables: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +bsf_script_file=Script file to run +bsf_script_language=Scripting language\: +bsf_script_parameters=Parameters to pass to script/file\: +bsh_assertion_script=Script (see below for variables that are defined) +bsh_assertion_script_variables=The following variables are defined for the script:\nRead/Write: Failure, FailureMessage, SampleResult, vars, props, log.\nReadOnly: Response[Data|Code|Message|Headers], RequestHeaders, SampleLabel, SamplerData, ctx +bsh_assertion_title=BeanShell Assertion +bsh_function_expression=Expression to evaluate +bsh_sampler_title=BeanShell Sampler +bsh_script=Script (see below for variables that are defined) +bsh_script_file=Script file +bsh_script_parameters=Parameters (-> String Parameters and String []bsh.args) +bsh_script_reset_interpreter=Reset bsh.Interpreter before each call +bsh_script_variables=The following variables are defined for the script\:\nSampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +busy_testing=I'm busy testing, please stop the test before changing settings +cache_manager_size=Max Number of elements in cache +cache_manager_title=HTTP Cache Manager +cache_session_id=Cache Session Id? +cancel=Cancel +cancel_exit_to_save=There are test items that have not been saved. Do you wish to save before exiting? +cancel_new_to_save=There are test items that have not been saved. Do you wish to save before clearing the test plan? +cancel_revert_project=There are test items that have not been saved. Do you wish to revert to the previously saved test plan? +change_parent=Change Controller +char_value=Unicode character number (decimal or 0xhex) +check_return_code_title=Check Return Code +choose_function=Choose a function +choose_language=Choose Language +clear=Clear +clear_all=Clear All +clear_cache_per_iter=Clear cache each iteration? +clear_cookies_per_iter=Clear cookies each iteration? +close=Close +column_delete_disallowed=Deleting this column is not permitted +column_number=Column number of CSV file | next | *alias +command_config_box_title=Command to Execute +command_field_title=Command: +compare=Compare +comparefilt=Compare filter +comparison_differ_content=Responses differ in content +comparison_differ_time=Responses differ in response time by more than +comparison_invalid_node=Invalid Node +comparison_regex_string=Regex String +comparison_regex_substitution=Substitution +comparison_response_time=Response Time: +comparison_unit=\ ms +comparison_visualizer_title=Comparison Assertion Visualizer +config_element=Config Element +config_save_settings=Configure +configure_wsdl=Configure +confirm=Confirm +constant_throughput_timer_memo=Add a delay between sampling to attain constant throughput +constant_timer_delay=Thread Delay (in milliseconds)\: +constant_timer_memo=Add a constant delay between sampling +constant_timer_title=Constant Timer +content_encoding=Content encoding\: +controller=Controller +cookie_manager_policy=Cookie Policy +cookie_manager_title=HTTP Cookie Manager +cookies_stored=User-Defined Cookies +copy=Copy +counter_config_title=Counter +counter_per_user=Track counter independently for each user +counter_reset_per_tg_iteration=Reset counter on each Thread Group Iteration +countlim=Size limit +csvread_file_file_name=CSV file to get values from | *alias +cut=Cut +cut_paste_function=Copy and paste function string +database_conn_pool_max_usage=Max Usage For Each Connection\: +database_conn_pool_props=Database Connection Pool +database_conn_pool_size=Number of Connections in Pool\: +database_conn_pool_title=JDBC Database Connection Pool Defaults +database_driver_class=Driver Class\: +database_login_title=JDBC Database Login Defaults +database_sql_query_string=SQL Query String\: +database_sql_query_title=JDBC SQL Query Defaults +database_testing_title=JDBC Request +database_url=JDBC URL\: +database_url_jdbc_props=Database URL and JDBC Driver +ddn=DN +de=German +debug_off=Disable debug +debug_on=Enable debug +default_parameters=Default Parameters +default_value_field=Default Value\: +delay=Startup delay (seconds) +delete=Delete +delete_parameter=Delete Variable +delete_test=Delete Test +delete_user=Delete User +deltest=Deletion test +deref=Dereference aliases +description=Description +detail=Detail +directory_field_title=Directory: +disable=Disable +distribution_graph_title=Distribution Graph (alpha) +distribution_note1=The graph will update every 10 samples +dn=DN +domain=Domain +done=Done +down=Down +duplicate=Duplicate +duration=Duration (seconds) +duration_assertion_duration_test=Duration to Assert +duration_assertion_failure=The operation lasted too long\: It took {0} milliseconds, but should not have lasted longer than {1} milliseconds. +duration_assertion_input_error=Please enter a valid positive integer. +duration_assertion_label=Duration in milliseconds\: +duration_assertion_title=Duration Assertion +edit=Edit +email_results_title=Email Results +en=English +enable=Enable +encode?=Encode? +encoded_value=URL Encoded Value +endtime=End Time +entry_dn=Entry DN +entrydn=Entry DN +environment_panel_title=Environment Variables +error_indicator_tooltip=Show the number of errors in log, click to open Log Viewer panel +error_loading_help=Error loading help page +error_occurred=Error Occurred +error_title=Error +es=Spanish +escape_html_string=String to escape +eval_name_param=Text containing variable and function references +evalvar_name_param=Name of variable +example_data=Sample Data +example_title=Example Sampler +exit=Exit +expected_return_code_title=Expected Return Code: +expiration=Expiration +field_name=Field name +file=File +file_already_in_use=That file is already in use +file_visualizer_append=Append to Existing Data File +file_visualizer_auto_flush=Automatically Flush After Each Data Sample +file_visualizer_browse=Browse... +file_visualizer_close=Close +file_visualizer_file_options=File Options +file_visualizer_filename=Filename +file_visualizer_flush=Flush +file_visualizer_missing_filename=No output filename specified. +file_visualizer_open=Open +file_visualizer_output_file=Write results to file / Read from file +file_visualizer_submit_data=Include Submitted Data +file_visualizer_title=File Reporter +file_visualizer_verbose=Verbose Output +filename=File Name +follow_redirects=Follow Redirects +follow_redirects_auto=Redirect Automatically +font.sansserif=Sans Serif +font.serif=Serif +fontstyle.bold=Bold +fontstyle.italic=Italic +fontstyle.normal=Normal +foreach_controller_title=ForEach Controller +foreach_input=Input variable prefix +foreach_output=Output variable name +foreach_use_separator=Add "_" before number ? +format=Number format +fr=French +ftp_binary_mode=Use Binary mode ? +ftp_get=get(RETR) +ftp_local_file=Local File: +ftp_local_file_contents=Local File Contents: +ftp_put=put(STOR) +ftp_remote_file=Remote File: +ftp_sample_title=FTP Request Defaults +ftp_save_response_data=Save File in Response ? +ftp_testing_title=FTP Request +function_dialog_menu_item=Function Helper Dialog +function_helper_title=Function Helper +function_name_param=Name of variable in which to store the result (required) +function_name_paropt=Name of variable in which to store the result (optional) +function_params=Function Parameters +functional_mode=Functional Test Mode (i.e. save Response Data and Sampler Data) +functional_mode_explanation=Selecting Functional Test Mode may adversely affect performance. +gaussian_timer_delay=Constant Delay Offset (in milliseconds)\: +gaussian_timer_memo=Adds a random delay with a gaussian distribution +gaussian_timer_range=Deviation (in milliseconds)\: +gaussian_timer_title=Gaussian Random Timer +generate=Generate +generator=Name of Generator class +generator_cnf_msg=Could not find the generator class. Please make sure you place your jar file in the /lib directory. +generator_illegal_msg=Could not access the generator class due to IllegalAccessException. +generator_instantiate_msg=Could not create an instance of the generator parser. Please make sure the generator implements Generator interface. +get_xml_from_file=File with SOAP XML Data (overrides above text) +get_xml_from_random=Message(s) Folder +graph_choose_graphs=Graphs to Display +graph_full_results_title=Graph Full Results +graph_results_average=Average +graph_results_data=Data +graph_results_deviation=Deviation +graph_results_latest_sample=Latest Sample +graph_results_median=Median +graph_results_ms=ms +graph_results_no_samples=No of Samples +graph_results_throughput=Throughput +graph_results_title=Graph Results +grouping_add_separators=Add separators between groups +grouping_in_controllers=Put each group in a new controller +grouping_in_transaction_controllers=Put each group in a new transaction controller +grouping_mode=Grouping\: +grouping_no_groups=Do not group samplers +grouping_store_first_only=Store 1st sampler of each group only +header_manager_title=HTTP Header Manager +headers_stored=Headers Stored in the Header Manager +help=Help +help_node=What's this node? +html_assertion_file=Write JTidy report to file +html_assertion_label=HTML Assertion +html_assertion_title=HTML Assertion +html_parameter_mask=HTML Parameter Mask +http_implementation=Implementation: +http_response_code=HTTP response code +http_url_rewriting_modifier_title=HTTP URL Re-writing Modifier +http_user_parameter_modifier=HTTP User Parameter Modifier +httpmirror_title=HTTP Mirror Server +httpmirror_settings=Settings +httpmirror_max_pool_size=Max number of Threads: +httpmirror_max_queue_size=Max queue size: +id_prefix=ID Prefix +id_suffix=ID Suffix +if_controller_evaluate_all=Evaluate for all children? +if_controller_expression=Interpret Condition as Variable Expression? +if_controller_label=Condition (default Javascript) +if_controller_title=If Controller +ignore_subcontrollers=Ignore sub-controller blocks +include_controller=Include Controller +include_equals=Include Equals? +include_path=Include Test Plan +increment=Increment +infinite=Forever +initial_context_factory=Initial Context Factory +insert_after=Insert After +insert_before=Insert Before +insert_parent=Insert Parent +interleave_control_title=Interleave Controller +intsum_param_1=First int to add. +intsum_param_2=Second int to add - further ints can be summed by adding further arguments. +invalid_data=Invalid data +invalid_mail=Error occurred sending the e-mail +invalid_mail_address=One or more invalid e-mail addresses detected +invalid_mail_server=Problem contacting the e-mail server (see JMeter log file) +invalid_variables=Invalid variables +iteration_counter_arg_1=TRUE, for each user to have own counter, FALSE for a global counter +iterator_num=Loop Count\: +ja=Japanese +jar_file=Jar Files +java_request=Java Request +java_request_defaults=Java Request Defaults +javascript_expression=JavaScript expression to evaluate +jexl_expression=JEXL expression to evaluate +jms_auth_required=Required +jms_client_caption=Receiver client uses MessageConsumer.receive() to listen for message. +jms_client_caption2=MessageListener uses onMessage(Message) interface to listen for new messages. +jms_client_id=Client ID +jms_client_type=Client +jms_communication_style=Communication style +jms_concrete_connection_factory=Concrete Connection Factory +jms_config=Message source +jms_config_title=JMS Configuration +jms_connection_factory=Connection Factory +jms_correlation_title=Use alternate fields for message correlation +jms_dest_setup=Setup +jms_dest_setup_dynamic=Each sample +jms_dest_setup_static=At startup +jms_durable_subscription_id=Durable Subscription ID +jms_file=File +jms_initial_context_factory=Initial Context Factory +jms_itertions=Number of samples to aggregate +jms_jndi_defaults_title=JNDI Default Configuration +jms_jndi_props=JNDI Properties +jms_map_message=Map Message +jms_message_title=Message properties +jms_message_type=Message Type +jms_msg_content=Content +jms_object_message=Object Message +jms_point_to_point=JMS Point-to-Point +jms_props=JMS Properties +jms_provider_url=Provider URL +jms_publisher=JMS Publisher +jms_pwd=Password +jms_queue=Queue +jms_queue_connection_factory=QueueConnection Factory +jms_queueing=JMS Resources +jms_random_file=Random File +jms_read_response=Read Response +jms_receive_queue=JNDI name Receive queue +jms_request=Request Only +jms_requestreply=Request Response +jms_sample_title=JMS Default Request +jms_selector=JMS Selector +jms_send_queue=JNDI name Request queue +jms_separator=Separator +jms_stop_between_samples=Stop between samples? +jms_subscriber_on_message=Use MessageListener.onMessage() +jms_subscriber_receive=Use MessageConsumer.receive() +jms_subscriber_title=JMS Subscriber +jms_testing_title=Messaging Request +jms_text_message=Text Message or Object Message serialized to XML by XStream +jms_timeout=Timeout (milliseconds) +jms_topic=Destination +jms_use_auth=Use Authorization? +jms_use_file=From file +jms_use_non_persistent_delivery=Use non-persistent delivery mode? +jms_use_properties_file=Use jndi.properties file +jms_use_random_file=Random File +jms_use_req_msgid_as_correlid=Use Request Message Id +jms_use_res_msgid_as_correlid=Use Response Message Id +jms_use_text=Textarea +jms_user=User +jndi_config_title=JNDI Configuration +jndi_lookup_name=Remote Interface +jndi_lookup_title=JNDI Lookup Configuration +jndi_method_button_invoke=Invoke +jndi_method_button_reflect=Reflect +jndi_method_home_name=Home Method Name +jndi_method_home_parms=Home Method Parameters +jndi_method_name=Method Configuration +jndi_method_remote_interface_list=Remote Interfaces +jndi_method_remote_name=Remote Method Name +jndi_method_remote_parms=Remote Method Parameters +jndi_method_title=Remote Method Configuration +jndi_testing_title=JNDI Request +jndi_url_jndi_props=JNDI Properties +junit_append_error=Append assertion errors +junit_append_exception=Append runtime exceptions +junit_constructor_error=Unable to create an instance of the class +junit_constructor_string=Constructor String Label +junit_create_instance_per_sample=Create a new instance per sample +junit_do_setup_teardown=Do not call setUp and tearDown +junit_error_code=Error Code +junit_error_default_code=9999 +junit_error_default_msg=An unexpected error occured +junit_error_msg=Error Message +junit_failure_code=Failure Code +junit_failure_default_code=0001 +junit_failure_default_msg=Test failed +junit_failure_msg=Failure Message +junit_junit4=Search for JUnit 4 annotations (instead of JUnit 3) +junit_pkg_filter=Package Filter +junit_request=JUnit Request +junit_request_defaults=JUnit Request Defaults +junit_success_code=Success Code +junit_success_default_code=1000 +junit_success_default_msg=Test successful +junit_success_msg=Success Message +junit_test_config=JUnit Test Parameters +junit_test_method=Test Method +ldap_argument_list=LDAPArgument List +ldap_connto=Connection timeout (in milliseconds) +ldap_parse_results=Parse the search results ? +ldap_sample_title=LDAP Request Defaults +ldap_search_baseobject=Perform baseobject search +ldap_search_onelevel=Perform onelevel search +ldap_search_subtree=Perform subtree search +ldap_secure=Use Secure LDAP Protocol ? +ldap_testing_title=LDAP Request +ldapext_sample_title=LDAP Extended Request Defaults +ldapext_testing_title=LDAP Extended Request +library=Library +load=Load +load_wsdl=Load WSDL +log_errors_only=Errors +log_file=Location of log File +log_function_comment=Additional comment (optional) +log_function_level=Log level (default INFO) or OUT or ERR +log_function_string=String to be logged +log_function_string_ret=String to be logged (and returned) +log_function_throwable=Throwable text (optional) +log_only=Log/Display Only: +log_parser=Name of Log Parser class +log_parser_cnf_msg=Could not find the class. Please make sure you place your jar file in the /lib directory. +log_parser_illegal_msg=Could not access the class due to IllegalAccessException. +log_parser_instantiate_msg=Could not create an instance of the log parser. Please make sure the parser implements LogParser interface. +log_sampler=Tomcat Access Log Sampler +log_success_only=Successes +logic_controller_title=Simple Controller +login_config=Login Configuration +login_config_element=Login Config Element +longsum_param_1=First long to add +longsum_param_2=Second long to add - further longs can be summed by adding further arguments. +loop_controller_title=Loop Controller +looping_control=Looping Control +lower_bound=Lower Bound +mail_reader_account=Username: +mail_reader_all_messages=All +mail_reader_delete=Delete messages from the server +mail_reader_folder=Folder: +mail_reader_num_messages=Number of messages to retrieve: +mail_reader_password=Password: +mail_reader_port=Server Port (optional): +mail_reader_server=Server Host: +mail_reader_server_type=Protocol (e.g. pop3, imaps): +mail_reader_storemime=Store the message using MIME (raw) +mail_reader_title=Mail Reader Sampler +mail_sent=Mail sent successfully +mailer_addressees=Addressee(s): +mailer_attributes_panel=Mailing attributes +mailer_connection_security=Connection security: +mailer_error=Couldn't send mail. Please correct any misentries. +mailer_failure_limit=Failure Limit: +mailer_failure_subject=Failure Subject: +mailer_failures=Failures: +mailer_from=From: +mailer_host=Host: +mailer_login=Login: +mailer_msg_title_error=Error +mailer_msg_title_information=Information +mailer_password=Password: +mailer_port=Port: +mailer_string=E-Mail Notification +mailer_success_limit=Success Limit: +mailer_success_subject=Success Subject: +mailer_test_mail=Test Mail +mailer_title_message=Message +mailer_title_settings=Mailer settings +mailer_title_smtpserver=SMTP server +mailer_visualizer_title=Mailer Visualizer +match_num_field=Match No. (0 for Random)\: +max=Maximum +maximum_param=The maximum value allowed for a range of values +md5hex_assertion_failure=Error asserting MD5 sum : got {0} but should have been {1} +md5hex_assertion_label=MD5Hex +md5hex_assertion_md5hex_test=MD5Hex to Assert +md5hex_assertion_title=MD5Hex Assertion +memory_cache=Memory Cache +menu_assertions=Assertions +menu_close=Close +menu_collapse_all=Collapse All +menu_config_element=Config Element +menu_edit=Edit +menu_expand_all=Expand All +menu_fragments=Test Fragment +menu_generative_controller=Sampler +menu_listener=Listener +menu_logic_controller=Logic Controller +menu_logger_panel=Log Viewer +menu_merge=Merge +menu_modifiers=Modifiers +menu_non_test_elements=Non-Test Elements +menu_open=Open +menu_post_processors=Post Processors +menu_pre_processors=Pre Processors +menu_response_based_modifiers=Response Based Modifiers +menu_search=Search +menu_search_reset=Reset Search +menu_tables=Table +menu_threads=Threads (Users) +menu_timer=Timer +menu_toolbar=Toolbar +metadata=MetaData +method=Method\: +mimetype=Mimetype +minimum_param=The minimum value allowed for a range of values +minute=minute +modddn=Old entry name +modification_controller_title=Modification Controller +modification_manager_title=Modification Manager +modify_test=Modify Test +modtest=Modification test +module_controller_module_to_run=Module To Run +module_controller_title=Module Controller +module_controller_warning=Could not find module: +monitor_equation_active=Active: (busy/max) > 25% +monitor_equation_dead=Dead: no response +monitor_equation_healthy=Healthy: (busy/max) < 25% +monitor_equation_load=Load: ( (busy / max) * 50) + ( (used memory / max memory) * 50) +monitor_equation_warning=Warning: (busy/max) > 67% +monitor_health_tab_title=Health +monitor_health_title=Monitor Results +monitor_is_title=Use as Monitor +monitor_label_left_bottom=0 % +monitor_label_left_middle=50 % +monitor_label_left_top=100 % +monitor_label_prefix=Connection Prefix +monitor_label_right_active=Active +monitor_label_right_dead=Dead +monitor_label_right_healthy=Healthy +monitor_label_right_warning=Warning +monitor_legend_health=Health +monitor_legend_load=Load +monitor_legend_memory_per=Memory % (used/total) +monitor_legend_thread_per=Thread % (busy/max) +monitor_load_factor_mem=50 +monitor_load_factor_thread=50 +monitor_performance_servers=Servers +monitor_performance_tab_title=Performance +monitor_performance_title=Performance Graph +name=Name\: +new=New +newdn=New distinguished name +next=Next +no=Norwegian +number_of_threads=Number of Threads (users)\: +obsolete_test_element=This test element is obsolete +once_only_controller_title=Once Only Controller +opcode=opCode +open=Open... +option=Options +optional_tasks=Optional Tasks +paramtable=Send Parameters With the Request\: +password=Password +paste=Paste +paste_insert=Paste As Insert +path=Path\: +path_extension_choice=Path Extension (use ";" as separator) +path_extension_dont_use_equals=Do not use equals in path extension (Intershop Enfinity compatibility) +path_extension_dont_use_questionmark=Do not use questionmark in path extension (Intershop Enfinity compatibility) +patterns_to_exclude=URL Patterns to Exclude +patterns_to_include=URL Patterns to Include +pkcs12_desc=PKCS 12 Key (*.p12) +pl=Polish +poisson_timer_delay=Constant Delay Offset (in milliseconds)\: +poisson_timer_memo=Adds a random delay with a poisson distribution +poisson_timer_range=Lambda (in milliseconds)\: +poisson_timer_title=Poisson Random Timer +port=Port\: +post_as_parameters=Parameters +post_body=Post Body +post_body_raw=Raw Post Body +post_thread_group_title=tearDown Thread Group +previous=Previous +property_as_field_label={0}\: +property_default_param=Default value +property_edit=Edit +property_editor.value_is_invalid_message=The text you just entered is not a valid value for this property.\nThe property will be reverted to its previous value. +property_editor.value_is_invalid_title=Invalid input +property_name_param=Name of property +property_returnvalue_param=Return Original Value of property (default false) ? +property_tool_tip={0}\: {1} +property_undefined=Undefined +property_value_param=Value of property +property_visualiser_title=Property Display +protocol=Protocol [http]\: +protocol_java_border=Java class +protocol_java_classname=Classname\: +protocol_java_config_tile=Configure Java Sample +protocol_java_test_title=Java Testing +provider_url=Provider URL +proxy_assertions=Add Assertions +proxy_cl_error=If specifying a proxy server, host and port must be given +proxy_content_type_exclude=Exclude\: +proxy_content_type_filter=Content-type filter +proxy_content_type_include=Include\: +proxy_daemon_bind_error=Could not create proxy - port in use. Choose another port. +proxy_daemon_error=Could not create proxy - see log for details +proxy_general_settings=Global Settings +proxy_headers=Capture HTTP Headers +proxy_httpsspoofing=Attempt HTTPS Spoofing +proxy_httpsspoofing_match=Only spoof URLs matching: +proxy_regex=Regex matching +proxy_sampler_settings=HTTP Sampler settings +proxy_sampler_type=Type\: +proxy_separators=Add Separators +proxy_target=Target Controller\: +proxy_test_plan_content=Test plan content +proxy_title=HTTP Proxy Server +pt_br=Portugese (Brazilian) +ramp_up=Ramp-Up Period (in seconds)\: +random_control_title=Random Controller +random_order_control_title=Random Order Controller +random_string_chars_to_use=Chars to use for random string generation +random_string_length=Random string length +read_response_message=Read response is not checked. To see the response, please check the box in the sampler. +read_response_note=If read response is unchecked, the sampler will not read the response +read_response_note2=or set the SampleResult. This improves performance, but it means +read_response_note3=the response content won't be logged. +read_soap_response=Read SOAP Response +realm=Realm +record_controller_title=Recording Controller +ref_name_field=Reference Name\: +regex_extractor_title=Regular Expression Extractor +regex_field=Regular Expression\: +regex_source=Response Field to check +regex_src_body=Body +regex_src_body_unescaped=Body (unescaped) +regex_src_hdrs=Headers +regex_src_url=URL +regexfunc_param_1=Regular expression used to search previous sample - or variable. +regexfunc_param_2=Template for the replacement string, using groups from the regular expression. Format is $[group]$. Example $1$. +regexfunc_param_3=Which match to use. An integer 1 or greater, RAND to indicate JMeter should randomly choose, A float, or ALL indicating all matches should be used ([1]) +regexfunc_param_4=Between text. If ALL is selected, the between text will be used to generate the results ([""]) +regexfunc_param_5=Default text. Used instead of the template if the regular expression finds no matches ([""]) +regexfunc_param_7=Input variable name containing the text to be parsed ([previous sample]) +regexp_render_no_text=Data response result isn't text. +regexp_tester_button_test=Test +regexp_tester_field=Regular expression\: +regexp_tester_title=RegExp Tester +remote_error_init=Error initialising remote server +remote_error_starting=Error starting remote server +remote_exit=Remote Exit +remote_exit_all=Remote Exit All +remote_shut=Remote Shutdown +remote_shut_all=Remote Shutdown All +remote_start=Remote Start +remote_start_all=Remote Start All +remote_stop=Remote Stop +remote_stop_all=Remote Stop All +remove=Remove +remove_confirm_title=Confirm remove? +remove_confirm_msg=Are you sure you want remove the selected element(s)? +rename=Rename entry +report=Report +report_bar_chart=Bar Chart +report_bar_graph_url=URL +report_base_directory=Base Directory +report_chart_caption=Chart Caption +report_chart_x_axis=X Axis +report_chart_x_axis_label=Label for X Axis +report_chart_y_axis=Y Axis +report_chart_y_axis_label=Label for Y Axis +report_line_graph=Line Graph +report_line_graph_urls=Include URLs +report_output_directory=Output Directory for Report +report_page=Report Page +report_page_element=Page Element +report_page_footer=Page Footer +report_page_header=Page Header +report_page_index=Create Page Index +report_page_intro=Page Introduction +report_page_style_url=Stylesheet url +report_page_title=Page Title +report_pie_chart=Pie Chart +report_plan=Report Plan +report_select=Select +report_summary=Report Summary +report_table=Report Table +report_writer=Report Writer +report_writer_html=HTML Report Writer +request_data=Request Data +reset_gui=Reset Gui +response_save_as_md5=Save response as MD5 hash? +restart=Restart +resultaction_title=Result Status Action Handler +resultsaver_addtimestamp=Add timestamp +resultsaver_errors=Save Failed Responses only +resultsaver_numberpadlen=Minumum Length of sequence number +resultsaver_prefix=Filename prefix\: +resultsaver_skipautonumber=Don't add number to prefix +resultsaver_skipsuffix=Don't add suffix +resultsaver_success=Save Successful Responses only +resultsaver_title=Save Responses to a file +resultsaver_variable=Variable Name: +retobj=Return object +return_code_config_box_title=Return Code Configuration +reuseconnection=Re-use connection +revert_project=Revert +revert_project?=Revert project? +root=Root +root_title=Root +run=Run +running_test=Running test +runtime_controller_title=Runtime Controller +runtime_seconds=Runtime (seconds) +sample_result_save_configuration=Sample Result Save Configuration +sample_scope=Apply to: +sample_scope_all=Main sample and sub-samples +sample_scope_children=Sub-samples only +sample_scope_parent=Main sample only +sample_scope_variable=JMeter Variable +sampler_label=Label +sampler_on_error_action=Action to be taken after a Sampler error +sampler_on_error_continue=Continue +sampler_on_error_start_next_loop=Start Next Thread Loop +sampler_on_error_stop_test=Stop Test +sampler_on_error_stop_test_now=Stop Test Now +sampler_on_error_stop_thread=Stop Thread +save=Save +save?=Save? +save_all_as=Save Test Plan as +save_as=Save Selection As... +save_as_error=More than one item selected! +save_as_image=Save Node As Image +save_as_image_all=Save Screen As Image +save_assertionresultsfailuremessage=Save Assertion Failure Message +save_assertions=Save Assertion Results (XML) +save_asxml=Save As XML +save_bytes=Save byte count +save_code=Save Response Code +save_datatype=Save Data Type +save_encoding=Save Encoding +save_fieldnames=Save Field Names (CSV) +save_filename=Save Response Filename +save_graphics=Save Graph +save_hostname=Save Hostname +save_idletime=Save Idle Time +save_label=Save Label +save_latency=Save Latency +save_message=Save Response Message +save_overwrite_existing_file=The selected file already exists, do you want to overwrite it? +save_requestheaders=Save Request Headers (XML) +save_responsedata=Save Response Data (XML) +save_responseheaders=Save Response Headers (XML) +save_samplecount=Save Sample and Error Counts +save_samplerdata=Save Sampler Data (XML) +save_subresults=Save Sub Results (XML) +save_success=Save Success +save_threadcounts=Save Active Thread Counts +save_threadname=Save Thread Name +save_time=Save Elapsed Time +save_timestamp=Save Time Stamp +save_url=Save URL +sbind=Single bind/unbind +scheduler=Scheduler +scheduler_configuration=Scheduler Configuration +scope=Scope +search_base=Search base +search_filter=Search Filter +search_test=Search Test +search_text_button_close=Close +search_text_button_find=Find +search_text_button_next=Find next +search_text_chkbox_case=Case sensitive +search_text_chkbox_regexp=Regular exp. +search_text_field=Search: +search_text_msg_not_found=Text not found +search_text_title_not_found=Not found +search_tree_title=Search Tree +search=Search +searchbase=Search base +searchfilter=Search Filter +searchtest=Search test +second=second +secure=Secure +send_file=Send Files With the Request\: +send_file_browse=Browse... +send_file_filename_label=File Path\: +send_file_mime_label=MIME Type\: +send_file_param_name_label=Parameter Name\: +server=Server Name or IP\: +servername=Servername \: +session_argument_name=Session Argument Name +setup_thread_group_title=setUp Thread Group +should_save=You should save your test plan before running it. \nIf you are using supporting data files (ie, for CSV Data Set or _StringFromFile), \nthen it is particularly important to first save your test script. \nDo you want to save your test plan first? +shutdown=Shutdown +simple_config_element=Simple Config Element +simple_data_writer_title=Simple Data Writer +size_assertion_comparator_error_equal=been equal to +size_assertion_comparator_error_greater=been greater than +size_assertion_comparator_error_greaterequal=been greater or equal to +size_assertion_comparator_error_less=been less than +size_assertion_comparator_error_lessequal=been less than or equal to +size_assertion_comparator_error_notequal=not been equal to +size_assertion_comparator_label=Type of Comparison +size_assertion_failure=The result was the wrong size\: It was {0} bytes, but should have {1} {2} bytes. +size_assertion_input_error=Please enter a valid positive integer. +size_assertion_label=Size in bytes\: +size_assertion_size_test=Size to Assert +size_assertion_title=Size Assertion +smime_assertion_issuer_dn=Issuer distinguished name +smime_assertion_message_position=Execute assertion on message at position +smime_assertion_not_signed=Message not signed +smime_assertion_signature=Signature +smime_assertion_signer=Signer certificate +smime_assertion_signer_by_file=Certificate file +smime_assertion_signer_constraints=Check values +smime_assertion_signer_dn=Signer distinguished name +smime_assertion_signer_email=Signer email address +smime_assertion_signer_no_check=No check +smime_assertion_signer_serial=Serial Number +smime_assertion_title=SMIME Assertion +smime_assertion_verify_signature=Verify signature +smtp_additional_settings=Additional Settings +smtp_attach_file=Attach file(s): +smtp_attach_file_tooltip=Separate multiple files with ";" +smtp_auth_settings=Auth settings +smtp_bcc=Address To BCC: +smtp_cc=Address To CC: +smtp_default_port=(Defaults: SMTP:25, SSL:465, StartTLS:587) +smtp_eml=Send .eml: +smtp_enabledebug=Enable debug logging? +smtp_enforcestarttls=Enforce StartTLS +smtp_enforcestarttls_tooltip=Enforces the server to use StartTLS.
If not selected and the SMTP-Server doesn't support StartTLS,
a normal SMTP-Connection will be used as fallback instead.
Please note that this checkbox creates a file in "/tmp/",
so this will cause problems under windows. +smtp_from=Address From: +smtp_header_add=Add Header +smtp_header_name=Header Name +smtp_header_remove=Remove +smtp_header_value=Header Value +smtp_mail_settings=Mail settings +smtp_message=Message: +smtp_message_settings=Message settings +smtp_messagesize=Calculate message size +smtp_password=Password: +smtp_plainbody=Send plain body (i.e. not multipart/mixed) +smtp_replyto=Address Reply-To: +smtp_sampler_title=SMTP Sampler +smtp_security_settings=Security settings +smtp_server=Server: +smtp_server_port=Port: +smtp_server_settings=Server settings +smtp_subject=Subject: +smtp_suppresssubj=Suppress Subject Header +smtp_timestamp=Include timestamp in subject +smtp_to=Address To: +smtp_trustall=Trust all certificates +smtp_trustall_tooltip=Enforces JMeter to trust all certificates, whatever CA it comes from. +smtp_truststore=Local truststore: +smtp_truststore_tooltip=The pathname of the truststore.
Relative paths are resolved against the current directory.
Failing that, against the directory containing the test script (JMX file) +smtp_useauth=Use Auth +smtp_usenone=Use no security features +smtp_username=Username: +smtp_usessl=Use SSL +smtp_usestarttls=Use StartTLS +smtp_usetruststore=Use local truststore +smtp_usetruststore_tooltip=Allows JMeter to use a local truststore. +soap_action=Soap Action +soap_data_title=Soap/XML-RPC Data +soap_sampler_title=SOAP/XML-RPC Request +soap_send_action=Send SOAPAction: +soap_sampler_file_invalid=Filename references a missing or unreadable file\: +spline_visualizer_average=Average +spline_visualizer_incoming=Incoming +spline_visualizer_maximum=Maximum +spline_visualizer_minimum=Minimum +spline_visualizer_title=Spline Visualizer +spline_visualizer_waitingmessage=Waiting for samples +split_function_separator=String to split on. Default is , (comma). +split_function_string=String to split +ssl_alias_prompt=Please type your preferred alias +ssl_alias_select=Select your alias for the test +ssl_alias_title=Client Alias +ssl_error_title=Key Store Problem +ssl_pass_prompt=Please type your password +ssl_pass_title=KeyStore Password +ssl_port=SSL Port +sslmanager=SSL Manager +start=Start +start_no_timers=Start no pauses +starttime=Start Time +stop=Stop +stopping_test=Shutting down all test threads. Please be patient. +stopping_test_failed=One or more test threads won't exit; see log file. +stopping_test_title=Stopping Test +string_from_file_encoding=File encoding if not the platform default (opt) +string_from_file_file_name=Enter path (absolute or relative) to file +string_from_file_seq_final=Final file sequence number (opt) +string_from_file_seq_start=Start file sequence number (opt) +summariser_title=Generate Summary Results +summary_report=Summary Report +switch_controller_label=Switch Value +switch_controller_title=Switch Controller +system_sampler_title=OS Process Sampler +table_visualizer_bytes=Bytes +table_visualizer_sample_num=Sample # +table_visualizer_sample_time=Sample Time(ms) +table_visualizer_start_time=Start Time +table_visualizer_status=Status +table_visualizer_success=Success +table_visualizer_thread_name=Thread Name +table_visualizer_warning=Warning +target_server=Target Server +tcp_classname=TCPClient classname\: +tcp_config_title=TCP Sampler Config +tcp_nodelay=Set NoDelay +tcp_port=Port Number\: +tcp_request_data=Text to send +tcp_sample_title=TCP Sampler +tcp_timeout=Timeout (milliseconds)\: +template_field=Template\: +test=Test +test_action_action=Action +test_action_duration=Duration (milliseconds) +test_action_pause=Pause +test_action_restart_next_loop=Go to next loop iteration +test_action_stop=Stop +test_action_stop_now=Stop Now +test_action_target=Target +test_action_target_test=All Threads +test_action_target_thread=Current Thread +test_action_title=Test Action +test_configuration=Test Configuration +test_fragment_title=Test Fragment +test_plan=Test Plan +test_plan_classpath_browse=Add directory or jar to classpath +testconfiguration=Test Configuration +testplan.serialized=Run Thread Groups consecutively (i.e. run groups one at a time) +testplan_comments=Comments\: +testt=Test +textbox_cancel=Cancel +textbox_close=Close +textbox_save_close=Save & Close +textbox_title_edit=Edit text +textbox_title_view=View text +textbox_tooltip_cell=Double click to view/edit +thread_delay_properties=Thread Delay Properties +thread_group_title=Thread Group +thread_properties=Thread Properties +threadgroup=Thread Group +throughput_control_bynumber_label=Total Executions +throughput_control_bypercent_label=Percent Executions +throughput_control_perthread_label=Per User +throughput_control_title=Throughput Controller +throughput_control_tplabel=Throughput +time_format=Format string for SimpleDateFormat (optional) +timelim=Time limit +toggle=Toggle +toolbar_icon_set_not_found=The file description of toolbar icon set is not found. See logs. +tr=Turkish +transaction_controller_include_timers=Include duration of timer and pre-post processors in generated sample +transaction_controller_parent=Generate parent sample +transaction_controller_title=Transaction Controller +unbind=Thread Unbind +unescape_html_string=String to unescape +unescape_string=String containing Java escapes +uniform_timer_delay=Constant Delay Offset (in milliseconds)\: +uniform_timer_memo=Adds a random delay with a uniform distribution +uniform_timer_range=Random Delay Maximum (in milliseconds)\: +uniform_timer_title=Uniform Random Timer +up=Up +update=Update +update_per_iter=Update Once Per Iteration +upload=File Upload +upper_bound=Upper Bound +url=URL +url_config_get=GET +url_config_http=HTTP +url_config_https=HTTPS +url_config_post=POST +url_config_protocol=Protocol\: +url_config_title=HTTP Request Defaults +url_full_config_title=UrlFull Sample +url_multipart_config_title=HTTP Multipart Request Defaults +use_expires=Use Cache-Control/Expires header when processing GET requests +use_keepalive=Use KeepAlive +use_multipart_for_http_post=Use multipart/form-data for POST +use_multipart_mode_browser=Browser-compatible headers +use_recording_controller=Use Recording Controller +user=User +user_defined_test=User Defined Test +user_defined_variables=User Defined Variables +user_param_mod_help_note=(Do not change this. Instead, modify the file of that name in JMeter's /bin directory) +user_parameters_table=Parameters +user_parameters_title=User Parameters +userdn=Username +username=Username +userpw=Password +value=Value +var_name=Reference Name +variable_name_param=Name of variable (may include variable and function references) +view_graph_tree_title=View Graph Tree +view_results_assertion_error=Assertion error: +view_results_assertion_failure=Assertion failure: +view_results_assertion_failure_message=Assertion failure message: +view_results_autoscroll=Scroll automatically? +view_results_childsamples=Child samples? +view_results_desc=Shows the text results of sampling in tree form +view_results_error_count=Error Count: +view_results_fields=fields: +view_results_in_table=View Results in Table +view_results_latency=Latency: +view_results_load_time=Load time: +view_results_render=Render: +view_results_render_html=HTML +view_results_render_html_embedded=HTML (download resources) +view_results_render_json=JSON +view_results_render_text=Text +view_results_render_xml=XML +view_results_request_headers=Request Headers: +view_results_response_code=Response code: +view_results_response_headers=Response headers: +view_results_response_message=Response message: +view_results_response_partial_message=Start of message: +view_results_response_too_large_message=Response too large to be displayed. Size: +view_results_sample_count=Sample Count: +view_results_sample_start=Sample Start: +view_results_search_pane=Search pane +view_results_size_body_in_bytes=Body size in bytes: +view_results_size_headers_in_bytes=Headers size in bytes: +view_results_size_in_bytes=Size in bytes: +view_results_tab_assertion=Assertion result +view_results_tab_request=Request +view_results_tab_response=Response data +view_results_tab_sampler=Sampler result +view_results_table_fields_key=Additional field +view_results_table_fields_value=Value +view_results_table_headers_key=Response header +view_results_table_headers_value=Value +view_results_table_request_headers_key=Request header +view_results_table_request_headers_value=Value +view_results_table_request_http_cookie=Cookie +view_results_table_request_http_host=Host +view_results_table_request_http_method=Method +view_results_table_request_http_nohttp=No HTTP Sample +view_results_table_request_http_path=Path +view_results_table_request_http_port=Port +view_results_table_request_http_protocol=Protocol +view_results_table_request_params_key=Parameter name +view_results_table_request_params_value=Value +view_results_table_request_raw_nodata=No data to display +view_results_table_request_tab_http=HTTP +view_results_table_request_tab_raw=Raw +view_results_table_result_tab_parsed=Parsed +view_results_table_result_tab_raw=Raw +view_results_thread_name=Thread Name: +view_results_title=View Results +view_results_tree_title=View Results Tree +warning=Warning! +web_cannot_convert_parameters_to_raw=Cannot convert parameters to RAW Post body \nbecause one of the parameters has a name +web_cannot_switch_tab=You cannot switch because data cannot be converted\n to target Tab data, empty data to switch +web_parameters_lost_message=Switching to RAW Post body will convert the parameters.\nParameter table will be cleared when you select\nanother node or save the test plan.\nOK to proceeed? +web_proxy_server_title=Proxy Server +web_request=HTTP Request +web_server=Web Server +web_server_client=Client implementation: +web_server_domain=Server Name or IP\: +web_server_port=Port Number\: +web_server_timeout_connect=Connect: +web_server_timeout_response=Response: +web_server_timeout_title=Timeouts (milliseconds) +web_testing2_source_ip=Source IP address: +web_testing2_title=HTTP Request HTTPClient +web_testing_concurrent_download=Use concurrent pool. Size: +web_testing_embedded_url_pattern=Embedded URLs must match\: +web_testing_retrieve_images=Retrieve All Embedded Resources from HTML Files +web_testing_title=HTTP Request +webservice_configuration_wizard=WSDL helper +webservice_get_xml_from_random_title=Use random messages SOAP +webservice_maintain_session=Maintain HTTP Session +webservice_message_soap=WebService message +webservice_methods=Web Methods +webservice_proxy_host=Proxy Host +webservice_proxy_note=If Use HTTP Proxy is checked, but no host or port are provided, the sampler +webservice_proxy_note2=will look at command line options. If no proxy host or port are provided by +webservice_proxy_note3=either, it will fail silently. +webservice_proxy_port=Proxy Port +webservice_sampler_title=WebService(SOAP) Request +webservice_soap_action=SOAPAction +webservice_timeout=Timeout: +webservice_use_proxy=Use HTTP Proxy +while_controller_label=Condition (function or variable) +while_controller_title=While Controller +workbench_title=WorkBench +wsdl_helper_error=The WSDL was not valid, please double check the url. +wsdl_url=WSDL URL +wsdl_url_error=The WSDL was emtpy. +xml_assertion_title=XML Assertion +xml_download_dtds=Fetch external DTDs +xml_namespace_button=Use Namespaces +xml_tolerant_button=Use Tidy (tolerant parser) +xml_validate_button=Validate XML +xml_whitespace_button=Ignore Whitespace +xmlschema_assertion_label=File Name: +xmlschema_assertion_title=XML Schema Assertion +xpath_assertion_button=Validate +xpath_assertion_check=Check XPath Expression +xpath_assertion_error=Error with XPath +xpath_assertion_failed=Invalid XPath Expression +xpath_assertion_label=XPath +xpath_assertion_negate=True if nothing matches +xpath_assertion_option=XML Parsing Options +xpath_assertion_test=XPath Assertion +xpath_assertion_tidy=Try and tidy up the input +xpath_assertion_title=XPath Assertion +xpath_assertion_valid=Valid XPath Expression +xpath_assertion_validation=Validate the XML against the DTD +xpath_assertion_whitespace=Ignore whitespace +xpath_expression=XPath expression to match against +xpath_extractor_fragment=Return entire XPath fragment instead of text content? +xpath_extractor_query=XPath query: +xpath_extractor_title=XPath Extractor +xpath_file_file_name=XML file to get values from +xpath_tidy_quiet=Quiet +xpath_tidy_report_errors=Report errors +xpath_tidy_show_warnings=Show warnings +you_must_enter_a_valid_number=You must enter a valid number +zh_cn=Chinese (Simplified) +zh_tw=Chinese (Traditional) \ No newline at end of file diff --git a/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_de.properties b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_de.properties new file mode 100644 index 0000000..ea3b5c6 --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_de.properties @@ -0,0 +1,575 @@ +# 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. + +about=\u00DCber Apache JMeter +add=Hinzuf\u00FCgen +add_as_child=Als ein Kind hinzuf\u00FCgen +add_parameter=Variable hinzuf\u00FCgen +add_pattern=Muster hinzuf\u00FCgen\: +add_test=Test hinzuf\u00FCgen +add_user=Benutzer hinzuf\u00FCgen +add_value=Wert hinzuf\u00FCgen +addtest=Test hinzuf\u00FCgen +aggregate_graph_column=Spalte +aggregate_graph_display=Graphen anzeigen +aggregate_graph_height=H\u00F6he +aggregate_graph_max_length_xaxis_label=Maximale L\u00E4nge des x-Achsen Bezeichners +aggregate_graph_ms=Millisekunden +aggregate_graph_response_time=Reaktionszeit +aggregate_graph_save_table_header=Tabellen Kopf speichern +aggregate_graph_save_table=Tabellen Daten speichern +aggregate_graph_save=Graphen speichern +aggregate_graph_title=Graph +aggregate_graph_user_title=Titel f\u00FCr den Graphen +aggregate_graph_width=Breite +aggregate_graph=Statistischer Graph +aggregate_report_90%_line=90% Marke +aggregate_report_bandwidth=KB/sek +aggregate_report_count=Anz. der Proben +aggregate_report_error%=% Fehler +aggregate_report_error=Fehler +aggregate_report_median=Mittel +aggregate_report_rate=Durchsatz +aggregate_report_total_label=Gesamt +aggregate_report=Report +als_message=Hinweis\: Der Zugriff-Log Parser ist allgmein gehalten. Es ist m\u00F6glich ein Plugin zu erstellen. +als_message2=Eigener Parser. Implementieren sie hierzu "LogParser" und f\u00FCgen sie es als .jar hinzu +analyze=Analysiere Daten Datei... +appearance=Aussehen (Look & Feel) +argument_must_not_be_negative=Der Wert darf nicht negativ sein\! +assertion_assume_success=Status ignorieren +assertion_code_resp=Antwort-Code (Response-Code) +assertion_contains=Enth\u00E4lt +assertion_equals=Gleicht +assertion_headers=Antwort-Header (Response-Header) +assertion_matches=Entsprechungen +assertion_message_resp=Antwort-Message +assertion_not=Nicht +assertion_pattern_match_rules=Regeln f\u00FCr passende Muster +assertion_patterns_to_test=Zu testende(s) Muster +assertion_resp_field=Zu testendes Antwort-Feld (Response-Feld) +assertion_substring=Teilzeichenkette (Substring) +assertion_text_resp=Text-Antwort (Text-Response) +assertion_textarea_label=Behauptungen\: +assertion_title=Versicherte Anwort +assertion_url_samp=URL gesampled +assertion_visualizer_title=Versicherungs Erebnis +attribute=Attribut +attrs=Attribute +auth_manager_title=HTTP Authorisierungs Manager +auths_stored=Im Authorization Manager gespeicherte Authorisierungen +average_bytes=Durchschnittliche Bytes +average=Durchschnitt +browse=Datei laden... +bsf_script_file=Auszuf\u00FChrendes Script +bsf_script_language=Scriptsprache +bsf_script_parameters=An das Script bzw. die Script-Datei zu \u00FCbergebende Parameter +bsf_script=Auszuf\u00FChrendes Script (Variablen\: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +bsh_assertion_script=Script (untenstehende Variablen sind definiert) +bsh_assertion_title=BeanShell Behauptung +bsh_function_expression=Auszuwertender Ausdruck +bsh_script_file=Script-Datei +bsh_script_variables=Folgende Variablen wurden f\u00FCr das Script definiert\:\nSampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +bsh_script=Script (untenstehende Variablen sind definiert) +busy_testing=Ich bin mit dem Testen besch\u00E4ftigt, bitte stoppen sie den Test bevor sie die Einstellungen \u00E4ndern. +cache_session_id=Session ID zwischenspeichern? +cancel_exit_to_save=Es gibt Tests die noch nicht gespeichert wurden. M\u00F6chten sie diese vor dem Beenden speichern? +cancel_new_to_save=Es gibt Tests die noch nicht gespeichert wurden. M\u00F6chten sie diese vor dem Bereinigen speichern? +cancel_revert_project=Es gibt Tests die noch nicht gespeichert wurden. M\u00F6chten sie zum zuletzt gespeicherten Testplan zur\u00FCck gehen? +cancel=Abbrechen +choose_function=W\u00E4hlen Sie eine Funktion +choose_language=W\u00E4hlen sie eine Sprache +clear_all=Alle L\u00F6schen +clear_cookies_per_iter=Cookies bei jedem Durchgang l\u00F6schen? +clear=L\u00F6schen +column_delete_disallowed=Das l\u00F6schen dieser Spalte ist nicht erlaubt +column_number=Spaltennummer der CSV Datei file | next | *alias +compare=Vergleichen +comparefilt=Vergleichsfilter +config_element=Konfigurations Element +config_save_settings=Konfigurieren +configure_wsdl=Konfigurieren +constant_throughput_timer_memo=Geben sie eine Pause zwischen den Proben an um einen konstanten Durchsatz zu gew\u00E4hrleisten +constant_timer_delay=Thread-Pause (in Millisekunden) +constant_timer_memo=Geben sie eine Pause zwischen den Proben an +constant_timer_title=Konstanter Timer +content_encoding=Content Kodierung\: +cookie_manager_policy=Cookie Richtlinie +cookies_stored=Anzahl der gespeicherten Cookies im Cookie Manager +copy=Kopieren +counter_config_title=Z\u00E4hler (Counter) +counter_per_user=Z\u00E4hler (Counter) f\u00FCr jeden Benutzer einzeln f\u00FChren +countlim=Gr\u00F6\u00DFen-Beschr\u00E4nkung +csvread_file_file_name=CVS Datei aus der die Werte gelesen werden | *alias +cut_paste_function=Kopieren und Einf\u00FCgen des Funktions Strings +cut=Ausschneiden +database_conn_pool_max_usage=Maximale Auslastung jeder Verbindung\: +database_conn_pool_props=Datenbank Verbindungs-Pool\: +database_conn_pool_size=Anzahl der Verbindungen im Pool\: +database_conn_pool_title=Vorgaben zum JDBC Datenbank Verbindungs Pool +database_driver_class=Treiber-Klasse\: +database_login_title=JDBC Datenbank Login Vorgabe +database_sql_query_string=SQL Abfrage\: +database_sql_query_title=Vorgaben zur JDBC SQL Abfrage +database_testing_title=JDBC Anfrage +database_url_jdbc_props=Database URL und JDBC Treiber +de=Deutsch +debug_off=Debugging deaktivieren +debug_on=Debugging aktivieren +default_parameters=Standard Parameter +default_value_field=Vorgabe-Wert\: +delay=Pause zu Beginn (Sekunden) +delete_parameter=L\u00F6sche Variable +delete_test=Test L\u00F6schen +delete_user=Benutzer L\u00F6schen +delete=L\u00F6schen +deltest=L\u00F6sch-Test +deref=Dereferenzierungs Aliasse +disable=Deaktivieren +distribution_graph_title=Verteilungs-Graph (Alpha) +distribution_note1=Der Graph wird mit jeder 10. Probe aktualisiert +done=Fertig +duration_assertion_duration_test=Dauer der Aufrechterhaltung +duration_assertion_failure=Die Operation dauerte zu lang\: es wurden {0} Millisekunden ben\u00F6tigt, h\u00E4tte aber maximal {1} Millisekunden dauern d\u00FCrfen. +duration_assertion_input_error=Geben Sie bitte einen g\u00FCltigen, positive Ganzzahl-Wert ein. +duration_assertion_label=Dauer in Millisekunden +duration_assertion_title=Aufrechterhaltungs-Dauer +duration=Dauer (Sekunden) +edit=Bearbeiten +email_results_title=Ergebnisse per eMail verschicken +en=Englisch +enable=Aktivieren +encode?=Encodieren? +encoded_value=URL-Encodierter Wert +endtime=End-Zeitpunkt +entry_dn=Ausgangs DN +entrydn=Ausgangs DN +error_loading_help=Fehler beim laden der Hilfe-Seite +error_occurred=Es ist ein Fehler aufgetreten +error_title=Fehler +es=Spanisch +eval_name_param=Ein Ausdruck der eine Variable und Funktions-Referenz enth\u00E4lt +evalvar_name_param=Variablenname +example_data=Beispieldaten +example_title=Beispiel Proben +exit=Beenden +expiration=Verfall +field_name=Feldname +file_already_in_use=Die Datei ist bereits ge\u00F6ffnet +file_visualizer_append=An eine existierende Daten-Datei anh\u00E4ngen +file_visualizer_auto_flush=Nach jeder Daten-Probe bereinigen (Flush) +file_visualizer_browse=Datei laden... +file_visualizer_close=Schliessen +file_visualizer_file_options=Datei Optionen +file_visualizer_filename=Dateinamen eingeben, oder eine existierende Datei ausw\u00E4hlen. +file_visualizer_flush=Bereinigen +file_visualizer_missing_filename=Kein Ausgabe Dateiname angegeben. +file_visualizer_open=\u00D6ffnen +file_visualizer_output_file=Schreibe alle Daten in eine Datei +file_visualizer_submit_data=Einschliesslich \u00FCbermittelter Daten +file_visualizer_title=Datei Reporter +file_visualizer_verbose=Umfangreiche Ausgabe (Verbose) +file=Datei +filename=Dateiname +follow_redirects_auto=Automatisch Redirects folgen +follow_redirects=Folge Redirects +foreach_input=Prefix der Eingabe-Variable +foreach_output=Name der Ausgabe-Variable +foreach_use_separator=Trennzeichen "_" vor jeder Nummer einf\u00FCgen? +format=Zahlenformat +fr=Franz\u00F6sisch +ftp_binary_mode=Bin\u00E4r-Modus verwenden? +ftp_local_file=Lokale Datei\: +ftp_remote_file=Entfernte Datei +ftp_sample_title=Vorgaben zum FTP Request +ftp_save_response_data=Datei in Antwort speichern? +ftp_testing_title=FTP Anfrage +function_dialog_menu_item=Funktions Hilfe-Dialog +function_helper_title=Funktions Hilfe +function_name_param=Name der Variablen, in der die Ergebnisse abgelegt werden sollen (ben\u00F6tigt) +function_name_paropt=Name der Variablen, in der die Ergebnisse abgelegt werden sollen (optional) +function_params=Funktions Parameter +functional_mode_explanation=Diese Funktion f\u00FChrt zu betr\u00E4chtlichen Performanceverlusten. +functional_mode=Funktionaler Test Mode +gaussian_timer_delay=Konstante Pause (in Millisekunden) +gaussian_timer_memo=Zus\u00E4tzliche Pause zur Gauss'schen Verteilung +gaussian_timer_range=Abweichung (in Millisekunden) +gaussian_timer_title=Gauss'scher Zufalls-Zeitgeber +generate=Generiere +generator_cnf_msg=Kann die Erzeuger-Klasse (Generator) nicht finden. Vergewissern sie sich, dass die das .jar Archiv in das /lib Verzeichnis gelegt haben. +generator_illegal_msg=Konnte wegen einer "IllegalAcessException" nicht auf die Erzeuger-Klasse (Generator) zugreifen. +generator_instantiate_msg=Konnte keine Instanz der Erzeuger-Klasse ertsllen. Stellen sie sicher, dass der Erzeuger das "Generator"-Interface implementiert\! +generator=Name der Erzeuger-Klasse +get_xml_from_file=Datei mit SOAP XML Daten (Vorrang vor obenstehendem Text) +get_xml_from_random=Verzeichnis mit Meldungen +graph_choose_graphs=Anzuzeigende Graphen +graph_full_results_title=Vollst\u00E4ndige Ergebnisse +graph_results_average=Durchschnitt +graph_results_data=Daten +graph_results_deviation=Abweichung +graph_results_latest_sample=Letzte Probe +graph_results_median=Mittel +graph_results_ms=Millisekunden (ms) +graph_results_no_samples=Anzahl der Proben +graph_results_throughput=Durchsatz +graph_results_title=Ergebnisse +grouping_add_separators=Zwischen den Gruppen Trennzeichen einf\u00FCgen +grouping_in_controllers=Jede Gruppe in einen neuen Controller legen +grouping_mode=Gruppierung +grouping_no_groups=Sampler nicht gruppieren +grouping_store_first_only=Nur den ersten Sampler jeder Gruppe speichern +headers_stored=Gespeicherte Header im Header Manager +help_node=Wof\u00FCr ist das? +help=Hilfe +html_assertion_file=Schreibe JTidy Bericht in eine Datei +html_assertion_label=HTML Bericht +html_assertion_title=Titel des HTML Bericht +html_parameter_mask=HTML Parameter Maske +http_url_rewriting_modifier_title=HTTP URL Re-writing Bezeichner +http_user_parameter_modifier=HTTP User Parameter Bezeichner +httpmirror_title=HTTP Spiegel +if_controller_evaluate_all=F\u00FCr alle Unterelemente auswerten? +if_controller_label=Bedingung (Javascript) +if_controller_title=If-Controller +ignore_subcontrollers=Ignoriere Sub-Controller Bl\u00F6cke +include_controller=Controller einschlie\u00DFen +include_equals=Gleichheitszeichen mit einbeziehen? +include_path=Test-Plan mit einbeziehen? +increment=Zunahme +infinite=endlos Wiederholen +insert_after=Dahinter einf\u00FCgen +insert_before=Davor einf\u00FCgen +insert_parent=Dar\u00FCber Einf\u00FCgen +interleave_control_title=Controller \u00FCberlagern +intsum_param_1=Erster Ganzzahl Wert (int) +intsum_param_2=Zweiter Ganzzahl Wert (int). Weitere Werte k\u00F6nnen durch Angabe weiterer Argumente addiert werden. +invalid_data=Ung\u00FCltige Daten +invalid_mail_address=Eine oder mehrere fehlerhafte E-Mail Adressen gefunden +invalid_mail_server=Probleme beim Verbinden mit dem Mail-Server (siehe JMeter Log-Datei) +invalid_mail=Fehler beim senden der E-Mail +invalid_variables=Ung\u00FCltige Variablen +iteration_counter_arg_1="TRUE" damit jeder Benutzer einen eingenen Z\u00E4hler hat. "FALSE" f\u00FCr einen globalen Z\u00E4hler. +iterator_num=Anzahl der Wiederholungen\: +jar_file=.jar Dateien +java_request_defaults=Java Anfrage (Request) Vorgabe +java_request=Java Anfrage (Request) +javascript_expression=Zu evaluierender JavaScript Ausdruck +jexl_expression=Auszuwertender JEXL Ausdruck +jms_auth_required=Ben\u00F6tigt +jndi_config_title=JNDI Konfiguration +jndi_url_jndi_props=JNDI Eigenschaften +load_wsdl=Lade WSDL +load=Laden +log_errors_only=Fehler +log_file=Ort der Log-Datei +log_function_comment=Zus\u00E4tzliche Kommentare (optional) +log_function_level=Log-Level "INFO" (Vorgabe), "OUT" oder "ERR" +log_function_string_ret=Zu loggende (und zur\u00FCckzugebende) Zeichenkette +log_function_string=Zu loggende Zeichenkette +log_function_throwable="Throwable" Test (optional) +log_only=Nur Loggen/Anzeigen\: +log_parser_cnf_msg=Kann die Klasse nicht finden. Vergewissern sie sich, dass die das .jar Archiv in das /lib Verzeichnis gelegt haben. +log_parser_illegal_msg=Konnte aufgrund einer "IllegalAcessException" nicht auf die Klasse zugreifen. +log_parser_instantiate_msg=Konnte keine Instanz der Log-Parsers erstellen. Stellen sie sicher, dass der Erzeuger das "LogParser"-Interface implementiert\! +log_parser=Name der Log-Parser Klasse +log_sampler=Tomcat Zugriffs-Log Sampler +log_success_only=Erfolge +logic_controller_title=Einfacher Controller +login_config_element=Login Konfigurations Element +login_config=Login Konfiguration +longsum_param_1=Erster long-Wert +longsum_param_2=Zweiter long-Wert. Durch weitere Parameter k\u00F6nnen zus\u00E4tzliche long-Werte hinzugef\u00FCgt werden +loop_controller_title=Schleifen-Controller (Loop Controller) +looping_control=Wiederholungs-Control +lower_bound=Untere Grenze +mail_reader_account=Benutzername\: +mail_reader_all_messages=Alle +mail_reader_delete=Nachrichten vom Server l\u00F6schen +mail_reader_folder=Verzeichnis\: +mail_reader_num_messages=Anzahl der zu ladenen Nachrichten\: +mail_reader_password=Passwort\: +mail_reader_server_type=Server-Typ\: +mail_sent=Mail erfolgreich gesendet +mailer_attributes_panel=Mail Eigenschaften +mailer_error=Konnte die Mail nicht senden. Bitte korrigieren Sie jede fehlerhafte Eingabe. +mailer_visualizer_title=Mailer-Visualisierung +maximum_param=Der maximale Wert welcher f\u00FCr einen Wertebereich erlaubt ist +md5hex_assertion_failure=Fehler beim \u00FCberpr\u00FCfen der MD5 Summe\: {0} erhalten, sollte {1} sein +md5hex_assertion_md5hex_test=Zu pr\u00FCfender MD5 Hex String +md5hex_assertion_title=MD5 Hex \u00DCberpr\u00FCfung +menu_assertions=\u00DCberpr\u00FCfung +menu_close=Schlie\u00DFen +menu_collapse_all=Alle schlie\u00DFen +menu_config_element=Konfigurations Element +menu_edit=Editieren +menu_expand_all=Alle \u00F6ffnen +menu_logic_controller=Logik-Controller +menu_merge=Zusammenf\u00FCgen +menu_modifiers=Modifizierer +menu_non_test_elements=Nicht-Test Elemente +menu_open=\u00D6ffnen +menu_post_processors=Post-Processors +menu_pre_processors=Pre-Processors +menu_response_based_modifiers=Antwort-Basierter Modifizierer +menu_timer=Zeitgeber (Timer) +method=Methode\: +minimum_param=Der minimale Wert welcher f\u00FCr einen Wertebereich erlaubt ist +minute=Minute +modddn=Alter Name +modification_controller_title=Modifikations-Controller +modification_manager_title=Modifikations-Manager +modify_test=Test \u00E4ndern +modtest=\u00C4nderungs-Test +module_controller_module_to_run=Auszurufendes Modul +module_controller_title=Modul-Controller +module_controller_warning=Konnte Modul nicht finden\: +monitor_equation_active=Aktiv\: (aktiv/maximum) > 25% +monitor_equation_dead=Abgestoben\: keine Antwort +monitor_equation_healthy=Gut\: (aktiv/maximum) < 25% +monitor_equation_load=Last\: ((aktiv/mamimum)*50) + ((genutzer Speicher/maximaler Speicher)*50) +monitor_equation_warning=Warnung\: (aktiv/maximum) > 67% +monitor_health_tab_title=Gut +monitor_health_title=Ergebnisse \u00DCberwachen +monitor_is_title=Als \u00DCberwacher (Monitor) benutzen +monitor_label_right_active=Aktiv +monitor_label_right_dead=Abgestorben +monitor_label_right_healthy=Gut +monitor_label_right_warning=Warnung +monitor_legend_health=Gut +monitor_legend_load=Last +monitor_legend_memory_per=Speicher % (genutzt/gesamt) +monitor_legend_thread_per=Thread % (aktiv/maimum) +monitor_performance_servers=Server +monitor_performance_title=Performance-Graph +new=Neu +newdn=Neuer DN (distinguished name) +no=Norwegisch +number_of_threads=Anzahl von Threads\: +obsolete_test_element=Dieser Test-Abschnitt ist hinf\u00E4lig +once_only_controller_title=Einmal-Controller +opcode=OPcode +open=\u00D6ffnen +option=Optionen +optional_tasks=Optionale Aufgaben +paramtable=Parameter die mit dem Request gesendet werden\: +password=Passwort +paste_insert=Als Eintrag einf\u00FCgen +paste=Einf\u00FCgen +path_extension_choice=Pfad-Erweiterung (benutze ";" als Trennzeichen) +path_extension_dont_use_equals=Keine Gleichheitszeichen in Pfad-Erweiterung benutzen (Intershop Enfinity compatibility) +path_extension_dont_use_questionmark=Keine Fragezeichen in Pfad-Erweiterung benutzen (Intershop Enfinity compatibility) +path=Pfad\: +patterns_to_exclude=Auszuschlie\u00DFende URL-Muster +patterns_to_include=Einzuschlie\u00DFende URL-Muster +property_default_param=Vorgabe-Wert +property_edit=Bearbeiten +property_editor.value_is_invalid_message=Der eingegebene Text ist f\u00FCr diese Eigenschaft ung\u00FCltig.\nDer Wert wird auf seinen vorherigen Wert zur\u00FCck gesetzt. +property_editor.value_is_invalid_title=Ung\u00FCltige Eingabe +property_name_param=Name der Eigenschaft +property_returnvalue_param=Urspr\u00FCnglichen Wert der Eigenschaft zur\u00FCckgeben? Vorgabe\: false +property_undefined=undefiniert +property_value_param=Wert der Eigeschaft +property_visualiser_title=Eigenschaften +protocol_java_border=Java-Klasse +protocol_java_classname=Klassenname (classname)\: +protocol_java_config_tile=Java Sample Konfigurieren +protocol_java_test_title=Java Tests +protocol=Protokoll [http]\: +proxy_assertions=Versicherungen hinzuf\u00FCgen +proxy_cl_error=Wenn Sie einen Proxy Server spezifizieren, m\u00FCssen Sie den Host und Port angeben +proxy_content_type_exclude=Ausschlie\u00DFen\: +proxy_content_type_filter=Content-Type Filter +proxy_content_type_include=Einschlie\u00DFen\: +proxy_headers=HTTP-Header \u00FCberwachen +proxy_httpsspoofing_match=URL Muster (optional) +proxy_httpsspoofing=HTTPS-Spoofing versuchen +proxy_regex=RegEx Muster +proxy_sampler_settings=HTTP Sampler Einstellungen +proxy_sampler_type=Typ\: +proxy_separators=F\u00FCgen sie Trennzeichen hinzu +proxy_target=Ziel-Controller (Target-Controller)\: +proxy_test_plan_content=Test-Plan Inhalt\: +random_control_title=Zufalls-Controller +random_order_control_title=Zufalls-Reihenfolgen-Controller +read_response_message=Empfange Antwort wurde nicht gepr\u00FCft. Um die Antwort anzusehen aktivieren Sie die Checkbox im Sampler. +read_soap_response=SOAP-Antwort lesen +realm=Bereich +regexfunc_param_1=Regul\u00E4re Ausdr\u00FCcke zum Suchen in den Results der vorherigen Requests +regexfunc_param_2=Beispiel f\u00FCr Ersetzungs Strings, benuzte Gruppen von den regul\u00E4ren Ausdr\u00FCcken +regexfunc_param_3=Which match to use. Einen Integer 1 oder gr\u00F6sser, RAND damit JMeter eine zuf\u00E4llige Auswahl trifft, eine Fliesskommazahl, oder ALL wenn alle Treffer benutzt werden +regexfunc_param_4=Zwischen Text. Wenn ALL ausgew\u00E4hlt ist, wird der zwischen Test benutzt um das Ergebnis zu generieren +regexfunc_param_5=Standard Text. Wird benutzt anstatt der Vorlage, falls der Regul\u00E4re Ausdruck keine Treffer findet +remove=Entfernen +report_bar_chart=Balken-Diagramm +report_base_directory=Basis-Verzeichnis +report_chart_caption=Diagramm-Titel +report_chart_x_axis_label=Bezeichner f\u00FCr die X-Achse +report_chart_x_axis=X-Achse +report_chart_y_axis_label=Bezeichner f\u00FCr die Y-Achse +report_chart_y_axis=Y-Achse +report_line_graph_urls=URLs einbeziehen +report_line_graph=Linien-Diagramm +report_output_directory=Ausgabe-Verzeichnis f\u00FCr den Bericht +report_page_element=Seitenelement +report_page_footer=Seitenfu\u00DF +report_page_header=Seitenkopf +report_page_index=Seiten-Index erstellen +report_page_intro=Seiteneinleitung +report_page_style_url=Stylesheet URL +report_page_title=Seitentitel +report_page=Bericht-Seite +report_pie_chart=Torten-Diagramm +report_select=Ausw\u00E4hlen +report_summary=Bericht-Zusammenfassung +report_writer_html=HTML Bericht-Schreiber +report_writer=Bericht-Schreiber +request_data=Request Daten +reset_gui=GUI zur\u00FCcksetzen +restart=Neu starten +revert_project?=Projekt zur\u00FCck setzten? +revert_project=Zur\u00FCcksetzen +root_title=Wurzel +root=Wurzel +run=Start +running_test=Test starten +sampler_on_error_action=Aktion die bei einem Sampler-Fehler ausgef\u00FChrt werden soll +sampler_on_error_continue=Fortfahren +sampler_on_error_stop_test=Test Anhalten +sampler_on_error_stop_thread=Thread Anhalten +save?=Speichern? +save_all_as=Test-Plan speichern unter +save_as_error=Mehr als ein Element ausgew\u00E4hlt\! +save_as_image_all=Bildschirm als Bild speichern +save_as_image=Als Bild speichern +save_as=Speichern unter +save_assertionresultsfailuremessage=Speichere Meldungen der Versicherungs-Fehler +save_assertions=Speichere Versicherungs-Ergebnisse (XML) +save_asxml=Speichere als XML +save_bytes=Speichere anzahl der Bytes +save_code=Speichere Response-Code +save_datatype=Speichere Daten-Typ +save_encoding=Speichere Kodierung +save_fieldnames=Speichere Feld-Namen (CSV) +save_filename=Speichere Response-Dateiname +save_graphics=Speichere Graphen +save_hostname=Speichere Hostenamen +save_label=Speichere Bezeichner +save_latency=Speichere Latenz +save_message=Speichere Response-Nachricht +save_overwrite_existing_file=Die ausgew\u00E4hlte Datei existiert bereits, m\u00F6chten sie sie \u00FCberschreiben? +save_requestheaders=Speichere Request-Header (XML) +save_responsedata=Speichere Response-Daten (XML) +save_responseheaders=Speichere Response-Header (XML) +save_samplecount=Speichere Proben und Fehler Anzahl +save_samplerdata=Speichere Sampler-Daten (XML) +save_subresults=Speichere Unter-Ergebnisse (XML) +save_success=Speichere Erfolge +save_threadcounts=Speichere aktive Thread Anzahl +save_threadname=Speichere Thread-Name +save_time=Speichere ben\u00F6tigte Zeit +save_timestamp=Speichere Zeitstempel +save_url=Speichere URL +save=Speichern +scheduler_configuration=Scheduler Konfiguration +scope=G\u00FCltigkeitsbereich (Scope) +second=Sekunde +secure=Sicher +send_file_browse=Datei ausw\u00E4hlen... +send_file_filename_label=Dateiname\: +send_file_param_name_label=Wert des "name"-Attributes\: +send_file=Datei mit dem Request senden\: +server=Server Name oder IP\: +should_save=Wenn sie supportete Daten Dateien (z.B. CSV) benutzen ist es wichtig zuerst das Test-Script zu speichern.\nM\u00F6chten sie den Test-Plan speichern? +shutdown=Beenden +size_assertion_comparator_error_equal=gleich +size_assertion_comparator_error_greater=gr\u00F6\u00DFer als +size_assertion_comparator_error_greaterequal=gr\u00F6\u00DFer oder gleich +size_assertion_comparator_error_less=kleiner als +size_assertion_comparator_error_lessequal=kleiner oder gleich +size_assertion_comparator_error_notequal=nicht gleich +size_assertion_comparator_label=Art des Vergleichs +size_assertion_failure=Das Ergebnis hatte die falsche Gr\u00F6\u00DFe ({0} Byte). Es h\u00E4tte {1} {2} Byte sein m\u00FCssen. +size_assertion_input_error=Bitte geben sie einen g\u00FCltigen Ganzzahl-Wert ein. +size_assertion_label=Gr\u00F6\u00DFe in Byte\: +size_assertion_size_test=Gr\u00F6\u00DFe versichern +size_assertion_title=Gr\u00F6\u00DFen Versicherung +soap_action=SOAP Aktion +soap_data_title=SOAP/XML-RPC Daten +soap_sampler_title=SOAP/XML-RPC Anfrage +soap_send_action=Sende SOAP Aktion\: +spline_visualizer_average=Durchschnitt +spline_visualizer_incoming=Eingehend +spline_visualizer_maximum=Maximal +spline_visualizer_minimum=Minimal +spline_visualizer_title=Spline Darstellung +spline_visualizer_waitingmessage=Warte auf Proben +ssl_alias_prompt=Bitte geben Sie Ihren bevorzugten Alias ein +ssl_alias_select=W\u00E4hlen Sie Ihren Alias f\u00FCr den Test +ssl_error_title=Problem beim Schl\u00FCssel Speichern +ssl_pass_prompt=Bitte geben Sie Ihr Passwort ein +ssl_pass_title=Schl\u00FCssel Speicher Passwort +starttime=Startzeit +stopping_test_title=Stoppe den Test +stopping_test=Stoppe alle Tests. Bitte warten ... +string_from_file_file_name=Geben sie den vollst\u00E4ndingen Datei-Pfad ein +string_from_file_seq_final=Letzte Datei-Sequenznummer (optional) +string_from_file_seq_start=Erste Datei-Sequenznummer (optional) +table_visualizer_sample_num=Proben Anzahl +table_visualizer_sample_time=Proben-Zeit (ms) +table_visualizer_start_time=Startzeit +table_visualizer_success=Erfolgreich +table_visualizer_thread_name=Thread-Name +table_visualizer_warning=Warnung +tcp_config_title=TCP Sampler Konfiguration +tcp_nodelay=Setzte "NoDelay" +tcp_port=Port-Nummer\: +tcp_request_data=Text senden +tcp_timeout=Timeout (Millisekunden)\: +test_action_duration=Dauer (Millisekunden) +test_action_target_test=Alle Threads +test_action_target_thread=Aktueller Thread +test_action_target=Ziel +test_action_title=Test-Aktion +test_configuration=Test-Konfiguration +test_plan_classpath_browse=F\u00FCgen sie das Verzeichnis oder .jar zum classpath hinzu +test_plan=Testplan +testconfiguration=Konfiguration Testen +testplan.serialized=Thread-Gruppen nacheinander starten +testplan_comments=Kommenare\: +thread_delay_properties=Thread-Pause Eigenschaften +thread_group_title=Thread Gruppe +thread_properties=Thread-Eigenschaften +threadgroup=Thread-Gruppe +throughput_control_bynumber_label=Ausf\u00FChrungen (Gesamt) +throughput_control_bypercent_label=Ausf\u00FChrungen (Prozent) +throughput_control_perthread_label=pro Benutzer +throughput_control_title=Durchsatz-Controller +throughput_control_tplabel=Durchsatz +time_format=Format-Zeichenkette des "SimpleDateFormat" (optional) +timelim=Zeit-Limit +tr=T\u00FCrkisch +upload=Datei hochladen +upper_bound=obere Grenze +url_config_title=HTTP Request Default Einstellungen +use_keepalive=Benutze KeepAlive +user_defined_variables=Benutzer definierte Variablen +user_param_mod_help_note=(\u00C4ndern Sie dies nicht. Stattdessen, bitte die Datei mit dem Namen in JMeter's /bin Ordner \u00E4ndern.) +username=Benutzername +value=Wert +view_results_in_table=Zeige Ergebnisse in der Tabelle +warning=Warnung\! +web_server_domain=Server Name oder IP\: +web_testing_retrieve_images=Hole alle Bilder und Java Applets (nur HTML Dateien) +you_must_enter_a_valid_number=Sie m\u00FCssen ein g\u00FCltige Nummer eingeben diff --git a/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_es.properties b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_es.properties new file mode 100644 index 0000000..2044493 --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_es.properties @@ -0,0 +1,1065 @@ +# 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. + +#Stored by I18NEdit, may be edited! +about=Acerca de Apache JMeter +add=A\u00F1adir +add_as_child=A\u00F1adir como hijo +add_parameter=A\u00F1adir Variable +add_pattern=A\u00F1adir Patr\u00F3n\: +add_test=A\u00F1adir Test +add_user=A\u00F1adir Usuario +add_value=A\u00F1adir Valor +addtest=A\u00F1adir test +aggregate_graph=Gr\u00E1ficos estad\u00EDsticos +aggregate_graph_column=Columna +aggregate_graph_display=Mostrar gr\u00E1fico +aggregate_graph_height=Altura +aggregate_graph_max_length_xaxis_label=Longitud m\u00E1xima de la etiqueta del eje x +aggregate_graph_ms=Milisegundos +aggregate_graph_response_time=Tiempo de respuesta +aggregate_graph_save=Guardar gr\u00E1fico +aggregate_graph_save_table=Guardar la tabla de datos +aggregate_graph_save_table_header=Guardar la cabecera de la tabla +aggregate_graph_title=Gr\u00E1fico +aggregate_graph_use_group_name=\u00BFIncluir el nombre del grupo en la etiqueta? +aggregate_graph_user_title=T\u00EDtulo del gr\u00E1fico +aggregate_graph_width=Anchura +aggregate_report=Informe Agregado +aggregate_report_90=90% +aggregate_report_90%_line=Linea de 90% +aggregate_report_bandwidth=Kb/sec +aggregate_report_count=\# Muestras +aggregate_report_error=Error +aggregate_report_error%=% Error +aggregate_report_max=M\u00E1x +aggregate_report_median=Mediana +aggregate_report_min=M\u00EDn +aggregate_report_rate=Rendimiento +aggregate_report_stddev=Desv. Est\u00E1ndar +aggregate_report_total_label=Total +ajp_sampler_title=AJP/1.3 Muestreador +als_message=Nota\: El Parser de Access Log tiene un dise\u00F1o gen\u00E9rico y le permite incorporar +als_message2=su propio parser. Para hacer esto, implemente "LogParser", y a\u00F1ada el jar al +als_message3=directorio /lib e introduzca la clases en el muestreador. +analyze=Analizar Archivo de Datos... +anchor_modifier_title=Parseador de Enlaces HTML +appearance=Apariencia +argument_must_not_be_negative=\u00A1El Argumento no puede ser negativo\! +assertion_assume_success=Ignorar el Estado +assertion_code_resp=C\u00F3digo de Respuesta +assertion_contains=Contiene +assertion_equals=igual +assertion_headers=Cabeceras de la respuesta +assertion_matches=Coincide +assertion_message_resp=Mensaje de Respuesta +assertion_not=No +assertion_pattern_match_rules=Reglas de Coincidencia de Patrones +assertion_patterns_to_test=Patr\u00F3n a Probar +assertion_resp_field=Campo de Respuesta a Probar +assertion_substring=Substring +assertion_text_resp=Respuesta Textual +assertion_textarea_label=Aserciones\: +assertion_title=Aserci\u00F3n de Respuesta +assertion_url_samp=URL Muestreada +assertion_visualizer_title=Resultados de la Aserci\u00F3n +attribute=Atributo +attrs=Atributos +auth_base_url=URL Base +auth_manager_title=Gestor de Autorizaci\u00F3n HTTP +auths_stored=Autorizaciones Almacenadas en el Gestor de Autorizaci\u00F3n +average=Media +average_bytes=Media de Bytes +bind=Enlace a Hilo +bouncy_castle_unavailable_message=Los jars para bouncy castle no est\u00E1n disponibles, por favor a\u00F1adalos a su classpath. +browse=Navegar... +bsf_sampler_title=Muestreador BSF +bsf_script=Script a lanzar (variables\: log, Label, FileName, Parameters, args[], SampleResult (aka prev), sampler, ctx, vars, props, OUT) +bsf_script_file=Archivo de Script a lanzar +bsf_script_language=Lenguaje de Script\: +bsf_script_parameters=Par\u00E1metros a pasar al script/archivo\: +bsh_assertion_script=Script (ver abajo para las variables que est\u00E1n definidas) +bsh_assertion_script_variables=Lectura/Escritura\: Failure, FailureMessage, SampleResult, log.\nS\u00F3lo Lectura\: Response[Data|Code|Message|Headers], RequestHeaders, SampleLabel, SamplerData +bsh_assertion_title=Aserci\u00F3n BeanShell +bsh_function_expression=Expresi\u00F3n a evaluar +bsh_sampler_title=Muestreador BeanShell +bsh_script=Script (ver abajo para las variables que est\u00E1n definidas) +bsh_script_file=Archivo de script +bsh_script_parameters=Par\u00E1metros (-> Par\u00E1metros String y String[]bsh.args) +bsh_script_reset_interpreter=Resetear el int\u00E9rprete bsh antes de cada llamada +bsh_script_variables=Las siguientes variables est\u00E1n definidas para el script\:\nSampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +busy_testing=Estoy ocupado probando, por favor pare el test antes de cambiar la configuraci\u00F3n +cache_manager_title=Gestionador de la Cach\u00E9 HTTP +cache_session_id=\u00BFIdentificador de la sesi\u00F3n de cach\u00E9? +cancel=Cancelar +cancel_exit_to_save=\u00BFHay elementos de prueba que no han sido salvados. \u00BFQuiere salvar antes de salir? +cancel_new_to_save=\u00BFHay elementos del test que no han sido salvados. \u00BFQuiere salvar antes de limpiar el plan de pruebas? +cancel_revert_project=Hay elementos del test que no han sido guardados. \u00BFDesea revertir a una versi\u00F3n guardada previamente del plan de test? +char_value=N\u00FAmero del car\u00E1cter Unicode (decimal or 0xhex) +choose_function=Elija una funci\u00F3n +choose_language=Elija lenguaje +clear=Limpiar +clear_all=Limpiar Todo +clear_cache_per_iter=\u00BFLimpiar la cach\u00E9 en cada iteraci\u00F3n? +clear_cookies_per_iter=\u00BFLimpiar las cookies en cada iteraci\u00F3n? +column_delete_disallowed=Borrar esta columna no est\u00E1 permitido +column_number=N\u00FAmero de columna del archivo CSV | siguiente | *alias +compare=Comparar +comparefilt=Filtro de Comparaci\u00F3n +comparison_differ_content=Las respuestas difieren en el contenido +comparison_differ_time=Las respuestas difieren en el tiempo de respuesta en m\u00E1s de +comparison_invalid_node=Nodo inv\u00E1lido +comparison_regex_string=Expresi\u00F3n regular +comparison_regex_substitution=Sustituci\u00F3n +comparison_response_time=Tiempo de respuesta\: +comparison_unit=ms +comparison_visualizer_title=Visualizador de la aserci\u00F3n de comparaci\u00F3n +config_element=Elemento de Configuraci\u00F3n +config_save_settings=Configurar +configure_wsdl=Configurar +constant_throughput_timer_memo=A\u00F1ade un retardo entre muestras para obtener un rendimiento constante +constant_timer_delay=Retardo de Hilo (en milisegundos)\: +constant_timer_memo=A\u00F1ade un retardo constante entre muestras +constant_timer_title=Temporizador Constante +content_encoding=Codificac\u00EDon del contenido\: +controller=Controlador +cookie_manager_policy=Pol\u00EDtica de Cookies +cookie_manager_title=Gestor de Cookies HTTP +cookies_stored=Cookies almacenadas en el Gestor de Cookies +copy=Copiar +counter_config_title=Contador +counter_per_user=Contador independiente para cada usuario +countlim=L\u00EDmite de tama\u00F1o +csvread_file_file_name=Archivo CSV del que obtener valores | *alias +cut=Cortar +cut_paste_function=Funci\u00F3n de cadena para copiar y pegar +database_conn_pool_max_usage=Uso m\u00E1ximo para cada Conexi\u00F3n +database_conn_pool_props=Pool de Conexiones a Base de Datos +database_conn_pool_size=N\u00FAmero de Conexiones en el Pool +database_conn_pool_title=Valores por defecto del Pool de Conexiones JDBC +database_driver_class=Clase del Driver\: +database_login_title=Valores por defecto para el Login a JDBC +database_sql_query_string=Query String de SQL\: +database_sql_query_title=Valores por defecto de Query SQL JDBC +database_testing_title=Petici\u00F3n JDBC +database_url=URL JDBC\: +database_url_jdbc_props=Driver JDBC y URL a Base de Datos +ddn=DN +de=Alem\u00E1n +debug_off=Deshabilitar depuraci\u00F3n +debug_on=Habilitar depuraci\u00F3n +default_parameters=Valores por defecto +default_value_field=Valor por defecto\: +delay=Retardo de arranque (segundos) +delete=Borrar +delete_parameter=Borrar Variable +delete_test=Borrar Test +delete_user=Borrar Usuario +deltest=Test de borrado +deref=Alias para desreferenciar +disable=Deshabilitar +distribution_graph_title=Gr\u00E1fico de Distribuci\u00F3n (alfa) +distribution_note1=El gr\u00E1fico se actualiza cada 10 muestras +dn=DN +domain=Dominio +done=Hecho +duration=Duraci\u00F3n (segundos) +duration_assertion_duration_test=Duraci\u00F3n a asegurar +duration_assertion_failure=La operaci\u00F3n dur\u00F3 demasiado\: tard\u00F3 {0} milisegundos, cuando no deber\u00EDa haber tardado m\u00E1s de {1} milisegundos. +duration_assertion_input_error=Por favor, introduzca un entero positivo v\u00E1lido. +duration_assertion_label=Duraci\u00F3n en milisegundos\: +duration_assertion_title=Aserci\u00F3n de Duraci\u00F3n +edit=Editar +email_results_title=Resultados del Email +en=Ingl\u00E9s +enable=Habilitar +encode?=\u00BFCodificar? +encoded_value=Valor de URL Codificada +endtime=Tiempo de Finalizaci\u00F3n +entry_dn=Introduzca DN +entrydn=Introduzca DN +error_loading_help=Error cargando p\u00E1gina de ayuda +error_occurred=Error +error_title=Error +es=Espa\u00F1ol +escape_html_string=Texto de escapado +eval_name_param=Texto que contien variables y referencias de funci\u00F3n +evalvar_name_param=Nombre de la variable +example_data=Dato de muestra +example_title=Muestreador de ejemplo +exit=Salir +expiration=Expiraci\u00F3n +field_name=Nombre de campo +file=Archivo +file_already_in_use=Ese archivo est\u00E1 en uso +file_visualizer_append=A\u00F1adir a archivo de datos existente +file_visualizer_auto_flush=Limpiar autom\u00E1ticamente despu\u00E9s de cada muestra de datos +file_visualizer_browse=Navegar... +file_visualizer_close=Cerrar +file_visualizer_file_options=Opciones de Archivo +file_visualizer_filename=Nombre de archivo +file_visualizer_flush=Limpiar +file_visualizer_missing_filename=No se ha especificado nombre de archivo de salida. +file_visualizer_open=Abrir +file_visualizer_output_file=Escribir todos los datos a Archivo +file_visualizer_submit_data=Incluir Datos Enviados +file_visualizer_title=Informe de Archivo +file_visualizer_verbose=Salida Verbosa +filename=Nombre de Archivo +follow_redirects=Seguir Redirecciones +follow_redirects_auto=Redirigir Autom\u00E1ticamente +foreach_controller_title=Controlador ForEach +foreach_input=Prefijo de variable de entrada +foreach_output=Nombre de variable de salida +foreach_use_separator=\u00BFA\u00F1adir "_" antes de n\u00FAmero? +format=Formato del n\u00FAmero +fr=Franc\u00E9s +ftp_binary_mode=\u00BFUsar modo binario? +ftp_get=get(RETR) +ftp_local_file=Fichero local\: +ftp_local_file_contents=Contenidos del fichero local\: +ftp_put=put(STOR) +ftp_remote_file=Fichero remoto\: +ftp_sample_title=Valores por defecto para petici\u00F3n FTP +ftp_save_response_data=\u00BFGuardar fichero en la respuesta? +ftp_testing_title=Petici\u00F3n FTP +function_dialog_menu_item=Di\u00E1logo de Ayuda de Funci\u00F3n +function_helper_title=Ayuda de Funci\u00F3n +function_name_param=Nombre de funci\u00F3n. Usado para almacenar valores a utilizar en cualquier sitio del plan de prueba. +function_name_paropt=Nombre de variable donde almacenar el resultado (opcional) +function_params=Par\u00E1metros de Funci\u00F3n +functional_mode=Modo de Prueba Funcional +functional_mode_explanation=Seleccione modo de prueba funcional solo si necesita archivar los datos recibidos del servidor para cada petici\u00F3n.\nSeleccionar esta opci\u00F3n impacta en el rendimiento considerablemente. +gaussian_timer_delay=Desplazamiento para Retardo Constante (en milisegundos)\: +gaussian_timer_memo=A\u00F1ade un retardo aleatorio con distribuci\u00F3n gaussiana. +gaussian_timer_range=Desviaci\u00F3n (en milisegundos)\: +gaussian_timer_title=Temporizador Aleatorio Gaussiano +generate=Generar +generator=Nombre de la clase Generadora +generator_cnf_msg=No pude encontrar la clase generadora. Por favor aseg\u00FArese de que puso el archivo jar en el directorio /lib +generator_illegal_msg=No pude acceder a la clase generadora debido a una "IllegalAcessException". +generator_instantiate_msg=No pude crear una instancia del parser generador. Por favor aseg\u00FArese de que el generador implementa la interfaz Generator. +get_xml_from_file=Archivo con datos SOAP XML (sobreescribe el texto anterior) +get_xml_from_random=Carpeta de Mensaje +graph_choose_graphs=Gr\u00E1ficos a Mostrar +graph_full_results_title=Resultados de Gr\u00E1fico Completo +graph_results_average=Media +graph_results_data=Datos +graph_results_deviation=Desviaci\u00F3n +graph_results_latest_sample=\u00DAltima Muestra +graph_results_median=Mediana +graph_results_ms=ms +graph_results_no_samples=No. de Muestras +graph_results_throughput=Rendimiento +graph_results_title=Gr\u00E1fico de Resultados +grouping_add_separators=A\u00F1adir separadores entre grupos +grouping_in_controllers=Poner cada grupo en un nuevo controlador +grouping_in_transaction_controllers=Poner cada grupo en un nuevo controlador de transacciones +grouping_mode=Agrupaci\u00F3n\: +grouping_no_groups=No agrupar muestreadores +grouping_store_first_only=Almacenar el primer muestreador de cada grupo solamente +header_manager_title=Gestor de Cabecera HTTP +headers_stored=Cabeceras Almacenadas en el Gestor de Cabeceras +help=Ayuda +help_node=\u00BFQu\u00E9 es este nodo? +html_assertion_file=Escribir el reporte JTidy en fichero +html_assertion_label=Aserci\u00F3n HTML +html_assertion_title=Aserci\u00F3n HTML +html_parameter_mask=M\u00E1scara de Par\u00E1metro HTML +http_implementation=Implementaci\u00F3n HTTP\: +http_response_code=c\u00F3digo de respuesta HTTP +http_url_rewriting_modifier_title=Modificador de re-escritura HTTP URL +http_user_parameter_modifier=Modificador de Par\u00E1metro de Usuario HTTP +httpmirror_title=Servidor espejo HTTP +id_prefix=Prefijo ID +id_suffix=Sufijo ID +if_controller_evaluate_all=\u00BFEvaluar para todos los hijos? +if_controller_expression=\u00BFInterpretar la condici\u00F3n como una variable de expresi\u00F3n? +if_controller_label=Condici\u00F3n +if_controller_title=Controlador If +ignore_subcontrollers=Ignorar bloques sub-controladores +include_controller=Incluir Controlador +include_equals=\u00BFIncluir Equals? +include_path=Incluir Plan de Pruebas +increment=Incrementar +infinite=Sin f\u00EDn +initial_context_factory=Factor\u00EDa Initial Context +insert_after=Insertar Despu\u00E9s +insert_before=Insertar Antes +insert_parent=Insertar Padre +interleave_control_title=Controlador Interleave +intsum_param_1=Primer int a a\u00F1adir. +intsum_param_2=Segundo int a a\u00F1adir - m\u00E1s ints pueden ser insertados a\u00F1adiendo m\u00E1s argumentos +invalid_data=Dato inv\u00E1lido +invalid_mail=Error al enviar el e-mail +invalid_mail_address=Una o m\u00E1s direcciones de e-mail inv\u00E1lidas +invalid_mail_server=Problema contactantdo el servidor de e-mail (mire los logs de JMeter) +invalid_variables=Variables inv\u00E1lidas +iteration_counter_arg_1=TRUE, para que cada usuario su propio contador, FALSE para tener un contador global +iterator_num=Contador del bucle\: +ja=Japon\u00E9s +jar_file=Ficheros .jar +java_request=Petici\u00F3n Java +java_request_defaults=Valores por defecto para Petici\u00F3n Java +javascript_expression=Expresi\u00F3n JavaScript a evaluar +jexl_expression=Expresi\u00F3n JEXL a evaluar +jms_auth_required=Requerido +jms_client_caption=El cliente Receive utiliza TopicSubscriber.receive() para escuchar un mensaje. +jms_client_caption2=MessageListener utiliza la interfaz onMessage(Message) para escuchar nuevos mensajes +jms_client_type=Cliente +jms_communication_style=Estilo de Comunicaci\u00F3n +jms_concrete_connection_factory=Factor\u00EDa de Connection Concreto +jms_config=Fuente del mensaje +jms_config_title=Configuraci\u00F3n JMS +jms_connection_factory=Factor\u00EDa de Connection +jms_correlation_title=Usar campos alternativos para la correlaci\u00F3n de mensajes +jms_dest_setup=Configuraci\u00F3n +jms_dest_setup_dynamic=En cada muestra +jms_dest_setup_static=Al arranque +jms_file=Archivo +jms_initial_context_factory=Factor\u00EDa de Initial Context +jms_itertions=N\u00FAmero de muestras a agregar +jms_jndi_defaults_title=Configuraci\u00F3n por defecto de JNDI +jms_jndi_props=Propiedades JNDI +jms_map_message=Mensaje Map +jms_message_title=Propiedades de Mensaje +jms_message_type=Tipo de Mensaje +jms_msg_content=Contenido +jms_object_message=Mensaje Object +jms_point_to_point=JMS Punto-a-Punto +jms_props=Propiedades JMS +jms_provider_url=URL Proveedor +jms_publisher=Publicador JMS +jms_pwd=Contrase\u00F1a +jms_queue=Cola +jms_queue_connection_factory=Factor\u00EDa de QueueConnection +jms_queueing=Recursos JMS +jms_random_file=Archivo Aleatorio +jms_read_response=Respuesta Le\u00EDda +jms_receive_queue=Nombre JNDI cola Recepci\u00F3n +jms_request=S\u00F3lo Petici\u00F3n +jms_requestreply=Respuesta a Petici\u00F3n +jms_sample_title=Petici\u00F3n JMS por defecto +jms_send_queue=Nombre JNDI Cola Petici\u00F3n +jms_stop_between_samples=\u00BFParar entre muestras? +jms_subscriber_on_message=Utilizar MessageListener.onMessage() +jms_subscriber_receive=Utilizar TopicSubscriber.receive() +jms_subscriber_title=Suscriptor JMS +jms_testing_title=Petici\u00F3n Mensajer\u00EDa +jms_text_message=Mensaje Texto +jms_timeout=Timeout (milisegundos) +jms_topic=T\u00F3pico +jms_use_auth=\u00BFUsar Autorizaci\u00F3n? +jms_use_file=Desde archivo +jms_use_non_persistent_delivery=\u00BFUsar modo de entrega no persistente? +jms_use_properties_file=Utilizar archivo jndi.properties +jms_use_random_file=Archivo Aleatorio +jms_use_req_msgid_as_correlid=Usar el identificador del mensaje Request +jms_use_res_msgid_as_correlid=Usar el identificador del mensaje Response +jms_use_text=\u00C1rea de Texto +jms_user=Usuario +jndi_config_title=Configuraci\u00F3n JNDI +jndi_lookup_name=Interfaz Remota +jndi_lookup_title=Configuraci\u00F3n del Lookup JNDI +jndi_method_button_invoke=Invocar +jndi_method_button_reflect=Reflejar +jndi_method_home_name=Nombre de M\u00E9todo Home +jndi_method_home_parms=Par\u00E1metros de M\u00E9todo Home +jndi_method_name=Configuraci\u00F3n de M\u00E9todo +jndi_method_remote_interface_list=Interfaces Remotas +jndi_method_remote_name=Nombre de M\u00E9todo Remoto +jndi_method_remote_parms=Par\u00E1metros de M\u00E9todo Remoto +jndi_method_title=Configuraci\u00F3n de M\u00E9todo Remoto +jndi_testing_title=Petici\u00F3n JNDI +jndi_url_jndi_props=Propiedades JNDI +junit_append_error=A\u00F1adir errores de aserci\u00F3n +junit_append_exception=A\u00F1adir excepciones de ejecuci\u00F3n +junit_constructor_error=Imposible crear una instancia de la clase +junit_constructor_string=Etiqueta del constructor de String +junit_do_setup_teardown=No llamar a setUp y tearDown +junit_error_code=C\u00F3digo de error +junit_error_default_code=9999 +junit_error_default_msg=Ocurri\u00F3 un error no esperado +junit_error_msg=Mensaje de error +junit_failure_code=Codigo de fallo +junit_failure_default_code=0001 +junit_failure_default_msg=Test fall\u00F3 +junit_failure_msg=Mensaje de fallo +junit_junit4=Buscar anotaciones JUnit 4 (en el caso de JUnit 3) +junit_pkg_filter=Filtro de paquetes +junit_request=Petici\u00F3n JUnit +junit_request_defaults=Valores por defecto de la petici\u00F3n JUnit +junit_success_code=C\u00F3digo de \u00E9xito +junit_success_default_code=1000 +junit_success_default_msg=Test satisfactorio +junit_success_msg=Mensaje de \u00E9xito +junit_test_config=Par\u00E1metros del test JUnit +junit_test_method=M\u00E9todo de Test +ldap_argument_list=Lista de LDAPArgument +ldap_connto=Timeout de conexi\u00F3n (en milisegundos) +ldap_parse_results=\u00BFParsear los resultados de la b\u00FAsqueda? +ldap_sample_title=Valores por defecto Petici\u00F3n LDAP +ldap_search_baseobject=Realizar la b\u00FAsqueda 'baseobject' +ldap_search_onelevel=Realizar la b\u00FAsqueda 'onelevel' +ldap_search_subtree=Realizar la b\u00FAsqueda 'subtree' +ldap_secure=\u00BFUsar el Protocolo LDAP Seguro? +ldap_testing_title=Petici\u00F3n LDAP +ldapext_sample_title=Valores por defecto Petici\u00F3n Extendidad LDAP +ldapext_testing_title=Petici\u00F3n Extendida LDAP +library=Librer\u00EDa +load=Cargar +load_wsdl=Cargar WSDL +log_errors_only=Escribir en Log S\u00F3lo Errores +log_file=Ubicaci\u00F3n del archivo de logs +log_function_comment=Comentario adicional (opcional) +log_function_level=Nivel de Log (por defecto INFO) o OUT o ERR +log_function_string=Texto a escribir en log +log_function_string_ret=Texto a ser escrito en log (y retornado) +log_function_throwable=Texto para 'Throwable' (Opcional) +log_only=Log/Mostrar s\u00F3lo\: +log_parser=Nombre de la clase Parser de Log +log_parser_cnf_msg=No pude encontrar la clase. Por favor, aseg\u00FArese de colocar el archivo jar en el directorio /lib. +log_parser_illegal_msg=No pude acceder a la clase debido a una "IllegalAcessException". +log_parser_instantiate_msg=No pude crear una instancia del parser de log. Por favor aseg\u00FArese de que el parser implementar la interfaz LogParser. +log_sampler=Muestreador de Log de Acceso de Tomcat +log_success_only=\u00C9xitos +logic_controller_title=Controlador Simple +login_config=Configuraci\u00F3n de Login +login_config_element=Elemento de Configuraci\u00F3n de Login +longsum_param_1=Primer 'long' a a\u00F1adir +longsum_param_2=Segundo 'long' a a\u00F1adir - m\u00E1s 'longs' pueden ser sumados a\u00F1adiendo m\u00E1s argumentos. +loop_controller_title=Controlador Bucle +looping_control=Control de Bucles +lower_bound=L\u00EDmite inferior +mail_reader_account=Usuario\: +mail_reader_all_messages=Todo +mail_reader_delete=Borrar archivos del servidor +mail_reader_folder=Carpeta\: +mail_reader_num_messages=N\u00FAmero de mensajes a recuperar\: +mail_reader_password=Contrase\u00F1a\: +mail_reader_port=Puerto del servidor (opcional)\: +mail_reader_server=Servidor\: +mail_reader_server_type=Tipo de Servidor\: +mail_reader_storemime=Almacenar el mensaje usando MIME(raw) +mail_reader_title=Muestreador Lector de Correo +mail_sent=Mail enviado con \u00E9xito +mailer_attributes_panel=Atributos de Mailing +mailer_error=No pude enviar mail. Por favor, corrija las entradas incorrectas. +mailer_visualizer_title=Visualizador de Mailer +match_num_field=Coincidencia No. (0 para Aleatorio)\: +max=M\u00E1ximo +maximum_param=El valor m\u00E1ximo permitido para un rango de valores +md5hex_assertion_failure=Error validando MD5\: obtuve {0} pero deber\u00EDa haber obtenido {1} +md5hex_assertion_label=MD5Hex +md5hex_assertion_md5hex_test=MD5Hex a Comprobar +md5hex_assertion_title=Aserci\u00F3n MD5Hex +memory_cache=Cach\u00E9 en Memoria +menu_assertions=Aserciones +menu_close=Cerrar +menu_collapse_all=Colapsar todo +menu_config_element=Elemento de Configuraci\u00F3n +menu_edit=Editar +menu_expand_all=Expandir todo +menu_fragments=Fragmento de Prueba +menu_generative_controller=Muestreador +menu_listener=Receptor +menu_logic_controller=Controlador L\u00F3gico +menu_merge=Mezclar +menu_modifiers=Modificadores +menu_non_test_elements=Elementos NoDePrueba +menu_open=Abrir +menu_post_processors=Post Procesadores +menu_pre_processors=Pre Procesadores +menu_response_based_modifiers=Modificadores Basados en Respuesta +menu_tables=Tabla +menu_threads=Hilos (Usuarios) +menu_timer=Temporizador +metadata=MetaDatos +method=M\u00E9todo\: +mimetype=Tipo MIME +minimum_param=El valor m\u00EDnimo admitido para un rango de valores +minute=minuto +modddn=Nombre de entrada antiguo +modification_controller_title=Controlador de Modificaci\u00F3n +modification_manager_title=Gestor de Modificaci\u00F3n +modify_test=Prueba de Modificaci\u00F3n +modtest=Prueba de Modificaci\u00F3n +module_controller_module_to_run=M\u00F3dulo a ejecutar +module_controller_title=Controlador de M\u00F3dulo +module_controller_warning=No pudo encontrar el m\u00F3dulo\: +monitor_equation_active=Activo\: (ocupado/m\u00E1x) > 25% +monitor_equation_dead=Muerto\: no hay respuesta +monitor_equation_healthy=Sano. (ocupado/m\u00E1x) < 25% +monitor_equation_load=Carga\: ((ocupado/m\u00E1x) * 50) + ((memoria usada/memoria m\u00E1x) * 50) +monitor_equation_warning=Aviso\: (ocupado/m\u00E1x) > 67% +monitor_health_tab_title=Salud +monitor_health_title=Resultados del Monitor +monitor_is_title=Utilizar como Monitor +monitor_label_left_bottom=0 % +monitor_label_left_middle=50 % +monitor_label_left_top=100 % +monitor_label_prefix=Prefijo de conexi\u00F3n +monitor_label_right_active=Activo +monitor_label_right_dead=Muerto +monitor_label_right_healthy=Sano +monitor_label_right_warning=Aviso +monitor_legend_health=Salud +monitor_legend_load=Carga +monitor_legend_memory_per=Memoria % (usada/total) +monitor_legend_thread_per=Hilo % (ocupado/m\u00E1x) +monitor_load_factor_mem=50 +monitor_load_factor_thread=50 +monitor_performance_servers=Servidores +monitor_performance_tab_title=Rendimiento +monitor_performance_title=Gr\u00E1fico de Rendimiento +name=Nombre\: +new=Nuevo +newdn=Nuevo distinghuised name +no=Noruego +number_of_threads=N\u00FAmero de Hilos +obsolete_test_element=Este elemento de test es obsoleto +once_only_controller_title=Controlador Only Once +opcode=opCode +open=Abrir... +option=Opciones +optional_tasks=Tareas Opcionales +paramtable=Enviar Par\u00E1metros Con la Petici\u00F3n\: +password=Contrase\u00F1a +paste=Pegar +paste_insert=Pegar como Inserci\u00F3n +path=Ruta\: +path_extension_choice=Extensi\u00F3n de Path (utilice ";" como separador) +path_extension_dont_use_equals=No utilice el signo igual en la extensi\u00F3n del path (compatibilidad con Intershop Enfinity) +path_extension_dont_use_questionmark=No utilice el signo interrogaci\u00F3n en la extensi\u00F3n del path (compatibilidad con Intershop Enfinity) +patterns_to_exclude=URL Patrones a Excluir +patterns_to_include=URL Patrones a Incluir +pkcs12_desc=Clave PKCS (*.p12) +pl=Polaco +port=Puerto\: +post_thread_group_title=Tirar abajo grupo de Hilos +property_as_field_label={0}\: +property_default_param=Valor por defecto +property_edit=Editar +property_editor.value_is_invalid_message=El texto que acaba de introducir no es un valor v\u00E1lido para esta propiedad. La propiedad ser\u00E1 devuelta a su valor anterior. +property_editor.value_is_invalid_title=Entrada inv\u00E1lida +property_name_param=Nombre de propiedad +property_returnvalue_param=\u00BFRetornar el valor original de la propiedad (falso, por defecto)? +property_tool_tip={0}\: {1} +property_undefined=No definido +property_value_param=Valor de propiedad +property_visualiser_title=Visualizador de propiedades +protocol=Protocolo\: +protocol_java_border=Clase java +protocol_java_classname=Nombre de clase\: +protocol_java_config_tile=Muestra de Configure Java +protocol_java_test_title=Test Java +provider_url=URL Proveedor +proxy_assertions=A\u00F1adir Aserciones +proxy_cl_error=Si est\u00E1 especificando un servidor proxy, el puerto y el host deben ser provistos. +proxy_content_type_exclude=Excluir\: +proxy_content_type_filter=Filtro de tipo de contenido +proxy_content_type_include=Incluir\: +proxy_daemon_bind_error=No pudo crear el proxy - puerto en uso. Escoger otro puerto. +proxy_daemon_error=No pudo crear el proxy - ver traza para m\u00E1s detalles +proxy_headers=Capturar Cabeceras HTTP +proxy_httpsspoofing=Intentar usurpado HTTPS (HTTPS spoofing) +proxy_httpsspoofing_match=Filtro de URLs para usurpaci\u00F3n HTTPS (HTTPS spoofing) \: +proxy_regex=Coincidencia Regex +proxy_sampler_settings=Par\u00E1metros muestra HTTP +proxy_sampler_type=Tipo\: +proxy_separators=A\u00F1adir Separadores +proxy_target=Controlador Objetivo\: +proxy_test_plan_content=Contenido del plan de pruebas +proxy_title=Servidor Proxy HTTP +pt_br=Portugu\u00E9s (Brasile\u00F1o) +ramp_up=Periodo de Subida (en segundos)\: +random_control_title=Controlador Aleatorio +random_order_control_title=Controlador Orden Aleatorio +read_response_message=La lectura de respuesta no est\u00E1 activada. Para ver la respuesta, por favor marque la caja en el sampler. +read_response_note=Si "leer respuesta" est\u00E1 desactivado, el muestreador no leer\u00E1 la respuesta +read_response_note2=ni establecer\u00E1 el "SampleResult". Esto mejora el rendimiento, pero significa +read_response_note3=que el contenido de respuesta no ser\u00E1 logado. +read_soap_response=Leer Respuesta SOAP +realm=Dominio (realm) +record_controller_title=Controlador Grabaci\u00F3n +ref_name_field=Nombre de Referencia\: +regex_extractor_title=Extractor de Expresiones Regulares +regex_field=Expresi\u00F3n Regular\: +regex_source=Campo de Respuesta a comprobar +regex_src_body=Cuerpo +regex_src_body_unescaped=Cuerpo (No escapado) +regex_src_hdrs=Cabeceras +regex_src_url=URL +regexfunc_param_1=Expresi\u00F3n regular usada para buscar resultados en la peticiones previas +regexfunc_param_2=Plantilla para la cadena de sustituci\u00F3n, utilizando grupos de la expresi\u00F3n regular. El formato es $[grupo]$.\nEjemplo $1$. +regexfunc_param_3=Qu\u00E9 coincidencia utilizar. Un entero 1 o mayor, RAND para indicar a JMeter que utilice un n\u00FAmero aleatorio, un floar o ALL para indicar que todas las coincidencias deber\u00EDan ser utilizadas +regexfunc_param_4=Texto intermedio. Si se selecciona ALL, the texto intermedio ser\u00E1 utilizado para generar los resultados +regexfunc_param_5=Texto por Defecto. Utilizado en lugar de la plantilla si la expresi\u00F3n regular no encuentra coincidencias. +regexfunc_param_7=Nombre de la variable de entrada que contiene el texto a ser parseado ([muestra anterior]) +regexp_render_no_text=El dato de respuesta del resultado no es texto. +regexp_tester_button_test=Test +regexp_tester_field=Expresi\u00F3n regular\: +regexp_tester_title=Testeador de RegExp +remote_error_init=Error inicializando el servidor remoto +remote_error_starting=Error arrancando el servidor remoto +remote_exit=Salir Remoto +remote_exit_all=Salir de Todo Remoto +remote_shut=Apagar remoto +remote_shut_all=Apagar todo remoto +remote_start=Arrancar Remoto +remote_start_all=Arrancar Todo Remoto +remote_stop=Parar Remoto +remote_stop_all=Parar Todo Remoto +remove=Borrar +rename=Renombrar entrada +report=Informe +report_bar_chart=Gr\u00E1fico de barras +report_bar_graph_url=URL +report_base_directory=Directorio base +report_chart_caption=Leyenda del gr\u00E1fico +report_chart_x_axis=Eje X +report_chart_x_axis_label=Etiqueta para el eje X +report_chart_y_axis=Eje Y +report_chart_y_axis_label=Etiqueta para el eje Y +report_line_graph=Gr\u00E1fico de l\u00EDneas +report_line_graph_urls=Incluir URL +report_output_directory=Directorio de salida para el informe +report_page=P\u00E1gina del informe +report_page_element=Elemento de p\u00E1gina +report_page_footer=Pie de p\u00E1gina +report_page_header=Cabecera de p\u00E1gina +report_page_index=Crear \u00EDndice de p\u00E1gina +report_page_intro=P\u00E1gina de introducci\u00F3n +report_page_style_url=URL de la hoja de estilos +report_page_title=T\u00EDtulo de p\u00E1gina +report_pie_chart=Gr\u00E1fico de tarta +report_plan=Esquema del reporte +report_select=Seleccionar +report_summary=Resumen de informe +report_table=Tabla de informe +report_writer=Escritor del reporte +report_writer_html=Escritor HTML del reporte +request_data=Pedir Datos +reset_gui=Resetear GUI +response_save_as_md5=\u00BFGuardar la respuesta como MD5 hash? +restart=Rearranque +resultaction_title=Manejador de Acci\u00F3n para Status de Resultados +resultsaver_errors=Guardar Respuestas Fallidas Solamente +resultsaver_prefix=Prefijo de nombre de archivo\: +resultsaver_skipautonumber=No a\u00F1adir n\u00FAmero al prefijo +resultsaver_skipsuffix=No a\u00F1adir sufijo +resultsaver_success=Guardar s\u00F3lo respuestas satisfactorias +resultsaver_title=Guardar respuestas en archivo +resultsaver_variable=Nombre de variable\: +retobj=Devolver objeto +reuseconnection=Reusar conexi\u00F3n +revert_project=Revertir +revert_project?=\u00BFRevertir proyecto? +root=Ra\u00EDz +root_title=Ra\u00EDz +run=Lanzar +running_test=Test lanzado +runtime_controller_title=Controlador Tiempo de Ejecuci\u00F3n +runtime_seconds=Tiempo de ejecuci\u00F3n (segundos) +sample_result_save_configuration=Guardar Configuraci\u00F3n de Resultado de Muestra +sample_scope=Aplicar a\: +sample_scope_all=Muestra principal y submuestras +sample_scope_children=S\u00F3lo submuestras +sample_scope_parent=S\u00F3lo muestra principal +sample_scope_variable=Variable JMeter +sampler_label=Etiqueta +sampler_on_error_action=Acci\u00F3n a tomar despu\u00E9s de un error de Muestreador +sampler_on_error_continue=Continuar +sampler_on_error_start_next_loop=Comenzar siguiente iteraci\u00F3n +sampler_on_error_stop_test=Parar Test +sampler_on_error_stop_test_now=Parar test ahora +sampler_on_error_stop_thread=Parar Hilo +save=Guardar +save?=\u00BFGuardar? +save_all_as=Guardar Plan de Pruebas como +save_as=Guardar selecci\u00F3n como... +save_as_error=\u00A1M\u00E1s de un item seleccionado\! +save_as_image=Guardar como imagen +save_as_image_all=Guardar la pantalla como imagen +save_assertionresultsfailuremessage=Guardar Mensaje de Fallo de Resultados de Aserci\u00F3n +save_assertions=Guardar Resultados de Aserci\u00F3n +save_asxml=Guardar Como XML +save_bytes=Guardar conteo de bytes +save_code=Guardar C\u00F3digo de Respuesta +save_datatype=Guardar Tipo de Datos +save_encoding=Guardar Codificaci\u00F3n +save_fieldnames=Guardar Nombre de Campo +save_filename=Guardar el nombre del fichero de respuesta +save_graphics=Guardar Gr\u00E1ficos +save_hostname=Guardar el nombre de host +save_idletime=Guardar tiempo inactivo +save_label=Guardar Etiqueta +save_latency=Guardar Latencia +save_message=Guardar Mensaje de Respuesta +save_overwrite_existing_file=El fichero seleccionado ya existe, \u00BFquiere sobreescribirlo? +save_requestheaders=Guardar Cabeceras de Petici\u00F3n +save_responsedata=Guardar Datos de Respuesta +save_responseheaders=Guardar Cabeceras de Respuesta +save_samplecount=Guardar muestra y conteo de error +save_samplerdata=Guardar Datos de Muestreador +save_subresults=Guardar Sub Resultados +save_success=Guardado Correctamente +save_threadcounts=Guardar conteos hilos activos +save_threadname=Guardar Nombre de Hilo +save_time=Guardar Tiempo +save_timestamp=Guardar Etiqueta de Tiempo +save_url=Guardar URL +sbind=Conexi\u00F3n/Desconexi\u00F3n Simple +scheduler=Planificador +scheduler_configuration=Configuraci\u00F3n del Planificador +scope=\u00C1mbito +search_base=Base de B\u00FAsqueda +search_filter=Filtro de B\u00FAsqueda +search_test=Prueba de B\u00FAsqueda +search_text_button_close=Cerrar +search_text_button_find=Encontrar +search_text_button_next=Encontrar siguiente +search_text_chkbox_case=Sensible a may\u00FAsculas +search_text_chkbox_regexp=Expresi\u00F3n regular +search_text_field=Buscar\: +search_text_msg_not_found=Texto no encontrado +search_text_title_not_found=No encontrado +searchbase=Base de B\u00FAsqueda +searchfilter=Filtro de B\u00FAsqueda +searchtest=Prueba de B\u00FAsqueda +second=segundo +secure=Seguro +send_file=Enviar un archivo Con la Petici\u00F3n +send_file_browse=Navegar... +send_file_filename_label=Nombre de Archivo\: +send_file_mime_label=Tipo MIME\: +send_file_param_name_label=Nombre de Par\u00E1metro\: +server=Nombre de Servidor o IP\: +servername=Nombre de Servidor\: +session_argument_name=Nombre de Argumento de Sesi\u00F3n +setup_thread_group_title=Montar grupo de Hilos +should_save=Deber\u00EDa guardar el plan de pruebas antes de lanzarlo. Si est\u00E1 utilizando archivos de datos (ie, para DCV o _StringFromFile), entonces es especialmente importante que primero guarde su script de prueba. +shutdown=Interrumpir +simple_config_element=Elemento de Configuraci\u00F3n Simple +simple_data_writer_title=Escritor de Datos Simple +size_assertion_comparator_error_equal=siendo igual a +size_assertion_comparator_error_greater=siendo mayor que +size_assertion_comparator_error_greaterequal=siendo mayor o igual a +size_assertion_comparator_error_less=siendo menor que +size_assertion_comparator_error_lessequal=siendo menor o igual que +size_assertion_comparator_error_notequal=no siendo igual a +size_assertion_comparator_label=Tipo de Comparaci\u00F3n +size_assertion_failure=El resultado tuvo el tama\u00F1o incorrecto\: fu\u00E9 {0} bytes, pero deber\u00EDa haber sido {1} {2} bytes. +size_assertion_input_error=Por favor, introduzca un entero positivo v\u00E1lido. +size_assertion_label=Tama\u00F1o en bytes\: +size_assertion_size_test=Tama\u00F1o a Comprobar +size_assertion_title=Aserci\u00F3n de Tama\u00F1o +smime_assertion_issuer_dn=Nombre \u00FAnico del emisor\: +smime_assertion_message_position=Ejecutar aserci\u00F3n sobre el mensaje a partir de la posici\u00F3n +smime_assertion_not_signed=Mensaje no firmado +smime_assertion_signature=Firma +smime_assertion_signer=Cerficado del firmante +smime_assertion_signer_by_file=Certificado +smime_assertion_signer_constraints=Chequear valores +smime_assertion_signer_dn=Nombre \u00FAnico del firmante +smime_assertion_signer_email=Direcci\u00F3n de correo del firmante +smime_assertion_signer_no_check=No chequear +smime_assertion_signer_serial=N\u00FAmero de serie +smime_assertion_title=Aserci\u00F3n SMIME +smime_assertion_verify_signature=Verificar firma +smtp_additional_settings=Par\u00E1metros adicionales +smtp_attach_file=Adjuntar fichero(s)\: +smtp_attach_file_tooltip=Separar ficheros con ";" +smtp_auth_settings=Par\u00E1metros de autentificaci\u00F3n +smtp_bcc=Direcciones en copia oculta (BCC)\: +smtp_cc=Direcciones en copia(CC)\: +smtp_default_port=(Por defecto\: SMTP\:25, SSL\:465, StartTLS\:587) +smtp_eml=Enviar .eml\: +smtp_enabledebug=\u00BFActivar las trazas de depuraci\u00F3n? +smtp_enforcestarttls=Imponer StartTLS +smtp_enforcestarttls_tooltip=Forza al servidor a usar StartTLS.
Si no es seleccionado el servidor SMTP no soporta StartTLS,
una conexi\u00F3n normal SMTP ser\u00E1 usada como reserva.
Por favor advierta que este objeto crea un fichero en "/tmp/",
so Esto causar\u00E1 problemas bajo Windows. +smtp_from=Direcci\u00F3n Desde\: +smtp_header_add=A\u00F1adir cabecera +smtp_header_name=Nombre de cabecera +smtp_header_remove=Suprimir +smtp_header_value=Valor de cabecera +smtp_mail_settings=Par\u00E1metros del correo +smtp_message=Mensaje\: +smtp_message_settings=Par\u00E1metros del mensaje\: +smtp_messagesize=Calcular tama\u00F1o del mensaje +smtp_password=Contrase\u00F1a\: +smtp_plainbody=Enviar texto plano(i.e. no multipart/mixed) +smtp_replyto=Direcci\u00F3n Responder-a\: +smtp_sampler_title=Muestra SMTP +smtp_security_settings=Par\u00E1metros de seguridad +smtp_server=Servidor\: +smtp_server_port=Puerto\: +smtp_server_settings=Par\u00E1metros del servidor +smtp_subject=Asunto\: +smtp_suppresssubj=Suprimir la cabecera del asunto +smtp_timestamp=Incluir timestamp en el asunto +smtp_to=Direcci\u00F3n A\: +smtp_trustall=Verificar todos los certificados +smtp_trustall_tooltip=Fuerza a JMeter a verificar todos los certificados, que vienen del CA. +smtp_truststore=Almacenamiento local de confianza\: +smtp_truststore_tooltip=Nombre de la ruta del almacenamiento local de confianza.
Rutas relativas son resueltas contra el directorio actual.
Si esto falla, contra el directorio que contiene el script de test (JMX file) +smtp_useauth=Usar autentificaci\u00F3n +smtp_usenone=No usar funcionalidades de seguridad +smtp_username=Nombre de usuario\: +smtp_usessl=Usar SSL +smtp_usestarttls=Usar StartTLS +smtp_usetruststore=Usar almacenamiento local de confianza +smtp_usetruststore_tooltip=Permite a JMeter usar un almacenamiento de confianza local. +soap_action=Acci\u00F3n Soap +soap_data_title=Datos Soap/XML-RPC +soap_sampler_title=Petici\u00F3n Soap/XML-RPC +soap_send_action=Enviar SOAPAction\: +spline_visualizer_average=Media +spline_visualizer_incoming=Entrando +spline_visualizer_maximum=M\u00E1ximo +spline_visualizer_minimum=M\u00EDnimo +spline_visualizer_title=Visualizador Spline +spline_visualizer_waitingmessage=Esperando muestras +split_function_separator=Texto para separar. Por defecto es , (coma) +split_function_string=Texto a separar +ssl_alias_prompt=Por favor, introduzca su alias favorito +ssl_alias_select=Seleccione su alias para la prueba +ssl_alias_title=Alias de Cliente +ssl_error_title=Problema con el KeyStore +ssl_pass_prompt=Por favor, introduzca su contrase\u00F1a +ssl_pass_title=Contrase\u00F1a de KeyStore +ssl_port=Puerto SSL +sslmanager=Gestor SSL +start=Arrancar +start_no_timers=Inicio no se detiene +starttime=Tiempo de Arranque +stop=Parar +stopping_test=Parando todos los hilos. Por favor, sea paciente. +stopping_test_failed=Uno o m\u00E1s hilos de test no saldr\u00E1n; ver fichero de log. +stopping_test_title=Parando la Prueba +string_from_file_encoding=Codificaci\u00F3n, si no el por defecto de la plataforma (opcional) +string_from_file_file_name=Introduzca ruta completa al archivo +string_from_file_seq_final=N\u00FAmero final de secuencia de archivo +string_from_file_seq_start=N\u00FAmero inicial de secuencia de archivo +summariser_title=Generar Resumen de Resultados +summary_report=Reporte resumen +switch_controller_label=Conmutar Valor +switch_controller_title=Conmutar Controlador +table_visualizer_bytes=Bytes +table_visualizer_sample_num=Muestra \# +table_visualizer_sample_time=Tiempo de Muestra (ms) +table_visualizer_start_time=Tiempo de comienzo +table_visualizer_status=Estado +table_visualizer_success=\u00C9xito +table_visualizer_thread_name=Nombre del hilo +table_visualizer_warning=Alerta +tcp_classname=Nombre de clase TCPClient\: +tcp_config_title=Configuraci\u00F3n de Muestreador TCP +tcp_nodelay=Establecer SinRetardo +tcp_port=Puerto\: +tcp_request_data=Texto a enviar +tcp_sample_title=Muestreador TCP +tcp_timeout=Timeout (milisegundos) +template_field=Plantilla\: +test=Prueba +test_action_action=Acci\u00F3n +test_action_duration=Duraci\u00F3n +test_action_pause=Pausa +test_action_stop=Parar +test_action_stop_now=Parar ahora +test_action_target=Objetivo +test_action_target_test=Todos los Hilos +test_action_target_thread=Hilo Actual +test_action_title=Acci\u00F3n de Prueba +test_configuration=Configuraci\u00F3n de Pruebas +test_fragment_title=Fragmento de Prueba +test_plan=Plan de Pruebas +test_plan_classpath_browse=A\u00F1adir directorio o jar al classpath +testconfiguration=Configuraci\u00F3n de Pruebas +testplan.serialized=Lanza cada Grupo de Hilos separadamente (i.e. lanza un grupo antes de lanzar el siguiente) +testplan_comments=Comentarios +testt=Prueba +textbox_cancel=Cancelar +textbox_close=Cerrar +textbox_save_close=Guardar y cerrar +textbox_title_edit=Editar texto +textbox_title_view=Ver texto +textbox_tooltip_cell=Doble click para ver/editar +thread_delay_properties=Propiedades de Retardo de Hilos +thread_group_title=Grupo de Hilos +thread_properties=Propiedades de Hilo +threadgroup=Grupo de Hilos +throughput_control_bynumber_label=Ejecuciones Totales +throughput_control_bypercent_label=Porcentaje de Ejecuciones +throughput_control_perthread_label=Por Usuario +throughput_control_title=Controlador Throughput +throughput_control_tplabel=Rendimiento +time_format=Cadena de formateo para SimpleDateFormat(opcional) +timelim=L\u00EDmite de Tiempo +tr=Turco +transaction_controller_include_timers=Incluir la duraci\u00F3n de temporizador y pre-post procesadores en la muestra generada +transaction_controller_parent=Generar muestra padre +transaction_controller_title=Controlador Transaction +unbind=Desligar Hilo +unescape_html_string=Cadena de texto para quitar caracteres de escapado +unescape_string=Cadena de texto contiene caracteres Java de escapado +uniform_timer_delay=Desplazamiento de Retraso Constante (en milisegundos)\: +uniform_timer_memo=A\u00F1ade un retardo aleatorio con una distribuci\u00F3n uniforme +uniform_timer_range=M\u00E1ximo retardo Aleatorio (en milisegundos) +uniform_timer_title=Temporizador Aleatorio Uniforme +update_per_iter=Actualizar Una Vez Por Iteraci\u00F3n +upload=Subida de Archivo +upper_bound=L\u00EDmite Superior +url=URL +url_config_get=GET +url_config_http=HTTP +url_config_https=HTTPS +url_config_post=POST +url_config_protocol=Protocolo\: +url_config_title=Valores por Defecto para Petici\u00F3n HTTP +url_full_config_title=Muestra UrlFull +url_multipart_config_title=Valores por Defecto para Petici\u00F3n HTTP Multipart +use_expires=Usar cabecera 'Cache-Control/Expires' cuando se procesan peticiones GET +use_keepalive=Utilizar KeepAlive +use_multipart_for_http_post=Usar 'multipart/form-data' para HTTP POST +use_multipart_mode_browser=Cabeceras compatibles con navegadores +use_recording_controller=Utilizar Controlador Recording +user=Usuario +user_defined_test=Prueba Definida por el Usuario +user_defined_variables=Variables definidas por el Usuario +user_param_mod_help_note=(No cambie esto. En su lugar, modifique el archivo con ese nombre en el directorio /bin de JMeter) +user_parameters_table=Par\u00E1metros +user_parameters_title=Par\u00E1metros de Usuario +userdn=Nombre de Usuario +username=Nombre de Usuario +userpw=Contrase\u00F1a +value=Valor +var_name=Nombre de Referencia +variable_name_param=Nombre de variable(puede incluir variables y referencias a funci\u00F3n) +view_graph_tree_title=Ver \u00C1rbol Gr\u00E1fico +view_results_assertion_error=Error de aserci\u00F3n\: +view_results_assertion_failure=Fallo de aserci\u00F3n\: +view_results_assertion_failure_message=Mensaje de fallo de aserci\u00F3n\: +view_results_desc=Muestra los resultados de texto del muestreo en forma de \u00E1rbol +view_results_error_count=Conteo de error\: +view_results_fields=campos\: +view_results_in_table=Ver Resultados en \u00C1rbol +view_results_latency=Latencia\: +view_results_load_time=Tiempo de carga\: +view_results_render=Renderizador\: +view_results_render_html=HTML +view_results_render_html_embedded=HTML(descargar elementos embebidos) +view_results_render_json=JSON +view_results_render_text=Texto +view_results_render_xml=XML +view_results_request_headers=Cabeceras de petici\u00F3n\: +view_results_response_code=C\u00F3digo de respuesta\: +view_results_response_headers=Cabeceras de respuesta\: +view_results_response_message=Mensaje de respuesta\: +view_results_response_too_large_message=Respuesta muy larga a ser mostrada. Tama\u00F1o\: +view_results_response_partial_message=Principio del mensaje: +view_results_sample_count=Conteo de muestra\: +view_results_sample_start=Comienzo de muestra\: +view_results_search_pane=Panel de b\u00FAsqueda +view_results_size_in_bytes=Tama\u00F1o en bytes\: +view_results_tab_assertion=Resultado de la aserci\u00F3n +view_results_tab_request=Petici\u00F3n +view_results_tab_response=Datos de Respuesta +view_results_tab_sampler=Resultado del Muestreador +view_results_table_fields_key=Campo adicional +view_results_table_fields_value=Valor +view_results_table_headers_key=Cabecera de respuesta +view_results_table_headers_value=Valor +view_results_table_request_headers_key=Cabecera de petici\u00F3n +view_results_table_request_headers_value=Valor +view_results_table_request_http_cookie=Cookie +view_results_table_request_http_host=M\u00E1quina +view_results_table_request_http_method=M\u00E9todo +view_results_table_request_http_nohttp=No muestra HTTP +view_results_table_request_http_path=Ruta +view_results_table_request_http_port=Puerto +view_results_table_request_http_protocol=Protocolo +view_results_table_request_params_key=Nombre de par\u00E1metro +view_results_table_request_params_value=Valor +view_results_table_request_raw_nodata=No mostrar datos +view_results_table_request_tab_http=HTTP +view_results_table_request_tab_raw=En bruto +view_results_table_result_tab_parsed=Parseado +view_results_table_result_tab_raw=En bruto +view_results_thread_name=Nombre del hilo\: +view_results_title=Ver Resultados +view_results_tree_title=Ver \u00C1rbol de Resultados +warning=\u00A1Atenci\u00F3n\! +web_proxy_server_title=Servidor Proxy +web_request=Petici\u00F3n HTTP +web_server=Servidor Web +web_server_client=Implementaci\u00F3n del Cliente\: +web_server_domain=Nombre de Servidor o IP\: +web_server_port=Puerto\: +web_server_timeout_connect=Conexi\u00F3n\: +web_server_timeout_response=Respuesta\: +web_server_timeout_title=Timeout (milisegundos) +web_testing2_source_ip=Direcci\u00F3n IP fuente\: +web_testing2_title=Petici\u00F3n HTTP HttpClient +web_testing_embedded_url_pattern=Las URLs embebidas deben coincidir a\: +web_testing_retrieve_images=Recuperar Todos los Recursos Empotrados de Archivos HTML +web_testing_title=Petici\u00F3n HTTP +webservice_proxy_host=Host Proxy +webservice_proxy_note=Si est\u00E1 seleccionado "Utilizar Proxy HTTP", pero no se proporciona host o puerto, el muestreador +webservice_proxy_note2=buscar\u00E1 opciones en la l\u00EDnea de comandos. Si no se proporcionan host o puerto +webservice_proxy_note3=all\u00ED, finalmente fallar\u00E1 silenciosamente. +webservice_proxy_port=Puerto Proxy +webservice_sampler_title=Petici\u00F3n WebService(SOAP) +webservice_soap_action=Acci\u00F3n SOAP +webservice_timeout=Timeout\: +webservice_use_proxy=Utilizar Proxy HTTP +while_controller_label=Condici\u00F3n (funci\u00F3n o variable) +while_controller_title=Controlador While +workbench_title=Banco de Trabajo +wsdl_helper_error=El WSDL no es v\u00E1lido, por favor compruebe la url. +wsdl_url=URL del WSDL +wsdl_url_error=El WSDL est\u00E1 vacio. +xml_assertion_title=Aserci\u00F3n XML +xml_download_dtds=Recuperar DTDs externos +xml_namespace_button=Utilizar NameSpaces +xml_tolerant_button=Parser XML/HTML Tolerante +xml_validate_button=Validar XML +xml_whitespace_button=Ignorar Espacios +xmlschema_assertion_label=Nombre de Archivo\: +xmlschema_assertion_title=Aserci\u00F3n de Esquema XML +xpath_assertion_button=Validar +xpath_assertion_check=Comprobar Expresi\u00F3n XPath +xpath_assertion_error=Error en XPath +xpath_assertion_failed=Expresi\u00F3n XPath Inv\u00E1lida +xpath_assertion_label=XPath +xpath_assertion_negate=True si nada coincide +xpath_assertion_option=Opciones para parsear XML +xpath_assertion_test=Aserci\u00F3n XPath +xpath_assertion_tidy=Prueba y ordena la entrada +xpath_assertion_title=Aserci\u00F3n XPath +xpath_assertion_valid=Expresi\u00F3n XPath V\u00E1lida +xpath_assertion_validation=Validar el XML contra el DTD +xpath_assertion_whitespace=Ignorar espacios +xpath_expression=Expresi\u00F3n XPath contra la que comparar +xpath_extractor_fragment=\u00BFRetornar el fragmento XPATH en el caso de contenido de texto? +xpath_extractor_query=Consulta XPath\: +xpath_extractor_title=Extractor XPath +xpath_file_file_name=Archivo XML del que obtener valores +xpath_tidy_quiet=Silencioso +xpath_tidy_report_errors=Reportar los errores +xpath_tidy_show_warnings=Mostrar advertencias +you_must_enter_a_valid_number=Debe introducir un n\u00FAmero v\u00E1lido +zh_cn=Chino (Simplificado) +zh_tw=Chino (Tradicional) diff --git a/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_fr.properties b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_fr.properties new file mode 100644 index 0000000..069bc6d --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_fr.properties @@ -0,0 +1,1192 @@ +# 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. + +#Stored by I18NEdit, may be edited! +about=A propos de JMeter +add=Ajouter +add_as_child=Ajouter en tant qu'enfant +add_from_clipboard=Ajouter depuis Presse-papier +add_parameter=Ajouter un param\u00E8tre +add_pattern=Ajouter un motif \: +add_test=Ajout +add_user=Ajouter un utilisateur +add_value=Ajouter valeur +addtest=Ajout +aggregate_graph=Graphique des statistiques +aggregate_graph_choose_color=Choisir couleur +aggregate_graph_choose_foreground_color=Couleur valeur +aggregate_graph_color_bar=Couleur \: +aggregate_graph_column=Colonne +aggregate_graph_column_selection=S\u00E9lection de colonnes par libell\u00E9 \: +aggregate_graph_column_settings=Param\u00E8tres colonne +aggregate_graph_columns_to_display=Colonnes \u00E0 afficher \: +aggregate_graph_dimension=Taille graphique +aggregate_graph_display=G\u00E9n\u00E9rer le graphique +aggregate_graph_draw_outlines=Bordure de barre ? +aggregate_graph_dynamic_size=Taille de graphique dynamique +aggregate_graph_font=Police \: +aggregate_graph_height=Hauteur \: +aggregate_graph_legend=L\u00E9gende +aggregate_graph_legend.placement.bottom=Bas +aggregate_graph_legend.placement.left=Gauche +aggregate_graph_legend.placement.right=Droite +aggregate_graph_legend.placement.top=Haut +aggregate_graph_legend_placement=Position \: +aggregate_graph_max_length_xaxis_label=Longueur maximum du libell\u00E9 de l'axe des abscisses \: +aggregate_graph_ms=Millisecondes +aggregate_graph_no_values_to_graph=Pas de valeurs pour le graphique +aggregate_graph_number_grouping=S\u00E9parateur de milliers ? +aggregate_graph_reload_data=Recharger les donn\u00E9es +aggregate_graph_response_time=Temps de r\u00E9ponse +aggregate_graph_save=Enregistrer le graphique +aggregate_graph_save_table=Enregistrer le tableau de donn\u00E9es +aggregate_graph_save_table_header=Inclure l'ent\u00EAte du tableau +aggregate_graph_size=Taille \: +aggregate_graph_style=Style \: +aggregate_graph_sync_with_name=Synchroniser avec nom +aggregate_graph_tab_graph=Graphique +aggregate_graph_tab_settings=Param\u00E8tres +aggregate_graph_title=Graphique agr\u00E9g\u00E9 +aggregate_graph_title_group=Titre +aggregate_graph_use_group_name=Ajouter le nom du groupe aux libell\u00E9s +aggregate_graph_user_title=Titre du graphique \: +aggregate_graph_value_font=Police de la valeur \: +aggregate_graph_value_labels_vertical=Libell\u00E9 de valeurs vertical ? +aggregate_graph_width=Largeur \: +aggregate_graph_xaxis_group=Abscisses +aggregate_graph_yaxis_group=Ordonn\u00E9es +aggregate_graph_yaxis_max_value=Echelle maximum \: +aggregate_report=Rapport agr\u00E9g\u00E9 +aggregate_report_90=90% +aggregate_report_90%_line=90e centile +aggregate_report_bandwidth=Ko/sec +aggregate_report_count=\# Echantillons +aggregate_report_error=Erreur +aggregate_report_error%=% Erreur +aggregate_report_max=Max +aggregate_report_median=M\u00E9diane +aggregate_report_min=Min +aggregate_report_rate=D\u00E9bit +aggregate_report_stddev=Ecart type +aggregate_report_total_label=TOTAL +ajp_sampler_title=Requ\u00EAte AJP/1.3 +als_message=Note \: Le parseur de log d'acc\u00E8s est g\u00E9n\u00E9rique et vous permet de se brancher \u00E0 +als_message2=votre propre parseur. Pour se faire, impl\u00E9menter le LogParser, ajouter le jar au +als_message3=r\u00E9pertoire /lib et entrer la classe (fichier .class) dans l'\u00E9chantillon (sampler). +analyze=En train d'analyser le fichier de donn\u00E9es +anchor_modifier_title=Analyseur de lien HTML +appearance=Apparence +argument_must_not_be_negative=L'argument ne peut pas \u00EAtre n\u00E9gatif \! +arguments_panel_title=Param\u00E8tres du processus syst\u00E8me +assertion_assume_success=Ignorer le statut +assertion_body_resp=Corps de r\u00E9ponse +assertion_code_resp=Code de r\u00E9ponse +assertion_contains=Contient (exp. r\u00E9guli\u00E8re) +assertion_equals=Est \u00E9gale \u00E0 (texte brut) +assertion_headers=Ent\u00EAtes de r\u00E9ponse +assertion_matches=Correspond \u00E0 (exp. r\u00E9guli\u00E8re) +assertion_message_resp=Message de r\u00E9ponse +assertion_network_size=R\u00E9ponse compl\u00E8te +assertion_not=Inverser +assertion_pattern_match_rules=Type de correspondance du motif +assertion_patterns_to_test=Motifs \u00E0 tester +assertion_resp_field=Section de r\u00E9ponse \u00E0 tester +assertion_resp_size_field=Taille \u00E0 v\u00E9rifier sur +assertion_substring=Contient (texte brut) +assertion_text_resp=Texte de r\u00E9ponse +assertion_textarea_label=Assertions \: +assertion_title=Assertion R\u00E9ponse +assertion_url_samp=URL Echantillon +assertion_visualizer_title=R\u00E9cepteur d'assertions +attribute=Attribut \: +attrs=Attributs +auth_base_url=URL de base +auth_manager_title=Gestionnaire d'autorisation HTTP +auths_stored=Autorisations stock\u00E9es +average=Moyenne +average_bytes=Moy. octets +bind=Connexion de l'unit\u00E9 +bouncy_castle_unavailable_message=Les jars de bouncycastle sont indisponibles, ajoutez les au classpath. +browse=Parcourir... +bsf_sampler_title=Echantillon BSF +bsf_script=Script \u00E0 lancer (variables\: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +bsf_script_file=Fichier script \u00E0 lancer \: +bsf_script_language=Langage de script \: +bsf_script_parameters=Param\u00E8tres \u00E0 passer au script/fichier \: +bsh_assertion_script=Script (IO\: Failure[Message], Response. IN\: Response[Data|Code|Message|Headers], RequestHeaders, Sample[Label|rData]) +bsh_assertion_script_variables=Les variables suivantes sont d\u00E9finies pour le script \:\nEn lecture/\u00E9criture \: Failure, FailureMessage, SampleResult, vars, props, log.\nEn lecture seule \: Response[Data|Code|Message|Headers], RequestHeaders, SampleLabel, SamplerData, ctx +bsh_assertion_title=Assertion BeanShell +bsh_function_expression=Expression \u00E0 \u00E9valuer +bsh_sampler_title=Echantillon BeanShell +bsh_script=Script (voir la suite pour les variables qui sont d\u00E9finies) +bsh_script_file=Fichier script \: +bsh_script_parameters=Param\u00E8tres (-> String Parameters et String []bsh.args) +bsh_script_reset_interpreter=R\u00E9initialiser l'interpr\u00E9teur bsh avant chaque appel +bsh_script_variables=Les variables suivantes sont d\u00E9finies pour le script \:\nSampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +busy_testing=Je suis occup\u00E9 \u00E0 tester, veuillez arr\u00EAter le test avant de changer le param\u00E8trage +cache_manager_size=Nombre maximum d'\u00E9l\u00E9ments dans le cache +cache_manager_title=Gestionnaire de cache HTTP +cache_session_id=Identifiant de session de cache ? +cancel=Annuler +cancel_exit_to_save=Il y a des \u00E9l\u00E9ments qui n'ont pas \u00E9t\u00E9 sauv\u00E9s. Voulez-vous enregistrer avant de sortir ? +cancel_new_to_save=Il y a des \u00E9l\u00E9ments qui n'ont pas \u00E9t\u00E9 sauv\u00E9s. Voulez-vous enregistrer avant de nettoyer le plan de test ? +cancel_revert_project=Il y a des \u00E9l\u00E9ments qui n'ont pas \u00E9t\u00E9 sauv\u00E9s. Annuler les changements et revenir \u00E0 la derni\u00E8re sauvegarde du plan de test ? +change_parent=Changer le contr\u00F4leur +char_value=Caract\u221A\u00AEre num\u221A\u00A9rique Unicode (d\u221A\u00A9cimal or 0xhex) +check_return_code_title=V\u00E9rifier le code retour +choose_function=Choisir une fonction +choose_language=Choisir une langue +clear=Nettoyer +clear_all=Nettoyer tout +clear_cache_per_iter=Nettoyer le cache \u00E0 chaque it\u00E9ration ? +clear_cookies_per_iter=Nettoyer les cookies \u00E0 chaque it\u00E9ration ? +close=Fermer +column_delete_disallowed=Supprimer cette colonne n'est pas possible +column_number=Num\u00E9ro de colonne du fichier CSV | next | *alias +command_config_box_title=Commande \u00E0 ex\u00E9cuter +command_field_title=Commande \: +compare=Comparaison +comparefilt=Filtre de comparaison +comparison_differ_content=Le contenu des r\u00E9ponses est diff\u00E9rent. +comparison_differ_time=La diff\u00E9rence du temps de r\u00E9ponse diff\u00E8re de plus de +comparison_invalid_node=Noeud invalide +comparison_regex_string=Expression r\u00E9guli\u00E8re +comparison_regex_substitution=Substitution +comparison_response_time=Temps de r\u00E9ponse \: +comparison_unit=ms +comparison_visualizer_title=R\u00E9cepteur d'assertions de comparaison +config_element=El\u00E9ment de configuration +config_save_settings=Configurer +configure_wsdl=Configurer +confirm=Confirmer +constant_throughput_timer_memo=Ajouter un d\u00E9lai entre les \u00E9chantillions pour obtenir un d\u00E9bit constant +constant_timer_delay=D\u00E9lai d'attente (en millisecondes) \: +constant_timer_memo=Ajouter un d\u00E9lai fixe entre les \u00E9chantillions de test +constant_timer_title=Compteur de temps fixe +content_encoding=Encodage contenu \: +controller=Contr\u00F4leur +cookie_manager_policy=Politique des cookies +cookie_manager_title=Gestionnaire de cookies HTTP +cookies_stored=Cookies stock\u00E9s +copy=Copier +counter_config_title=Compteur +counter_per_user=Suivre le compteur ind\u00E9pendamment pour chaque unit\u00E9 de test +counter_reset_per_tg_iteration=R\u00E9initialiser le compteur \u00E0 chaque it\u00E9ration du groupe d'unit\u00E9s +countlim=Limiter le nombre d'\u00E9l\u00E9ments retourn\u00E9s \u00E0 +csvread_file_file_name=Fichier CSV pour obtenir les valeurs de | *alias +cut=Couper +cut_paste_function=Fonction de copier/coller de cha\u00EEne de caract\u00E8re +database_conn_pool_max_usage=Utilisation max pour chaque connexion\: +database_conn_pool_props=Pool de connexions \u221A\u2020 la base de donn\u221A\u00A9es +database_conn_pool_size=Nombre de Connexions dans le Pool\: +database_conn_pool_title=Valeurs par d\u221A\u00A9faut du Pool de connexions JDBC +database_driver_class=Classe du Driver\: +database_login_title=Valeurs par d\u221A\u00A9faut de la base de donn\u221A\u00A9es JDBC +database_sql_query_string=Requ\u00EAte SQL \: +database_sql_query_title=Requ\u00EAte SQL JDBC par d\u00E9faut +database_testing_title=Requ\u221A\u2122te JDBC +database_url=URL JDBC\: +database_url_jdbc_props=URL et driver JDBC de la base de donn\u221A\u00A9es +ddn=DN \: +de=Allemand +debug_off=D\u00E9sactiver le d\u00E9bogage +debug_on=Activer le d\u00E9bogage +default_parameters=Param\u00E8tres par d\u00E9faut +default_value_field=Valeur par d\u00E9faut \: +delay=D\u00E9lai avant d\u00E9marrage (secondes) \: +delete=Supprimer +delete_parameter=Supprimer le param\u00E8tre +delete_test=Suppression +delete_user=Supprimer l'utilisateur +deltest=Suppression +deref=D\u00E9r\u00E9f\u00E9rencement des alias +description=Description +detail=D\u00E9tail +directory_field_title=R\u00E9pertoire d'ex\u00E9cution \: +disable=D\u00E9sactiver +distribution_graph_title=Graphique de distribution (alpha) +distribution_note1=Ce graphique se mettra \u00E0 jour tous les 10 \u00E9chantillons +dn=Racine DN \: +domain=Domaine \: +done=Fait +down=Descendre +duplicate=Dupliquer +duration=Dur\u00E9e (secondes) \: +duration_assertion_duration_test=Dur\u00E9e maximale \u00E0 v\u00E9rifier +duration_assertion_failure=L''op\u00E9ration a dur\u00E9e trop longtemps\: cela a pris {0} millisecondes, mais n''aurait pas d\u00FB durer plus de {1} millisecondes. +duration_assertion_input_error=Veuillez entrer un entier positif valide. +duration_assertion_label=Dur\u00E9e en millisecondes \: +duration_assertion_title=Assertion Dur\u00E9e +edit=Editer +email_results_title=R\u00E9sultat d'email +en=Anglais +enable=Activer +encode?=Encodage +encoded_value=Valeur de l'URL encod\u00E9e +endtime=Date et heure de fin \: +entry_dn=Entr\u00E9e DN \: +entrydn=Entr\u00E9e DN +environment_panel_title=Variables d'environnement +error_indicator_tooltip=Affiche le nombre d'erreurs dans le journal(log), cliquer pour afficher la console. +error_loading_help=Erreur au chargement de la page d'aide +error_occurred=Une erreur est survenue +error_title=Erreur +es=Espagnol +escape_html_string=Cha\u00EEne d'\u00E9chappement +eval_name_param=Variable contenant du texte et r\u00E9f\u00E9rences de fonctions +evalvar_name_param=Nom de variable +example_data=Exemple de donn\u00E9e +example_title=Echantillon exemple +exit=Quitter +expected_return_code_title=Code retour attendu \: +expiration=Expiration +field_name=Nom du champ +file=Fichier +file_already_in_use=Ce fichier est d\u00E9j\u00E0 utilis\u00E9 +file_visualizer_append=Concat\u00E9ner au fichier de donn\u00E9es existant +file_visualizer_auto_flush=Vider automatiquement apr\u00E8s chaque echantillon de donn\u00E9es +file_visualizer_browse=Parcourir... +file_visualizer_close=Fermer +file_visualizer_file_options=Options de fichier +file_visualizer_filename=Nom du fichier \: +file_visualizer_flush=Vider +file_visualizer_missing_filename=Aucun fichier de sortie sp\u00E9cifi\u00E9. +file_visualizer_open=Ouvrir... +file_visualizer_output_file=Ecrire les donn\u00E9es dans un fichier +file_visualizer_submit_data=Inclure les donn\u00E9es envoy\u00E9es +file_visualizer_title=Rapporteur de fichier +file_visualizer_verbose=Sortie verbeuse +filename=Nom de fichier \: +follow_redirects=Suivre les redirect. +follow_redirects_auto=Rediriger automat. +font.sansserif=Sans Serif +font.serif=Serif +fontstyle.bold=Gras +fontstyle.italic=Italique +fontstyle.normal=Normal +foreach_controller_title=Contr\u00F4leur Pour chaque (ForEach) +foreach_input=Pr\u00E9fixe de la variable d'entr\u00E9e \: +foreach_output=Nom de la variable de sortie \: +foreach_use_separator=Ajouter un soulign\u00E9 "_" avant le nombre ? +format=Format du nombre \: +fr=Fran\u00E7ais +ftp_binary_mode=Utiliser le mode binaire ? +ftp_get=R\u00E9cup\u00E9rer (get) +ftp_local_file=Fichier local \: +ftp_local_file_contents=Contenus fichier local \: +ftp_put=D\u00E9poser (put) +ftp_remote_file=Fichier distant \: +ftp_sample_title=Param\u00E8tres FTP par d\u00E9faut +ftp_save_response_data=Enregistrer le fichier dans la r\u00E9ponse ? +ftp_testing_title=Requ\u00EAte FTP +function_dialog_menu_item=Assistant de fonctions +function_helper_title=Assistant de fonctions +function_name_param=Nom de la fonction. Utilis\u00E9 pour stocker les valeurs \u00E0 utiliser ailleurs dans la plan de test +function_name_paropt=Nom de variable dans laquelle le r\u00E9sultat sera stock\u00E9 (optionnel) +function_params=Param\u00E8tres de la fonction +functional_mode=Mode de test fonctionnel +functional_mode_explanation=S\u00E9lectionner le mode de test fonctionnel uniquement si vous avez besoin\nd'enregistrer les donn\u00E9es re\u00E7ues du serveur dans un fichier \u00E0 chaque requ\u00EAte. \n\nS\u00E9lectionner cette option affecte consid\u00E9rablement les performances. +gaussian_timer_delay=D\u00E9lai de d\u00E9calage bas\u00E9 gaussian (en millisecondes) \: +gaussian_timer_memo=Ajoute un d\u00E9lai al\u00E9atoire avec une distribution gaussienne +gaussian_timer_range=D\u00E9viation (en millisecondes) \: +gaussian_timer_title=Compteur de temps al\u00E9atoire gaussien +generate=G\u00E9n\u00E9rer +generator=Nom de la classe g\u00E9n\u00E9ratrice +generator_cnf_msg=N'a pas p\u00FB trouver la classe g\u00E9n\u00E9ratrice. Assurez-vous que vous avez plac\u00E9 votre fichier jar dans le r\u00E9pertoire /lib +generator_illegal_msg=N'a pas p\u00FB acc\u00E9der \u00E0 la classes g\u00E9n\u00E9ratrice \u00E0 cause d'une IllegalAccessException. +generator_instantiate_msg=N'a pas p\u00FB cr\u00E9er une instance du parseur g\u00E9n\u00E9rateur. Assurez-vous que le g\u00E9n\u00E9rateur impl\u00E9mente l'interface Generator. +get_xml_from_file=Fichier avec les donn\u00E9es XML SOAP (remplace le texte ci-dessus) +get_xml_from_random=R\u00E9pertoire contenant les fichier(s) \: +graph_choose_graphs=Graphique \u00E0 afficher +graph_full_results_title=Graphique de r\u00E9sultats complets +graph_results_average=Moyenne +graph_results_data=Donn\u00E9es +graph_results_deviation=Ecart type +graph_results_latest_sample=Dernier \u00E9chantillon +graph_results_median=M\u00E9diane +graph_results_ms=ms +graph_results_no_samples=Nombre d'\u00E9chantillons +graph_results_throughput=D\u00E9bit +graph_results_title=Graphique de r\u00E9sultats +grouping_add_separators=Ajouter des s\u00E9parateurs entre les groupes +grouping_in_controllers=Mettre chaque groupe dans un nouveau contr\u00F4leur +grouping_in_transaction_controllers=Mettre chaque groupe dans un nouveau contr\u00F4leur de transaction +grouping_mode=Grouper \: +grouping_no_groups=Ne pas grouper les \u00E9chantillons +grouping_store_first_only=Stocker le 1er \u00E9chantillon pour chaque groupe uniquement +header_manager_title=Gestionnaire d'ent\u00EAtes HTTP +headers_stored=Ent\u00EAtes stock\u00E9es +help=Aide +help_node=Quel est ce noeud ? +html_assertion_file=Ecrire un rapport JTidy dans un fichier +html_assertion_label=Assertion HTML +html_assertion_title=Assertion HTML +html_parameter_mask=Masque de param\u00E8tre HTML +http_implementation=Impl\u00E9mentation \: +http_response_code=Code de r\u00E9ponse HTTP +http_url_rewriting_modifier_title=Transcripteur d'URL HTTP +http_user_parameter_modifier=Modificateur de param\u00E8tre utilisateur HTTP +httpmirror_max_pool_size=Taille maximum du pool d'unit\u00E9s \: +httpmirror_max_queue_size=Taille maximum de la file d'attente \: +httpmirror_settings=Param\u00E8tres +httpmirror_title=Serveur HTTP miroir +id_prefix=Pr\u00E9fixe d'ID +id_suffix=Suffixe d'ID +if_controller_evaluate_all=Evaluer pour tous les fils ? +if_controller_expression=Interpr\u00E9ter la condition comme une expression +if_controller_label=Condition (d\u00E9faut Javascript) \: +if_controller_title=Contr\u00F4leur Si (If) +ignore_subcontrollers=Ignorer les sous-blocs de contr\u00F4leurs +include_controller=Contr\u00F4leur Inclusion +include_equals=Inclure \u00E9gale ? +include_path=Plan de test \u00E0 inclure +increment=Incr\u00E9ment \: +infinite=Infini +initial_context_factory=Fabrique de contexte initiale +insert_after=Ins\u00E9rer apr\u00E8s +insert_before=Ins\u00E9rer avant +insert_parent=Ins\u00E9rer en tant que parent +interleave_control_title=Contr\u00F4leur Interleave +intsum_param_1=Premier entier \u00E0 ajouter +intsum_param_2=Deuxi\u00E8me entier \u00E0 ajouter - les entier(s) suivants peuvent \u00EAtre ajout\u00E9(s) avec les arguments suivants. +invalid_data=Donn\u00E9e invalide +invalid_mail=Une erreur est survenue lors de l'envoi de l'email +invalid_mail_address=Une ou plusieurs adresse(s) invalide(s) ont \u00E9t\u00E9 d\u00E9tect\u00E9e(s) +invalid_mail_server=Le serveur de mail est inconnu (voir le fichier de journalisation JMeter) +invalid_variables=Variables invalides +iteration_counter_arg_1=TRUE, pour que chaque utilisateur ait son propre compteur, FALSE pour un compteur global +iterator_num=Nombre d'it\u00E9rations \: +ja=Japonais +jar_file=Fichiers .jar +java_request=Requ\u00EAte Java +java_request_defaults=Requ\u00EAte Java par d\u00E9faut +javascript_expression=Expression JavaScript \u00E0 \u00E9valuer +jexl_expression=Expression JEXL \u00E0 \u00E9valuer +jms_auth_required=Obligatoire +jms_client_caption=Le client r\u00E9cepteur utilise MessageConsumer.receive () pour \u00E9couter les messages. +jms_client_caption2=MessageListener utilise l'interface onMessage(Message) pour \u00E9couter les nouveaux messages. +jms_client_id=ID du Client +jms_client_type=Client +jms_communication_style=Type de communication \: +jms_concrete_connection_factory=Fabrique de connexion +jms_config=Source du message \: +jms_config_title=Configuration JMS +jms_connection_factory=Fabrique de connexion +jms_correlation_title=Champs alternatifs pour la correspondance de message +jms_dest_setup=Evaluer +jms_dest_setup_dynamic=A chaque \u00E9chantillon +jms_dest_setup_static=Au d\u00E9marrage +jms_durable_subscription_id=ID d'abonnement durable +jms_file=Fichier +jms_initial_context_factory=Fabrique de connexion initiale +jms_itertions=Nombre d'\u00E9chantillons \u00E0 agr\u00E9ger +jms_jndi_defaults_title=Configuration JNDI par d\u00E9faut +jms_jndi_props=Propri\u00E9t\u00E9s JNDI +jms_map_message=Message Map +jms_message_title=Propri\u00E9t\u00E9s du message +jms_message_type=Type de message \: +jms_msg_content=Contenu +jms_object_message=Message Object +jms_point_to_point=Requ\u00EAte JMS Point-\u00E0-point +jms_props=Propri\u00E9t\u00E9s JMS +jms_provider_url=URL du fournisseur +jms_publisher=Requ\u00EAte JMS Publication +jms_pwd=Mot de passe +jms_queue=File +jms_queue_connection_factory=Fabrique QueueConnection +jms_queueing=Ressources JMS +jms_random_file=Fichier al\u00E9atoire +jms_read_response=Lire la r\u00E9ponse +jms_receive_queue=Nom JNDI de la file d'attente Receive +jms_request=Requ\u00EAte seule +jms_requestreply=Requ\u00EAte R\u00E9ponse +jms_sample_title=Requ\u00EAte JMS par d\u00E9faut +jms_selector=S\u00E9lecteur JMS +jms_send_queue=Nom JNDI de la file d'attente Request +jms_separator=S\u00E9parateur +jms_stop_between_samples=Arr\u00EAter entre les \u00E9chantillons ? +jms_subscriber_on_message=Utiliser MessageListener.onMessage() +jms_subscriber_receive=Utiliser MessageConsumer.receive() +jms_subscriber_title=Requ\u00EAte JMS Abonnement +jms_testing_title=Messagerie Request +jms_text_message=Message texte ou Message Objet s\u00E9rialis\u00E9 en XML par XStream +jms_timeout=D\u00E9lai (millisecondes) +jms_topic=Destination +jms_use_auth=Utiliser l'authentification ? +jms_use_file=Depuis un fichier +jms_use_non_persistent_delivery=Utiliser un mode de livraison non persistant ? +jms_use_properties_file=Utiliser le fichier jndi.properties +jms_use_random_file=Fichier al\u00E9atoire +jms_use_req_msgid_as_correlid=Utiliser l'ID du message Request +jms_use_res_msgid_as_correlid=Utiliser l'ID du message Response +jms_use_text=Zone de texte (ci-dessous) +jms_user=Utilisateur +jndi_config_title=Configuration JNDI +jndi_lookup_name=Interface remote +jndi_lookup_title=Configuration Lookup JNDI +jndi_method_button_invoke=Invoquer +jndi_method_button_reflect=R\u00E9flection +jndi_method_home_name=Nom de la m\u00E9thode home +jndi_method_home_parms=Param\u00E8tres de la m\u00E9thode home +jndi_method_name=Configuration m\u00E9thode +jndi_method_remote_interface_list=Interfaces remote +jndi_method_remote_name=Nom m\u00E9thodes remote +jndi_method_remote_parms=Param\u00E8tres m\u00E9thode remote +jndi_method_title=Configuration m\u00E9thode remote +jndi_testing_title=Requ\u00EAte JNDI +jndi_url_jndi_props=Propri\u00E9t\u00E9s JNDI +junit_append_error=Concat\u00E9ner les erreurs d'assertion +junit_append_exception=Concat\u00E9ner les exceptions d'ex\u00E9cution +junit_constructor_error=Impossible de cr\u00E9er une instance de la classe +junit_constructor_string=Libell\u00E9 de cha\u00EEne Constructeur +junit_create_instance_per_sample=Cr\u00E9er une nouvelle instance pour chaque \u00E9chantillon +junit_do_setup_teardown=Ne pas appeler setUp et tearDown +junit_error_code=Code d'erreur +junit_error_default_code=9999 +junit_error_default_msg=Une erreur inattendue est survenue +junit_error_msg=Message d'erreur +junit_failure_code=Code d'\u00E9chec +junit_failure_default_code=0001 +junit_failure_default_msg=Test \u00E9chou\u00E9 +junit_failure_msg=Message d'\u00E9chec +junit_junit4=Rechercher les annotations JUnit 4 (au lieu de JUnit 3) +junit_pkg_filter=Filtre de paquets +junit_request=Requ\u00EAte JUnit +junit_request_defaults=Requ\u00EAte par d\u00E9faut JUnit +junit_success_code=Code de succ\u00E8s +junit_success_default_code=1000 +junit_success_default_msg=Test r\u00E9ussi +junit_success_msg=Message de succ\u00E8s +junit_test_config=Param\u00E8tres Test JUnit +junit_test_method=M\u00E9thode de test +ldap_argument_list=Liste d'arguments LDAP +ldap_connto=D\u00E9lai d'attente de connexion (millisecondes) +ldap_parse_results=Examiner les r\u00E9sultats de recherche ? +ldap_sample_title=Requ\u00EAte LDAP par d\u00E9faut +ldap_search_baseobject=Effectuer une recherche 'baseobject' +ldap_search_onelevel=Effectuer une recherche 'onelevel' +ldap_search_subtree=Effectuer une recherche 'subtree' +ldap_secure=Utiliser le protocole LDAP s\u00E9curis\u00E9 (ldaps) ? +ldap_testing_title=Requ\u00EAte LDAP +ldapext_sample_title=Requ\u00EAte LDAP \u00E9tendue par d\u00E9faut +ldapext_testing_title=Requ\u00EAte LDAP \u00E9tendue +library=Librairie +load=Charger +load_wsdl=Charger WSDL +log_errors_only=Erreurs +log_file=Emplacement du fichier de journal (log) +log_function_comment=Commentaire (facultatif) +log_function_level=Niveau de journalisation (INFO par d\u00E9faut), OUT ou ERR +log_function_string=Cha\u00EEne \u00E0 tracer +log_function_string_ret=Cha\u00EEne \u00E0 tracer (et \u00E0 retourner) +log_function_throwable=Texte de l'exception Throwable (optionnel) +log_only=Uniquement \: +log_parser=Nom de la classe de parseur des journaux (log) +log_parser_cnf_msg=N'a pas p\u00FB trouver cette classe. Assurez-vous que vous avez plac\u00E9 votre fichier jar dans le r\u00E9pertoire /lib +log_parser_illegal_msg=N'a pas p\u00FB acc\u00E9der \u00E0 la classe \u00E0 cause d'une exception IllegalAccessException. +log_parser_instantiate_msg=N'a pas p\u00FB cr\u00E9er une instance du parseur de log. Assurez-vous que le parseur impl\u00E9mente l'interface LogParser. +log_sampler=Echantillon Journaux d'acc\u00E8s Tomcat +log_success_only=Succ\u00E8s +logic_controller_title=Contr\u00F4leur Simple +login_config=Configuration Identification +login_config_element=Configuration Identification +longsum_param_1=Premier long \u221A\u2020 ajouter +longsum_param_2=Second long \u221A\u2020 ajouter - les autres longs pourront \u221A\u2122tre cumul\u221A\u00A9s en ajoutant d'autres arguments. +loop_controller_title=Contr\u00F4leur Boucle +looping_control=Contr\u00F4le de boucle +lower_bound=Borne Inf\u00E9rieure +mail_reader_account=Nom utilisateur \: +mail_reader_all_messages=Tous +mail_reader_delete=Supprimer les messages du serveur +mail_reader_folder=Dossier \: +mail_reader_num_messages=Nombre de message \u00E0 r\u00E9cup\u00E9rer \: +mail_reader_password=Mot de passe \: +mail_reader_port=Port (optionnel) \: +mail_reader_server=Serveur \: +mail_reader_server_type=Protocole (ex. pop3, imaps) \: +mail_reader_storemime=Stocker le message en utilisant MIME (brut) +mail_reader_title=Echantillon Lecteur d'email +mail_sent=Email envoy\u00E9 avec succ\u00E8s +mailer_addressees=Destinataire(s) \: +mailer_attributes_panel=Attributs de courrier +mailer_connection_security=S\u00E9curit\u00E9 connexion \: +mailer_error=N'a pas p\u00FB envoyer l'email. Veuillez corriger les erreurs de saisie. +mailer_failure_limit=Limite d'\u00E9chec \: +mailer_failure_subject=Sujet Echec \: +mailer_failures=Nombre d'\u00E9checs \: +mailer_from=Exp\u00E9diteur \: +mailer_host=Serveur \: +mailer_login=Identifiant \: +mailer_msg_title_error=Erreur +mailer_msg_title_information=Information +mailer_password=Mot de passe \: +mailer_port=Port \: +mailer_string=Notification d'email +mailer_success_limit=Limite de succ\u00E8s \: +mailer_success_subject=Sujet Succ\u00E8s \: +mailer_test_mail=Tester email +mailer_title_message=Message +mailer_title_settings=Param\u00E8tres +mailer_title_smtpserver=Serveur SMTP +mailer_visualizer_title=R\u00E9cepteur Notification Email +match_num_field=R\u00E9cup\u00E9rer la Ni\u00E8me corresp. (0 \: Al\u00E9atoire) \: +max=Maximum \: +maximum_param=La valeur maximum autoris\u00E9e pour un \u00E9cart de valeurs +md5hex_assertion_failure=Erreur de v\u00E9rification de la somme MD5 \: obtenu {0} mais aurait d\u00FB \u00EAtre {1} +md5hex_assertion_label=MD5Hex +md5hex_assertion_md5hex_test=MD5Hex \u00E0 v\u00E9rifier +md5hex_assertion_title=Assertion MD5Hex +memory_cache=Cache de m\u00E9moire +menu_assertions=Assertions +menu_close=Fermer +menu_collapse_all=R\u00E9duire tout +menu_config_element=Configurations +menu_edit=Editer +menu_expand_all=Etendre tout +menu_fragments=Fragment d'\u00E9l\u00E9ments +menu_generative_controller=Echantillons +menu_listener=R\u00E9cepteurs +menu_logger_panel=Afficher la console +menu_logic_controller=Contr\u00F4leurs Logiques +menu_merge=Fusionner... +menu_modifiers=Modificateurs +menu_non_test_elements=El\u00E9ments hors test +menu_open=Ouvrir... +menu_post_processors=Post-Processeurs +menu_pre_processors=Pr\u00E9-Processeurs +menu_response_based_modifiers=Modificateurs bas\u00E9s sur la r\u00E9ponse +menu_search=Rechercher +menu_search_reset=Effacer la recherche +menu_tables=Table +menu_threads=Moteurs d'utilisateurs +menu_timer=Compteurs de temps +menu_toolbar=Barre d'outils +metadata=M\u00E9ta-donn\u00E9es +method=M\u00E9thode \: +mimetype=Type MIME +minimum_param=La valeur minimale autoris\u00E9e pour l'\u00E9cart de valeurs +minute=minute +modddn=Ancienne valeur +modification_controller_title=Contr\u00F4leur Modification +modification_manager_title=Gestionnaire Modification +modify_test=Modification +modtest=Modification +module_controller_module_to_run=Module \u00E0 ex\u00E9cuter \: +module_controller_title=Contr\u00F4leur Module +module_controller_warning=Ne peut pas trouver le module \: +monitor_equation_active=Activit\u00E9 \: (occup\u00E9e/max) > 25% +monitor_equation_dead=Mort \: pas de r\u00E9ponse +monitor_equation_healthy=Sant\u00E9 \: (occup\u00E9e/max) < 25% +monitor_equation_load=Charge \: ((occup\u00E9e / max) * 50) + ((m\u00E9moire utilis\u00E9e / m\u00E9moire maximum) * 50) +monitor_equation_warning=Attention \: (occup\u00E9/max) > 67% +monitor_health_tab_title=Sant\u00E9 +monitor_health_title=Moniteur de connecteurs +monitor_is_title=Utiliser comme moniteur +monitor_label_left_bottom=0 % +monitor_label_left_middle=50 % +monitor_label_left_top=100 % +monitor_label_prefix=Pr\u00E9fixe de connecteur \: +monitor_label_right_active=Actif +monitor_label_right_dead=Mort +monitor_label_right_healthy=Sant\u00E9 +monitor_label_right_warning=Attention +monitor_legend_health=Sant\u00E9 +monitor_legend_load=Charge +monitor_legend_memory_per=M\u00E9moire % (utilis\u00E9e/total) +monitor_legend_thread_per=Unit\u00E9 % (occup\u00E9/max) +monitor_load_factor_mem=50 +monitor_load_factor_thread=50 +monitor_performance_servers=Serveurs +monitor_performance_tab_title=Performance +monitor_performance_title=Graphique de performance +name=Nom \: +new=Nouveau +newdn=Nouveau DN +next=Suivant +no=Norv\u00E9gien +number_of_threads=Nombre d'unit\u00E9s (utilisateurs) \: +obsolete_test_element=Cet \u00E9l\u00E9ment de test est obsol\u00E8te +once_only_controller_title=Contr\u00F4leur Ex\u00E9cution unique +opcode=Code d'op\u00E9ration +open=Ouvrir... +option=Options +optional_tasks=T\u00E2ches optionnelles +paramtable=Envoyer les param\u00E8tres avec la requ\u00EAte \: +password=Mot de passe \: +paste=Coller +paste_insert=Coller ins\u00E9rer +path=Chemin \: +path_extension_choice=Extension de chemin (utiliser ";" comme separateur) +path_extension_dont_use_equals=Ne pas utiliser \u00E9gale dans l'extension de chemin (Compatibilit\u00E9 Intershop Enfinity) +path_extension_dont_use_questionmark=Ne pas utiliser le point d'interrogation dans l'extension du chemin (Compatiblit\u00E9 Intershop Enfinity) +patterns_to_exclude=URL \: motifs \u00E0 exclure +patterns_to_include=URL \: motifs \u00E0 inclure +pkcs12_desc=Clef PKCS 12 (*.p12) +pl=Polonais +poisson_timer_delay=D\u00E9lai de d\u00E9calage bas\u00E9 sur la loi de poisson (en millisecondes) \: +poisson_timer_memo=Ajoute un d\u00E9lai al\u00E9atoire avec une distribution de type Poisson +poisson_timer_range=D\u00E9viation (en millisecondes) \: +poisson_timer_title=Compteur de temps al\u00E9atoire selon la loi de Poisson +port=Port \: +post_as_parameters=Param\u00E8tres +post_body=Donn\u00E9es POST +post_body_raw=Donn\u00E9es POST brutes +post_thread_group_title=Groupe d'unit\u00E9s de fin +previous=Pr\u00E9c\u00E9dent +property_as_field_label={0}\: +property_default_param=Valeur par d\u00E9faut +property_edit=Editer +property_editor.value_is_invalid_message=Le texte que vous venez d'entrer n'a pas une valeur valide pour cette propri\u00E9t\u00E9.\nLa propri\u00E9t\u00E9 va revenir \u00E0 sa valeur pr\u00E9c\u00E9dente. +property_editor.value_is_invalid_title=Texte saisi invalide +property_name_param=Nom de la propri\u00E9t\u00E9 +property_returnvalue_param=Revenir \u00E0 la valeur originale de la propri\u00E9t\u00E9 (d\u00E9faut non) ? +property_tool_tip={0}\: {1} +property_undefined=Non d\u00E9fini +property_value_param=Valeur de propri\u00E9t\u00E9 +property_visualiser_title=Afficheur de propri\u00E9t\u00E9s +protocol=Protocole [http] \: +protocol_java_border=Classe Java +protocol_java_classname=Nom de classe \: +protocol_java_config_tile=Configurer \u00E9chantillon Java +protocol_java_test_title=Test Java +provider_url=Provider URL +proxy_assertions=Ajouter une Assertion R\u00E9ponse +proxy_cl_error=Si un serveur proxy est sp\u00E9cifi\u00E9, h\u00F4te et port doivent \u00EAtre donn\u00E9 +proxy_content_type_exclude=Exclure \: +proxy_content_type_filter=Filtre de type de contenu +proxy_content_type_include=Inclure \: +proxy_daemon_bind_error=Impossible de lancer le serveur proxy, le port est d\u00E9j\u00E0 utilis\u00E9. Choisissez un autre port. +proxy_daemon_error=Impossible de lancer le serveur proxy, voir le journal pour plus de d\u00E9tails +proxy_general_settings=Param\u00E8tres g\u00E9n\u00E9raux +proxy_headers=Capturer les ent\u00EAtes HTTP +proxy_httpsspoofing=Tenter d'usurper le HTTPS +proxy_httpsspoofing_match=Filtre d'URL pour usurpation HTTPS (regexp) \: +proxy_regex=Correspondance des variables par regex ? +proxy_sampler_settings=Param\u00E8tres Echantillon HTTP +proxy_sampler_type=Type \: +proxy_separators=Ajouter des s\u00E9parateurs +proxy_target=Contr\u00F4leur Cible \: +proxy_test_plan_content=Param\u00E8tres du plan de test +proxy_title=Serveur Proxy HTTP +pt_br=Portugais (Br\u00E9sil) +ramp_up=Dur\u00E9e de mont\u00E9e en charge (en secondes) \: +random_control_title=Contr\u00F4leur Al\u00E9atoire +random_order_control_title=Contr\u00F4leur d'Ordre al\u00E9atoire +random_string_chars_to_use=Caract\u00E8res \u00E0 utiliser pour la g\u00E9n\u00E9ration de la cha\u00EEne al\u00E9atoire +random_string_length=Longueur de cha\u00EEne al\u00E9atoire +read_response_message='Lire la r\u00E9ponse SOAP' n'est pas coch\u00E9. Pour voir la r\u00E9ponse, cocher la case dans la requ\u00EAte WebService svp. +read_response_note=Si 'Lire la r\u00E9ponse SOAP' n'est pas coch\u00E9, la requ\u00EAte WebService ne lira pas la r\u00E9ponse. +read_response_note2=et ne remplira pas l'objet SampleResult. Cela am\u00E9liore les performances, mais signifie que +read_response_note3=le contenu de la r\u00E9ponse ne sera pas tra\u00E7\u00E9. +read_soap_response=Lire la r\u00E9ponse SOAP +realm=Univers (realm) +record_controller_title=Contr\u00F4leur Enregistreur +ref_name_field=Nom de r\u00E9f\u00E9rence \: +regex_extractor_title=Extracteur Expression r\u00E9guli\u00E8re +regex_field=Expression r\u00E9guli\u00E8re \: +regex_source=Port\u00E9e +regex_src_body=Corps +regex_src_body_unescaped=Corps (non \u00E9chapp\u00E9) +regex_src_hdrs=Ent\u00EAtes +regex_src_url=URL +regexfunc_param_1=Expression r\u00E9guli\u00E8re utilis\u00E9e pour chercher les r\u00E9sultats de la requ\u00EAte pr\u00E9c\u00E9dente. +regexfunc_param_2=Canevas pour la ch\u00EEne de caract\u00E8re de remplacement, utilisant des groupes d'expressions r\u00E9guli\u00E8res. Le format est $[group]$. Exemple $1$. +regexfunc_param_3=Quelle correspondance utiliser. Un entier 1 ou plus grand, RAND pour indiquer que JMeter doit choisir al\u00E9atoirement , A d\u00E9cimal, ou ALL indique que toutes les correspondances doivent \u00EAtre utilis\u00E9es +regexfunc_param_4=Entre le texte. Si ALL est s\u00E9lectionn\u00E9, l'entre-texte sera utilis\u00E9 pour g\u00E9n\u00E9rer les r\u00E9sultats ([""]) +regexfunc_param_5=Text par d\u00E9faut. Utilis\u00E9 \u00E0 la place du canevas si l'expression r\u00E9guli\u00E8re ne trouve pas de correspondance +regexfunc_param_7=Variable en entr\u221A\u00A9e contenant le texte \u221A\u2020 parser ([\u221A\u00A9chantillon pr\u221A\u00A9c\u221A\u00A9dent]) +regexp_render_no_text=Les donn\u00E9es de r\u00E9ponse ne sont pas du texte. +regexp_tester_button_test=Tester +regexp_tester_field=Expression r\u00E9guli\u00E8re \: +regexp_tester_title=Testeur de RegExp +remote_error_init=Erreur lors de l'initialisation du serveur distant +remote_error_starting=Erreur lors du d\u221A\u00A9marrage du serveur distant +remote_exit=Sortie distante +remote_exit_all=Sortie distante de tous +remote_shut=Extinction \u00E0 distance +remote_shut_all=Extinction \u00E0 distance de tous +remote_start=D\u00E9marrage distant +remote_start_all=D\u00E9marrage distant de tous +remote_stop=Arr\u00EAt distant +remote_stop_all=Arr\u00EAt distant de tous +remove=Supprimer +remove_confirm_msg=Etes-vous s\u00FBr de vouloir supprimer ce(s) \u00E9l\u00E9ment(s) ? +remove_confirm_title=Confirmer la suppression ? +rename=Renommer une entr\u00E9e +report=Rapport +report_bar_chart=Graphique \u221A\u2020 barres +report_bar_graph_url=URL +report_base_directory=R\u221A\u00A9pertoire de Base +report_chart_caption=L\u221A\u00A9gende du graph +report_chart_x_axis=Axe X +report_chart_x_axis_label=Libell\u221A\u00A9 de l'Axe X +report_chart_y_axis=Axe Y +report_chart_y_axis_label=Libell\u221A\u00A9 de l'Axe Y +report_line_graph=Graphique Lin\u221A\u00A9aire +report_line_graph_urls=Inclure les URLs +report_output_directory=R\u221A\u00A9pertoire de sortie du rapport +report_page=Page de Rapport +report_page_element=Page Element +report_page_footer=Pied de page +report_page_header=Ent\u221A\u2122te de Page +report_page_index=Cr\u221A\u00A9er la Page d'Index +report_page_intro=Page d'Introduction +report_page_style_url=Url de la feuille de style +report_page_title=Titre de la Page +report_pie_chart=Camembert +report_plan=Plan du rapport +report_select=Selectionner +report_summary=Rapport r\u221A\u00A9sum\u221A\u00A9 +report_table=Table du Rapport +report_writer=R\u221A\u00A9dacteur du Rapport +report_writer_html=R\u221A\u00A9dacteur de rapport HTML +request_data=Donn\u00E9e requ\u00EAte +reset_gui=R\u00E9initialiser l'\u00E9l\u00E9ment +response_save_as_md5=R\u00E9ponse en empreinte MD5 +restart=Red\u00E9marrer +resultaction_title=Op\u00E9rateur R\u00E9sultats Action +resultsaver_addtimestamp=Ajouter un timestamp +resultsaver_errors=Enregistrer seulement les r\u00E9ponses en \u00E9checs +resultsaver_numberpadlen=Taille minimale du num\u00E9ro de s\u00E9quence +resultsaver_prefix=Pr\u00E9fixe du nom de fichier \: +resultsaver_skipautonumber=Ne pas ajouter de nombre au pr\u00E9fixe +resultsaver_skipsuffix=Ne pas ajouter de suffixe +resultsaver_success=Enregistrer seulement les r\u00E9ponses en succ\u00E8s +resultsaver_title=Sauvegarder les r\u00E9ponses vers un fichier +resultsaver_variable=Nom de variable \: +retobj=Retourner les objets +return_code_config_box_title=Configuration du code retour +reuseconnection=R\u00E9-utiliser la connexion +revert_project=Annuler les changements +revert_project?=Annuler les changements sur le projet ? +root=Racine +root_title=Racine +run=Lancer +running_test=Lancer test +runtime_controller_title=Contr\u00F4leur Dur\u00E9e d'ex\u00E9cution +runtime_seconds=Temps d'ex\u00E9cution (secondes) \: +sample_result_save_configuration=Sauvegarder la configuration de la sauvegarde des \u00E9chantillons +sample_scope=Appliquer sur +sample_scope_all=L'\u00E9chantillon et ses ressources li\u00E9es +sample_scope_children=Les ressources li\u00E9es +sample_scope_parent=L'\u00E9chantillon +sample_scope_variable=Une variable \: +sampler_label=Libell\u00E9 +sampler_on_error_action=Action \u00E0 suivre apr\u00E8s une erreur d'\u00E9chantillon +sampler_on_error_continue=Continuer +sampler_on_error_start_next_loop=D\u00E9marrer it\u00E9ration suivante +sampler_on_error_stop_test=Arr\u00EAter le test +sampler_on_error_stop_test_now=Arr\u00EAter le test imm\u00E9diatement +sampler_on_error_stop_thread=Arr\u00EAter l'unit\u00E9 +save=Enregistrer le plan de test +save?=Enregistrer ? +save_all_as=Enregistrer le plan de test sous... +save_as=Enregistrer sous... +save_as_error=Au moins un \u00E9l\u00E9ment doit \u00EAtre s\u00E9lectionn\u00E9 \! +save_as_image=Enregistrer en tant qu'image sous... +save_as_image_all=Enregistrer l'\u00E9cran en tant qu'image... +save_assertionresultsfailuremessage=Messages d'erreur des assertions +save_assertions=R\u00E9sultats des assertions (XML) +save_asxml=Enregistrer au format XML +save_bytes=Nombre d'octets +save_code=Code de r\u00E9ponse HTTP +save_datatype=Type de donn\u00E9es +save_encoding=Encodage +save_fieldnames=Libell\u00E9 des colonnes (CSV) +save_filename=Nom de fichier de r\u00E9ponse +save_graphics=Enregistrer le graphique +save_hostname=Nom d'h\u00F4te +save_idletime=Temps d'inactivit\u00E9 +save_label=Libell\u00E9 +save_latency=Latence +save_message=Message de r\u00E9ponse +save_overwrite_existing_file=Le fichier s\u00E9lectionn\u00E9 existe d\u00E9j\u00E0, voulez-vous l'\u00E9craser ? +save_requestheaders=Ent\u00EAtes de requ\u00EAte (XML) +save_responsedata=Donn\u00E9es de r\u00E9ponse (XML) +save_responseheaders=Ent\u00EAtes de r\u00E9ponse (XML) +save_samplecount=Nombre d'\u00E9chantillon et d'erreur +save_samplerdata=Donn\u00E9es d'\u00E9chantillon (XML) +save_subresults=Sous r\u00E9sultats (XML) +save_success=Succ\u00E8s +save_threadcounts=Nombre d'unit\u00E9s actives +save_threadname=Nom d'unit\u00E9 +save_time=Temps \u00E9coul\u00E9 +save_timestamp=Horodatage +save_url=URL +sbind=Simple connexion/d\u00E9connexion +scheduler=Programmateur de d\u00E9marrage +scheduler_configuration=Configuration du programmateur +scope=Port\u00E9e +search=Rechercher +search_base=Base de recherche +search_filter=Filtre de recherche +search_test=Recherche +search_text_button_close=Fermer +search_text_button_find=Rechercher +search_text_button_next=Suivant +search_text_chkbox_case=Consid\u00E9rer la casse +search_text_chkbox_regexp=Exp. reguli\u00E8re +search_text_field=Rechercher \: +search_text_msg_not_found=Texte non trouv\u00E9 +search_text_title_not_found=Pas trouv\u00E9 +search_tree_title=Rechercher dans l'arbre +searchbase=Base de recherche +searchfilter=Filtre de recherche +searchtest=Recherche +second=seconde +secure=S\u00E9curis\u00E9 \: +send_file=Envoyer un fichier avec la requ\u00EAte \: +send_file_browse=Parcourir... +send_file_filename_label=Chemin du fichier \: +send_file_mime_label=Type MIME \: +send_file_param_name_label=Nom du param\u00E8tre \: +server=Nom ou IP du serveur \: +servername=Nom du serveur \: +session_argument_name=Nom des arguments de la session +setup_thread_group_title=Groupe d'unit\u00E9s de d\u00E9but +should_save=Vous devez enregistrer le plan de test avant de le lancer. \nSi vous utilisez des fichiers de donn\u00E9es (i.e. Source de donn\u00E9es CSV ou la fonction _StringFromFile), \nalors c'est particuli\u00E8rement important d'enregistrer d'abord votre script de test. \nVoulez-vous enregistrer maintenant votre plan de test ? +shutdown=Eteindre +simple_config_element=Configuration Simple +simple_data_writer_title=Enregistreur de donn\u00E9es +size_assertion_comparator_error_equal=est \u00E9gale \u00E0 +size_assertion_comparator_error_greater=est plus grand que +size_assertion_comparator_error_greaterequal=est plus grand ou \u00E9gale \u00E0 +size_assertion_comparator_error_less=est inf\u00E9rieur \u00E0 +size_assertion_comparator_error_lessequal=est inf\u00E9rieur ou \u00E9gale \u00E0 +size_assertion_comparator_error_notequal=n'est pas \u00E9gale \u00E0 +size_assertion_comparator_label=Type de comparaison +size_assertion_failure=Le r\u00E9sultat n''a pas la bonne taille \: il \u00E9tait de {0} octet(s), mais aurait d\u00FB \u00EAtre de {1} {2} octet(s). +size_assertion_input_error=Entrer un entier positif valide svp. +size_assertion_label=Taille en octets \: +size_assertion_size_test=Taille \u00E0 v\u00E9rifier +size_assertion_title=Assertion Taille +smime_assertion_issuer_dn=Nom unique de l'\u00E9metteur \: +smime_assertion_message_position=V\u00E9rifier l'assertion sur le message \u00E0 partir de la position +smime_assertion_not_signed=Message non sign\u00E9 +smime_assertion_signature=Signature +smime_assertion_signer=Certificat signataire +smime_assertion_signer_by_file=Fichier du certificat \: +smime_assertion_signer_constraints=V\u00E9rifier les valeurs \: +smime_assertion_signer_dn=Nom unique du signataire \: +smime_assertion_signer_email=Adresse courriel du signataire \: +smime_assertion_signer_no_check=Pas de v\u00E9rification +smime_assertion_signer_serial=Num\u00E9ro de s\u00E9rie \: +smime_assertion_title=Assertion SMIME +smime_assertion_verify_signature=V\u00E9rifier la signature +smtp_additional_settings=Param\u00E8tres suppl\u00E9mentaires +smtp_attach_file=Fichier(s) attach\u00E9(s) \: +smtp_attach_file_tooltip=S\u00E9parer les fichiers par le point-virgule ";" +smtp_auth_settings=Param\u00E8tres d'authentification +smtp_bcc=Adresse en copie cach\u00E9e (Bcc) \: +smtp_cc=Adresse en copie (CC) \: +smtp_default_port=(D\u00E9fauts \: SMTP \: 25, SSL \: 465, StartTLS \: 587) +smtp_eml=Envoyer un message .eml \: +smtp_enabledebug=Activer les traces de d\u00E9bogage ? +smtp_enforcestarttls=Forcer le StartTLS +smtp_enforcestarttls_tooltip=Force le serveur a utiliser StartTLS.
Si il n'est pas s\u00E9lectionn\u00E9 et que le serveur SMTP ne supporte pas StartTLS,
une connexion SMTP normale sera utilis\u00E9e \u00E0 la place.
Merci de noter que la case \u00E0 cocher cr\u00E9\u00E9e un fichier dans /tmp/,
donc cela peut poser des probl\u00E8mes sous Windows. +smtp_from=Adresse exp\u00E9diteur (From) \: +smtp_header_add=Ajouter une ent\u00EAte +smtp_header_name=Nom d'ent\u00EAte +smtp_header_remove=Supprimer +smtp_header_value=Valeur d'ent\u00EAte +smtp_mail_settings=Param\u00E8tres du courriel +smtp_message=Message \: +smtp_message_settings=Param\u00E8tres du message +smtp_messagesize=Calculer la taille du message +smtp_password=Mot de passe \: +smtp_plainbody=Envoyer le message en texte (i.e. sans multipart/mixed) +smtp_replyto=Adresse de r\u00E9ponse (Reply-To) \: +smtp_sampler_title=Requ\u00EAte SMTP +smtp_security_settings=Param\u00E8tres de s\u00E9curit\u00E9 +smtp_server=Serveur \: +smtp_server_port=Port \: +smtp_server_settings=Param\u00E8tres du serveur +smtp_subject=Sujet \: +smtp_suppresssubj=Supprimer l'ent\u00EAte Sujet (Subject) +smtp_timestamp=Ajouter un horodatage dans le sujet +smtp_to=Adresse destinataire (To) \: +smtp_trustall=Faire confiance \u00E0 tous les certificats +smtp_trustall_tooltip=Forcer JMeter \u00E0 faire confiance \u00E0 tous les certificats, quelque soit l'autorit\u00E9 de certification du certificat. +smtp_truststore=Coffre de cl\u00E9s local \: +smtp_truststore_tooltip=Le chemin du coffre de confiance.
Les chemins relatifs sont d\u00E9termin\u00E9s \u00E0 partir du r\u00E9pertoire courant.
En cas d'\u00E9chec, c'est le r\u00E9pertoire contenant le script JMX qui est utilis\u00E9. +smtp_useauth=Utiliser l'authentification +smtp_usenone=Pas de fonctionnalit\u00E9 de s\u00E9curit\u00E9 +smtp_username=Identifiant \: +smtp_usessl=Utiliser SSL +smtp_usestarttls=Utiliser StartTLS +smtp_usetruststore=Utiliser le coffre de confiance local +smtp_usetruststore_tooltip=Autoriser JMeter \u00E0 utiliser le coffre de confiance local. +soap_action=Action Soap +soap_data_title=Donn\u00E9es Soap/XML-RPC +soap_sampler_file_invalid=Le nom de fichier r\u00E9f\u00E9rence un fichier absent ou sans droits de lecture\: +soap_sampler_title=Requ\u00EAte SOAP/XML-RPC +soap_send_action=Envoyer l'action SOAP \: +spline_visualizer_average=Moyenne \: +spline_visualizer_incoming=Entr\u00E9e \: +spline_visualizer_maximum=Maximum \: +spline_visualizer_minimum=Minimum \: +spline_visualizer_title=Moniteur de courbe (spline) +spline_visualizer_waitingmessage=En attente de r\u00E9sultats d'\u00E9chantillons +split_function_separator=S\u00E9parateur utilis\u00E9 pour scinder le texte. Par d\u00E9faut , (virgule) est utilis\u00E9. +split_function_string=Texte \u00E0 scinder +ssl_alias_prompt=Veuillez entrer votre alias pr\u00E9f\u00E9r\u00E9 +ssl_alias_select=S\u00E9lectionner votre alias pour le test +ssl_alias_title=Alias du client +ssl_error_title=Probl\u00E8me de KeyStore +ssl_pass_prompt=Entrer votre mot de passe +ssl_pass_title=Mot de passe KeyStore +ssl_port=Port SSL +sslmanager=Gestionnaire SSL +start=Lancer +start_no_timers=Lancer sans pauses +starttime=Date et heure de d\u00E9marrage \: +stop=Arr\u00EAter +stopping_test=En train d'\u00E9teindre toutes les unit\u00E9s de tests. Soyez patient, merci. +stopping_test_failed=Au moins une unit\u00E9 non arr\u00EAt\u00E9e; voir le journal. +stopping_test_title=En train d'arr\u00EAter le test +string_from_file_encoding=Encodage du fichier (optionnel) +string_from_file_file_name=Entrer le chemin (absolu ou relatif) du fichier +string_from_file_seq_final=Nombre final de s\u00E9quence de fichier +string_from_file_seq_start=D\u00E9marer le nombre de s\u00E9quence de fichier +summariser_title=G\u00E9n\u00E9rer les resultats consolid\u00E9s +summary_report=Rapport consolid\u00E9 +switch_controller_label=Aller vers le num\u00E9ro d'\u00E9l\u00E9ment (ou nom) subordonn\u00E9 \: +switch_controller_title=Contr\u00F4leur Aller \u00E0 +system_sampler_title=Appel de processus syst\u00E8me +table_visualizer_bytes=Octets +table_visualizer_sample_num=Echantillon \# +table_visualizer_sample_time=Temps (ms) +table_visualizer_start_time=Heure d\u00E9but +table_visualizer_status=Statut +table_visualizer_success=Succ\u00E8s +table_visualizer_thread_name=Nom d'unit\u00E9 +table_visualizer_warning=Alerte +target_server=Serveur cible +tcp_classname=Nom de classe TCPClient \: +tcp_config_title=Param\u00E8tres TCP par d\u00E9faut +tcp_nodelay=D\u00E9finir aucun d\u00E9lai (NoDelay) +tcp_port=Num\u00E9ro de port \: +tcp_request_data=Texte \u00E0 envoyer \: +tcp_sample_title=Requ\u00EAte TCP +tcp_timeout=Expiration (millisecondes) \: +template_field=Canevas \: +test=Test +test_action_action=Action \: +test_action_duration=Dur\u00E9e (millisecondes) \: +test_action_pause=Mettre en pause +test_action_restart_next_loop=Passer \u00E0 l'it\u00E9ration suivante de la boucle +test_action_stop=Arr\u00EAter +test_action_stop_now=Arr\u00EAter imm\u00E9diatement +test_action_target=Cible \: +test_action_target_test=Toutes les unit\u00E9s +test_action_target_thread=Unit\u00E9 courante +test_action_title=Action test +test_configuration=Type de test +test_fragment_title=Fragment d'\u00E9l\u00E9ments +test_plan=Plan de test +test_plan_classpath_browse=Ajouter un r\u00E9pertoire ou un fichier 'jar' au 'classpath' +testconfiguration=Tester la configuration +testplan.serialized=Lancer les groupes d'unit\u00E9s en s\u00E9rie (c'est-\u00E0-dire \: lance un groupe \u00E0 la fois) +testplan_comments=Commentaires \: +testt=Test +textbox_cancel=Annuler +textbox_close=Fermer +textbox_save_close=Enregistrer & Fermer +textbox_title_edit=Editer texte +textbox_title_view=Voir texte +textbox_tooltip_cell=Double clic pour voir/editer +thread_delay_properties=Propri\u00E9t\u00E9s de temporisation de l'unit\u00E9 +thread_group_title=Groupe d'unit\u00E9s +thread_properties=Propri\u00E9t\u00E9s du groupe d'unit\u00E9s +threadgroup=Groupe d'unit\u00E9s +throughput_control_bynumber_label=Ex\u00E9cutions totales +throughput_control_bypercent_label=Pourcentage d'ex\u00E9cution +throughput_control_perthread_label=Par utilisateur +throughput_control_title=Contr\u00F4leur D\u00E9bit +throughput_control_tplabel=D\u00E9bit \: +time_format=Chaine de formatage sur le mod\u00E8le SimpleDateFormat (optionnel) +timelim=Limiter le temps de r\u00E9ponses \u00E0 (ms) +toggle=Permuter +toolbar_icon_set_not_found=Le fichier de description des ic\u00F4nes de la barre d'outils n'est pas trouv\u00E9. Voir les journaux. +tr=Turc +transaction_controller_include_timers=Inclure la dur\u00E9e des compteurs de temps et pre/post processeurs dans le calcul du temps +transaction_controller_parent=G\u00E9n\u00E9rer en \u00E9chantillon parent +transaction_controller_title=Contr\u00F4leur Transaction +unbind=D\u00E9connexion de l'unit\u00E9 +unescape_html_string=Cha\u00EEne \u00E0 \u00E9chapper +unescape_string=Cha\u00EEne de caract\u00E8res contenant des\u00E9chappements Java +uniform_timer_delay=D\u00E9lai de d\u00E9calage constant (en millisecondes) \: +uniform_timer_memo=Ajoute un d\u00E9lai al\u00E9atoire avec une distribution uniforme +uniform_timer_range=D\u00E9viation al\u00E9atoire maximum (en millisecondes) \: +uniform_timer_title=Compteur de temps al\u00E9atoire uniforme +up=Monter +update=Mettre \u00E0 jour +update_per_iter=Mettre \u00E0 jour une fois par it\u00E9ration +upload=Fichier \u00E0 uploader +upper_bound=Borne sup\u00E9rieure +url=URL +url_config_get=GET +url_config_http=HTTP +url_config_https=HTTPS +url_config_post=POST +url_config_protocol=Protocole \: +url_config_title=Param\u00E8tres HTTP par d\u00E9faut +url_full_config_title=Echantillon d'URL complet +url_multipart_config_title=Requ\u00EAte HTTP Multipart par d\u00E9faut +use_expires=Utiliser les ent\u00EAtes Cache-Control/Expires lors du traitement des requ\u00EAtes GET +use_keepalive=Connexion persist. +use_multipart_for_http_post=Multipart/form-data +use_multipart_mode_browser=Ent\u00EAtes compat. navigateur +use_recording_controller=Utiliser un contr\u00F4leur enregistreur +user=Utilisateur +user_defined_test=Test d\u00E9fini par l'utilisateur +user_defined_variables=Variables pr\u00E9-d\u00E9finies +user_param_mod_help_note=(Ne pas changer. A la place, modifier le fichier de ce nom dans le r\u00E9pertoire /bin de JMeter) +user_parameters_table=Param\u00E8tres +user_parameters_title=Param\u00E8tres Utilisateur +userdn=Identifiant +username=Nom d'utilisateur \: +userpw=Mot de passe +value=Valeur \: +var_name=Nom de r\u00E9f\u00E9rence \: +variable_name_param=Nom de variable (peut inclure une r\u00E9f\u00E9rence de variable ou fonction) +view_graph_tree_title=Voir le graphique en arbre +view_results_assertion_error=Erreur d'assertion \: +view_results_assertion_failure=Echec d'assertion \: +view_results_assertion_failure_message=Message d'\u00E9chec d'assertion \: +view_results_autoscroll=D\u00E9filement automatique ? +view_results_childsamples=Echantillons enfants? +view_results_desc=Affiche les r\u00E9sultats d'un \u00E9chantillon dans un arbre de r\u00E9sultats +view_results_error_count=Compteur erreur\: +view_results_fields=champs \: +view_results_in_table=Tableau de r\u00E9sultats +view_results_latency=Latence \: +view_results_load_time=Temps de r\u00E9ponse \: +view_results_render=Rendu \: +view_results_render_html=HTML +view_results_render_html_embedded=HTML et ressources +view_results_render_json=JSON +view_results_render_text=Texte brut +view_results_render_xml=XML +view_results_request_headers=Ent\u00EAtes de requ\u00EAte \: +view_results_response_code=Code de retour \: +view_results_response_headers=Ent\u00EAtes de r\u00E9ponse \: +view_results_response_message=Message de retour \: +view_results_response_partial_message=D\u00E9but du message\: +view_results_response_too_large_message=R\u00E9ponse d\u00E9passant la taille maximale d'affichage. Taille \: +view_results_sample_count=Compteur \u00E9chantillon \: +view_results_sample_start=Date d\u00E9but \u00E9chantillon \: +view_results_search_pane=Volet recherche +view_results_size_body_in_bytes=Taille du corps en octets \: +view_results_size_headers_in_bytes=Taille de l'ent\u00EAte en octets \: +view_results_size_in_bytes=Taille en octets \: +view_results_tab_assertion=R\u00E9sultats d'assertion +view_results_tab_request=Requ\u00EAte +view_results_tab_response=Donn\u00E9es de r\u00E9ponse +view_results_tab_sampler=R\u00E9sultat de l'\u00E9chantillon +view_results_table_fields_key=Champ suppl\u00E9mentaire +view_results_table_fields_value=Valeur +view_results_table_headers_key=Ent\u00EAtes de r\u00E9ponse +view_results_table_headers_value=Valeur +view_results_table_request_headers_key=Ent\u00EAtes de requ\u00EAte +view_results_table_request_headers_value=Valeur +view_results_table_request_http_cookie=Cookie +view_results_table_request_http_host=H\u00F4te +view_results_table_request_http_method=M\u00E9thode +view_results_table_request_http_nohttp=N'est pas un \u00E9chantillon HTTP +view_results_table_request_http_path=Chemin +view_results_table_request_http_port=Port +view_results_table_request_http_protocol=Protocole +view_results_table_request_params_key=Nom de param\u00E8tre +view_results_table_request_params_value=Valeur +view_results_table_request_raw_nodata=Pas de donn\u00E9es \u00E0 afficher +view_results_table_request_tab_http=HTTP +view_results_table_request_tab_raw=Brut +view_results_table_result_tab_parsed=D\u00E9cod\u00E9 +view_results_table_result_tab_raw=Brut +view_results_thread_name=Nom d'unit\u00E9 \: +view_results_title=Voir les r\u00E9sultats +view_results_tree_title=Arbre de r\u00E9sultats +warning=Attention \! +web_cannot_convert_parameters_to_raw=Ne peut pas convertir les param\u00E8tres en Donn\u00E9es POST brutes\ncar l'un des param\u00E8tres a un nom. +web_cannot_switch_tab=Vous ne pouvez pas basculer car ces donn\u00E9es ne peuvent \u00EAtre converties.\nVider les donn\u00E9es pour basculer. +web_parameters_lost_message=Basculer vers les Donn\u00E9es POST brutes va convertir en format brut\net perdre le format tabulaire quand vous s\u00E9lectionnerez un autre noeud\nou \u00E0 la sauvegarde du plan de test, \u00EAtes-vous s\u00FBr ? +web_proxy_server_title=Requ\u00EAte via un serveur proxy +web_request=Requ\u00EAte HTTP +web_server=Serveur web +web_server_client=Impl\u00E9mentation client \: +web_server_domain=Nom ou adresse IP \: +web_server_port=Port \: +web_server_timeout_connect=Connexion \: +web_server_timeout_response=R\u00E9ponse \: +web_server_timeout_title=D\u00E9lai expiration (ms) +web_testing2_source_ip=Adresse IP source \: +web_testing2_title=Requ\u00EAte HTTP HTTPClient +web_testing_concurrent_download=Utiliser pool unit\u00E9. Nbre \: +web_testing_embedded_url_pattern=Les URL \u00E0 inclure doivent correspondre \u00E0 \: +web_testing_retrieve_images=R\u00E9cup\u00E9rer les ressources incluses +web_testing_title=Requ\u00EAte HTTP +webservice_configuration_wizard=Assistant de configuration WSDL +webservice_get_xml_from_random_title=Utiliser al\u00E9atoirement des messages SOAP +webservice_maintain_session=Maintenir la Session HTTP +webservice_message_soap=Message WebService +webservice_methods=M\u00E9thode(s) WebService \: +webservice_proxy_host=H\u00F4te proxy +webservice_proxy_note=Si 'utiliser un proxy HTTP' est coch\u00E9e, mais qu'aucun h\u00F4te ou port est fournit, l'\u00E9chantillon +webservice_proxy_note2=regardera les options de ligne de commandes. Si aucun h\u00F4te ou port du proxy sont fournit +webservice_proxy_note3=non plus, il \u00E9chouera silencieusement. +webservice_proxy_port=Port proxy +webservice_sampler_title=Requ\u00EAte WebService (SOAP) +webservice_soap_action=Action SOAP \: +webservice_timeout=D\u00E9lai expiration \: +webservice_use_proxy=Utiliser un proxy HTTP +while_controller_label=Condition (fonction ou variable) \: +while_controller_title=Contr\u00F4leur Tant Que +workbench_title=Plan de travail +wsdl_helper_error=Le WSDL n'est pas valide, veuillez rev\u00E9rifier l'URL. +wsdl_url=URL du WSDL +wsdl_url_error=Le WSDL est vide. +xml_assertion_title=Assertion XML +xml_download_dtds=R\u00E9cup\u00E9rer les DTD externes +xml_namespace_button=Utiliser les espaces de noms +xml_tolerant_button=Utiliser Tidy (analyseur tol\u00E9rant) +xml_validate_button=Validation XML +xml_whitespace_button=Ignorer les espaces +xmlschema_assertion_label=Nom de fichier \: +xmlschema_assertion_title=Assertion Sch\u00E9ma XML +xpath_assertion_button=Valider +xpath_assertion_check=V\u00E9rifier l'expression XPath +xpath_assertion_error=Erreur avec XPath +xpath_assertion_failed=Expression XPath invalide +xpath_assertion_label=XPath +xpath_assertion_negate=Vrai si aucune correspondance trouv\u00E9e +xpath_assertion_option=Options d'analyse XML +xpath_assertion_test=V\u00E9rificateur XPath +xpath_assertion_tidy=Essayer et nettoyer l'entr\u00E9e +xpath_assertion_title=Assertion XPath +xpath_assertion_valid=Expression XPath valide +xpath_assertion_validation=Valider le code XML \u00E0 travers le fichier DTD +xpath_assertion_whitespace=Ignorer les espaces +xpath_expression=Expression XPath de correspondance +xpath_extractor_fragment=Retourner le fragment XPath entier au lieu du contenu +xpath_extractor_query=Requ\u00EAte XPath \: +xpath_extractor_title=Extracteur XPath +xpath_file_file_name=Fichier XML contenant les valeurs +xpath_tidy_quiet=Silencieux +xpath_tidy_report_errors=Rapporter les erreurs +xpath_tidy_show_warnings=Afficher les alertes +you_must_enter_a_valid_number=Vous devez entrer un nombre valide +zh_cn=Chinois (simplifi\u00E9) +zh_tw=Chinois (traditionnel) diff --git a/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_ja.properties b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_ja.properties new file mode 100644 index 0000000..525cfe0 --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_ja.properties @@ -0,0 +1,480 @@ +# 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. + +#Stored by I18NEdit, may be edited! +about=Apache JMeter \u306B\u3064\u3044\u3066 +add=\u8FFD\u52A0 +add_as_child=\u5B50\u3068\u3057\u3066\u8FFD\u52A0 +add_parameter=\u5909\u6570\u306E\u8FFD\u52A0 +add_pattern=\u30D1\u30BF\u30FC\u30F3\u8FFD\u52A0\: +add_test=\u30C6\u30B9\u30C8\u306E\u8FFD\u52A0 +add_user=\u30E6\u30FC\u30B6\u30FC\u306E\u8FFD\u52A0 +add_value=\u5024\u306E\u8FFD\u52A0 +aggregate_report=\u7D71\u8A08\u30EC\u30DD\u30FC\u30C8 +aggregate_report_total_label=\u5408\u8A08 +als_message=\u6CE8\u610F\: \u30A2\u30AF\u30BB\u30B9\u30ED\u30B0\u30D1\u30FC\u30B5\u306F\u6C4E\u7528\u7684\u306B\u8A2D\u8A08\u3055\u308C\u3066\u3044\u308B\u306E\u3067\u3001\u72EC\u81EA\u30D1\u30FC\u30B5\u3092 +als_message2=\u30D7\u30E9\u30B0\u30A4\u30F3\u53EF\u80FD\u3067\u3059\u3002\u305D\u306E\u305F\u3081\u306B\u306F\u3001LogParser\u3092\u5B9F\u88C5\u3057\u3066/lib\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B +als_message3=jar\u30D5\u30A1\u30A4\u30EB\u3092\u8FFD\u52A0\u3057\u3001\u30B5\u30F3\u30D7\u30E9\u30FC\u3067\u30AF\u30E9\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +analyze=\u30C7\u30FC\u30BF\u30D5\u30A1\u30A4\u30EB\u3092\u5206\u6790... +anchor_modifier_title=HTML \u30EA\u30F3\u30AF\u30D1\u30FC\u30B5 +appearance=\u30EB\u30C3\u30AF&\u30D5\u30A3\u30FC\u30EB +argument_must_not_be_negative=\u5F15\u6570\u306F\u8CA0\u306E\u5024\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093\uFF01 +assertion_code_resp=\u5FDC\u7B54\u30B3\u30FC\u30C9 +assertion_contains=\u542B\u3080 +assertion_matches=\u4E00\u81F4\u3059\u308B +assertion_message_resp=\u5FDC\u7B54\u30E1\u30C3\u30BB\u30FC\u30B8 +assertion_not=\u5426\u5B9A +assertion_pattern_match_rules=\u30D1\u30BF\u30FC\u30F3\u30DE\u30C3\u30C1\u30F3\u30B0\u30EB\u30FC\u30EB +assertion_patterns_to_test=\u30C6\u30B9\u30C8\u30D1\u30BF\u30FC\u30F3 +assertion_resp_field=\u30C6\u30B9\u30C8\u3059\u308B\u30EC\u30B9\u30DD\u30F3\u30B9\u30D5\u30A3\u30FC\u30EB\u30C9 +assertion_text_resp=\u30C6\u30AD\u30B9\u30C8\u306E\u30EC\u30B9\u30DD\u30F3\u30B9 +assertion_textarea_label=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3\: +assertion_title=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +assertion_url_samp=\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u3055\u308C\u305F URL +assertion_visualizer_title=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 \u7D50\u679C +auth_base_url=\u57FA\u5E95URL +auth_manager_title=HTTP \u8A8D\u8A3C\u30DE\u30CD\u30FC\u30B8\u30E3 +auths_stored=\u8A8D\u8A3C\u30DE\u30CD\u30FC\u30B8\u30E3\u306B\u4FDD\u5B58\u3055\u308C\u3066\u3044\u308B\u8A8D\u8A3C +browse=\u53C2\u7167... +bsf_sampler_title=BSF\u30B5\u30F3\u30D7\u30E9\u30FC +bsf_script=\u5B9F\u884C\u3059\u308B\u30B9\u30AF\u30EA\u30D7\u30C8 +bsf_script_file=\u5B9F\u884C\u3059\u308B\u30B9\u30AF\u30EA\u30D7\u30C8\u30D5\u30A1\u30A4\u30EB +bsf_script_language=\u30B9\u30AF\u30EA\u30D7\u30C8\u8A00\u8A9E\: +bsf_script_parameters=\u30B9\u30AF\u30EA\u30D7\u30C8/\u30D5\u30A1\u30A4\u30EB\u3078\u6E21\u3059\u30D1\u30E9\u30E1\u30FC\u30BF\: +bsh_assertion_script=\u30B9\u30AF\u30EA\u30D7\u30C8(Response[Data|Code|Message|Headers], RequestHeaders, Sample[Label|rData], Result, Failure[Message]) +bsh_assertion_title=BeanShell\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +bsh_function_expression=\u8A55\u4FA1\u5BFE\u8C61\u306E\u5F0F +bsh_sampler_title=BeanShell\u30B5\u30F3\u30D7\u30E9\u30FC +bsh_script=\u30B9\u30AF\u30EA\u30D7\u30C8 (variables\: SampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName) +bsh_script_file=\u30B9\u30AF\u30EA\u30D7\u30C8\u30D5\u30A1\u30A4\u30EB +bsh_script_parameters=\u30D1\u30E9\u30E1\u30FC\u30BF\uFF08-> String Parameters and String []bsh.args\uFF09 +busy_testing=\u73FE\u5728\u30C6\u30B9\u30C8\u4E2D\u3067\u3059\u3002\u8A2D\u5B9A\u5909\u66F4\u306E\u524D\u306B\u30C6\u30B9\u30C8\u3092\u505C\u6B62\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +cancel=\u30AD\u30E3\u30F3\u30BB\u30EB +cancel_exit_to_save=\u4FDD\u5B58\u3055\u308C\u3066\u3044\u306A\u3044\u30C6\u30B9\u30C8\u9805\u76EE\u304C\u3042\u308A\u307E\u3059\u3002\u7D42\u4E86\u3059\u308B\u524D\u306B\u4FDD\u5B58\u3057\u307E\u3059\u304B\uFF1F +cancel_new_to_save=\u4FDD\u5B58\u3055\u308C\u3066\u3044\u306A\u3044\u30C6\u30B9\u30C8\u9805\u76EE\u304C\u3042\u308A\u307E\u3059\u3002\u30C6\u30B9\u30C8\u8A08\u753B\u3092\u6D88\u53BB\u3059\u308B\u524D\u306B\u4FDD\u5B58\u3057\u307E\u3059\u304B\uFF1F +choose_function=\u95A2\u6570\u306E\u9078\u629E +choose_language=\u8A00\u8A9E\u306E\u9078\u629E +clear=\u6D88\u53BB +clear_all=\u5168\u3066\u6D88\u53BB +clear_cookies_per_iter=\u7E70\u308A\u8FD4\u3057\u3054\u3068\u306B\u30AF\u30C3\u30AD\u30FC\u3092\u7834\u68C4\u3057\u307E\u3059\u304B\uFF1F +column_delete_disallowed=\u3053\u306E\u30AB\u30E9\u30E0\u306E\u524A\u9664\u6A29\u9650\u304C\u3042\u308A\u307E\u305B\u3093 +column_number=CSV\u30D5\u30A1\u30A4\u30EB\u306E\u30AB\u30E9\u30E0\u756A\u53F7 +config_element=\u8A2D\u5B9A\u30A8\u30EC\u30E1\u30F3\u30C8 +configure_wsdl=\u8A2D\u5B9A +constant_throughput_timer_memo=\u4E00\u5B9A\u306E\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8\u306B\u5230\u9054\u3057\u305F\u3089\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u9593\u306B\u9045\u5EF6\u3092\u8FFD\u52A0 +constant_timer_delay=\u30B9\u30EC\u30C3\u30C9\u9045\u5EF6\u6642\u9593 (\u30DF\u30EA\u79D2)\: +constant_timer_memo=\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u9593\u306B\u4E00\u5B9A\u306E\u9045\u5EF6\u3092\u8FFD\u52A0 +constant_timer_title=\u5B9A\u6570\u30BF\u30A4\u30DE +controller=\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +cookie_manager_title=HTTP \u30AF\u30C3\u30AD\u30FC\u30DE\u30CD\u30FC\u30B8\u30E3 +cookies_stored=\u30AF\u30C3\u30AD\u30FC\u30DE\u30CD\u30FC\u30B8\u30E3\u306B\u4FDD\u5B58\u3055\u308C\u3066\u3044\u308B\u30AF\u30C3\u30AD\u30FC +copy=\u30B3\u30D4\u30FC +counter_config_title=\u30AB\u30A6\u30F3\u30BF +counter_per_user=\u5404\u30E6\u30FC\u30B6\u72EC\u7ACB\u306E\u30C8\u30E9\u30C3\u30AF\u30AB\u30A6\u30F3\u30BF +csvread_file_file_name=\u5024\u3092\u8AAD\u307F\u8FBC\u3080CSV\u30D5\u30A1\u30A4\u30EB +cut=\u30AB\u30C3\u30C8 +cut_paste_function=\u751F\u6210\u3055\u308C\u305F\u95A2\u6570\u6587\u5B57\u5217\u3092\u30B3\u30D4\u30FC\u3057\u8CBC\u308A\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044\u3002 +database_sql_query_string=SQL \u30AF\u30A8\u30EA\u30FC\u6587\u5B57\u5217\: +database_sql_query_title=JDBC SQL \u30AF\u30A8\u30EA\u30FC\u521D\u671F\u5024\u8A2D\u5B9A +de=\u30C9\u30A4\u30C4\u8A9E +default_parameters=\u30C7\u30D5\u30A9\u30EB\u30C8\u30D1\u30E9\u30E1\u30FC\u30BF +default_value_field=\u521D\u671F\u5024\uFF1A +delay=\u8D77\u52D5\u9045\u5EF6\uFF08\u79D2\uFF09 +delete=\u524A\u9664 +delete_parameter=\u5909\u6570\u306E\u524A\u9664 +delete_test=\u30C6\u30B9\u30C8\u306E\u524A\u9664 +delete_user=\u30E6\u30FC\u30B6\u30FC\u306E\u524A\u9664 +disable=\u7121\u52B9 +dn=\uFF24\uFF2E +domain=\u30C9\u30E1\u30A4\u30F3 +duration=\u6301\u7D9A\u6642\u9593\uFF08\u79D2\uFF09 +duration_assertion_duration_test=\u30A2\u30B5\u30FC\u30C8\u306E\u6301\u7D9A +duration_assertion_failure=\u64CD\u4F5C\u306B\u6642\u9593\u304C\u304B\u304B\u308A\u3059\u304E\u307E\u3057\u305F\:{0}\u30DF\u30EA\u79D2\u304B\u304B\u308A\u307E\u3057\u305F\u304C\u3001{1}\u30DF\u30EA\u79D2\u3088\u308A\u3082\u9577\u304F\u304B\u304B\u308B\u3079\u304D\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002 +duration_assertion_input_error=\u59A5\u5F53\u306A\u6B63\u306E\u6574\u6570\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +duration_assertion_label=\u6301\u7D9A\u6642\u9593(\u30DF\u30EA\u79D2) +duration_assertion_title=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3\u306E\u6301\u7D9A +edit=\u7DE8\u96C6 +email_results_title=\u7D50\u679C\u3092\u30E1\u30FC\u30EB\u3067\u9001\u4FE1 +en=\u82F1\u8A9E +enable=\u6709\u52B9 +encoded_value=URL\u30A8\u30F3\u30B3\u30FC\u30C9\u5024 +endtime=\u7D42\u4E86\u6642\u523B +entry_dn=\u30A8\u30F3\u30C8\u30EADN +error_loading_help=\u30D8\u30EB\u30D7\u30DA\u30FC\u30B8\u30ED\u30FC\u30C9\u4E2D\u306E\u30A8\u30E9\u30FC +error_occurred=\u30A8\u30E9\u30FC\u304C\u767A\u751F +example_data=\u30B5\u30F3\u30D7\u30EB\u30C7\u30FC\u30BF +example_title=Example\u30B5\u30F3\u30D7\u30E9\u30FC +exit=\u7D42\u4E86 +expiration=\u671F\u9650 +field_name=\u30D5\u30A3\u30FC\u30EB\u30C9\u540D +file=\u30D5\u30A1\u30A4\u30EB +file_already_in_use=\u305D\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u3059\u3067\u306B\u4F7F\u7528\u4E2D\u3067\u3059\u3002 +file_visualizer_append=\u65E2\u306B\u5B58\u5728\u3059\u308B\u30C7\u30FC\u30BF\u30D5\u30A1\u30A4\u30EB\u3078\u8FFD\u52A0 +file_visualizer_auto_flush=\u5404\u30C7\u30FC\u30BF\u3092\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u3057\u305F\u3042\u3068\u306B\u81EA\u52D5\u7684\u306B\u66F8\u51FA\u3057 +file_visualizer_browse=\u53C2\u7167... +file_visualizer_close=\u9589\u3058\u308B +file_visualizer_file_options=\u30D5\u30A1\u30A4\u30EB\u30AA\u30D7\u30B7\u30E7\u30F3 +file_visualizer_filename=\u30D5\u30A1\u30A4\u30EB\u540D +file_visualizer_flush=\u66F8\u51FA\u3057 +file_visualizer_missing_filename=\u51FA\u529B\u30D5\u30A1\u30A4\u30EB\u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002 +file_visualizer_open=\u958B\u304F +file_visualizer_output_file=\u5168\u3066\u306E\u30C7\u30FC\u30BF\u3092\u30D5\u30A1\u30A4\u30EB\u306B\u51FA\u529B +file_visualizer_submit_data=\u9001\u4FE1\u30C7\u30FC\u30BF\u3092\u542B\u307E\u305B\u308B +file_visualizer_title=\u30D5\u30A1\u30A4\u30EB\u30EC\u30DD\u30FC\u30BF +file_visualizer_verbose=\u8A73\u7D30\u306A\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u51FA\u529B +filename=\u30D5\u30A1\u30A4\u30EB\u540D +follow_redirects=\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u306B\u5BFE\u5FDC +follow_redirects_auto=\u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8 +foreach_controller_title=ForEach\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +foreach_input=Input\u5909\u6570\u540D\u63A5\u982D\u8F9E +foreach_output=Output\u5909\u6570\u540D +ftp_sample_title=FTP \u30EA\u30AF\u30A8\u30B9\u30C8\u521D\u671F\u5024\u8A2D\u5B9A +ftp_testing_title=FTP \u30EA\u30AF\u30A8\u30B9\u30C8 +function_dialog_menu_item=\u95A2\u6570\u30D8\u30EB\u30D1\u30FC\u30C0\u30A4\u30A2\u30ED\u30B0 +function_helper_title=\u95A2\u6570\u30D8\u30EB\u30D1\u30FC +function_name_param=\u95A2\u6570\u540D\u3002\u30C6\u30B9\u30C8\u8A08\u753B\u3067\u4F7F\u7528\u3059\u308B\u5024\u3092\u4FDD\u6301\u3059\u308B\u306E\u306B\u4F7F\u7528\u3055\u308C\u307E\u3059\u3002 +function_params=\u95A2\u6570\u30D1\u30E9\u30E1\u30FC\u30BF +functional_mode=Functional \u30C6\u30B9\u30C8\u30E2\u30FC\u30C9 +functional_mode_explanation=\u5404\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5BFE\u3059\u308B\u30B5\u30FC\u30D0\u30FC\u306E\u5FDC\u7B54\u3092\n\u30D5\u30A1\u30A4\u30EB\u3078\u66F8\u304D\u8FBC\u307F\u305F\u3044\u5834\u5408\u306E\u307FFunctional \u30C6\u30B9\u30C8\u30E2\u30FC\u30C9\u3092\u9078\u629E\u3057\u3066\u4E0B\u3055\u3044\u3002\n\n\u3053\u306E\u30AA\u30D7\u30B7\u30E7\u30F3\u3092\u9078\u3093\u3060\u3068\u304D\u306E\u6027\u80FD\u306B\u5BFE\u3059\u308B\u5F71\u97FF\u306B\u7559\u610F\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +gaussian_timer_delay=\u9045\u5EF6\u6642\u9593\u30AA\u30D5\u30BB\u30C3\u30C8\u5B9A\u6570 (\u30DF\u30EA\u79D2)\: +gaussian_timer_memo=\u30AC\u30A6\u30B9\u5206\u5E03\u306B\u3088\u308B\u30E9\u30F3\u30C0\u30E0\u306A\u9045\u5EF6\u3092\u8FFD\u52A0 +gaussian_timer_range=\u504F\u5DEE (\u30DF\u30EA\u79D2)\: +gaussian_timer_title=\u30AC\u30A6\u30B9\u4E71\u6570\u30BF\u30A4\u30DE +generate=\u751F\u6210 +generator=\u751F\u6210\u30AF\u30E9\u30B9\u540D +generator_cnf_msg=\u751F\u6210\u30AF\u30E9\u30B9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002/lib\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u751F\u6210\u30AF\u30E9\u30B9\u3092\u542B\u3080jar\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308B\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002 +generator_illegal_msg=IllegalAcessException\u306B\u3088\u308A\u751F\u6210\u30AF\u30E9\u30B9\u3078\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002 +generator_instantiate_msg=\u751F\u6210\u30D1\u30FC\u30B5\u306E\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002Generator\u30A4\u30F3\u30BF\u30D5\u30A7\u30FC\u30B9\u3092\u5B9F\u88C5\u3059\u308B\u751F\u6210\u30AF\u30E9\u30B9\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002 +get_xml_from_file=SOAP XML\u30C7\u30FC\u30BF\u306E\u30D5\u30A1\u30A4\u30EB\uFF08\u4E0A\u8A18\u30C6\u30AD\u30B9\u30C8\u306F\u7121\u8996\u3055\u308C\u307E\u3059\uFF09 +get_xml_from_random=\u30E1\u30C3\u30BB\u30FC\u30B8\u30D5\u30A9\u30EB\u30C0 +graph_choose_graphs=\u8868\u793A\u3059\u308B\u30B0\u30E9\u30D5 +graph_full_results_title=\u7D50\u679C\u3092\u30B0\u30E9\u30D5\u8868\u793A(\u8A73\u7D30) +graph_results_average=\u5E73\u5747 +graph_results_data=\u30C7\u30FC\u30BF +graph_results_deviation=\u504F\u5DEE +graph_results_latest_sample=\u6700\u65B0\u306E\u30B5\u30F3\u30D7\u30EB +graph_results_median=\u4E2D\u592E\u5024 +graph_results_ms=\u30DF\u30EA\u79D2(ms) +graph_results_no_samples=\u30B5\u30F3\u30D7\u30EB\u6570 +graph_results_throughput=\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8 +graph_results_title=\u30B0\u30E9\u30D5\u8868\u793A +grouping_add_separators=\u30B0\u30EB\u30FC\u30D7\u9593\u306B\u30BB\u30D1\u30EC\u30FC\u30BF\u3092\u8FFD\u52A0 +grouping_in_controllers=\u65B0\u898F\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9\u3078\u5404\u30B0\u30EB\u30FC\u30D7\u3092\u7F6E\u304F +grouping_mode=\u30B0\u30EB\u30FC\u30D7\u306B\u3059\u308B\: +grouping_no_groups=\u30B5\u30F3\u30D7\u30E9\u30FC\u3092\u30B0\u30EB\u30FC\u30D7\u306B\u3057\u306A\u3044 +grouping_store_first_only=\u5404\u30B0\u30EB\u30FC\u30D7\u306E\u6700\u521D\u306E\u30B5\u30F3\u30D7\u30E9\u30FC\u3060\u3051\u4FDD\u5B58 +header_manager_title=HTTP \u30D8\u30C3\u30C0\u30DE\u30CD\u30FC\u30B8\u30E3 +headers_stored=\u30D8\u30C3\u30C0\u30FC\u30DE\u30CD\u30FC\u30B8\u30E3\u306B\u4FDD\u5B58\u3055\u308C\u3066\u3044\u308B\u30D8\u30C3\u30C0 +help=\u30D8\u30EB\u30D7 +html_parameter_mask=HTML\u30D1\u30E9\u30E1\u30FC\u30BF\u30DE\u30B9\u30AF +http_response_code=HTTP\u5FDC\u7B54\u30B3\u30FC\u30C9 +http_url_rewriting_modifier_title=HTTP URL-Rewriting \u4FEE\u98FE\u5B50 +http_user_parameter_modifier=HTTP\u30E6\u30FC\u30B6\u30FC\u30D1\u30E9\u30E1\u30FC\u30BF\u306E\u5909\u66F4 +id_prefix=ID\u63A5\u982D\u8F9E +id_suffix=ID \u63A5\u5C3E\u8F9E +if_controller_label=\u6761\u4EF6 +if_controller_title=If \u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +ignore_subcontrollers=\u30B5\u30D6\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9\u30D6\u30ED\u30C3\u30AF\u3092\u7121\u8996 +include_equals=\u7B49\u53F7\u542B\u3080\uFF1F +increment=\u5897\u5206 +infinite=\u7121\u9650\u30EB\u30FC\u30D7 +insert_after=\u5F8C\u3078\u633F\u5165 +insert_before=\u524D\u3078\u633F\u5165 +insert_parent=\u4E0A\u306E\u968E\u5C64\u306B\u633F\u5165 +interleave_control_title=\u30A4\u30F3\u30BF\u30EA\u30FC\u30D6\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +intsum_param_1=\u6700\u521D\u306B\u8FFD\u52A0\u3059\u308Bint +intsum_param_2=\uFF12\u56DE\u76EE\u306B\u8FFD\u52A0\u3059\u308Bint - \u3055\u3089\u306A\u308B\u5F15\u6570\u3092\u8FFD\u52A0\u3057\u3066\u5408\u8A08\u304C\u8A08\u7B97\u3055\u308C\u308B +invalid_data=\u9069\u5207\u3067\u306A\u3044\u30C7\u30FC\u30BF +invalid_mail_server=\u4E0D\u660E\u306A\u30E1\u30FC\u30EB\u30B5\u30FC\u30D0\u30FC\u3067\u3059\u3002 +iteration_counter_arg_1=TRUE, \u30E6\u30FC\u30B6\u30FC\u6BCE\u306B\u30AB\u30A6\u30F3\u30BF\u30FC\u3092\u6301\u3064\u3053\u3068\u304C\u3067\u304D\u308B, FALSE \u30B0\u30ED\u30FC\u30D0\u30EB\u30AB\u30A6\u30F3\u30BF\u30FC\u3068\u306A\u308B +iterator_num=\u30EB\u30FC\u30D7\u56DE\u6570\: +java_request=Java \u30EA\u30AF\u30A8\u30B9\u30C8 +java_request_defaults=Java \u30EA\u30AF\u30A8\u30B9\u30C8\u521D\u671F\u5024\u8A2D\u5B9A +jndi_config_title=JNDI \u8A2D\u5B9A +jndi_lookup_name=\u30EA\u30E2\u30FC\u30C8\u30A4\u30F3\u30BF\u30D5\u30A7\u30FC\u30B9 +jndi_lookup_title=JNDI \u30EB\u30C3\u30AF\u30A2\u30C3\u30D7\u8A2D\u5B9A +jndi_method_button_invoke=\u547C\u3073\u51FA\u3057 +jndi_method_button_reflect=\u30EA\u30D5\u30EC\u30AF\u30C8 +jndi_method_home_name=\u30DB\u30FC\u30E0\u30E1\u30BD\u30C3\u30C9\u540D +jndi_method_home_parms=\u30DB\u30FC\u30E0\u30E1\u30BD\u30C3\u30C9\u306E\u5F15\u6570 +jndi_method_name=\u30E1\u30BD\u30C3\u30C9\u8A2D\u5B9A +jndi_method_remote_interface_list=\u30EA\u30E2\u30FC\u30C8\u30A4\u30F3\u30BF\u30D5\u30A7\u30FC\u30B9 +jndi_method_remote_name=\u30EA\u30E2\u30FC\u30C8\u30E1\u30BD\u30C3\u30C9\u540D +jndi_method_remote_parms=\u30EA\u30E2\u30FC\u30C8\u30E1\u30BD\u30C3\u30C9\u306E\u5F15\u6570 +jndi_method_title=\u30EA\u30E2\u30FC\u30C8\u30E1\u30BD\u30C3\u30C9\u8A2D\u5B9A +jndi_testing_title=JNDI \u30EA\u30AF\u30A8\u30B9\u30C8 +jndi_url_jndi_props=JNDI \u30D7\u30ED\u30D1\u30C6\u30A3 +ja=\u65E5\u672C\u8A9E +ldap_sample_title=LDAP\u30EA\u30AF\u30A8\u30B9\u30C8\u521D\u671F\u5024\u8A2D\u5B9A +ldap_testing_title=LDAP\u30EA\u30AF\u30A8\u30B9\u30C8 +load=\u8AAD\u8FBC +load_wsdl=WSDL\u306E\u30ED\u30FC\u30C9 +log_errors_only=\u30ED\u30B0\u30A8\u30E9\u30FC\u306E\u307F +log_file=\u30ED\u30B0\u30D5\u30A1\u30A4\u30EB\u306E\u5834\u6240 +log_parser=Log\u30D1\u30FC\u30B5\u30AF\u30E9\u30B9\u540D +log_parser_cnf_msg=\u30AF\u30E9\u30B9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002/lib\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u30AF\u30E9\u30B9\u3092\u542B\u3080jar\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308B\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002 +log_parser_illegal_msg=IllegalAcessException\u306B\u3088\u308A\u30AF\u30E9\u30B9\u3078\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002 +log_parser_instantiate_msg=\u30ED\u30B0\u30D1\u30FC\u30B5\u306E\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002LogParser\u30A4\u30F3\u30BF\u30D5\u30A7\u30FC\u30B9\u3092\u5B9F\u88C5\u3059\u308B\u30D1\u30FC\u30B5\u30AF\u30E9\u30B9\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002 +log_sampler=Tomcat\u30A2\u30AF\u30BB\u30B9\u30ED\u30B0\u30B5\u30F3\u30D7\u30E9\u30FC +logic_controller_title=\u30B7\u30F3\u30D7\u30EB\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +login_config=\u30ED\u30B0\u30A4\u30F3\u8A2D\u5B9A +login_config_element=\u30ED\u30B0\u30A4\u30F3\u8A2D\u5B9A\u30A8\u30EC\u30E1\u30F3\u30C8 +loop_controller_title=\u30EB\u30FC\u30D7\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +looping_control=\u30EB\u30FC\u30D7\u30B3\u30F3\u30C8\u30ED\u30FC\u30EB +lower_bound=\u4E0B\u9650 +mailer_attributes_panel=\u30E1\u30FC\u30EB\u306E\u5C5E\u6027 +mailer_error=\u30E1\u30FC\u30EB\u3092\u9001\u4FE1\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u5165\u529B\u5185\u5BB9\u306B\u9593\u9055\u3044\u304C\u306A\u3044\u304B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +mailer_visualizer_title=\u30E1\u30FC\u30E9\u30FC\u30D3\u30B8\u30E5\u30A2\u30E9\u30A4\u30B6 +match_num_field=\u4E00\u81F4\u756A\u53F7\uFF080\u304B\u3089\u4E71\u6570\uFF09\uFF1A +max=\u6700\u5927\u5024 +maximum_param=\u5024\u57DF\u306E\u6700\u5927\u5024 +md5hex_assertion_failure=MD5 sum \u30A2\u30B5\u30FC\u30C8\u30A8\u30E9\u30FC \: \u7D50\u679C\u306F {0} \u3067\u3057\u305F\u304C\u3001{1}\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093\u3002 +md5hex_assertion_md5hex_test=\u30A2\u30B5\u30FC\u30C8\u5BFE\u8C61\u306EMD5Hex +md5hex_assertion_title=MD5Hex\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +memory_cache=\u30E1\u30E2\u30EA\u30FC\u30AD\u30E3\u30C3\u30B7\u30E5 +menu_assertions=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +menu_close=\u9589\u3058\u308B +menu_config_element=\u8A2D\u5B9A\u30A8\u30EC\u30E1\u30F3\u30C8 +menu_edit=\u7DE8\u96C6 +menu_generative_controller=\u30B5\u30F3\u30D7\u30E9\u30FC +menu_listener=\u30EA\u30B9\u30CA\u30FC +menu_logic_controller=\u30ED\u30B8\u30C3\u30AF\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +menu_merge=\u4F75\u5408\uFF08\u30DE\u30FC\u30B8\uFF09 +menu_modifiers=\u4FEE\u98FE\u5B50 +menu_non_test_elements=Non-Test\u30A8\u30EC\u30E1\u30F3\u30C8 +menu_open=\u958B\u304F +menu_post_processors=\u5F8C\u51E6\u7406 +menu_pre_processors=\u524D\u51E6\u7406 +menu_response_based_modifiers=\u30EC\u30B9\u30DD\u30F3\u30B9\u57FA\u6E96\u306E\u4FEE\u98FE\u5B50 +menu_timer=\u30BF\u30A4\u30DE +metadata=\u30E1\u30BF\u30C7\u30FC\u30BF +method=\u30E1\u30BD\u30C3\u30C9\: +mimetype=Mime\u30BF\u30A4\u30D7 +minimum_param=\u5024\u57DF\u306E\u6700\u5C0F\u5024 +minute=\u5206 +modification_controller_title=\u5909\u66F4\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +modification_manager_title=\u5909\u66F4\u30DE\u30CD\u30FC\u30B8\u30E3 +modify_test=\u30C6\u30B9\u30C8\u306E\u5909\u66F4 +module_controller_title=\u30E2\u30B8\u30E5\u30FC\u30EB\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +monitor_equation_dead=Dead\: \u5FDC\u7B54\u306A\u3057 +monitor_health_title=\u30E2\u30CB\u30BF\u7D50\u679C +monitor_is_title=\u30E2\u30CB\u30BF\u3068\u3057\u3066\u4F7F\u7528 +monitor_legend_memory_per=\u30E1\u30E2\u30EA % (used/total) +monitor_legend_thread_per=\u30B9\u30EC\u30C3\u30C9 % (busy/max) +monitor_performance_servers=\u30B5\u30FC\u30D0 +monitor_performance_tab_title=\u30D1\u30D5\u30A9\u30FC\u30DE\u30F3\u30B9 +monitor_performance_title=\u30D1\u30D5\u30A9\u30FC\u30DE\u30F3\u30B9\u30B0\u30E9\u30D5 +name=\u540D\u524D\: +new=\u65B0\u898F +no=\u30CE\u30EB\u30A6\u30A7\u30FC\u8A9E +number_of_threads=\u30B9\u30EC\u30C3\u30C9\u6570\: +once_only_controller_title=\u4E00\u5EA6\u3060\u3051\u5B9F\u884C\u3055\u308C\u308B\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +open=\u958B\u304F... +option=\u30AA\u30D7\u30B7\u30E7\u30F3 +optional_tasks=\u30AA\u30D7\u30B7\u30E7\u30F3\u30BF\u30B9\u30AF +paramtable=\u30EA\u30AF\u30A8\u30B9\u30C8\u3067\u9001\u308B\u30D1\u30E9\u30E1\u30FC\u30BF\: +password=\u30D1\u30B9\u30EF\u30FC\u30C9 +paste=\u30DA\u30FC\u30B9\u30C8 +paste_insert=\u633F\u5165\u3068\u3057\u3066\u30DA\u30FC\u30B9\u30C8 +path=\u30D1\u30B9\: +path_extension_choice=\u30D1\u30B9\u306E\u62E1\u5F35(\u533A\u5207\u308A\u306B\u306F";"\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044) +path_extension_dont_use_equals=\u30D1\u30B9\u306E\u62E1\u5F35\u306B\u7B49\u53F7\u3092\u4F7F\u308F\u306A\u3044\uFF08Intershop Enfinity \u4E92\u63DB\u306E\u305F\u3081\uFF09 +patterns_to_exclude=\u9664\u5916\u3059\u308B\u30D1\u30BF\u30FC\u30F3 +patterns_to_include=\u633F\u5165\u3059\u308B\u30D1\u30BF\u30FC\u30F3 +port=\u30DD\u30FC\u30C8\: +property_default_param=\u521D\u671F\u5024 +property_edit=\u7DE8\u96C6 +property_editor.value_is_invalid_message=\u5165\u529B\u3055\u308C\u305F\u30C6\u30AD\u30B9\u30C8\u306F\u3053\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u306B\u9069\u3057\u3066\u3044\u307E\u305B\u3093\u3002\n\u5143\u306E\u5024\u306B\u623B\u3057\u307E\u3059\u3002 +property_editor.value_is_invalid_title=\u9069\u5207\u3067\u306A\u3044\u5165\u529B +property_name_param=\u30D7\u30ED\u30D1\u30C6\u30A3\u540D +property_undefined=\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u306A\u3044 +protocol=\u30D7\u30ED\u30C8\u30B3\u30EB\: +protocol_java_border=Java \u30AF\u30E9\u30B9 +protocol_java_classname=\u30AF\u30E9\u30B9\u540D\: +protocol_java_config_tile=Java \u30B5\u30F3\u30D7\u30EB\u306E\u8A2D\u5B9A +protocol_java_test_title=Java \u30C6\u30B9\u30C8 +proxy_assertions=\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3\u306E\u8FFD\u52A0 +proxy_cl_error=\u30D7\u30ED\u30AD\u30B7\u30FC\u30B5\u30FC\u30D0\u30FC\u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u308B\u5834\u5408\u306F\u3001\u30DB\u30B9\u30C8\u540D\u3068\u30DD\u30FC\u30C8\u3082\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +proxy_headers=HTTP\u30D8\u30C3\u30C0\u306E\u53D6\u308A\u8FBC\u307F +proxy_separators=\u30BB\u30D1\u30EC\u30FC\u30BF\u306E\u8FFD\u52A0 +proxy_target=\u5BFE\u8C61\u3068\u306A\u308B\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9\: +proxy_title=HTTP \u30D7\u30ED\u30AD\u30B7\u30B5\u30FC\u30D0 +ramp_up=Ramp-Up \u671F\u9593 (\u79D2)\: +random_control_title=\u4E71\u6570\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +random_order_control_title=\u30E9\u30F3\u30C0\u30E0\u9806\u5E8F\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +read_response_message=\u5FDC\u7B54\u8AAD\u307F\u8FBC\u307F\u304C\u30C1\u30A7\u30C3\u30AF\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5FDC\u7B54\u3092\u898B\u308B\u305F\u3081\u306B\u306F\u30B5\u30F3\u30D7\u30E9\u30FC\u306E\u5FDC\u7B54\u3092\u30C1\u30A7\u30C3\u30AF\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +read_response_note=\u5FDC\u7B54\u8AAD\u307F\u8FBC\u307F\u304C\u30C1\u30A7\u30C3\u30AF\u3055\u308C\u3066\u3044\u306A\u3051\u308C\u3070\u3001\u30B5\u30F3\u30D7\u30E9\u30FC\u306F\u5FDC\u7B54\u305B\u305A\u3001 +read_response_note2=SampleResult\u3092\u8A2D\u5B9A\u3057\u307E\u305B\u3093\u3002\u3053\u308C\u306F\u30D1\u30D5\u30A9\u30FC\u30DE\u30F3\u30B9\u3092\u826F\u304F\u3057\u307E\u3059\u304C\u3001 +read_response_note3=\u5FDC\u7B54\u5185\u5BB9\u306F\u30ED\u30B0\u306B\u6B8B\u3055\u308C\u306A\u3044\u3053\u3068\u306B\u306A\u308A\u307E\u3059\u3002 +read_soap_response=SOAP\u30EC\u30B9\u30DD\u30F3\u30B9\u8AAD\u8FBC +record_controller_title=\u8A18\u9332\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +ref_name_field=\u53C2\u7167\u540D\uFF1A +regex_extractor_title=\u6B63\u898F\u8868\u73FE\u62BD\u51FA +regex_field=\u6B63\u898F\u8868\u73FE\uFF1A +regexfunc_param_1=\u76F4\u524D\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u7D50\u679C\u304B\u3089\u691C\u7D22\u3059\u308B\u305F\u3081\u306E\u6B63\u898F\u8868\u73FE\u3067\u3059\u3002 +regexfunc_param_2=\u6587\u5B57\u5217\u3092\u7F6E\u63DB\u3059\u308B\u305F\u3081\u306E\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3067\u3001\u6B63\u898F\u8868\u73FE\u306E\u30B0\u30EB\u30FC\u30D7\u5316\u3092\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002\u66F8\u5F0F\u306F$[group]$\u3002\u4F8B\uFF09 $1$\u3002 +regexfunc_param_3=\u30DE\u30C3\u30C1\u30F3\u30B0\u3067\u4F7F\u7528\u3057\u307E\u3059\u30021\u4EE5\u4E0A\u306E\u6574\u6570\u3001RAND(JMeter\u304C\u30E9\u30F3\u30C0\u30E0\u306B\u9078\u629E\u3059\u308B)\u3001\u6D6E\u52D5\u5C0F\u6570\u70B9\u3001ALL(\u5168\u3066\u306B\u4E00\u81F4\u3059\u308B)\u3001\u306E\u3044\u305A\u308C\u304B\u3092\u6307\u5B9A\u3067\u304D\u307E\u3059\u3002 +regexfunc_param_4=\u30C6\u30AD\u30B9\u30C8\u306E\u7BC4\u56F2\u3067\u3059\u3002ALL\u304C\u9078\u629E\u3055\u308C\u305F\u5834\u5408\u3001\u7D50\u679C\u3092\u751F\u6210\u3059\u308B\u305F\u3081\u306B\u4F7F\u308F\u308C\u307E\u3059\u3002 +regexfunc_param_5=\u521D\u671F\u30C6\u30AD\u30B9\u30C8\u3067\u3059\u3002\u6B63\u898F\u8868\u73FE\u3068\u4E00\u81F4\u3059\u308B\u6587\u5B57\u5217\u304C\u306A\u304B\u3063\u305F\u5834\u5408\u306B\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u306E\u4EE3\u308F\u308A\u3068\u3057\u3066\u4F7F\u7528\u3055\u308C\u307E\u3059\u3002 +remote_exit=\u7D42\u4E86(\u30EA\u30E2\u30FC\u30C8) +remote_exit_all=\u5168\u3066\u7D42\u4E86(\u30EA\u30E2\u30FC\u30C8) +remote_start=\u958B\u59CB(\u30EA\u30E2\u30FC\u30C8) +remote_start_all=\u5168\u3066\u958B\u59CB(\u30EA\u30E2\u30FC\u30C8) +remote_stop=\u505C\u6B62(\u30EA\u30E2\u30FC\u30C8) +remote_stop_all=\u5168\u3066\u505C\u6B62(\u30EA\u30E2\u30FC\u30C8) +remove=\u524A\u9664 +report=\u30EC\u30DD\u30FC\u30C8 +request_data=\u30EA\u30AF\u30A8\u30B9\u30C8\u30C7\u30FC\u30BF +restart=\u30EA\u30B9\u30BF\u30FC\u30C8 +resultaction_title=\u30A2\u30AF\u30B7\u30E7\u30F3\u30CF\u30F3\u30C9\u30E9\u306E\u7D42\u4E86\u72B6\u614B +resultsaver_prefix=\u30D5\u30A1\u30A4\u30EB\u540D\u306E\u63A5\u982D\u8F9E\: +resultsaver_title=\u5FDC\u7B54\u3092\u30D5\u30A1\u30A4\u30EB\u3078\u4FDD\u5B58 +root=\u30EB\u30FC\u30C8 +root_title=\u30EB\u30FC\u30C8 +run=\u5B9F\u884C +running_test=\u30C6\u30B9\u30C8\u5B9F\u884C\u4E2D +sampler_on_error_action=\u30B5\u30F3\u30D7\u30E9\u30FC\u30A8\u30E9\u30FC\u5F8C\u306E\u30A2\u30AF\u30B7\u30E7\u30F3 +sampler_on_error_continue=\u7D9A\u884C +sampler_on_error_stop_test=\u30C6\u30B9\u30C8\u505C\u6B62 +sampler_on_error_stop_thread=\u30B9\u30EC\u30C3\u30C9\u505C\u6B62 +save=\u30C6\u30B9\u30C8\u8A08\u753B\u3092\u4FDD\u5B58 +save?=\u4FDD\u5B58? +save_all_as=\u30C6\u30B9\u30C8\u8A08\u753B\u306B\u540D\u524D\u3092\u3064\u3051\u3066\u4FDD\u5B58 +save_as=\u5225\u540D\u3067\u4FDD\u5B58... +scheduler=\u30B9\u30B1\u30B8\u30E5\u30FC\u30E9 +scheduler_configuration=\u30B9\u30B1\u30B8\u30E5\u30FC\u30E9\u8A2D\u5B9A +search_base=\u691C\u7D22\u57FA\u6E96 +search_filter=\u691C\u7D22\u30D5\u30A3\u30EB\u30BF +search_test=\u30C6\u30B9\u30C8\u306E\u691C\u7D22 +second=\u79D2 +secure=\u30BB\u30AD\u30E5\u30A2 +send_file=\u30EA\u30AF\u30A8\u30B9\u30C8\u3068\u4E00\u7DD2\u306B\u9001\u4FE1\u3055\u308C\u308B\u30D5\u30A1\u30A4\u30EB\: +send_file_browse=\u53C2\u7167... +send_file_filename_label=\u30D5\u30A1\u30A4\u30EB\u540D\: +send_file_mime_label=MIME \u30BF\u30A4\u30D7\: +send_file_param_name_label=\u30D1\u30E9\u30E1\u30FC\u30BF\u540D\: +server=\u30B5\u30FC\u30D0\u540D\u307E\u305F\u306F IP\: +servername=\u30B5\u30FC\u30D0\u540D\uFF1A +session_argument_name=\u30BB\u30C3\u30B7\u30E7\u30F3\u5F15\u6570\u540D +shutdown=\u30B7\u30E3\u30C3\u30C8\u30C0\u30A6\u30F3 +simple_config_element=\u30B7\u30F3\u30D7\u30EB\u8A2D\u5B9A\u30A8\u30EC\u30E1\u30F3\u30C8 +simple_data_writer_title=\u30B7\u30F3\u30D7\u30EB\u30C7\u30FC\u30BF\u30E9\u30A4\u30BF +size_assertion_comparator_error_equal=\u7B49\u3057\u3044 +size_assertion_comparator_error_greater=\u5927\u306A\u308A\u5C0F +size_assertion_comparator_error_greaterequal=\u4EE5\u4E0A +size_assertion_comparator_error_less=\u5C0F\u306A\u308A\u5927 +size_assertion_comparator_error_lessequal=\u4EE5\u4E0B +size_assertion_comparator_error_notequal=\u7B49\u3057\u304F\u306A\u3044 +size_assertion_comparator_label=\u6BD4\u8F03\u306E\u578B +size_assertion_failure=\u7D50\u679C\u306F\u6B63\u3057\u304F\u306A\u3044\u30B5\u30A4\u30BA\u3067\u3059\u3002\:{0}\u30D0\u30A4\u30C8\u3067\u3059\u304C\u3001{1}\u30D0\u30A4\u30C8\u304B{2}\u30D0\u30A4\u30C8\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093\u3002 +size_assertion_input_error=\u9069\u5207\u306A\u6B63\u306E\u6574\u6570\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +size_assertion_label=\u30D0\u30A4\u30C8\u30B5\u30A4\u30BA\: +size_assertion_size_test=\u30A2\u30B5\u30FC\u30C8\u306E\u30B5\u30A4\u30BA +size_assertion_title=\u30B5\u30A4\u30BA\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +soap_action=Soap\u30A2\u30AF\u30B7\u30E7\u30F3 +soap_data_title=Soap/XML-RPC \u30C7\u30FC\u30BF +soap_sampler_title=Soap/XML-RPC\u30EA\u30AF\u30A8\u30B9\u30C8 +spline_visualizer_average=\u5E73\u5747 +spline_visualizer_incoming=\u5230\u7740 +spline_visualizer_maximum=\u6700\u5927 +spline_visualizer_minimum=\u6700\u5C0F +spline_visualizer_title=\u30B9\u30D7\u30E9\u30A4\u30F3\u30D3\u30B8\u30E5\u30A2\u30E9\u30A4\u30B6 +spline_visualizer_waitingmessage=\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u958B\u59CB\u307E\u3067\u304A\u5F85\u3061\u4E0B\u3055\u3044\u3002 +ssl_alias_prompt=\u5B9A\u7FA9\u6E08\u307F\u306E\u30A8\u30A4\u30EA\u30A2\u30B9\u3092\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002 +ssl_alias_select=\u30C6\u30B9\u30C8\u3059\u308B\u30A8\u30A4\u30EA\u30A2\u30B9\u3092\u9078\u629E\u3057\u3066\u4E0B\u3055\u3044\u3002 +ssl_alias_title=\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u30A8\u30A4\u30EA\u30A2\u30B9 +ssl_error_title=\u30AD\u30FC\u30B9\u30C8\u30A2\u30A8\u30E9\u30FC +ssl_pass_prompt=\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002 +ssl_pass_title=\u30AD\u30FC\u30B9\u30C8\u30A2\u30D1\u30B9\u30EF\u30FC\u30C9 +ssl_port=SSL\u30DD\u30FC\u30C8 +sslmanager=SSL \u30DE\u30CD\u30FC\u30B8\u30E3 +start=\u958B\u59CB +starttime=\u958B\u59CB\u6642\u523B +stop=\u505C\u6B62 +stopping_test=\u3059\u3079\u3066\u306E\u30C6\u30B9\u30C8\u7528\u30B9\u30EC\u30C3\u30C9\u3092\u505C\u6B62\u4E2D\u3067\u3059\u3002\u3057\u3070\u3089\u304F\u304A\u5F85\u3061\u304F\u3060\u3055\u3044\u3002 +stopping_test_title=\u30C6\u30B9\u30C8\u306E\u505C\u6B62\u4E2D +string_from_file_file_name=\u30D5\u30A1\u30A4\u30EB\u306E\u30D5\u30EB\u30D1\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044 +string_from_file_seq_final=\u6700\u7D42\u30D5\u30A1\u30A4\u30EB\u30B7\u30FC\u30B1\u30F3\u30B9\u756A\u53F7 +string_from_file_seq_start=\u958B\u59CB\u30D5\u30A1\u30A4\u30EB\u30B7\u30FC\u30B1\u30F3\u30B9\u756A\u53F7 +summariser_title=\u7D50\u679C\u306E\u6982\u8981\u3092\u751F\u6210 +tcp_config_title=TCP\u30B5\u30F3\u30D7\u30E9\u30FC\u8A2D\u5B9A +tcp_nodelay=\u9045\u5EF6\u306A\u3057\u3092\u8A2D\u5B9A +tcp_port=\u30DD\u30FC\u30C8\u756A\u53F7\: +tcp_request_data=\u9001\u4FE1\u3059\u308B\u30C6\u30AD\u30B9\u30C8 +tcp_sample_title=TCP\u30B5\u30F3\u30D7\u30E9\u30FC +tcp_timeout=\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\: +template_field=\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\uFF1A +test=\u30C6\u30B9\u30C8 +test_configuration=\u30C6\u30B9\u30C8\u8A2D\u5B9A +test_plan=\u30C6\u30B9\u30C8\u8A08\u753B +testplan.serialized=\u5404\u30B9\u30EC\u30C3\u30C9\u30B0\u30EB\u30FC\u30D7\u3092\u5225\u3005\u306B\u5B9F\u884C +testplan_comments=\u30B3\u30E1\u30F3\u30C8\: +thread_delay_properties=\u30B9\u30EC\u30C3\u30C9\u9045\u5EF6\u6642\u9593\u30D7\u30ED\u30D1\u30C6\u30A3 +thread_group_title=\u30B9\u30EC\u30C3\u30C9\u30B0\u30EB\u30FC\u30D7 +thread_properties=\u30B9\u30EC\u30C3\u30C9\u30D7\u30ED\u30D1\u30C6\u30A3 +threadgroup=\u30B9\u30EC\u30C3\u30C9\u30B0\u30EB\u30FC\u30D7 +throughput_control_bynumber_label=\u5168\u4F53\u5B9F\u884C +throughput_control_bypercent_label=\u30D1\u30FC\u30BB\u30F3\u30C8\u5B9F\u884C +throughput_control_perthread_label=\u30E6\u30FC\u30B6\u30FC\u3054\u3068 +throughput_control_title=\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +throughput_control_tplabel=\u30B9\u30EB\u30FC\u30D7\u30C3\u30C8 +transaction_controller_title=\u30C8\u30E9\u30F3\u30B6\u30AF\u30B7\u30E7\u30F3\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9 +uniform_timer_delay=\u9045\u5EF6\u6642\u9593\u30AA\u30D5\u30BB\u30C3\u30C8\u5B9A\u6570 (\u30DF\u30EA\u79D2)\: +uniform_timer_memo=\u4E00\u69D8\u5206\u5E03\u306B\u3088\u308B\u30E9\u30F3\u30C0\u30E0\u306A\u9045\u5EF6\u3092\u8FFD\u52A0 +uniform_timer_range=\u6700\u5927\u9045\u5EF6\u6642\u9593 (\u30DF\u30EA\u79D2)\: +uniform_timer_title=\u4E00\u69D8\u4E71\u6570\u30BF\u30A4\u30DE +update_per_iter=\u7E70\u308A\u8FD4\u3057\u3054\u3068\u306B\u66F4\u65B0 +upload=\u30D5\u30A1\u30A4\u30EB\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9 +upper_bound=\u4E0A\u9650 +url_config_protocol=\u30D7\u30ED\u30C8\u30B3\u30EB\: +url_config_title=HTTP \u30EA\u30AF\u30A8\u30B9\u30C8\u521D\u671F\u5024\u8A2D\u5B9A +url_full_config_title=UrlFull \u30B5\u30F3\u30D7\u30EB +url_multipart_config_title=HTTP\u30DE\u30EB\u30C1\u30D1\u30FC\u30C8\u30EA\u30AF\u30A8\u30B9\u30C8\u521D\u671F\u5024\u8A2D\u5B9A +use_keepalive=KeepAlive \u3092\u6709\u52B9\u306B\u3059\u308B +use_recording_controller=\u8A18\u9332\u30B3\u30F3\u30C8\u30ED\u30FC\u30E9\u306E\u4F7F\u7528 +user=\u30E6\u30FC\u30B6\u30FC +user_defined_test=\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u30C6\u30B9\u30C8 +user_defined_variables=\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5909\u6570 +user_param_mod_help_note=(\u5909\u66F4\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002\u5909\u66F4\u3059\u308B\u5834\u5408\u306F\u3001JMeter\u306E/bin\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u3042\u308B\u540C\u540D\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u5909\u66F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002) +user_parameters_table=\u30D1\u30E9\u30E1\u30FC\u30BF +user_parameters_title=\u30E6\u30FC\u30B6\u30FC\u30D1\u30E9\u30E1\u30FC\u30BF +username=\u30E6\u30FC\u30B6\u30FC\u540D +value=\u5024 +var_name=\u53C2\u7167\u540D +view_graph_tree_title=\u7D50\u679C\u3092\u30B0\u30E9\u30D5\u3068\u30C4\u30EA\u30FC\u3067\u8868\u793A +view_results_in_table=\u7D50\u679C\u3092\u8868\u3067\u8868\u793A +view_results_tab_request=\u30EA\u30AF\u30A8\u30B9\u30C8 +view_results_tab_response=\u5FDC\u7B54\u30C7\u30FC\u30BF +view_results_title=\u7D50\u679C\u8868\u793A +view_results_tree_title=\u7D50\u679C\u3092\u30C4\u30EA\u30FC\u3067\u8868\u793A +web_request=HTTP \u30EA\u30AF\u30A8\u30B9\u30C8 +web_server=Web \u30B5\u30FC\u30D0 +web_server_domain=\u30B5\u30FC\u30D0\u540D\u307E\u305F\u306F IP\: +web_server_port=\u30DD\u30FC\u30C8\u756A\u53F7\: +web_testing_retrieve_images=\u5168\u3066\u306E\u30A4\u30E1\u30FC\u30B8\u3068\u30A2\u30D7\u30EC\u30C3\u30C8\u3092\u7E70\u308A\u8FD4\u3057\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3059\u308B(HTML \u30D5\u30A1\u30A4\u30EB\u306E\u307F) +web_testing_title=HTTP \u30EA\u30AF\u30A8\u30B9\u30C8 +webservice_proxy_host=\u30D7\u30ED\u30AD\u30B7\u30DB\u30B9\u30C8 +webservice_proxy_note=HTTP\u30D7\u30ED\u30AD\u30B7\u304C\u30C1\u30A7\u30C3\u30AF\u3055\u308C\u3066\u3044\u308B\u5834\u5408\u3001\u30DB\u30B9\u30C8\u540D\u3068\u30DD\u30FC\u30C8\u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u306A\u3044\u3068\u30B5\u30F3\u30D7\u30E9\u30FC\u306F +webservice_proxy_note2=\u30B3\u30DE\u30F3\u30C9\u30E9\u30A4\u30F3\u30AA\u30D7\u30B7\u30E7\u30F3\u3092\u53C2\u7167\u3057\u307E\u3059\u3002\u30D7\u30ED\u30AD\u30B7\u30DB\u30B9\u30C8\u3084\u30D7\u30ED\u30AD\u30B7\u30DD\u30FC\u30C8\u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u306A\u3044\u5834\u5408\u306F +webservice_proxy_note3=\u3060\u307E\u3063\u305F\u307E\u307E\u7D42\u4E86\u3057\u307E\u3059\u3002 +webservice_proxy_port=\u30D7\u30ED\u30AD\u30B7\u30DD\u30FC\u30C8 +webservice_sampler_title=Web\u30B5\u30FC\u30D3\u30B9(SOAP)\u30EA\u30AF\u30A8\u30B9\u30C8 (Beta Code) +webservice_soap_action=Soap\u30A2\u30AF\u30B7\u30E7\u30F3 +webservice_use_proxy=HTTP\u30D7\u30ED\u30AD\u30B7\u306E\u4F7F\u7528 +workbench_title=\u30EF\u30FC\u30AF\u30D9\u30F3\u30C1 +wsdl_helper_error=WSDL\u304C\u4E0D\u9069\u5207\u3067\u3059\u3002URL\u3092\uFF12\u91CD\u30C1\u30A7\u30C3\u30AF\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +wsdl_url_error=WSDL\u304C\u7A7A\u3067\u3059\u3002 +xml_assertion_title=XML \u30A2\u30B5\u30FC\u30B7\u30E7\u30F3 +you_must_enter_a_valid_number=\u9069\u5207\u306A\u6570\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044 diff --git a/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_no.properties b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_no.properties new file mode 100644 index 0000000..29de5ad --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_no.properties @@ -0,0 +1,155 @@ +# 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. + +about=Om Apache JMeter +add=Legg til +add_pattern=Legg til m\u00F8nster\: +add_value=Legg til verdi +analyze=Analyser data fil... +assertion_contains=Inneholder +assertion_matches=Matcher +assertion_not=Ikke +assertion_pattern_match_rules=M\u00F8nster matching regler +assertion_patterns_to_test=M\u00F8nster \u00E5 teste +assertion_resp_field=Svarfelt \u00E5 teste +assertion_text_resp=Tektssvar +auth_base_url=Basis URL +auth_manager_title=HTTP autentiseringsmanager +auths_stored=Autentiseringer lagret hos autentiseringsmanager +browse=Bla gjennom +clear_all=Nullstill alle +clear=Nullstill +config_element=Konfigurasjonselement +constant_timer_delay=Tr\u00E5dforsinkelse (i millisekund)\: +constant_timer_title=Konstant timer +controller=Kontroller +cookies_stored=Cookies lagret hos cookie manager +database_sql_query_string=SQL foresp\u00F8rsel\: +database_sql_query_title=JDBC SQL foresp\u00F8rsel standard instillinger +default_parameters=Standard parametre +delete=Slett +domain=Domene +edit=Rediger +endtime=EndTime +exit=Avslutt +expiration=Utl\u00F8per +file=Fil +file_visualizer_append=Legg til en eksisterende fil +file_visualizer_auto_flush=Automatisk t\u00F8mming etter hver m\u00E5ledata +file_visualizer_browse=Bla gjennom... +file_visualizer_close=Lukk +file_visualizer_filename=Skriv et nytt filnavn, eller bla gjennom til en eksisterende fil +file_visualizer_file_options=Fil egenskaper +file_visualizer_flush=T\u00F8m +file_visualizer_missing_filename=Ingen utfil spesifisert. +file_visualizer_open=\u00C5pne +file_visualizer_output_file=Skriv alle data til en fil +file_visualizer_submit_data=Inkluder sendte data +file_visualizer_title=Filrapport\u00F8r +file_visualizer_verbose=Utf\u00F8rlig output +ftp_sample_title=FTP foresp\u00F8rsel standard instillinger +ftp_testing_title=FTP foresp\u00F8rsel +gaussian_timer_delay=Konstant forsinkelsesoffset (i millisekund)\: +gaussian_timer_range=Avvik (i millisekund)\: +gaussian_timer_title=Gaussisk tilfeldig timer +graph_full_results_title=Graf full resultater +graph_results_average=Gjennomsnitt +graph_results_deviation=Avvik +graph_results_title=Graf resultater +headers_stored=Headere lagret hos headermanager +help=Hjelp +infinite=Uendelig +interleave_control_title=Vekslende kontroller +iterator_num=L\u00F8kketeller\: +jndi_config_title=JNDI konfigurasjon +jndi_lookup_title=JNDI lookup konfigurasjon +jndi_method_home_name=Home metode navn +jndi_method_home_parms=Home metode parametre +jndi_method_name=Metode konfigurasjon +jndi_method_remote_name=Remote metode navn +jndi_method_remote_parms=Remote metode parametre +jndi_method_title=Remote metode konfigurasjon +jndi_testing_title=JNDI foresp\u00F8rsel +jndi_url_jndi_props=JNDI egenskaper +load=Hent +logic_controller_title=Enkel kontroller +login_config=Innlogging konfigurasjon +loop_controller_title=L\u00F8kke kontroller +looping_control=L\u00F8kkekontroll +menu_edit=Rediger +method=Metode\: +modification_manager_title=Modifiseringsmanager +name=Navn\: +number_of_threads=Antall tr\u00E5der\: +once_only_controller_title=En gang kontroller +open=\u00C5pne... +optional_tasks=Valgfrie oppgaver +option=Innstillinger +paramtable=Send parametre med foresp\u00F8rselen\: +password=Passord +path=Sti\: +patterns_to_exclude=URL M\u00F8nster \u00E5 ekskludere +patterns_to_include=URL M\u00F8nster \u00E5 inkludere +protocol=Protokoll\: +proxy_title=HTTP proxy server +ramp_up=Oppstartsperiode (i sekunder)\: +random_control_title=Random kontroller +remote_start=Remote start +remote_stop=Remote stopp +remove=Fjern +report=Rapport +root=Rot +root_title=Rot +run=Kj\u00F8r +save_all_as=Lagre alle som... +save_as=Lagre som... +save_as_image=Lagre som Image +save=Lagre alle +secure=Sikker +send_file_browse=Bla gjennom... +send_file_filename_label=Filnavn\: +send_file_mime_label=MIME type\: +send_file_param_name_label=Parameter navn\: +send_file=Send en fil med foresp\u00F8rselen\: +server=Server navn eller IP\: +spline_visualizer_average=Gjennomsnitt +spline_visualizer_incoming=Innkommende +spline_visualizer_maximum=Maksimum +spline_visualizer_title=Spline visualiserer +spline_visualizer_waitingmessage=Venter p\u00E5 m\u00E5linger +ssl_alias_prompt=Tast inn ditt preferred alias +ssl_alias_select=Velg ditt alias for testeen +ssl_alias_title=klient alias +sslmanager=SSL manager +ssl_pass_prompt=Tast inn ditt passord +starttime=StartTime +stop=Stopp +thread_delay_properties=Tr\u00E5dforsinkelse egenskaper +thread_group_title=Tr\u00E5dgruppe +threadgroup=Tr\u00E5dgruppe +uniform_timer_delay=Konstant forsinkelsesoffset (i millisekund)\: +uniform_timer_range=Tilfeldig forsinkelse maksimum (i millisekund)\: +uniform_timer_title=Uniform tilfeldig timer +upload=Fil opplasting +url_config_title=HTTP foresp\u00F8rsel standard instillinger +username=Brukernavn +value=Verdi +view_results_title=Vis resultat +view_results_tree_title=Vis resultattre +web_server_domain=Server navn eller IP\: +web_server_port=Port nummer\: +web_testing_retrieve_images=Hent alle bilder og Java Applets (kun HTML) +web_testing_title=HTTP foresp\u00F8rsel +workbench_title=Arbeidsbenk diff --git a/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_pl.properties b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_pl.properties new file mode 100644 index 0000000..5719935 --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_pl.properties @@ -0,0 +1,240 @@ +#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net) +# 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. +# Warning: JMeterUtils.getResString() replaces space with '_' +# and converts keys to lowercase before lookup +#=> All keys in this file must also be lower case or they won't match +# +about=O programie Apache JMeter +add=Dodaj +add_parameter=Dodaj parametr +add_pattern=Dodaj wzorzec: +add_test=Dodaj test +add_user=Dodaj u\u017Cytkownika +add_value=Dodaj warto\u015B\u0107 +addtest=Dodaj test +aggregate_graph=Wykresy statystyczne +aggregate_graph_column=Kolumna +aggregate_graph_display=Wy\u015Bwietl wykres +aggregate_graph_height=Wysoko\u015B\u0107 +aggregate_graph_max_length_xaxis_label=Maksymalna wysoko\u015B\u0107 etykiety osi OX +aggregate_graph_ms=Milisekund +aggregate_graph_response_time=Czas odpowiedzi +aggregate_graph_save=Zapisz wykres +aggregate_graph_save_table=Zapisz dane z tabeli +aggregate_graph_save_table_header=Zapisz nag\u0142\u00F3wek tabeli +aggregate_graph_title=Wykres skumulowany +aggregate_graph_user_title=Tytu\u0142 wykresu +aggregate_graph_width=Szeroko\u015B\u0107 +aggregate_report=Dane zagregowane +aggregate_report_90%_line=Linia 90% +aggregate_report_bandwidth=KB/sek +aggregate_report_count=Liczba pr\u00F3bek +aggregate_report_error=B\u0142\u0105d +aggregate_report_error%=% b\u0142\u0119d\u00F3w +aggregate_report_median=Mediana +aggregate_report_rate=Przepustowo\u015B\u0107 +aggregate_report_stddev=Odch. std. +aggregate_report_total_label=RAZEM +ajp_sampler_title=Pr\u00F3bnik AJP/1.3 +analyze=Analizuj plik z danymi... +anchor_modifier_title=Parser link\u00F3w HTML +argument_must_not_be_negative=Parametr musi by\u0107 nieujemny! +assertion_assume_success=Ignoruj status +assertion_code_resp=Kod odpowiedzi +assertion_contains=Zawiera +assertion_equals=R\u00F3wna si\u0119 +assertion_headers=Nag\u0142\u00F3wki odpowiedzi +assertion_matches=Pasuje do +assertion_message_resp=Tre\u015B\u0107 odpowiedzi +assertion_not=Nie +assertion_text_resp=Tekst odpowiedzi +assertion_textarea_label=Asercje: +attribute=Atrybut +attrs=Atrybuty +average=\u015Arednia +average_bytes=bit\u00F3w \u015Brednio +browse=Przegl\u0105daj... +bsf_sampler_title=Pr\u00F3bnik BSF +bsf_script_language=J\u0119zyk skryptowy: +bsf_script_parameters=Parametry do przekazania do skryptu/pliku: +bsh_assertion_script=Skrypt (see below for variables that are defined) +bsh_script=Skrypt (see below for variables that are defined) +bsh_script_file=Plik ze skryptem +cancel=Anuluj +choose_function=Wybierz funkcj\u0119 +choose_language=Wybierz j\u0119zyk +clear=Wyczy\u015B\u0107 +clear_all=Wyczy\u015B\u0107 wszystko +clear_cache_per_iter=Czy\u015Bci\u0107 cache po ka\u017Cdej iteracji? +column_delete_disallowed=Tej kolumny nie mo\u017Cna usuwa\u0107 +compare=Por\u00F3wnaj +config_element=Element konfiguruj\u0105cy +config_save_settings=Konfiguruj +configure_wsdl=Konfiguruj +controller=Kontroler +copy=Kopiuj +counter_config_title=Licznik +counter_per_user=Osobny licznik dla ka\u017Cdego u\u017Cytkownika +countlim=Limit rozmiaru +cut=Wytnij +de=Niemiecki +debug_off=Wy\u0142\u0105cz debugowanie +debug_on=W\u0142\u0105cz debugowanie +default_parameters=Parametry domy\u015Blne +default_value_field=Domy\u015Blna warto\u015B\u0107: +delay=Uruchom w ci\u0105gu (sekund) +delete=Usu\u0144 +delete_parameter=Usu\u0144 parametr +delete_test=Usu\u0144 test +delete_user=Usu\u0144 u\u017Cytkownika +deltest=Test usuwania +disable=Wy\u0142\u0105cz +distribution_graph_title=Rozk\u0142ad funkcji g\u0119sto\u015Bci (alpha) +distribution_note1=Wykres uaktualnia si\u0119 automatycznie co 10 pr\u00F3bek +domain=Domena +done=Gotowe +duration=Czas trwania (sekund) +edit=Edytuj +en=Angielski +enable=W\u0142\u0105cz +endtime=Czas zako\u0144czenia +error_loading_help=Wyst\u0105pi\u0142 b\u0142\u0105d przy pr\u00F3bie wy\u015Bwietlenia strony z pomoc\u0105 +error_occurred=Wyst\u0105pi\u0142 b\u0142\u0105d +error_title=B\u0142\u0105d +es=Hiszpa\u0144ski +exit=Edycja +file=Plik +file_visualizer_close=Zamknij +file_visualizer_filename=Nazwa pliku +file_visualizer_flush=Zapisz +file_visualizer_open=Otw\u00f3rz +file_visualizer_output_file=Zapisuj/Czytaj wyniki z pliku +filename=Nazwa pliku +functional_mode=Tryb test\u00F3w funkcjonalnych (i.e. zapisuj dane wej\u015bciowe i wyj\u015bciowe pr\u00f3bnika) +functional_mode_explanation=W\u0142\u0105czenie trybu test\u00F3w funkcjonalnych mo\u017Ce wyra\u017Anie obni\u017Cy\u0107 wydajno\u015B\u0107. +help=Pomoc +help_node=Co to za w\u0119ze\u0142? +iterator_num=Liczba powt\u00F3rze\u0144: +ja=Japo\u0144ski +jms_client_type=Klient +jms_config=Konfiguracja +jms_config_title=Konfiguracja JMS +jms_message_type=Typ wiadomo\u015Bci +jms_pwd=Has\u0142o +jms_queue=Kolejka +jms_user=U\u017Cytkownik +load=Wczytaj +load_wsdl=Wczytaj WSDL +log_errors_only=B\u0142\u0119dy +log_file=Po\u0142o\u017Cenie pliku log\u00F3w +log_function_comment=Dodatkowy komentarz (opcjonalnie) +log_function_level=Szczeg\u00F3\u0142owo\u015B\u0107 logowania (domy\u015Blnie INFO) lub OUT lub ERR +log_only=Zapisuj do loga/Wy\u015bwietlaj tylko: +log_success_only=Sukcesy +mail_reader_account=U\u017Cytkownik: +mail_reader_password=Has\u0142o: +mail_reader_server=Serwer: +mail_reader_server_type=Typ serwera: +mail_sent=Wiadomo\u015B\u0107 zosta\u0142a wys\u0142ana +max=Maksimum +menu_assertions=Assercje +menu_close=Zamknij +menu_collapse_all=Zwi\u0144 wszystko +menu_config_element=Element konfiguruj\u0105cy +menu_edit=Edytuj +menu_expand_all=Rozwi\u0144 wszystko +menu_listener=S\u0142uchacze +menu_merge=Scal +menu_open=Otw\u00F3rz +menu_post_processors=Post Procesory +menu_pre_processors=Pre Procesory +name=Nazwa: +number_of_threads=Liczba w\u0105tk\u00F3w (u\u017Cytkownik\u00F3w): +open=Otw\u00F3rz... +option=Opcje +password=Has\u0142o +paste=Wklej +pl=Polski +property_default_param=Warto\u015B\u0107 domy\u015Blna +property_edit=Edytuj +property_undefined=Niezdefiniowano +property_value_param=Warto\u015B\u0107 parametru +proxy_assertions=Dodaj asercje +proxy_headers=Przechwytuj nag\u0142\u00F3wki HTTP +proxy_sampler_type=Typ: +proxy_title=Serwer proxy HTTP +ramp_up=Uruchom w ci\u0105gu (sekund): +regex_field=Wyra\u017Cenie regularne: +remove=Usu\u0144 +report_chart_x_axis=O\u015B X +report_chart_x_axis_label=Etykieta osi X +report_chart_y_axis=O\u015B Y +report_chart_y_axis_label=Etykieta osi Y +report_line_graph=Wykres liniowy +report_page_title=Tytu\u0142 strony +reset_gui=Resetuj Gui +resultsaver_prefix=Prefiks do nazwy pliku: +revert_project=Cofnij +run=Uruchom +sample_scope=Kt\u00F3re pr\u00F3bki testowa\u0107 +sampler_label=Etykieta +sampler_on_error_action=Co robi\u0107 je\u015Bli Pr\u00F3bnik zg\u0142osi b\u0142\u0105d? +sampler_on_error_continue=Kontunuuj +sampler_on_error_stop_test=Przerwij test +sampler_on_error_stop_test_now=Natychmiast przerwij test +sampler_on_error_stop_thread=Przerwij w\u0105tek +save=Zapisz +save?=Zapisa\u0107? +save_all_as=Zapisz plan test\u00F3w jako +save_as=Zapisz zaznaczenie jako... +save_as_image=Zapisz w\u0119ze\u0142 jako obrazek +save_as_image_all=Zapisz ekran jako obrazek +save_asxml=Zapisz jako XML +save_bytes=Zapisz liczb\u0119 bit\u00F3w +save_code=Zapisz kod odpowiedzi +save_datatype=Zapisz typ danych +save_encoding=Zapisz stron\u0119 kodow\u0105 +save_fieldnames=Zapisz nazwy kolumn (CSV) +scheduler=Kalendarz +scheduler_configuration=Konfiguracja kalendarza +scope=Zakres +second=sekund +send_file_browse=Przegl\u0105daj ... +send_file_filename_label=\u015acie\u017cka do pliku: +ssl_pass_prompt=Prosz\u0119 wpisz has\u0142o +template_field=Szablon: +test_action_duration=Czas trwania (milisekund) +test_action_pause=Pauza +test_plan=Plan test\u00F3w +test_plan_classpath_browse=Dodaj katalog lub jara do classpatha +testplan.serialized=Uruchamiaj grupy w\u0105tk\u00F3w jedna po drugiej (tzn. jedn\u0105 na raz) +testplan_comments=Uwagi: +thread_group_title=Grupa w\u0105tk\u00F3w +thread_properties=W\u0105tki +threadgroup=Grupa w\u0105tk\u00F3w +user_defined_test=Test zdefiniowany przez u\u017Cytkownika +user_defined_variables=Zmienne zdefiniowane przez u\u017Cytkownika +user_parameters_table=Parametry +user_parameters_title=Parametry u\u017Cytkownika +userdn=U\u017Cytkownik +username=U\u017Cytkownik +userpw=Has\u0142o +value=Warto\u015B\u0107: +workbench_title=Brudnopis +xpath_tidy_show_warnings=Pokazuj ostrze\u017Cenia +you_must_enter_a_valid_number=Tu trzeba wpisa\u0107 liczb\u0119 +zh_cn=Chi\u0144ski (Uproszczony) +zh_tw=Chi\u0144ski (Tradycyjny) diff --git a/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_pt_BR.properties b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_pt_BR.properties new file mode 100644 index 0000000..c7c8bcd --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_pt_BR.properties @@ -0,0 +1,902 @@ +# 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. + +# Warning: JMeterUtils.getResString() replaces space with '_' +# and converts keys to lowercase before lookup +# => All keys in this file must also be lower case or they won't match +# + +# Please add new entries in alphabetical order + +about=Sobre Apache JMeter +add=Adicionar +add_as_child=Adicionar como filho +add_parameter=Adicionar Var\u00E1vel +add_pattern=Adicionar Padr\u00E3o\: +add_test=Adicionar Teste +add_user=Adicionar Usu\u00E1rio +add_value=Adicionar Valor +addtest=Adicionar teste +aggregate_graph=Gr\u00E1ficos Estat\u00EDsticos +aggregate_graph_column=Coluna +aggregate_graph_display=Exibir Gr\u00E1fico +aggregate_graph_height=Altura +aggregate_graph_max_length_xaxis_label=Largura m\u00E1xima do r\u00F3tulo do eixo x +aggregate_graph_ms=Milisegundos +aggregate_graph_response_time=Tempo de Tesposta +aggregate_graph_save=Salvar Gr\u00E1fico +aggregate_graph_save_table=Salvar Dados da Tabela +aggregate_graph_save_table_header=Salvar Cabe\u00E7alho da Tabela +aggregate_graph_title=Gr\u00E1fico Agregado +aggregate_graph_use_group_name=Incluir nome do grupo no r\u00F3tulo? +aggregate_graph_user_title=T\u00EDtulo para o Gr\u00E1fico +aggregate_graph_width=Largura +aggregate_report=Relat\u00F3rio Agregado +aggregate_report_90%_line=Linha de 90% +aggregate_report_bandwidth=KB/s +aggregate_report_count=\# Amostras +aggregate_report_error=Erro +aggregate_report_error%=% de Erro +aggregate_report_max=M\u00E1x. +aggregate_report_median=Mediana +aggregate_report_min=M\u00EDn. +aggregate_report_rate=Vaz\u00E3o +aggregate_report_stddev=Desvio Padr\u00E3o +ajp_sampler_title=Testador AJP/1.3 +als_message=Nota\: O Processador de Logs de Acesso \u00E9 gen\u00E9rico em seu projeto e permite que voc\u00EA o especialize +als_message2=seu pr\u00F3prio processador. Para tanto, implementar o LogParser, e adicionar o arquivo jar +als_message3=diret\u00F3rio /lib e entre com a classe no testador +analyze=Analizar Arquivo de Dados... +anchor_modifier_title=Processador de Links HTML +appearance=Apar\u00EAncia +argument_must_not_be_negative=O Argumento n\u00E3o pode ser negativo\! +assertion_assume_success=Ignorar estado +assertion_code_resp=C\u00F3digo de Resposta +assertion_contains=Cont\u00E9m +assertion_equals=Igual +assertion_headers=Cabe\u00E7alhos da Resposta +assertion_matches=Combina +assertion_message_resp=Mensagem da Resposta +assertion_not=N\u00E3o +assertion_pattern_match_rules=Regras para Combina\u00E7\u00E3o de Padr\u00F5es +assertion_patterns_to_test=Padr\u00F5es a serem Testados +assertion_resp_field=Testar que Campo da Resposta +assertion_text_resp=Resposta de Texto +assertion_textarea_label=Asser\u00E7\u00F5es\: +assertion_title=Asser\u00E7\u00F5es de Resposta +assertion_url_samp=URL Amostrada +assertion_visualizer_title=Resultados de Asser\u00E7\u00E3o +attribute=Atributo +attrs=Atributos +auth_base_url=URL Base +auth_manager_title=Gerenciador de Autoriza\u00E7\u00E3o HTTP +auths_stored=Autoriza\u00E7\u00F5es Armazenadas no Gerenciador de Autoriza\u00E7\u00E3o +average=M\u00E9dia +average_bytes=M\u00E9dia de Bytes +bind=Liga\u00E7\u00E3o do Usu\u00E1rio Virtual +browse=Procurar... +bsf_sampler_title=Testador BSF +bsf_script=Script a ser executado (vari\u00E1veis\: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +bsf_script_file=Arquivo de script a ser executado +bsf_script_language=Linguagem de scripting\: +bsf_script_parameters=Par\u00E2metros a serem passados ao script/arquivo\: +bsh_assertion_script=Script (veja abaixo quais vari\u00E1veis que est\u00E3o definidas) +bsh_assertion_script_variables=As seguintes vari\u00E1veis est\u00E3o definidas para o script\:\nEscrita/Leitura\: Failure, FailureMessage, SampleResult, vars, props, log.\nSomente Leitura\: Response[Data|Code|Message|Headers], RequestHeaders, SampleLabel, SamplerData, ctx +bsh_assertion_title=Asser\u00E7\u00E3o BeanShell +bsh_function_expression=Express\u00E3o a ser avaliada +bsh_sampler_title=Testador BeanShell +bsh_script=Script (veja abaixo quais vari\u00E1veis est\u00E3o definidas) +bsh_script_file=Arquivo de script +bsh_script_parameters=Par\u00E2metros (\=> String Parameters e String []bsh.args) +bsh_script_reset_interpreter=Reiniciar bsh.Interpreter antes de cada chamada +bsh_script_variables=As seguintes vari\u00E1veis est\u00E3o definidas para o script\:\nSampleResult, RespondeCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +busy_testing=Eu estou ocupado testando, por favor pare o teste antes de alterar as configura\u00E7\u00F5es +cache_manager_title=Gerenciador de Cache HTTP +cache_session_id=Fazer cache do ID da sess\u00E3o? +cancel=Cancelar +cancel_exit_to_save=Existem itens n\u00E3o salvos. Voc\u00EA deseja salvar antes de sair? +cancel_new_to_save=Existem itens n\u00E3o salvos. Voc\u00EA deseja salvar antes de limpar o plano de teste? +cancel_revert_project=Existem itens n\u00E3o salvos. Voc\u00EA deseja reverter para o plano de teste salvo previamente? +char_value=N\u00FAmero unicode do caracter (decimal ou 0xhex) +choose_function=Escolher Fun\u00E7\u00E3o +choose_language=Escolher Linguagem +clear=Limpar +clear_all=Limpar Tudo +clear_cache_per_iter=Limpar cache a cada itera\u00E7\u00E3o? +clear_cookies_per_iter=Limpar cookies a cada itera\u00E7\u00E3o? +column_delete_disallowed=N\u00E3o \u00E9 permitida a exclus\u00E3o desta coluna. +column_number=N\u00FAmero da coluna no arquivo CSV | pr\u00F3x | *apelido +compare=Comparar +comparefilt=Filtro de compara\u00E7\u00E3o +config_element=Elemento de Configura\u00E7\u00E3o +config_save_settings=Configurar +configure_wsdl=Configurar +constant_throughput_timer_memo=Adicionar um atraso entre amostragens para obter vaz\u00E3o constante +constant_timer_delay=Atraso do usu\u00E1rio virtual (em milisegundos) +constant_timer_memo=Adicionar um atraso constante entre amostragens +constant_timer_title=Temporizador Constante +content_encoding=Codifica\u00E7\u00E3o do conte\u00FAdo\: +controller=Controlador +cookie_manager_policy=Pol\u00EDtica de Cookie +cookie_manager_title=Gerenciador de Cookie HTTP +cookies_stored=Cookies Definidos pelo Usu\u00E1rio +copy=Copiar +counter_config_title=Contador +counter_per_user=Realiza contagem independentemente para cada usu\u00E1rio +countlim=Tamanho limite +csvread_file_file_name=Arquivo CSV de onde os valores ser\u00E3o obtidos | *apelido +cut=Recortar +cut_paste_function=Copiar e colar texto da fun\u00E7\u00E3o +database_conn_pool_max_usage=Uso M\u00E1ximo Para Cada Conex\u00E3o\: +database_conn_pool_props=Grupo de Conex\u00F5es com o Banco de Dados +database_conn_pool_size=N\u00FAmero de Conex\u00F5es no Grupo de Conex\u00F5es +database_conn_pool_title=Padr\u00F5es JDBC do Grupo de Conex\u00F5es ao Banco de Dados +database_driver_class=Classe do Driver\: +database_login_title=Padr\u00F5es JDBC para Acesso ao Banco de Dados +database_sql_query_string=Consulta SQL\: +database_sql_query_title=Padr\u00F5es JDBC para Consultas SQL +database_testing_title=Requisi\u00E7\u00E3o JDBC +database_url=URL JDBC\: +database_url_jdbc_props=URL do Banco de Dados e Driver JDBC +de=Alem\u00E3o +debug_off=Desabilitar debug +debug_on=Habilitar debug +default_parameters=Par\u00E2metros Padr\u00E3o +default_value_field=Valor Padr\u00E3o\: +delay=Atraso para in\u00EDcio (segundos) +delete=Excluir +delete_parameter=Excluir Vari\u00E1vel +delete_test=Excluir Teste +delete_user=Excluir Usu\u00E1rio +deltest=Teste de exclus\u00E3o +deref=Dereferenciar apelidos +disable=Desabilitar +distribution_graph_title=Gr\u00E1fico de Distribui\u00E7\u00E3o (alfa) +distribution_note1=O gr\u00E1fico ser\u00E1 atualizado a cada 10 amostras +domain=Dom\u00EDnio +done=Pronto +duration=Dura\u00E7\u00E3o (segundos) +duration_assertion_duration_test=Dura\u00E7\u00E3o para Avaliar +duration_assertion_failure=A opera\u00E7\u00E3o tomou muito tempo\: levou {0} milisegundos, mas n\u00E3o deveria ter levado mais do que {1} milisegundos +duration_assertion_input_error=Favor entrar com um inteiro positivo v\u00E1lido. +duration_assertion_label=Dura\u00E7\u00E3o em milisegundos\: +duration_assertion_title=Asser\u00E7\u00E3o de Dura\u00E7\u00E3o +edit=Editar +email_results_title=Enviar Resultados por Email +en=Ingl\u00EAs +enable=Habilitar +encode?=Codificar? +encoded_value=Valor da URL Codificada +endtime=Tempo de T\u00E9rmino +entry_dn=Entrada DN +entrydn=Entrada DN +error_loading_help=Erro ao carregar p\u00E1gina de ajuda +error_occurred=Um erro ocorreu +error_title=Erro +es=Espanhol +escape_html_string=String a ser escapada +eval_name_param=Texto contendo refer\u00EAncias de fun\u00E7\u00F5es e vari\u00E1veis +evalvar_name_param=Nome da vari\u00E1vel +example_data=Dados da amostra +example_title=Testador de Exemplo +exit=Sair +expiration=Expira\u00E7\u00E3o +field_name=Nome do campo +file=Arquivo +file_already_in_use=Este arquivo j\u00E1 est\u00E1 em uso +file_visualizer_append=Adicionar a um Arquivo de Dados Existente +file_visualizer_auto_flush=Automaticamente descarregar dados (flush) ap\u00F3s cada amostra de dados +file_visualizer_browse=Procurar... +file_visualizer_close=Fechar +file_visualizer_file_options=Op\u00E7\u00F5es do Arquivo +file_visualizer_filename=Nome do arquivo +file_visualizer_flush=Descarregar (flush) +file_visualizer_missing_filename=N\u00E3o foi especificado nenhum nome de arquivo de sa\u00EDda. +file_visualizer_open=Abrir +file_visualizer_output_file=Escrever resultados para arquivo / Ler a partir do arquivo +file_visualizer_submit_data=Incluir Dados Enviados +file_visualizer_title=Relat\u00F3rios de Arquivo +file_visualizer_verbose=Sa\u00EDda detalhada (verbose) +filename=Nome do arquivo +follow_redirects=Seguir redire\u00E7\u00F5es +follow_redirects_auto=Redirecionar automaticamente +foreach_controller_title=Controlador ParaCada (ForEach) +foreach_input=Prefixo da vari\u00E1vel de entrada +foreach_output=Nome da vari\u00E1vel de sa\u00EDda +foreach_use_separator=Adicionar "_" antes do n\u00FAmero? +format=Formato do n\u00FAmero +fr=Franc\u00EAs +ftp_binary_mode=Usar modo bin\u00E1rio? +ftp_local_file=Arquivo local\: +ftp_local_file_contents=Conte\u00FAdo do Arquivo Local\: +ftp_remote_file=Arquivo remoto\: +ftp_sample_title=Padr\u00F5es para Requisi\u00E7ao FTP +ftp_save_response_data=Salvar Arquivos na Resposta? +ftp_testing_title=Requisi\u00E7\u00E3o FTP +function_dialog_menu_item=Di\u00E1logo de Fun\u00E7\u00E3o de Ajuda +function_helper_title=Fun\u00E7\u00E3o de Ajuda +function_name_param=Nome da vari\u00E1vel na qual ser\u00E1 armazenado o resultado (requerido) +function_name_paropt=Nome da vari\u00E1vel na qual ser\u00E1 armazenado o resultado (opcional) +function_params=Par\u00E2metros da Fun\u00E7\u00E3o +functional_mode=Modo de Teste Funcional (ex\: salvar dados das respostas e dados dos testadores) +functional_mode_explanation=Selecionando Modo de Teste Funcional pode afetar o desempenho de modo adverso. +gaussian_timer_delay=Offset do Atraso Constante (em milisegundos)\: +gaussian_timer_memo=Adiciona um atraso aleat\u00F3rio atrav\u00E9s de uma distribui\u00E7\u00E3o gaussiana. +gaussian_timer_range=Desvio (em milisegundos)\: +gaussian_timer_title=Temporizador Aleat\u00F3rio Gaussiano +generate=Gerar +generator=Nome da classe Geradora +generator_cnf_msg=N\u00E3o foi poss\u00EDvel encontrar a classe geradora. Verifique se voc\u00EA colocou o jar correto no diret\u00F3rio /lib. +generator_illegal_msg=N\u00E3o foi poss\u00EDvel acessar a classe geradora devido a uma IllegalAccessException. +generator_instantiate_msg=N\u00E3o foi poss\u00EDvel criar uma inst\u00E2ncia do processador gerador. Verifique se o gerador implementa a interface Generator. +get_xml_from_file=Arquivo com dados XML SOAP (substitui o texto acima) +get_xml_from_random=Diret\u00F3rio das Mensagens +graph_choose_graphs=Gr\u00E1ficos a serem Exibidos +graph_full_results_title=Gr\u00E1fico de Resultados Completos +graph_results_average=M\u00E9dia +graph_results_data=Dados +graph_results_deviation=Desvio +graph_results_latest_sample=\u00DAltima Amostra +graph_results_median=Mediana +graph_results_no_samples=N\u00FAm. de Amostras +graph_results_throughput=Vaz\u00E3o +graph_results_title=Gr\u00E1fico de Resultados +grouping_add_separators=Adicionar separadores entre grupos +grouping_in_controllers=Colocar cada grupo em um novo controlador +grouping_mode=Agrupamento\: +grouping_no_groups=N\u00E3o agrupar testadores +grouping_store_first_only=Armazenar primeiro testador de cada grupo apenas +header_manager_title=Gerenciador de Cabe\u00E7alhos HTTP +headers_stored=Cabe\u00E7alhos Armazenados no Gerenciador de Cabe\u00E7alhos +help=Ajuda +help_node=O que \u00E9 este n\u00F3? +html_assertion_file=Escrever relat\u00F3rio do JTidy em arquivo +html_assertion_label=Asser\u00E7\u00E3o HTML +html_assertion_title=Asser\u00E7\u00E3o HTML +html_parameter_mask=M\u00E1scara de Par\u00E2metro HTML +http_implementation=Implementa\u00E7\u00E3o\: +http_response_code=C\u00F3digo da Resposta HTTP +http_url_rewriting_modifier_title=Modificador de Re-escrita de URL HTTP +http_user_parameter_modifier=Modificador de Par\u00E2metros HTTP do Usu\u00E1rio +httpmirror_title=Servidor Espelho HTTP +id_prefix=Prefixo do ID +id_suffix=Sufixo do ID +if_controller_evaluate_all=Avaliar para todos os filhos? +if_controller_expression=Interpretar Condi\u00E7\u00E3o como Express\u00E3o de Vari\u00E1vel? +if_controller_label=Condi\u00E7\u00E3o (padr\u00E3o\: Javascript) +if_controller_title=Controlador Se +ignore_subcontrollers=Ignorar blocos de sub-controladores +include_controller=Controlador de Inclus\u00E3o +include_equals=Incluir Igual? +include_path=Incluir Plano de Teste +increment=Incremento +infinite=Infinito +initial_context_factory=F\u00E1brica de Contexto Inicial +insert_after=Inserir Depois +insert_before=Inserir Antes +insert_parent=Inserir como Pai +interleave_control_title=Controlador de Intercala\u00E7\u00E3o +intsum_param_1=Primeiro inteiro para adicionar. +intsum_param_2=Segundo inteiro a ser adicionado - posteriormente inteiros podem ser somados atrav\u00E9s da adi\u00E7\u00E3o de novos argumentos. +invalid_data=Dados inv\u00E1lidos +invalid_mail=Ocorreu um erro ao enviar o e-mail +invalid_mail_address=Foram detectados um ou mais endere\u00E7os de email inv\u00E1lidos +invalid_mail_server=Houve um problema ao contactar o servidor de email (veja arquivo de log do JMeter) +invalid_variables=Vari\u00E1veis inv\u00E1lidas +iteration_counter_arg_1=TRUE, para que cada usu\u00E1rio tenha seu pr\u00F3prio contador, FALSE para um contador global +iterator_num=Contador de Itera\u00E7\u00E3o +ja=Japon\u00EAs +jar_file=Arquivos Jar +java_request=Requisi\u00E7\u00E3o Java +java_request_defaults=Padr\u00F5es de Requisi\u00E7\u00E3o Java +javascript_expression=Express\u00E3o JavaScript a ser avaliada +jexl_expression=Express\u00E3o JEXL a ser avaliada +jms_auth_required=Requerido +jms_client_caption=Cliente recebedor usa TopicSubscriber.receive() para aguardar uma mensagem. +jms_client_caption2=MessageListener usa a interface onMessage(Message) para aguardar novas mensagens. +jms_client_type=Cliente +jms_communication_style=Estilo de comunica\u00E7\u00E3o +jms_concrete_connection_factory=F\u00E1brica Concreta de Conex\u00E3o +jms_config=Configura\u00E7\u00E3o +jms_config_title=Configura\u00E7\u00E3o JMS +jms_connection_factory=F\u00E1brica de Conex\u00E3o +jms_file=Arquivo +jms_initial_context_factory=F\u00E1brica de Contexto Inicial +jms_itertions=N\u00FAmeros de amostras para agregar +jms_jndi_defaults_title=Configura\u00E7\u00E3o Padr\u00E3o de JNDI +jms_jndi_props=Propriedades JNDI +jms_message_title=Propriedades das mensagens +jms_message_type=Tipo das Mensagens +jms_msg_content=Conte\u00FAdo +jms_object_message=Mensagens de Objetos +jms_point_to_point=JMS Ponto a Ponto +jms_props=Propriedades JMS +jms_provider_url=URL do Provedor +jms_publisher=Publicador JMS +jms_pwd=Senha +jms_queue=Fila +jms_queue_connection_factory=F\u00E1brica de Conex\u00F5es em Fila +jms_queueing=Recursos JMS +jms_random_file=Arquivo Aleat\u00F3rio +jms_read_response=Resposta Lida +jms_receive_queue=Nome da Fila de Recebimento JNDI +jms_request=Somente Requisitar +jms_requestreply=Requisitar e Responder +jms_sample_title=Padr\u00F5es de Requisi\u00E7\u00E3o JMS +jms_send_queue=Fila de Requisi\u00E7\u00E3o de nomes JNDI +jms_subscriber_on_message=Utilizar MessageListener.onMessage() +jms_subscriber_receive=Usar TopicSubscriber.receive() +jms_subscriber_title=Assinante JMS +jms_testing_title=Requisi\u00E7\u00E3o de Mensagens +jms_text_message=Mensagem de Texto +jms_timeout=Tempo limite (timeout) em milisegundos +jms_topic=T\u00F3pico +jms_use_auth=Usar Autoriza\u00E7\u00E3o? +jms_use_file=Do Arquivo +jms_use_non_persistent_delivery=Utilizar modo de entrega n\u00E3o persistente +jms_use_properties_file=Utilizar aquivo jndi.properties +jms_use_random_file=Arquivo Aleat\u00F3rio +jms_use_req_msgid_as_correlid=Utilizar ID de Mensagem de Requisi\u00E7\u00E3o como ID de Correla\u00E7\u00E3o +jms_use_text=\u00C1rea de texto +jms_user=Usu\u00E1rio +jndi_config_title=Configura\u00E7\u00E3o JNDI +jndi_lookup_name=Interface Remota +jndi_lookup_title=Configura\u00E7\u00E3o de Lookup JNDI +jndi_method_button_invoke=Invocar +jndi_method_button_reflect=Refletir +jndi_method_home_name=Nome do M\u00E9todo Home +jndi_method_home_parms=Par\u00E2metros do M\u00E9todo Home +jndi_method_name=Configura\u00E7\u00E3o do M\u00E9todo +jndi_method_remote_interface_list=Interfaces Remotas +jndi_method_remote_name=Nome do M\u00E9todo Remoto +jndi_method_remote_parms=Par\u00E2metros do M\u00E9todo Remoto +jndi_method_title=Configura\u00E7\u00E3o do M\u00E9todo Remoto +jndi_testing_title=Requisi\u00E7\u00E3o JNDI +jndi_url_jndi_props=Propriedades JNDI +junit_append_error=Adicionar erros de asser\u00E7\u00E3o +junit_append_exception=Adicionar exce\u00E7\u00F5es de tempo de execu\u00E7\u00E3o +junit_constructor_error=N\u00E3o foi poss\u00EDvel criar uma inst\u00E2ncia da classe +junit_constructor_string=R\u00F3tulo String do Construtor +junit_do_setup_teardown=N\u00E3o chamar setUp e tearDown +junit_error_code=C\u00F3digo de Erro +junit_error_default_msg=Houve um erro inesperado +junit_error_msg=Mensagem de Erro +junit_failure_code=C\u00F3digo da Falha +junit_failure_default_msg=O teste falhou +junit_failure_msg=Mensagns de Falha +junit_pkg_filter=Filtro de Pacote +junit_request=Requisi\u00E7\u00E3o JUnit +junit_request_defaults=Padr\u00F5es de Requisi\u00E7\u00E3o JUnit +junit_success_code=C\u00F3digo de Sucesso +junit_success_default_msg=Teste com sucesso +junit_success_msg=Mensagem de Sucesso +junit_test_config=Par\u00E2metros de Teste JUnit +junit_test_method=M\u00E9todo de Teste +ldap_argument_list=Lista de Argumentos LDAP +ldap_connto=Tempo limite de Conex\u00E3o (timeout) em milisegundos +ldap_parse_results=Processar os resultados da busca? +ldap_sample_title=Padr\u00F5es de Requisi\u00E7\u00E3o LDAP +ldap_search_baseobject=Realizar busca b\u00E1sica de objetos +ldap_search_onelevel=Realizar busca de um n\u00EDvel +ldap_search_subtree=Realizar busca em sub-\u00E1rvore +ldap_secure=Utilizar Protocolo LDAP Seguro? +ldap_testing_title=Requisi\u00E7\u00E3o LDAP +ldapext_sample_title=Padr\u00F5es de Requisi\u00E7\u00E3o LDAP Estendidas +ldapext_testing_title=Requisi\u00E7\u00E3o LDAP Estendida +library=Biblioteca +load=Carregar +load_wsdl=Carregar WSDL +log_errors_only=Erros +log_file=Localiza\u00E7\u00E3o do arquivo de log +log_function_comment=Coment\u00E1rios adicionais (opcional) +log_function_level=N\u00EDvel de log (padr\u00E3o INFO) ou OUT ou ERR +log_function_string=String a ser logada +log_function_string_ret=String a ser logada (e retornada) +log_function_throwable=Texto a ser lan\u00E7ado (opcional) +log_only=Apenas Logar/Exibir +log_parser=Nome da Classe Processadora de Logs +log_parser_cnf_msg=N\u00E3o foi poss\u00EDvel encontrar a classe. Verifique se o jar se encontra no diret\u00F3rio /lib. +log_parser_illegal_msg=N\u00E3o foi poss\u00EDvel acessar a classe devido a IllegalAccessException. +log_parser_instantiate_msg=N\u00E3o foi poss\u00EDvel criar uma inst\u00E2ncia do processador de logs. Verifique se o processador implementa a interface LogParser. +log_sampler=Testador de Log de Acessp do Tomcat +log_success_only=Sucessos +logic_controller_title=Controlador Simples +login_config=Configura\u00E7\u00E3o de Login +login_config_element=Elemento de Configura\u00E7\u00E3o de Login +longsum_param_1=Primeiro long a ser adicionado +longsum_param_2=Segundo long a ser adicionado - adicionalmente novos longs podem ser somados atrav\u00E9s da adi\u00E7\u00E3o de novos argumentos +loop_controller_title=Controlador de Itera\u00E7\u00E3o +looping_control=Controle de Itera\u00E7\u00E3o +lower_bound=Limite Inferior +mail_reader_account=Nome do usu\u00E1rio\: +mail_reader_all_messages=Todos +mail_reader_delete=Excluir mensagens do servidor +mail_reader_folder=Diret\u00F3rio\: +mail_reader_num_messages=N\u00FAmero de mensagens a serem recuperadas\: +mail_reader_password=Senha\: +mail_reader_server=Servidor\: +mail_reader_server_type=Tipo do Servidor\: +mail_reader_storemime=Armazenar as mensagens utilizando MIME +mail_reader_title=Testador Leitor de Emails +mail_sent=Email enviado com sucesso +mailer_attributes_panel=Atributos de envio de emails +mailer_error=N\u00E3o foi poss\u00EDvel enviar email. Favor verificar as configura\u00E7\u00F5es. +mailer_visualizer_title=Vizualizador de Email +match_num_field=N\u00FAmero para Combina\u00E7\u00E3o (0 para aleat\u00F3rio) +max=M\u00E1ximo +maximum_param=O valor m\u00E1ximo permitido para um intervalo de valores +md5hex_assertion_failure=Erro avaliando soma MD5\: encontrado {0} mas deveria haver {1} +md5hex_assertion_md5hex_test=MD5Hex para Asser\u00E7\u00E3o +md5hex_assertion_title=Asser\u00E7\u00E3o MD5Hex +memory_cache=Cache em Mem\u00F3ria +menu_assertions=Asser\u00E7\u00F5es +menu_close=Fechar +menu_collapse_all=Fechar Todos +menu_config_element=Elemento de Configura\u00E7\u00E3o +menu_edit=Editar +menu_expand_all=Expandir Todos +menu_generative_controller=Testador +menu_listener=Ouvinte +menu_logic_controller=Controlador L\u00F3gico +menu_merge=Mesclar +menu_modifiers=Modificadores +menu_non_test_elements=Elementos que n\u00E3o s\u00E3o de Teste +menu_open=Abrir +menu_post_processors=P\u00F3s-Processadores +menu_pre_processors=Pr\u00E9-Processadores +menu_response_based_modifiers=Modificadores Baseados na Resposta +menu_timer=Temporizador +metadata=Metadados +method=M\u00E9todo\: +minimum_param=Valor m\u00EDnimo permitido para um intervalo de valores +minute=minuto +modddn=Velho nome da entrada +modification_controller_title=Controlador de Modifica\u00E7\u00E3o +modification_manager_title=Gerenciador de Modifica\u00E7\u00E3o +modify_test=Modificar Teste +modtest=Teste de Modifica\u00E7\u00E3o +module_controller_module_to_run=M\u00F3dulo a ser executado +module_controller_title=Controlador de M\u00F3dulo +module_controller_warning=N\u00E3o foi poss\u00EDvel encontrar o m\u00F3dulo\: +monitor_equation_active=Ativo\: (ocupado / max) > 25% +monitor_equation_dead=Morto\: sem resposta +monitor_equation_healthy=Saud\u00E1vel\: (ocupado / max) < 25% +monitor_equation_load=Carga\: ((ocupado / max) * 50) + ((mem\u00F3ria utilizada / max mem\u00F3ria) * 50) +monitor_equation_warning=Alerta\: (ocupado / max) > 67% +monitor_health_tab_title=Sa\u00FAde +monitor_health_title=Monitorar Resultados +monitor_is_title=Usar como Monitor +monitor_label_prefix=Prefixo de Conex\u00E3o +monitor_label_right_active=Ativo +monitor_label_right_dead=Morto +monitor_label_right_healthy=Saud\u00E1vel +monitor_label_right_warning=Alerta +monitor_legend_health=Sa\u00FAde +monitor_legend_load=Carga +monitor_legend_memory_per=Mem\u00F3ria % (usada / total) +monitor_legend_thread_per=Thread % (ocupado / max) +monitor_performance_servers=Servidores +monitor_performance_tab_title=Desempenho +monitor_performance_title=Gr\u00E1fico de Desempenho +name=Nome\: +new=Novo +newdn=Novo nome distingu\u00EDvel +no=Noruegu\u00EAs +number_of_threads=N\u00FAmero de Usu\u00E1rios Virtuais (threads)\: +obsolete_test_element=O elemento de teste est\u00E1 obsoleto. +once_only_controller_title=Controlador de Uma \u00DAnica Vez +open=Abrir... +option=Op\u00E7\u00F5es +optional_tasks=Tarefas Opcionais +paramtable=Enviar Par\u00E2metros Com a Requisi\u00E7\u00E3o +password=Senha +paste=Colar +paste_insert=Colar como Inser\u00E7\u00E3o +path=Caminho\: +path_extension_choice=Extens\u00F5es do Caminho (usar ";" como separador) +path_extension_dont_use_equals=N\u00E3o usar igual nas extens\u00F5es do caminho (compatibilidae com Intershop Enfinity) +path_extension_dont_use_questionmark=N\u00E3o utilizar s\u00EDmbolo de interroga\u00E7\u00E3o nas extens\u00F5es do caminho (Compatibilidade com Intershop Enfinity) +patterns_to_exclude=Padr\u00F5es de URL a serem exclu\u00EDdos +patterns_to_include=Padr\u00F5es de URL a serem inclu\u00EDdos +pl=Polon\u00EAs +port=Porta\: +property_default_param=Valor padr\u00E3o +property_edit=Editar +property_editor.value_is_invalid_message=O texto informado n\u00E3o \u00E9 um valor v\u00E1lido para esta propriedade.\nA propriedade ser\u00E1 revertida para o seu valor pr\u00E9vio. +property_editor.value_is_invalid_title=Entrada inv\u00E1lida. +property_name_param=Nome da propriedade +property_returnvalue_param=Retornar Valor Original da Propriedade (padr\u00E3o\: false)? +property_undefined=Indefinido +property_value_param=Valor da propriedade +property_visualiser_title=Exibi\u00E7\u00E3o da Propriedade +protocol=Protocolo [http]\: +protocol_java_border=Classe Java +protocol_java_classname=Nome da classe\: +protocol_java_config_tile=Configurar amostra Java +protocol_java_test_title=Teste Java +provider_url=URL do Provedor +proxy_assertions=Adicionar Asser\u00E7\u00F5es +proxy_cl_error=Se estiver especificando um servidor proxy, nome do servidor e porta precisam ser informados +proxy_content_type_exclude=Excluir\: +proxy_content_type_filter=Filtro de tipo de conte\u00FAdo\: +proxy_content_type_include=Incluir\: +proxy_daemon_bind_error=N\u00E3o foi poss\u00EDvel criar o proxy - porta em uso\: Escolha outra porta. +proxy_daemon_error=N\u00E3o foi poss\u00EDvel criar o proxy - veja log para detalhes +proxy_headers=Capturar Cabe\u00E7alhos HTTP +proxy_httpsspoofing=Tentar ataque HTTPS (spoofing) +proxy_httpsspoofing_match=String de combina\u00E7\u00E3o de URL opcional\: +proxy_regex=Combina\u00E7\u00E3o de express\u00E3o regular +proxy_sampler_settings=Configura\u00E7\u00F5es do Testador HTTP +proxy_sampler_type=Tipo\: +proxy_separators=Adicionar Separadores +proxy_target=Controlador alvo\: +proxy_test_plan_content=Conte\u00FAdo do Plano de Teste +proxy_title=Servidor HTTP Proxy +pt_br=Portugu\u00EAs (Brasileiro) +ramp_up=Tempo de inicializa\u00E7\u00E3o (em segundos) +random_control_title=Controlador Aleat\u00F3rio +random_order_control_title=Controlador de Ordem Aleat\u00F3ria +read_response_message=N\u00E3o est\u00E1 configurado para ler a resposta. Para ver a resposta, favor marcar esta op\u00E7\u00E3o no testador. +read_response_note=Se ler resposta est\u00E1 desmarcado, o testador n\u00E3o ir\u00E1 ler a resposta +read_response_note2=ou configurar o SampleResult. Isto melhora o desempenho, mas significa que +read_response_note3=o conte\u00FAdo da resposta n\u00E3o ser\u00E1 logado. +read_soap_response=Ler Respostas SOAP +realm=Reino (realm) +record_controller_title=Controlador de Grava\u00E7\u00E3o +ref_name_field=Nome de Refer\u00EAncia\: +regex_extractor_title=Extractor de Express\u00E3o Regular +regex_field=Express\u00E3o Regular +regex_source=Campo da Resposta a ser verificado +regex_src_body=Corpo (body) +regex_src_body_unescaped=Corpo (body) - n\u00E3o escapado +regex_src_hdrs=Cabe\u00E7alhos (headers) +regexfunc_param_1=Express\u00E3o regular usada para buscar amostras anteriores - ou vari\u00E1vel. +regexfunc_param_2=Modelo (template) para substitui\u00E7\u00E3o de string, usando grupos de express\u00F5es reuglares. Formato \u00E9 ${grupo}$. Exemplo $1$. +regexfunc_param_3=Que combina\u00E7\u00E3o usar. Um inteiro 1 ou maior, RAND indica que JMeter deve escolher aleatoriamente, um ponto flutuante (float) ou ALL indicando todas as combina\u00E7\u00F5es devem ser usadas ([1]) +regexfunc_param_4=Entre texto. Se ALL est\u00E1 selecionado, o que estiver ENTRE o texto informado ser\u00E1 utilizado para gerar os resultados ([""]) +regexfunc_param_5=Texto padr\u00E3o. Usado no lugar do modelo (template) se a express\u00E3o regular n\u00E3o econtrar nenhuma combina\u00E7\u00E3o ([""]) +regexfunc_param_7=Nome da vari\u00E1vel de entrada contidas no texto a ser processado ([amostra anterior]) +remote_error_init=Erro inicializando o servidor remoto +remote_error_starting=Erro inicializando o servidor remoto +remote_exit=Sa\u00EDda Remota +remote_exit_all=Sair de Todos Remotos +remote_start=Inicializar Remoto +remote_start_all=Inicializar Todos Remotos +remote_stop=Parar Remoto +remote_stop_all=Parar Todos Remotos +remove=Remover +rename=Renomear entrada +report=Relat\u00F3rio +report_bar_chart=Gr\u00E1fico de Barras +report_base_directory=Diret\u00F3rio Base +report_chart_caption=Legenda do Gr\u00E1fico +report_chart_x_axis=Eixo X +report_chart_x_axis_label=R\u00F3tulo do Eixo X +report_chart_y_axis=Eixo Y +report_chart_y_axis_label=R\u00F3tulo do Eixo Y +report_line_graph=Gr\u00E1fico de Linha +report_line_graph_urls=Incluir URLs +report_output_directory=Diret\u00F3rio de Sa\u00EDda do Relat\u00F3rio +report_page=P\u00E1gina do Relat\u00F3rio +report_page_element=Elemento da P\u00E1gina +report_page_footer=Rodap\u00E9 da P\u00E1gina +report_page_header=Cabe\u00E7alho da P\u00E1gina +report_page_index=Criar \u00CDndice de P\u00E1ginas +report_page_intro=P\u00E1gina de Introdu\u00E7\u00E3o +report_page_style_url=URL da folha de estilos (stylesheet) +report_page_title=T\u00EDtulo da P\u00E1gina +report_pie_chart=Gr\u00E1fico de Pizza +report_plan=Plano de Relat\u00F3rio +report_select=Selecionar +report_summary=Sum\u00E1rio do Relat\u00F3rio +report_table=Tabela do Relat\u00F3rio +report_writer=Escritor de Relat\u00F3rio +report_writer_html=Escritor de Relat\u00F3rio HTML +request_data=Requisitar Dados +reset_gui=Reiniciar GUI +response_save_as_md5=Salvar respostas como chave MD5? +restart=Reiniciar +resultaction_title=Manuseador de A\u00E7\u00F5es de Estados do Resultado +resultsaver_errors=Somente Salvar Respostas que Falharam +resultsaver_prefix=Prefixo do nome do arquivo +resultsaver_skipautonumber=N\u00E3o adicionar n\u00FAmeros ao prefixo +resultsaver_success=Somente Salvar Respostas de Sucesso +resultsaver_title=Salvar respostas para arquivo +resultsaver_variable=Nome da Vari\u00E1vel\: +retobj=Objeto de retorno +reuseconnection=Reusar conex\u00E3o +revert_project=Reverter +revert_project?=Reverter projeto? +root=Raiz +root_title=Raiz +run=Executar +running_test=Testes executando +runtime_controller_title=Controlador de Tempo de Execu\u00E7\u00E3o +runtime_seconds=Tempo de execu\u00E7\u00E3o (segundos) +sample_result_save_configuration=Configura\u00E7\u00E3o de Salvar Resultados da Amostra +sample_scope=Quais amostras testar +sample_scope_all=Amostras principais e sub-amostras +sample_scope_children=Somente Sub-amostras +sample_scope_parent=Somente Amostras principais +sampler_label=R\u00F3tulo +sampler_on_error_action=A\u00E7\u00E3o a ser tomada depois de erro do testador +sampler_on_error_continue=Continuar +sampler_on_error_stop_test=Interromper Teste +sampler_on_error_stop_test_now=Interrompe Teste Agora +sampler_on_error_stop_thread=Interromper Usu\u00E1rio Virtual +save=Salvar +save?=Salvar? +save_all_as=Salvar Plano de Teste como +save_as=Salvar Sele\u00E7\u00E3o Como... +save_as_error=Mais de um item selecionado\! +save_as_image=Salvar N\u00F3 como Imagem +save_as_image_all=Salvar Tela Como Imagem +save_assertionresultsfailuremessage=Salvar Mensagens de Falha de Asser\u00E7\u00E3o +save_assertions=Salvar Resultados de Asser\u00E7\u00F5es (XML) +save_asxml=Salvar como XML +save_bytes=Salvar quantidade de bytes +save_code=Salvar C\u00F3digo da Resposta +save_datatype=Salvar Tipo de Dados +save_encoding=Salvar Codifica\u00E7\u00E3o +save_fieldnames=Salvar Nomes dos Campos (CSV) +save_filename=Nome do Arquivo para Salvar Respostas +save_graphics=Salvar Gr\u00E1fico +save_hostname=Salvar Nome do Host +save_label=Salvar R\u00F3tulo +save_latency=Salvar Lat\u00EAncia +save_message=Salvar Mensagens das Respostas +save_overwrite_existing_file=O arquivo selecionado j\u00E1 existe, voc\u00EA quer substitu\u00ED-lo? +save_requestheaders=Salvar Cabe\u00E7alhos das Requisi\u00E7\u00F5es (XML) +save_responsedata=Salvar Dados das Respostas (XML) +save_responseheaders=Salvar Cabe\u00E7alhos das Respostas (XML) +save_samplecount=Salvar Amostra e Contador de Erros +save_samplerdata=Salvar Dados do Testador (XML) +save_subresults=Salvar sub resultados (XML) +save_success=Salvar Sucessos +save_threadcounts=Salvar Contador de Usu\u00E1rios Virtuais Ativos +save_threadname=Salvar Nome do Usu\u00E1rio Virtual +save_time=Salvar Tempo Decorrido +save_timestamp=Salvar Data e Hora +save_url=Salvar URL +sbind=Ligar/Desligar \u00FAnico (single bind/unbind) +scheduler=Agendador +scheduler_configuration=Configura\u00E7\u00E3o do Agendador +scope=Escopo +search_base=Base de busca +search_filter=Filtro de busca +search_test=Teste de busca +searchbase=Base de busca +searchfilter=Filtro de Busca +searchtest=Teste de Busca +second=segundo +secure=Seguro +send_file=Enviar Arquivos com a Requisi\u00E7\u00E3o +send_file_browse=Procurar... +send_file_filename_label=Caminho do Arquivo\: +send_file_param_name_label=Nome do Par\u00E2metro\: +server=Nome do servidor ou IP\: +servername=Nome do servidor\: +session_argument_name=Nome do Argumento de Sess\u00E3o +should_save=Voc\u00EA deveria salvar seu plano de teste antes de execut\u00E1-lo.\nSe voc\u00EA est\u00E1 usando suporte a arquivos de dados (ex\: Conjunto de Dados CSV ou _StringFromFile),\nent\u00E3o \u00E9 particularmente importante salvar seu script de teste.\nVoc\u00EA quer salvar seu plano de teste primeiro? +shutdown=Desligar +simple_config_element=Elemento de Configura\u00E7\u00E3o Simples +simple_data_writer_title=Escritor de Dados Simples +size_assertion_comparator_error_equal=igual a +size_assertion_comparator_error_greater=maior que +size_assertion_comparator_error_greaterequal=maior ou igual que +size_assertion_comparator_error_less=menor que +size_assertion_comparator_error_lessequal=menor ou igual que +size_assertion_comparator_error_notequal=diferente de +size_assertion_comparator_label=Tipo de Compara\u00E7\u00E3o +size_assertion_failure=O resultado estava com tamanho errado\: Tinha {0} bytes, mas deveria ter {1} {2} bytes +size_assertion_input_error=Favor entrar um inteiro positivo v\u00E1lido. +size_assertion_label=Tamanho em bytes\: +size_assertion_size_test=Tamanho para Asser\u00E7\u00E3o +size_assertion_title=Asser\u00E7\u00E3o de Tamanho +soap_action=A\u00E7\u00E3o SOAP +soap_data_title=Dados Soap/XML-RPC +soap_sampler_title=Requisi\u00E7\u00E3o SOAP/XML-RPC +soap_send_action=Enviar a\u00E7\u00E3o SOAP\: +spline_visualizer_average=M\u00E9dia +spline_visualizer_incoming=Recebidos +spline_visualizer_maximum=M\u00E1ximo +spline_visualizer_minimum=M\u00EDnimo +spline_visualizer_title=Visualizador Spline +spline_visualizer_waitingmessage=Aguardando amostras +split_function_separator=Separador. Padr\u00E3o \u00E9 , (v\u00EDrgula) +split_function_string=String a ser separada +ssl_alias_prompt=Favor digitar seu apelido preferido +ssl_alias_select=Selecione seu apelido para o teste +ssl_alias_title=Apelido do Cliente +ssl_error_title=Problema com Armazenamento de Chave +ssl_pass_prompt=Favor digitar sua senha +ssl_pass_title=Senha do Armaz\u00E9m de Chaves (KeyStore) +ssl_port=Porta SSL +sslmanager=Gerenciador SSL +start=Iniciar +start_no_timers=Iniciar sem pausas +starttime=Tempo de In\u00EDcio +stop=Interromper +stopping_test=Interrompendo todos os usu\u00E1rios virtuais. Por favor, seja paciente. +stopping_test_failed=Um ou mais usu\u00E1rios virtuais n\u00E3o pararam; veja arquivo de log. +stopping_test_title=Interrompendo Teste +string_from_file_file_name=Entre com o caminho completo do arquivo +string_from_file_seq_final=N\u00FAmero de sequ\u00EAncia final do arquivo (opcional) +string_from_file_seq_start=Numero de sequ\u00EAncia inicial do arquivo (opcional) +summariser_title=Gerar Sum\u00E1rio de Resultados +summary_report=Relat\u00F3rio de Sum\u00E1rio +switch_controller_label=Valor de Sele\u00E7\u00E3o +switch_controller_title=Controlador de Sele\u00E7\u00E3o +table_visualizer_sample_num=Amostra \# +table_visualizer_sample_time=Tempo da amostra (ms) +table_visualizer_start_time=Tempo de in\u00EDcio +table_visualizer_status=Estado +table_visualizer_success=Sucesso +table_visualizer_thread_name=Nome do Usu\u00E1rio Virtual +table_visualizer_warning=Alerta +tcp_classname=Nome da classe TCPClient\: +tcp_config_title=Configura\u00E7\u00E3o do Testador TCP +tcp_nodelay=Alterar "Sem Atrasos" +tcp_port=N\u00FAmero da Porta\: +tcp_request_data=Texto para envio +tcp_sample_title=Testador TCP +tcp_timeout=Tempo limite (ms) +template_field=Modelo\: +test=Teste +test_action_action=A\u00E7\u00E3o +test_action_duration=Dura\u00E7\u00E3o (ms) +test_action_pause=Pausar +test_action_stop=Encerrar +test_action_stop_now=Encerrar Agora +test_action_target=Alvo +test_action_target_test=Todos Usu\u00E1ros Virtuais +test_action_target_thread=Usu\u00E1rio Virtual Atual +test_action_title=A\u00E7\u00E3o de Teste +test_configuration=Configura\u00E7\u00E3o do Teste +test_plan=Plano de Teste +test_plan_classpath_browse=Adiconar diret\u00F3rio ou jar ao caminho de classes (classpath) +testconfiguration=Configura\u00E7\u00E3o de Teste +testplan.serialized=Executar Grupos de Usu\u00E1rios consecutivamente (ex\: executar um grupo de cada vez) +testplan_comments=Coment\u00E1rios\: +testt=Teste +thread_delay_properties=Propriedades de Atraso do Usu\u00E1rio Virtual +thread_group_title=Grupo de Usu\u00E1rios +thread_properties=Propriedades do Usu\u00E1rio Virtual +threadgroup=Grupo de Usu\u00E1rios +throughput_control_bynumber_label=Total de Execu\u00E7\u00F5es +throughput_control_bypercent_label=Percentagem de Execu\u00E7\u00F5es +throughput_control_perthread_label=Por Usu\u00E1rio +throughput_control_title=Controlador de Vaz\u00E3o +throughput_control_tplabel=Vaz\u00E3o +time_format=Formato de string para o SimpleDateFormat (opcional) +timelim=Tempo limite +tr=Turco +transaction_controller_include_timers=Incluem dura\u221a\u00df\u221a\u00a3o do temporizador e pr\u221a\u00a9-p\u221a\u2265s processadores em amostra gerada +transaction_controller_parent=Gerar amostras do pai +transaction_controller_title=Controlador de Transa\u00E7\u00E3o +unbind=Liberar Usu\u00E1rio Virtual +unescape_html_string=String a ser escapada +unescape_string=String contendo escapes de Java +uniform_timer_delay=Limite de Atraso Constante (em ms) +uniform_timer_memo=Adiciona um atraso aleat\u00F3rio com uma distribui\u00E7\u00E3o uniforme +uniform_timer_range=Atraso M\u00E1ximo Aleat\u00F3rio (ms) +uniform_timer_title=Temporizador Aleat\u00F3rio Uniforme +update_per_iter=Atualizar uma \u00FAnica vez por itera\u00E7\u00E3o +upload=Subir Arquivo +upper_bound=Limite Superior +url_config_protocol=Protocolo\: +url_config_title=Padr\u00F5es de Requisi\u00E7\u00E3o HTTP +url_full_config_title=Amostra de URL Completa +url_multipart_config_title=Padr\u00F5es de Requisi\u00E7\u00E3o HTTP Multiparte +use_keepalive=Usar Manter Ativo (KeepAlive) +use_multipart_for_http_post=Usar multipart/form-data para HTTP POST +use_recording_controller=Usar Controlador de Grava\u00E7\u00E3o +user=Usu\u00E1rio +user_defined_test=Teste Definido pelo Usu\u00E1rio +user_defined_variables=Vari\u00E1veis Definidas Pelo Usu\u00E1rio +user_param_mod_help_note=(N\u00E3o modifique isto. Modifique o arquivo com aquele nome no diret\u00F3rio /bin do JMeter) +user_parameters_table=Par\u00E2metros +user_parameters_title=Par\u00E2metros do Usu\u00E1rio +userdn=Nome do Usu\u00E1rio +username=Nome do Usu\u00E1rio +userpw=Senha +value=Valor +var_name=Nome de Refer\u00EAncia +variable_name_param=Nome da vari\u00E1vel (pode incluir refer\u00EAncias de vari\u00E1veis e fun\u00E7\u00F5es) +view_graph_tree_title=Ver Gr\u00E1fico de \u00C1rvore +view_results_assertion_error=Erro de asser\u00E7\u00E3o\: +view_results_assertion_failure=Falha de asser\u00E7\u00E3o\: +view_results_assertion_failure_message=Mensagem de falha de asser\u00E7\u00E3o\: +view_results_desc=Exibe os resultados de amostragem na forma de \u00E1rvore +view_results_error_count=Contador de Erros\: +view_results_fields=campos\: +view_results_in_table=Ver Resultados em Tabela +view_results_latency=Lat\u00EAncia\: +view_results_load_time=Tempo de Carga\: +view_results_render_html=Renderizar HTML +view_results_render_json=Renderizar JSON +view_results_render_text=Exibir Texto +view_results_render_xml=Renderizar XML +view_results_request_headers=Cabe\u00E7alhos das Requisi\u00E7\u00F5es +view_results_response_code=C\u00F3digo de Resposta\: +view_results_response_headers=Cabe\u00E7alhos da Resposta\: +view_results_response_message=Mensagem de resposta\: +view_results_response_too_large_message=Resposta muito grande para ser exibida. Tamanho\: +view_results_response_partial_message=In\u00EDcio da mensagem: +view_results_sample_count=Contagem de amostras\: +view_results_sample_start=In\u00EDcio da Amostra\: +view_results_size_in_bytes=Tamanho em bytes\: +view_results_tab_assertion=Resultados da asser\u00E7\u00E3o +view_results_tab_request=Requisi\u00E7\u00E3o +view_results_tab_response=Dados da resposta +view_results_tab_sampler=Resultados do testador +view_results_thread_name=Nome do Usu\u00E1rio Virtual\: +view_results_title=Ver Resultados +view_results_tree_title=Ver \u00C1rvore de Resultados +warning=Alerta\! +web_request=Requisi\u00E7\u00E3o HTTP +web_server=Servidor Web +web_server_client=Implementa\u00E7\u00E3o do Cliente\: +web_server_domain=Nome do Servidor ou IP\: +web_server_port=N\u00FAmero da Porta\: +web_server_timeout_connect=Conectar\: +web_server_timeout_response=Resposta\: +web_server_timeout_title=Tempo limite (ms) +web_testing2_title=Cliente HTTP de Requisi\u00E7\u00E3o HTTP +web_testing_embedded_url_pattern=URLs embutidas precisam combinar\: +web_testing_retrieve_images=Recuperar todos recursos embutidos a partir de arquivos HTML +web_testing_title=Requisi\u00E7\u00E3o HTTP +webservice_proxy_note=Se Usar Proxy HTTP estiver marcado, mas nenhum host ou porta s\u00E3o fornecidos, o testador +webservice_proxy_note2=ir\u00E1 analizar as op\u00E7\u00F5es de linha de comando. Se nenhum host proxy ou portas forem providos +webservice_proxy_note3=tamb\u00E9m, ele ir\u00E1 falhar em modo silencioso. +webservice_proxy_port=Porta do Proxy +webservice_sampler_title=Requisi\u00E7\u00E3o (SOAP) Requisi\u00E7\u00E3o +webservice_soap_action=A\u00E7\u00E3o SOAP +webservice_timeout=Tempo limite\: +webservice_use_proxy=Usar Proxy HTTP +while_controller_label=Condi\u00E7\u00F5e (fun\u00E7\u00E3o ou vari\u00E1vel) +while_controller_title=Controlador de Enquanto +workbench_title=\u00C1rea de Trabalho +wsdl_helper_error=O WSDL \u00E9 inv\u00E1lido, favor verificar a url. +wsdl_url_error=O WSDL estava vazio. +xml_assertion_title=Asser\u00E7\u00E3o XML +xml_namespace_button=Usar Espa\u00E7o de Nome (namespace) +xml_tolerant_button=Processador de XML/HTML Tolerante +xml_validate_button=Validar XML +xml_whitespace_button=Ignorar espa\u00E7os em branco +xmlschema_assertion_label=Nome do arquivo\: +xmlschema_assertion_title=Asser\u00E7\u00E3o de Esquema de XML +xpath_assertion_button=Validar +xpath_assertion_check=Verificar Express\u00E3o XPath +xpath_assertion_error=Erro com XPath +xpath_assertion_failed=Express\u00F5es XPath Inv\u00E1lidas +xpath_assertion_negate=Verdadeiro se nada combina +xpath_assertion_option=Op\u00E7\u00F5es de Processamento de XML +xpath_assertion_test=Asser\u00E7\u00E3o XPath +xpath_assertion_tidy=Tentar e melhorar entrada +xpath_assertion_title=Asser\u00E7\u00E3o XPath +xpath_assertion_valid=Express\u00E3o XPath V\u00E1lida +xpath_assertion_validation=Validar XML de acordo com DTD +xpath_assertion_whitespace=Ignorar espa\u00E7os em branco +xpath_expression=Express\u00F5es XPath que ser\u00E3o combinadas +xpath_extractor_query=Consulta XPath +xpath_extractor_title=Extractor XPath +xpath_file_file_name=Arquivo XML de onde os valores ser\u00E3o extra\u00EDdos +xpath_tidy_quiet=Quieto +xpath_tidy_report_errors=Reportar erros +xpath_tidy_show_warnings=Exibir alertas +you_must_enter_a_valid_number=Voc\u00EA precisa entrar com um n\u00FAmero v\u00E1lido +zh_cn=Chin\u00EAs (Simplificado) +zh_tw=Chin\u00EAs (Tradicional) diff --git a/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_tr.properties b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_tr.properties new file mode 100644 index 0000000..b2c0754 --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_tr.properties @@ -0,0 +1,840 @@ +# 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. + +#Stored by I18NEdit, may be edited! +about=Apache JMeter Hakk\u0131nda +add=Ekle +add_as_child=\u00C7ocuk Olarak Ekle +add_parameter=De\u011Fi\u015Fken Olarak Ekle +add_pattern=Desen Ekle\: +add_test=Test Ekle +add_user=Kullan\u0131c\u0131 Ekle +add_value=De\u011Fer Ekle +addtest=Test ekle +aggregate_graph=\u0130statiksel Grafikler +aggregate_graph_column=Kolon +aggregate_graph_display=Grafik G\u00F6ster +aggregate_graph_height=Y\u00FCkseklik +aggregate_graph_max_length_xaxis_label=X-ekseni etiketinin maximum uzunlu\u011Fu +aggregate_graph_ms=Milisaniyeler +aggregate_graph_response_time=Cevap Zaman\u0131 +aggregate_graph_save=Grafi\u011Fi kaydet +aggregate_graph_save_table=Tablo Verisini Kaydet +aggregate_graph_save_table_header=Tablo Ba\u015Fl\u0131\u011F\u0131n\u0131 Kaydet +aggregate_graph_title=Toplu Grafik +aggregate_graph_user_title=Grafik Ba\u015Fl\u0131\u011F\u0131 +aggregate_graph_width=Geni\u015Flik +aggregate_report=Toplu Rapor +aggregate_report_90%_line=90% Sat\u0131r +aggregate_report_bandwidth=KB/sn +aggregate_report_count=\# \u00D6rnek +aggregate_report_error=Hata +aggregate_report_error%=Hata % +aggregate_report_max=En \u00C7ok +aggregate_report_median=Ortalama +aggregate_report_min=En Az +aggregate_report_rate=Transfer Oran\u0131 +aggregate_report_total_label=TOPLAM +als_message=Not\: Eri\u015Fim Logu Ayr\u0131\u015Ft\u0131r\u0131c\u0131s\u0131 eklentiye izin veren genel-ge\u00E7er bir tasar\u0131ma sahiptir +als_message2=\u00F6zg\u00FCn ayr\u0131\u015Ft\u0131r\u0131c\u0131. Bu \u015Fekilde yapmak i\u00E7in, LogParser'i ger\u00E7ekle, jar'\u0131 \u015Furaya ekle\: +als_message3=/lib dizini ve \u00F6rnekleyicideki s\u0131n\u0131f\u0131 gir. +analyze=Veri Dosyas\u0131n\u0131 Analiz Et... +anchor_modifier_title=HTML Ba\u011Flant\u0131s\u0131 Ayr\u0131\u015Ft\u0131r\u0131c\u0131s\u0131 +appearance=Temalar +argument_must_not_be_negative=Ba\u011F\u0131ms\u0131z de\u011Fi\u015Fken negatif olmamal\u0131\! +assertion_assume_success=Durumu Yoksay +assertion_code_resp=Cevap Kodu +assertion_contains=\u0130\u00E7erir +assertion_equals=E\u015Fittir +assertion_headers=Cevap Ba\u015Fl\u0131klar\u0131 +assertion_matches=\u00D6rt\u00FC\u015F\u00FCr +assertion_message_resp=Cevap Mesaj\u0131 +assertion_pattern_match_rules=Desen \u00D6rt\u00FC\u015Fme Kurallar\u0131 +assertion_patterns_to_test=Test Edilecek Desenler +assertion_resp_field=Test Edilecek Cevap Alan\u0131 +assertion_text_resp=Metin Cevap +assertion_textarea_label=Do\u011Frulamalar\: +assertion_title=Cevap Do\u011Frulamas\u0131 +assertion_url_samp=URL \u00D6rneklendi +assertion_visualizer_title=Do\u011Frulama Sonu\u00E7lar\u0131 +attribute=\u00D6znitelik +attrs=\u00D6znitelikler +auth_base_url=Temel URL +auth_manager_title=HTTP Yetkilendirme Y\u00F6neticisi +auths_stored=Yetkilendirme Y\u00F6neticisinde Tutulan Yetkilendirmeler +average=Ortalama +average_bytes=Ort. Byte +bind=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Ba\u011Flamas\u0131 +browse=G\u00F6zat... +bsf_sampler_title=BSF \u00D6rnekleyicisi +bsf_script=\u00C7al\u0131\u015Ft\u0131r\u0131lacak betik (de\u011Fi\u015Fkenler\: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +bsf_script_file=\u00C7al\u0131\u015Ft\u0131r\u0131lacak betik dosyas\u0131 +bsf_script_language=Betik dili\: +bsf_script_parameters=Beti\u011Fe veya betik dosyas\u0131na ge\u00E7ilecek parametreler +bsh_assertion_script=Betik (tan\u0131ml\u0131 de\u011Fi\u015Fkenler i\u00E7in a\u015Fa\u011F\u0131ya bak\u0131n) +bsh_assertion_script_variables=Betik i\u00E7in \u015Fu de\u011Fi\u015Fkenler tan\u0131mlanm\u0131\u015Ft\u0131r\:\nOkuma/Yazma\: Failure, FailureMessage, SampleResult, vars, props, log.\nSalt Okunur\: Response[Data|Code|Message|Headers], RequestHeaders, SampleLabel, SamplerData, ctx +bsh_assertion_title=BeanShell Do\u011Frulamas\u0131 +bsh_function_expression=De\u011Ferlendirilecek ifade +bsh_sampler_title=BeanShell \u00D6rnekleyici +bsh_script=Betik (tan\u0131ml\u0131 de\u011Fi\u015Fkenler i\u00E7in a\u015Fa\u011F\u0131ya bak\u0131n) +bsh_script_file=Betik Dosyas\u0131 +bsh_script_parameters=Parametreler (-> Dizgi (String) Parametreler ve String []bsh.args) +bsh_script_variables=Betik i\u00E7in \u015Fu de\u011Fi\u015Fkenler tan\u0131ml\u0131d\u0131r\:\nSampleResult, ResponseCode, ResponseMessage, IsSuccess, Label, FileName, ctx, vars, props, log +busy_testing=Testle me\u015Fgul\u00FCm, l\u00FCtfen ayarlar\u0131 de\u011Fi\u015Ftirmeden \u00F6nce testi durdurun +cache_session_id=\u00D6nbellek oturum Id'si? +cancel=\u0130ptal +cancel_exit_to_save=Kaydedilmemi\u015F test maddeleri var. \u00C7\u0131kmadan \u00F6nce kaydetmek ister misiniz? +cancel_new_to_save=Kaydedilmemi\u015F test maddeleri var. Testi temizlemeden \u00F6nce kaydetmek ister misin? +cancel_revert_project=Kaydedilmemi\u015F test maddeleri var. Daha \u00F6nce kaydedilmi\u015F olan test plan\u0131na d\u00F6nmek ister misiniz? +choose_function=Fonksiyon se\u00E7in +choose_language=Dil se\u00E7in +clear=Temizle +clear_all=Hepsini Temizle +clear_cookies_per_iter=Her tekrar i\u00E7in \u00E7erezleri temizle? +column_delete_disallowed=Bu kolonu silmek i\u00E7in izin yok +column_number=CSV dosyas\u0131 i\u00E7in kolon numaras\u0131 | ileri | *takma ad +compare=Kar\u015F\u0131la\u015Ft\u0131r +comparefilt=Filtreyi kar\u015F\u0131la\u015Ft\u0131r +config_element=Ayar Eleman\u0131 +config_save_settings=Ayarla +configure_wsdl=Ayarla +constant_throughput_timer_memo=Sabit bir transfer oran\u0131 elde etmek i\u00E7in \u00F6rneklemeler aras\u0131na gecikme ekle +constant_timer_delay=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Gecikmesi (milisaniyeler) +constant_timer_memo=\u00D6rneklemeler aras\u0131na sabit bir gecikme ekle +constant_timer_title=Sabit Zamanlay\u0131c\u0131 +content_encoding=\u0130\u00E7erik kodlamas\u0131\: +controller=Denet\u00E7i +cookie_manager_policy=\u00C7erez Politikas\u0131 +cookie_manager_title=HTTP \u00C7erez Y\u00F6neticisi +cookies_stored=\u00C7erez Y\u00F6neticisinde Tutulan \u00C7erezler +copy=Kopyala +counter_config_title=Saya\u00E7 +counter_per_user=Sayac\u0131 her kullan\u0131c\u0131 i\u00E7in ba\u011F\u0131ms\u0131z \u00E7al\u0131\u015Ft\u0131r +countlim=Boyut s\u0131n\u0131r\u0131 +csvread_file_file_name=De\u011Ferlerin okunaca\u011F\u0131 CSV dosyas\u0131 | *k\u0131saltma +cut=Kes +cut_paste_function=Fonksiyon metnini kopyala ve yap\u0131\u015Ft\u0131r +database_conn_pool_max_usage=Her Ba\u011Flant\u0131 i\u00E7in En Fazla Kullan\u0131m\: +database_conn_pool_props=Veritaban\u0131 Ba\u011Flant\u0131s\u0131 Havuzu +database_conn_pool_size=Havuzdaki Ba\u011Flant\u0131 Say\u0131s\u0131 +database_conn_pool_title=JDBC Veritaban Ba\u011Flant\u0131s\u0131 Havuzu \u00D6ntan\u0131ml\u0131 De\u011Ferleri +database_driver_class=S\u00FCr\u00FCc\u00FC S\u0131n\u0131f\u0131\: +database_login_title=JDBC Veritaban\u0131 Giri\u015Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +database_sql_query_string=SQL Sorgusu Metni\: +database_sql_query_title=JDBC SQL Sorgusu \u00D6ntan\u0131ml\u0131 De\u011Ferleri +database_testing_title=JDBC \u0130ste\u011Fi +database_url=JDBC Adresi\: +database_url_jdbc_props=Veritaban\u0131 Adresi ve JDBC S\u00FCr\u00FCc\u00FCs\u00FC +de=Alman +debug_off=Hata ay\u0131klamas\u0131n\u0131 saf d\u0131\u015F\u0131 b\u0131rak +debug_on=Hata ay\u0131klamas\u0131n\u0131 etkinle\u015Ftir +default_parameters=\u00D6ntan\u0131ml\u0131 Parametreler +default_value_field=\u00D6ntan\u0131ml\u0131 De\u011Fer\: +delay=Ba\u015Flang\u0131\u00E7 gecikmesi (saniye) +delete=Sil +delete_parameter=De\u011Fi\u015Fkeni Sil +delete_test=Testi Sil +delete_user=Kullan\u0131c\u0131y\u0131 Sil +deltest=Testi sil +deref=K\u0131saltmalar\u0131 g\u00F6ster +disable=Safd\u0131\u015F\u0131 b\u0131rak +distribution_graph_title=Da\u011F\u0131t\u0131m Grafi\u011Fi (alfa) +distribution_note1=Grafik 10 \u00F6rnekte bir g\u00FCncellenecek +domain=Etki Alan\u0131 +done=Bitti +duration=S\u00FCre (saniye) +duration_assertion_duration_test=Do\u011Frulama S\u00FCresi +duration_assertion_failure=\u0130\u015Flem \u00E7ok uzun s\u00FCrd\u00FC\: {0} milisaniye s\u00FCrmesi gerekirken, {1} milisaniyeden fazla s\u00FCrd\u00FC. +duration_assertion_input_error=Pozitif bir tamsay\u0131 giriniz. +duration_assertion_label=Milisaniye olarak s\u00FCre\: +duration_assertion_title=S\u00FCre Do\u011Frulamas\u0131 +edit=D\u00FCzenle +email_results_title=E-posta Sonu\u00E7lar\u0131 +en=\u0130ngilizce +enable=Etkinle\u015Ftir +encode?=Kodlama? +encoded_value=URL Kodlanm\u0131\u015F De\u011Fer +endtime=Biti\u015F Zaman\u0131 +entry_dn=Giri\u015F DN'i +entrydn=Giri\u015F DN'i +error_loading_help=Yard\u0131m sayfas\u0131n\u0131 y\u00FCklerken hata +error_occurred=Hata Olu\u015Ftu +error_title=Hata +es=\u0130spanyolca +eval_name_param=De\u011Fi\u015Fken ve fonksiyon referanslar\u0131 i\u00E7eren metin +evalvar_name_param=De\u011Fi\u015Fken ismi +example_data=\u00D6rnek Veri +example_title=\u00D6rnekleyici \u00F6rne\u011Fi +exit=\u00C7\u0131k\u0131\u015F +expiration=S\u00FCre dolumu +field_name=Alan ismi +file=Dosya +file_already_in_use=Bu dosya zaten kullan\u0131l\u0131yor +file_visualizer_append=Varolan Veri Dosyas\u0131na Ekle +file_visualizer_auto_flush=Her \u00D6rnekten Sonra Otomatik Olarak Temizle +file_visualizer_browse=G\u00F6zat... +file_visualizer_close=Kapat +file_visualizer_file_options=Dosya Se\u00E7enekleri +file_visualizer_filename=Dosya ismi +file_visualizer_flush=Temizle +file_visualizer_missing_filename=\u00C7\u0131kt\u0131 dosyas\u0131 belirtilmedi. +file_visualizer_open=A\u00E7 +file_visualizer_output_file=Sonu\u00E7lar\u0131 dosyaya yaz / Dosyadan oku +file_visualizer_submit_data=Girilen Veriyi Ekle +file_visualizer_title=Dosya Raprolay\u0131c\u0131 +file_visualizer_verbose=\u00C7\u0131kt\u0131y\u0131 Detayland\u0131r +filename=Dosya \u0130smi +follow_redirects=Y\u00F6nlendirmeleri \u0130zle +follow_redirects_auto=Otomatik Olarak Y\u00F6nlendir +foreach_controller_title=ForEach Denet\u00E7isi +foreach_input=Giri\u015F de\u011Fi\u015Fkeni \u00F6neki +foreach_output=\u00C7\u0131k\u0131\u015F de\u011Fi\u015Fkeni ismi +foreach_use_separator=Numara \u00F6n\u00FCne "_" ekle ? +format=Numara bi\u00E7imi +fr=Frans\u0131zca +ftp_binary_mode=\u0130kili kipi kullan ? +ftp_local_file=Yerel Dosya\: +ftp_remote_file=Uzak Dosya\: +ftp_sample_title=FTP \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +ftp_save_response_data=Cevab\u0131 Dosyaya Kaydet ? +ftp_testing_title=FTP \u0130ste\u011Fi +function_dialog_menu_item=Fonksiyon Yard\u0131m\u0131 Diyalo\u011Fu +function_helper_title=Fonksiyon Yard\u0131m\u0131 +function_name_param=Sonucu tutacak de\u011F\u015Fkenin ismi (gerekli) +function_name_paropt=Sonucu tutacak de\u011Fi\u015Fkenin ismi (iste\u011Fe ba\u011Fl\u0131) +function_params=Fonksiyon Parametresi +functional_mode=Fonksiyonel Test Kipi (\u00F6r\: Cevap Verisini ve \u00D6rnekleyici Verisini kaydet) +functional_mode_explanation=Ba\u015Far\u0131m\u0131 olumsuz etkileyecek olmas\u0131na ra\u011Fmen Fonksiyonel Test Kipi se\u00E7iliyor. +gaussian_timer_delay=Sabit Gecikme S\u0131n\u0131r\u0131 (milisaniye) +gaussian_timer_memo=Gauss da\u011F\u0131l\u0131m\u0131na g\u00F6re rastgele bir gecikme ekler +gaussian_timer_range=Sapma (milisaniye) +gaussian_timer_title=Gauss Rastgele Zamanlay\u0131c\u0131 +generate=\u00DCret +generator=\u00DCretici S\u0131n\u0131f\u0131n \u0130smi +generator_cnf_msg=\u00DCretici s\u0131n\u0131f\u0131 bulamad\u0131. L\u00FCtfen jar dosyas\u0131n\u0131 /lib dizini alt\u0131na yerle\u015Ftirdi\u011Finizden emin olun. +generator_illegal_msg=IllegalAccessException nedeniyle \u00FCretici s\u0131n\u0131fa eri\u015Femedi. +generator_instantiate_msg=\u00DCretici ayr\u0131\u015Ft\u0131r\u0131c\u0131 i\u00E7in \u00F6rnek yaratamad\u0131. L\u00FCtfen \u00FCreticinin "Generator" arabirimini ger\u00E7ekledi\u011Finden emin olun. +get_xml_from_file=SOAP XML Verisini i\u00E7eren Dosya (A\u015Fa\u011F\u0131daki metnin \u00FCzerine yazar) +get_xml_from_random=Mesaj Klas\u00F6r\u00FC +graph_choose_graphs=G\u00F6sterilecek Grafikler +graph_full_results_title=Grafik Tam Sonu\u00E7lar\u0131 +graph_results_average=Ortalama +graph_results_data=Veri +graph_results_deviation=Sapma +graph_results_latest_sample=Son \u00D6rnek +graph_results_median=Orta +graph_results_no_samples=\u00D6rnek Say\u0131s\u0131 +graph_results_throughput=Transfer Oran\u0131 +graph_results_title=Grafik Sonu\u00E7lar\u0131 +grouping_add_separators=Gruplar aras\u0131na ayra\u00E7 ekle +grouping_in_controllers=Her grubu yeni bir denet\u00E7iye koy +grouping_mode=Gruplama\: +grouping_no_groups=\u00D6rnekleyicileri gruplama +grouping_store_first_only=Her grubun sadece 1. \u00F6rnekleyicilerini tut +header_manager_title=HTTP Ba\u015Fl\u0131k Y\u00F6neticisi +headers_stored=Ba\u015Fl\u0131k Y\u00F6neticisinde Tutulan Ba\u015Fl\u0131klar +help=Yard\u0131m +help_node=Bu d\u00FC\u011F\u00FCm nedir? +html_assertion_file=JTidy raporunu dosyaya yaz +html_assertion_label=HTML Do\u011Frulama +html_assertion_title=HTML Do\u011Frulama +html_parameter_mask=HTML Parametre Maskesi +http_implementation=Uygulamas\u0131\: +http_response_code=HTTP cevap kodu +http_url_rewriting_modifier_title=HTTP URL Yeniden Yazma Niteleyicisi +http_user_parameter_modifier=HTTP Kullan\u0131c\u0131 Parametresi Niteleyicisi +httpmirror_title=HTTP Ayna Sunucusu +id_prefix=ID \u00D6neki +id_suffix=ID Soneki +if_controller_evaluate_all=T\u00FCm \u00E7ocuklar i\u00E7in hesapla? +if_controller_label=Durum (Javascript) +if_controller_title=If Denet\u00E7isi +ignore_subcontrollers=Alt denet\u00E7i bloklar\u0131n\u0131 yoksay +include_controller=\u0130\u00E7erme Denet\u00E7isi +include_equals=E\u015Fleniyorsa i\u00E7er? +include_path=Test Plan\u0131n\u0131 \u0130\u00E7er +increment=Artt\u0131r +infinite=Her zaman +initial_context_factory=\u0130lk Ba\u011Flam Fabrikas\u0131 +insert_after=Arkas\u0131na Ekle +insert_before=\u00D6n\u00FCne Ekle +insert_parent=Ebeveynine Ekle +interleave_control_title=Aral\u0131k Denet\u00E7isi +intsum_param_1=Eklenecek ilk tamsay\u0131. +intsum_param_2=Eklenecek ikinci tamsay\u0131 - sonraki tamsay\u0131lar di\u011Fer argumanlar\u0131 ekleyerek toplanabilir. +invalid_data=Ge\u00E7ersiz veri +invalid_mail=E-posta g\u00F6nderirken hata olu\u015Ftu +invalid_mail_address=Bir veya daha fazla ge\u00E7ersiz e-posta adresi tespit edildi +invalid_mail_server=E-posta sunucusuna ba\u011Flan\u0131rken problem (JMeter log dosyas\u0131na bak\u0131n\u0131z) +invalid_variables=Ge\u00E7ersiz de\u011Fi\u015Fkenler +iteration_counter_arg_1=TRUE, her kullan\u0131c\u0131n\u0131n kendi sayac\u0131na sahip olmas\u0131 i\u00E7in, FALSE genel-ge\u00E7er saya\u00E7 i\u00E7in +iterator_num=D\u00F6ng\u00FC Say\u0131s\u0131\: +ja=Japonca +jar_file=Jar Dosyalar\u0131 +java_request=Java \u0130ste\u011Fi +java_request_defaults=Java \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +javascript_expression=De\u011Ferlendirilecek javascript ifadesi +jexl_expression=De\u011Ferlendirilecek JEXL ifadesi +jms_auth_required=Gerekli +jms_client_caption=Mesaj dinlemek i\u00E7in TopicSubscriber.receive() kullanan istemciyi al. +jms_client_caption2=MessageListener yeni mesajlar\u0131 dinlemek i\u00E7in onMessage(Message)'\u0131 kullan\u0131r. +jms_client_type=\u0130stemci +jms_communication_style=\u0130leti\u015Fim \u015Eekli +jms_concrete_connection_factory=Somut Ba\u011Flant\u0131 Fabrikas\u0131 +jms_config=Ayarlar +jms_config_title=JMS Ayar\u0131 +jms_connection_factory=Ba\u011Flant\u0131 Fabrikas\u0131 +jms_file=Dosya +jms_initial_context_factory=Ba\u015Flang\u0131\u00E7 Ba\u011Flam Fabrikas\u0131 +jms_itertions=Toplanacak istek say\u0131s\u0131 +jms_jndi_defaults_title=JNDI \u00D6ntan\u0131ml\u0131 Ayarlar\u0131 +jms_jndi_props=JDNI \u00D6zellikleri +jms_message_title=Mesaj \u00F6zellikleri +jms_message_type=Mesaj Tipi +jms_msg_content=\u0130\u00E7erik +jms_object_message=Nesne Mesaj\u0131 +jms_point_to_point=JMS U\u00E7tan Uca +jms_props=JMS \u00D6zellikleri +jms_provider_url=Sa\u011Flay\u0131c\u0131 Adresi (URL) +jms_publisher=JMS Yay\u0131nc\u0131s\u0131 +jms_pwd=\u015Eifre +jms_queue=S\u0131ra +jms_queue_connection_factory=QueueConnection Fabrikas\u0131 +jms_queueing=JMS Kaynaklar\u0131 +jms_random_file=Rastgele Dosyas\u0131 +jms_read_response=Cevab\u0131 Oku +jms_receive_queue=S\u0131ray\u0131 alan JNDI ismi +jms_request=Sadece \u0130stek +jms_requestreply=\u0130stek Cevap +jms_sample_title=JMS \u00D6ntan\u0131ml\u0131 \u0130ste\u011Fi +jms_send_queue=S\u0131ray\u0131 alan JNDI ismi +jms_subscriber_on_message=MessageListener.onMessage()'\u0131 kullan +jms_subscriber_receive=TopicSubscriber.receive()'\u0131 kullan +jms_subscriber_title=JMS Abonesi +jms_testing_title=Mesajla\u015Fma \u0130ste\u011Fi +jms_text_message=Metin Mesaj\u0131 +jms_timeout=Zaman A\u015F\u0131m\u0131 (milisaniye) +jms_topic=Konu +jms_use_file=Dosyadan +jms_use_non_persistent_delivery=S\u00FCrekli olmayan da\u011F\u0131t\u0131m kipini kullan? +jms_use_properties_file=jndi.properties dosyas\u0131n\u0131 kullan +jms_use_random_file=Rastgele Dosyas\u0131 +jms_use_text=Metin alan\u0131 +jms_user=Kullan\u0131c\u0131 +jndi_config_title=JNDI Ayar\u0131 +jndi_lookup_name=Uzak Arabirim +jndi_lookup_title=JNDI Arama Ayar\u0131 +jndi_method_button_invoke=\u00C7a\u011F\u0131r +jndi_method_button_reflect=Yans\u0131t +jndi_method_home_name=Yerel Metod \u0130smi +jndi_method_home_parms=Yerel Metod Parametreleri +jndi_method_name=Metod Ayar\u0131 +jndi_method_remote_interface_list=Uzak Arabirimler +jndi_method_remote_name=Uzak Metod \u0130smi +jndi_method_remote_parms=Uzak Metod Parametreleri +jndi_method_title=Uzak Metod Ayar\u0131 +jndi_testing_title=JNDI \u0130ste\u011Fi +jndi_url_jndi_props=JNDI \u00D6zellikleri +junit_append_error=Do\u011Frulama hatalar\u0131n\u0131 ekle +junit_append_exception=\u00C7al\u0131\u015Fma zaman\u0131 istisnalar\u0131n\u0131 ekle +junit_constructor_error=S\u0131n\u0131f \u00F6rne\u011Fi yarat\u0131lamad\u0131 +junit_constructor_string=Yap\u0131c\u0131 Metin Etiketi +junit_do_setup_teardown=setUp ve tearDown'u \u00E7a\u011F\u0131rma +junit_error_code=Hata Kodu +junit_error_default_msg=Beklenmedik hata olu\u015Ftu +junit_error_msg=Hata Mesaj\u0131 +junit_failure_code=Ba\u015Far\u0131s\u0131zl\u0131k Kodu +junit_failure_default_msg=Test ba\u015Far\u0131s\u0131z oldu. +junit_failure_msg=Ba\u015Far\u0131s\u0131zl\u0131k Mesaj\u0131 +junit_pkg_filter=Paket Filtresi +junit_request=JUnit \u0130ste\u011Fi +junit_request_defaults=JUnit \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +junit_success_code=Ba\u015Far\u0131 Kodu +junit_success_default_msg=Test ba\u015Far\u0131s\u0131z +junit_success_msg=Ba\u015Far\u0131 Mesaj\u0131 +junit_test_config=JUnit Test Parametreleri +junit_test_method=Test Metodu +ldap_argument_list=LDAP Arg\u00FCman Listesi +ldap_connto=Ba\u011Flant\u0131 zaman a\u015F\u0131m\u0131 (milisaniye) +ldap_parse_results=Arama sonu\u00E7lar\u0131n\u0131 ayr\u0131\u015Ft\u0131r ? +ldap_sample_title=LDAP \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 Ayarlar\u0131 +ldap_search_baseobject=Temel-nesne aramas\u0131 ger\u00E7ekle\u015Ftir +ldap_search_onelevel=Tek-seviye aramas\u0131 ger\u00E7ekle\u015Ftir +ldap_search_subtree=Alt-a\u011Fa\u00E7 aramas\u0131 ger\u00E7ekle\u015Ftir +ldap_secure=G\u00FCvenli LDAP Protokulu kullan ? +ldap_testing_title=LDAP \u0130ste\u011Fi +ldapext_sample_title=LDAP Geli\u015Fmi\u015F \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +ldapext_testing_title=LDAP Geli\u015Fmi\u015F \u0130ste\u011Fi +load=Y\u00FCkle +load_wsdl=WSDL Y\u00FCkle +log_errors_only=Hatalar +log_file=Log Dosyas\u0131 Yolu +log_function_comment=Ek yorum (iste\u011Fe ba\u011Fl\u0131) +log_function_level=Log seviyesi (\u00F6ntan\u0131ml\u0131 INFO) ya OUT ya da ERR +log_function_string=Loglanacak metin +log_function_string_ret=Loglanacak (ve d\u00F6n\u00FClecek) metin +log_function_throwable=At\u0131lacak metin (iste\u011Fe ba\u011Fl\u0131) +log_only=Sadece Log/G\u00F6r\u00FCnt\u00FCleme\: +log_parser=Log Ayr\u0131\u015Ft\u0131r\u0131c\u0131s\u0131 S\u0131n\u0131f\u0131n\u0131n \u0130smi +log_parser_cnf_msg=S\u0131n\u0131f\u0131 bulamad\u0131. L\u00FCtfen jar dosyas\u0131n\u0131 /lib dizini alt\u0131na yerle\u015Ftirdi\u011Finizden emin olun. +log_parser_illegal_msg=IllegalAcessException nedeniyle s\u0131n\u0131fa eri\u015Femedi. +log_parser_instantiate_msg=Log ayr\u0131\u015Ft\u0131r\u0131c\u0131 \u00F6rne\u011Fi yaratamad\u0131. L\u00FCtfen ayr\u0131\u015Ft\u0131r\u0131c\u0131n\u0131n LogParser arabirimini ger\u00E7ekledi\u011Finden emin olun. +log_sampler=Tomcat Eri\u015Fimi Log \u00D6rnekleyicisi +log_success_only=Ba\u015Far\u0131lar +logic_controller_title=Basit Denet\u00E7i +login_config=Kullan\u0131c\u0131 Giri\u015Fi Ayar\u0131 +login_config_element=Kullan\u0131c\u0131 Giri\u015Fi Eleman\u0131 +longsum_param_1=Eklenek ilk b\u00FCy\u00FCk say\u0131 (long) +longsum_param_2=Eklenecek ikinci b\u00FCy\u00FCk say\u0131 (long) - di\u011Fer b\u00FCy\u00FCk say\u0131lar di\u011Fer arg\u00FCmanlar\u0131n toplanmas\u0131yla elde edilebilir. +loop_controller_title=D\u00F6ng\u00FC Denet\u00E7isi +looping_control=D\u00F6ng\u00FC Kontrol\u00FC +lower_bound=A\u015Fa\u011F\u0131 S\u0131n\u0131r +mail_reader_account=Kullan\u0131c\u0131 ismi\: +mail_reader_all_messages=Hepsi +mail_reader_delete=Sunucudan mesajlar\u0131 sil +mail_reader_folder=Klas\u00F6r\: +mail_reader_num_messages=\u00C7ekilecek mesajlar\u0131n say\u0131s\u0131\: +mail_reader_password=\u015Eifre\: +mail_reader_server=Sunucu\: +mail_reader_server_type=Sunucu Tipi\: +mail_reader_title=Eposta Okuyucu \u00D6rnekleyicisi +mail_sent=Eposta ba\u015Far\u0131yla g\u00F6nderildi +mailer_attributes_panel=Eposta \u00F6znitelikleri +mailer_error=Eposta g\u00F6nderilemedi. L\u00FCtfen sorunlu girdileri d\u00FCzeltin. +mailer_visualizer_title=Eposta G\u00F6r\u00FCnt\u00FCleyicisi +match_num_field=E\u015Fle\u015Fme Numaras\u0131. (Rastgele i\u00E7in 0) +max=En Fazla +maximum_param=\u0130zin verilen de\u011Fer aral\u0131\u011F\u0131 i\u00E7in en b\u00FCy\u00FCk de\u011Fer +md5hex_assertion_failure=MD5 toplam\u0131 do\u011Frulamas\u0131 hatas\u0131\: {1} beklenirken {0} al\u0131nd\u0131 +md5hex_assertion_label=MDBHex +md5hex_assertion_md5hex_test=Do\u011Frulanacak MD5Hex +md5hex_assertion_title=MD5Hex Do\u011Frulamas\u0131 +memory_cache=\u00D6nbellek +menu_assertions=Do\u011Frulamalar +menu_close=Kapat +menu_collapse_all=Hepsini Kapat +menu_config_element=Ayar Eleman\u0131 +menu_edit=D\u00FCzenle +menu_expand_all=Hepsini A\u00E7 +menu_generative_controller=\u00D6rnekleyici +menu_listener=Dinleyici +menu_logic_controller=Mant\u0131k Denet\u00E7isi +menu_merge=Birle\u015Ftir +menu_modifiers=Niteleyiciler +menu_non_test_elements=Test-d\u0131\u015F\u0131 Elemanlar +menu_open=A\u00E7 +menu_post_processors=Test Sonras\u0131 \u0130\u015Flemciler +menu_pre_processors=Test \u00D6ncesi \u0130\u015Flemciler +menu_response_based_modifiers=Cevap Temelli Niteleyiciler +menu_timer=Zamanlay\u0131c\u0131 +metadata=Veri hakk\u0131nda veri (metadata) +method=Metod\: +mimetype=Mime tipi +minimum_param=\u0130zin verilen de\u011Fer aral\u0131\u011F\u0131 i\u00E7in en k\u00FC\u00E7\u00FCk de\u011Fer +minute=dakika +modddn=Eski girdi ismi +modification_controller_title=De\u011Fi\u015Fiklik Denet\u00E7isi +modification_manager_title=De\u011Fi\u015Fiklik Y\u00F6neticisi +modify_test=Testi De\u011Fi\u015Ftir +modtest=De\u011Fi\u015Fiklik testi +module_controller_module_to_run=\u00C7al\u0131\u015Ft\u0131r\u0131lacak Birim +module_controller_title=Birim Denet\u00E7isi +module_controller_warning=Birim bulunamad\u0131\: +monitor_equation_active=Aktif\: (me\u015Fgul/maksimum) > 25% +monitor_equation_dead=\u00D6l\u00FC\: cevap yok +monitor_equation_healthy=Sa\u011Fl\u0131kl\u0131\: (me\u015Fgul/maksimum) < 25% +monitor_equation_load=Y\u00FCk\: ((me\u015Fgul / maksimum * 50) + ((kullan\u0131lan bellek / maksimum bellek)) > 25% +monitor_equation_warning=Uyar\u0131\: (me\u015Fgul/maksimum) > 67% +monitor_health_tab_title=Sa\u011Fl\u0131k +monitor_health_title=\u0130zleme Sonu\u00E7lar\u0131 +monitor_is_title=\u0130zleyici olarak kullan +monitor_label_right_active=Aktif +monitor_label_right_dead=\u00D6l\u00FC +monitor_label_right_healthy=Sa\u011Fl\u0131kl\u0131 +monitor_label_right_warning=Uyar\u0131 +monitor_legend_health=Sa\u011Fl\u0131k +monitor_legend_load=Y\u00FCk +monitor_legend_memory_per=Bellek % (kullan\u0131lan/toplam) +monitor_legend_thread_per=\u0130\u015F par\u00E7ac\u0131\u011F\u0131 % (me\u015Fgul/maksimum) +monitor_performance_servers=Sunucular +monitor_performance_tab_title=Ba\u015Far\u0131m +monitor_performance_title=Ba\u015Far\u0131m Grafi\u011Fi +name=\u0130sim\: +new=Yeni +newdn=Yeni ay\u0131rt edici isim +no=Norve\u00E7ce +number_of_threads=\u0130\u015F par\u00E7ac\u0131\u011F\u0131 say\u0131s\u0131 +obsolete_test_element=Test eleman\u0131 belirsiz +once_only_controller_title=Bir Kerelik Denet\u00E7i +open=A\u00E7... +option=Se\u00E7enekler +optional_tasks=\u0130ste\u011Fe Ba\u011Fl\u0131 G\u00F6revler +paramtable=\u0130stekle parametreleri g\u00F6nder\: +password=\u015Eifre +paste=Yap\u0131\u015Ft\u0131r +paste_insert=Ekleme Olarak Yap\u0131\u015Ft\u0131r +path=Yol\: +path_extension_choice=Yol Uzatmas\u0131 (ayra\u00E7 olarak ";" kullan) +path_extension_dont_use_equals=Yol uzatmas\u0131nda e\u015Fitlik kullanmay\u0131n (Intershop Enfinity uyumlulu\u011Fu) +path_extension_dont_use_questionmark=Yol uzatmas\u0131nda soru i\u015Fareti kullanmay\u0131n (Intershop Enfinity uyumlulu\u011Fu) +patterns_to_exclude=Hari\u00E7 Tutulacak URL Desenleri +patterns_to_include=Dahil Edilecek URL Desenleri +pkcs12_desc=PKCS 12 Anahtar (*.p12) +property_default_param=\u00D6ntan\u0131ml\u0131 de\u011Fer +property_edit=D\u00FCzenle +property_editor.value_is_invalid_message=Girdi\u011Finiz metin bu \u00F6zellik i\u00E7in ge\u00E7erli de\u011Fil.\n\u00D6zellik \u00F6nceki de\u011Ferine geri d\u00F6nd\u00FCr\u00FClecek. +property_editor.value_is_invalid_title=Ge\u00E7ersiz girdi +property_name_param=\u00D6zellik ismi +property_returnvalue_param=\u00D6zelli\u011Fin orjinal de\u011Ferini d\u00F6n (\u00F6ntan\u0131ml\u0131 false)? +property_undefined=Tan\u0131ms\u0131z +property_value_param=\u00D6zelli\u011Fin de\u011Feri +property_visualiser_title=\u00D6zellik G\u00F6r\u00FCnt\u00FCleme +protocol=Protokol [http]\: +protocol_java_border=Java s\u0131n\u0131f\u0131 +protocol_java_classname=S\u0131n\u0131f ismi\: +protocol_java_config_tile=Java \u00D6rne\u011Fi Ayarla +protocol_java_test_title=Java Testi +provider_url=Sa\u011Flay\u0131c\u0131 Adresi (URL) +proxy_assertions=Do\u011Frulamalar\u0131 Ekle +proxy_cl_error=Vekil sunucu belirtiliyorsa, sunucu ve port verilmeli +proxy_content_type_exclude=Hari\u00E7 tut\: +proxy_content_type_filter=\u0130\u00E7erik-tipi filtresi +proxy_content_type_include=\u0130\u00E7eren\: +proxy_headers=HTTP Ba\u015Fl\u0131klar\u0131n\u0131 Yakala +proxy_httpsspoofing=HTTPS Taklidi Dene +proxy_httpsspoofing_match=\u0130ste\u011Fe ba\u011Fl\u0131 URL e\u015Fle\u015Fme metni\: +proxy_regex=D\u00FCzenli ifade e\u015Fle\u015Fmesi +proxy_sampler_settings=HTTP \u00D6rnekleyici Ayarlar\u0131 +proxy_sampler_type=Tip\: +proxy_separators=Ayra\u00E7lar\u0131 Ekle +proxy_target=Hedef Denet\u00E7isi\: +proxy_test_plan_content=Test plan\u0131 i\u00E7eri\u011Fi +proxy_title=HTTP Vekil Sunucusu +ramp_up=Rampa S\u00FCresi (saniyeler)\: +random_control_title=Rastgele Denet\u00E7isi +random_order_control_title=Rastgele S\u0131ra Denet\u00E7isi +read_response_message="Cevap Oku" se\u00E7ene\u011Fi i\u015Faretli de\u011Fil. Cevab\u0131 g\u00F6rmek i\u00E7in, l\u00FCtfen \u00F6rnekleyicideki ilgili kutuyu i\u015Faretleyin. +read_response_note=E\u011Fer "cevap oku" se\u00E7ene\u011Fi se\u00E7ili de\u011Filse, \u00F6rnekleyici cevab\u0131 okumaz +read_response_note2=ya da SampleResult'\u0131 kur. Bu ba\u015Far\u0131m\u0131 artt\u0131r\u0131r, ama \u015Fu anlama gelir +read_response_note3=cevap i\u00E7eri\u011Fi loglanmayacak. +read_soap_response=SOAP Cevab\u0131n\u0131 Oku +realm=Alan (Realm) +record_controller_title=Kaydetme Denet\u00E7isi +ref_name_field=Referans \u0130smi\: +regex_extractor_title=D\u00FCzenli \u0130fade \u00C7\u0131kar\u0131c\u0131 +regex_field=D\u00FCzenli \u0130fade\: +regex_source=Se\u00E7ilecek Cevap Alanlar\u0131 +regex_src_body=G\u00F6vde +regex_src_hdrs=Ba\u015Fl\u0131klar +regex_src_url=Adres (URL) +regexfunc_param_1=\u00D6nceki istekte sonu\u00E7lar\u0131 aramak i\u00E7in kullan\u0131lan d\u00FCzenli ifade +regexfunc_param_2=De\u011Fi\u015Ftirme metni i\u00E7in, d\u00FCzenli ifadeden gruplar\u0131 kullanan \u015Fablon.\nBi\u00E7im $[group]$. \u00D6rnek $1$. +regexfunc_param_3=Hangi e\u015Fle\u015Fme kullan\u0131lacak. 1 ya da 1'den b\u00FCy\u00FCk bir tamsay\u0131, rastgele se\u00E7im i\u00E7in RAND, b\u00FCy\u00FCk say\u0131 (float) i\u00E7in A, t\u00FCm e\u015Fle\u015Fmelerin kullan\u0131lmas\u0131 i\u00E7in ALL ([1]) +regexfunc_param_4=Metinler aras\u0131nda. E\u011Fer ALL se\u00E7iliyse, aradaki metin sonu\u00E7lar\u0131 yaratmak i\u00E7in kullan\u0131lacak ([""]) +regexfunc_param_5=\u00D6ntan\u0131ml\u0131 metin. E\u011Fer d\u00FCzenli ifade bir e\u015Fle\u015Fme yakalayamazsa, yerine kullan\u0131lacak \u015Fablon ([""]) +remote_error_init=Uzak sunucuyu s\u0131f\u0131rlarken hata +remote_error_starting=Uzak sunucuyu ba\u015Flat\u0131rken hata +remote_exit=Uzakta \u00C7\u0131k +remote_exit_all=Uzakta Hepsinden \u00C7\u0131k +remote_start=Uzakta Ba\u015Flat +remote_start_all=Uzakta Hepsini Ba\u015Flat +remote_stop=Uzakta Durdur +remote_stop_all=Uzakta Hepsini Durdur +remove=Kald\u0131r +rename=Girdiyi yeniden adland\u0131r +report=Rapor +report_bar_chart=Bar Grafi\u011Fi +report_bar_graph_url=Adres (URL) +report_base_directory=Temel Dizin +report_chart_caption=Grafik Ba\u015Fl\u0131\u011F\u0131 +report_chart_x_axis=X Ekseni +report_chart_x_axis_label=X Ekseni Etiketi +report_chart_y_axis=Y Ekseni +report_chart_y_axis_label=Y Ekseni Etiketi +report_line_graph=\u00C7izgi Grafik +report_line_graph_urls=\u0130\u00E7erilen Adresler (URLler) +report_output_directory=Rapor i\u00E7in \u00C7\u0131kt\u0131 Dizini +report_page=Rapor Sayfas\u0131 +report_page_element=Sayfa Eleman\u0131 +report_page_footer=Sayfa Altl\u0131\u011F\u0131 +report_page_header=Sayfa Ba\u015Fl\u0131\u011F\u0131 +report_page_index=Sayfa \u0130ndeksi Yarat +report_page_intro=Sayfa Giri\u015Fi +report_page_style_url=CSS Adresi (URL) +report_page_title=Sayfa Ba\u015Fl\u0131\u011F\u0131 +report_pie_chart=Elma Grafi\u011Fi +report_plan=Rapor Plan\u0131 +report_select=Se\u00E7 +report_summary=Rapor \u00D6zeti +report_table=Rapor Tablosu +report_writer=Rapor Yaz\u0131c\u0131 +report_writer_html=HTML Raporu Yaz\u0131c\u0131 +request_data=\u0130stek Verisi +reset_gui=Aray\u00FCz\u00FC S\u0131f\u0131rla +restart=Ba\u015Ftan ba\u015Flat +resultaction_title=Sonu\u00E7 Durumu Eylem \u0130\u015Fleyici +resultsaver_errors=Sadece Ba\u015Far\u0131s\u0131z Cevaplar\u0131 Kaydet +resultsaver_prefix=Dosya ismi \u00F6neki\: +resultsaver_title=Cevaplar\u0131 dosyaya kaydet +retobj=Nesne d\u00F6n +reuseconnection=Ba\u011Flant\u0131y\u0131 tekrar kullan +revert_project=Geri d\u00F6nd\u00FCr +revert_project?=Projeyi geri d\u00F6nd\u00FCr? +root=K\u00F6k +root_title=K\u00F6k +run=\u00C7al\u0131\u015Ft\u0131r +running_test=Testi \u00E7al\u0131\u015Ft\u0131r +runtime_controller_title=\u00C7al\u0131\u015Fma Zaman\u0131 Denet\u00E7isi +runtime_seconds=\u00C7al\u0131\u015Fma Zaman\u0131 (saniyeler) +sample_result_save_configuration=\u00D6rnek Sonu\u00E7 Kaydetme Ayar\u0131 +sampler_label=Etiket +sampler_on_error_action=\u00D6rnekleyici hatas\u0131ndan sonra yap\u0131lacak hareket +sampler_on_error_continue=Devam et +sampler_on_error_stop_test=Testi Durdur +sampler_on_error_stop_thread=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Durdur +save=Kaydet +save?=Kaydet? +save_all_as=Test Plan\u0131 olarak Kaydet +save_as=Se\u00E7imi Farkl\u0131 Kaydet... +save_as_error=Birden fazla madde se\u00E7ili\! +save_as_image=D\u00FC\u011F\u00FCm\u00FC Resim olarak Kaydet +save_as_image_all=Ekran\u0131 Resim olarak Kaydet +save_assertionresultsfailuremessage=Do\u011Frulama Ba\u015Far\u0131s\u0131zl\u0131\u011F\u0131 Mesaj\u0131n\u0131 Kaydet +save_assertions=Do\u011Frulama Sonu\u00E7lar\u0131n\u0131 Kaydet (XML) +save_asxml=XML olarak Kaydet +save_bytes=Bayt say\u0131s\u0131n\u0131 kaydet +save_code=Cevap Kodunu Kaydet +save_datatype=Data Tipini Kaydet +save_encoding=Kodlamay\u0131 Kaydet +save_fieldnames=Alan \u0130simlerini Kaydet (CSV) +save_filename=Cevap Dosya \u0130smini Kaydet +save_graphics=Grafi\u011Fi Kaydet +save_hostname=Sunucu \u0130smini Kaydet +save_label=Etiketi Kaydet +save_latency=Gecikme S\u00FCresi Kaydet +save_message=Cevap Mesaj\u0131n\u0131 Kaydet +save_overwrite_existing_file=Se\u00E7ili dosya zaten mevcut, \u00FCst\u00FCne yazmak ister misiniz? +save_requestheaders=\u0130stek Ba\u015Fl\u0131klar\u0131n\u0131 Kaydet (XML) +save_responsedata=Cevap Verisini Kaydet (XML) +save_responseheaders=Cevap Ba\u015Fl\u0131klar\u0131n\u0131 Kaydet (XML) +save_samplecount=\u00D6rnek ve Hata Say\u0131s\u0131n\u0131 Kaydet +save_samplerdata=\u00D6rnekleyici Verisini Kaydet (XML) +save_subresults=Alt Sonu\u00E7lar\u0131 Kaydet (XML)\n +save_success=Ba\u015Far\u0131y\u0131 Kaydet +save_threadcounts=Aktif \u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Say\u0131s\u0131n\u0131 Kaydet +save_threadname=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 \u0130smini Kaydet +save_time=Ge\u00E7en Zaman\u0131 Kaydet +save_timestamp=Tarih Bilgisini Kaydet +save_url=URL Adresini Kaydet +sbind=Yaln\u0131z ba\u011Fla/ba\u011Flama +scheduler=Planla +scheduler_configuration=Planlama Ayar\u0131 +scope=Kapsam +search_base=Ara\u015Ft\u0131rma temeli +search_filter=Ara\u015Ft\u0131rma Filtresi +search_test=Ara\u015Ft\u0131rma Testi +searchbase=Ara\u015Ft\u0131rma temeli +searchfilter=Ara\u015Ft\u0131rma Filtresi +searchtest=Ara\u015Ft\u0131rma testi +second=saniye +secure=G\u00FCvenli +send_file=\u0130stekle Beraber Dosya G\u00F6nder\: +send_file_browse=G\u00F6zat... +send_file_filename_label=Dosya Yolu\: +send_file_mime_label=MIME Tipi\: +send_file_param_name_label=Parametre \u0130smi\: +server=Sunucu \u0130smi veya IP\: +servername=Sunucu \u0130smi \: +session_argument_name=Oturum Arg\u00FCman\u0131 \u0130smi +should_save=Testi \u00E7al\u0131\u015Ft\u0131rmadan \u00F6nce test plan\u0131n\u0131 kaydetmeniz tavsiye edilir.\nE\u011Fer destek veri dosyalar\u0131 kullan\u0131yorsan\u0131z (\u00F6r\: CSV Veri K\u00FCmesi ya da _StringFromFile), \u00F6ncelikle test beti\u011Fini kaydetmeniz \u00F6nemlidir.\n\u00D6ncelikle test plan\u0131n\u0131 kaydetmek istiyor musunuz? +shutdown=Kapat +simple_config_element=Basit Ayar Eleman\u0131 +simple_data_writer_title=Basit Veri Yaz\u0131c\u0131 +size_assertion_comparator_error_equal=e\u015Fittir +size_assertion_comparator_error_greater=b\u00FCy\u00FCkt\u00FCr +size_assertion_comparator_error_greaterequal=b\u00FCy\u00FCkt\u00FCr ya da e\u015Fittir +size_assertion_comparator_error_less=k\u00FC\u00E7\u00FCkt\u00FCr +size_assertion_comparator_error_lessequal=k\u00FC\u00E7\u00FCkt\u00FCr ya da e\u015Fittir +size_assertion_comparator_error_notequal=e\u015Fit de\u011Fildir +size_assertion_comparator_label=Kar\u015F\u0131la\u015Ft\u0131rma Tipi +size_assertion_failure=Sonu\u00E7 boyutunda yanl\u0131\u015Fl\u0131k\: {0} bayt, halbuki {1} {2} bayt olmas\u0131 bekleniyordu. +size_assertion_input_error=L\u00FCtfen ge\u00E7erli pozitif bir tamsay\u0131 girin. +size_assertion_label=Boyut (bayt)\: +size_assertion_size_test=Do\u011Frulanacak Boyut +size_assertion_title=Boyut Do\u011Frulamas\u0131 +soap_data_title=Soap/XML-RPC Verisi +soap_sampler_title=SOAP/XML-RPC \u0130ste\u011Fi +soap_send_action=SOAPAction g\u00F6nder\: +spline_visualizer_average=Ortalama +spline_visualizer_incoming=Gelen +spline_visualizer_maximum=Maksimum +spline_visualizer_title=Cetvel G\u00F6r\u00FCnt\u00FCleyici +spline_visualizer_waitingmessage=\u00D6rnekler i\u00E7in bekliyor +ssl_alias_prompt=L\u00FCtfen tercih etti\u011Finiz k\u0131saltmay\u0131 girin +ssl_alias_select=Test k\u0131saltman\u0131z\u0131 se\u00E7iniz +ssl_alias_title=\u0130stemci K\u0131saltmas\u0131 +ssl_error_title=Anahtar Kayd\u0131 Problemi +ssl_pass_prompt=L\u00FCtfen \u015Fifrenizi girin +ssl_pass_title=Anahtar Kayd\u0131 Problemi +ssl_port=SSL Portu +sslmanager=SSL Y\u00F6neticisi +start=Ba\u015Flat +starttime=Ba\u015Flama Zaman\u0131 +stop=Durdur +stopping_test=T\u00FCm i\u015F par\u00E7ac\u0131klar\u0131n\u0131 kapat\u0131yor. L\u00FCtfen sab\u0131rl\u0131 olun. +stopping_test_title=Testleri Durduruyor +string_from_file_file_name=Dosya tam yolunu girin +string_from_file_seq_final=Dosya s\u0131ra numaras\u0131n\u0131 sonland\u0131r (iste\u011Fe ba\u011Fl\u0131) +string_from_file_seq_start=Dosya s\u0131ra numaras\u0131n\u0131 ba\u015Flat (iste\u011Fe ba\u011Fl\u0131) +summariser_title=\u00D6zet Sonu\u00E7lar Olu\u015Ftur +summary_report=\u00D6zet Rapor +switch_controller_label=Dallanma (Switch) De\u011Feri +switch_controller_title=Dallanma (Switch) Denet\u00E7isi +table_visualizer_bytes=Bayt +table_visualizer_sample_num=\u00D6rnek \# +table_visualizer_sample_time=\u00D6rnek Zaman\u0131(ms) +table_visualizer_start_time=Ba\u015Flama Zaman\u0131 +table_visualizer_status=Durum +table_visualizer_success=Ba\u015Far\u0131 +table_visualizer_thread_name=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 \u0130smi +table_visualizer_warning=Uyar\u0131 +tcp_config_title=TCP \u00D6rnekleyici Ayar\u0131 +tcp_nodelay=Hi\u00E7 Gecikmeye Ayarla +tcp_port=Port Numaras\u0131\: +tcp_request_data=G\u00F6nderilecek metin +tcp_sample_title=TCP \u00D6rnekleyici +tcp_timeout=Zaman A\u015F\u0131m\u0131 (milisaniye) +template_field=\u015Eablon\: +test_action_action=Hareket +test_action_duration=S\u00FCre (milisaniye) +test_action_pause=Duraklat +test_action_stop=Durdur +test_action_target=Hedef +test_action_target_test=T\u00FCm \u0130\u015F Par\u00E7ac\u0131klar\u0131 +test_action_target_thread=\u015Eu Anki \u0130\u015F Par\u00E7ac\u0131\u011F\u0131 +test_action_title=Test Hareketi +test_configuration=Test Ayar\u0131 +test_plan=Test Plan\u0131 +test_plan_classpath_browse=S\u0131n\u0131f yoluna (classpath) dizin veya jar ekle +testconfiguration=Test Ayar\u0131 +testplan.serialized=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Gruplar\u0131n\u0131 Ard\u0131\u015F\u0131k \u00C7al\u0131\u015Ft\u0131r (\u00F6r\: her defas\u0131nda bir grup \u00E7al\u0131\u015Ft\u0131r) +testplan_comments=Yorumlar\: +thread_delay_properties=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Gecikme \u00D6zellikleri +thread_group_title=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Grubu +thread_properties=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 \u00D6zellikleri +threadgroup=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131 Grubu +throughput_control_bynumber_label=Toplam Y\u00FCr\u00FCtme +throughput_control_bypercent_label=Y\u00FCr\u00FCtme Y\u00FCzdesi +throughput_control_perthread_label=Kullan\u0131c\u0131 Ba\u015F\u0131na +throughput_control_title=Transfer Oran\u0131 Denet\u00E7isi +throughput_control_tplabel=Transfer Oran\u0131 +time_format=Metni SimpleDateFormat i\u00E7in bi\u00E7imlendir (iste\u011Fe ba\u011Fl\u0131) +timelim=Zaman s\u0131n\u0131r\u0131 +tr=T\u00FCrk\u00E7e +transaction_controller_parent=Ebeveyn \u00F6rnek olu\u015Ftur +transaction_controller_title=\u0130\u015Flem (transaction) Denet\u00E7isi +unbind=\u0130\u015F Par\u00E7ac\u0131\u011F\u0131n\u0131 B\u0131rak +uniform_timer_delay=Sabit Gecikme S\u0131n\u0131r\u0131 (milisaniye)\: +uniform_timer_memo=Tek bi\u00E7imli da\u011F\u0131l\u0131mla rastgele gecikme ekler +uniform_timer_range=Maksimum Rastgele Gecikme (milisaniye)\: +uniform_timer_title=Tek Bi\u00E7imli Rastgele Zamanlay\u0131c\u0131 +update_per_iter=Her Tekrar i\u00E7in Bir Defa Yenile +upload=Dosya Y\u00FCkleme +upper_bound=\u00DCst S\u0131n\u0131r +url=Adres (URL) +url_config_protocol=Protokol\: +url_config_title=HTTP \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +url_full_config_title=UrlFull \u00D6rne\u011Fi +url_multipart_config_title=HTTP Multipart \u0130ste\u011Fi \u00D6ntan\u0131ml\u0131 De\u011Ferleri +use_keepalive=Canl\u0131Tut (KeepAlive) kullan +use_multipart_for_http_post=HTTP POST i\u00E7in multipart/form-data kullan +use_recording_controller=Kaydetme Denet\u00E7isi Kullan +user=Kullan\u0131c\u0131 +user_defined_test=Kullan\u0131c\u0131 Tan\u0131ml\u0131 Test +user_defined_variables=Kullan\u0131c\u0131 Tan\u0131ml\u0131 De\u011Fi\u015Fkenler +user_param_mod_help_note=(Buray\u0131 de\u011Fi\u015Ftirme. Onun yerine, JMeter'in /bin dizinindeki dosya ismini d\u00FCzenle) +user_parameters_table=Parametreler +user_parameters_title=Kullan\u0131c\u0131 Parametreleri +userdn=Kullan\u0131c\u0131 ismi +username=Kullan\u0131c\u0131 ismi +userpw=\u015Eifre +value=De\u011Fer +var_name=Referans \u0130smi +variable_name_param=De\u011Fi\u015Fken ismi (de\u011Fi\u015Fken ve fonksiyon referanslar\u0131 i\u00E7erebilir) +view_graph_tree_title=Grafik A\u011Fac\u0131n\u0131 G\u00F6ster +view_results_in_table=Sonu\u00E7 Tablosunu G\u00F6ster +view_results_render_html=HTML i\u015Fle +view_results_render_json=JSON i\u015Fle +view_results_render_text=Metin G\u00F6ster +view_results_render_xml=XML \u0130\u015Fle +view_results_tab_assertion=Do\u011Frulama sonucu +view_results_tab_request=\u0130stek +view_results_tab_response=Cevap verisi +view_results_tab_sampler=\u00D6rnekleyici sonucu +view_results_title=Sonu\u00E7lar\u0131 G\u00F6ster +view_results_tree_title=Sonu\u00E7lar\u0131 G\u00F6sterme A\u011Fac\u0131 +warning=Uyar\u0131\! +web_request=HTTP \u0130ste\u011Fi +web_server=A\u011F Sunucusu +web_server_client=\u0130stemci uygulamas\u0131\: +web_server_domain=Sunucu \u0130smi veya IP\: +web_server_port=Port Numaras\u0131\: +web_testing2_title=HTTP \u0130ste\u011Fi HTTPClient +web_testing_embedded_url_pattern=G\u00F6m\u00FCl\u00FC Adresler (URL) \u00F6rt\u00FC\u015Fmeli\: +web_testing_retrieve_images=HTML Dosyalardan T\u00FCm G\u00F6m\u00FCl\u00FC Kaynaklar\u0131 Al +web_testing_title=HTTP \u0130ste\u011Fi +webservice_proxy_host=Vekil Makine +webservice_proxy_note=E\u011Fer "HTTP Vekil Sunucu kullan" se\u00E7iliyse, ama sunucu veya port belirtilmemi\u015Fse, \u00F6rnekleyici +webservice_proxy_note2=komut sat\u0131r\u0131 se\u00E7eneklerine bakacakt\u0131r. E\u011Fer vekil sunucu veya port belirtilmediyse +webservice_proxy_note3=ya da, sessizce ba\u015Far\u0131s\u0131z olacakt\u0131r. +webservice_proxy_port=Vekil Port +webservice_sampler_title=WebService(SOAP) \u0130ste\u011Fi +webservice_timeout=Zaman A\u015F\u0131m\u0131\: +webservice_use_proxy=HTTP Vekil Sunucusunu kullan +while_controller_label=Ko\u015Ful (fonksiyon veya de\u011Fi\u015Fken) +while_controller_title=While Denet\u00E7isi +workbench_title=Tezgah +wsdl_helper_error=WSDL ge\u00E7erli de\u011Fil, l\u00FCtfen url adresini iki defa kontrol edin. +wsdl_url=WSDL Adresi +wsdl_url_error=WSDL bo\u015F. +xml_assertion_title=XML Do\u011Frulamas\u0131 +xml_namespace_button=Namespace kullan +xml_tolerant_button=Ho\u015Fg\u00F6r\u00FClen XML/HTML Ayr\u0131\u015Ft\u0131r\u0131c\u0131 +xml_validate_button=XML'i do\u011Frula +xml_whitespace_button=G\u00F6r\u00FCnmeyen Karakterleri Yoksay +xmlschema_assertion_label=Dosya \u0130smi\: +xmlschema_assertion_title=XML \u015Eemas\u0131 Do\u011Frulamas\u0131 +xpath_assertion_button=Do\u011Frula +xpath_assertion_check=XPath \u0130fadesini Kontrol Et +xpath_assertion_error=XPath'te Hata +xpath_assertion_failed=Ge\u00E7ersiz XPath \u0130fadesi +xpath_assertion_negate=E\u011Fer e\u015Fle\u015Fen yoksa True +xpath_assertion_option=XML Ayr\u0131\u015Ft\u0131r\u0131c\u0131 Se\u00E7enekleri +xpath_assertion_test=XPath Do\u011Frulamas\u0131 +xpath_assertion_tidy=Dene ve girdiyi d\u00FCzenle +xpath_assertion_title=XPath Do\u011Frulamas\u0131 +xpath_assertion_valid=Ge\u00E7erli XPath \u0130fadesi +xpath_assertion_validation=XML'i DTD'ye g\u00F6re kontrol et +xpath_assertion_whitespace=G\u00F6r\u00FCnmeyen Karakterleri Yoksay +xpath_expression=Kar\u015F\u0131la\u015Ft\u0131r\u0131lacak XPath ifadesi +xpath_extractor_query=XPath sorgusu\: +xpath_extractor_title=XPath \u00C7\u0131kar\u0131c\u0131 +xpath_file_file_name=De\u011Ferlerin okunaca\u011F\u0131 XML dosyas\u0131 +xpath_tidy_quiet=Sessiz +xpath_tidy_report_errors=Hatalar\u0131 raporla +xpath_tidy_show_warnings=Uyar\u0131lar\u0131 g\u00F6ster +you_must_enter_a_valid_number=Ge\u00E7erli bir rakam girmelisiniz +zh_cn=\u00C7ince (Basitle\u015Ftirilmi\u015F) +zh_tw=\u00C7ince (Geleneksel) diff --git a/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_zh_CN.properties b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_zh_CN.properties new file mode 100644 index 0000000..e76abe7 --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_zh_CN.properties @@ -0,0 +1,439 @@ +# 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. + +about=\u5173\u4E8EApache JMeter +add=\u6DFB\u52A0 +add_as_child=\u6DFB\u52A0\u5B50\u8282\u70B9 +add_parameter=\u6DFB\u52A0\u53D8\u91CF +add_pattern=\u6DFB\u52A0\u6A21\u5F0F\uFF1A +add_test=\u6DFB\u52A0\u6D4B\u8BD5 +add_user=\u6DFB\u52A0\u7528\u6237 +add_value=\u6DFB\u52A0\u6570\u503C +aggregate_report=\u805A\u5408\u62A5\u544A +aggregate_report_total_label=\u603B\u4F53 +als_message=\u6CE8\u610F\uFF1A\u8BBF\u95EE\u65E5\u5FD7\u89E3\u6790\u5668\uFF08Access Log Parser\uFF09\u662F\u901A\u7528\u7684\u5E76\u5141\u8BB8\u5B9A\u4E49\u63D2\u4EF6 +als_message2=\u81EA\u5B9A\u4E49\u7684\u89E3\u6790\u5668\u3002\u8981\u8FD9\u4E48\u505A\uFF0C\u5B9E\u73B0LogParser\uFF0C\u6DFB\u52A0jar\u5230 +als_message3=/lib\u76EE\u5F55\u5E76\u5728sampler\u4E2D\u8F93\u5165\u7C7B\u540D\u79F0\u3002 +analyze=\u5206\u6790\u6570\u636E\u6587\u4EF6... +anchor_modifier_title=HTML\u94FE\u63A5\u89E3\u6790\u5668 +appearance=\u5916\u89C2 +argument_must_not_be_negative=\u53C2\u6570\u4E0D\u5141\u8BB8\u662F\u8D1F\u503C\uFF01 +assertion_code_resp=\u54CD\u5E94\u4EE3\u7801 +assertion_contains=\u5305\u62EC +assertion_matches=\u5339\u914D +assertion_message_resp=\u54CD\u5E94\u4FE1\u606F +assertion_not=\u5426 +assertion_pattern_match_rules=\u6A21\u5F0F\u5339\u914D\u89C4\u5219 +assertion_patterns_to_test=\u8981\u6D4B\u8BD5\u7684\u6A21\u5F0F +assertion_resp_field=\u8981\u6D4B\u8BD5\u7684\u54CD\u5E94\u5B57\u6BB5 +assertion_text_resp=\u54CD\u5E94\u6587\u672C +assertion_textarea_label=\u65AD\u8A00\uFF1A +assertion_title=\u54CD\u5E94\u65AD\u8A00 +assertion_url_samp=URL\u6837\u672C +assertion_visualizer_title=\u65AD\u8A00\u7ED3\u679C +auth_base_url=\u57FA\u7840URL +auth_manager_title=HTTP\u6388\u6743\u7BA1\u7406\u5668 +auths_stored=\u5B58\u50A8\u5728\u6388\u6743\u7BA1\u7406\u5668\u4E2D\u7684\u6388\u6743 +browse=\u6D4F\u89C8... +bsf_sampler_title=BSF \u53D6\u6837\u5668 +bsf_script=\u8981\u8FD0\u884C\u7684\u811A\u672C +bsf_script_file=\u8981\u8FD0\u884C\u7684\u811A\u672C\u6587\u4EF6 +bsf_script_language=\u811A\u672C\u8BED\u8A00\uFF1A +bsf_script_parameters=\u4F20\u9012\u7ED9\u811A\u672C/\u6587\u4EF6\u7684\u53C2\u6570\uFF1A +bsh_assertion_title=BeanShell\u65AD\u8A00 +bsh_function_expression=\u8868\u8FBE\u5F0F\u6C42\u503C +bsh_script_file=\u811A\u672C\u6587\u4EF6 +bsh_script_parameters=\u53C2\u6570\uFF08-> String Parameters \u548C String [ ]bash.args\uFF09 +busy_testing=\u6B63\u5728\u6D4B\u8BD5\uFF0C\u8BF7\u5728\u4FEE\u6539\u8BBE\u7F6E\u524D\u505C\u6B62\u6D4B\u8BD5 +cancel=\u53D6\u6D88 +cancel_exit_to_save=\u6D4B\u8BD5\u6761\u76EE\u672A\u5B58\u50A8\u3002\u4F60\u60F3\u5728\u9000\u51FA\u524D\u5B58\u50A8\u5417\uFF1F +cancel_new_to_save=\u6D4B\u8BD5\u6761\u76EE\u672A\u5B58\u50A8\u3002\u4F60\u60F3\u5728\u6E05\u7A7A\u6D4B\u8BD5\u8BA1\u5212\u524D\u5B58\u50A8\u5417\uFF1F +choose_function=\u9009\u62E9\u4E00\u4E2A\u529F\u80FD +choose_language=\u9009\u62E9\u8BED\u8A00 +clear=\u6E05\u9664 +clear_all=\u6E05\u9664\u5168\u90E8 +clear_cookies_per_iter=\u6BCF\u6B21\u53CD\u590D\u6E05\u9664Cookies \uFF1F +column_delete_disallowed=\u4E0D\u5141\u8BB8\u5220\u9664\u6B64\u5217 +column_number=CSV\u6587\u4EF6\u5217\u53F7| next| *alias +configure_wsdl=\u914D\u7F6E +constant_throughput_timer_memo=\u5728\u53D6\u6837\u95F4\u6DFB\u52A0\u5EF6\u8FDF\u6765\u83B7\u5F97\u56FA\u5B9A\u7684\u541E\u5410\u91CF +constant_timer_delay=\u7EBF\u7A0B\u5EF6\u8FDF\uFF08\u6BEB\u79D2\uFF09\uFF1A +constant_timer_memo=\u5728\u53D6\u6837\u95F4\u6DFB\u52A0\u56FA\u5B9A\u5EF6\u8FDF +constant_timer_title=\u56FA\u5B9A\u5B9A\u65F6\u5668 +controller=\u63A7\u5236\u5668 +cookie_manager_title=HTTP Cookie \u7BA1\u7406\u5668 +cookies_stored=\u5B58\u50A8\u5728Cookie\u7BA1\u7406\u5668\u4E2D\u7684Cookie +copy=\u590D\u5236 +counter_config_title=\u8BA1\u6570\u5668 +counter_per_user=\u4E0E\u6BCF\u7528\u6237\u72EC\u7ACB\u7684\u8DDF\u8E2A\u8BA1\u6570\u5668 +cut=\u526A\u5207 +cut_paste_function=\u62F7\u8D1D\u5E76\u7C98\u8D34\u51FD\u6570\u5B57\u7B26\u4E32 +database_sql_query_string=SQL\u67E5\u8BE2\u5B57\u7B26\u4E32\uFF1A +database_sql_query_title=JDBC SQL \u67E5\u8BE2\u7F3A\u7701\u503C +de=\u5FB7\u8BED +default_parameters=\u7F3A\u7701\u53C2\u6570 +default_value_field=\u7F3A\u7701\u503C\uFF1A +delay=\u542F\u52A8\u5EF6\u8FDF\uFF08\u79D2\uFF09 +delete=\u5220\u9664 +delete_parameter=\u5220\u9664\u53D8\u91CF +delete_test=\u5220\u9664\u6D4B\u8BD5 +delete_user=\u5220\u9664\u7528\u6237 +disable=\u7981\u7528 +domain=\u57DF +duration=\u6301\u7EED\u65F6\u95F4\uFF08\u79D2\uFF09 +duration_assertion_duration_test=\u65AD\u8A00\u6301\u7EED\u65F6\u95F4 +duration_assertion_failure=\u64CD\u4F5C\u6301\u7EED\u592A\u957F\u65F6\u95F4\uFF1A\u4ED6\u82B1\u8D39\u4E86{0}\u6BEB\u79D2\uFF0C\u4F46\u4E0D\u5E94\u8BE5\u8D85\u8FC7{1}\u6BEB\u79D2\u3002 +duration_assertion_input_error=\u8BF7\u8F93\u5165\u4E00\u4E2A\u6709\u6548\u7684\u6B63\u6574\u6570\u3002 +duration_assertion_label=\u6301\u7EED\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF1A +duration_assertion_title=\u65AD\u8A00\u6301\u7EED\u65F6\u95F4 +edit=\u7F16\u8F91 +email_results_title=\u7535\u5B50\u90AE\u4EF6\u7ED3\u679C +en=\u82F1\u8BED +enable=\u542F\u7528 +encode?=\u7F16\u7801\uFF1F +encoded_value=URL\u7F16\u7801\u540E\u7684\u503C +endtime=\u7ED3\u675F\u65F6\u95F4 +entry_dn=\u5165\u53E3DN +error_loading_help=\u52A0\u8F7D\u5E2E\u52A9\u9875\u9762\u51FA\u9519 +error_occurred=\u53D1\u751F\u9519\u8BEF +example_data=\u6837\u672C\u6570\u636E +example_title=\u793A\u4F8B\u53D6\u6837\u5668 +exit=\u9000\u51FA +expiration=\u8FC7\u671F +field_name=\u5B57\u6BB5\u540D\u6210 +file=\u6587\u4EF6 +file_already_in_use=\u6587\u4EF6\u6B63\u5728\u4F7F\u7528 +file_visualizer_append=\u6DFB\u52A0\u5230\u5DF2\u7ECF\u5B58\u5728\u7684\u6570\u636E\u6587\u4EF6 +file_visualizer_auto_flush=\u5728\u6BCF\u6B21\u6570\u636E\u53D6\u6837\u540E\u81EA\u52A8\u66F4\u65B0 +file_visualizer_browse=\u6D4F\u89C8... +file_visualizer_close=\u5173\u95ED +file_visualizer_file_options=\u6587\u4EF6\u64CD\u4F5C +file_visualizer_filename=\u6587\u4EF6\u540D +file_visualizer_flush=\u66F4\u65B0 +file_visualizer_missing_filename=\u6CA1\u6709\u6307\u5B9A\u8F93\u51FA\u6587\u4EF6\u540D\u3002 +file_visualizer_open=\u6253\u5F00 +file_visualizer_output_file=\u6240\u6709\u6570\u636E\u5199\u5165\u4E00\u4E2A\u6587\u4EF6 +file_visualizer_submit_data=\u5305\u62EC\u88AB\u63D0\u4EA4\u7684\u6570\u636E +file_visualizer_title=\u6587\u4EF6\u62A5\u544A\u5668 +file_visualizer_verbose=\u8BE6\u7EC6\u7684\u8F93\u51FA +filename=\u6587\u4EF6\u540D\u79F0 +follow_redirects=\u8DDF\u968F\u91CD\u5B9A\u5411 +follow_redirects_auto=\u81ea\u52a8\u91cd\u5b9a\u5411 +foreach_controller_title=ForEach\u63A7\u5236\u5668 +foreach_input=\u8F93\u5165\u53D8\u91CF\u524D\u7F00 +foreach_output=\u8F93\u51FA\u53D8\u91CF\u540D\u79F0 +ftp_sample_title=FTP\u8BF7\u6C42\u7F3A\u7701\u503C +ftp_testing_title=FTP\u8BF7\u6C42 +function_dialog_menu_item=\u51FD\u6570\u52A9\u624B\u5BF9\u8BDD\u6846 +function_helper_title=\u51FD\u6570\u52A9\u624B +function_name_param=\u51FD\u6570\u540D\u79F0\u3002\u7528\u4E8E\u5B58\u50A8\u5728\u6D4B\u8BD5\u8BA1\u5212\u4E2D\u5176\u4ED6\u7684\u65B9\u5F0F\u4F7F\u7528\u7684\u503C\u3002 +function_params=\u51FD\u6570\u53C2\u6570 +functional_mode=\u51FD\u6570\u6D4B\u8BD5\u6A21\u5F0F +functional_mode_explanation=\u53EA\u6709\u5F53\u4F60\u9700\u8981\u8BB0\u5F55\u6BCF\u4E2A\u8BF7\u6C42\u4ECE\u670D\u52A1\u5668\u53D6\u5F97\u7684\u6570\u636E\u5230\u6587\u4EF6\u65F6\n\u624D\u9700\u8981\u9009\u62E9\u51FD\u6570\u6D4B\u8BD5\u6A21\u5F0F\u3002\n\n\u9009\u62E9\u8FD9\u4E2A\u9009\u9879\u5F88\u5F71\u54CD\u6027\u80FD\u3002\n +gaussian_timer_delay=\u56FA\u5B9A\u5EF6\u8FDF\u504F\u79FB\uFF08\u6BEB\u79D2\uFF09\uFF1A +gaussian_timer_memo=\u6DFB\u52A0\u4E00\u4E2A\u968F\u673A\u7684\u9AD8\u65AF\u5206\u5E03\u5EF6\u8FDF +gaussian_timer_range=\u504F\u5DEE\uFF08\u6BEB\u79D2\uFF09\uFF1A +gaussian_timer_title=\u9AD8\u65AF\u968F\u673A\u5B9A\u65F6\u5668 +generate=\u751F\u6210 +generator=\u751F\u6210\u5668\u7C7B\u540D\u79F0 +generator_cnf_msg=\u4E0D\u80FD\u627E\u5230\u751F\u6210\u5668\u7C7B\u3002\u8BF7\u786E\u5B9A\u4F60\u5C06jar\u6587\u4EF6\u653E\u7F6E\u5728/lib\u76EE\u5F55\u4E2D\u3002 +generator_illegal_msg=\u7531\u4E8EIllegalAcessException\uFF0C\u4E0D\u80FD\u8BBF\u95EE\u751F\u6210\u5668\u7C7B\u3002 +generator_instantiate_msg=\u4E0D\u80FD\u521B\u5EFA\u751F\u6210\u5668\u89E3\u6790\u5668\u7684\u5B9E\u4F8B\u3002\u8BF7\u786E\u4FDD\u751F\u6210\u5668\u5B9E\u73B0\u4E86Generator\u63A5\u53E3\u3002 +get_xml_from_file=\u5E26SOAP XML \u6570\u636E\u7684\u6587\u4EF6\uFF08\u8986\u76D6\u4E0A\u9762\u7684\u6587\u672C\uFF09 +get_xml_from_random=\u6D88\u606F\u6587\u4EF6\u5939 +graph_choose_graphs=\u8981\u663E\u793A\u7684\u56FE\u5F62 +graph_full_results_title=\u56FE\u5F62\u7ED3\u679C +graph_results_average=\u5E73\u5747 +graph_results_data=\u6570\u636E +graph_results_deviation=\u504F\u79BB +graph_results_latest_sample=\u6700\u65B0\u6837\u672C +graph_results_median=\u4E2D\u503C +graph_results_no_samples=\u6837\u672C\u6570\u76EE +graph_results_throughput=\u541E\u5410\u91CF +graph_results_title=\u56FE\u5F62\u7ED3\u679C +grouping_add_separators=\u5728\u7EC4\u95F4\u6DFB\u52A0\u5206\u9694 +grouping_in_controllers=\u6BCF\u4E2A\u7EC4\u653E\u5165\u4E00\u4E2A\u65B0\u7684\u63A7\u5236\u5668 +grouping_mode=\u5206\u7EC4\uFF1A +grouping_no_groups=\u4E0D\u5BF9\u6837\u672C\u5206\u7EC4 +grouping_store_first_only=\u53EA\u5B58\u50A8\u6BCF\u4E2A\u7EC4\u7684\u7B2C\u4E00\u4E2A\u6837\u672C +header_manager_title=HTTP\u4FE1\u606F\u5934\u7BA1\u7406\u5668 +headers_stored=\u4FE1\u606F\u5934\u5B58\u50A8\u5728\u4FE1\u606F\u5934\u7BA1\u7406\u5668\u4E2D +help=\u5E2E\u52A9 +html_parameter_mask=HTML\u53C2\u6570\u63A9\u7801 +http_response_code=HTTP\u578B\u5E94\u4EE3\u7801 +http_url_rewriting_modifier_title=HTTP URL \u91CD\u5199\u4FEE\u9970\u7B26 +http_user_parameter_modifier=HTTP \u7528\u6237\u53C2\u6570\u4FEE\u9970\u7B26 +id_prefix=ID\u524D\u7F00 +id_suffix=ID\u540E\u7F00 +if_controller_label=\u6761\u4EF6 +if_controller_title=\u5982\u679C\uFF08If\uFF09\u63A7\u5236\u5668 +ignore_subcontrollers=\u5FFD\u7565\u8D44\u63A7\u5236\u5668\u5757 +include_equals=\u5305\u542B\u7B49\u4E8E\uFF1F +increment=\u9012\u589E +infinite=\u6C38\u8FDC +insert_after=\u4E4B\u540E\u63D2\u5165 +insert_before=\u4E4B\u524D\u63D2\u5165 +insert_parent=\u63D2\u5165\u4E0A\u7EA7 +interleave_control_title=\u4EA4\u66FF\u63A7\u5236\u5668 +intsum_param_1=\u8981\u6DFB\u52A0\u7684\u7B2C\u4E00\u4E2A\u6574\u6570\u3002 +intsum_param_2=\u8981\u6DFB\u52A0\u7684\u7B2C\u4E8C\u4E2A\u6574\u6570\u2014\u2014\u66F4\u591A\u7684\u6574\u6570\u53EF\u4EE5\u901A\u8FC7\u6DFB\u52A0\u66F4\u591A\u7684\u53C2\u6570\u6765\u6C42\u548C\u3002 +invalid_data=\u65E0\u6548\u6570\u636E +invalid_mail_server=\u90AE\u4EF6\u670D\u52A1\u5668\u4E0D\u53EF\u77E5\u3002 +iteration_counter_arg_1=TRUE\uFF0C\u6BCF\u4E2A\u7528\u6237\u6709\u81EA\u5DF1\u7684\u8BA1\u6570\u5668\uFF1BFALSE\uFF0C\u4F7F\u7528\u5168\u5C40\u8BA1\u6570\u5668 +iterator_num=\u5FAA\u73AF\u6B21\u6570 +java_request=Java\u8BF7\u6C42 +java_request_defaults=Java\u8BF7\u6C42\u9ED8\u8BA4\u503C +jndi_config_title=JNDI\u914D\u7F6E +jndi_lookup_name=\u8FDC\u7A0B\u63A5\u53E3 +jndi_lookup_title=JNDI\u67E5\u8BE2\u914D\u7F6E +jndi_method_button_invoke=\u8C03\u7528 +jndi_method_button_reflect=\u53CD\u5C04 +jndi_method_home_name=\u672C\u5730\u65B9\u6CD5\u540D\u79F0 +jndi_method_home_parms=\u672C\u5730\u65B9\u6CD5\u53C2\u6570 +jndi_method_name=\u65B9\u6CD5\u914D\u7F6E +jndi_method_remote_interface_list=\u8FDC\u7A0B\u63A5\u53E3 +jndi_method_remote_name=\u8FDC\u7A0B\u65B9\u6CD5\u540D\u79F0 +jndi_method_remote_parms=\u8FDC\u7A0B\u65B9\u6CD5\u53C2\u6570 +jndi_method_title=\u8FDC\u7A0B\u65B9\u6CD5\u914D\u7F6E +jndi_testing_title=JNDI\u8BF7\u6C42 +jndi_url_jndi_props=JNDI\u5C5E\u6027 +ja=\u65E5\u8BED +ldap_sample_title=LDAP\u8BF7\u6C42\u9ED8\u8BA4\u503C +ldap_testing_title=LDAP\u8BF7\u6C42 +load=\u8F7D\u5165 +load_wsdl=\u8F7D\u5165WSDL +log_errors_only=\u4EC5\u65E5\u5FD7\u9519\u8BEF +log_file=\u65E5\u5FD7\u6587\u4EF6\u4F4D\u7F6E +log_parser=\u65E5\u5FD7\u89E3\u6790\u5668\u7C7B\u540D +log_parser_cnf_msg=\u627E\u4E0D\u5230\u7C7B\u3002\u786E\u8BA4\u4F60\u5C06jar\u6587\u4EF6\u653E\u5728\u4E86/lib\u76EE\u5F55\u4E2D\u3002 +log_parser_illegal_msg=\u56E0\u4E3AIllegalAccessException\u4E0D\u80FD\u8BBF\u95EE\u7C7B\u3002 +log_parser_instantiate_msg=\u4E0D\u80FD\u521B\u5EFA\u65E5\u5FD7\u89E3\u6790\u5668\u5B9E\u4F8B\u3002\u786E\u8BA4\u89E3\u6790\u5668\u5B9E\u73B0\u4E86LogParser\u63A5\u53E3\u3002 +log_sampler=Tomcat\u8BBF\u95EE\u65E5\u5FD7\u53D6\u6837\u5668 +logic_controller_title=\u7B80\u5355\u63A7\u5236\u5668 +login_config=\u767B\u9646\u914D\u7F6E +login_config_element=\u767B\u9646\u914D\u7F6E\u5143\u4EF6/\u7D20 +loop_controller_title=\u5FAA\u73AF\u63A7\u5236\u5668 +looping_control=\u5FAA\u73AF\u63A7\u5236 +lower_bound=\u8F83\u4F4E\u8303\u56F4 +mailer_attributes_panel=\u90AE\u4EF6\u5C5E\u6027 +mailer_error=\u4E0D\u80FD\u53D1\u9001\u90AE\u4EF6\u3002\u8BF7\u4FEE\u6B63\u9519\u8BEF\u3002 +mailer_visualizer_title=\u90AE\u4EF6\u89C2\u5BDF\u4EEA +match_num_field=\u5339\u914D\u6570\u5B57\uFF080\u4EE3\u8868\u968F\u673A\uFF09\uFF1A +max=\u6700\u5927\u503C +maximum_param=\u4E00\u4E2A\u8303\u56F4\u5185\u5141\u8BB8\u7684\u6700\u5927\u503C +md5hex_assertion_failure=MD5\u603B\u5408\u65AD\u8A00\u9519\u8BEF\uFF1A\u5F97\u5230\u4E86{0}\uFF0C\u4F46\u5E94\u8BE5\u662F{1} +md5hex_assertion_md5hex_test=\u8981\u65AD\u8A00\u7684MD5Hex +md5hex_assertion_title=MD5Hex\u65AD\u8A00 +memory_cache=\u5185\u5B58\u7F13\u5B58 +menu_assertions=\u65AD\u8A00 +menu_close=\u5173\u95ED +menu_config_element=\u914D\u7F6E\u5143\u4EF6 +menu_edit=\u7F16\u8F91 +menu_listener=\u76D1\u542C\u5668 +menu_logic_controller=\u903B\u8F91\u63A7\u5236\u5668 +menu_merge=\u5408\u5E76 +menu_modifiers=\u4FEE\u9970\u7B26 +menu_non_test_elements=\u975E\u6D4B\u8BD5\u5143\u4EF6 +menu_open=\u6253\u5F00 +menu_post_processors=\u540E\u7F6E\u5904\u7406\u5668 +menu_pre_processors=\u524D\u7F6E\u5904\u7406\u5668 +menu_response_based_modifiers=\u57FA\u4E8E\u76F8\u5E94\u7684\u4FEE\u9970\u7B26 +menu_timer=\u5B9A\u65F6\u5668 +metadata=\u539F\u6570\u636E +method=\u65B9\u6CD5\uFF1A +mimetype=MIME\u7C7B\u578B +minimum_param=\u4E00\u4E2A\u8303\u56F4\u5185\u7684\u6700\u5C0F\u503C +minute=\u5206\u949F +modification_controller_title=\u4FEE\u6B63\u63A7\u5236\u5668 +modification_manager_title=\u4FEE\u6B63\u7BA1\u7406\u5668 +modify_test=\u4FEE\u6539\u6D4B\u8BD5 +module_controller_title=\u6A21\u5757\u63A7\u5236\u5668 +monitor_equation_active=\u6D3B\u52A8\uFF1A(busy/max)>25% +monitor_equation_dead=\u975E\u6D3B\u52A8\uFF1A\u6CA1\u6709\u54CD\u5E94 +monitor_equation_healthy=Healthy\:(busy/max)<25% +monitor_health_title=\u76D1\u89C6\u5668\u7ED3\u679C +monitor_is_title=\u7528\u4F5C\u76D1\u89C6\u5668 +monitor_label_right_active=\u6D3B\u52A8\u7684 +monitor_label_right_dead=\u975E\u6D3B\u52A8\u7684 +monitor_label_right_warning=\u8B66\u544A +monitor_legend_load=\u8D1F\u8F7D +monitor_legend_thread_per=\u7EBF\u7A0B%(busy/max) +monitor_performance_servers=\u670D\u52A1\u5668 +monitor_performance_tab_title=\u6027\u80FD +monitor_performance_title=\u6027\u80FD\u56FE +name=\u540D\u79F0\uFF1A +new=\u65B0\u5EFA +no=\u632A\u5A01\u8BED +number_of_threads=\u7EBF\u7A0B\u6570\uFF1A +once_only_controller_title=\u4EC5\u4E00\u6B21\u63A7\u5236\u5668 +open=\u6253\u5F00... +option=\u9009\u9879 +optional_tasks=\u5176\u4ED6\u4EFB\u52A1 +paramtable=\u540C\u8BF7\u6C42\u4E00\u8D77\u53D1\u9001\u53C2\u6570\uFF1A +password=\u5BC6\u7801 +paste=\u7C98\u8D34 +paste_insert=\u4F5C\u4E3A\u63D2\u5165\u7C98\u8D34 +path=\u8DEF\u5F84\uFF1A +path_extension_choice=\u8DEF\u5F84\u6269\u5C55\uFF08\u4F7F\u7528";"\u4F5C\u5206\u9694\u7B26\uFF09 +patterns_to_exclude=\u6392\u9664\u6A21\u5F0F +patterns_to_include=\u5305\u542B\u6A21\u5F0F +port=\u7AEF\u53E3\uFF1A +property_default_param=\u9ED8\u8BA4\u503C +property_edit=\u7F16\u8F91 +property_editor.value_is_invalid_title=\u65E0\u6548\u8F93\u5165 +property_name_param=\u5C5E\u6027\u540D\u79F0 +property_undefined=\u672A\u5B9A\u4E49 +protocol=\u534F\u8BAE\uFF1A +protocol_java_border=Java\u7C7B +protocol_java_classname=\u7C7B\u540D\u79F0\uFF1A +protocol_java_config_tile=\u914D\u7F6EJava\u6837\u672C +protocol_java_test_title=Java\u6D4B\u8BD5 +proxy_assertions=\u6DFB\u52A0\u65AD\u8A00 +proxy_cl_error=\u5982\u679C\u6307\u5B9A\u4EE3\u7406\u670D\u52A1\u5668\uFF0C\u4E3B\u673A\u548C\u7AEF\u53E3\u5FC5\u987B\u6307\u5B9A +proxy_headers=\u8BB0\u5F55HTTP\u4FE1\u606F\u5934 +proxy_separators=\u6DFB\u52A0\u5206\u9694\u7B26 +proxy_target=\u76EE\u6807\u63A7\u5236\u5668\uFF1A +proxy_title=HTTP\u4EE3\u7406\u670D\u52A1\u5668 +random_control_title=\u968F\u673A\u63A7\u5236\u5668 +random_order_control_title=\u968F\u673A\u987A\u5E8F\u63A7\u5236\u5668 +read_response_message=\u8BFB\u53D6\u54CD\u5E94\u6CA1\u6709\u9009\u4E2D\u3002\u8981\u770B\u5230\u54CD\u5E94\uFF0C\u8BF7\u5728\u53D6\u6837\u5668\u4E2D\u9009\u4E2D\u8BFB\u53D6\u54CD\u5E94\u590D\u9009\u6846\u3002 +read_response_note=\u5982\u679C\u8BFB\u53D6\u54CD\u5E94\u6CA1\u6709\u9009\u4E2D\uFF0C\u53D6\u6837\u5668\u4E0D\u4F1A\u8BFB\u53D6\u54CD\u5E94\u3002 +read_response_note2=\u6216\u8BBE\u7F6E\u6837\u672C\u7ED3\u679C\u3002\u8FD9\u4F1A\u63D0\u9AD8\u6027\u80FD\u3002 +read_response_note3=\u54CD\u5E94\u5185\u5BB9\u4E0D\u4F1A\u8BB0\u5F55\u3002 +read_soap_response=\u8BFB\u53D6SOAP\u54CD\u5E94 +record_controller_title=\u5F55\u5236\u63A7\u5236\u5668 +ref_name_field=\u5F15\u7528\u540D\u79F0\uFF1A +regex_extractor_title=\u6B63\u5219\u8868\u8FBE\u5F0F\u63D0\u53D6\u5668 +regex_field=\u6B63\u5219\u8868\u8FBE\u5F0F\uFF1A +regex_source=\u8981\u68C0\u67E5\u7684\u54CD\u5E94\u5B57\u6BB5 +regex_src_body=\u4E3B\u4F53 +regex_src_hdrs=\u4FE1\u606F\u5934 +regexfunc_param_1=\u7528\u4E8E\u4ECE\u524D\u4E00\u4E2A\u8BF7\u6C42\u641C\u7D22\u7ED3\u679C\u7684\u6B63\u5219\u8868\u8FBE\u5F0F +remote_exit=\u8FDC\u7A0B\u9000\u51FA +remote_exit_all=\u8FDC\u7A0B\u5168\u90E8\u9000\u51FA +remote_start=\u8FDC\u7A0B\u542F\u52A8 +remote_start_all=\u8FDC\u7A0B\u5168\u90E8\u542F\u52A8 +remote_stop=\u8FDC\u7A0B\u505C\u6B62 +remote_stop_all=\u8FDC\u7A0B\u5168\u90E8\u505C\u6B62 +remove=\u5220\u9664 +report=\u62A5\u544A +request_data=\u8BF7\u6C42\u6570\u636E +restart=\u91CD\u542F +resultsaver_prefix=\u6587\u4EF6\u540D\u79F0\u524D\u7F00\uFF1A +resultsaver_title=\u4FDD\u5B58\u54CD\u5E94\u5230\u6587\u4EF6 +root=\u6839 +root_title=\u6839 +run=\u8FD0\u884C +running_test=\u6B63\u5728\u8FD0\u884C\u7684\u6D4B\u8BD5 +sampler_on_error_action=\u5728\u53D6\u6837\u5668\u9519\u8BEF\u540E\u8981\u6267\u884C\u7684\u52A8\u4F5C +sampler_on_error_continue=\u7EE7\u7EED +sampler_on_error_stop_test=\u505C\u6B62\u6D4B\u8BD5 +sampler_on_error_stop_thread=\u505C\u6B62\u7EBF\u7A0B +save=\u4FDD\u5B58\u6D4B\u8BD5\u8BA1\u5212 +save?=\u4FDD\u5B58\uFF1F +save_all_as=\u4FDD\u5B58\u6D4B\u8BD5\u8BA1\u5212\u4E3A +save_as=\u4FDD\u5B58\u4E3A... +scheduler=\u8C03\u5EA6\u5668 +scheduler_configuration=\u8C03\u5EA6\u5668\u914D\u7F6E +search_filter=\u641C\u7D22\u8FC7\u6EE4\u5668 +search_test=\u641C\u7D22\u6D4B\u8BD5 +secure=\u5B89\u5168 +send_file=\u540C\u8BF7\u6C42\u4E00\u8D77\u53D1\u9001\u6587\u4EF6\uFF1A +send_file_browse=\u6D4F\u89C8... +send_file_filename_label=\u6587\u4EF6\u540D\u79F0\uFF1A +send_file_mime_label=MIME\u7C7B\u578B\uFF1A +send_file_param_name_label=\u53C2\u6570\u540D\u79F0\uFF1A +server=\u670D\u52A1\u5668\u540D\u79F0\u6216IP\uFF1A +servername=\u670D\u52A1\u5668\u540D\u79F0\uFF1A +session_argument_name=\u4F1A\u8BDD\u53C2\u6570\u540D\u79F0\uFF1A +shutdown=\u5173\u95ED +simple_config_element=\u7B80\u5355\u914D\u7F6E\u5143\u4EF6 +size_assertion_comparator_label=\u6BD4\u8F83\u7C7B\u578B +size_assertion_input_error=\u8BF7\u8F93\u5165\u4E00\u4E2A\u6709\u6548\u7684\u6B63\u6574\u6570\u3002 +size_assertion_label=\u5B57\u8282\u5927\u5C0F\uFF1A +soap_action=Soap\u52A8\u4F5C +spline_visualizer_average=\u5E73\u5747\u503C +spline_visualizer_incoming=\u8FDB\u5165 +spline_visualizer_maximum=\u6700\u5927\u503C +spline_visualizer_minimum=\u6700\u5C0F\u503C +spline_visualizer_waitingmessage=\u7B49\u5F85\u6837\u672C +ssl_alias_prompt=\u8BF7\u8F93\u5165\u9996\u9009\u7684\u522B\u540D +ssl_alias_select=\u4E3A\u6D4B\u8BD5\u9009\u62E9\u4F60\u7684\u522B\u540D +ssl_alias_title=\u5BA2\u6237\u7AEF\u522B\u540D +ssl_pass_prompt=\u8BF7\u8F93\u5165\u4F60\u7684\u5BC6\u7801 +ssl_port=SSL\u7AEF\u53E3 +sslmanager=SSL\u7BA1\u7406\u5668 +start=\u542F\u52A8 +starttime=\u542F\u52A8\u65F6\u95F4 +stop=\u505C\u6B62 +stopping_test=\u505C\u6B62\u5168\u90E8\u6D4B\u8BD5\u7EBF\u7A0B\u3002\u8BF7\u8010\u5FC3\u7B49\u5F85\u3002 +stopping_test_title=\u6B63\u5728\u505C\u6B62\u6D4B\u8BD5 +string_from_file_file_name=\u8F93\u5165\u6587\u4EF6\u7684\u5168\u8DEF\u5F84 +summariser_title=\u751F\u6210\u6982\u8981\u7ED3\u679C +tcp_config_title=TCP\u53D6\u6837\u5668\u914D\u7F6E +tcp_nodelay=\u8BBE\u7F6E\u65E0\u5EF6\u8FDF +tcp_port=\u7AEF\u53E3\u53F7\uFF1A +tcp_request_data=\u8981\u53D1\u9001\u7684\u6587\u672C +tcp_sample_title=TCP\u53D6\u6837\u5668 +tcp_timeout=\u8D85\u65F6\uFF1A +template_field=\u6A21\u677F\uFF1A +test=\u6D4B\u8BD5 +test_configuration=\u6D4B\u8BD5\u914D\u7F6E +test_plan=\u6D4B\u8BD5\u8BA1\u5212 +testplan.serialized=\u72EC\u7ACB\u8FD0\u884C\u6BCF\u4E2A\u7EBF\u7A0B\u7EC4\uFF08\u4F8B\u5982\u5728\u4E00\u4E2A\u7EC4\u8FD0\u884C\u7ED3\u675F\u540E\u542F\u52A8\u4E0B\u4E00\u4E2A\uFF09 +testplan_comments=\u6CE8\u91CA\uFF1A +thread_delay_properties=\u7EBF\u7A0B\u5EF6\u8FDF\u5C5E\u6027 +thread_group_title=\u7EBF\u7A0B\u7EC4 +thread_properties=\u7EBF\u7A0B\u5C5E\u6027 +threadgroup=\u7EBF\u7A0B\u7EC4 +throughput_control_title=\u541E\u5410\u91CF\u63A7\u5236\u5668 +throughput_control_tplabel=\u541E\u5410\u91CF +transaction_controller_title=\u4E8B\u52A1\u63A7\u5236\u5668 +update_per_iter=\u6BCF\u6B21\u8DCC\u4EE3\u66F4\u65B0\u4E00\u6B21 +upload=\u6587\u4EF6\u4E0A\u8F7D +upper_bound=\u4E0A\u9650 +url_config_protocol=\u534F\u8BAE\uFF1A +url_config_title=HTTP\u8BF7\u6C42\u9ED8\u8BA4\u503C +use_recording_controller=\u4F7F\u7528\u5F55\u5236\u63A7\u5236\u5668 +user=\u7528\u6237 +user_defined_test=\u7528\u6237\u5B9A\u4E49\u7684\u6D4B\u8BD5 +user_defined_variables=\u7528\u6237\u5B9A\u4E49\u7684\u53D8\u91CF +user_parameters_table=\u53C2\u6570 +user_parameters_title=\u7528\u6237\u53C2\u6570 +username=\u7528\u6237\u540D +value=\u503C +var_name=\u5F15\u7528\u540D\u79F0 +view_graph_tree_title=\u5BDF\u770B\u7ED3\u679C\u6811 +view_results_in_table=\u7528\u8868\u683C\u5BDF\u770B\u7ED3\u679C +view_results_tab_request=\u8BF7\u6C42 +view_results_tab_response=\u54CD\u5E94\u6570\u636E +view_results_tab_sampler=\u53D6\u6837\u5668\u7ED3\u679C +view_results_title=\u5BDF\u770B\u7ED3\u679C +view_results_tree_title=\u5BDF\u770B\u7ED3\u679C\u6811 +web_request=HTTP\u8BF7\u6C42 +web_server=Web\u670D\u52A1\u5668 +web_server_domain=\u670D\u52A1\u5668\u540D\u79F0\u6216IP\uFF1A +web_server_port=\u7AEF\u53E3\u53F7\uFF1A +web_testing_retrieve_images=\u4ECEHTML\u6587\u4EF6\u83B7\u53D6\u6240\u6709\u5185\u542B\u7684\u8D44\u6E90 +web_testing_title=HTTP\u8BF7\u6C42 +webservice_proxy_host=\u4EE3\u7406\u670D\u52A1\u5668\u4E3B\u673A +webservice_proxy_port=\u4EE3\u7406\u670D\u52A1\u5668\u7AEF\u53E3 +webservice_use_proxy=\u4F7F\u7528\u4EE3\u7406\u670D\u52A1\u5668 +workbench_title=\u5DE5\u4F5C\u53F0 +wsdl_helper_error=WSDL\u65E0\u6548\uFF0C\u8BF7\u68C0\u67E5URL\u3002 +wsdl_url_error=WSDL\u662F\u7A7A\u7684\u3002 +xml_assertion_title=XML\u65AD\u8A00 +you_must_enter_a_valid_number=\u5FC5\u987B\u8F93\u5165\u6709\u6548\u7684\u6570\u5B57 + diff --git a/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_zh_TW.properties b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_zh_TW.properties new file mode 100644 index 0000000..4031e0d --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/resources/messages_zh_TW.properties @@ -0,0 +1,654 @@ +# 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. + +#Stored by I18NEdit, may be edited! +about=\u95DC\u65BC Apache JMeter +add=\u65B0\u589E +add_as_child=\u65B0\u589E\u70BA\u5B50\u5143\u4EF6 +add_parameter=\u65B0\u589E\u53C3\u6578 +add_pattern=\u65B0\u589E\u6A23\u5F0F\: +add_test=\u65B0\u589E\u6E2C\u8A66 +add_user=\u65B0\u589E\u4F7F\u7528\u8005 +add_value=\u65B0\u589E\u503C +addtest=\u65B0\u589E\u6E2C\u8A66 +aggregate_report=\u5F59\u6574\u5831\u544A +aggregate_report_90%_line=90% \u76F4\u7DDA +aggregate_report_bandwidth=\u6BCF\u79D2\u4EDF\u4F4D\u5143\u7D44 +aggregate_report_count=\u53D6\u6A23\u6578 +aggregate_report_error%=\u932F\u8AA4\u7387 +aggregate_report_max=\u6700\u5927\u503C +aggregate_report_median=\u4E2D\u9593\u503C +aggregate_report_min=\u6700\u5C0F\u503C +aggregate_report_rate=\u8655\u7406\u91CF +aggregate_report_total_label=\u7E3D\u8A08 +als_message=\u8A3B\uFF1AAccess Log Parser \u8A2D\u8A08\u6210\u901A\u7528\uFF0C\u4F60\u53EF\u4EE5\u81EA\u884C\u52A0\u5F37\u529F\u80FD +als_message2=\u8981\u5BE6\u4F5C LogParser\uFF0C\u4E26\u5C07 jar \u653E\u5728 +als_message3=/lib \u76EE\u9304\uFF0C\u7136\u5F8C\u5728\u53D6\u6A23\u4E2D\u8F38\u5165 class \u540D\u7A31 +analyze=\u5206\u6790\u8CC7\u6599\u6A94\u6848\u4E2D... +anchor_modifier_title=HTML \u93C8\u7D50\u5256\u6790\u5668 +argument_must_not_be_negative=\u53C3\u6578\u4E0D\u53EF\u4EE5\u70BA\u8CA0\u503C\uFF01 +assertion_assume_success=\u5FFD\u7565\u72C0\u614B +assertion_code_resp=\u56DE\u8986\u4EE3\u78BC +assertion_contains=\u5305\u542B +assertion_matches=\u76F8\u7B26 +assertion_message_resp=\u56DE\u8986\u8A0A\u606F +assertion_not=\u975E +assertion_pattern_match_rules=\u6A23\u5F0F\u6BD4\u5C0D\u898F\u5247 +assertion_patterns_to_test=\u6E2C\u8A66\u7528\u6A23\u5F0F +assertion_resp_field=\u9808\u6AA2\u67E5\u7684\u56DE\u8986\u6B04\u4F4D +assertion_text_resp=\u56DE\u8986\u6587\u5B57 +assertion_textarea_label=\u9A57\u8B49\uFF1A +assertion_title=\u9A57\u8B49\u56DE\u8986 +assertion_url_samp=\u53D6\u6A23\u7684 URL +assertion_visualizer_title=\u9A57\u8B49\u7D50\u679C +attribute=\u5C6C\u6027 +attrs=\u5C6C\u6027 +auth_manager_title=HTTP \u6388\u6B0A\u7BA1\u7406\u54E1 +auths_stored=\u6388\u6B0A\u7BA1\u7406\u54E1\u4E2D\u8A18\u8F09\u7684\u6388\u6B0A\u8CC7\u6599 +average=\u5E73\u5747\u503C +bind=\u57F7\u884C\u7DD2\u9023\u7D50 +browse=\u700F\u89BD... +bsf_sampler_title=BSF \u53D6\u6A23 +bsf_script=\u8173\u672C +bsf_script_file=\u8173\u672C\u6A94 +bsf_script_language=\u8173\u672C\u8A9E\u8A00\uFF1A +bsf_script_parameters=\u50B3\u7D66\u8173\u672C(\u6A94\u6848)\u7684\u53C3\u6578\uFF1A +bsh_assertion_script=\u8173\u672C +bsh_assertion_script_variables=\u56DE\u8986[\u8CC7\u6599|\u4EE3\u78BC|\u8A0A\u606F|\u8868\u982D], \u8981\u6C42\u8868\u982D, \u53D6\u6A23\u6A19\u984C, \u53D6\u6A23\u8CC7\u6599 +bsh_assertion_title=BeanShell \u9A57\u8B49 +bsh_function_expression=\u88AB\u9A57\u8B49\u7684\u8868\u793A\u5F0F +bsh_sampler_title=BeanShell \u53D6\u6A23 +bsh_script=\u8173\u672C(\u8B8A\u6578\uFF1A\u53D6\u6A23\u7D50\u679C,\u56DE\u8986\u4EE3\u78BC,\u56DE\u8986\u8A0A\u606F,\u662F\u5426\u6210\u529F,\u6A19\u984C,\u6A94\u540D) +bsh_script_file=\u8173\u672C\u6A94\u6848 +bsh_script_parameters=\u53C3\u6578(->\u5B57\u4E32\u53C3\u6578\u548C String []bsh.args) +busy_testing=\u6211\u6B63\u5FD9\u8457\u6E2C\u5462, \u8981\u6539\u8A2D\u5B9A\u503C\u8ACB\u5148\u505C\u6B62\u6E2C\u8A66 +cancel=\u53D6\u6D88 +cancel_exit_to_save=\u5C1A\u672A\u5132\u5B58, \u5148\u5132\u5B58\u518D\u96E2\u958B\u597D\u55CE\uFF1F +cancel_new_to_save=\u5C1A\u672A\u5132\u5B58, \u5148\u5132\u5B58\u518D\u6E05\u9664\u597D\u55CE\uFF1F +choose_function=\u9078\u64C7\u4E00\u500B\u529F\u80FD +choose_language=\u9078\u64C7\u4E00\u7A2E\u8A9E\u8A00 +clear=\u6E05\u9664 +clear_all=\u5168\u90E8\u6E05\u9664 +clear_cookies_per_iter=\u6BCF\u56DE\u5408\u90FD\u5148\u6E05\u9664 Cookies\uFF1F +column_delete_disallowed=\u6B64\u6B04\u4F4D\u4E0D\u5141\u8A31\u522A\u9664 +compare=\u6BD4\u8F03 +comparefilt=\u6BD4\u8F03\u904E\u6FFE\u5668 +config_element=\u8A2D\u5B9A +config_save_settings=\u8A2D\u5B9A +configure_wsdl=\u8A2D\u5B9A +constant_throughput_timer_memo=\u70BA\u4F7F\u8655\u7406\u91CF\u70BA\u56FA\u5B9A\u503C, \u5728\u53D6\u6A23\u9593\u52A0\u5165\u5EF6\u9072\u6642\u9593 +constant_timer_delay=\u5EF6\u9072\u6642\u9593(\u55AE\u4F4D\u662F\u5343\u5206\u4E4B\u4E00\u79D2) +constant_timer_memo=\u5728\u53D6\u6A23\u9593\u52A0\u5165\u56FA\u5B9A\u7684\u5EF6\u9072\u6642\u9593 +constant_timer_title=\u56FA\u5B9A\u503C\u8A08\u6642\u5668 +controller=\u63A7\u5236\u5668 +cookie_manager_title=HTTP Cookie \u7BA1\u7406\u54E1 +cookies_stored=Cookie \u7BA1\u7406\u54E1\u4E2D\u8A18\u9304\u7684 Cookies +copy=\u8907\u88FD +counter_config_title=\u8A08\u6578\u5668 +counter_per_user=\u6BCF\u500B\u4F7F\u7528\u8005\u7684\u8A08\u6578\u5668 +countlim=\u5927\u5C0F\u9650\u5236 +cut=\u526A\u4E0B +cut_paste_function=\u8907\u88FD\u548C\u8CBC\u4E0A\u5B57\u4E32 +database_conn_pool_max_usage=\u6BCF\u500B\u9023\u7DDA\u6700\u5927\u4F7F\u7528\u91CF\uFF1A +database_conn_pool_props=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60 +database_conn_pool_size=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u4E2D\u7684\u9023\u7DDA\u6578\uFF1A +database_conn_pool_title=JDBC \u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u9810\u8A2D\u503C +database_driver_class=\u9A45\u52D5\u7A0B\u5F0F Class \uFF1A +database_login_title=JDBC \u8CC7\u6599\u5EAB\u767B\u5165\u9810\u8A2D\u503C +database_sql_query_string=SQL \u654D\u8FF0\uFF1A +database_sql_query_title=JDBC SQL \u654D\u8FF0\u9810\u8A2D\u503C +database_testing_title=JDBC \u8981\u6C42 +database_url=JDBC URL\uFF1A +database_url_jdbc_props=\u8CC7\u6599\u5EAB URL \u548C JDBC \u9A45\u52D5\u7A0B\u5F0F +de=\u5FB7\u570B +default_parameters=\u9810\u8A2D\u53C3\u6578 +default_value_field=\u9810\u8A2D\u503C\uFF1A +delay=\u555F\u52D5\u5EF6\u9072\u6642\u9593(\u79D2) +delete=\u522A\u9664 +delete_parameter=\u522A\u9664\u503C +delete_test=\u522A\u9664\u6E2C\u8A66 +delete_user=\u522A\u9664\u4F7F\u7528\u8005 +deltest=\u522A\u9664\u6E2C\u8A66 +deref=\u4E0D\u5F15\u7528\u5225\u540D +disable=\u4E0D\u81F4\u80FD +distribution_graph_title=\u5206\u6563\u5716\u5F62(alpha) +distribution_note1=\u672C\u5716\u65BC\u6BCF10\u500B\u53D6\u6A23\u9032\u884C\u66F4\u65B0 +domain=\u7DB2\u57DF +done=\u5B8C\u6210 +duration=\u671F\u9593 +duration_assertion_duration_test=\u88AB\u9A57\u8B49\u7684\u671F\u9593 +duration_assertion_failure=\u6B64\u52D5\u4F5C\u592A\u4E45\u4E86,\u61C9\u8A72\u5728{1}\u5FAE\u79D2\u4E4B\u5167\u5B8C\u6210,\u537B\u82B1\u4E86{0}\u5FAE\u79D2 +duration_assertion_input_error=\u8ACB\u8F38\u5165\u5408\u6CD5\u6B63\u6574\u6578\u503C +duration_assertion_label=\u671F\u9593(\u5FAE\u79D2) +duration_assertion_title=\u9A57\u8B49\u671F\u9593 +edit=\u7DE8\u8F2F +email_results_title=\u96FB\u90F5\u7D50\u679C +en=\u82F1\u6587 +enable=\u555F\u52D5 +encode?=\u7DE8\u78BC\uFF1F +encoded_value=URL \u7DE8\u78BC\u503C +endtime=\u7D50\u675F\u6642\u9593 +entry_dn=\u9032\u5165 DN +entrydn=\u9032\u5165 DN +error_loading_help=\u8F09\u5165\u8F14\u52A9\u8AAA\u660E\u5931\u6557 +error_occurred=\u767C\u751F\u932F\u8AA4 +example_data=\u8CC7\u6599\u7BC4\u4F8B +example_title=\u53D6\u6A23\u7BC4\u4F8B +exit=\u96E2\u958B +expiration=\u5230\u671F +field_name=\u6B04\u4F4D\u540D\u7A31 +file=\u6A94\u6848 +file_already_in_use=\u6A94\u6848\u5DF2\u5728\u4F7F\u7528\u4E2D +file_visualizer_append=\u52A0\u5165\u65E2\u6709\u7684\u6A94\u6848 +file_visualizer_auto_flush=\u53D6\u5F97\u6BCF\u8CC7\u6599\u5F8C\u81EA\u52D5 Flush +file_visualizer_browse=\u700F\u89BD +file_visualizer_close=\u95DC\u9589 +file_visualizer_file_options=\u6A94\u6848\u9078\u9805 +file_visualizer_filename=\u6A94\u540D +file_visualizer_missing_filename=\u6A94\u540D\u672A\u6307\u5B9A +file_visualizer_open=\u958B\u555F +file_visualizer_output_file=\u5C07\u5168\u90E8\u8CC7\u6599\u5BEB\u6210\u6A94\u6848 +file_visualizer_submit_data=\u5305\u542B\u5DF2\u50B3\u9001\u8CC7\u6599 +file_visualizer_title=\u6A94\u6848\u5831\u544A\u54E1 +file_visualizer_verbose=\u700F\u89BD\u8F38\u51FA +filename=\u6A94\u540D +follow_redirects=\u8DDF\u96A8\u91CD\u5C0E +follow_redirects_auto=\u81EA\u52D5\u8DDF\u96A8\u91CD\u5C0E +foreach_controller_title=ForEach \u63A7\u5236\u5668 +foreach_input=\u8B8A\u6578\u524D\u7F6E\u5B57\u4E32 +foreach_output=\u8F38\u51FA\u8B8A\u6578\u540D\u7A31 +foreach_use_separator=\u5728\u7DE8\u865F\u524D\u52A0\u4E0A\u5E95\u7DDA\u7B26\u865F\uFF1F +fr=\u6CD5\u6587 +ftp_sample_title=FTP \u8981\u6C42\u9810\u8A2D\u503C +ftp_testing_title=FTP \u8981\u6C42 +function_dialog_menu_item=\u529F\u80FD\u8F14\u52A9\u8AAA\u660E\u5C0D\u8A71 +function_helper_title=\u529F\u80FD\u8F14\u52A9\u8AAA\u660E +function_name_param=\u529F\u80FD\u540D\u7A31. \u6703\u51FA\u73FE\u5728\u6574\u4EFD\u6E2C\u8A66\u8A08\u756B\u4E2D +function_params=\u529F\u80FD\u53C3\u6578 +functional_mode=\u529F\u80FD\u6027\u6E2C\u8A66\u6A21\u5F0F +functional_mode_explanation=\u53EA\u6709\u5728\u9700\u8981\u5C07\u6240\u6709\u56DE\u8986\u90FD\u5B58\u6210\u6A94\u6848\u6642\u624D\u9078\u7528\u9019\u7A2E\u6A21\u5F0F\n\n\u9078\u7528\u9019\u7A2E\u6A21\u5F0F\u5C0D\u6548\u80FD\u6703\u6709\u986F\u8457\u5F71\u97FF +gaussian_timer_delay=\u5B9A\u503C\u5EF6\u9072\u5DEE(\u5FAE\u79D2) +gaussian_timer_memo=\u4EE5\u9AD8\u65AF\u5206\u914D\u6CD5\u52A0\u5165\u96A8\u6A5F\u5EF6\u9072\u6642\u9593 +gaussian_timer_range=\u96E2\u5DEE(\u5FAE\u79D2) +gaussian_timer_title=\u9AD8\u65AF\u96A8\u6A5F\u8A08\u6642\u5668 +generate=\u7522\u751F +generator=\u9AD8\u65AF\u6CD5 class \u540D\u7A31 +generator_cnf_msg=\u627E\u4E0D\u5230\u7522\u751F\u5668 class. \u8ACB\u78BA\u8A8D jar \u6A94\u653E\u5728 /lib \u76EE\u9304 +generator_illegal_msg=\u7121\u6CD5\u5B58\u53D6\u7522\u751F\u5668 class (iIllegalAcessException) +generator_instantiate_msg=\u7121\u6CD5\u4F7F\u7528\u7522\u751F\u5668, \u8ACB\u78BA\u8A8D\u5DF2\u5BE6\u4F5C Generator \u4ECB\u9762 +get_xml_from_file=SOAP XML \u6A94(\u53D6\u4EE3\u4E0A\u9762\u6587\u5B57) +get_xml_from_random=\u8A0A\u606F\u8CC7\u6599\u593E +graph_choose_graphs=\u8981\u986F\u793A\u7684\u5716\u5F62 +graph_full_results_title=\u5B8C\u6574\u7D50\u679C\u5716\u5F62 +graph_results_average=\u5E73\u5747 +graph_results_data=\u8CC7\u6599 +graph_results_deviation=\u8B8A\u7570\u5DEE +graph_results_latest_sample=\u6700\u8FD1\u7684\u53D6\u6A23 +graph_results_median=\u4E2D\u9593\u503C +graph_results_ms=\u5FAE\u79D2 +graph_results_no_samples=\u53D6\u6A23\u7684\u7DE8\u865F +graph_results_throughput=\u8655\u7406\u91CF +graph_results_title=\u7D50\u679C\u5716\u5F62 +grouping_add_separators=\u7FA4\u7D44\u9593\u7684\u5206\u9694\u7DDA +grouping_in_controllers=\u6BCF\u500B\u7FA4\u7D44\u5206\u653E\u81F3\u4E0D\u540C\u63A7\u5236\u5668 +grouping_mode=\u7FA4\u7D44\uFF1A +grouping_no_groups=\u4E0D\u8981\u5C07\u53D6\u6A23\u5206\u7FA4\u7D44 +grouping_store_first_only=\u50C5\u5132\u5B58\u6BCF\u500B\u7FA4\u7D44\u7684\u7B2C\u4E00\u500B\u53D6\u6A23 +header_manager_title=HTTP \u6A19\u982D\u7BA1\u7406\u54E1 +headers_stored=\u6A19\u982D\u7BA1\u7406\u54E1\u4E2D\u5132\u5B58\u7684\u6A19\u982D\u8CC7\u6599 +help=\u8F14\u52A9\u8AAA\u660E +html_assertion_label=HTML \u9A57\u8B49 +html_assertion_title=HTML \u9A57\u8B49 +html_parameter_mask=HTML \u53C3\u6578\u906E\u7F69 +http_response_code=HTTP \u56DE\u61C9\u4EE3\u78BC +http_url_rewriting_modifier_title=HTTP URL \u91CD\u5C0E\u4FEE\u98FE\u8A5E +http_user_parameter_modifier=HTTP \u4F7F\u7528\u8005\u53C3\u6578\u4FEE\u98FE\u8A5E +id_prefix=ID \u524D\u7F6E\u5B57\u4E32 +id_suffix=ID \u5F8C\u7F6E\u5B57\u4E32 +if_controller_label=\u689D\u4EF6 +if_controller_title=\u82E5...\u63A7\u5236\u5668 +ignore_subcontrollers=\u5FFD\u7565\u5B50\u63A7\u5236\u5668\u5167\u5BB9 +include_equals=\u5305\u542B\u76F8\u7B49\uFF1F +increment=\u589E\u91CF +infinite=\u6C38\u4E45 +insert_after=\u52A0\u5728\u4E4B\u5F8C +insert_before=\u52A0\u5728\u4E4B\u524D +insert_parent=\u52A0\u5230\u4E0A\u4E00\u968E\u5C64 +interleave_control_title=\u4EA4\u932F\u63A7\u5236\u5668 +intsum_param_1=\u7B2C\u4E00\u500B\u6574\u6578\u53C3\u6578 +intsum_param_2=\u7B2C\u4E8C\u500B\u6574\u6578\u53C3\u6578\u2500\u5176\u4ED6\u7684\u6574\u6578\u53EF\u4EE5\u7531\u65B0\u589E\u7684\u53C3\u6578\u52A0\u7E3D +invalid_data=\u7121\u6548\u8CC7\u6599 +invalid_mail=\u50B3\u9001\u96FB\u90F5\u6642\u767C\u751F\u932F\u8AA4 +invalid_mail_address=\u5075\u6E2C\u5230\u4E00\u500B\u4EE5\u4E0A\u7684\u7121\u6548\u96FB\u90F5\u5730\u5740 +invalid_mail_server=\u7121\u6CD5\u9023\u4E0A\u96FB\u90F5\u4F3A\u670D\u5668(\u8A73\u898BJMeter\u6B77\u7A0B\u6A94) +iteration_counter_arg_1=\u6BCF\u500B\u4F7F\u7528\u8005\u4F7F\u7528\u4E0D\u540C\u8A08\u6578\u5668(TRUE)\u6216\u5171\u7528\u4E00\u500B\u5168\u57DF\u8A08\u6578\u5668(FALSE) +iterator_num=\u8FF4\u5708\u6B21\u6578\uFF1A +java_request=Java \u8981\u6C42 +java_request_defaults=Java \u8981\u6C42\u9810\u8A2D\u503C +jms_auth_required=\u5FC5\u8981 +jms_client_caption=\u63A5\u6536\u7AEF\u900F\u904ETopicSubscriber.receive()\u63A5\u807D\u8A0A\u606F +jms_client_caption2=MessageListener\u900F\u904EonMessage(Message\u4ECB\u9762\u63A5\u807D\u8A0A\u606F +jms_client_type=\u7528\u6236\u7AEF +jms_communication_style=\u6E9D\u901A\u6A21\u5F0F +jms_concrete_connection_factory=\u5805\u56FA\u9023\u7DDA\u5DE5\u5EE0 +jms_config=\u8A2D\u7F6E +jms_config_title=JMS \u8A2D\u7F6E +jms_connection_factory=\u9023\u7DDA\u5DE5\u5EE0 +jms_file=\u6A94\u6848 +jms_initial_context_factory=JNDI \u521D\u59CB\u672C\u6587\u5DE5\u5EE0 +jms_itertions=\u8981\u7D2F\u8A08\u7684\u53D6\u6A23\u6578 +jms_jndi_defaults_title=JNDI \u9810\u8A2D\u914D\u7F6E +jms_jndi_props=JNDI \u5C6C\u6027 +jms_message_title=\u8A0A\u606F +jms_message_type=\u8A0A\u606F\u7A2E\u985E +jms_msg_content=\u8A0A\u606F\u5167\u5BB9 +jms_object_message=\u7269\u4EF6\u8A0A\u606F +jms_props=JMS \u5C6C\u6027 +jms_provider_url=\u63D0\u4F9B\u8005 URL +jms_publisher=JMS \u767C\u4F48\u8005 +jms_pwd=\u5BC6\u78BC +jms_queue=\u4F47\u5217 +jms_queue_connection_factory=\u4F47\u5217\u9023\u7DDA\u5DE5\u5EE0 +jms_queueing=JMS \u8CC7\u6E90 +jms_random_file=\u96A8\u6A5F\u6A94\u6848 +jms_read_response=\u8B80\u53D6\u56DE\u8986 +jms_receive_queue=\u63A5\u6536\u4F47\u5217 +jms_request=\u55AE\u5411\u8981\u6C42 +jms_requestreply=\u8981\u6C42\u4E14\u56DE\u8986 +jms_sample_title=JMS \u9810\u8A2D\u8981\u6C42 +jms_send_queue=\u50B3\u9001\u4F47\u5217 +jms_subscriber_on_message=\u4F7F\u7528 MessageListener.onMessage() +jms_subscriber_receive=\u4F7F\u7528 TopicSubscriber.receive() +jms_subscriber_title=JMS \u8A02\u95B1\u8005 +jms_testing_title=\u8981\u6C42\u8A0A\u606F +jms_text_message=\u6587\u5B57\u8A0A\u606F +jms_timeout=\u903E\u6642 +jms_topic=\u984C\u76EE +jms_use_file=\u5F9E\u6A94\u6848 +jms_use_properties_file=\u4F7F\u7528 jndi.properties \u6A94 +jms_use_random_file=\u96A8\u6A5F\u6A94\u6848 +jms_use_text=\u6587\u5B57\u5340\u57DF +jms_user=\u4F7F\u7528\u8005 +jndi_config_title=JNDI \u914D\u7F6E +jndi_lookup_name=\u9060\u7AEF\u4ECB\u9762 +jndi_lookup_title=JNDI \u5C0B\u67E5\u914D\u7F6E +jndi_method_button_invoke=\u8D77\u52D5 +jndi_method_button_reflect=\u53CD\u6620 +jndi_method_home_name=\u672C\u7AEF\u65B9\u6CD5 +jndi_method_home_parms=\u672C\u7AEF\u65B9\u6CD5\u53C3\u6578 +jndi_method_name=\u65B9\u6CD5\u914D\u7F6E +jndi_method_remote_interface_list=\u9060\u7AEF\u4ECB\u9762 +jndi_method_remote_name=\u9060\u7AEF\u65B9\u6CD5\u540D\u7A31 +jndi_method_remote_parms=\u9060\u7AEF\u65B9\u6CD5\u53C3\u6578 +jndi_method_title=\u9060\u7AEF\u65B9\u6CD5\u914D\u7F6E +jndi_testing_title=JNDI \u8981\u6C42 +jndi_url_jndi_props=JNDI \u5C6C\u6027 +ja=\u65E5\u6587 +ldap_argument_list=LDAP\u53C3\u6578\u5217\u8868 +ldap_sample_title=LDAP \u8981\u6C42\u9810\u8A2D\u503C +ldap_testing_title=LDAP \u8981\u6C42 +ldapext_sample_title=LDAP \u5EF6\u4F38\u8981\u6C42\u9810\u8A2D\u503C +ldapext_testing_title=LDAP \u5EF6\u4F38\u8981\u6C42 +load=\u8F09\u5165 +load_wsdl=\u8F09\u5165 WSDL +log_errors_only=\u53EA\u8A18\u9304\u932F\u8AA4 +log_file=\u6B77\u7A0B\u6A94\u4F4D\u7F6E +log_parser=\u6B77\u7A0B\u6A94\u5256\u6790\u7A0B\u5F0F +log_parser_cnf_msg=\u627E\u4E0D\u5230\u8A72 class, \u8ACB\u78BA\u5B9A jar \u6A94\u653E\u5728 /lib \u76EE\u9304\u4E0B +log_parser_illegal_msg=\u7121\u6CD5\u5B58\u53D6 class (IllegalAcessException) +log_parser_instantiate_msg=\u7121\u6CD5\u5EFA\u7ACB log parser. \u8ACB\u5B9A\u6709\u5BE6\u4F5C LogParser \u4ECB\u9762 +log_sampler=Tomcat \u5B58\u53D6\u8A18\u9304\u5256\u6790\u5668 +logic_controller_title=\u7C21\u6613\u63A7\u5236\u5668 +login_config=\u767B\u5165\u914D\u7F6E +login_config_element=\u767B\u5165\u914D\u7F6E\u5143\u7D20 +loop_controller_title=\u8FF4\u5708\u63A7\u5236\u5668 +looping_control=\u8FF4\u5708\u63A7\u5236 +lower_bound=\u4F4E\u9650 +mail_reader_account=\u4F7F\u7528\u8005 +mail_reader_all_messages=\u5168\u90E8 +mail_reader_delete=\u5F9E\u4F3A\u670D\u5668\u522A\u9664 +mail_reader_folder=\u8CC7\u6599\u593E +mail_reader_num_messages=\u5F85\u63A5\u6536\u8A0A\u606F\u6578 +mail_reader_password=\u5BC6\u78BC +mail_reader_server=\u4F3A\u670D\u5668 +mail_reader_server_type=\u4F3A\u670D\u5668\u7A2E\u985E +mail_reader_title=\u90F5\u4EF6\u8B80\u53D6\u8005\u53D6\u6A23 +mail_sent=\u90F5\u4EF6\u50B3\u9001\u6210\u529F +mailer_attributes_panel=\u90F5\u5BC4\u5C6C\u6027 +mailer_error=\u7121\u6CD5\u5BC4\u51FA. \u8ACB\u66F4\u6B63\u932F\u8AA4\u503C +mailer_visualizer_title=\u90F5\u4EF6\u8996\u89BA\u5316 +match_num_field=\u7B26\u5408\u6578\u5B57(0\u8868\u793A\u96A8\u6A5F) +max=\u6700\u5927\u503C +maximum_param=\u5141\u8A31\u7BC4\u570D\u4E2D\u7684\u6700\u5927\u503C +md5hex_assertion_failure=\u9A57\u8B49 MD5 \u932F\u8AA4,\u61C9\u8A72\u662F{1}\u537B\u5F97\u5230{0} +md5hex_assertion_md5hex_test=\u88AB\u9A57\u8B49\u7684 MD5Hex +md5hex_assertion_title=MD5Hex \u9A57\u8B49 +memory_cache=\u8A18\u61B6\u5FEB\u53D6 +menu_assertions=\u9A57\u8B49 +menu_close=\u95DC\u9589 +menu_config_element=\u8A2D\u5B9A\u5143\u7D20 +menu_edit=\u7DE8\u8F2F +menu_generative_controller=\u53D6\u6A23 +menu_listener=\u63A5\u807D +menu_logic_controller=\u908F\u8F2F\u63A7\u5236\u5668 +menu_merge=\u5408\u4F75 +menu_modifiers=\u4FEE\u98FE\u5143 +menu_non_test_elements=\u975E\u6E2C\u8A66\u5143\u7D20 +menu_open=\u958B\u555F +menu_post_processors=\u5F8C\u7F6E\u8655\u7406\u5668 +menu_pre_processors=\u524D\u7F6E\u8655\u7406\u5668 +menu_response_based_modifiers=\u4EE5\u56DE\u8986\u70BA\u57FA\u6E96\u7684\u4FEE\u98FE\u5143 +menu_timer=\u8A08\u6642\u5668 +metadata=\u8CC7\u6599\u5B9A\u7FA9 +method=\u65B9\u6CD5 +mimetype=MIME\u7A2E\u985E +minimum_param=\u5141\u8A31\u7BC4\u570D\u4E2D\u7684\u6700\u5C0F\u503C +minute=\u5206\u9418 +modddn=\u820A\u8F38\u5165\u540D\u7A31 +modification_controller_title=\u4FEE\u98FE\u63A7\u5236\u5668 +modification_manager_title=\u4FEE\u98FE\u7BA1\u7406\u54E1 +modify_test=\u4FEE\u98FE\u6E2C\u8A66 +modtest=\u4FEE\u98FE\u6E2C\u8A66 +module_controller_title=\u6A21\u7D44\u63A7\u5236\u5668 +monitor_equation_active=\u6B63\u5E38\u904B\u4F5C (\u5FD9\u788C/\u6700\u5927) \u5927\u65BC 25 % +monitor_equation_dead=\u505C\u6A5F \: \u6C92\u6709\u56DE\u61C9 +monitor_equation_healthy=\u5065\u5EB7 (\u5FD9\u788C/\u6700\u5927) < 25% +monitor_equation_load=\u8CA0\u8F09 ( (busy / max) * 50) + ( (used memory / max memory) * 50) +monitor_equation_warning=\u8B66\u544A (busy/max) > 67% +monitor_health_tab_title=\u5065\u5EB7 +monitor_health_title=\u76E3\u8996\u7D50\u679C +monitor_is_title=\u7576\u6210\u76E3\u8996\u5668 +monitor_label_right_active=\u6B63\u5E38\u904B\u4F5C +monitor_label_right_dead=\u505C\u6A5F +monitor_label_right_healthy=\u5065\u5EB7 +monitor_label_right_warning=\u8B66\u793A +monitor_legend_health=\u5065\u5EB7 +monitor_legend_load=\u8CA0\u8F09 +monitor_legend_memory_per=\u8A18\u61B6\u9AD4\u4F7F\u7528\u7387 % (userd/total) +monitor_legend_thread_per=\u57F7\u884C\u7DD2 % (busy/max) +monitor_performance_servers=\u4F3A\u670D\u5668 +monitor_performance_tab_title=\u6548\u80FD +monitor_performance_title=\u6548\u80FD\u5716\u5F62 +name=\u540D\u7A31 +new=\u65B0 +newdn=\u65B0\u7684\u8B58\u5225\u540D\u7A31 +no=\u632A\u5A01 +number_of_threads=\u57F7\u884C\u7DD2\u6578\u91CF +once_only_controller_title=\u53EA\u6709\u4E00\u6B21\u63A7\u5236\u5668 +open=\u958B\u555F... +option=\u9078\u9805 +optional_tasks=\u9078\u64C7\u6027\u5DE5\u4F5C +paramtable=\u9001\u51FA\u542B\u53C3\u6578\u7684\u8981\u6C42 +password=\u5BC6\u78BC +paste=\u8CBC\u4E0A +paste_insert=\u8CBC\u4E0A(\u63D2\u5165) +path=\u8DEF\u5F91 +path_extension_choice=\u5EF6\u4F38\u8DEF\u5F91(\u4F7F\u7528\u5206\u865F\u505A\u70BA\u5206\u9694\u865F) +path_extension_dont_use_equals=\u4E0D\u8981\u5728\u5EF6\u4F38\u8DEF\u5F91\u4E2D\u4F7F\u7528\u7B49\u865F(Intershop Enfinity compatibility) +path_extension_dont_use_questionmark=\u4E0D\u8981\u5728\u5EF6\u4F38\u8DEF\u5F91\u4E2D\u4F7F\u7528\u554F\u865F(Intershop Enfinity compatibility) +patterns_to_exclude=\u9664\u5916\u7684\u578B\u5F0F +patterns_to_include=\u8981\u5305\u542B\u7684\u578B\u5F0F +port=\u7AEF\u53E3 +property_default_param=\u9810\u8A2D\u503C +property_edit=\u7DE8\u8F2F +property_editor.value_is_invalid_message=\u7531\u65BC\u4F60\u7684\u8F38\u5165\u503C\u4E0D\u5408\u6CD5.\u81EA\u52D5\u56DE\u5FA9\u5230\u539F\u503C +property_editor.value_is_invalid_title=\u7121\u6548\u8F38\u5165 +property_name_param=\u5C6C\u6027\u540D\u7A31 +property_undefined=\u672A\u5B9A\u7FA9 +protocol=\u5354\u5B9A +protocol_java_config_tile=\u8A2D\u5B9A Java \u7BC4\u4F8B +protocol_java_test_title=Java \u6E2C\u8A66 +proxy_assertions=\u589E\u52A0\u9A57\u8B49 +proxy_cl_error=\u82E5\u8981\u6307\u5B9A\u4EE3\u7406\u4F3A\u670D\u5668,\u9808\u63D0\u4F9B\u4E3B\u6A5F\u540D\u7A31\u548C\u7AEF\u53E3 +proxy_headers=\u622A\u53D6 HTTP \u8868\u982D +proxy_separators=\u589E\u52A0\u5206\u9694 +proxy_target=\u76EE\u6A19\u63A7\u5236\u5668 +proxy_title=HTTP \u4EE3\u7406\u4F3A\u670D\u5668 +ramp_up=\u555F\u52D5\u5EF6\u9072(\u79D2) +random_control_title=\u96A8\u6A5F\u63A7\u5236\u5668 +random_order_control_title=\u96A8\u6A5F\u9806\u5E8F\u63A7\u5236\u5668 +read_response_message=\u56DE\u8986\u4E0D\u6703\u88AB\u6AA2\u67E5. \u8981\u770B\u5230\u56DE\u8986\u7684\u8A71, \u8ACB\u9078\u53D6\u53D6\u6A23\u4E2D\u7684\u9078\u53D6\u5340 +read_response_note=\u5982\u679C\u6C92\u9EDE\u9078 read response, \u53D6\u6A23\u5C07\u4E0D\u6703\u8B80\u53D6\u56DE\u8986\u8CC7\u6599 +read_response_note2=\u4E5F\u4E0D\u6703\u8A2D\u5B9A SampleResult. \u5982\u6B64\u53EF\u4EE5\u6539\u5584\u6548\u80FD, \u4F46\u4E5F\u8868\u793A +read_response_note3=\u5C07\u4E0D\u6703\u8A18\u9304\u56DE\u8986\u7684\u5167\u5BB9 +read_soap_response=\u8B80\u53D6 SOAP \u56DE\u8986 +record_controller_title=\u9304\u88FD\u63A7\u5236\u5668 +ref_name_field=\u53C3\u7167\u540D\u7A31 +regex_extractor_title=\u6B63\u898F\u8868\u793A\u5F0F\u5256\u6790\u5668 +regex_field=\u6B63\u898F\u8868\u793A\u5F0F +regex_source=\u6B63\u898F\u8868\u793A\u5F0F\u6B04\u4F4D +regex_src_body=\u672C\u6587 +regex_src_hdrs=\u8868\u982D +remote_exit=\u9060\u7AEF\u96E2\u958B +remote_exit_all=\u9060\u7AEF\u96E2\u958B\u5168\u90E8 +remote_start=\u9060\u7AEF\u555F\u52D5 +remote_start_all=\u9060\u7AEF\u555F\u52D5\u5168\u90E8 +remote_stop=\u9060\u7AEF\u505C\u6B62 +remote_stop_all=\u9060\u7AEF\u505C\u6B62\u5168\u90E8 +remove=\u79FB\u9664 +rename=\u66F4\u540D +report=\u5831\u544A +request_data=\u8981\u6C42\u8CC7\u6599 +restart=\u91CD\u65B0\u555F\u52D5 +resultaction_title=\u7D50\u679C\u72C0\u614B\u52D5\u4F5C\u8655\u7406\u5668 +resultsaver_errors=\u53EA\u5132\u5B58\u5931\u6557\u7684\u56DE\u8986 +resultsaver_prefix=\u6A94\u540D\u524D\u7F6E\u5B57\u4E32 +resultsaver_title=\u5C07\u56DE\u8986\u5B58\u5230\u6A94\u6848 +retobj=\u50B3\u56DE\u7269\u4EF6 +root=\u6839 +root_title=\u6839 +run=\u57F7\u884C +running_test=\u57F7\u884C\u6E2C\u8A66 +runtime_controller_title=\u57F7\u884C\u6642\u671F\u63A7\u5236\u5668 +runtime_seconds=\u57F7\u884C\u6642\u671F(\u79D2) +sample_result_save_configuration=\u53D6\u6A23\u7D50\u679C\u5132\u5B58\u914D\u7F6E +sampler_on_error_action=\u53D6\u6A23\u932F\u8AA4\u5F8C\u63A1\u53D6\u7684\u52D5\u4F5C +sampler_on_error_continue=\u7E7C\u7E8C +sampler_on_error_stop_test=\u505C\u6B62\u6E2C\u8A66 +sampler_on_error_stop_thread=\u505C\u6B62\u57F7\u884C\u7DD2 +save=\u5132\u5B58\u6E2C\u8A66\u8A08\u756B +save?=\u5132\u5B58\uFF1F +save_all_as=\u5C07\u6E2C\u8A66\u8A08\u756B\u5132\u5B58\u6210... +save_as=\u5132\u5B58\u6210... +save_as_image=\u5132\u5B58\u6210\u5716\u5F62 +save_assertionresultsfailuremessage=\u5132\u5B58\u9A57\u8B49\u7D50\u679C\u5931\u6557\u8A0A\u606F +save_assertions=\u5132\u5B58\u9A57\u8B49\u7D50\u679C +save_asxml=\u5132\u5B58\u6210 XML +save_code=\u5132\u5B58\u56DE\u8986\u4EE3\u78BC +save_datatype=\u5132\u5B58\u8CC7\u6599\u578B\u614B +save_encoding=\u5132\u5B58\u7DE8\u78BC +save_fieldnames=\u5132\u5B58\u6B04\u4F4D\u540D\u7A31 +save_graphics=\u5132\u5B58\u5716\u5F62 +save_label=\u5132\u5B58\u6A19\u984C +save_latency=\u5132\u5B58 Latency +save_message=\u5132\u5B58\u56DE\u8986\u8A0A\u606F +save_requestheaders=\u5132\u5B58\u8981\u6C42\u8868\u982D +save_responsedata=\u5132\u5B58\u56DE\u8986\u8CC7\u6599 +save_responseheaders=\u5132\u5B58\u56DE\u8986\u8868\u982D +save_samplerdata=\u5132\u5B58\u53D6\u6A23\u8CC7\u6599 +save_subresults=\u5132\u5B58\u5B50\u7D50\u679C +save_success=\u5132\u5B58\u6210\u529F +save_threadname=\u5132\u5B58\u57F7\u884C\u7DD2\u540D\u7A31 +save_time=\u5132\u5B58\u6642\u9593 +save_timestamp=\u5132\u5B58\u6642\u9593\u6233\u8A18 +sbind=\u55AE\u4E00\u7E6B\u7D50/\u4E0D\u7E6B\u7D50 +scheduler=\u5B9A\u6642\u5668 +scheduler_configuration=\u5B9A\u6642\u5668\u914D\u7F6E +scope=\u7BC4\u570D +search_base=\u641C\u5C0B\u57FA\u6E96 +search_filter=\u641C\u5C0B\u904E\u6FFE\u689D\u4EF6 +search_test=\u641C\u5C0B\u6E2C\u8A66 +searchbase=\u641C\u5C0B\u57FA\u6E96 +searchfilter=\u641C\u5C0B\u904E\u6FFE\u689D\u4EF6 +searchtest=\u641C\u5C0B\u6E2C\u8A66 +second=\u79D2 +secure=\u5B89\u5168 +send_file=\u8207\u8981\u6C42\u4E00\u540C\u50B3\u9001\u6A94\u6848 +send_file_browse=\u700F\u89BD... +send_file_filename_label=\u6A94\u540D +send_file_mime_label=MIME \u578B\u5F0F +send_file_param_name_label=\u53C3\u6578\u540D\u7A31 +server=\u4F3A\u670D\u5668\u540D\u7A31\u6216 IP +servername=\u4F3A\u670D\u5668\u540D\u7A31 +session_argument_name=\u9023\u7DDA\u968E\u6BB5\u53C3\u6578\u540D\u7A31 +should_save=\u57F7\u884C\u6E2C\u8A66\u524D\u8981\u5148\u5C07\u6E2C\u8A66\u8173\u672C\u5B58\u6A94. \u5C24\u5176\u662F\u7576\u4F60\u4F7F\u7528 CSV Data Set \u6216 _StringFromFile \u6642 +shutdown=\u95DC\u9589 +simple_config_element=\u7C21\u6613\u8A2D\u7F6E\u5143\u7D20 +simple_data_writer_title=\u7C21\u6613\u8CC7\u6599\u5BEB\u4F5C\u8005 +size_assertion_comparator_error_equal=\u7B49\u65BC +size_assertion_comparator_error_greater=\u5927\u65BC +size_assertion_comparator_error_greaterequal=\u5927\u65BC\u6216\u7B49\u65BC +size_assertion_comparator_error_less=\u5C0F\u65BC +size_assertion_comparator_error_lessequal=\u5C0F\u65BC\u6216\u7B49\u65BC +size_assertion_comparator_error_notequal=\u4E0D\u7B49\u65BC +size_assertion_comparator_label=\u6BD4\u8F03\u7684\u985E\u5225 +size_assertion_failure=\u5927\u5C0F\u932F\u8AA4, \u61C9\u8A72\u6709 {1}{2}\u4F4D\u5143\u7D44, \u537B\u6709 {0} \u4F4D\u5143\u7D44 +size_assertion_input_error=\u8ACB\u8F38\u5165\u6709\u6548\u6B63\u6574\u6578 +size_assertion_label=\u5927\u5C0F(\u4F4D\u5143\u7D44) +size_assertion_size_test=\u9A57\u8B49\u5927\u5C0F +size_assertion_title=\u9A57\u8B49\u5927\u5C0F +soap_action=Soap \u52D5\u4F5C +soap_data_title=Soap/XML-RPC \u8CC7\u6599 +soap_sampler_title=SOAP/XML-RPC \u8981\u6C42 +spline_visualizer_average=\u5E73\u5747 +spline_visualizer_incoming=\u6B63\u4F86\u81E8\u7684 +spline_visualizer_maximum=\u6700\u5927\u503C +spline_visualizer_minimum=\u6700\u5C0F\u503C +spline_visualizer_title=\u6A23\u689D\u5716 +spline_visualizer_waitingmessage=\u7B49\u5F85\u53D6\u6A23\u7D50\u679C +ssl_alias_prompt=\u8ACB\u8F38\u5165\u9810\u9078\u7684 alias +ssl_alias_select=\u8ACB\u9078\u64C7\u6E2C\u8A66\u8981\u7528\u7684 alias +ssl_error_title=KeyStore \u554F\u984C +ssl_pass_prompt=\u8ACB\u8F38\u5165\u5BC6\u78BC +ssl_pass_title=KeyStore \u5BC6\u78BC +ssl_port=SSL \u7AEF\u53E3 +sslmanager=SSL \u7BA1\u7406\u54E1 +start=\u958B\u59CB +starttime=\u958B\u59CB\u6642\u9593 +stop=\u505C\u6B62 +stopping_test=\u6B63\u5728\u505C\u6B62\u6240\u6709\u6E2C\u8A66\u7DD2. \u8ACB\u8010\u5FC3\u7B49\u5F85 +stopping_test_title=\u505C\u6B62\u6E2C\u8A66 +string_from_file_file_name=\u8F38\u5165\u6A94\u6848\u5B8C\u6574\u8DEF\u5F91 +string_from_file_seq_final=\u6A94\u6848\u5E8F\u865F(\u7D50\u675F) +string_from_file_seq_start=\u6A94\u6848\u5E8F\u865F(\u958B\u59CB) +summariser_title=\u7522\u751F\u7E3D\u8A08\u7D50\u679C +switch_controller_label=\u5207\u63DB\u503C +switch_controller_title=\u5207\u63DB\u63A7\u5236\u5668 +table_visualizer_bytes=\u4F4D\u5143\u7D44 +table_visualizer_sample_num=\u53D6\u6A23\u7DE8\u865F \# +table_visualizer_sample_time=\u53D6\u6A23\u6642\u9593(\u5FAE\u79D2) +tcp_config_title=TCP \u53D6\u6A23\u8A2D\u5B9A +tcp_nodelay=\u8A2D\u70BA\u4E0D\u5EF6\u9072 +tcp_port=\u7AEF\u53E3\u865F\u78BC +tcp_request_data=\u6B32\u50B3\u9001\u6587\u5B57 +tcp_sample_title=TCP \u53D6\u6A23 +tcp_timeout=\u903E\u6642(\u5FAE\u79D2) +template_field=\u7BC4\u672C +test=\u6E2C\u8A66 +testconfiguration=\u6E2C\u8A66\u914D\u7F6E +test_action_action=\u52D5\u4F5C +test_action_duration=\u671F\u9593 +test_action_pause=\u66AB\u505C +test_action_stop=\u505C\u6B62 +test_action_target=\u6A19\u7684 +test_action_target_test=\u6240\u6709\u57F7\u884C\u7DD2 +test_action_target_thread=\u76EE\u524D\u57F7\u884C\u7DD2 +test_action_title=\u6E2C\u8A66\u52D5\u4F5C +test_configuration=\u6E2C\u8A66\u914D\u7F6E +test_plan=\u6E2C\u8A66\u8A08\u756B +testplan.serialized=\u4F9D\u5E8F\u57F7\u884C\u57F7\u884C\u7DD2\u7FA4\u7D44,\u57F7\u884C\u5B8C\u4E00\u500B\u624D\u6703\u57F7\u884C\u4E0B\u4E00\u500B +testplan_comments=\u5099\u8A3B +testt=\u6E2C\u8A66 +thread_delay_properties=\u57F7\u884C\u7DD2\u5EF6\u9072\u5C6C\u6027 +thread_group_title=\u57F7\u884C\u7DD2\u7FA4\u7D44 +thread_properties=\u57F7\u884C\u7DD2\u5C6C\u6027 +threadgroup=\u57F7\u884C\u7DD2\u7FA4\u7D44 +throughput_control_bynumber_label=\u7E3D\u57F7\u884C\u6578 +throughput_control_bypercent_label=\u767E\u5206\u6BD4\u57F7\u884C +throughput_control_perthread_label=\u6BCF\u500B\u4F7F\u7528\u8005 +throughput_control_title=\u8655\u7406\u91CF\u63A7\u5236\u5668 +throughput_control_tplabel=\u8655\u7406\u91CF +timelim=\u6642\u9593\u9650\u5236 +transaction_controller_title=\u4EA4\u6613\u63A7\u5236\u5668 +unbind=\u672A\u7E6B\u7D50\u57F7\u884C\u7DD2 +uniform_timer_delay=\u5E38\u6578\u5EF6\u9072\u5DEE(\u5FAE\u79D2) +uniform_timer_memo=\u52A0\u5165\u4E00\u81F4\u5206\u4F48\u7684\u96A8\u6A5F\u5EF6\u9072 +uniform_timer_range=\u96A8\u6A5F\u5EF6\u9072\u6700\u5927\u503C(\u5FAE\u79D2) +uniform_timer_title=\u4E00\u81F4\u96A8\u6A5F\u8A08\u6642\u5668 +update_per_iter=\u6BCF\u56DE\u5408\u8B8A\u66F4\u4E00\u6B21 +upload=\u6A94\u6848\u4E0A\u50B3 +upper_bound=\u4E0A\u9650 +url_config_protocol=\u5354\u5B9A +url_config_title=HTTP \u8981\u6C42\u9810\u8A2D\u503C +url_full_config_title=UrlFull \u7BC4\u4F8B +url_multipart_config_title=HTTP Multipart \u8981\u6C42\u9810\u8A2D\u503C +use_recording_controller=\u4F7F\u7528\u9304\u88FD\u63A7\u5236\u5668 +user=\u4F7F\u7528\u8005 +user_defined_test=\u4F7F\u7528\u8005\u81EA\u8A02\u6E2C\u8A66 +user_defined_variables=\u4F7F\u7528\u8005\u81EA\u8A02\u8B8A\u6578 +user_param_mod_help_note=(\u4E0D\u8981\u8B8A\u66F4\u9019\u88E1, \u8981\u6539\u5C31\u6539\u5728 /bin \u76EE\u9304\u4E0B\u540C\u540D\u7684\u6A94\u6848\u5167\u5BB9) +user_parameters_table=\u53C3\u6578 +user_parameters_title=\u4F7F\u7528\u8005\u53C3\u6578 +userdn=\u4F7F\u7528\u8005\u540D\u7A31 +username=\u4F7F\u7528\u8005\u540D\u7A31 +userpw=\u5BC6\u78BC +value=\u503C +var_name=\u53C3\u7167\u540D\u7A31 +view_graph_tree_title=\u6AA2\u8996\u5716\u5F62\u6A39 +view_results_in_table=\u6AA2\u8996\u8868\u683C\u5F0F\u7D50\u679C +view_results_tab_request=\u8981\u6C42 +view_results_tab_response=\u56DE\u8986\u8CC7\u6599 +view_results_tab_sampler=\u53D6\u6A23\u7D50\u679C +view_results_title=\u6AA2\u8996\u7D50\u679C +view_results_tree_title=\u6AA2\u8996\u7D50\u679C\u6A39 +warning=\u8B66\u544A\uFF01 +web_request=HTTP \u8981\u6C42 +web_server=Web \u4F3A\u670D\u5668 +web_server_domain=\u4E3B\u6A5F\u540D\u7A31\u6216 IP +web_server_port=\u7AEF\u53E3\u865F\u78BC +web_testing_retrieve_images=\u53D6\u56DE\u6240\u6709\u5D4C\u5165 HTML \u7684\u8CC7\u6E90 +web_testing_title=HTTP \u8981\u6C42 +webservice_proxy_host=\u4EE3\u7406\u4F3A\u670D\u5668 +webservice_proxy_note=\u5982\u679C\u9078\u7528HTTP\u4EE3\u7406\u4F3A\u670D\u5668,\u537B\u6C92\u6709\u6307\u5B9A\u4E3B\u6A5F\u548C\u7AEF\u53E3\u7684\u8A71 +webservice_proxy_note2=\u53D6\u6A23\u6703\u7531\u57F7\u884C\u547D\u4EE4\u5217\u53C3\u6578\u53D6\u5F97, \u5982\u679C\u547D\u4EE4\u5217\u53C3\u6578 +webservice_proxy_note3=\u4E5F\u6C92\u8A2D\u5B9A, \u5373\u6703\u5931\u6557 +webservice_proxy_port=\u4EE3\u7406\u4F3A\u670D\u5668\u7AEF\u53E3 +webservice_sampler_title=WebService(SOAP) \u8981\u6C42 +webservice_use_proxy=\u4F7F\u7528 HTTP \u4EE3\u7406\u4F3A\u670D\u5668 +while_controller_label=\u689D\u4EF6 (blank/LAST \u6216 true) +while_controller_title=\u7576.. \u63A7\u5236\u5668 +workbench_title=\u5DE5\u4F5C\u53F0 +wsdl_helper_error=\u7121\u6548\u7684 WSDL,\u8ACB\u78BA\u8A8D URL \u7121\u8AA4 +wsdl_url_error=WSDL \u662F\u7A7A\u7684 +xml_assertion_title=XML \u9A57\u8B49 +xml_namespace_button=\u4F7F\u7528\u540D\u7A31\u7A7A\u9593 +xml_tolerant_button=Tolerant XML/HTML \u5256\u6790\u5668 +xml_validate_button=\u9A57\u8B49 XML +xml_whitespace_button=\u5FFD\u7565\u767D\u7A7A\u5B57\u5143 +xpath_assertion_button=\u9A57\u8B49 +xpath_assertion_check=\u6AA2\u67E5 XPath \u654D\u8FF0 +xpath_assertion_error=XPath \u932F\u8AA4 +xpath_assertion_failed=\u7121\u6548 XPath \u654D\u8FF0 +xpath_assertion_negate=\u5982\u679C\u5168\u4E0D\u7B26\u5408\u5247\u70BA True +xpath_assertion_option=\u5256\u6790 XML \u9078\u9805 +xpath_assertion_test=\u9A57\u8B49 XPath +xpath_assertion_tidy=\u8A66\u8457\u4E26\u5C07\u8F38\u5165\u8CC7\u6599 tidy up +xpath_assertion_title=\u9A57\u8B49 XPath +xpath_assertion_valid=\u5408\u6CD5 XPath \u654D\u8FF0 +xpath_assertion_validation=\u4EE5 DTD \u6AA2\u67E5 XML +xpath_assertion_whitespace=\u5FFD\u7565\u767D\u7A7A\u5B57\u5143 +xpath_expression=\u8981\u6BD4\u5C0D\u7528\u7684 XPath \u654D\u8FF0 +xpath_file_file_name=\u53D6\u503C\u4F86\u6E90\u7684 XML \u6A94\u540D +you_must_enter_a_valid_number=\u5FC5\u9808\u8F38\u5165\u4E00\u500B\u5408\u6CD5\u6578\u5B57 diff --git a/ApacheJmeter/src/core/org/apache/jmeter/visualizers/package.html b/ApacheJmeter/src/core/org/apache/jmeter/visualizers/package.html new file mode 100644 index 0000000..fc1ec96 --- /dev/null +++ b/ApacheJmeter/src/core/org/apache/jmeter/visualizers/package.html @@ -0,0 +1,33 @@ + + + + + + + + +This package contains the interfaces that have to be implemented by +any class wishing to display or present data collected in SampleResults. +

+The primary classes/interfaces to be concerned with for implementers is the {@link org.apache.jmeter.visualizers.Visualizer Visualizer} interface, and the {@link org.apache.jmeter.visualizers.gui.AbstractVisualizer AbstractVisualizer} abstract class. + + + diff --git a/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources.properties b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources.properties new file mode 100644 index 0000000..a2e0faf --- /dev/null +++ b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources.properties @@ -0,0 +1,17 @@ +# 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. + +displayName=Example2 +myStringProperty.displayName=A String \ No newline at end of file diff --git a/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_es.properties b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_es.properties new file mode 100644 index 0000000..75cfa74 --- /dev/null +++ b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_es.properties @@ -0,0 +1,18 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=Ejemplo2 +myStringProperty.displayName=Una Cadena diff --git a/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_pt_BR.properties b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_pt_BR.properties new file mode 100644 index 0000000..292db27 --- /dev/null +++ b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_pt_BR.properties @@ -0,0 +1,17 @@ +# 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. + +displayName=Exemplo 2 +myStringProperty.displayName=Uma String (cadeia de caracteres) diff --git a/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_tr.properties b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_tr.properties new file mode 100644 index 0000000..349534f --- /dev/null +++ b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_tr.properties @@ -0,0 +1,18 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=\u00D6rnek2 +myStringProperty.displayName=Bir Metin diff --git a/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_zh_TW.properties b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_zh_TW.properties new file mode 100644 index 0000000..78081b9 --- /dev/null +++ b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example2/Example2Resources_zh_TW.properties @@ -0,0 +1,18 @@ +# 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. + +#Stored by I18NEdit, may be edited! +displayName=\u7BC4\u4F8B 2 +myStringProperty.displayName=\u5B57\u4E32 diff --git a/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3Resources.properties b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3Resources.properties new file mode 100644 index 0000000..c6122ec --- /dev/null +++ b/ApacheJmeter/src/examples/org/apache/jmeter/examples/testbeans/example3/Example3Resources.properties @@ -0,0 +1,16 @@ +# 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. + +displayName=Example3 \ No newline at end of file diff --git a/ApacheJmeter/src/functions/org/apache/jmeter/functions/package.html b/ApacheJmeter/src/functions/org/apache/jmeter/functions/package.html new file mode 100644 index 0000000..d49c81c --- /dev/null +++ b/ApacheJmeter/src/functions/org/apache/jmeter/functions/package.html @@ -0,0 +1,41 @@ + + + +

Functions

+

Methods to be implemented

+ setParameters(Collection) + +

+ execute(prevResult,currentSampler) + Note that either or both of the parameters may be null. + +

Calling sequence

+ When the test plan is prepared for running, one instance of the class is created for each occurrence + of a function call. The setParameters() method is then called on each instance. + Once the test is running, the execute method can be called by any thread, and is + therefore synchronized. + + This is unlike most of (all?) the JMeter test elements, which are created for each thread. + + Any context that needs to be maintained for a thread must be done using ThreadLocal or similar. + + + \ No newline at end of file diff --git a/ApacheJmeter/src/i18nedit.properties b/ApacheJmeter/src/i18nedit.properties new file mode 100644 index 0000000..f199c62 --- /dev/null +++ b/ApacheJmeter/src/i18nedit.properties @@ -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. + +#I18NEdit settings for project +# Do not change the default; it must remain as "en" +locale.default=en +locales=de es fr ja no pl pt_BR zh_CN zh_TW +main.name=jakarta-jmeter +# Do not change the sourcelocale unless you are sure what you are doing +personal.User.sourcelocale=en +# Change the target locale as needed +personal.User.targetlocale=xx +personal.User.workmode=directed \ No newline at end of file diff --git a/ApacheJmeter/src/jorphan/org/apache/commons/cli/avalon/package.html b/ApacheJmeter/src/jorphan/org/apache/commons/cli/avalon/package.html new file mode 100644 index 0000000..6e43a71 --- /dev/null +++ b/ApacheJmeter/src/jorphan/org/apache/commons/cli/avalon/package.html @@ -0,0 +1,183 @@ + + + + Package Documentation for org.apache.commons.cli.avalon Package + + + Utility code for parsing command-line options. +

+

+These classes were originally in the Avalon project in the package org.apache.avalon.excalibur.cli +

+ +

Introduction

+

The utilities in org.apache.commons.cli.avalon assist + you in parsing command line options during startup time. It allows you + to associate a short option and a long option to the same command, and + then test for it in a switch statement.

+ +

Usage Example

+
+import java.util.List;
+
+import org.apache.commons.cli.avalon.CLArgsParser;
+import org.apache.commons.cli.avalon.CLOption;
+import org.apache.commons.cli.avalon.CLOptionDescriptor;
+import org.apache.commons.cli.avalon.CLUtil;
+
+/**
+* Demonstrates the excalibur command-line parsing utility.
+*
+*/
+public class CLDemo {
+    // Define our short one-letter option identifiers.
+    protected static final int HELP_OPT = 'h';
+    protected static final int VERSION_OPT = 'v';
+    protected static final int MSG_OPT = 'm';
+
+    /**
+     *  Define the understood options. Each CLOptionDescriptor contains:
+     * - The "long" version of the option. Eg, "help" means that "--help" will
+     * be recognised.
+     * - The option flags, governing the option's argument(s).
+     * - The "short" version of the option. Eg, 'h' means that "-h" will be
+     * recognised.
+     * - A description of the option.
+     */
+    protected static final CLOptionDescriptor [] options = new CLOptionDescriptor [] {
+        new CLOptionDescriptor("help",
+                CLOptionDescriptor.ARGUMENT_DISALLOWED,
+                HELP_OPT,
+                "print this message and exit"),
+        new CLOptionDescriptor("version",
+                CLOptionDescriptor.ARGUMENT_DISALLOWED,
+                VERSION_OPT,
+                "print the version information and exit"),
+        new CLOptionDescriptor("msg",
+                CLOptionDescriptor.ARGUMENT_REQUIRED,
+                MSG_OPT,
+                "the message to print"),
+    };
+
+    public static void main(String args[]) {
+        // Parse the arguments
+        CLArgsParser parser = new CLArgsParser(args, options);
+
+        if( null != parser.getErrorString() ) {
+           System.err.println( "Error: " + parser.getErrorString() );
+           return;
+        }
+
+        // Get a list of parsed options
+        List clOptions = parser.getArguments();
+        int size = clOptions.size();
+
+        for (int i = 0; i < size; i++) {
+            CLOption option = (CLOption) clOptions.get(i);
+
+            switch (option.getId()) {
+                case CLOption.TEXT_ARGUMENT:
+                    System.out.println("Unknown arg: "+option.getArgument());
+                    break;
+
+                case HELP_OPT:
+                    printUsage();
+                    break;
+
+                case VERSION_OPT:
+                    printVersion();
+                    break;
+
+
+                case MSG_OPT:
+                    System.out.println(option.getArgument());
+                    break;
+            }
+        }
+    }
+
+    private static void printVersion() {
+        System.out.println("1.0");
+        System.exit(0);
+    }
+
+    private static void printUsage() {
+        String lSep = System.getProperty("line.separator");
+        StringBuffer msg = new StringBuffer();
+        msg.append("------------------------------------------------------------------------ ").append(lSep);
+        msg.append("Excalibur command-line arg parser demo").append(lSep);
+        msg.append("Usage: java "+CLDemo.class.getName()+" [options]").append(lSep).append(lSep);
+        msg.append("Options: ").append(lSep);
+        msg.append(CLUtil.describeOptions(CLDemo.options).toString());
+        System.out.println(msg.toString());
+        System.exit(0);
+    }
+}
+
+ +

Parsing Rules

+

+ The command line is parsed according to the following rules. There are + two forms of options in this package, the Long form and the Short form. + The long form of an option is preceded by the '--' characters while the + short form is preceded by a single '-'. Some example options would be; + "--an-option", "-a", "--day", "-s -f -a". +

+

+ In the tradition of UNIX programs, the short form of an option can occur + immediately after another short form option. So if 'a', 'b' and 'c' are + short forms of options that take no parameters then the following + command lines are equivalent: "-abc", "-a -bc", "-a -b -c", "-ab -c", etc. +

+

+ Options can also accept arguments if specified. You can specify that an + option requires an argument in which the text immediately following the + option will be considered to be an argument to the option. So if 'a' was an + option that required an argument then the following would be equivalent; + "-abc", "-a bc" (namely the option 'a' with argument 'bc'). +

+

+ Options can also specify optional arguments. In this case if there is any + text immediately following the option character then it is considered an + argument. Otherwise, the option has no arguments. For example if 'a' was an + option that required an optional argument then "-abc" is an option 'a' with + argument "bc" while "-a bc" is an option 'a' with no argument, followed by + the text "bc".

+

It is also possible to place an '=' sign between the option + and its argument. So if we assume that a is an option that + requires an argument then the following are equivalent; + "-a=bc" and "-abc". +

+

+ In the case of a long option with an optional argument, the '=' sign is required. + For example. --optarg=1, not --optarg 1. +

+

+ In some cases it is also necessary to disable command line parsing so that you + can pass a text argument to the program that starts with a '-' character. To do + this insert the sequence '--' onto the command line with no text immediately + following it. This will disable processing for the rest of the command line. + The '--' characters will not be passed to the user program. For instance the + line "-- -b" would result in the program being passed the + text "-b" (ie. not as an option). +

+ + diff --git a/ApacheJmeter/src/org/apache/commons/cli/avalon/AbstractParserControl.java b/ApacheJmeter/src/org/apache/commons/cli/avalon/AbstractParserControl.java new file mode 100644 index 0000000..243612e --- /dev/null +++ b/ApacheJmeter/src/org/apache/commons/cli/avalon/AbstractParserControl.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +/** + * Class to inherit from so when in future when new controls are added clients + * will no have to implement them. + * + * @see ParserControl + */ +public abstract class AbstractParserControl implements ParserControl { + /** + * By default always continue parsing by returning false. + * + * @param lastOptionCode + * the code of last option parsed + * @return return true to halt, false to continue parsing + * @see ParserControl#isFinished(int) + */ + public boolean isFinished(int lastOptionCode) { + return false; + } +} diff --git a/ApacheJmeter/src/org/apache/commons/cli/avalon/CLArgsParser.java b/ApacheJmeter/src/org/apache/commons/cli/avalon/CLArgsParser.java new file mode 100644 index 0000000..ba42aee --- /dev/null +++ b/ApacheJmeter/src/org/apache/commons/cli/avalon/CLArgsParser.java @@ -0,0 +1,682 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +import java.text.ParseException; +import java.util.Hashtable; +import java.util.Vector; + +/** + * Parser for command line arguments. + * + * This parses command lines according to the standard (?) of GNU utilities. + * + * Note: This is still used in 1.1 libraries so do not add 1.2+ dependencies. + * + * Note that CLArgs uses a backing hashtable for the options index and so + * duplicate arguments are only returned by getArguments(). + * + * @see ParserControl + * @see CLOption + * @see CLOptionDescriptor + */ +public final class CLArgsParser { + // cached character == Integer.MAX_VALUE when invalid + private static final int INVALID = Integer.MAX_VALUE; + + private static final int STATE_NORMAL = 0; + + private static final int STATE_REQUIRE_2ARGS = 1; + + private static final int STATE_REQUIRE_ARG = 2; + + private static final int STATE_OPTIONAL_ARG = 3; + + private static final int STATE_NO_OPTIONS = 4; + + private static final int STATE_OPTION_MODE = 5; + + // Values for creating tokens + private static final int TOKEN_SEPARATOR = 0; + + private static final int TOKEN_STRING = 1; + + private static final char[] ARG_SEPARATORS = new char[] { (char) 0, '=' }; + + private static final char[] NULL_SEPARATORS = new char[] { (char) 0 }; + + private final CLOptionDescriptor[] m_optionDescriptors; + + private final Vector m_options; + + // Key is String or Integer + private Hashtable m_optionIndex; + + private final ParserControl m_control; + + private String m_errorMessage; + + private String[] m_unparsedArgs = new String[] {}; + + // variables used while parsing options. + private char m_ch; + + private String[] m_args; + + private boolean m_isLong; + + private int m_argIndex; + + private int m_stringIndex; + + private int m_stringLength; + + private int m_lastChar = INVALID; + + private int m_lastOptionId; + + private CLOption m_option; + + private int m_state = STATE_NORMAL; + + /** + * Retrieve an array of arguments that have not been parsed due to the + * parser halting. + * + * @return an array of unparsed args + */ + public final String[] getUnparsedArgs() { + return m_unparsedArgs; + } + + /** + * Retrieve a list of options that were parsed from command list. + * + * @return the list of options + */ + public final Vector getArguments() { + // System.out.println( "Arguments: " + m_options ); + return m_options; + } + + /** + * Retrieve the {@link CLOption} with specified id, or null + * if no command line option is found. + * + * @param id + * the command line option id + * @return the {@link CLOption} with the specified id, or null + * if no CLOption is found. + * @see CLOption + */ + public final CLOption getArgumentById(final int id) { + return m_optionIndex.get(Integer.valueOf(id)); + } + + /** + * Retrieve the {@link CLOption} with specified name, or null + * if no command line option is found. + * + * @param name + * the command line option name + * @return the {@link CLOption} with the specified name, or + * null if no CLOption is found. + * @see CLOption + */ + public final CLOption getArgumentByName(final String name) { + return m_optionIndex.get(name); + } + + /** + * Get Descriptor for option id. + * + * @param id + * the id + * @return the descriptor + */ + private final CLOptionDescriptor getDescriptorFor(final int id) { + for (int i = 0; i < m_optionDescriptors.length; i++) { + if (m_optionDescriptors[i].getId() == id) { + return m_optionDescriptors[i]; + } + } + + return null; + } + + /** + * Retrieve a descriptor by name. + * + * @param name + * the name + * @return the descriptor + */ + private final CLOptionDescriptor getDescriptorFor(final String name) { + for (int i = 0; i < m_optionDescriptors.length; i++) { + if (m_optionDescriptors[i].getName().equals(name)) { + return m_optionDescriptors[i]; + } + } + + return null; + } + + /** + * Retrieve an error message that occured during parsing if one existed. + * + * @return the error string + */ + public final String getErrorString() { + // System.out.println( "ErrorString: " + m_errorMessage ); + return m_errorMessage; + } + + /** + * Require state to be placed in for option. + * + * @param descriptor + * the Option Descriptor + * @return the state + */ + private final int getStateFor(final CLOptionDescriptor descriptor) { + final int flags = descriptor.getFlags(); + if ((flags & CLOptionDescriptor.ARGUMENTS_REQUIRED_2) == CLOptionDescriptor.ARGUMENTS_REQUIRED_2) { + return STATE_REQUIRE_2ARGS; + } else if ((flags & CLOptionDescriptor.ARGUMENT_REQUIRED) == CLOptionDescriptor.ARGUMENT_REQUIRED) { + return STATE_REQUIRE_ARG; + } else if ((flags & CLOptionDescriptor.ARGUMENT_OPTIONAL) == CLOptionDescriptor.ARGUMENT_OPTIONAL) { + return STATE_OPTIONAL_ARG; + } else { + return STATE_NORMAL; + } + } + + /** + * Create a parser that can deal with options and parses certain args. + * + * @param args + * the args, typically that passed to the + * public static void main(String[] args) method. + * @param optionDescriptors + * the option descriptors + * @param control + * the parser control used determine behaviour of parser + */ + public CLArgsParser(final String[] args, final CLOptionDescriptor[] optionDescriptors, final ParserControl control) { + m_optionDescriptors = optionDescriptors; + m_control = control; + m_options = new Vector(); + m_args = args; + + try { + parse(); + checkIncompatibilities(m_options); + buildOptionIndex(); + } catch (final ParseException pe) { + m_errorMessage = pe.getMessage(); + } + + // System.out.println( "Built : " + m_options ); + // System.out.println( "From : " + Arrays.asList( args ) ); + } + + /** + * Check for duplicates of an option. It is an error to have duplicates + * unless appropriate flags is set in descriptor. + * + * @param arguments + * the arguments + */ + private final void checkIncompatibilities(final Vector arguments) throws ParseException { + final int size = arguments.size(); + + for (int i = 0; i < size; i++) { + final CLOption option = arguments.elementAt(i); + final int id = option.getDescriptor().getId(); + final CLOptionDescriptor descriptor = getDescriptorFor(id); + + // this occurs when id == 0 and user has not supplied a descriptor + // for arguments + if (null == descriptor) { + continue; + } + + final int[] incompatible = descriptor.getIncompatible(); + + checkIncompatible(arguments, incompatible, i); + } + } + + private final void checkIncompatible(final Vector arguments, final int[] incompatible, final int original) + throws ParseException { + final int size = arguments.size(); + + for (int i = 0; i < size; i++) { + if (original == i) { + continue; + } + + final CLOption option = arguments.elementAt(i); + final int id = option.getDescriptor().getId(); + + for (int j = 0; j < incompatible.length; j++) { + if (id == incompatible[j]) { + final CLOption originalOption = arguments.elementAt(original); + final int originalId = originalOption.getDescriptor().getId(); + + String message = null; + + if (id == originalId) { + message = "Duplicate options for " + describeDualOption(originalId) + " found."; + } else { + message = "Incompatible options -" + describeDualOption(id) + " and " + + describeDualOption(originalId) + " found."; + } + throw new ParseException(message, 0); + } + } + } + } + + private final String describeDualOption(final int id) { + final CLOptionDescriptor descriptor = getDescriptorFor(id); + if (null == descriptor) { + return ""; + } else { + final StringBuilder sb = new StringBuilder(); + boolean hasCharOption = false; + + if (Character.isLetter((char) id)) { + sb.append('-'); + sb.append((char) id); + hasCharOption = true; + } + + final String longOption = descriptor.getName(); + if (null != longOption) { + if (hasCharOption) { + sb.append('/'); + } + sb.append("--"); + sb.append(longOption); + } + + return sb.toString(); + } + } + + /** + * Create a parser that deals with options and parses certain args. + * + * @param args + * the args + * @param optionDescriptors + * the option descriptors + */ + public CLArgsParser(final String[] args, final CLOptionDescriptor[] optionDescriptors) { + this(args, optionDescriptors, null); + } + + /** + * Create a string array that is subset of input array. The sub-array should + * start at array entry indicated by index. That array element should only + * include characters from charIndex onwards. + * + * @param array + * the original array + * @param index + * the cut-point in array + * @param charIndex + * the cut-point in element of array + * @return the result array + */ + private final String[] subArray(final String[] array, final int index, final int charIndex) { + final int remaining = array.length - index; + final String[] result = new String[remaining]; + + if (remaining > 1) { + System.arraycopy(array, index + 1, result, 1, remaining - 1); + } + + result[0] = array[index].substring(charIndex - 1); + + return result; + } + + /** + * Actually parse arguments + */ + private final void parse() throws ParseException { + if (0 == m_args.length) { + return; + } + + m_stringLength = m_args[m_argIndex].length(); + + while (true) { + m_ch = peekAtChar(); + + if (m_argIndex >= m_args.length) { + break; + } + + if (null != m_control && m_control.isFinished(m_lastOptionId)) { + // this may need mangling due to peeks + m_unparsedArgs = subArray(m_args, m_argIndex, m_stringIndex); + return; + } + + if (STATE_OPTION_MODE == m_state) { + // if get to an arg barrier then return to normal mode + // else continue accumulating options + if (0 == m_ch) { + getChar(); // strip the null + m_state = STATE_NORMAL; + } else { + parseShortOption(); + } + } else if (STATE_NORMAL == m_state) { + parseNormal(); + } else if (STATE_NO_OPTIONS == m_state) { + // should never get to here when stringIndex != 0 + addOption(new CLOption(m_args[m_argIndex++])); + } else { + parseArguments(); + } + } + + // Reached end of input arguments - perform final processing + if (m_option != null) { + if (STATE_OPTIONAL_ARG == m_state) { + m_options.addElement(m_option); + } else if (STATE_REQUIRE_ARG == m_state) { + final CLOptionDescriptor descriptor = getDescriptorFor(m_option.getDescriptor().getId()); + final String message = "Missing argument to option " + getOptionDescription(descriptor); + throw new ParseException(message, 0); + } else if (STATE_REQUIRE_2ARGS == m_state) { + if (1 == m_option.getArgumentCount()) { + m_option.addArgument(""); + m_options.addElement(m_option); + } else { + final CLOptionDescriptor descriptor = getDescriptorFor(m_option.getDescriptor().getId()); + final String message = "Missing argument to option " + getOptionDescription(descriptor); + throw new ParseException(message, 0); + } + } else { + throw new ParseException("IllegalState " + m_state + ": " + m_option, 0); + } + } + } + + private final String getOptionDescription(final CLOptionDescriptor descriptor) { + if (m_isLong) { + return "--" + descriptor.getName(); + } else { + return "-" + (char) descriptor.getId(); + } + } + + private final char peekAtChar() { + if (INVALID == m_lastChar) { + m_lastChar = readChar(); + } + return (char) m_lastChar; + } + + private final char getChar() { + if (INVALID != m_lastChar) { + final char result = (char) m_lastChar; + m_lastChar = INVALID; + return result; + } else { + return readChar(); + } + } + + private final char readChar() { + if (m_stringIndex >= m_stringLength) { + m_argIndex++; + m_stringIndex = 0; + + if (m_argIndex < m_args.length) { + m_stringLength = m_args[m_argIndex].length(); + } else { + m_stringLength = 0; + } + + return 0; + } + + if (m_argIndex >= m_args.length) { + return 0; + } + + return m_args[m_argIndex].charAt(m_stringIndex++); + } + + private char m_tokesep; // Keep track of token separator + + private final Token nextToken(final char[] separators) { + m_ch = getChar(); + + if (isSeparator(m_ch, separators)) { + m_tokesep=m_ch; + m_ch = getChar(); + return new Token(TOKEN_SEPARATOR, null); + } + + final StringBuilder sb = new StringBuilder(); + + do { + sb.append(m_ch); + m_ch = getChar(); + } while (!isSeparator(m_ch, separators)); + + m_tokesep=m_ch; + return new Token(TOKEN_STRING, sb.toString()); + } + + private final boolean isSeparator(final char ch, final char[] separators) { + for (int i = 0; i < separators.length; i++) { + if (ch == separators[i]) { + return true; + } + } + + return false; + } + + private final void addOption(final CLOption option) { + m_options.addElement(option); + m_lastOptionId = option.getDescriptor().getId(); + m_option = null; + } + + private final void parseOption(final CLOptionDescriptor descriptor, final String optionString) + throws ParseException { + if (null == descriptor) { + throw new ParseException("Unknown option " + optionString, 0); + } + + m_state = getStateFor(descriptor); + m_option = new CLOption(descriptor); + + if (STATE_NORMAL == m_state) { + addOption(m_option); + } + } + + private final void parseShortOption() throws ParseException { + m_ch = getChar(); + final CLOptionDescriptor descriptor = getDescriptorFor(m_ch); + m_isLong = false; + parseOption(descriptor, "-" + m_ch); + + if (STATE_NORMAL == m_state) { + m_state = STATE_OPTION_MODE; + } + } + + private final void parseArguments() throws ParseException { + if (STATE_REQUIRE_ARG == m_state) { + if ('=' == m_ch || 0 == m_ch) { + getChar(); + } + + final Token token = nextToken(NULL_SEPARATORS); + m_option.addArgument(token.getValue()); + + addOption(m_option); + m_state = STATE_NORMAL; + } else if (STATE_OPTIONAL_ARG == m_state) { + if ('-' == m_ch || 0 == m_ch) { + getChar(); // consume stray character + addOption(m_option); + m_state = STATE_NORMAL; + return; + } + + if (m_isLong && '=' != m_tokesep){ // Long optional arg must have = as separator + addOption(m_option); + m_state = STATE_NORMAL; + return; + } + + if ('=' == m_ch) { + getChar(); + } + + final Token token = nextToken(NULL_SEPARATORS); + m_option.addArgument(token.getValue()); + + addOption(m_option); + m_state = STATE_NORMAL; + } else if (STATE_REQUIRE_2ARGS == m_state) { + if (0 == m_option.getArgumentCount()) { + /* + * Fix bug: -D arg1=arg2 was causing parse error; however + * --define arg1=arg2 is OK This seems to be because the parser + * skips the terminator for the long options, but was not doing + * so for the short options. + */ + if (!m_isLong) { + if (0 == peekAtChar()) { + getChar(); + } + } + final Token token = nextToken(ARG_SEPARATORS); + + if (TOKEN_SEPARATOR == token.getType()) { + final CLOptionDescriptor descriptor = getDescriptorFor(m_option.getDescriptor().getId()); + final String message = "Unable to parse first argument for option " + + getOptionDescription(descriptor); + throw new ParseException(message, 0); + } else { + m_option.addArgument(token.getValue()); + } + // Are we about to start a new option? + if (0 == m_ch && '-' == peekAtChar()) { + // Yes, so the second argument is missing + m_option.addArgument(""); + m_options.addElement(m_option); + m_state = STATE_NORMAL; + } + } else // 2nd argument + { + final StringBuilder sb = new StringBuilder(); + + m_ch = getChar(); + while (!isSeparator(m_ch, NULL_SEPARATORS)) { + sb.append(m_ch); + m_ch = getChar(); + } + + final String argument = sb.toString(); + + // System.out.println( "Arguement:" + argument ); + + m_option.addArgument(argument); + addOption(m_option); + m_option = null; + m_state = STATE_NORMAL; + } + } + } + + /** + * Parse Options from Normal mode. + */ + private final void parseNormal() throws ParseException { + if ('-' != m_ch) { + // Parse the arguments that are not options + final String argument = nextToken(NULL_SEPARATORS).getValue(); + addOption(new CLOption(argument)); + m_state = STATE_NORMAL; + } else { + getChar(); // strip the - + + if (0 == peekAtChar()) { + throw new ParseException("Malformed option -", 0); + } else { + m_ch = peekAtChar(); + + // if it is a short option then parse it else ... + if ('-' != m_ch) { + parseShortOption(); + } else { + getChar(); // strip the - + // -- sequence .. it can either mean a change of state + // to STATE_NO_OPTIONS or else a long option + + if (0 == peekAtChar()) { + getChar(); + m_state = STATE_NO_OPTIONS; + } else { + // its a long option + final String optionName = nextToken(ARG_SEPARATORS).getValue(); + final CLOptionDescriptor descriptor = getDescriptorFor(optionName); + m_isLong = true; + parseOption(descriptor, "--" + optionName); + } + } + } + } + } + + /** + * Build the m_optionIndex lookup map for the parsed options. + */ + private final void buildOptionIndex() { + final int size = m_options.size(); + m_optionIndex = new Hashtable(size * 2); + + for (int i = 0; i < size; i++) { + final CLOption option = m_options.get(i); + final CLOptionDescriptor optionDescriptor = getDescriptorFor(option.getDescriptor().getId()); + + m_optionIndex.put(Integer.valueOf(option.getDescriptor().getId()), option); + + if (null != optionDescriptor && null != optionDescriptor.getName()) { + m_optionIndex.put(optionDescriptor.getName(), option); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/commons/cli/avalon/CLOption.java b/ApacheJmeter/src/org/apache/commons/cli/avalon/CLOption.java new file mode 100644 index 0000000..b0c2a8d --- /dev/null +++ b/ApacheJmeter/src/org/apache/commons/cli/avalon/CLOption.java @@ -0,0 +1,176 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +import java.util.Arrays; + +/** + * Basic class describing an instance of option. + * + */ +public final class CLOption { + /** + * Value of {@link CLOptionDescriptor#getId} when the option is a text argument. + */ + public static final int TEXT_ARGUMENT = 0; + + /** + * Default descriptor. Required, since code assumes that getDescriptor will + * never return null. + */ + private static final CLOptionDescriptor TEXT_ARGUMENT_DESCRIPTOR = new CLOptionDescriptor(null, + CLOptionDescriptor.ARGUMENT_OPTIONAL, TEXT_ARGUMENT, null); + + private String[] m_arguments; + + private CLOptionDescriptor m_descriptor = TEXT_ARGUMENT_DESCRIPTOR; + + /** + * Retrieve argument to option if it takes arguments. + * + * @return the (first) argument + */ + public final String getArgument() { + return getArgument(0); + } + + /** + * Retrieve indexed argument to option if it takes arguments. + * + * @param index + * The argument index, from 0 to {@link #getArgumentCount()}-1. + * @return the argument + */ + public final String getArgument(final int index) { + if (null == m_arguments || index < 0 || index >= m_arguments.length) { + return null; + } else { + return m_arguments[index]; + } + } + + public final CLOptionDescriptor getDescriptor() { + return m_descriptor; + } + + /** + * Constructor taking an descriptor + * + * @param descriptor + * the descriptor iff null, will default to a "text argument" + * descriptor. + */ + public CLOption(final CLOptionDescriptor descriptor) { + if (descriptor != null) { + m_descriptor = descriptor; + } + } + + /** + * Constructor taking argument for option. + * + * @param argument + * the argument + */ + public CLOption(final String argument) { + this((CLOptionDescriptor) null); + addArgument(argument); + } + + /** + * Mutator of Argument property. + * + * @param argument + * the argument + */ + public final void addArgument(final String argument) { + if (null == m_arguments) { + m_arguments = new String[] { argument }; + } else { + final String[] arguments = new String[m_arguments.length + 1]; + System.arraycopy(m_arguments, 0, arguments, 0, m_arguments.length); + arguments[m_arguments.length] = argument; + m_arguments = arguments; + } + } + + /** + * Get number of arguments. + * + * @return the number of arguments + */ + public final int getArgumentCount() { + if (null == m_arguments) { + return 0; + } else { + return m_arguments.length; + } + } + + /** + * Convert to String. + * + * @return the string value + */ + @Override + public final String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("["); + final char id = (char) m_descriptor.getId(); + if (id == TEXT_ARGUMENT) { + sb.append("TEXT "); + } else { + sb.append("Option "); + sb.append(id); + } + + if (null != m_arguments) { + sb.append(", "); + sb.append(Arrays.asList(m_arguments)); + } + + sb.append(" ]"); + + return sb.toString(); + } + + /* + * Convert to a shorter String for test purposes + * + * @return the string value + */ + final String toShortString() { + final StringBuilder sb = new StringBuilder(); + final char id = (char) m_descriptor.getId(); + if (id != TEXT_ARGUMENT) { + sb.append("-"); + sb.append(id); + } + + if (null != m_arguments) { + if (id != TEXT_ARGUMENT) { + sb.append("="); + } + sb.append(Arrays.asList(m_arguments)); + } + return sb.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/commons/cli/avalon/CLOptionDescriptor.java b/ApacheJmeter/src/org/apache/commons/cli/avalon/CLOptionDescriptor.java new file mode 100644 index 0000000..63f82ae --- /dev/null +++ b/ApacheJmeter/src/org/apache/commons/cli/avalon/CLOptionDescriptor.java @@ -0,0 +1,201 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +/** + * Basic class describing an type of option. Typically, one creates a static + * array of CLOptionDescriptors, and passes it to + * {@link CLArgsParser#CLArgsParser(String[], CLOptionDescriptor[])}. + * + * @see CLArgsParser + * @see CLUtil + */ +public final class CLOptionDescriptor { + /** Flag to say that one argument is required */ + public static final int ARGUMENT_REQUIRED = 1 << 1; + + /** Flag to say that the argument is optional */ + public static final int ARGUMENT_OPTIONAL = 1 << 2; + + /** Flag to say this option does not take arguments */ + public static final int ARGUMENT_DISALLOWED = 1 << 3; + + /** Flag to say this option requires 2 arguments */ + public static final int ARGUMENTS_REQUIRED_2 = 1 << 4; + + /** Flag to say this option may be repeated on the command line */ + public static final int DUPLICATES_ALLOWED = 1 << 5; + + private final int m_id; + + private final int m_flags; + + private final String m_name; + + private final String m_description; + + private final int[] m_incompatible; + + /** + * Constructor. + * + * @param name + * the name/long option + * @param flags + * the flags + * @param id + * the id/character option + * @param description + * description of option usage + */ + public CLOptionDescriptor(final String name, final int flags, final int id, final String description) { + + checkFlags(flags); + + m_id = id; + m_name = name; + m_flags = flags; + m_description = description; + m_incompatible = ((flags & DUPLICATES_ALLOWED) != 0) ? new int[0] : new int[] { id }; + } + + + /** + * Constructor. + * + * @param name + * the name/long option + * @param flags + * the flags + * @param id + * the id/character option + * @param description + * description of option usage + * @param incompatible + * descriptors for incompatible options + */ + public CLOptionDescriptor(final String name, final int flags, final int id, final String description, + final CLOptionDescriptor[] incompatible) { + + checkFlags(flags); + + m_id = id; + m_name = name; + m_flags = flags; + m_description = description; + + m_incompatible = new int[incompatible.length]; + for (int i = 0; i < incompatible.length; i++) { + m_incompatible[i] = incompatible[i].getId(); + } + } + + private void checkFlags(final int flags) { + int modeCount = 0; + if ((ARGUMENT_REQUIRED & flags) == ARGUMENT_REQUIRED) { + modeCount++; + } + if ((ARGUMENT_OPTIONAL & flags) == ARGUMENT_OPTIONAL) { + modeCount++; + } + if ((ARGUMENT_DISALLOWED & flags) == ARGUMENT_DISALLOWED) { + modeCount++; + } + if ((ARGUMENTS_REQUIRED_2 & flags) == ARGUMENTS_REQUIRED_2) { + modeCount++; + } + + if (0 == modeCount) { + final String message = "No mode specified for option " + this; + throw new IllegalStateException(message); + } else if (1 != modeCount) { + final String message = "Multiple modes specified for option " + this; + throw new IllegalStateException(message); + } + } + + /** + * Get the array of incompatible option ids. + * + * @return the array of incompatible option ids + */ + protected final int[] getIncompatible() { + return m_incompatible; + } + + /** + * Retrieve textual description. + * + * @return the description + */ + public final String getDescription() { + return m_description; + } + + /** + * Retrieve flags about option. Flags include details such as whether it + * allows parameters etc. + * + * @return the flags + */ + public final int getFlags() { + return m_flags; + } + + /** + * Retrieve the id for option. The id is also the character if using single + * character options. + * + * @return the id + */ + public final int getId() { + return m_id; + } + + /** + * Retrieve name of option which is also text for long option. + * + * @return name/long option + */ + public final String getName() { + return m_name; + } + + /** + * Convert to String. + * + * @return the converted value to string. + */ + @Override + public final String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("[OptionDescriptor "); + sb.append(m_name); + sb.append(", "); + sb.append(m_id); + sb.append(", "); + sb.append(m_flags); + sb.append(", "); + sb.append(m_description); + sb.append(" ]"); + return sb.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/commons/cli/avalon/CLUtil.java b/ApacheJmeter/src/org/apache/commons/cli/avalon/CLUtil.java new file mode 100644 index 0000000..29bbc23 --- /dev/null +++ b/ApacheJmeter/src/org/apache/commons/cli/avalon/CLUtil.java @@ -0,0 +1,108 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +/** + * CLUtil offers basic utility operations for use both internal and external to + * package. + * + * @see CLOptionDescriptor + */ +public final class CLUtil { + private static final int MAX_DESCRIPTION_COLUMN_LENGTH = 60; + + /** + * Private Constructor so that no instance can ever be created. + * + */ + private CLUtil() { + } + + /** + * Format options into StringBuilder and return. This is typically used to + * print "Usage" text in response to a "--help" or invalid option. + * + * @param options + * the option descriptors + * @return the formatted description/help for options + */ + public static final StringBuilder describeOptions(final CLOptionDescriptor[] options) { + final String lSep = System.getProperty("line.separator"); + final StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < options.length; i++) { + final char ch = (char) options[i].getId(); + final String name = options[i].getName(); + String description = options[i].getDescription(); + int flags = options[i].getFlags(); + boolean argumentOptional = ((flags & CLOptionDescriptor.ARGUMENT_OPTIONAL) == CLOptionDescriptor.ARGUMENT_OPTIONAL); + boolean argumentRequired = ((flags & CLOptionDescriptor.ARGUMENT_REQUIRED) == CLOptionDescriptor.ARGUMENT_REQUIRED); + boolean twoArgumentsRequired = ((flags & CLOptionDescriptor.ARGUMENTS_REQUIRED_2) == CLOptionDescriptor.ARGUMENTS_REQUIRED_2); + boolean needComma = false; + if (twoArgumentsRequired) { + argumentRequired = true; + } + + sb.append('\t'); + + if (Character.isLetter(ch)) { + sb.append("-"); + sb.append(ch); + needComma = true; + } + + if (null != name) { + if (needComma) { + sb.append(", "); + } + + sb.append("--"); + sb.append(name); + } + + if (argumentOptional) { + sb.append(" []"); + } + if (argumentRequired) { + sb.append(" "); + } + if (twoArgumentsRequired) { + sb.append("="); + } + sb.append(lSep); + + if (null != description) { + while (description.length() > MAX_DESCRIPTION_COLUMN_LENGTH) { + final String descriptionPart = description.substring(0, MAX_DESCRIPTION_COLUMN_LENGTH); + description = description.substring(MAX_DESCRIPTION_COLUMN_LENGTH); + sb.append("\t\t"); + sb.append(descriptionPart); + sb.append(lSep); + } + + sb.append("\t\t"); + sb.append(description); + sb.append(lSep); + } + } + return sb; + } +} diff --git a/ApacheJmeter/src/org/apache/commons/cli/avalon/ParserControl.java b/ApacheJmeter/src/org/apache/commons/cli/avalon/ParserControl.java new file mode 100644 index 0000000..6247f9e --- /dev/null +++ b/ApacheJmeter/src/org/apache/commons/cli/avalon/ParserControl.java @@ -0,0 +1,38 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +/** + * ParserControl is used to control particular behaviour of the parser. + * + * @see AbstractParserControl + */ +public interface ParserControl { + /** + * Called by the parser to determine whether it should stop after last + * option parsed. + * + * @param lastOptionCode + * the code of last option parsed + * @return return true to halt, false to continue parsing + */ + boolean isFinished(int lastOptionCode); +} diff --git a/ApacheJmeter/src/org/apache/commons/cli/avalon/Token.java b/ApacheJmeter/src/org/apache/commons/cli/avalon/Token.java new file mode 100644 index 0000000..95dd7fa --- /dev/null +++ b/ApacheJmeter/src/org/apache/commons/cli/avalon/Token.java @@ -0,0 +1,71 @@ +/* + * 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.commons.cli.avalon; + +// Renamed from org.apache.avalon.excalibur.cli + +/** + * Token handles tokenizing the CLI arguments + * + */ +class Token { + /** Type for a separator token */ + public static final int TOKEN_SEPARATOR = 0; + + /** Type for a text token */ + public static final int TOKEN_STRING = 1; + + private final int m_type; + + private final String m_value; + + /** + * New Token object with a type and value + */ + Token(final int type, final String value) { + m_type = type; + m_value = value; + } + + /** + * Get the value of the token + */ + final String getValue() { + return m_value; + } + + /** + * Get the type of the token + */ + final int getType() { + return m_type; + } + + /** + * Convert to a string + */ + @Override + public final String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append(m_type); + sb.append(":"); + sb.append(m_value); + return sb.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/commons/jexl/bsf/JexlEngine.java b/ApacheJmeter/src/org/apache/commons/jexl/bsf/JexlEngine.java new file mode 100644 index 0000000..0ecf534 --- /dev/null +++ b/ApacheJmeter/src/org/apache/commons/jexl/bsf/JexlEngine.java @@ -0,0 +1,149 @@ +/* + * 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.commons.jexl.bsf; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.Vector; + +import org.apache.bsf.BSFDeclaredBean; +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.bsf.util.BSFEngineImpl; + +import org.apache.commons.jexl.JexlContext; +import org.apache.commons.jexl.JexlHelper; +import org.apache.commons.jexl.Script; +import org.apache.commons.jexl.ScriptFactory; +//import org.apache.jorphan.logging.LoggingManager; +//import org.apache.log.Logger; + +// See JIRA: JEXL-39 + +/** + * BSFEngine for Commons JEXL. + */ +public class JexlEngine extends BSFEngineImpl { + +// private static final Logger log = LoggingManager.getLoggerForClass(); + + private JexlContext jc; + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") // super-class does not use generics + @Override + public void initialize(BSFManager mgr, String lang, + @SuppressWarnings("rawtypes") Vector declaredBeans) // super-class does not use generics + throws BSFException { + super.initialize(mgr, lang, declaredBeans); + jc = JexlHelper.createContext(); + for (int i = 0; i < declaredBeans.size(); i++) { + BSFDeclaredBean bean = (BSFDeclaredBean) declaredBeans.elementAt(i); + jc.getVars().put(bean.name, bean.bean); + } + } + + /** {@inheritDoc} */ + @Override + public void terminate() { + if (jc != null) { + jc.getVars().clear(); + jc = null; + } + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + public void declareBean(BSFDeclaredBean bean) throws BSFException { + jc.getVars().put(bean.name, bean.bean); + } + + /** {@inheritDoc} */ + @Override + public void undeclareBean(BSFDeclaredBean bean) throws BSFException { + jc.getVars().remove(bean.name); + } + + /** {@inheritDoc} */ + public Object eval(String fileName, int lineNo, int colNo, Object expr) + throws BSFException { + if (expr == null) { + return null; + } + try { + Script jExpr = null; + if (expr instanceof File) { + jExpr = ScriptFactory.createScript((File) expr); + } else if (expr instanceof URL) { + jExpr = ScriptFactory.createScript((URL) expr); + } else { + jExpr = ScriptFactory.createScript((String) expr); + } + return jExpr.execute(jc); + } catch (Exception e) { + // TODO Better messages + throw new BSFException(e.getMessage()); + } + } + + /** {@inheritDoc} */ + @Override + public void exec(String fileName, int lineNo, int colNo, Object script) + throws BSFException { + if (script == null) { + return; + } + try { + Script jExpr = null; + if (script instanceof File) { + jExpr = ScriptFactory.createScript((File) script); + } else if (script instanceof URL) { + jExpr = ScriptFactory.createScript((URL) script); + } else { + jExpr = ScriptFactory.createScript((String) script); + } + jExpr.execute(jc); + } catch (Exception e) { + throw new BSFException(e.getMessage()); + } + } + + /** {@inheritDoc} */ + @Override + public void iexec(String fileName, int lineNo, int colNo, Object script) + throws BSFException { + exec(fileName, lineNo, colNo, script); + } + + /** {@inheritDoc} */ + public Object call(Object object, String name, Object[] args) + throws BSFException { + try { + Class[] types = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + types[i] = args[i].getClass(); + } + Method m = object.getClass().getMethod(name, types); + return m.invoke(object, args); + } catch (Exception e) { + throw new BSFException(e.getMessage()); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/DynamicClassLoader.java b/ApacheJmeter/src/org/apache/jmeter/DynamicClassLoader.java new file mode 100644 index 0000000..76d7412 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/DynamicClassLoader.java @@ -0,0 +1,65 @@ +/* + * 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; + +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLStreamHandlerFactory; + +/** + * This is a basic URL classloader for loading new resources + * dynamically. + * + * It allows public access to the addURL() method. + * + * It also adds a convenience method to update the current thread classloader + * + */ +public class DynamicClassLoader extends URLClassLoader { + + public DynamicClassLoader(URL[] urls) { + super(urls); + } + + public DynamicClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + public DynamicClassLoader(URL[] urls, ClassLoader parent, + URLStreamHandlerFactory factory) { + super(urls, parent, factory); + } + + // Make the addURL method visible + @Override + public void addURL(URL url) { + super.addURL(url); + } + + /** + * + * @param urls - list of URLs to add to the thread's classloader + */ + public static void updateLoader(URL [] urls) { + DynamicClassLoader loader + = (DynamicClassLoader) Thread.currentThread().getContextClassLoader(); + for(URL url : urls) { + loader.addURL(url); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/JMeter.java b/ApacheJmeter/src/org/apache/jmeter/JMeter.java new file mode 100644 index 0000000..1d49c4d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/JMeter.java @@ -0,0 +1,1158 @@ +/* + * 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; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.Thread.UncaughtExceptionHandler; +import java.net.Authenticator; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.SocketException; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.swing.JTree; +import javax.swing.tree.TreePath; + +import org.apache.commons.cli.avalon.CLArgsParser; +import org.apache.commons.cli.avalon.CLOption; +import org.apache.commons.cli.avalon.CLOptionDescriptor; +import org.apache.commons.cli.avalon.CLUtil; +import org.apache.jmeter.control.ReplaceableController; +import org.apache.jmeter.engine.ClientJMeterEngine; +import org.apache.jmeter.engine.JMeterEngine; +import org.apache.jmeter.engine.RemoteJMeterEngineImpl; +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.MainFrame; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.Load; +import org.apache.jmeter.gui.action.LoadRecentProject; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.FocusRequester; +import org.apache.jmeter.plugin.JMeterPlugin; +import org.apache.jmeter.plugin.PluginManager; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.reporters.Summariser; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellServer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.SearchByClass; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassTools; +import org.apache.jorphan.util.JMeterException; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.converters.ConversionException; + +/** + * Main JMeter class; processes options and starts the GUI, non-GUI or server as appropriate. + */ +public class JMeter implements JMeterPlugin { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final int UDP_PORT_DEFAULT = 4445; // needed for ShutdownClient + + public static final String HTTP_PROXY_PASS = "http.proxyPass"; // $NON-NLS-1$ + + public static final String HTTP_PROXY_USER = "http.proxyUser"; // $NON-NLS-1$ + + public static final String JMETER_NON_GUI = "JMeter.NonGui"; // $NON-NLS-1$ + + // If the -t flag is to "LAST", then the last loaded file (if any) is used + private static final String USE_LAST_JMX = "LAST"; + // If the -j or -l flag is set to LAST or LAST.log|LAST.jtl, then the last loaded file name is used to + // generate the log file name by removing .JMX and replacing it with .log|.jtl + + private static final int PROXY_PASSWORD = 'a';// $NON-NLS-1$ + private static final int JMETER_HOME_OPT = 'd';// $NON-NLS-1$ + private static final int HELP_OPT = 'h';// $NON-NLS-1$ + // jmeter.log + private static final int JMLOGFILE_OPT = 'j';// $NON-NLS-1$ + // sample result log file + private static final int LOGFILE_OPT = 'l';// $NON-NLS-1$ + private static final int NONGUI_OPT = 'n';// $NON-NLS-1$ + private static final int PROPFILE_OPT = 'p';// $NON-NLS-1$ + private static final int PROPFILE2_OPT = 'q';// $NON-NLS-1$ + private static final int REMOTE_OPT = 'r';// $NON-NLS-1$ + private static final int SERVER_OPT = 's';// $NON-NLS-1$ + private static final int TESTFILE_OPT = 't';// $NON-NLS-1$ + private static final int PROXY_USERNAME = 'u';// $NON-NLS-1$ + private static final int VERSION_OPT = 'v';// $NON-NLS-1$ + + private static final int SYSTEM_PROPERTY = 'D';// $NON-NLS-1$ + private static final int JMETER_GLOBAL_PROP = 'G';// $NON-NLS-1$ + private static final int PROXY_HOST = 'H';// $NON-NLS-1$ + private static final int JMETER_PROPERTY = 'J';// $NON-NLS-1$ + private static final int LOGLEVEL = 'L';// $NON-NLS-1$ + private static final int NONPROXY_HOSTS = 'N';// $NON-NLS-1$ + private static final int PROXY_PORT = 'P';// $NON-NLS-1$ + private static final int REMOTE_OPT_PARAM = 'R';// $NON-NLS-1$ + private static final int SYSTEM_PROPFILE = 'S';// $NON-NLS-1$ + private static final int REMOTE_STOP = 'X';// $NON-NLS-1$ + + + + /** + * Define the understood options. Each CLOptionDescriptor contains: + *
    + *
  • The "long" version of the option. Eg, "help" means that "--help" + * will be recognised.
  • + *
  • The option flags, governing the option's argument(s).
  • + *
  • The "short" version of the option. Eg, 'h' means that "-h" will be + * recognised.
  • + *
  • A description of the option.
  • + *
+ */ + private static final CLOptionDescriptor[] options = new CLOptionDescriptor[] { + new CLOptionDescriptor("help", CLOptionDescriptor.ARGUMENT_DISALLOWED, HELP_OPT, + "print usage information and exit"), + new CLOptionDescriptor("version", CLOptionDescriptor.ARGUMENT_DISALLOWED, VERSION_OPT, + "print the version information and exit"), + new CLOptionDescriptor("propfile", CLOptionDescriptor.ARGUMENT_REQUIRED, PROPFILE_OPT, + "the jmeter property file to use"), + new CLOptionDescriptor("addprop", CLOptionDescriptor.ARGUMENT_REQUIRED + | CLOptionDescriptor.DUPLICATES_ALLOWED, PROPFILE2_OPT, + "additional JMeter property file(s)"), + new CLOptionDescriptor("testfile", CLOptionDescriptor.ARGUMENT_REQUIRED, TESTFILE_OPT, + "the jmeter test(.jmx) file to run"), + new CLOptionDescriptor("logfile", CLOptionDescriptor.ARGUMENT_REQUIRED, LOGFILE_OPT, + "the file to log samples to"), + new CLOptionDescriptor("jmeterlogfile", CLOptionDescriptor.ARGUMENT_REQUIRED, JMLOGFILE_OPT, + "jmeter run log file (jmeter.log)"), + new CLOptionDescriptor("nongui", CLOptionDescriptor.ARGUMENT_DISALLOWED, NONGUI_OPT, + "run JMeter in nongui mode"), + new CLOptionDescriptor("server", CLOptionDescriptor.ARGUMENT_DISALLOWED, SERVER_OPT, + "run the JMeter server"), + new CLOptionDescriptor("proxyHost", CLOptionDescriptor.ARGUMENT_REQUIRED, PROXY_HOST, + "Set a proxy server for JMeter to use"), + new CLOptionDescriptor("proxyPort", CLOptionDescriptor.ARGUMENT_REQUIRED, PROXY_PORT, + "Set proxy server port for JMeter to use"), + new CLOptionDescriptor("nonProxyHosts", CLOptionDescriptor.ARGUMENT_REQUIRED, NONPROXY_HOSTS, + "Set nonproxy host list (e.g. *.apache.org|localhost)"), + new CLOptionDescriptor("username", CLOptionDescriptor.ARGUMENT_REQUIRED, PROXY_USERNAME, + "Set username for proxy server that JMeter is to use"), + new CLOptionDescriptor("password", CLOptionDescriptor.ARGUMENT_REQUIRED, PROXY_PASSWORD, + "Set password for proxy server that JMeter is to use"), + new CLOptionDescriptor("jmeterproperty", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENTS_REQUIRED_2, JMETER_PROPERTY, + "Define additional JMeter properties"), + new CLOptionDescriptor("globalproperty", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENTS_REQUIRED_2, JMETER_GLOBAL_PROP, + "Define Global properties (sent to servers)\n\t\te.g. -Gport=123 or -Gglobal.properties"), + new CLOptionDescriptor("systemproperty", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENTS_REQUIRED_2, SYSTEM_PROPERTY, + "Define additional system properties"), + new CLOptionDescriptor("systemPropertyFile", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENT_REQUIRED, SYSTEM_PROPFILE, + "additional system property file(s)"), + new CLOptionDescriptor("loglevel", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENTS_REQUIRED_2, LOGLEVEL, + "[category=]level e.g. jorphan=INFO or jmeter.util=DEBUG"), + new CLOptionDescriptor("runremote", CLOptionDescriptor.ARGUMENT_DISALLOWED, REMOTE_OPT, + "Start remote servers (as defined in remote_hosts)"), + new CLOptionDescriptor("remotestart", CLOptionDescriptor.ARGUMENT_REQUIRED, REMOTE_OPT_PARAM, + "Start these remote servers (overrides remote_hosts)"), + new CLOptionDescriptor("homedir", CLOptionDescriptor.ARGUMENT_REQUIRED, JMETER_HOME_OPT, + "the jmeter home directory to use"), + new CLOptionDescriptor("remoteexit", CLOptionDescriptor.ARGUMENT_DISALLOWED, REMOTE_STOP, + "Exit the remote servers at end of test (non-GUI)"), + }; + + public JMeter() { + } + + // Hack to allow automated tests to find when test has ended + //transient boolean testEnded = false; + + private JMeter parent; + + private Properties remoteProps; // Properties to be sent to remote servers + + private boolean remoteStop; // should remote engines be stopped at end of non-GUI test? + + /** + * Starts up JMeter in GUI mode + */ + private void startGui(String testFile) { + + PluginManager.install(this, true); + JMeterTreeModel treeModel = new JMeterTreeModel(); + JMeterTreeListener treeLis = new JMeterTreeListener(treeModel); + treeLis.setActionHandler(ActionRouter.getInstance()); + // NOTUSED: GuiPackage guiPack = + GuiPackage.getInstance(treeLis, treeModel); + MainFrame main = new MainFrame(ActionRouter.getInstance(), treeModel, treeLis); + ComponentUtil.centerComponentInWindow(main, 80); + main.setVisible(true); + ActionRouter.getInstance().actionPerformed(new ActionEvent(main, 1, ActionNames.ADD_ALL)); + if (testFile != null) { + FileInputStream reader = null; + try { + File f = new File(testFile); + log.info("Loading file: " + f); + FileServer.getFileServer().setBaseForScript(f); + + reader = new FileInputStream(f); + HashTree tree = SaveService.loadTree(reader); + + GuiPackage.getInstance().setTestPlanFile(f.getAbsolutePath()); + + Load.insertLoadedTree(1, tree); + } catch (ConversionException e) { + log.error("Failure loading test file", e); + JMeterUtils.reportErrorToUser(SaveService.CEtoString(e)); + } catch (Exception e) { + log.error("Failure loading test file", e); + JMeterUtils.reportErrorToUser(e.toString()); + } finally { + JOrphanUtils.closeQuietly(reader); + } + } else { + JTree jTree = GuiPackage.getInstance().getMainFrame().getTree(); + TreePath path = jTree.getPathForRow(0); + jTree.setSelectionPath(path); + new FocusRequester(jTree); + } + } + + /** + * Takes the command line arguments and uses them to determine how to + * startup JMeter. + * + * Called reflectively by {@link NewDriver#main(String[])} + */ + public void start(String[] args) { + + CLArgsParser parser = new CLArgsParser(args, options); + String error = parser.getErrorString(); + if (error == null){// Check option combinations + boolean gui = parser.getArgumentById(NONGUI_OPT)==null; + boolean nonGuiOnly = parser.getArgumentById(REMOTE_OPT)!=null + || parser.getArgumentById(REMOTE_OPT_PARAM)!=null + || parser.getArgumentById(REMOTE_STOP)!=null; + if (gui && nonGuiOnly) { + error = "-r and -R and -X are only valid in non-GUI mode"; + } + } + if (null != error) { + System.err.println("Error: " + error); + System.out.println("Usage"); + System.out.println(CLUtil.describeOptions(options).toString()); + return; + } + try { + initializeProperties(parser); // Also initialises JMeter logging + + /* + * The following is needed for HTTPClient. + * (originally tried doing this in HTTPSampler2, + * but it appears that it was done too late when running in GUI mode) + * Set the commons logging default to Avalon Logkit, if not already defined + */ + if (System.getProperty("org.apache.commons.logging.Log") == null) { // $NON-NLS-1$ + System.setProperty("org.apache.commons.logging.Log" // $NON-NLS-1$ + , "org.apache.commons.logging.impl.LogKitLogger"); // $NON-NLS-1$ + } + + Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { + public void uncaughtException(Thread t, Throwable e) { + if (!(e instanceof ThreadDeath)) { + log.error("Uncaught exception: ", e); + System.err.println("Uncaught Exception " + e + ". See log file for details."); + } + } + }); + + log.info(JMeterUtils.getJMeterCopyright()); + log.info("Version " + JMeterUtils.getJMeterVersion()); + logProperty("java.version"); //$NON-NLS-1$ + logProperty("java.vm.name"); //$NON-NLS-1$ + logProperty("os.name"); //$NON-NLS-1$ + logProperty("os.arch"); //$NON-NLS-1$ + logProperty("os.version"); //$NON-NLS-1$ + logProperty("file.encoding"); // $NON-NLS-1$ + log.info("Default Locale=" + Locale.getDefault().getDisplayName()); + log.info("JMeter Locale=" + JMeterUtils.getLocale().getDisplayName()); + log.info("JMeterHome=" + JMeterUtils.getJMeterHome()); + logProperty("user.dir"," ="); //$NON-NLS-1$ + log.info("PWD ="+new File(".").getCanonicalPath());//$NON-NLS-1$ + log.info("IP: "+JMeterUtils.getLocalHostIP() + +" Name: "+JMeterUtils.getLocalHostName() + +" FullName: "+JMeterUtils.getLocalHostFullName()); + setProxy(parser); + + updateClassLoader(); + if (log.isDebugEnabled()) + { + String jcp=System.getProperty("java.class.path");// $NON-NLS-1$ + String bits[] =jcp.split(File.pathSeparator); + log.debug("ClassPath"); + for(String bit : bits){ + log.debug(bit); + } + log.debug(jcp); + } + + // Set some (hopefully!) useful properties + long now=System.currentTimeMillis(); + JMeterUtils.setProperty("START.MS",Long.toString(now));// $NON-NLS-1$ + Date today=new Date(now); // so it agrees with above + // TODO perhaps should share code with __time() function for this... + JMeterUtils.setProperty("START.YMD",new SimpleDateFormat("yyyyMMdd").format(today));// $NON-NLS-1$ $NON-NLS-2$ + JMeterUtils.setProperty("START.HMS",new SimpleDateFormat("HHmmss").format(today));// $NON-NLS-1$ $NON-NLS-2$ + + if (parser.getArgumentById(VERSION_OPT) != null) { + System.out.println(JMeterUtils.getJMeterCopyright()); + System.out.println("Version " + JMeterUtils.getJMeterVersion()); + } else if (parser.getArgumentById(HELP_OPT) != null) { + System.out.println(JMeterUtils.getResourceFileAsText("org/apache/jmeter/help.txt"));// $NON-NLS-1$ + } else if (parser.getArgumentById(SERVER_OPT) != null) { + // Start the server + try { + RemoteJMeterEngineImpl.startServer(JMeterUtils.getPropDefault("server_port", 0)); // $NON-NLS-1$ + } catch (Exception ex) { + System.err.println("Server failed to start: "+ex); + log.error("Giving up, as server failed with:", ex); + throw ex; + } + startOptionalServers(); + } else { + String testFile=null; + CLOption testFileOpt = parser.getArgumentById(TESTFILE_OPT); + if (testFileOpt != null){ + testFile = testFileOpt.getArgument(); + if (USE_LAST_JMX.equals(testFile)) { + testFile = LoadRecentProject.getRecentFile(0);// most recent + } + } + if (parser.getArgumentById(NONGUI_OPT) == null) { + startGui(testFile); + startOptionalServers(); + } else { + CLOption rem=parser.getArgumentById(REMOTE_OPT_PARAM); + if (rem==null) { rem=parser.getArgumentById(REMOTE_OPT); } + CLOption jtl = parser.getArgumentById(LOGFILE_OPT); + String jtlFile = null; + if (jtl != null){ + jtlFile=processLAST(jtl.getArgument(), ".jtl"); // $NON-NLS-1$ + } + startNonGui(testFile, jtlFile, rem); + startOptionalServers(); + } + } + } catch (IllegalUserActionException e) { + System.out.println(e.getMessage()); + System.out.println("Incorrect Usage"); + System.out.println(CLUtil.describeOptions(options).toString()); + } catch (Throwable e) { + if (log != null){ + log.fatalError("An error occurred: ",e); + } else { + e.printStackTrace(); + } + System.out.println("An error occurred: " + e.getMessage()); + System.exit(1); // TODO - could this be return? + } + } + + // Update classloader if necessary + private void updateClassLoader() { + updatePath("search_paths",";"); //$NON-NLS-1$//$NON-NLS-2$ + updatePath("user.classpath",File.pathSeparator);//$NON-NLS-1$ + } + + private void updatePath(String property, String sep) { + String userpath= JMeterUtils.getPropDefault(property,"");// $NON-NLS-1$ + if (userpath.length() <= 0) { return; } + log.info(property+"="+userpath); //$NON-NLS-1$ + StringTokenizer tok = new StringTokenizer(userpath, sep); + while(tok.hasMoreTokens()) { + String path=tok.nextToken(); + File f=new File(path); + if (!f.canRead() && !f.isDirectory()) { + log.warn("Can't read "+path); + } else { + log.info("Adding to classpath: "+path); + try { + NewDriver.addPath(path); + } catch (MalformedURLException e) { + log.warn("Error adding: "+path+" "+e.getLocalizedMessage()); + } + } + } + } + + /** + * + */ + private void startOptionalServers() { + int bshport = JMeterUtils.getPropDefault("beanshell.server.port", 0);// $NON-NLS-1$ + String bshfile = JMeterUtils.getPropDefault("beanshell.server.file", "");// $NON-NLS-1$ $NON-NLS-2$ + if (bshport > 0) { + log.info("Starting Beanshell server (" + bshport + "," + bshfile + ")"); + Runnable t = new BeanShellServer(bshport, bshfile); + t.run(); + } + + // Should we run a beanshell script on startup? + String bshinit = JMeterUtils.getProperty("beanshell.init.file");// $NON-NLS-1$ + if (bshinit != null){ + log.info("Run Beanshell on file: "+bshinit); + try { + BeanShellInterpreter bsi = new BeanShellInterpreter();//bshinit,log); + bsi.source(bshinit); + } catch (ClassNotFoundException e) { + log.warn("Could not start Beanshell: "+e.getLocalizedMessage()); + } catch (JMeterException e) { + log.warn("Could not process Beanshell file: "+e.getLocalizedMessage()); + } + } + + int mirrorPort=JMeterUtils.getPropDefault("mirror.server.port", 0);// $NON-NLS-1$ + if (mirrorPort > 0){ + log.info("Starting Mirror server (" + mirrorPort + ")"); + try { + Object instance = ClassTools.construct( + "org.apache.jmeter.protocol.http.control.HttpMirrorControl",// $NON-NLS-1$ + mirrorPort); + ClassTools.invoke(instance,"startHttpMirror"); + } catch (JMeterException e) { + log.warn("Could not start Mirror server",e); + } + } + } + + /** + * Sets a proxy server for the JVM if the command line arguments are + * specified. + */ + private void setProxy(CLArgsParser parser) throws IllegalUserActionException { + if (parser.getArgumentById(PROXY_USERNAME) != null) { + Properties jmeterProps = JMeterUtils.getJMeterProperties(); + if (parser.getArgumentById(PROXY_PASSWORD) != null) { + String u, p; + Authenticator.setDefault(new ProxyAuthenticator(u = parser.getArgumentById(PROXY_USERNAME) + .getArgument(), p = parser.getArgumentById(PROXY_PASSWORD).getArgument())); + log.info("Set Proxy login: " + u + "/" + p); + jmeterProps.setProperty(HTTP_PROXY_USER, u);//for Httpclient + jmeterProps.setProperty(HTTP_PROXY_PASS, p);//for Httpclient + } else { + String u; + Authenticator.setDefault(new ProxyAuthenticator(u = parser.getArgumentById(PROXY_USERNAME) + .getArgument(), "")); + log.info("Set Proxy login: " + u); + jmeterProps.setProperty(HTTP_PROXY_USER, u); + } + } + if (parser.getArgumentById(PROXY_HOST) != null && parser.getArgumentById(PROXY_PORT) != null) { + String h = parser.getArgumentById(PROXY_HOST).getArgument(); + String p = parser.getArgumentById(PROXY_PORT).getArgument(); + System.setProperty("http.proxyHost", h );// $NON-NLS-1$ + System.setProperty("https.proxyHost", h);// $NON-NLS-1$ + System.setProperty("http.proxyPort", p);// $NON-NLS-1$ + System.setProperty("https.proxyPort", p);// $NON-NLS-1$ + log.info("Set http[s].proxyHost: " + h + " Port: " + p); + } else if (parser.getArgumentById(PROXY_HOST) != null || parser.getArgumentById(PROXY_PORT) != null) { + throw new IllegalUserActionException(JMeterUtils.getResString("proxy_cl_error"));// $NON-NLS-1$ + } + + if (parser.getArgumentById(NONPROXY_HOSTS) != null) { + String n = parser.getArgumentById(NONPROXY_HOSTS).getArgument(); + System.setProperty("http.nonProxyHosts", n );// $NON-NLS-1$ + System.setProperty("https.nonProxyHosts", n );// $NON-NLS-1$ + log.info("Set http[s].nonProxyHosts: "+n); + } + } + + private void initializeProperties(CLArgsParser parser) { + if (parser.getArgumentById(PROPFILE_OPT) != null) { + JMeterUtils.loadJMeterProperties(parser.getArgumentById(PROPFILE_OPT).getArgument()); + } else { + JMeterUtils.loadJMeterProperties(NewDriver.getJMeterDir() + File.separator + + "bin" + File.separator // $NON-NLS-1$ + + "jmeter.properties");// $NON-NLS-1$ + } + + if (parser.getArgumentById(JMLOGFILE_OPT) != null){ + String jmlogfile=parser.getArgumentById(JMLOGFILE_OPT).getArgument(); + jmlogfile = processLAST(jmlogfile, ".log");// $NON-NLS-1$ + JMeterUtils.setProperty(LoggingManager.LOG_FILE,jmlogfile); + } + + JMeterUtils.initLogging(); + JMeterUtils.initLocale(); + // Bug 33845 - allow direct override of Home dir + if (parser.getArgumentById(JMETER_HOME_OPT) == null) { + JMeterUtils.setJMeterHome(NewDriver.getJMeterDir()); + } else { + JMeterUtils.setJMeterHome(parser.getArgumentById(JMETER_HOME_OPT).getArgument()); + } + + Properties jmeterProps = JMeterUtils.getJMeterProperties(); + remoteProps = new Properties(); + + // Add local JMeter properties, if the file is found + String userProp = JMeterUtils.getPropDefault("user.properties",""); //$NON-NLS-1$ + if (userProp.length() > 0){ //$NON-NLS-1$ + FileInputStream fis=null; + try { + File file = JMeterUtils.findFile(userProp); + if (file.canRead()){ + log.info("Loading user properties from: "+file.getCanonicalPath()); + fis = new FileInputStream(file); + Properties tmp = new Properties(); + tmp.load(fis); + jmeterProps.putAll(tmp); + LoggingManager.setLoggingLevels(tmp);//Do what would be done earlier + } + } catch (IOException e) { + log.warn("Error loading user property file: " + userProp, e); + } finally { + JOrphanUtils.closeQuietly(fis); + } + } + + // Add local system properties, if the file is found + String sysProp = JMeterUtils.getPropDefault("system.properties",""); //$NON-NLS-1$ + if (sysProp.length() > 0){ + FileInputStream fis=null; + try { + File file = JMeterUtils.findFile(sysProp); + if (file.canRead()){ + log.info("Loading system properties from: "+file.getCanonicalPath()); + fis = new FileInputStream(file); + System.getProperties().load(fis); + } + } catch (IOException e) { + log.warn("Error loading system property file: " + sysProp, e); + } finally { + JOrphanUtils.closeQuietly(fis); + } + } + + // Process command line property definitions + // These can potentially occur multiple times + + List clOptions = parser.getArguments(); + int size = clOptions.size(); + + for (int i = 0; i < size; i++) { + CLOption option = clOptions.get(i); + String name = option.getArgument(0); + String value = option.getArgument(1); + FileInputStream fis = null; + + switch (option.getDescriptor().getId()) { + + // Should not have any text arguments + case CLOption.TEXT_ARGUMENT: + throw new IllegalArgumentException("Unknown arg: "+option.getArgument()); + + case PROPFILE2_OPT: // Bug 33920 - allow multiple props + try { + fis = new FileInputStream(new File(name)); + Properties tmp = new Properties(); + tmp.load(fis); + jmeterProps.putAll(tmp); + LoggingManager.setLoggingLevels(tmp);//Do what would be done earlier + } catch (FileNotFoundException e) { + log.warn("Can't find additional property file: " + name, e); + } catch (IOException e) { + log.warn("Error loading additional property file: " + name, e); + } finally { + JOrphanUtils.closeQuietly(fis); + } + break; + case SYSTEM_PROPFILE: + log.info("Setting System properties from file: " + name); + try { + fis = new FileInputStream(new File(name)); + System.getProperties().load(fis); + } catch (IOException e) { + log.warn("Cannot find system property file "+e.getLocalizedMessage()); + } finally { + JOrphanUtils.closeQuietly(fis); + } + break; + case SYSTEM_PROPERTY: + if (value.length() > 0) { // Set it + log.info("Setting System property: " + name + "=" + value); + System.getProperties().setProperty(name, value); + } else { // Reset it + log.warn("Removing System property: " + name); + System.getProperties().remove(name); + } + break; + case JMETER_PROPERTY: + if (value.length() > 0) { // Set it + log.info("Setting JMeter property: " + name + "=" + value); + jmeterProps.setProperty(name, value); + } else { // Reset it + log.warn("Removing JMeter property: " + name); + jmeterProps.remove(name); + } + break; + case JMETER_GLOBAL_PROP: + if (value.length() > 0) { // Set it + log.info("Setting Global property: " + name + "=" + value); + remoteProps.setProperty(name, value); + } else { + File propFile = new File(name); + if (propFile.canRead()) { + log.info("Setting Global properties from the file "+name); + try { + fis = new FileInputStream(propFile); + remoteProps.load(fis); + } catch (FileNotFoundException e) { + log.warn("Could not find properties file: "+e.getLocalizedMessage()); + } catch (IOException e) { + log.warn("Could not load properties file: "+e.getLocalizedMessage()); + } finally { + JOrphanUtils.closeQuietly(fis); + } + } + } + break; + case LOGLEVEL: + if (value.length() > 0) { // Set category + log.info("LogLevel: " + name + "=" + value); + LoggingManager.setPriority(value, name); + } else { // Set root level + log.warn("LogLevel: " + name); + LoggingManager.setPriority(name); + } + break; + case REMOTE_STOP: + remoteStop = true; + break; + default: + // ignored + break; + } + } + + String sample_variables = (String) jmeterProps.get(SampleEvent.SAMPLE_VARIABLES); + if (sample_variables != null){ + remoteProps.put(SampleEvent.SAMPLE_VARIABLES, sample_variables); + } + } + + /* + * Checks for LAST or LASTsuffix. + * Returns the LAST name with .JMX replaced by suffix. + */ + private String processLAST(String jmlogfile, String suffix) { + if (USE_LAST_JMX.equals(jmlogfile) || USE_LAST_JMX.concat(suffix).equals(jmlogfile)){ + String last = LoadRecentProject.getRecentFile(0);// most recent + final String JMX_SUFFIX = ".JMX"; // $NON-NLS-1$ + if (last.toUpperCase(Locale.ENGLISH).endsWith(JMX_SUFFIX)){ + jmlogfile=last.substring(0, last.length() - JMX_SUFFIX.length()).concat(suffix); + } + } + return jmlogfile; + } + + private void startNonGui(String testFile, String logFile, CLOption remoteStart) + throws IllegalUserActionException { + // add a system property so samplers can check to see if JMeter + // is running in NonGui mode + System.setProperty(JMETER_NON_GUI, "true");// $NON-NLS-1$ + JMeter driver = new JMeter();// TODO - why does it create a new instance? + driver.remoteProps = this.remoteProps; + driver.remoteStop = this.remoteStop; + driver.parent = this; + PluginManager.install(this, false); + + String remote_hosts_string = null; + if (remoteStart != null) { + remote_hosts_string = remoteStart.getArgument(); + if (remote_hosts_string == null) { + remote_hosts_string = JMeterUtils.getPropDefault( + "remote_hosts", //$NON-NLS-1$ + "127.0.0.1");//$NON-NLS-1$ + } + } + if (testFile == null) { + throw new IllegalUserActionException("Non-GUI runs require a test plan"); + } + driver.runNonGui(testFile, logFile, remoteStart != null, remote_hosts_string); + } + + // run test in batch mode + private void runNonGui(String testFile, String logFile, boolean remoteStart, String remote_hosts_string) { + FileInputStream reader = null; + try { + File f = new File(testFile); + if (!f.exists() || !f.isFile()) { + println("Could not open " + testFile); + return; + } + FileServer.getFileServer().setBaseForScript(f); + + reader = new FileInputStream(f); + log.info("Loading file: " + f); + + HashTree tree = SaveService.loadTree(reader); + + @SuppressWarnings("deprecation") // Deliberate use of deprecated ctor + JMeterTreeModel treeModel = new JMeterTreeModel(new Object());// Create non-GUI version to avoid headless problems + JMeterTreeNode root = (JMeterTreeNode) treeModel.getRoot(); + treeModel.addSubTree(tree, root); + + // Hack to resolve ModuleControllers in non GUI mode + SearchByClass replaceableControllers = new SearchByClass(ReplaceableController.class); + tree.traverse(replaceableControllers); + Collection replaceableControllersRes = replaceableControllers.getSearchResults(); + for (Iterator iter = replaceableControllersRes.iterator(); iter.hasNext();) { + ReplaceableController replaceableController = iter.next(); + replaceableController.resolveReplacementSubTree(root); + } + + // Remove the disabled items + // For GUI runs this is done in Start.java + convertSubTree(tree); + + Summariser summer = null; + String summariserName = JMeterUtils.getPropDefault("summariser.name", "");//$NON-NLS-1$ + if (summariserName.length() > 0) { + log.info("Creating summariser <" + summariserName + ">"); + println("Creating summariser <" + summariserName + ">"); + summer = new Summariser(summariserName); + } + + if (logFile != null) { + ResultCollector logger = new ResultCollector(summer); + logger.setFilename(logFile); + tree.add(tree.getArray()[0], logger); + } + else { + // only add Summariser if it can not be shared with the ResultCollector + if (summer != null) { + tree.add(tree.getArray()[0], summer); + } + } + + List engines = new LinkedList(); + tree.add(tree.getArray()[0], new ListenToTest(parent, (remoteStart && remoteStop) ? engines : null)); + println("Created the tree successfully using "+testFile); + if (!remoteStart) { + JMeterEngine engine = new StandardJMeterEngine(); + engine.configure(tree); + long now=System.currentTimeMillis(); + println("Starting the test @ "+new Date(now)+" ("+now+")"); + engine.runTest(); + engines.add(engine); + } else { + java.util.StringTokenizer st = new java.util.StringTokenizer(remote_hosts_string, ",");//$NON-NLS-1$ + while (st.hasMoreElements()) { + String el = (String) st.nextElement(); + println("Configuring remote engine for " + el); + log.info("Configuring remote engine for " + el); + JMeterEngine eng = doRemoteInit(el.trim(), tree); + if (null != eng) { + engines.add(eng); + } else { + println("Failed to configure "+el); + } + } + if (engines.isEmpty()) { + println("No remote engines were started."); + return; + } + println("Starting remote engines"); + log.info("Starting remote engines"); + long now=System.currentTimeMillis(); + println("Starting the test @ "+new Date(now)+" ("+now+")"); + for (JMeterEngine engine : engines) { + engine.runTest(); + } + println("Remote engines have been started"); + log.info("Remote engines have been started"); + } + startUdpDdaemon(engines); + } catch (Exception e) { + System.out.println("Error in NonGUIDriver " + e.toString()); + log.error("Error in NonGUIDriver", e); + } finally { + JOrphanUtils.closeQuietly(reader); + } + } + + /** + * Refactored from AbstractAction.java + * + * @param tree + */ + public static void convertSubTree(HashTree tree) { + LinkedList copyList = new LinkedList(tree.list()); + for (Object o : copyList) { + if (o instanceof TestElement) { + TestElement item = (TestElement) o; + if (item.isEnabled()) { + if (item instanceof ReplaceableController) { + ReplaceableController rc; + + // TODO this bit of code needs to be tidied up + // Unfortunately ModuleController is in components, not core + if (item.getClass().getName().equals("org.apache.jmeter.control.ModuleController")){ // Bug 47165 + rc = (ReplaceableController) item; + } else { + // HACK: force the controller to load its tree + rc = (ReplaceableController) item.clone(); + } + + HashTree subTree = tree.getTree(item); + if (subTree != null) { + HashTree replacementTree = rc.getReplacementSubTree(); + if (replacementTree != null) { + convertSubTree(replacementTree); + tree.replace(item, rc); + tree.set(rc, replacementTree); + } + } else { // null subTree + convertSubTree(tree.getTree(item)); + } + } else { // not Replaceable Controller + convertSubTree(tree.getTree(item)); + } + } else { // Not enabled + tree.remove(item); + } + } else { // Not a TestElement + JMeterTreeNode item = (JMeterTreeNode) o; + if (item.isEnabled()) { + // Replacement only needs to occur when starting the engine + // @see StandardJMeterEngine.run() + if (item.getUserObject() instanceof ReplaceableController) { + ReplaceableController rc = + (ReplaceableController) item.getTestElement(); + HashTree subTree = tree.getTree(item); + + if (subTree != null) { + HashTree replacementTree = rc.getReplacementSubTree(); + if (replacementTree != null) { + convertSubTree(replacementTree); + tree.replace(item, rc); + tree.set(rc, replacementTree); + } + } + } else { // Not a ReplaceableController + convertSubTree(tree.getTree(item)); + TestElement testElement = item.getTestElement(); + tree.replace(item, testElement); + } + } else { // Not enabled + tree.remove(item); + } + } + } + } + + private JMeterEngine doRemoteInit(String hostName, HashTree testTree) { + JMeterEngine engine = null; + try { + engine = new ClientJMeterEngine(hostName); + } catch (Exception e) { + log.fatalError("Failure connecting to remote host: "+hostName, e); + System.err.println("Failure connecting to remote host: "+hostName+" "+e); + return null; + } + engine.configure(testTree); + if (!remoteProps.isEmpty()) { + engine.setProperties(remoteProps); + } + return engine; + } + + /* + * Listen to test and handle tidyup after non-GUI test completes. + * If running a remote test, then after waiting a few seconds for listeners to finish files, + * it calls ClientJMeterEngine.tidyRMI() to deal with the Naming Timer Thread. + */ + private static class ListenToTest implements TestListener, Runnable, Remoteable { + private AtomicInteger started = new AtomicInteger(0); // keep track of remote tests + + //NOT YET USED private JMeter _parent; + + private final List engines; + + public ListenToTest(JMeter parent, List engines) { + //_parent = parent; + this.engines=engines; + } + + public void testEnded(String host) { + long now=System.currentTimeMillis(); + log.info("Finished remote host: " + host + " ("+now+")"); + if (started.decrementAndGet() <= 0) { + Thread stopSoon = new Thread(this); + stopSoon.start(); + } + } + + public void testEnded() { + long now = System.currentTimeMillis(); + println("Tidying up ... @ "+new Date(now)+" ("+now+")"); + println("... end of run"); + checkForRemainingThreads(); + } + + public void testStarted(String host) { + started.incrementAndGet(); + long now=System.currentTimeMillis(); + log.info("Started remote host: " + host + " ("+now+")"); + } + + public void testStarted() { + long now=System.currentTimeMillis(); + log.info(JMeterUtils.getResString("running_test")+" ("+now+")");//$NON-NLS-1$ + } + + /** + * This is a hack to allow listeners a chance to close their files. Must + * implement a queue for sample responses tied to the engine, and the + * engine won't deliver testEnded signal till all sample responses have + * been delivered. Should also improve performance of remote JMeter + * testing. + */ + public void run() { + long now = System.currentTimeMillis(); + println("Tidying up remote @ "+new Date(now)+" ("+now+")"); + if (engines!=null){ // it will be null unless remoteStop = true + println("Exitting remote servers"); + for (JMeterEngine e : engines){ + e.exit(); + } + } + try { + Thread.sleep(5000); // Allow listeners to close files + } catch (InterruptedException ignored) { + } + ClientJMeterEngine.tidyRMI(log); + println("... end of run"); + checkForRemainingThreads(); + } + + /** + * @see TestListener#testIterationStart(LoopIterationEvent) + */ + public void testIterationStart(LoopIterationEvent event) { + // ignored + } + + /** + * Runs daemon thread which waits a short while; + * if JVM does not exit, lists remaining non-daemon threads on stdout. + */ + private void checkForRemainingThreads() { + // This cannot be a JMeter class variable, because properties + // are not initialised until later. + final int REMAIN_THREAD_PAUSE = + JMeterUtils.getPropDefault("jmeter.exit.check.pause", 2000); // $NON-NLS-1$ + + if (REMAIN_THREAD_PAUSE > 0) { + Thread daemon = new Thread(){ + @Override + public void run(){ + try { + Thread.sleep(REMAIN_THREAD_PAUSE); // Allow enough time for JVM to exit + } catch (InterruptedException ignored) { + } + // This is a daemon thread, which should only reach here if there are other + // non-daemon threads still active + System.out.println("The JVM should have exitted but did not."); + System.out.println("The following non-daemon threads are still running (DestroyJavaVM is OK):"); + JOrphanUtils.displayThreads(false); + } + + }; + daemon.setDaemon(true); + daemon.start(); + } + } + + } + + private static void println(String str) { + System.out.println(str); + } + + private static final String[][] DEFAULT_ICONS = { + { "org.apache.jmeter.control.gui.TestPlanGui", "org/apache/jmeter/images/beaker.gif" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.timers.gui.AbstractTimerGui", "org/apache/jmeter/images/timer.gif" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.threads.gui.ThreadGroupGui", "org/apache/jmeter/images/thread.gif" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.visualizers.gui.AbstractListenerGui", "org/apache/jmeter/images/meter.png" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.config.gui.AbstractConfigGui", "org/apache/jmeter/images/testtubes.png" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.processor.gui.AbstractPreProcessorGui", "org/apache/jmeter/images/leafnode.gif"}, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.processor.gui.AbstractPostProcessorGui","org/apache/jmeter/images/leafnodeflip.gif"},//$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.control.gui.AbstractControllerGui", "org/apache/jmeter/images/knob.gif" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.control.gui.WorkBenchGui", "org/apache/jmeter/images/clipboard.gif" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.samplers.gui.AbstractSamplerGui", "org/apache/jmeter/images/pipet.png" }, //$NON-NLS-1$ $NON-NLS-2$ + { "org.apache.jmeter.assertions.gui.AbstractAssertionGui", "org/apache/jmeter/images/question.gif"} //$NON-NLS-1$ $NON-NLS-2$ + }; + + public String[][] getIconMappings() { + final String defaultIconProp = "org/apache/jmeter/images/icon.properties"; //$NON-NLS-1$ + String iconProp = JMeterUtils.getPropDefault("jmeter.icons", defaultIconProp);//$NON-NLS-1$ + Properties p = JMeterUtils.loadProperties(iconProp); + if (p == null && !iconProp.equals(defaultIconProp)) { + log.info(iconProp + " not found - using " + defaultIconProp); + iconProp = defaultIconProp; + p = JMeterUtils.loadProperties(iconProp); + } + if (p == null) { + log.info(iconProp + " not found - using inbuilt icon set"); + return DEFAULT_ICONS; + } + log.info("Loaded icon properties from " + iconProp); + String[][] iconlist = new String[p.size()][3]; + Enumeration pe = p.keys(); + int i = 0; + while (pe.hasMoreElements()) { + String key = (String) pe.nextElement(); + String icons[] = JOrphanUtils.split(p.getProperty(key), " ");//$NON-NLS-1$ + iconlist[i][0] = key; + iconlist[i][1] = icons[0]; + if (icons.length > 1) { + iconlist[i][2] = icons[1]; + } + i++; + } + return iconlist; + } + + public String[][] getResourceBundles() { + return new String[0][]; + } + + /** + * Check if JMeter is running in non-GUI mode. + * + * @return true if JMeter is running in non-GUI mode. + */ + public static boolean isNonGUI(){ + return "true".equals(System.getProperty(JMeter.JMETER_NON_GUI)); //$NON-NLS-1$ + } + + private void logProperty(String prop){ + log.info(prop+"="+System.getProperty(prop));//$NON-NLS-1$ + } + private void logProperty(String prop,String separator){ + log.info(prop+separator+System.getProperty(prop));//$NON-NLS-1$ + } + + private static void startUdpDdaemon(final List engines) { + int port = JMeterUtils.getPropDefault("jmeterengine.nongui.port", UDP_PORT_DEFAULT); // $NON-NLS-1$ + int maxPort = JMeterUtils.getPropDefault("jmeterengine.nongui.maxport", 4455); // $NON-NLS-1$ + if (port > 1000){ + final DatagramSocket socket = getSocket(port, maxPort); + if (socket != null) { + Thread waiter = new Thread("UDP Listener"){ + @Override + public void run() { + waitForSignals(engines, socket); + } + }; + waiter.setDaemon(true); + waiter.start(); + } else { + System.out.println("Failed to create UDP port"); + } + } + } + + private static void waitForSignals(final List engines, DatagramSocket socket) { + byte[] buf = new byte[80]; + System.out.println("Waiting for possible shutdown message on port "+socket.getLocalPort()); + DatagramPacket request = new DatagramPacket(buf, buf.length); + try { + while(true) { + socket.receive(request); + InetAddress address = request.getAddress(); + // Only accept commands from the local host + if (address.isLoopbackAddress()){ + String command = new String(request.getData(), request.getOffset(), request.getLength(),"ASCII"); + System.out.println("Command: "+command+" received from "+address); + log.info("Command: "+command+" received from "+address); + if (command.equals("StopTestNow")){ + for(JMeterEngine engine : engines) { + engine.stopTest(true); + } + } else if (command.equals("Shutdown")) { + for(JMeterEngine engine : engines) { + engine.stopTest(false); + } + } else { + System.out.println("Command: "+command+" not recognised "); + } + } + } + } catch (Exception e) { + System.out.println(e); + } finally { + socket.close(); + } + } + + private static DatagramSocket getSocket(int udpPort, int udpPortMax) { + DatagramSocket socket = null; + int i = udpPort; + while (i<= udpPortMax) { + try { + socket = new DatagramSocket(i); + break; + } catch (SocketException e) { + i++; + } + } + + return socket; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/JMeterReport.java b/ApacheJmeter/src/org/apache/jmeter/JMeterReport.java new file mode 100644 index 0000000..1f8d833 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/JMeterReport.java @@ -0,0 +1,404 @@ +/* + * 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; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; + +import org.apache.commons.cli.avalon.CLArgsParser; +import org.apache.commons.cli.avalon.CLOption; +import org.apache.commons.cli.avalon.CLOptionDescriptor; +import org.apache.commons.cli.avalon.CLUtil; +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.control.gui.ReportGui; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.plugin.JMeterPlugin; +import org.apache.jmeter.plugin.PluginManager; +import org.apache.jmeter.report.gui.ReportPageGui; +import org.apache.jmeter.report.gui.action.ReportActionRouter; +import org.apache.jmeter.report.gui.action.ReportCheckDirty; +import org.apache.jmeter.report.gui.action.ReportLoad; +import org.apache.jmeter.report.gui.tree.ReportTreeListener; +import org.apache.jmeter.report.gui.tree.ReportTreeModel; +import org.apache.jmeter.report.writers.gui.HTMLReportWriterGui; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractListenerGui; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * + * JMeterReport is the main class for the reporting component. For now, + * the plan is to make the reporting component a separate GUI, which + * can run in GUI or console mode. The purpose of the GUI is to design + * reports, which can then be run. One of the primary goals of the + * reporting component is to make it so the reports can be run in an + * automated process. + * The report GUI is different than the main JMeter GUI in several ways. + *
    + *
  • the gui is not multi-threaded
  • + *
  • the gui uses different components
  • + *
  • the gui is focused on designing reports from the jtl logs + * generated during a test run
  • + *
+ * The class follows the same design as JMeter.java. This should keep + * things consistent and make it easier to maintain. + */ +public class JMeterReport implements JMeterPlugin { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int PROPFILE_OPT = 'p'; + + private static final int PROPFILE2_OPT = 'q'; // Bug 33920 - additional + // prop files + + private static final int TESTFILE_OPT = 't'; + + private static final int LOGFILE_OPT = 'l'; + + private static final int NONGUI_OPT = 'n'; + + private static final int HELP_OPT = 'h'; + + private static final int VERSION_OPT = 'v'; + + private static final int SERVER_OPT = 's'; + + private static final int JMETER_PROPERTY = 'J'; + + private static final int SYSTEM_PROPERTY = 'D'; + + private static final int LOGLEVEL = 'L'; + + private static final int REMOTE_OPT = 'r'; + + private static final int JMETER_HOME_OPT = 'd'; + + private static final CLOptionDescriptor[] options = new CLOptionDescriptor[] { + new CLOptionDescriptor("help", CLOptionDescriptor.ARGUMENT_DISALLOWED, HELP_OPT, + "print usage information and exit"), + new CLOptionDescriptor("version", CLOptionDescriptor.ARGUMENT_DISALLOWED, VERSION_OPT, + "print the version information and exit"), + new CLOptionDescriptor("propfile", CLOptionDescriptor.ARGUMENT_REQUIRED, PROPFILE_OPT, + "the jmeter property file to use"), + new CLOptionDescriptor("addprop", CLOptionDescriptor.ARGUMENT_REQUIRED + | CLOptionDescriptor.DUPLICATES_ALLOWED, // Bug 33920 - + // allow + // multiple + // props + PROPFILE2_OPT, "additional property file(s)"), + new CLOptionDescriptor("testfile", CLOptionDescriptor.ARGUMENT_REQUIRED, TESTFILE_OPT, + "the jmeter test(.jmx) file to run"), + new CLOptionDescriptor("logfile", CLOptionDescriptor.ARGUMENT_REQUIRED, LOGFILE_OPT, + "the file to log samples to"), + new CLOptionDescriptor("nongui", CLOptionDescriptor.ARGUMENT_DISALLOWED, NONGUI_OPT, + "run JMeter in nongui mode"), + new CLOptionDescriptor("server", CLOptionDescriptor.ARGUMENT_DISALLOWED, SERVER_OPT, + "run the JMeter server"), + new CLOptionDescriptor("jmeterproperty", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENTS_REQUIRED_2, JMETER_PROPERTY, "Define additional JMeter properties"), + new CLOptionDescriptor("systemproperty", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENTS_REQUIRED_2, SYSTEM_PROPERTY, "Define additional JMeter properties"), + new CLOptionDescriptor("loglevel", CLOptionDescriptor.DUPLICATES_ALLOWED + | CLOptionDescriptor.ARGUMENTS_REQUIRED_2, LOGLEVEL, + "Define loglevel: [category=]level e.g. jorphan=INFO or " + "jmeter.util=DEBUG"), + new CLOptionDescriptor("runremote", CLOptionDescriptor.ARGUMENT_DISALLOWED, REMOTE_OPT, + "Start remote servers from non-gui mode"), + new CLOptionDescriptor("homedir", CLOptionDescriptor.ARGUMENT_REQUIRED, JMETER_HOME_OPT, + "the jmeter home directory to use"), }; + + /** + * + */ + public JMeterReport() { + super(); + } + + /** + * The default icons for the report GUI. + */ + private static final String[][] DEFAULT_ICONS = { + { AbstractListenerGui.class.getName(), "org/apache/jmeter/images/meter.png" }, + { AbstractConfigGui.class.getName(), "org/apache/jmeter/images/testtubes.png" }, + { HTMLReportWriterGui.class.getName(), "org/apache/jmeter/images/new/pencil.png" }, + { ReportPageGui.class.getName(), "org/apache/jmeter/images/new/scroll.png" }, + { ReportGui.class.getName(), "org/apache/jmeter/images/new/book.png" } + }; + + /** {@inheritDoc} */ + public String[][] getIconMappings() { + String iconProp = JMeterUtils.getPropDefault("jmeter.icons", "org/apache/jmeter/images/icon.properties"); + Properties p = JMeterUtils.loadProperties(iconProp); + if (p == null) { + log.info(iconProp + " not found - using default icon set"); + return DEFAULT_ICONS; + } + log.info("Loaded icon properties from " + iconProp); + String[][] iconlist = new String[p.size()][3]; + Enumeration pe = p.keys(); + int i = 0; + while (pe.hasMoreElements()) { + String key = (String) pe.nextElement(); + String icons[] = JOrphanUtils.split(p.getProperty(key), " "); + iconlist[i][0] = key; + iconlist[i][1] = icons[0]; + if (icons.length > 1){ + iconlist[i][2] = icons[1]; + } + i++; + } + return iconlist; + } + + /** {@inheritDoc} */ + public String[][] getResourceBundles() { + return new String[0][]; + } + + public void startNonGui(CLOption testFile, CLOption logFile){ + System.setProperty(JMeter.JMETER_NON_GUI, "true"); + PluginManager.install(this, false); + } + + public void startGui(CLOption testFile) { + PluginManager.install(this, true); + ReportTreeModel treeModel = new ReportTreeModel(); + ReportTreeListener treeLis = new ReportTreeListener(treeModel); + treeLis.setActionHandler(ReportActionRouter.getInstance()); + ReportGuiPackage.getInstance(treeLis, treeModel); + org.apache.jmeter.gui.ReportMainFrame main = + new org.apache.jmeter.gui.ReportMainFrame(ReportActionRouter.getInstance(), + treeModel, treeLis); + ComponentUtil.centerComponentInWindow(main, 80); + main.setVisible(true); + + ReportActionRouter.getInstance().actionPerformed(new ActionEvent(main, 1, ReportCheckDirty.ADD_ALL)); + if (testFile != null) { + FileInputStream reader = null; + try { + File f = new File(testFile.getArgument()); + log.info("Loading file: " + f); + reader = new FileInputStream(f); + HashTree tree = SaveService.loadTree(reader); + + ReportGuiPackage.getInstance().setReportPlanFile(f.getAbsolutePath()); + + new ReportLoad().insertLoadedTree(1, tree); + } catch (Exception e) { + log.error("Failure loading test file", e); + JMeterUtils.reportErrorToUser(e.toString()); + } + finally{ + JOrphanUtils.closeQuietly(reader); + } + } + } + +// private void run(String testFile, String logFile, boolean remoteStart) { +// FileInputStream reader = null; +// try { +// File f = new File(testFile); +// if (!f.exists() || !f.isFile()) { +// System.out.println("Could not open " + testFile); +// return; +// } +// FileServer.getFileServer().setBasedir(f.getAbsolutePath()); +// +// reader = new FileInputStream(f); +// log.info("Loading file: " + f); +// +// HashTree tree = SaveService.loadTree(reader); +// +// // Remove the disabled items +// // For GUI runs this is done in Start.java +// convertSubTree(tree); +// +// if (logFile != null) { +// ResultCollector logger = new ResultCollector(); +// logger.setFilename(logFile); +// tree.add(tree.getArray()[0], logger); +// } +// String summariserName = JMeterUtils.getPropDefault( +// "summariser.name", "");//$NON-NLS-1$ +// if (summariserName.length() > 0) { +// log.info("Creating summariser <" + summariserName + ">"); +// System.out.println("Creating summariser <" + summariserName + ">"); +// Summariser summer = new Summariser(summariserName); +// tree.add(tree.getArray()[0], summer); +// } +// tree.add(tree.getArray()[0], new ListenToTest(parent)); +// System.out.println("Created the tree successfully"); +// /** +// JMeterEngine engine = null; +// if (!remoteStart) { +// engine = new StandardJMeterEngine(); +// engine.configure(tree); +// System.out.println("Starting the test"); +// engine.runTest(); +// } else { +// String remote_hosts_string = JMeterUtils.getPropDefault( +// "remote_hosts", "127.0.0.1"); +// java.util.StringTokenizer st = new java.util.StringTokenizer( +// remote_hosts_string, ","); +// List engines = new LinkedList(); +// while (st.hasMoreElements()) { +// String el = (String) st.nextElement(); +// System.out.println("Configuring remote engine for " + el); +// // engines.add(doRemoteInit(el.trim(), tree)); +// } +// System.out.println("Starting remote engines"); +// Iterator iter = engines.iterator(); +// while (iter.hasNext()) { +// engine = (JMeterEngine) iter.next(); +// engine.runTest(); +// } +// System.out.println("Remote engines have been started"); +// } +// **/ +// } catch (Exception e) { +// System.out.println("Error in NonGUIDriver " + e.toString()); +// log.error("", e); +// } +// finally{ +// JOrphanUtils.closeQuietly(reader); +// } +// } + + + /** + * + * @param args + */ + public void start(String[] args) { + CLArgsParser parser = new CLArgsParser(args, options); + if (null != parser.getErrorString()) { + System.err.println("Error: " + parser.getErrorString()); + System.out.println("Usage"); + System.out.println(CLUtil.describeOptions(options).toString()); + return; + } + try { + initializeProperties(parser); + log.info("Version " + JMeterUtils.getJMeterVersion()); + log.info("java.version=" + System.getProperty("java.version")); + log.info(JMeterUtils.getJMeterCopyright()); + if (parser.getArgumentById(VERSION_OPT) != null) { + System.out.println(JMeterUtils.getJMeterCopyright()); + System.out.println("Version " + JMeterUtils.getJMeterVersion()); + } else if (parser.getArgumentById(HELP_OPT) != null) { + System.out.println(JMeterUtils.getResourceFileAsText("org/apache/jmeter/help.txt")); + } else if (parser.getArgumentById(NONGUI_OPT) == null) { + startGui(parser.getArgumentById(TESTFILE_OPT)); + } else { + startNonGui(parser.getArgumentById(TESTFILE_OPT), parser.getArgumentById(LOGFILE_OPT)); + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("An error occurred: " + e.getMessage()); + System.exit(-1); + } + } + + private void initializeProperties(CLArgsParser parser) { + if (parser.getArgumentById(PROPFILE_OPT) != null) { + JMeterUtils.getProperties(parser.getArgumentById(PROPFILE_OPT).getArgument()); + } else { + JMeterUtils.getProperties(NewDriver.getJMeterDir() + File.separator + "bin" + File.separator + + "jmeter.properties"); + } + + // Bug 33845 - allow direct override of Home dir + if (parser.getArgumentById(JMETER_HOME_OPT) == null) { + JMeterUtils.setJMeterHome(NewDriver.getJMeterDir()); + } else { + JMeterUtils.setJMeterHome(parser.getArgumentById(JMETER_HOME_OPT).getArgument()); + } + + // Process command line property definitions (can occur multiple times) + + Properties jmeterProps = JMeterUtils.getJMeterProperties(); + List clOptions = parser.getArguments(); + int size = clOptions.size(); + + for (int i = 0; i < size; i++) { + CLOption option = clOptions.get(i); + String name = option.getArgument(0); + String value = option.getArgument(1); + + switch (option.getDescriptor().getId()) { + case PROPFILE2_OPT: // Bug 33920 - allow multiple props + File f = new File(name); + FileInputStream inStream = null; + try { + inStream = new FileInputStream(f); + jmeterProps.load(inStream); + } catch (FileNotFoundException e) { + log.warn("Can't find additional property file: " + name, e); + } catch (IOException e) { + log.warn("Error loading additional property file: " + name, e); + } finally { + IOUtils.closeQuietly(inStream); + } + break; + case SYSTEM_PROPERTY: + if (value.length() > 0) { // Set it + log.info("Setting System property: " + name + "=" + value); + System.getProperties().setProperty(name, value); + } else { // Reset it + log.warn("Removing System property: " + name); + System.getProperties().remove(name); + } + break; + case JMETER_PROPERTY: + if (value.length() > 0) { // Set it + log.info("Setting JMeter property: " + name + "=" + value); + jmeterProps.setProperty(name, value); + } else { // Reset it + log.warn("Removing JMeter property: " + name); + jmeterProps.remove(name); + } + break; + case LOGLEVEL: + if (value.length() > 0) { // Set category + log.info("LogLevel: " + name + "=" + value); + LoggingManager.setPriority(value, name); + } else { // Set root level + log.warn("LogLevel: " + name); + LoggingManager.setPriority(name); + } + break; + default: + // ignored + break; + } + } + + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/NewDriver.java b/ApacheJmeter/src/org/apache/jmeter/NewDriver.java new file mode 100644 index 0000000..076afc4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/NewDriver.java @@ -0,0 +1,222 @@ +/* + * 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; + +// N.B. this must only use standard Java packages +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.AccessController; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * Main class for JMeter - sets up initial classpath and the loader. + * + */ +public final class NewDriver { + + private static final String CLASSPATH_SEPARATOR = System.getProperty("path.separator");// $NON-NLS-1$ + + private static final String OS_NAME = System.getProperty("os.name");// $NON-NLS-1$ + + private static final String OS_NAME_LC = OS_NAME.toLowerCase(java.util.Locale.ENGLISH); + + private static final String JAVA_CLASS_PATH = "java.class.path";// $NON-NLS-1$ + + /** The class loader to use for loading JMeter classes. */ + private static final DynamicClassLoader loader; + + /** The directory JMeter is installed in. */ + private static final String jmDir; + + static { + final List jars = new LinkedList(); + final String initial_classpath = System.getProperty(JAVA_CLASS_PATH); + + // Find JMeter home dir from the initial classpath + String tmpDir=null; + StringTokenizer tok = new StringTokenizer(initial_classpath, File.pathSeparator); + if (tok.countTokens() == 1 + || (tok.countTokens() == 2 // Java on Mac OS can add a second entry to the initial classpath + && OS_NAME_LC.startsWith("mac os x")// $NON-NLS-1$ + ) + ) { + File jar = new File(tok.nextToken()); + try { + tmpDir = jar.getCanonicalFile().getParentFile().getParent(); + } catch (IOException e) { + } + } else {// e.g. started from IDE with full classpath + tmpDir = System.getProperty("jmeter.home","");// Allow override $NON-NLS-1$ $NON-NLS-2$ + if (tmpDir.length() == 0) { + File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$ + tmpDir = userDir.getAbsoluteFile().getParent(); + } + } + jmDir=tmpDir; + + /* + * Does the system support UNC paths? If so, may need to fix them up + * later + */ + boolean usesUNC = OS_NAME_LC.startsWith("windows");// $NON-NLS-1$ + + // Add standard jar locations to initial classpath + StringBuilder classpath = new StringBuilder(); + File[] libDirs = new File[] { new File(jmDir + File.separator + "lib"),// $NON-NLS-1$ $NON-NLS-2$ + new File(jmDir + File.separator + "lib" + File.separator + "ext"),// $NON-NLS-1$ $NON-NLS-2$ + new File(jmDir + File.separator + "lib" + File.separator + "junit")};// $NON-NLS-1$ $NON-NLS-2$ + for (int a = 0; a < libDirs.length; a++) { + File[] libJars = libDirs[a].listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) {// only accept jar files + return name.endsWith(".jar");// $NON-NLS-1$ + } + }); + if (libJars == null) { + new Throwable("Could not access " + libDirs[a]).printStackTrace(); + continue; + } + Arrays.sort(libJars); // Bug 50708 Ensure predictable order of jars + for (int i = 0; i < libJars.length; i++) { + try { + String s = libJars[i].getPath(); + + // Fix path to allow the use of UNC URLs + if (usesUNC) { + if (s.startsWith("\\\\") && !s.startsWith("\\\\\\")) {// $NON-NLS-1$ $NON-NLS-2$ + s = "\\\\" + s;// $NON-NLS-1$ + } else if (s.startsWith("//") && !s.startsWith("///")) {// $NON-NLS-1$ $NON-NLS-2$ + s = "//" + s;// $NON-NLS-1$ + } + } // usesUNC + + jars.add(new File(s).toURI().toURL());// See Java bug 4496398 + classpath.append(CLASSPATH_SEPARATOR); + classpath.append(s); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + } + + // ClassFinder needs the classpath + System.setProperty(JAVA_CLASS_PATH, initial_classpath + classpath.toString()); + loader = AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public DynamicClassLoader run() { + return new DynamicClassLoader(jars.toArray(new URL[0])); + } + } + ); + } + + /** + * Prevent instantiation. + */ + private NewDriver() { + } + + /** + * Add a URL to the loader classpath only; does not update the system classpath. + * + * @param path to be added. + */ + public static void addURL(String path) { + File furl = new File(path); + try { + loader.addURL(furl.toURI().toURL()); // See Java bug 4496398 + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + + /** + * Add a URL to the loader classpath only; does not update the system classpath. + * + * @param url + */ + public static void addURL(URL url) { + loader.addURL(url); + } + + /** + * Add a directory or jar to the loader and system classpaths. + * + * @param path to add to the loader and system classpath + * @throws MalformedURLException + */ + public static void addPath(String path) throws MalformedURLException { + File file = new File(path); + // Ensure that directory URLs end in "/" + if (file.isDirectory() && !path.endsWith("/")) {// $NON-NLS-1$ + file = new File(path + "/");// $NON-NLS-1$ + } + loader.addURL(file.toURI().toURL()); // See Java bug 4496398 + StringBuilder sb = new StringBuilder(System.getProperty(JAVA_CLASS_PATH)); + sb.append(CLASSPATH_SEPARATOR); + sb.append(path); + // ClassFinder needs this + System.setProperty(JAVA_CLASS_PATH,sb.toString()); + } + + /** + * Get the directory where JMeter is installed. This is the absolute path + * name. + * + * @return the directory where JMeter is installed. + */ + public static String getJMeterDir() { + return jmDir; + } + + /** + * The main program which actually runs JMeter. + * + * @param args + * the command line arguments + */ + public static void main(String[] args) { + Thread.currentThread().setContextClassLoader(loader); + if (System.getProperty("log4j.configuration") == null) {// $NON-NLS-1$ $NON-NLS-2$ + File conf = new File(jmDir, "bin" + File.separator + "log4j.conf");// $NON-NLS-1$ $NON-NLS-2$ + System.setProperty("log4j.configuration", "file:" + conf); + } + + try { + Class initialClass; + if (args != null && args.length > 0 && args[0].equals("report")) {// $NON-NLS-1$ + initialClass = loader.loadClass("org.apache.jmeter.JMeterReport");// $NON-NLS-1$ + } else { + initialClass = loader.loadClass("org.apache.jmeter.JMeter");// $NON-NLS-1$ + } + Object instance = initialClass.newInstance(); + Method startup = initialClass.getMethod("start", new Class[] { (new String[0]).getClass() });// $NON-NLS-1$ + startup.invoke(instance, new Object[] { args }); + } catch(Throwable e){ + e.printStackTrace(); + System.err.println("JMeter home directory was detected as: "+jmDir); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/ProxyAuthenticator.java b/ApacheJmeter/src/org/apache/jmeter/ProxyAuthenticator.java new file mode 100644 index 0000000..5dc1694 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/ProxyAuthenticator.java @@ -0,0 +1,70 @@ +/* + * 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; + +import java.net.Authenticator; +import java.net.PasswordAuthentication; + +/** + * Provides JMeter the ability to use proxy servers that require username and + * password. + * + * @version $Revision: 937726 $ + */ +public class ProxyAuthenticator extends Authenticator { + /** The username to authenticate with. */ + private final String userName; + + /** The password to authenticate with. */ + private final char password[]; + + /** + * Create a ProxyAuthenticator with the specified username and password. + * + * @param userName + * the username to authenticate with + * @param password + * the password to authenticate with + */ + public ProxyAuthenticator(String userName, String password) { + this.userName = userName; + this.password = password.toCharArray(); + } + + /** + * Return a PasswordAuthentication instance using the userName and password + * specified in the constructor. + * Only applies to PROXY request types. + * + * @return a PasswordAuthentication instance to use for authenticating with + * the proxy + */ + @Override + protected PasswordAuthentication getPasswordAuthentication() { + switch (getRequestorType()){ + case PROXY: + return new PasswordAuthentication(userName, password); + case SERVER: + break; + default: + break; + } + return null; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/Assertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/Assertion.java new file mode 100644 index 0000000..9e625a3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/Assertion.java @@ -0,0 +1,44 @@ +/* + * 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.assertions; + +import org.apache.jmeter.samplers.SampleResult; + +/** + * An Assertion checks a SampleResult to determine whether or not it is + * successful. The resulting success status can be obtained from a corresponding + * Assertion Result. For example, if a web response doesn't contain an expected + * expression, it would be considered a failure. + * + * @version $Revision: 674351 $ + */ +public interface Assertion { + /** + * Returns the AssertionResult object encapsulating information about the + * success or failure of the assertion. + * + * @param response + * the SampleResult containing information about the Sample + * (duration, success, etc) + * + * @return the AssertionResult containing the information about whether the + * assertion passed or failed. + */ + AssertionResult getResult(SampleResult response); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/AssertionResult.java b/ApacheJmeter/src/org/apache/jmeter/assertions/AssertionResult.java new file mode 100644 index 0000000..0abad16 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/AssertionResult.java @@ -0,0 +1,165 @@ +/* + * 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.assertions; + +import java.io.Serializable; + +/** + * Implements Response Assertion checking. + */ +public class AssertionResult implements Serializable { + public static final String RESPONSE_WAS_NULL = "Response was null"; // $NON-NLS-1$ + + private static final long serialVersionUID = 240L; + + /** Name of the assertion. */ + private final String name; + + /** True if the assertion failed. */ + private boolean failure; + + /** True if there was an error checking the assertion. */ + private boolean error; + + /** A message describing the failure. */ + private String failureMessage; + + /** + * Create a new Assertion Result. The result will indicate no failure or + * error. + * @deprecated - use the named constructor + */ + @Deprecated + public AssertionResult() { // Needs to be public for tests + this.name = null; + } + + /** + * Create a new Assertion Result. The result will indicate no failure or + * error. + * + * @param name the name of the assertion + */ + public AssertionResult(String name) { + this.name = name; + } + + /** + * Get the name of the assertion + * + * @return the name of the assertion + */ + public String getName() { + return name; + } + + /** + * Check if the assertion failed. If it failed, the failure message may give + * more details about the failure. + * + * @return true if the assertion failed, false if the sample met the + * assertion criteria + */ + public boolean isFailure() { + return failure; + } + + /** + * Check if an error occurred while checking the assertion. If an error + * occurred, the failure message may give more details about the error. + * + * @return true if an error occurred while checking the assertion, false + * otherwise. + */ + public boolean isError() { + return error; + } + + /** + * Get the message associated with any failure or error. This method may + * return null if no message was set. + * + * @return a failure or error message, or null if no message has been set + */ + public String getFailureMessage() { + return failureMessage; + } + + /** + * Set the flag indicating whether or not an error occurred. + * + * @param e + * true if an error occurred, false otherwise + */ + public void setError(boolean e) { + error = e; + } + + /** + * Set the flag indicating whether or not a failure occurred. + * + * @param f + * true if a failure occurred, false otherwise + */ + public void setFailure(boolean f) { + failure = f; + } + + /** + * Set the failure message giving more details about a failure or error. + * + * @param message + * the message to set + */ + public void setFailureMessage(String message) { + failureMessage = message; + } + + /** + * Convenience method for setting up failed results + * + * @param message + * the message to set + * @return this + * + */ + public AssertionResult setResultForFailure(String message) { + error = false; + failure = true; + failureMessage = message; + return this; + } + + /** + * Convenience method for setting up results where the response was null + * + * @return assertion result with appropriate fields set up + */ + public AssertionResult setResultForNull() { + error = false; + failure = true; + failureMessage = RESPONSE_WAS_NULL; + return this; + } + + @Override + public String toString() { + return getName() != null ? getName() : super.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/BSFAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/BSFAssertion.java new file mode 100644 index 0000000..ba332d9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/BSFAssertion.java @@ -0,0 +1,56 @@ +/* + * 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.assertions; + +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class BSFAssertion extends BSFTestElement implements Cloneable, Assertion, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 234L; + + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + BSFManager mgr =null; + try { + mgr = getManager(); + mgr.declareBean("SampleResult", response, SampleResult.class); + mgr.declareBean("AssertionResult", result, AssertionResult.class); + processFileOrScript(mgr); + result.setError(false); + } catch (BSFException e) { + log.warn("Problem in BSF script "+e); + result.setFailure(true); + result.setError(true); + result.setFailureMessage(e.toString()); + } finally { + if(mgr != null) { + mgr.terminate(); + } + } + return result; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/BSFAssertionBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/assertions/BSFAssertionBeanInfo.java new file mode 100644 index 0000000..c9ffad5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/BSFAssertionBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.assertions; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +public class BSFAssertionBeanInfo extends BSFBeanInfoSupport { + + public BSFAssertionBeanInfo() { + super(BSFAssertion.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/BeanShellAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/BeanShellAssertion.java new file mode 100644 index 0000000..92ee88d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/BeanShellAssertion.java @@ -0,0 +1,132 @@ +/* + * 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.assertions; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * An Assertion which understands BeanShell + * + */ +public class BeanShellAssertion extends BeanShellTestElement implements Assertion { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 3; + + public static final String FILENAME = "BeanShellAssertion.filename"; //$NON-NLS-1$ + + public static final String SCRIPT = "BeanShellAssertion.query"; //$NON-NLS-1$ + + public static final String PARAMETERS = "BeanShellAssertion.parameters"; //$NON-NLS-1$ + + public static final String RESET_INTERPRETER = "BeanShellAssertion.resetInterpreter"; //$NON-NLS-1$ + + // can be specified in jmeter.properties + public static final String INIT_FILE = "beanshell.assertion.init"; //$NON-NLS-1$ + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + @Override + public String getScript() { + return getPropertyAsString(SCRIPT); + } + + @Override + public String getFilename() { + return getPropertyAsString(FILENAME); + } + + @Override + public String getParameters() { + return getPropertyAsString(PARAMETERS); + } + + @Override + public boolean isResetInterpreter() { + return getPropertyAsBoolean(RESET_INTERPRETER); + } + + /** + * {@inheritDoc} + */ + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + result.setFailure(true); + result.setError(true); + result.setFailureMessage("BeanShell Interpreter not found"); + return result; + } + try { + + // Add SamplerData for consistency with BeanShell Sampler + bshInterpreter.set("SampleResult", response); //$NON-NLS-1$ + bshInterpreter.set("Response", response); //$NON-NLS-1$ + bshInterpreter.set("ResponseData", response.getResponseData());//$NON-NLS-1$ + bshInterpreter.set("ResponseCode", response.getResponseCode());//$NON-NLS-1$ + bshInterpreter.set("ResponseMessage", response.getResponseMessage());//$NON-NLS-1$ + bshInterpreter.set("ResponseHeaders", response.getResponseHeaders());//$NON-NLS-1$ + bshInterpreter.set("RequestHeaders", response.getRequestHeaders());//$NON-NLS-1$ + bshInterpreter.set("SampleLabel", response.getSampleLabel());//$NON-NLS-1$ + bshInterpreter.set("SamplerData", response.getSamplerData());//$NON-NLS-1$ + bshInterpreter.set("Successful", response.isSuccessful());//$NON-NLS-1$ + + // The following are used to set the Result details on return from + // the script: + bshInterpreter.set("FailureMessage", "");//$NON-NLS-1$ //$NON-NLS-2$ + bshInterpreter.set("Failure", false);//$NON-NLS-1$ + + processFileOrScript(bshInterpreter); + + result.setFailureMessage(bshInterpreter.get("FailureMessage").toString());//$NON-NLS-1$ + result.setFailure(Boolean.valueOf(bshInterpreter.get("Failure") //$NON-NLS-1$ + .toString()).booleanValue()); + result.setError(false); + } + /* + * To avoid class loading problems when the BSH jar is missing, we don't + * try to catch this error separately catch (bsh.EvalError ex) { + * log.debug("",ex); result.setError(true); + * result.setFailureMessage(ex.toString()); } + */ + // but we do trap this error to make tests work better + catch (NoClassDefFoundError ex) { + log.error("BeanShell Jar missing? " + ex.toString()); + result.setError(true); + result.setFailureMessage("BeanShell Jar missing? " + ex.toString()); + response.setStopThread(true); // No point continuing + } catch (Exception ex) // Mainly for bsh.EvalError + { + result.setError(true); + result.setFailureMessage(ex.toString()); + log.warn(ex.toString()); + } + + return result; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertion.java new file mode 100644 index 0000000..7d68ea1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertion.java @@ -0,0 +1,207 @@ +/* + * 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.assertions; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.oro.text.regex.StringSubstitution; +import org.apache.oro.text.regex.Util; + +public class CompareAssertion extends AbstractTestElement implements Assertion, TestBean, Serializable, + LoopIterationListener { + + private static final long serialVersionUID = 240L; + + private transient List responses; + + private transient final StringSubstitution emptySub = new StringSubstitution(""); //$NON-NLS-1$ + + private boolean compareContent = true; + + private long compareTime = -1; + + private Collection stringsToSkip; + + public CompareAssertion() { + super(); + } + + public AssertionResult getResult(SampleResult response) { + responses.add(response); + if (responses.size() > 1) { + CompareAssertionResult result = new CompareAssertionResult(getName()); + compareContent(result); + compareTime(result); + return result; + } else + return new AssertionResult(getName()); + } + + private void compareTime(CompareAssertionResult result) { + if (compareTime >= 0) { + long prevTime = -1; + SampleResult prevResult = null; + boolean success = true; + for(SampleResult sResult : responses) { + long currentTime = sResult.getTime(); + if (prevTime != -1) { + success = Math.abs(prevTime - currentTime) <= compareTime; + prevResult = sResult; + } + if (!success) { + result.setFailure(true); + StringBuilder buf = new StringBuilder(); + appendResultDetails(buf, prevResult); + buf.append(JMeterUtils.getResString("comparison_response_time")).append(prevTime); + result.addToBaseResult(buf.toString()); + buf = new StringBuilder(); + appendResultDetails(buf, sResult); + buf.append(JMeterUtils.getResString("comparison_response_time")).append(currentTime); + result.addToSecondaryResult(buf.toString()); + result.setFailureMessage( + JMeterUtils.getResString("comparison_differ_time")+ //$NON-NLS-1$ + compareTime+ + JMeterUtils.getResString("comparison_unit")); //$NON-NLS-1$ + break; + } + prevResult = sResult; + prevTime = currentTime; + } + } + } + + private void compareContent(CompareAssertionResult result) { + if (compareContent) { + String prevContent = null; + SampleResult prevResult = null; + boolean success = true; + for (SampleResult sResult : responses) { + String currentContent = sResult.getResponseDataAsString(); + currentContent = filterString(currentContent); + if (prevContent != null) { + success = prevContent.equals(currentContent); + } + if (!success) { + result.setFailure(true); + StringBuilder buf = new StringBuilder(); + appendResultDetails(buf, prevResult); + buf.append(prevContent); + result.addToBaseResult(buf.toString()); + buf = new StringBuilder(); + appendResultDetails(buf, sResult); + buf.append(currentContent); + result.addToSecondaryResult(buf.toString()); + result.setFailureMessage(JMeterUtils.getResString("comparison_differ_content")); //$NON-NLS-1$ + break; + } + prevResult = sResult; + prevContent = currentContent; + } + } + } + + private void appendResultDetails(StringBuilder buf, SampleResult result) { + final String samplerData = result.getSamplerData(); + if (samplerData != null){ + buf.append(samplerData.trim()); + } + buf.append("\n"); //$NON-NLS-1$ + final String requestHeaders = result.getRequestHeaders(); + if (requestHeaders != null){ + buf.append(requestHeaders); + } + buf.append("\n\n"); //$NON-NLS-1$ + } + + private String filterString(String content) { + if (stringsToSkip == null || stringsToSkip.size() == 0) { + return content; + } else { + for (SubstitutionElement regex : stringsToSkip) { + emptySub.setSubstitution(regex.getSubstitute()); + content = Util.substitute(JMeterUtils.getMatcher(), JMeterUtils.getPatternCache().getPattern(regex.getRegex()), + emptySub, content, Util.SUBSTITUTE_ALL); + } + } + return content; + } + + public void iterationStart(LoopIterationEvent iterEvent) { + responses = new LinkedList(); + } + + public void iterationEnd(LoopIterationEvent iterEvent) { + responses = null; + } + + /** + * @return Returns the compareContent. + */ + public boolean isCompareContent() { + return compareContent; + } + + /** + * @param compareContent + * The compareContent to set. + */ + public void setCompareContent(boolean compareContent) { + this.compareContent = compareContent; + } + + /** + * @return Returns the compareTime. + */ + public long getCompareTime() { + return compareTime; + } + + /** + * @param compareTime + * The compareTime to set. + */ + public void setCompareTime(long compareTime) { + this.compareTime = compareTime; + } + + /** + * @return Returns the stringsToSkip. + */ + public Collection getStringsToSkip() { + return stringsToSkip; + } + + /** + * @param stringsToSkip + * The stringsToSkip to set. + */ + public void setStringsToSkip(Collection stringsToSkip) { + this.stringsToSkip = stringsToSkip; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertionBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertionBeanInfo.java new file mode 100644 index 0000000..b5c309d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertionBeanInfo.java @@ -0,0 +1,56 @@ +/* + * 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.assertions; + +import java.beans.PropertyDescriptor; +import java.util.ArrayList; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.TableEditor; +import org.apache.jmeter.util.JMeterUtils; + +public class CompareAssertionBeanInfo extends BeanInfoSupport { + + public CompareAssertionBeanInfo() { + super(CompareAssertion.class); + createPropertyGroup("compareChoices", new String[] { "compareContent", "compareTime" }); //$NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ + createPropertyGroup("comparison_filters", new String[]{"stringsToSkip"}); //$NON-NLS-1$ $NON-NLS-2$ + PropertyDescriptor p = property("compareContent"); //$NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p = property("compareTime"); //$NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Long.valueOf(-1)); + p.setValue(NOT_EXPRESSION, Boolean.FALSE); + p = property("stringsToSkip"); //$NON-NLS-1$ + p.setPropertyEditorClass(TableEditor.class); + p.setValue(TableEditor.CLASSNAME,SubstitutionElement.class.getName()); + p.setValue(TableEditor.HEADERS,new String[]{ + JMeterUtils.getResString("comparison_regex_string"), //$NON-NLS-1$ + JMeterUtils.getResString("comparison_regex_substitution")}); //$NON-NLS-1$ + p.setValue(TableEditor.OBJECT_PROPERTIES, // These are the names of the get/set methods + new String[]{SubstitutionElement.REGEX, SubstitutionElement.SUBSTITUTE}); + p.setValue(NOT_UNDEFINED,Boolean.TRUE); + p.setValue(DEFAULT,new ArrayList()); + p.setValue(MULTILINE,Boolean.TRUE); + + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertionResult.java b/ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertionResult.java new file mode 100644 index 0000000..963f665 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/CompareAssertionResult.java @@ -0,0 +1,93 @@ +/* + * 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.assertions; + + +public class CompareAssertionResult extends AssertionResult { + private static final long serialVersionUID = 1; + + private transient final ResultHolder comparedResults = new ResultHolder(); + + /** + * For testing only + * @deprecated Use the other ctor + */ + @Deprecated + public CompareAssertionResult() { // needs to be public for testing + super(); + } + + public CompareAssertionResult(String name) { + super(name); + } + + public void addToBaseResult(String resultData) + { + comparedResults.addToBaseResult(resultData); + } + + public void addToSecondaryResult(String resultData) + { + comparedResults.addToSecondaryResult(resultData); + } + + public String getBaseResult() + { + return comparedResults.baseResult; + } + + public String getSecondaryResult() + { + return comparedResults.secondaryResult; + } + + private static class ResultHolder + { + private String baseResult; + private String secondaryResult; + + public ResultHolder() + { + } + + public void addToBaseResult(String r) + { + if(baseResult == null) + { + baseResult = r; + } + else + { + baseResult = baseResult + "\n\n" + r; //$NON-NLS-1$ + } + } + + public void addToSecondaryResult(String r) + { + if(secondaryResult == null) + { + secondaryResult = r; + } + else + { + secondaryResult = secondaryResult + "\n\n" + r; //$NON-NLS-1$ + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/DurationAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/DurationAssertion.java new file mode 100644 index 0000000..5c9be8d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/DurationAssertion.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.assertions; + +import java.io.Serializable; +import java.text.MessageFormat; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedAssertion; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Checks if an Sample is sampled within a specified time-frame. If the duration + * is larger than the timeframe the Assertion is considered a failure. + * + */ +public class DurationAssertion extends AbstractScopedAssertion implements Serializable, Assertion { + private static final long serialVersionUID = 240L; + + /** Key for storing assertion-informations in the jmx-file. */ + public static final String DURATION_KEY = "DurationAssertion.duration"; // $NON-NLS-1$ + + /** + * Returns the result of the Assertion. Here it checks wether the Sample + * took to long to be considered successful. If so an AssertionResult + * containing a FailureMessage will be returned. Otherwise the returned + * AssertionResult will reflect the success of the Sample. + */ + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + result.setFailure(false); + long duration=getAllowedDuration(); + if (duration > 0) { + long responseTime=response.getTime(); + // has the Sample lasted too long? + if ( responseTime > duration) { + result.setFailure(true); + Object[] arguments = { Long.valueOf(responseTime), Long.valueOf(duration) }; + String message = MessageFormat.format( + JMeterUtils.getResString("duration_assertion_failure") // $NON-NLS-1$ + , arguments); + result.setFailureMessage(message); + } + } + return result; + } + + /** + * Returns the duration to be asserted. A duration of 0 indicates this + * assertion is to be ignored. + */ + private long getAllowedDuration() { + return getPropertyAsLong(DURATION_KEY); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/HTMLAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/HTMLAssertion.java new file mode 100644 index 0000000..1b29253 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/HTMLAssertion.java @@ -0,0 +1,375 @@ +/* + * 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.assertions; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; +import java.text.MessageFormat; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.w3c.tidy.Node; +import org.w3c.tidy.Tidy; + +/** + * Assertion to validate the response of a Sample with Tidy. + */ +public class HTMLAssertion extends AbstractTestElement implements Serializable, Assertion { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String DEFAULT_DOCTYPE = "omit"; //$NON-NLS-1$ + + public static final String DOCTYPE_KEY = "html_assertion_doctype"; //$NON-NLS-1$ + + public static final String ERRORS_ONLY_KEY = "html_assertion_errorsonly"; //$NON-NLS-1$ + + public static final String ERROR_THRESHOLD_KEY = "html_assertion_error_threshold"; //$NON-NLS-1$ + + public static final String WARNING_THRESHOLD_KEY = "html_assertion_warning_threshold"; //$NON-NLS-1$ + + public static final String FORMAT_KEY = "html_assertion_format"; //$NON-NLS-1$ + + public static final String FILENAME_KEY = "html_assertion_filename"; //$NON-NLS-1$ + + /** + * + */ + public HTMLAssertion() { + log.debug("HTMLAssertion(): called"); + } + + /** + * Returns the result of the Assertion. If so an AssertionResult containing + * a FailureMessage will be returned. Otherwise the returned AssertionResult + * will reflect the success of the Sample. + */ + public AssertionResult getResult(SampleResult inResponse) { + log.debug("HTMLAssertions.getResult() called"); + + // no error as default + AssertionResult result = new AssertionResult(getName()); + + if (inResponse.getResponseData().length == 0) { + return result.setResultForNull(); + } + + result.setFailure(false); + + // create parser + Tidy tidy = null; + try { + if (log.isDebugEnabled()){ + log.debug("HTMLAssertions.getResult(): Setup tidy ..."); + log.debug("doctype: " + getDoctype()); + log.debug("errors only: " + isErrorsOnly()); + log.debug("error threshold: " + getErrorThreshold()); + log.debug("warning threshold: " + getWarningThreshold()); + log.debug("html mode: " + isHTML()); + log.debug("xhtml mode: " + isXHTML()); + log.debug("xml mode: " + isXML()); + } + tidy = new Tidy(); + tidy.setInputEncoding("UTF8"); + tidy.setOutputEncoding("UTF8"); + tidy.setQuiet(false); + tidy.setShowWarnings(true); + tidy.setOnlyErrors(isErrorsOnly()); + tidy.setDocType(getDoctype()); + if (isXHTML()) { + tidy.setXHTML(true); + } else if (isXML()) { + tidy.setXmlTags(true); + } + tidy.setErrfile(getFilename()); + + if (log.isDebugEnabled()) { + log.debug("err file: " + getFilename()); + log.debug("getParser : tidy parser created - " + tidy); + log.debug("HTMLAssertions.getResult(): Tidy instance created!"); + } + + } catch (Exception e) {//TODO replace with proper Exception + log.error("Unable to instantiate tidy parser", e); + result.setFailure(true); + result.setFailureMessage("Unable to instantiate tidy parser"); + // return with an error + return result; + } + + /* + * Run tidy. + */ + try { + log.debug("HTMLAssertions.getResult(): start parsing with tidy ..."); + + StringWriter errbuf = new StringWriter(); + tidy.setErrout(new PrintWriter(errbuf)); + // Node node = tidy.parseDOM(new + // ByteArrayInputStream(response.getResponseData()), null); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + log.debug("Start : parse"); + Node node = tidy.parse(new ByteArrayInputStream(inResponse.getResponseData()), os); + if (log.isDebugEnabled()) { + log.debug("node : " + node); + log.debug("End : parse"); + log.debug("HTMLAssertions.getResult(): parsing with tidy done!"); + log.debug("Output: " + os.toString()); + } + + // write output to file + writeOutput(errbuf.toString()); + + // evaluate result + if ((tidy.getParseErrors() > getErrorThreshold()) + || (!isErrorsOnly() && (tidy.getParseWarnings() > getWarningThreshold()))) { + if (log.isDebugEnabled()) { + log.debug("HTMLAssertions.getResult(): errors/warnings detected:"); + log.debug(errbuf.toString()); + } + result.setFailure(true); + result.setFailureMessage(MessageFormat.format("Tidy Parser errors: " + tidy.getParseErrors() + + " (allowed " + getErrorThreshold() + ") " + "Tidy Parser warnings: " + + tidy.getParseWarnings() + " (allowed " + getWarningThreshold() + ")", new Object[0])); + // return with an error + + } else if ((tidy.getParseErrors() > 0) || (tidy.getParseWarnings() > 0)) { + // return with no error + log.debug("HTMLAssertions.getResult(): there were errors/warnings but threshold to high"); + result.setFailure(false); + } else { + // return with no error + log.debug("HTMLAssertions.getResult(): no errors/warnings detected:"); + result.setFailure(false); + } + + } catch (Exception e) {//TODO replace with proper Exception + // return with an error + log.warn("Cannot parse result content", e); + result.setFailure(true); + result.setFailureMessage(e.getMessage()); + } + return result; + } + + /** + * Writes the output of tidy to file. + * + * @param inOutput + */ + private void writeOutput(String inOutput) { + String lFilename = getFilename(); + + // check if filename defined + if ((lFilename != null) && (!"".equals(lFilename.trim()))) { + FileWriter lOutputWriter = null; + try { + + // open file + lOutputWriter = new FileWriter(lFilename, false); + + // write to file + lOutputWriter.write(inOutput); + + // flush + lOutputWriter.flush(); + + if (log.isDebugEnabled()) { + log.debug("writeOutput() -> output successfully written to file " + lFilename); + } + + } catch (IOException ex) { + log.warn("writeOutput() -> could not write output to file " + lFilename, ex); + } finally { + // close file + IOUtils.closeQuietly(lOutputWriter); + } + } + } + + /** + * Gets the doctype + * + * @return the documemt type + */ + public String getDoctype() { + return getPropertyAsString(DOCTYPE_KEY); + } + + /** + * Check if errors will be reported only + * + * @return boolean - report errors only? + */ + public boolean isErrorsOnly() { + return getPropertyAsBoolean(ERRORS_ONLY_KEY); + } + + /** + * Gets the threshold setting for errors + * + * @return long error threshold + */ + public long getErrorThreshold() { + return getPropertyAsLong(ERROR_THRESHOLD_KEY); + } + + /** + * Gets the threshold setting for warnings + * + * @return long warning threshold + */ + public long getWarningThreshold() { + return getPropertyAsLong(WARNING_THRESHOLD_KEY); + } + + /** + * Sets the doctype setting + * + * @param inDoctype + */ + public void setDoctype(String inDoctype) { + if ((inDoctype == null) || (inDoctype.trim().equals(""))) { + setProperty(new StringProperty(DOCTYPE_KEY, DEFAULT_DOCTYPE)); + } else { + setProperty(new StringProperty(DOCTYPE_KEY, inDoctype)); + } + } + + /** + * Sets if errors shoud be tracked only + * + * @param inErrorsOnly + */ + public void setErrorsOnly(boolean inErrorsOnly) { + setProperty(new BooleanProperty(ERRORS_ONLY_KEY, inErrorsOnly)); + } + + /** + * Sets the threshold on error level + * + * @param inErrorThreshold + */ + public void setErrorThreshold(long inErrorThreshold) { + if (inErrorThreshold < 0L) { + throw new IllegalArgumentException(JMeterUtils.getResString("argument_must_not_be_negative")); //$NON-NLS-1$ + } + if (inErrorThreshold == Long.MAX_VALUE) { + setProperty(new LongProperty(ERROR_THRESHOLD_KEY, 0)); + } else { + setProperty(new LongProperty(ERROR_THRESHOLD_KEY, inErrorThreshold)); + } + } + + /** + * Sets the threshold on warning level + * + * @param inWarningThreshold + */ + public void setWarningThreshold(long inWarningThreshold) { + if (inWarningThreshold < 0L) { + throw new IllegalArgumentException(JMeterUtils.getResString("argument_must_not_be_negative")); //$NON-NLS-1$ + } + if (inWarningThreshold == Long.MAX_VALUE) { + setProperty(new LongProperty(WARNING_THRESHOLD_KEY, 0)); + } else { + setProperty(new LongProperty(WARNING_THRESHOLD_KEY, inWarningThreshold)); + } + } + + /** + * Enables html validation mode + */ + public void setHTML() { + setProperty(new LongProperty(FORMAT_KEY, 0)); + } + + /** + * Check if html validation mode is set + * + * @return boolean + */ + public boolean isHTML() { + return getPropertyAsLong(FORMAT_KEY) == 0; + } + + /** + * Enables xhtml validation mode + */ + public void setXHTML() { + setProperty(new LongProperty(FORMAT_KEY, 1)); + } + + /** + * Check if xhtml validation mode is set + * + * @return boolean + */ + public boolean isXHTML() { + return getPropertyAsLong(FORMAT_KEY) == 1; + } + + /** + * Enables xml validation mode + */ + public void setXML() { + setProperty(new LongProperty(FORMAT_KEY, 2)); + } + + /** + * Check if xml validation mode is set + * + * @return boolean + */ + public boolean isXML() { + return getPropertyAsLong(FORMAT_KEY) == 2; + } + + /** + * Sets the name of the file where tidy writes the output to + * + * @return name of file + */ + public String getFilename() { + return getPropertyAsString(FILENAME_KEY); + } + + /** + * Sets the name of the tidy output file + * + * @param inName + */ + public void setFilename(String inName) { + setProperty(FILENAME_KEY, inName); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/JSR223Assertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/JSR223Assertion.java new file mode 100644 index 0000000..b8b4859 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/JSR223Assertion.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.assertions; + +import java.io.IOException; + +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223Assertion extends JSR223TestElement implements Cloneable, Assertion, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 234L; + + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + try { + ScriptEngineManager mgr = getManager(); + if (mgr == null) { + result.setFailure(true); + result.setError(true); + result.setFailureMessage("JSR223 Manager not found"); + return result; + } + mgr.put("SampleResult", response); + mgr.put("AssertionResult", result); + processFileOrScript(mgr); + result.setError(false); + } catch (IOException e) { + log.warn("Problem in JSR223 script "+e); + result.setError(true); + result.setFailureMessage(e.toString()); + } catch (ScriptException e) { + log.warn("Problem in JSR223 script "+e); + result.setError(true); + result.setFailureMessage(e.toString()); + } + return result; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/JSR223AssertionBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/assertions/JSR223AssertionBeanInfo.java new file mode 100644 index 0000000..5bddb2d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/JSR223AssertionBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.assertions; + +import org.apache.jmeter.util.JSR223BeanInfoSupport; + +public class JSR223AssertionBeanInfo extends JSR223BeanInfoSupport { + + public JSR223AssertionBeanInfo() { + super(JSR223Assertion.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/MD5HexAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/MD5HexAssertion.java new file mode 100644 index 0000000..3381bd5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/MD5HexAssertion.java @@ -0,0 +1,111 @@ +/* + * 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. + * + */ + +/** + * MD5HexAssertion class creates an MD5 checksum from the response
+ * and matches it with the MD5 hex provided. + * The assertion will fail when the expected hex is different from the
+ * one calculated from the response OR when the expected hex is left empty. + * + */ +package org.apache.jmeter.assertions; + +import java.io.Serializable; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.MessageFormat; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class MD5HexAssertion extends AbstractTestElement implements Serializable, Assertion { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** Key for storing assertion-informations in the jmx-file. */ + private static final String MD5HEX_KEY = "MD5HexAssertion.size"; + + /* + * @param response @return + */ + public AssertionResult getResult(SampleResult response) { + + AssertionResult result = new AssertionResult(getName()); + result.setFailure(false); + byte[] resultData = response.getResponseData(); + + if (resultData.length == 0) { + result.setError(false); + result.setFailure(true); + result.setFailureMessage("Response was null"); + return result; + } + + // no point in checking if we don't have anything to compare against + if (getAllowedMD5Hex().equals("")) { + result.setError(false); + result.setFailure(true); + result.setFailureMessage("MD5Hex to test against is empty"); + return result; + } + + String md5Result = baMD5Hex(resultData); + + // String md5Result = DigestUtils.md5Hex(resultData); + + if (!md5Result.equalsIgnoreCase(getAllowedMD5Hex())) { + result.setFailure(true); + + Object[] arguments = { md5Result, getAllowedMD5Hex() }; + String message = MessageFormat.format(JMeterUtils.getResString("md5hex_assertion_failure"), arguments); // $NON-NLS-1$ + result.setFailureMessage(message); + + } + + return result; + } + + public void setAllowedMD5Hex(String hex) { + setProperty(new StringProperty(MD5HexAssertion.MD5HEX_KEY, hex)); + } + + public String getAllowedMD5Hex() { + return getPropertyAsString(MD5HexAssertion.MD5HEX_KEY); + } + + // package protected so can be accessed by test class + static String baMD5Hex(byte ba[]) { + byte[] md5Result = {}; + + try { + MessageDigest md; + md = MessageDigest.getInstance("MD5"); + md5Result = md.digest(ba); + } catch (NoSuchAlgorithmException e) { + log.error("", e); + } + return JOrphanUtils.baToHexString(md5Result); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/ResponseAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/ResponseAssertion.java new file mode 100644 index 0000000..3d9c933 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/ResponseAssertion.java @@ -0,0 +1,528 @@ +/* + * 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.assertions; + +import java.io.Serializable; +import java.net.URL; +import java.util.ArrayList; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedAssertion; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +// @see org.apache.jmeter.assertions.ResponseAssertionTest for unit tests + +/** + * Test element to handle Response Assertions, @see AssertionGui + */ +public class ResponseAssertion extends AbstractScopedAssertion implements Serializable, Assertion { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private final static String TEST_FIELD = "Assertion.test_field"; // $NON-NLS-1$ + + // Values for TEST_FIELD + // N.B. we cannot change the text value as it is in test plans + private final static String SAMPLE_URL = "Assertion.sample_label"; // $NON-NLS-1$ + + private final static String RESPONSE_DATA = "Assertion.response_data"; // $NON-NLS-1$ + + private final static String RESPONSE_CODE = "Assertion.response_code"; // $NON-NLS-1$ + + private final static String RESPONSE_MESSAGE = "Assertion.response_message"; // $NON-NLS-1$ + + private final static String RESPONSE_HEADERS = "Assertion.response_headers"; // $NON-NLS-1$ + + private final static String ASSUME_SUCCESS = "Assertion.assume_success"; // $NON-NLS-1$ + + private final static String TEST_STRINGS = "Asserion.test_strings"; // $NON-NLS-1$ + + private final static String TEST_TYPE = "Assertion.test_type"; // $NON-NLS-1$ + + /* + * Mask values for TEST_TYPE TODO: remove either MATCH or CONTAINS - they + * are mutually exckusive + */ + private final static int MATCH = 1 << 0; + + private final static int CONTAINS = 1 << 1; + + private final static int NOT = 1 << 2; + + private final static int EQUALS = 1 << 3; + + private final static int SUBSTRING = 1 << 4; + + // Mask should contain all types (but not NOT) + private final static int TYPE_MASK = CONTAINS | EQUALS | MATCH | SUBSTRING; + + private static final int EQUALS_SECTION_DIFF_LEN + = JMeterUtils.getPropDefault("assertion.equals_section_diff_len", 100); + + /** Signifies truncated text in diff display. */ + private static final String EQUALS_DIFF_TRUNC = "..."; + + private static final String RECEIVED_STR = "****** received : "; + private static final String COMPARISON_STR = "****** comparison: "; + private static final String DIFF_DELTA_START + = JMeterUtils.getPropDefault("assertion.equals_diff_delta_start", "[[["); + private static final String DIFF_DELTA_END + = JMeterUtils.getPropDefault("assertion.equals_diff_delta_end", "]]]"); + + public ResponseAssertion() { + setProperty(new CollectionProperty(TEST_STRINGS, new ArrayList())); + } + + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(TEST_STRINGS, new ArrayList())); + } + + private void setTestField(String testField) { + setProperty(TEST_FIELD, testField); + } + + public void setTestFieldURL(){ + setTestField(SAMPLE_URL); + } + + public void setTestFieldResponseCode(){ + setTestField(RESPONSE_CODE); + } + + public void setTestFieldResponseData(){ + setTestField(RESPONSE_DATA); + } + + public void setTestFieldResponseMessage(){ + setTestField(RESPONSE_MESSAGE); + } + + public void setTestFieldResponseHeaders(){ + setTestField(RESPONSE_HEADERS); + } + + public boolean isTestFieldURL(){ + return SAMPLE_URL.equals(getTestField()); + } + + public boolean isTestFieldResponseCode(){ + return RESPONSE_CODE.equals(getTestField()); + } + + public boolean isTestFieldResponseData(){ + return RESPONSE_DATA.equals(getTestField()); + } + + public boolean isTestFieldResponseMessage(){ + return RESPONSE_MESSAGE.equals(getTestField()); + } + + public boolean isTestFieldResponseHeaders(){ + return RESPONSE_HEADERS.equals(getTestField()); + } + + private void setTestType(int testType) { + setProperty(new IntegerProperty(TEST_TYPE, testType)); + } + + private void setTestTypeMasked(int testType) { + int value = getTestType() & ~(TYPE_MASK) | testType; + setProperty(new IntegerProperty(TEST_TYPE, value)); + } + + public void addTestString(String testString) { + getTestStrings().addProperty(new StringProperty(String.valueOf(testString.hashCode()), testString)); + } + + public void clearTestStrings() { + getTestStrings().clear(); + } + + public AssertionResult getResult(SampleResult response) { + AssertionResult result; + + // None of the other Assertions check the response status, so remove + // this check + // for the time being, at least... + // if (!response.isSuccessful()) + // { + // result = new AssertionResult(); + // result.setError(true); + // byte [] ba = response.getResponseData(); + // result.setFailureMessage( + // ba == null ? "Unknown Error (responseData is empty)" : new String(ba) + // ); + // return result; + // } + + result = evaluateResponse(response); + return result; + } + + /*************************************************************************** + * !ToDoo (Method description) + * + * @return !ToDo (Return description) + **************************************************************************/ + public String getTestField() { + return getPropertyAsString(TEST_FIELD); + } + + /*************************************************************************** + * !ToDoo (Method description) + * + * @return !ToDo (Return description) + **************************************************************************/ + public int getTestType() { + JMeterProperty type = getProperty(TEST_TYPE); + if (type instanceof NullProperty) { + return CONTAINS; + } + return type.getIntValue(); + } + + /*************************************************************************** + * !ToDoo (Method description) + * + * @return !ToDo (Return description) + **************************************************************************/ + public CollectionProperty getTestStrings() { + return (CollectionProperty) getProperty(TEST_STRINGS); + } + + public boolean isEqualsType() { + return (getTestType() & EQUALS) != 0; + } + + public boolean isSubstringType() { + return (getTestType() & SUBSTRING) != 0; + } + + public boolean isContainsType() { + return (getTestType() & CONTAINS) != 0; + } + + public boolean isMatchType() { + return (getTestType() & MATCH) != 0; + } + + public boolean isNotType() { + return (getTestType() & NOT) != 0; + } + + public void setToContainsType() { + setTestTypeMasked(CONTAINS); + } + + public void setToMatchType() { + setTestTypeMasked(MATCH); + } + + public void setToEqualsType() { + setTestTypeMasked(EQUALS); + } + + public void setToSubstringType() { + setTestTypeMasked(SUBSTRING); + } + + public void setToNotType() { + setTestType((getTestType() | NOT)); + } + + public void unsetNotType() { + setTestType(getTestType() & ~NOT); + } + + public boolean getAssumeSuccess() { + return getPropertyAsBoolean(ASSUME_SUCCESS, false); + } + + public void setAssumeSuccess(boolean b) { + setProperty(ASSUME_SUCCESS, b); + } + + /** + * Make sure the response satisfies the specified assertion requirements. + * + * @param response + * an instance of SampleResult + * @return an instance of AssertionResult + */ + private AssertionResult evaluateResponse(SampleResult response) { + boolean pass = true; + boolean notTest = (NOT & getTestType()) > 0; + AssertionResult result = new AssertionResult(getName()); + String toCheck = ""; // The string to check (Url or data) + + if (getAssumeSuccess()) { + response.setSuccessful(true);// Allow testing of failure codes + } + + // What are we testing against? + if (isScopeVariable()){ + toCheck = getThreadContext().getVariables().get(getVariableName()); + } else if (isTestFieldResponseData()) { + toCheck = response.getResponseDataAsString(); // (bug25052) + } else if (isTestFieldResponseCode()) { + toCheck = response.getResponseCode(); + } else if (isTestFieldResponseMessage()) { + toCheck = response.getResponseMessage(); + } else if (isTestFieldResponseHeaders()) { + toCheck = response.getResponseHeaders(); + } else { // Assume it is the URL + toCheck = ""; + final URL url = response.getURL(); + if (url != null){ + toCheck = url.toString(); + } + } + + result.setFailure(false); + result.setError(false); + + if (toCheck.length() == 0) { + if (notTest) { + return result; + } + return result.setResultForNull(); + } + + boolean contains = isContainsType(); // do it once outside loop + boolean equals = isEqualsType(); + boolean substring = isSubstringType(); + boolean matches = isMatchType(); + boolean debugEnabled = log.isDebugEnabled(); + if (debugEnabled){ + log.debug("Type:" + (contains?"Contains":"Match") + (notTest? "(not)": "")); + } + + try { + // Get the Matcher for this thread + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + PropertyIterator iter = getTestStrings().iterator(); + while (iter.hasNext()) { + String stringPattern = iter.next().getStringValue(); + Pattern pattern = null; + if (contains || matches) { + pattern = JMeterUtils.getPatternCache().getPattern(stringPattern, Perl5Compiler.READ_ONLY_MASK); + } + boolean found; + if (contains) { + found = localMatcher.contains(toCheck, pattern); + } else if (equals) { + found = toCheck.equals(stringPattern); + } else if (substring) { + found = toCheck.indexOf(stringPattern) != -1; + } else { + found = localMatcher.matches(toCheck, pattern); + } + pass = notTest ? !found : found; + if (!pass) { + if (debugEnabled){log.debug("Failed: "+stringPattern);} + result.setFailure(true); + result.setFailureMessage(getFailText(stringPattern,toCheck)); + break; + } + if (debugEnabled){log.debug("Passed: "+stringPattern);} + } + } catch (MalformedCachePatternException e) { + result.setError(true); + result.setFailure(false); + result.setFailureMessage("Bad test configuration " + e); + } + return result; + } + + /** + * Generate the failure reason from the TestType + * + * @param stringPattern + * @return the message for the assertion report + */ + // TODO strings should be resources + private String getFailText(String stringPattern, String toCheck) { + + StringBuilder sb = new StringBuilder(200); + sb.append("Test failed: "); + + if (isScopeVariable()){ + sb.append("variable(").append(getVariableName()).append(')'); + } else if (isTestFieldResponseData()) { + sb.append("text"); + } else if (isTestFieldResponseCode()) { + sb.append("code"); + } else if (isTestFieldResponseMessage()) { + sb.append("message"); + } else if (isTestFieldResponseHeaders()) { + sb.append("headers"); + } else // Assume it is the URL + { + sb.append("URL"); + } + + switch (getTestType()) { + case CONTAINS: + case SUBSTRING: + sb.append(" expected to contain "); + break; + case NOT | CONTAINS: + case NOT | SUBSTRING: + sb.append(" expected not to contain "); + break; + case MATCH: + sb.append(" expected to match "); + break; + case NOT | MATCH: + sb.append(" expected not to match "); + break; + case EQUALS: + sb.append(" expected to equal "); + break; + case NOT | EQUALS: + sb.append(" expected not to equal "); + break; + default:// should never happen... + sb.append(" expected something using "); + } + + sb.append("/"); + + if (isEqualsType()){ + sb.append(equalsComparisonText(toCheck, stringPattern)); + } else { + sb.append(stringPattern); + } + + sb.append("/"); + + return sb.toString(); + } + + + private static String trunc(final boolean right, final String str) + { + if (str.length() <= EQUALS_SECTION_DIFF_LEN) { + return str; + } else if (right) { + return str.substring(0, EQUALS_SECTION_DIFF_LEN) + EQUALS_DIFF_TRUNC; + } else { + return EQUALS_DIFF_TRUNC + str.substring(str.length() - EQUALS_SECTION_DIFF_LEN, str.length()); + } + } + + /** + * Returns some helpful logging text to determine where equality between two strings + * is broken, with one pointer working from the front of the strings and another working + * backwards from the end. + * + * @param received String received from sampler. + * @param comparison String specified for "equals" response assertion. + * @return Two lines of text separated by newlines, and then forward and backward pointers + * denoting first position of difference. + */ + private static StringBuilder equalsComparisonText(final String received, final String comparison) + { + int firstDiff; + int lastRecDiff = -1; + int lastCompDiff = -1; + final int recLength = received.length(); + final int compLength = comparison.length(); + final int minLength = Math.min(recLength, compLength); + final String startingEqSeq; + String recDeltaSeq = ""; + String compDeltaSeq = ""; + String endingEqSeq = ""; + + final StringBuilder text = new StringBuilder(Math.max(recLength, compLength) * 2); + for (firstDiff = 0; firstDiff < minLength; firstDiff++) { + if (received.charAt(firstDiff) != comparison.charAt(firstDiff)){ + break; + } + } + if (firstDiff == 0) { + startingEqSeq = ""; + } else { + startingEqSeq = trunc(false, received.substring(0, firstDiff)); + } + + lastRecDiff = recLength - 1; + lastCompDiff = compLength - 1; + + while ((lastRecDiff > firstDiff) && (lastCompDiff > firstDiff) + && received.charAt(lastRecDiff) == comparison.charAt(lastCompDiff)) + { + lastRecDiff--; + lastCompDiff--; + } + endingEqSeq = trunc(true, received.substring(lastRecDiff + 1, recLength)); + if (endingEqSeq.length() == 0) + { + recDeltaSeq = trunc(true, received.substring(firstDiff, recLength)); + compDeltaSeq = trunc(true, comparison.substring(firstDiff, compLength)); + } + else + { + recDeltaSeq = trunc(true, received.substring(firstDiff, lastRecDiff + 1)); + compDeltaSeq = trunc(true, comparison.substring(firstDiff, lastCompDiff + 1)); + } + final StringBuilder pad = new StringBuilder(Math.abs(recDeltaSeq.length() - compDeltaSeq.length())); + for (int i = 0; i < pad.capacity(); i++){ + pad.append(' '); + } + if (recDeltaSeq.length() > compDeltaSeq.length()){ + compDeltaSeq += pad.toString(); + } else { + recDeltaSeq += pad.toString(); + } + + text.append("\n\n"); + text.append(RECEIVED_STR); + text.append(startingEqSeq); + text.append(DIFF_DELTA_START); + text.append(recDeltaSeq); + text.append(DIFF_DELTA_END); + text.append(endingEqSeq); + text.append("\n\n"); + text.append(COMPARISON_STR); + text.append(startingEqSeq); + text.append(DIFF_DELTA_START); + text.append(compDeltaSeq); + text.append(DIFF_DELTA_END); + text.append(endingEqSeq); + text.append("\n\n"); + return text; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/SMIMEAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/SMIMEAssertion.java new file mode 100644 index 0000000..6e377b5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/SMIMEAssertion.java @@ -0,0 +1,354 @@ +/* + * 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.assertions; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.Security; +import java.security.cert.CertStore; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; + +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import javax.security.auth.x500.X500Principal; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.X509Name; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.SignerInformation; +import org.bouncycastle.cms.SignerInformationStore; +import org.bouncycastle.jce.PrincipalUtil; +import org.bouncycastle.jce.X509Principal; +import org.bouncycastle.jce.cert.CertSelector; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.mail.smime.SMIMEException; +import org.bouncycastle.mail.smime.SMIMESignedParser; +import org.bouncycastle.x509.extension.X509ExtensionUtil; + +/** + * Helper class which isolates the BouncyCastle code. + */ +class SMIMEAssertion { + + // Use the name of the test element, otherwise cannot enable/disable debug from the GUI + private static final Logger log = LoggingManager.getLoggerForShortName(SMIMEAssertionTestElement.class.getName()); + + SMIMEAssertion() { + super(); + } + + public static AssertionResult getResult(SMIMEAssertionTestElement testElement, SampleResult response, String name) { + checkForBouncycastle(); + AssertionResult res = new AssertionResult(name); + try { + MimeMessage msg = null; + final int msgPos = testElement.getSpecificMessagePositionAsInt(); + if (msgPos < 0){ // means counting from end + SampleResult subResults[] = response.getSubResults(); + final int pos = subResults.length + msgPos; + log.debug("Getting message number: "+pos+" of "+subResults.length); + msg = getMessageFromResponse(response,pos); + } else { + log.debug("Getting message number: "+msgPos); + msg = getMessageFromResponse(response, msgPos); + } + + SMIMESignedParser s = null; + if (log.isDebugEnabled()) { + log.debug("Content-type: "+msg.getContentType()); + } + if (msg.isMimeType("multipart/signed")) { // $NON-NLS-1$ + MimeMultipart multipart = (MimeMultipart) msg.getContent(); + s = new SMIMESignedParser(multipart); + } else if (msg.isMimeType("application/pkcs7-mime") // $NON-NLS-1$ + || msg.isMimeType("application/x-pkcs7-mime")) { // $NON-NLS-1$ + s = new SMIMESignedParser(msg); + } + + if (null != s) { + log.debug("Found signature"); + + if (testElement.isNotSigned()) { + res.setFailure(true); + res.setFailureMessage("Mime message is signed"); + } else if (testElement.isVerifySignature() || !testElement.isSignerNoCheck()) { + res = verifySignature(testElement, s, name); + } + + } else { + log.debug("Did not find signature"); + if (!testElement.isNotSigned()) { + res.setFailure(true); + res.setFailureMessage("Mime message is not signed"); + } + } + + } catch (MessagingException e) { + String msg = "Cannot parse mime msg: " + e.getMessage(); + log.warn(msg, e); + res.setFailure(true); + res.setFailureMessage(msg); + } catch (CMSException e) { + res.setFailure(true); + res.setFailureMessage("Error reading the signature: " + + e.getMessage()); + } catch (SMIMEException e) { + res.setFailure(true); + res + .setFailureMessage("Cannot extract signed body part from signature: " + + e.getMessage()); + } catch (IOException e) { // should never happen + log.error("Cannot read mime message content: " + e.getMessage(), e); + res.setError(true); + res.setFailureMessage(e.getMessage()); + } + + return res; + } + + private static AssertionResult verifySignature(SMIMEAssertionTestElement testElement, SMIMESignedParser s, String name) + throws CMSException { + AssertionResult res = new AssertionResult(name); + + try { + org.bouncycastle.jce.cert.CertStore certs = s.getCertificatesAndCRLs("Collection", "BC"); // $NON-NLS-1$ // $NON-NLS-2$ //tms changed to compile + + SignerInformationStore signers = s.getSignerInfos(); + Iterator signerIt = signers.getSigners().iterator(); + + if (signerIt.hasNext()) { + + SignerInformation signer = (SignerInformation) signerIt.next(); + Iterator certIt = certs.getCertificates((CertSelector) signer.getSID()).iterator();//tms changed to compile + + if (certIt.hasNext()) { + // the signer certificate + X509Certificate cert = (X509Certificate) certIt.next(); + + if (testElement.isVerifySignature()) { + + if (!signer.verify(cert.getPublicKey(), "BC")) { // $NON-NLS-1$ + res.setFailure(true); + res.setFailureMessage("Signature is invalid"); + } + } + + if (testElement.isSignerCheckConstraints()) { + StringBuilder failureMessage = new StringBuilder(); + + String serial = testElement.getSignerSerial(); + if (serial.trim().length() > 0) { + BigInteger serialNbr = readSerialNumber(serial); + if (!serialNbr.equals(cert.getSerialNumber())) { + res.setFailure(true); + failureMessage + .append("Serial number ") + .append(serialNbr) + .append(" does not match serial from signer certificate: ") + .append(cert.getSerialNumber()).append("\n"); + } + } + + String email = testElement.getSignerEmail(); + if (email.trim().length() > 0) { + List emailfromCert = getEmailFromCert(cert); + if (!emailfromCert.contains(email)) { + res.setFailure(true); + failureMessage + .append("Email address \"") + .append(email) + .append("\" not present in signer certificate\n"); + } + + } + + String subject = testElement.getSignerDn(); + if (subject.length() > 0) { + final X500Principal certPrincipal = cert.getSubjectX500Principal(); + log.debug(certPrincipal.getName(X500Principal.CANONICAL)); + X500Principal principal = new X500Principal(subject); + log.debug(principal.getName(X500Principal.CANONICAL)); + if (!principal.equals(certPrincipal)) { + res.setFailure(true); + failureMessage + .append("Distinguished name of signer certificate does not match \"") + .append(subject).append("\"\n"); + } + } + + String issuer = testElement.getIssuerDn(); + if (issuer.length() > 0) { + final X500Principal issuerX500Principal = cert.getIssuerX500Principal(); + log.debug(issuerX500Principal.getName(X500Principal.CANONICAL)); + X500Principal principal = new X500Principal(issuer); + log.debug(principal.getName(X500Principal.CANONICAL)); + if (!principal.equals(issuerX500Principal)) { + res.setFailure(true); + failureMessage + .append("Issuer distinguished name of signer certificate does not match \"") + .append(subject).append("\"\n"); + } + } + + if (failureMessage.length() > 0) { + res.setFailureMessage(failureMessage.toString()); + } + } + + if (testElement.isSignerCheckByFile()) { + CertificateFactory cf = CertificateFactory + .getInstance("X.509"); + X509Certificate certFromFile; + FileInputStream inStream = null; + try { + inStream = new FileInputStream(testElement.getSignerCertFile()); + certFromFile = (X509Certificate) cf.generateCertificate(inStream); + } finally { + IOUtils.closeQuietly(inStream); + } + + if (!certFromFile.equals(cert)) { + res.setFailure(true); + res.setFailureMessage("Signer certificate does not match certificate " + + testElement.getSignerCertFile()); + } + } + + } else { + res.setFailure(true); + res.setFailureMessage("No signer certificate found in signature"); + } + + } + + // TODO support multiple signers + if (signerIt.hasNext()) { + log.warn("SMIME message contains multiple signers! Checking multiple signers is not supported."); + } + + } catch (GeneralSecurityException e) { + log.error(e.getMessage(), e); + res.setError(true); + res.setFailureMessage(e.getMessage()); + } catch (FileNotFoundException e) { + res.setFailure(true); + res.setFailureMessage("certificate file not found: " + e.getMessage()); + } + + return res; + } + + /** + * extracts a MIME message from the SampleResult + */ + private static MimeMessage getMessageFromResponse(SampleResult response, + int messageNumber) throws MessagingException { + SampleResult subResults[] = response.getSubResults(); + + if (messageNumber >= subResults.length || messageNumber < 0) { + throw new MessagingException("Message number not present in results: "+messageNumber); + } + + final SampleResult sampleResult = subResults[messageNumber]; + if (log.isDebugEnabled()) { + log.debug("Bytes: "+sampleResult.getBytes()+" CT: "+sampleResult.getContentType()); + } + byte[] data = sampleResult.getResponseData(); + Session session = Session.getDefaultInstance(new Properties()); + MimeMessage msg = new MimeMessage(session, new ByteArrayInputStream(data)); + + log.debug("msg.getSize() = " + msg.getSize()); + return msg; + } + + /** + * Convert the value of serialString into a BigInteger. Strings + * starting with 0x or 0X are parsed as hex numbers, otherwise as decimal + * number. + * + * @param serialString + * the String representation of the serial Number + * @return + */ + private static BigInteger readSerialNumber(String serialString) { + if (serialString.startsWith("0x") || serialString.startsWith("0X")) { // $NON-NLS-1$ // $NON-NLS-2$ + return new BigInteger(serialString.substring(2), 16); + } + return new BigInteger(serialString); + } + + /** + * Extract email addresses from a certificate + * + * @param cert + * @return a List of all email addresses found + * @throws CertificateException + */ + private static List getEmailFromCert(X509Certificate cert) + throws CertificateException { + List res = new ArrayList(); + + X509Principal subject = PrincipalUtil.getSubjectX509Principal(cert); + Iterator addressIt = subject.getValues(X509Name.EmailAddress).iterator(); + while (addressIt.hasNext()) { + String address = (String) addressIt.next(); + res.add(address); + } + + Iterator subjectAltNamesIt = + X509ExtensionUtil.getSubjectAlternativeNames(cert).iterator(); + while (subjectAltNamesIt.hasNext()) { + List altName = (List) subjectAltNamesIt.next(); + int type = ((Integer) altName.get(0)).intValue(); + if (type == GeneralName.rfc822Name) { + String address = (String) altName.get(1); + res.add(address); + } + } + + return res; + } + + /** + * Check if the Bouncycastle jce provider is installed and dynamically load + * it, if needed; + * + */ + private static void checkForBouncycastle() { + if (null == Security.getProvider("BC")) { // $NON-NLS-1$ + Security.addProvider(new BouncyCastleProvider()); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/SMIMEAssertionTestElement.java b/ApacheJmeter/src/org/apache/jmeter/assertions/SMIMEAssertionTestElement.java new file mode 100644 index 0000000..b2e344f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/SMIMEAssertionTestElement.java @@ -0,0 +1,153 @@ +/* + * 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.assertions; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class SMIMEAssertionTestElement extends AbstractTestElement implements + Serializable, Assertion { + + private static final long serialVersionUID = 1L; + + //+JMX file attributes - do not change values! + private static final String VERIFY_SIGNATURE_KEY = "SMIMEAssert.verifySignature"; // $NON-NLS-1$ + private static final String NOT_SIGNED_KEY = "SMIMEAssert.notSigned"; // $NON-NLS-1$ + private static final String SIGNER_NO_CHECK_KEY = "SMIMEAssert.signerNoCheck"; // $NON-NLS-1$ + private static final String SIGNER_CHECK_BY_FILE_KEY = "SMIMEAssert.signerCheckByFile"; // $NON-NLS-1$ + private static final String SIGNER_CERT_FILE_KEY = "SMIMEAssert.signerCertFile"; // $NON-NLS-1$ + private static final String SINGER_CHECK_CONSTRAINTS_KEY = "SMIMEAssert.signerCheckConstraints"; // $NON-NLS-1$ + private static final String SIGNER_SERIAL_KEY = "SMIMEAssert.signerSerial"; // $NON-NLS-1$ + private static final String SIGNER_EMAIL_KEY = "SMIMEAssert.signerEmail"; // $NON-NLS-1$ + private static final String SIGNER_DN_KEY = "SMIMEAssert.signerDn"; // $NON-NLS-1$ + private static final String ISSUER_DN_KEY = "SMIMEAssert.issuerDn"; // $NON-NLS-1$ + private static final String MESSAGE_POSITION = "SMIMEAssert.messagePosition"; // $NON-NLS-1$ + //-JMX file attributes + + public SMIMEAssertionTestElement() { + super(); + } + + public AssertionResult getResult(SampleResult response) { + try { + return SMIMEAssertion.getResult(this, response, getName()); + } catch (NoClassDefFoundError e) { + AssertionResult assertionResult = new AssertionResult(getName()); + assertionResult.setError(true); + assertionResult.setResultForFailure(JMeterUtils + .getResString("bouncy_castle_unavailable_message")); + return assertionResult; + } + } + + public boolean isVerifySignature() { + return getPropertyAsBoolean(VERIFY_SIGNATURE_KEY); + } + + public void setVerifySignature(boolean verifySignature) { + setProperty(VERIFY_SIGNATURE_KEY, verifySignature); + } + + public String getIssuerDn() { + return getPropertyAsString(ISSUER_DN_KEY); + } + + public void setIssuerDn(String issuertDn) { + setProperty(ISSUER_DN_KEY, issuertDn); + } + + public boolean isSignerCheckByFile() { + return getPropertyAsBoolean(SIGNER_CHECK_BY_FILE_KEY); + } + + public void setSignerCheckByFile(boolean signerCheckByFile) { + setProperty(SIGNER_CHECK_BY_FILE_KEY, signerCheckByFile); + } + + public boolean isSignerCheckConstraints() { + return getPropertyAsBoolean(SINGER_CHECK_CONSTRAINTS_KEY); + } + + public void setSignerCheckConstraints(boolean signerCheckConstraints) { + setProperty(SINGER_CHECK_CONSTRAINTS_KEY, signerCheckConstraints); + } + + public boolean isSignerNoCheck() { + return getPropertyAsBoolean(SIGNER_NO_CHECK_KEY); + } + + public void setSignerNoCheck(boolean signerNoCheck) { + setProperty(SIGNER_NO_CHECK_KEY, signerNoCheck); + } + + public String getSignerCertFile() { + return getPropertyAsString(SIGNER_CERT_FILE_KEY); + } + + public void setSignerCertFile(String signerCertFile) { + setProperty(SIGNER_CERT_FILE_KEY, signerCertFile); + } + + public String getSignerDn() { + return getPropertyAsString(SIGNER_DN_KEY); + } + + public void setSignerDn(String signerDn) { + setProperty(SIGNER_DN_KEY, signerDn); + } + + public String getSignerSerial() { + return getPropertyAsString(SIGNER_SERIAL_KEY); + } + + public void setSignerSerial(String signerSerial) { + setProperty(SIGNER_SERIAL_KEY, signerSerial); + } + + public String getSignerEmail() { + return getPropertyAsString(SIGNER_EMAIL_KEY); + } + + public void setSignerEmail(String signerEmail) { + setProperty(SIGNER_EMAIL_KEY, signerEmail); + } + + public boolean isNotSigned() { + return getPropertyAsBoolean(NOT_SIGNED_KEY); + } + + public void setNotSigned(boolean notSigned) { + setProperty(NOT_SIGNED_KEY, notSigned); + } + + public String getSpecificMessagePosition() { + return getPropertyAsString(MESSAGE_POSITION); + } + + public int getSpecificMessagePositionAsInt() { + return getPropertyAsInt(MESSAGE_POSITION, 0); + } + + public void setSpecificMessagePosition(String position) { + setProperty(MESSAGE_POSITION, position); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/SizeAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/SizeAssertion.java new file mode 100644 index 0000000..a58daee --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/SizeAssertion.java @@ -0,0 +1,240 @@ +/* + * 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.assertions; + +import java.io.Serializable; +import java.text.MessageFormat; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedAssertion; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.util.JMeterUtils; + +//@see org.apache.jmeter.assertions.SizeAssertionTest for unit tests + +/** + * Checks if the results of a Sample matches a particular size. + * + */ +public class SizeAssertion extends AbstractScopedAssertion implements Serializable, Assertion { + + private static final long serialVersionUID = 241L; + + // * Static int to signify the type of logical comparitor to assert + public final static int EQUAL = 1; + + public final static int NOTEQUAL = 2; + + public final static int GREATERTHAN = 3; + + public final static int LESSTHAN = 4; + + public final static int GREATERTHANEQUAL = 5; + + public final static int LESSTHANEQUAL = 6; + + /** Key for storing assertion-informations in the jmx-file. */ + private static final String SIZE_KEY = "SizeAssertion.size"; // $NON-NLS-1$ + + private static final String OPERATOR_KEY = "SizeAssertion.operator"; // $NON-NLS-1$ + + private final static String TEST_FIELD = "Assertion.test_field"; // $NON-NLS-1$ + + private final static String RESPONSE_NETWORK_SIZE = "SizeAssertion.response_network_size"; // $NON-NLS-1$ + + private final static String RESPONSE_HEADERS = "SizeAssertion.response_headers"; // $NON-NLS-1$ + + private final static String RESPONSE_BODY = "SizeAssertion.response_data"; // $NON-NLS-1$ + + private final static String RESPONSE_CODE = "SizeAssertion.response_code"; // $NON-NLS-1$ + + private final static String RESPONSE_MESSAGE = "SizeAssertion.response_message"; // $NON-NLS-1$ + + /** + * Returns the result of the Assertion. + * Here it checks the Sample responseData length. + */ + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + result.setFailure(false); + long resultSize=0; + if (isScopeVariable()){ + String variableName = getVariableName(); + String value = getThreadContext().getVariables().get(variableName); + try { + resultSize = Integer.parseInt(value); + } catch (NumberFormatException e) { + result.setFailure(true); + result.setFailureMessage("Error parsing variable name: "+variableName+" value: "+value); + return result; + } + } else if (isTestFieldResponseHeaders()) { + resultSize = response.getHeadersSize(); + } else if (isTestFieldResponseBody()) { + resultSize = response.getBodySize(); + } else if (isTestFieldResponseCode()) { + resultSize = response.getResponseCode().length(); + } else if (isTestFieldResponseMessage()) { + resultSize = response.getResponseMessage().length(); + } else { + resultSize = response.getBytes(); + } + // is the Sample the correct size? + final String msg = compareSize(resultSize); + if (msg.length() > 0) { + result.setFailure(true); + Object[] arguments = { Long.valueOf(resultSize), msg, Long.valueOf(getAllowedSize()) }; + String message = MessageFormat.format(JMeterUtils.getResString("size_assertion_failure"), arguments); //$NON-NLS-1$ + result.setFailureMessage(message); + } + return result; + } + + /** + * Returns the size in bytes to be asserted. + */ + public String getAllowedSize() { + return getPropertyAsString(SIZE_KEY); + } + + /*************************************************************************** + * set the Operator + **************************************************************************/ + public void setCompOper(int operator) { + setProperty(new IntegerProperty(OPERATOR_KEY, operator)); + + } + + /** + * Returns the operator to be asserted. EQUAL = 1, NOTEQUAL = 2 GREATERTHAN = + * 3,LESSTHAN = 4,GREATERTHANEQUAL = 5,LESSTHANEQUAL = 6 + */ + + public int getCompOper() { + return getPropertyAsInt(OPERATOR_KEY); + } + + /** + * Set the size that shall be asserted. + * + * @param size a number of bytes. + */ + public void setAllowedSize(String size) { + setProperty(SIZE_KEY, size); + } + + public void setAllowedSize(long size) { + setProperty(SIZE_KEY, Long.toString(size)); + } + + /** + * Compares the the size of a return result to the set allowed size using a + * logical comparator set in setLogicalComparator(). + * + * Possible values are: equal, not equal, greater than, less than, greater + * than eqaul, less than equal, . + * + */ + private String compareSize(long resultSize) { + String comparatorErrorMessage; + long allowedSize = Long.parseLong(getAllowedSize()); + boolean result = false; + int comp = getCompOper(); + switch (comp) { + case EQUAL: + result = (resultSize == allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_equal"); //$NON-NLS-1$ + break; + case NOTEQUAL: + result = (resultSize != allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_notequal"); //$NON-NLS-1$ + break; + case GREATERTHAN: + result = (resultSize > allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_greater"); //$NON-NLS-1$ + break; + case LESSTHAN: + result = (resultSize < allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_less"); //$NON-NLS-1$ + break; + case GREATERTHANEQUAL: + result = (resultSize >= allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_greaterequal"); //$NON-NLS-1$ + break; + case LESSTHANEQUAL: + result = (resultSize <= allowedSize); + comparatorErrorMessage = JMeterUtils.getResString("size_assertion_comparator_error_lessequal"); //$NON-NLS-1$ + break; + default: + result = false; + comparatorErrorMessage = "ERROR - invalid condition"; + break; + } + return result ? "" : comparatorErrorMessage; + } + + private void setTestField(String testField) { + setProperty(TEST_FIELD, testField); + } + + public void setTestFieldNetworkSize(){ + setTestField(RESPONSE_NETWORK_SIZE); + } + + public void setTestFieldResponseHeaders(){ + setTestField(RESPONSE_HEADERS); + } + + public void setTestFieldResponseBody(){ + setTestField(RESPONSE_BODY); + } + + public void setTestFieldResponseCode(){ + setTestField(RESPONSE_CODE); + } + + public void setTestFieldResponseMessage(){ + setTestField(RESPONSE_MESSAGE); + } + + public String getTestField() { + return getPropertyAsString(TEST_FIELD); + } + + public boolean isTestFieldNetworkSize(){ + return RESPONSE_NETWORK_SIZE.equals(getTestField()); + } + + public boolean isTestFieldResponseHeaders(){ + return RESPONSE_HEADERS.equals(getTestField()); + } + + public boolean isTestFieldResponseBody(){ + return RESPONSE_BODY.equals(getTestField()); + } + + public boolean isTestFieldResponseCode(){ + return RESPONSE_CODE.equals(getTestField()); + } + + public boolean isTestFieldResponseMessage(){ + return RESPONSE_MESSAGE.equals(getTestField()); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/SubstitutionElement.java b/ApacheJmeter/src/org/apache/jmeter/assertions/SubstitutionElement.java new file mode 100644 index 0000000..d07c0d0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/SubstitutionElement.java @@ -0,0 +1,55 @@ +/* + * 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.assertions; + +import org.apache.jmeter.testelement.AbstractTestElement; + +public class SubstitutionElement extends AbstractTestElement { + private static final long serialVersionUID = 1; + + // These constants are used both for the JMX file and for the setters/getters + public static final String REGEX = "regex"; // $NON-NLS-1$ + + public static final String SUBSTITUTE = "substitute"; // $NON-NLS-1$ + + public SubstitutionElement() { + super(); + } + + public String getRegex() + { + return getProperty(REGEX).getStringValue(); + } + + public void setRegex(String regex) + { + setProperty(REGEX,regex); + } + + public String getSubstitute() + { + return getProperty(SUBSTITUTE).getStringValue(); + } + + public void setSubstitute(String sub) + { + setProperty(SUBSTITUTE,sub); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/XMLAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/XMLAssertion.java new file mode 100644 index 0000000..f6e5325 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/XMLAssertion.java @@ -0,0 +1,111 @@ +/* + * 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.assertions; + +import java.io.IOException; +import java.io.Serializable; +import java.io.StringReader; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; + +/** + * Checks if the result is a well-formed XML content using jdom + * + */ +public class XMLAssertion extends AbstractTestElement implements Serializable, Assertion { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final char NEW_LINE = '\n'; // $NON-NLS-1$ + + // one builder for all requests in a thread + private static final ThreadLocal myBuilder = new ThreadLocal() { + @Override + protected SAXBuilder initialValue() { + return new SAXBuilder(); + } + }; + + /** + * Returns the result of the Assertion. Here it checks wether the Sample + * took to long to be considered successful. If so an AssertionResult + * containing a FailureMessage will be returned. Otherwise the returned + * AssertionResult will reflect the success of the Sample. + */ + public AssertionResult getResult(SampleResult response) { + // no error as default + AssertionResult result = new AssertionResult(getName()); + byte[] responseData = response.getResponseData(); + if (responseData.length == 0) { + return result.setResultForNull(); + } + result.setFailure(false); + + // the result data + String resultData = new String(getResultBody(responseData)); // TODO - charset? + + SAXBuilder builder = myBuilder.get(); + + try { + builder.build(new StringReader(resultData)); + } catch (JDOMException e) { + log.debug("Cannot parse result content", e); // may well happen + result.setFailure(true); + result.setFailureMessage(e.getMessage()); + } catch (IOException e) { + log.error("Cannot read result content", e); // should never happen + result.setError(true); + result.setFailureMessage(e.getMessage()); + } + + return result; + } + + /** + * Return the body of the http return. + */ + private byte[] getResultBody(byte[] resultData) { + for (int i = 0; i < (resultData.length - 1); i++) { + if (resultData[i] == NEW_LINE && resultData[i + 1] == NEW_LINE) { + return getByteArraySlice(resultData, (i + 2), resultData.length - 1); + } + } + return resultData; + } + + /** + * Return a slice of a byte array + */ + private byte[] getByteArraySlice(byte[] array, int begin, int end) { + byte[] slice = new byte[(end - begin + 1)]; + int count = 0; + for (int i = begin; i <= end; i++) { + slice[count] = array[i]; + count++; + } + + return slice; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/XMLSchemaAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/XMLSchemaAssertion.java new file mode 100644 index 0000000..0c070b5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/XMLSchemaAssertion.java @@ -0,0 +1,210 @@ +/* + * 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.assertions; + +import java.io.IOException; +import java.io.Serializable; +import java.io.StringReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +// See Bug 34383 + +/** + * XMLSchemaAssertion.java Validate response against an XML Schema author Dave Maung + * + */ +public class XMLSchemaAssertion extends AbstractTestElement implements Serializable, Assertion { + + private static final long serialVersionUID = 233L; + + public static final String FILE_NAME_IS_REQUIRED = "FileName is required"; + + public static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; + + public static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; + + public static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource"; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String XSD_FILENAME_KEY = "xmlschema_assertion_filename"; + + /** + * getResult + * + */ + public AssertionResult getResult(SampleResult response) { + AssertionResult result = new AssertionResult(getName()); + // Note: initialised with error = failure = false + + String resultData = response.getResponseDataAsString(); + if (resultData.length() == 0) { + return result.setResultForNull(); + } + + String xsdFileName = getXsdFileName(); + if (log.isDebugEnabled()) { + log.debug("xmlString: " + resultData); + log.debug("xsdFileName: " + xsdFileName); + } + if (xsdFileName == null || xsdFileName.length() == 0) { + result.setResultForFailure(FILE_NAME_IS_REQUIRED); + } else { + setSchemaResult(result, resultData, xsdFileName); + } + return result; + } + + public void setXsdFileName(String xmlSchemaFileName) throws IllegalArgumentException { + setProperty(XSD_FILENAME_KEY, xmlSchemaFileName); + } + + public String getXsdFileName() { + return getPropertyAsString(XSD_FILENAME_KEY); + } + + /** + * set Schema result + * + * @param result + * @param xmlStr + * @param xsdFileName + */ + private void setSchemaResult(AssertionResult result, String xmlStr, String xsdFileName) { + try { + // boolean toReturn = true; + + // Document doc = null; + DocumentBuilderFactory parserFactory = DocumentBuilderFactory.newInstance(); + parserFactory.setValidating(true); + parserFactory.setNamespaceAware(true); + parserFactory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA); + parserFactory.setAttribute(JAXP_SCHEMA_SOURCE, xsdFileName); + + // create a parser: + DocumentBuilder parser = parserFactory.newDocumentBuilder(); + parser.setErrorHandler(new SAXErrorHandler(result)); + + // doc = + parser.parse(new InputSource(new StringReader(xmlStr))); + // if everything went fine then xml schema validation is valid + } catch (SAXParseException e) { + + // Only set message if error not yet flagged + if (!result.isError() && !result.isFailure()) { + result.setError(true); + result.setFailureMessage(errorDetails(e)); + } + + } catch (SAXException e) { + + log.warn(e.toString()); + result.setResultForFailure(e.getMessage()); + + } catch (IOException e) { + + log.warn("IO error", e); + result.setResultForFailure(e.getMessage()); + + } catch (ParserConfigurationException e) { + + log.warn("Problem with Parser Config", e); + result.setResultForFailure(e.getMessage()); + + } + + } + + // Helper method to construct SAX error details + private static String errorDetails(SAXParseException spe) { + StringBuilder str = new StringBuilder(80); + int i; + i = spe.getLineNumber(); + if (i != -1) { + str.append("line="); + str.append(i); + str.append(" col="); + str.append(spe.getColumnNumber()); + str.append(" "); + } + str.append(spe.getLocalizedMessage()); + return str.toString(); + } + + /** + * SAXErrorHandler class + */ + private static class SAXErrorHandler implements ErrorHandler { + private AssertionResult result; + + public SAXErrorHandler(AssertionResult result) { + this.result = result; + } + + /* + * Can be caused by: - failure to read XSD file - xml does not match XSD + */ + public void error(SAXParseException exception) throws SAXParseException { + + String msg = "error: " + errorDetails(exception); + log.debug(msg); + result.setFailureMessage(msg); + result.setError(true); + throw exception; + } + + /* + * Can be caused by: - premature end of file - non-whitespace content + * after trailer + */ + public void fatalError(SAXParseException exception) throws SAXParseException { + + String msg = "fatal: " + errorDetails(exception); + log.debug(msg); + result.setFailureMessage(msg); + result.setError(true); + throw exception; + } + + /* + * Not clear what can cause this ? conflicting versions perhaps + */ + public void warning(SAXParseException exception) throws SAXParseException { + + String msg = "warning: " + errorDetails(exception); + log.debug(msg); + result.setFailureMessage(msg); + // result.setError(true); // TODO is this the correct strategy? + // throw exception; // allow assertion to pass + + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/XPathAssertion.java b/ApacheJmeter/src/org/apache/jmeter/assertions/XPathAssertion.java new file mode 100644 index 0000000..b9c488a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/XPathAssertion.java @@ -0,0 +1,261 @@ +/* + * 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.assertions; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.Serializable; + +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.TidyException; +import org.apache.jmeter.util.XPathUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * Checks if the result is a well-formed XML content and whether it matches an + * XPath + * + */ +public class XPathAssertion extends AbstractTestElement implements Serializable, Assertion { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + //+ JMX file attributes + private static final String XPATH_KEY = "XPath.xpath"; // $NON-NLS-1$ + private static final String WHITESPACE_KEY = "XPath.whitespace"; // $NON-NLS-1$ + private static final String VALIDATE_KEY = "XPath.validate"; // $NON-NLS-1$ + private static final String TOLERANT_KEY = "XPath.tolerant"; // $NON-NLS-1$ + private static final String NEGATE_KEY = "XPath.negate"; // $NON-NLS-1$ + private static final String NAMESPACE_KEY = "XPath.namespace"; // $NON-NLS-1$ + private static final String QUIET_KEY = "XPath.quiet"; // $NON-NLS-1$ + private static final String REPORT_ERRORS_KEY = "XPath.report_errors"; // $NON-NLS-1$ + private static final String SHOW_WARNINGS_KEY = "XPath.show_warnings"; // $NON-NLS-1$ + private static final String DOWNLOAD_DTDS = "XPath.download_dtds"; // $NON-NLS-1$ + //- JMX file attributes + + public static final String DEFAULT_XPATH = "/"; + + /** + * Returns the result of the Assertion. Checks if the result is well-formed + * XML, and that the XPath expression is matched (or not, as the case may + * be) + */ + public AssertionResult getResult(SampleResult response) { + // no error as default + AssertionResult result = new AssertionResult(getName()); + byte[] responseData = response.getResponseData(); + if (responseData.length == 0) { + return result.setResultForNull(); + } + result.setFailure(false); + result.setFailureMessage(""); + + if (log.isDebugEnabled()) { + log.debug(new StringBuilder("Validation is set to ").append(isValidating()).toString()); + log.debug(new StringBuilder("Whitespace is set to ").append(isWhitespace()).toString()); + log.debug(new StringBuilder("Tolerant is set to ").append(isTolerant()).toString()); + } + + Document doc = null; + + boolean isXML = JOrphanUtils.isXML(responseData); + + try { + doc = XPathUtil.makeDocument(new ByteArrayInputStream(responseData), isValidating(), + isWhitespace(), isNamespace(), isTolerant(), isQuiet(), showWarnings() , reportErrors(), isXML + , isDownloadDTDs()); + } catch (SAXException e) { + log.debug("Caught sax exception: " + e); + result.setError(true); + result.setFailureMessage(new StringBuilder("SAXException: ").append(e.getMessage()).toString()); + return result; + } catch (IOException e) { + log.warn("Cannot parse result content", e); + result.setError(true); + result.setFailureMessage(new StringBuilder("IOException: ").append(e.getMessage()).toString()); + return result; + } catch (ParserConfigurationException e) { + log.warn("Cannot parse result content", e); + result.setError(true); + result.setFailureMessage(new StringBuilder("ParserConfigurationException: ").append(e.getMessage()) + .toString()); + return result; + } catch (TidyException e) { + result.setError(true); + result.setFailureMessage(e.getMessage()); + return result; + } + + if (doc == null || doc.getDocumentElement() == null) { + result.setError(true); + result.setFailureMessage("Document is null, probably not parsable"); + return result; + } + XPathUtil.computeAssertionResult(result, doc, getXPathString(), isNegated()); + return result; + } + + /** + * Get The XPath String that will be used in matching the document + * + * @return String xpath String + */ + public String getXPathString() { + return getPropertyAsString(XPATH_KEY, DEFAULT_XPATH); + } + + /** + * Set the XPath String this will be used as an xpath + * + * @param xpath + * String + */ + public void setXPathString(String xpath) { + setProperty(new StringProperty(XPATH_KEY, xpath)); + } + + /** + * Set whether to ignore element whitespace + * + * @param whitespace + */ + public void setWhitespace(boolean whitespace) { + setProperty(new BooleanProperty(WHITESPACE_KEY, whitespace)); + } + + /** + * Set use validation + * + * @param validate + */ + public void setValidating(boolean validate) { + setProperty(new BooleanProperty(VALIDATE_KEY, validate)); + } + + /** + * Set whether this is namespace aware + * + * @param namespace + */ + public void setNamespace(boolean namespace) { + setProperty(new BooleanProperty(NAMESPACE_KEY, namespace)); + } + + /** + * Set tolerant mode if required + * + * @param tolerant + * true/false + */ + public void setTolerant(boolean tolerant) { + setProperty(new BooleanProperty(TOLERANT_KEY, tolerant)); + } + + public void setNegated(boolean negate) { + setProperty(new BooleanProperty(NEGATE_KEY, negate)); + } + + /** + * Is this whitepsace ignored. + * + * @return boolean + */ + public boolean isWhitespace() { + return getPropertyAsBoolean(WHITESPACE_KEY, false); + } + + /** + * Is this validating + * + * @return boolean + */ + public boolean isValidating() { + return getPropertyAsBoolean(VALIDATE_KEY, false); + } + + /** + * Is this namespace aware? + * + * @return boolean + */ + public boolean isNamespace() { + return getPropertyAsBoolean(NAMESPACE_KEY, false); + } + + /** + * Is this using tolerant mode? + * + * @return boolean + */ + public boolean isTolerant() { + return getPropertyAsBoolean(TOLERANT_KEY, false); + } + + /** + * Negate the XPath test, that is return true if something is not found. + * + * @return boolean negated + */ + public boolean isNegated() { + return getPropertyAsBoolean(NEGATE_KEY, false); + } + + public void setReportErrors(boolean val) { + setProperty(REPORT_ERRORS_KEY, val, false); + } + + public boolean reportErrors() { + return getPropertyAsBoolean(REPORT_ERRORS_KEY, false); + } + + public void setShowWarnings(boolean val) { + setProperty(SHOW_WARNINGS_KEY, val, false); + } + + public boolean showWarnings() { + return getPropertyAsBoolean(SHOW_WARNINGS_KEY, false); + } + + public void setQuiet(boolean val) { + setProperty(QUIET_KEY, val, true); + } + + public boolean isQuiet() { + return getPropertyAsBoolean(QUIET_KEY, true); + } + + public void setDownloadDTDs(boolean val) { + setProperty(DOWNLOAD_DTDS, val, false); + } + + public boolean isDownloadDTDs() { + return getPropertyAsBoolean(DOWNLOAD_DTDS, false); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/AbstractAssertionGui.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/AbstractAssertionGui.java new file mode 100644 index 0000000..6e0f277 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/AbstractAssertionGui.java @@ -0,0 +1,53 @@ +/* + * 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.assertions.gui; + +import java.util.Arrays; +import java.util.Collection; + + +import org.apache.jmeter.gui.AbstractScopedJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which manage assertions. + * + * Assertions which can be applied to different scopes (parent, children or both) + * need to use the createScopePanel() to add the panel to the GUI, and they also + * need to use saveScopeSettings() and showScopeSettings() to keep the test element + * and GUI in synch. + * + */ +public abstract class AbstractAssertionGui extends AbstractScopedJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#ASSERTIONS}, which is + * appropriate for most assertion components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.ASSERTIONS }); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/AssertionGui.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/AssertionGui.java new file mode 100644 index 0000000..5b999c9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/AssertionGui.java @@ -0,0 +1,421 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTable; + +import org.apache.jmeter.assertions.ResponseAssertion; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.PowerTableModel; +import org.apache.jmeter.gui.util.TextAreaCellRenderer; +import org.apache.jmeter.gui.util.TextAreaTableCellEditor; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; + +/** + * GUI interface for a {@link ResponseAssertion}. + * + */ +public class AssertionGui extends AbstractAssertionGui { + private static final long serialVersionUID = 240L; + + /** The name of the table column in the list of patterns. */ + private static final String COL_RESOURCE_NAME = "assertion_patterns_to_test"; //$NON-NLS-1$ + + /** Radio button indicating that the text response should be tested. */ + private JRadioButton responseStringButton; + + /** Radio button indicating that the URL should be tested. */ + private JRadioButton urlButton; + + /** Radio button indicating that the responseMessage should be tested. */ + private JRadioButton responseMessageButton; + + /** Radio button indicating that the responseCode should be tested. */ + private JRadioButton responseCodeButton; + + /** Radio button indicating that the headers should be tested. */ + private JRadioButton responseHeadersButton; + + /** + * Checkbox to indicate whether the response should be forced successful + * before testing. This is intended for use when checking the status code or + * status message. + */ + private JCheckBox assumeSuccess; + + /** + * Radio button indicating to test if the field contains one of the + * patterns. + */ + private JRadioButton containsBox; + + /** + * Radio button indicating to test if the field matches one of the patterns. + */ + private JRadioButton matchesBox; + + /** + * Radio button indicating if the field equals the string. + */ + private JRadioButton equalsBox; + + /** + * Radio button indicating if the field contains the string. + */ + private JRadioButton substringBox; + + /** + * Checkbox indicating to test that the field does NOT contain/match the + * patterns. + */ + private JCheckBox notBox; + + /** A table of patterns to test against. */ + private JTable stringTable; + + /** Button to add a new pattern. */ + private JButton addPattern; + + /** Button to delete a pattern. */ + private JButton deletePattern; + + /** Table model for the pattern table. */ + private PowerTableModel tableModel; + + /** + * Create a new AssertionGui panel. + */ + public AssertionGui() { + init(); + } + + public String getLabelResource() { + return "assertion_title"; // $NON-NLS-1$ + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + ResponseAssertion el = new ResponseAssertion(); + modifyTestElement(el); + return el; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement el) { + configureTestElement(el); + if (el instanceof ResponseAssertion) { + ResponseAssertion ra = (ResponseAssertion) el; + + saveScopeSettings(ra); + + ra.clearTestStrings(); + String[] testStrings = tableModel.getData().getColumn(COL_RESOURCE_NAME); + for (int i = 0; i < testStrings.length; i++) { + ra.addTestString(testStrings[i]); + } + + if (responseStringButton.isSelected()) { + ra.setTestFieldResponseData(); + } else if (responseCodeButton.isSelected()) { + ra.setTestFieldResponseCode(); + } else if (responseMessageButton.isSelected()) { + ra.setTestFieldResponseMessage(); + } else if (responseHeadersButton.isSelected()) { + ra.setTestFieldResponseHeaders(); + } else { // Assume URL + ra.setTestFieldURL(); + } + + ra.setAssumeSuccess(assumeSuccess.isSelected()); + + if (containsBox.isSelected()) { + ra.setToContainsType(); + } else if (equalsBox.isSelected()) { + ra.setToEqualsType(); + } else if (substringBox.isSelected()) { + ra.setToSubstringType(); + } else { + ra.setToMatchType(); + } + + if (notBox.isSelected()) { + ra.setToNotType(); + } else { + ra.unsetNotType(); + } + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + tableModel.clearData(); + + responseStringButton.setSelected(true); + urlButton.setSelected(false); + responseCodeButton.setSelected(false); + responseMessageButton.setSelected(false); + responseHeadersButton.setSelected(false); + assumeSuccess.setSelected(false); + + containsBox.setSelected(true); + notBox.setSelected(false); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + ResponseAssertion model = (ResponseAssertion) el; + + showScopeSettings(model, true); + + if (model.isContainsType()) { + containsBox.setSelected(true); + } else if (model.isEqualsType()) { + equalsBox.setSelected(true); + } else if (model.isSubstringType()) { + substringBox.setSelected(true); + } else { + matchesBox.setSelected(true); + } + + notBox.setSelected(model.isNotType()); + + if (model.isTestFieldResponseData()) { + responseStringButton.setSelected(true); + } else if (model.isTestFieldResponseCode()) { + responseCodeButton.setSelected(true); + } else if (model.isTestFieldResponseMessage()) { + responseMessageButton.setSelected(true); + } else if (model.isTestFieldResponseHeaders()) { + responseHeadersButton.setSelected(true); + } else // Assume it is the URL + { + urlButton.setSelected(true); + } + + assumeSuccess.setSelected(model.getAssumeSuccess()); + + tableModel.clearData(); + PropertyIterator tests = model.getTestStrings().iterator(); + while (tests.hasNext()) { + tableModel.addRow(new Object[] { tests.next().getStringValue() }); + } + + if (model.getTestStrings().size() == 0) { + deletePattern.setEnabled(false); + } else { + deletePattern.setEnabled(true); + } + + tableModel.fireTableDataChanged(); + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout()); + Box box = Box.createVerticalBox(); + setBorder(makeBorder()); + + box.add(makeTitlePanel()); + box.add(createScopePanel(true)); + box.add(createFieldPanel()); + box.add(createTypePanel()); + add(box, BorderLayout.NORTH); + add(createStringPanel(), BorderLayout.CENTER); + } + + /** + * Create a panel allowing the user to choose which response field should be + * tested. + * + * @return a new panel for selecting the response field + */ + private JPanel createFieldPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("assertion_resp_field"))); //$NON-NLS-1$ + + responseStringButton = new JRadioButton(JMeterUtils.getResString("assertion_text_resp")); //$NON-NLS-1$ + urlButton = new JRadioButton(JMeterUtils.getResString("assertion_url_samp")); //$NON-NLS-1$ + responseCodeButton = new JRadioButton(JMeterUtils.getResString("assertion_code_resp")); //$NON-NLS-1$ + responseMessageButton = new JRadioButton(JMeterUtils.getResString("assertion_message_resp")); //$NON-NLS-1$ + responseHeadersButton = new JRadioButton(JMeterUtils.getResString("assertion_headers")); //$NON-NLS-1$ + + ButtonGroup group = new ButtonGroup(); + group.add(responseStringButton); + group.add(urlButton); + group.add(responseCodeButton); + group.add(responseMessageButton); + group.add(responseHeadersButton); + + panel.add(responseStringButton); + panel.add(urlButton); + panel.add(responseCodeButton); + panel.add(responseMessageButton); + panel.add(responseHeadersButton); + + responseStringButton.setSelected(true); + + assumeSuccess = new JCheckBox(JMeterUtils.getResString("assertion_assume_success")); //$NON-NLS-1$ + panel.add(assumeSuccess); + + return panel; + } + + /** + * Create a panel allowing the user to choose what type of test should be + * performed. + * + * @return a new panel for selecting the type of assertion test + */ + private JPanel createTypePanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("assertion_pattern_match_rules"))); //$NON-NLS-1$ + + ButtonGroup group = new ButtonGroup(); + + containsBox = new JRadioButton(JMeterUtils.getResString("assertion_contains")); //$NON-NLS-1$ + group.add(containsBox); + containsBox.setSelected(true); + panel.add(containsBox); + + matchesBox = new JRadioButton(JMeterUtils.getResString("assertion_matches")); //$NON-NLS-1$ + group.add(matchesBox); + panel.add(matchesBox); + + equalsBox = new JRadioButton(JMeterUtils.getResString("assertion_equals")); //$NON-NLS-1$ + group.add(equalsBox); + panel.add(equalsBox); + + substringBox = new JRadioButton(JMeterUtils.getResString("assertion_substring")); //$NON-NLS-1$ + group.add(substringBox); + panel.add(substringBox); + + notBox = new JCheckBox(JMeterUtils.getResString("assertion_not")); //$NON-NLS-1$ + panel.add(notBox); + + return panel; + } + + /** + * Create a panel allowing the user to supply a list of string patterns to + * test against. + * + * @return a new panel for adding string patterns + */ + private JPanel createStringPanel() { + tableModel = new PowerTableModel(new String[] { COL_RESOURCE_NAME }, new Class[] { String.class }); + stringTable = new JTable(tableModel); + stringTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + + TextAreaCellRenderer renderer = new TextAreaCellRenderer(); + stringTable.setRowHeight(renderer.getPreferredHeight()); + stringTable.setDefaultRenderer(String.class, renderer); + stringTable.setDefaultEditor(String.class, new TextAreaTableCellEditor()); + stringTable.setPreferredScrollableViewportSize(new Dimension(100, 70)); + + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("assertion_patterns_to_test"))); //$NON-NLS-1$ + + panel.add(new JScrollPane(stringTable), BorderLayout.CENTER); + panel.add(createButtonPanel(), BorderLayout.SOUTH); + + return panel; + } + + /** + * Create a panel with buttons to add and delete string patterns. + * + * @return the new panel with add and delete buttons + */ + private JPanel createButtonPanel() { + addPattern = new JButton(JMeterUtils.getResString("add")); //$NON-NLS-1$ + addPattern.addActionListener(new AddPatternListener()); + + deletePattern = new JButton(JMeterUtils.getResString("delete")); //$NON-NLS-1$ + deletePattern.addActionListener(new ClearPatternsListener()); + deletePattern.setEnabled(false); + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(addPattern); + buttonPanel.add(deletePattern); + return buttonPanel; + } + + /** + * An ActionListener for deleting a pattern. + * + */ + private class ClearPatternsListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + int index = stringTable.getSelectedRow(); + if (index > -1) { + stringTable.getCellEditor(index, stringTable.getSelectedColumn()).cancelCellEditing(); + tableModel.removeRow(index); + tableModel.fireTableDataChanged(); + } + if (stringTable.getModel().getRowCount() == 0) { + deletePattern.setEnabled(false); + } + } + } + + /** + * An ActionListener for adding a pattern. + * + */ + private class AddPatternListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + tableModel.addNewRow(); + deletePattern.setEnabled(true); + tableModel.fireTableDataChanged(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/BeanShellAssertionGui.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/BeanShellAssertionGui.java new file mode 100644 index 0000000..ada6ee7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/BeanShellAssertionGui.java @@ -0,0 +1,168 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import org.apache.jmeter.assertions.BeanShellAssertion; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.util.JMeterUtils; + +public class BeanShellAssertionGui extends AbstractAssertionGui { + + private static final long serialVersionUID = 240L; + + private JCheckBox resetInterpreter;// reset the bsh.Interpreter before each execution + + private JTextField filename;// script file name (if present) + + private JTextField parameters;// parameters to pass to script file (or script) + + private JTextArea scriptField;// script area + + public BeanShellAssertionGui() { + init(); + } + + @Override + public void configure(TestElement element) { + scriptField.setText(element.getPropertyAsString(BeanShellAssertion.SCRIPT)); + filename.setText(element.getPropertyAsString(BeanShellAssertion.FILENAME)); + parameters.setText(element.getPropertyAsString(BeanShellAssertion.PARAMETERS)); + resetInterpreter.setSelected(element.getPropertyAsBoolean(BeanShellAssertion.RESET_INTERPRETER)); + super.configure(element); + } + + public TestElement createTestElement() { + BeanShellAssertion sampler = new BeanShellAssertion(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement te) { + te.clear(); + this.configureTestElement(te); + te.setProperty(BeanShellAssertion.SCRIPT, scriptField.getText()); + te.setProperty(BeanShellAssertion.FILENAME, filename.getText()); + te.setProperty(BeanShellAssertion.PARAMETERS, parameters.getText()); + te.setProperty(new BooleanProperty(BeanShellAssertion.RESET_INTERPRETER, resetInterpreter.isSelected())); + } + + public String getLabelResource() { + return "bsh_assertion_title"; // $NON-NLS-1$ + } + + private JPanel createFilenamePanel()// TODO ought to be a FileChooser ... + { + JLabel label = new JLabel(JMeterUtils.getResString("bsh_script_file")); //$NON-NLS-1$ + + filename = new JTextField(10); + filename.setName(BeanShellAssertion.FILENAME); + label.setLabelFor(filename); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(filename, BorderLayout.CENTER); + return filenamePanel; + } + + private JPanel createResetPanel() { + resetInterpreter = new JCheckBox(JMeterUtils.getResString("bsh_script_reset_interpreter")); // $NON-NLS-1$ + resetInterpreter.setName(BeanShellAssertion.PARAMETERS); + + JPanel resetInterpreterPanel = new JPanel(new BorderLayout()); + resetInterpreterPanel.add(resetInterpreter, BorderLayout.WEST); + return resetInterpreterPanel; + } + + private JPanel createParameterPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("bsh_script_parameters")); //$NON-NLS-1$ + + parameters = new JTextField(10); + parameters.setName(BeanShellAssertion.PARAMETERS); + label.setLabelFor(parameters); + + JPanel parameterPanel = new JPanel(new BorderLayout(5, 0)); + parameterPanel.add(label, BorderLayout.WEST); + parameterPanel.add(parameters, BorderLayout.CENTER); + return parameterPanel; + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createResetPanel()); + box.add(createParameterPanel()); + box.add(createFilenamePanel()); + add(box, BorderLayout.NORTH); + + JPanel panel = createScriptPanel(); + add(panel, BorderLayout.CENTER); + // Don't let the input field shrink too much + add(Box.createVerticalStrut(panel.getPreferredSize().height), BorderLayout.WEST); + } + + private JPanel createScriptPanel() { + scriptField = new JTextArea(); + scriptField.setRows(4); + scriptField.setLineWrap(true); + scriptField.setWrapStyleWord(true); + + JLabel label = new JLabel(JMeterUtils.getResString("bsh_assertion_script")); //$NON-NLS-1$ + label.setLabelFor(scriptField); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(label, BorderLayout.NORTH); + panel.add(new JScrollPane(scriptField), BorderLayout.CENTER); + + JTextArea explain = new JTextArea(JMeterUtils.getResString("bsh_assertion_script_variables")); //$NON-NLS-1$ + explain.setLineWrap(true); + explain.setEditable(false); + explain.setBackground(this.getBackground()); + panel.add(explain, BorderLayout.SOUTH); + + return panel; + } + + @Override + public void clearGui() { + super.clearGui(); + filename.setText(""); // $NON-NLS-1$ + parameters.setText(""); // $NON-NLS-1$ + scriptField.setText(""); // $NON-NLS-1$ + resetInterpreter.setSelected(false); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/DurationAssertionGui.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/DurationAssertionGui.java new file mode 100644 index 0000000..5889bfa --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/DurationAssertionGui.java @@ -0,0 +1,121 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.assertions.DurationAssertion; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * GUI for {@link DurationAssertion} + */ +public class DurationAssertionGui extends AbstractAssertionGui { + + private static final long serialVersionUID = 240L; + + private JTextField duration; + + public DurationAssertionGui() { + init(); + } + + public String getLabelResource() { + return "duration_assertion_title"; // $NON-NLS-1$ + } + + public String getDurationAttributesTitle() { + return JMeterUtils.getResString("duration_assertion_duration_test"); // $NON-NLS-1$ + } + + public TestElement createTestElement() { + DurationAssertion el = new DurationAssertion(); + modifyTestElement(el); + return el; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement el) { + configureTestElement(el); + if (el instanceof DurationAssertion) { + DurationAssertion assertion = (DurationAssertion) el; + assertion.setProperty(DurationAssertion.DURATION_KEY,duration.getText()); + saveScopeSettings(assertion); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + duration.setText(""); //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof DurationAssertion){ + DurationAssertion da = (DurationAssertion) el; + duration.setText(da.getPropertyAsString(DurationAssertion.DURATION_KEY)); + showScopeSettings(da); + } + } + + private void init() { + setLayout(new BorderLayout(0, 10)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new VerticalPanel(); + mainPanel.add(createScopePanel()); + + // USER_INPUT + VerticalPanel durationPanel = new VerticalPanel(); + durationPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + getDurationAttributesTitle())); + + JPanel labelPanel = new JPanel(new BorderLayout(5, 0)); + JLabel durationLabel = + new JLabel(JMeterUtils.getResString("duration_assertion_label")); // $NON-NLS-1$ + labelPanel.add(durationLabel, BorderLayout.WEST); + + duration = new JTextField(); + labelPanel.add(duration, BorderLayout.CENTER); + durationLabel.setLabelFor(duration); + durationPanel.add(labelPanel); + + mainPanel.add(durationPanel); + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/HTMLAssertionGui.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/HTMLAssertionGui.java new file mode 100644 index 0000000..041e2e3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/HTMLAssertionGui.java @@ -0,0 +1,363 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import org.apache.jmeter.assertions.HTMLAssertion; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * GUI for HTMLAssertion + */ +public class HTMLAssertionGui extends AbstractAssertionGui implements KeyListener, ActionListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 1L; + + // Names for the fields + private static final String WARNING_THRESHOLD_FIELD = "warningThresholdField"; // $NON-NLS-1$ + + private static final String ERROR_THRESHOLD_FIELD = "errorThresholdField"; // $NON-NLS-1$ + + // instance attributes + private JTextField errorThresholdField = null; + + private JTextField warningThresholdField = null; + + private JCheckBox errorsOnly = null; + + private JComboBox docTypeBox = null; + + private JRadioButton htmlRadioButton = null; + + private JRadioButton xhtmlRadioButton = null; + + private JRadioButton xmlRadioButton = null; + + private FilePanel filePanel = null; + + /** + * The constructor. + */ + public HTMLAssertionGui() { + init(); + } + + /** + * Returns the label to be shown within the JTree-Component. + */ + public String getLabelResource() { + return "html_assertion_title"; // $NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + HTMLAssertion el = new HTMLAssertion(); + modifyTestElement(el); + return el; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement inElement) { + + log.debug("HTMLAssertionGui.modifyTestElement() called"); + + configureTestElement(inElement); + + String errorThresholdString = errorThresholdField.getText(); + long errorThreshold = 0; + + try { + errorThreshold = Long.parseLong(errorThresholdString); + } catch (NumberFormatException e) { + errorThreshold = 0; + } + ((HTMLAssertion) inElement).setErrorThreshold(errorThreshold); + + String warningThresholdString = warningThresholdField.getText(); + long warningThreshold = 0; + try { + warningThreshold = Long.parseLong(warningThresholdString); + } catch (NumberFormatException e) { + warningThreshold = 0; + } + ((HTMLAssertion) inElement).setWarningThreshold(warningThreshold); + + String docTypeString = docTypeBox.getSelectedItem().toString(); + ((HTMLAssertion) inElement).setDoctype(docTypeString); + + boolean trackErrorsOnly = errorsOnly.isSelected(); + ((HTMLAssertion) inElement).setErrorsOnly(trackErrorsOnly); + + if (htmlRadioButton.isSelected()) { + ((HTMLAssertion) inElement).setHTML(); + } else if (xhtmlRadioButton.isSelected()) { + ((HTMLAssertion) inElement).setXHTML(); + } else { + ((HTMLAssertion) inElement).setXML(); + } + ((HTMLAssertion) inElement).setFilename(filePanel.getFilename()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + docTypeBox.setSelectedIndex(0); + htmlRadioButton.setSelected(true); + xhtmlRadioButton.setSelected(false); + xmlRadioButton.setSelected(false); + errorThresholdField.setText("0"); //$NON-NLS-1$ + warningThresholdField.setText("0"); //$NON-NLS-1$ + filePanel.setFilename(""); //$NON-NLS-1$ + errorsOnly.setSelected(false); + } + + /** + * Configures the associated test element. + * + * @param inElement + */ + @Override + public void configure(TestElement inElement) { + super.configure(inElement); + HTMLAssertion lAssertion = (HTMLAssertion) inElement; + errorThresholdField.setText(String.valueOf(lAssertion.getErrorThreshold())); + warningThresholdField.setText(String.valueOf(lAssertion.getWarningThreshold())); + errorsOnly.setSelected(lAssertion.isErrorsOnly()); + docTypeBox.setSelectedItem(lAssertion.getDoctype()); + if (lAssertion.isHTML()) { + htmlRadioButton.setSelected(true); + } else if (lAssertion.isXHTML()) { + xhtmlRadioButton.setSelected(true); + } else { + xmlRadioButton.setSelected(true); + } + if (lAssertion.isErrorsOnly()) { + warningThresholdField.setEnabled(false); + warningThresholdField.setEditable(false); + } + else { + warningThresholdField.setEnabled(true); + warningThresholdField.setEditable(true); + } + filePanel.setFilename(lAssertion.getFilename()); + } + + /** + * Inits the GUI. + */ + private void init() { + + setLayout(new BorderLayout(0, 10)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + // USER_INPUT + VerticalPanel assertionPanel = new VerticalPanel(); + assertionPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Tidy Settings")); + + // doctype + HorizontalPanel docTypePanel = new HorizontalPanel(); + docTypeBox = new JComboBox(new Object[] { "omit", "auto", "strict", "loose" }); + // docTypePanel.add(new + // JLabel(JMeterUtils.getResString("duration_assertion_label"))); //$NON-NLS-1$ + docTypePanel.add(new JLabel("Doctype:")); + docTypePanel.add(docTypeBox); + assertionPanel.add(docTypePanel); + + // format (HMTL, XHTML, XML) + VerticalPanel formatPanel = new VerticalPanel(); + formatPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Format")); + htmlRadioButton = new JRadioButton("HTML", true); //$NON-NLS-1$ + xhtmlRadioButton = new JRadioButton("XHTML", false); //$NON-NLS-1$ + xmlRadioButton = new JRadioButton("XML", false); //$NON-NLS-1$ + ButtonGroup buttonGroup = new ButtonGroup(); + buttonGroup.add(htmlRadioButton); + buttonGroup.add(xhtmlRadioButton); + buttonGroup.add(xmlRadioButton); + formatPanel.add(htmlRadioButton); + formatPanel.add(xhtmlRadioButton); + formatPanel.add(xmlRadioButton); + assertionPanel.add(formatPanel); + + // errors only + errorsOnly = new JCheckBox("Errors only", false); + errorsOnly.addActionListener(this); + assertionPanel.add(errorsOnly); + + // thresholds + HorizontalPanel thresholdPanel = new HorizontalPanel(); + thresholdPanel.add(new JLabel("Error threshold:")); + errorThresholdField = new JTextField("0", 5); // $NON-NLS-1$ + errorThresholdField.setName(ERROR_THRESHOLD_FIELD); + errorThresholdField.addKeyListener(this); + thresholdPanel.add(errorThresholdField); + thresholdPanel.add(new JLabel("Warning threshold:")); + warningThresholdField = new JTextField("0", 5); // $NON-NLS-1$ + warningThresholdField.setName(WARNING_THRESHOLD_FIELD); + warningThresholdField.addKeyListener(this); + thresholdPanel.add(warningThresholdField); + assertionPanel.add(thresholdPanel); + + // file panel + filePanel = new FilePanel(JMeterUtils.getResString("html_assertion_file"), ".txt"); //$NON-NLS-1$ //$NON-NLS-2$ + assertionPanel.add(filePanel); + + mainPanel.add(assertionPanel, BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } + + /** + * This method is called if one of the threshold field looses the focus + * + * @param inEvent + */ + public void focusLost(FocusEvent inEvent) { + log.debug("HTMLAssertionGui.focusLost() called"); + + String errorThresholdString = errorThresholdField.getText(); + if (errorThresholdString != null) { + boolean isInvalid = false; + try { + long errorThreshold = Long.parseLong(errorThresholdString); + if (errorThreshold < 0) { + isInvalid = true; + } + } catch (NumberFormatException ex) { + isInvalid = true; + } + if (isInvalid) { + log.warn("HTMLAssertionGui: Error threshold Not a valid number!"); + JOptionPane.showMessageDialog(null, "Threshold for errors is invalid", "Error", + JOptionPane.ERROR_MESSAGE); + } + } + + String warningThresholdString = warningThresholdField.getText(); + if (warningThresholdString != null) { + boolean isInvalid = false; + try { + long warningThreshold = Long.parseLong(warningThresholdString); + if (warningThreshold < 0) { + isInvalid = true; + } + } catch (NumberFormatException ex) { + isInvalid = true; + } + if (isInvalid) { + log.warn("HTMLAssertionGui: Error threshold Not a valid number!"); + JOptionPane.showMessageDialog(null, "Threshold for warnings is invalid", "Error", + JOptionPane.ERROR_MESSAGE); + } + } + } + + /** + * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent) + */ + public void focusGained(FocusEvent e) { + // NOOP + } + + /** + * This method is called from erros-only checkbox + * + * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + public void actionPerformed(ActionEvent e) { + if (errorsOnly.isSelected()) { + warningThresholdField.setEnabled(false); + warningThresholdField.setEditable(false); + } else { + warningThresholdField.setEnabled(true); + warningThresholdField.setEditable(true); + } + } + + public void keyPressed(KeyEvent e) { + // NOOP + } + + public void keyReleased(KeyEvent e) { + String fieldName = e.getComponent().getName(); + + if (fieldName.equals(WARNING_THRESHOLD_FIELD)) { + validateInteger(warningThresholdField); + } + + if (fieldName.equals(ERROR_THRESHOLD_FIELD)) { + validateInteger(errorThresholdField); + } + } + + private void validateInteger(JTextField field){ + try { + Integer.parseInt(field.getText()); + } catch (NumberFormatException nfe) { + int length = field.getText().length(); + if (length > 0) { + JOptionPane.showMessageDialog(this, "Only digits allowed", "Invalid data", + JOptionPane.WARNING_MESSAGE); + // Drop the last character: + field.setText(field.getText().substring(0, length-1)); + } + } + + } + public void keyTyped(KeyEvent e) { + // NOOP + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/MD5HexAssertionGUI.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/MD5HexAssertionGUI.java new file mode 100644 index 0000000..0ca6c94 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/MD5HexAssertionGUI.java @@ -0,0 +1,116 @@ +/* + * 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. + * + */ + +/** + * GUI class supporting the MD5Hex assertion functionality. + * + */ +package org.apache.jmeter.assertions.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.assertions.MD5HexAssertion; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class MD5HexAssertionGUI extends AbstractAssertionGui { + + private static final long serialVersionUID = 240L; + + private JTextField md5HexInput; + + public MD5HexAssertionGUI() { + init(); + } + + private void init() { + + setLayout(new BorderLayout(0, 10)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + // USER_INPUT + HorizontalPanel md5HexPanel = new HorizontalPanel(); + md5HexPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("md5hex_assertion_md5hex_test"))); // $NON-NLS-1$ + + md5HexPanel.add(new JLabel(JMeterUtils.getResString("md5hex_assertion_label"))); //$NON-NLS-1$ + + md5HexInput = new JTextField(25); + // md5HexInput.addFocusListener(this); + md5HexPanel.add(md5HexInput); + + mainPanel.add(md5HexPanel, BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + + } + + @Override + public void configure(TestElement el) { + super.configure(el); + MD5HexAssertion assertion = (MD5HexAssertion) el; + this.md5HexInput.setText(String.valueOf(assertion.getAllowedMD5Hex())); + } + + public String getLabelResource() { + return "md5hex_assertion_title"; // $NON-NLS-1$ + } + + /* + * @return + */ + public TestElement createTestElement() { + + MD5HexAssertion el = new MD5HexAssertion(); + modifyTestElement(el); + return el; + + } + + /* + * @param element + */ + public void modifyTestElement(TestElement element) { + configureTestElement(element); + String md5HexString = this.md5HexInput.getText(); + // initialize to empty string, this will fail the assertion + if (md5HexString == null || md5HexString.length() == 0) { + md5HexString = ""; + } + ((MD5HexAssertion) element).setAllowedMD5Hex(md5HexString); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + md5HexInput.setText(""); //$NON-NLS-1$ + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/SMIMEAssertionGui.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/SMIMEAssertionGui.java new file mode 100644 index 0000000..2316435 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/SMIMEAssertionGui.java @@ -0,0 +1,244 @@ + /* + * 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.assertions.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.assertions.SMIMEAssertionTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + + public class SMIMEAssertionGui extends AbstractAssertionGui { + + private static final long serialVersionUID = 1L; + + private final JCheckBox verifySignature = + new JCheckBox(JMeterUtils.getResString("smime_assertion_verify_signature")); // $NON-NLS-1$ + + private final JCheckBox notSigned = + new JCheckBox(JMeterUtils.getResString("smime_assertion_not_signed")); // $NON-NLS-1$ + + private final JRadioButton signerNoCheck = + new JRadioButton(JMeterUtils.getResString("smime_assertion_signer_no_check")); // $NON-NLS-1$ + + private final JRadioButton signerCheckConstraints = + new JRadioButton(JMeterUtils.getResString("smime_assertion_signer_constraints")); // $NON-NLS-1$ + + private final JRadioButton signerCheckByFile = + new JRadioButton(JMeterUtils.getResString("smime_assertion_signer_by_file")); // $NON-NLS-1$ + + private final JTextField signerDnField = new JTextField(50); + + private final JTextField signerSerialNumberField = new JTextField(25); + + private final JTextField signerEmailField = new JTextField(25); + + private final JTextField issuerDnField = new JTextField(50); + + private final JTextField signerCertFile = new JTextField(25); + + private final JTextField messagePositionTf = new JTextField(25); + + public SMIMEAssertionGui() { + init(); + } + + public String getLabelResource() { + return "smime_assertion_title"; + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + issuerDnField.setText(""); + messagePositionTf.setText(""); + notSigned.setSelected(false); + signerCertFile.setText(""); + signerCheckByFile.setSelected(false); + signerCheckConstraints.setSelected(false); + signerDnField.setText(""); + signerEmailField.setText(""); + signerNoCheck.setSelected(false); + signerSerialNumberField.setText(""); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createSignaturePanel()); + box.add(createSignerPanel()); + box.add(createMessagePositionPanel()); + add(box, BorderLayout.NORTH); + } + + private JPanel createSignaturePanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils + .getResString("smime_assertion_signature"))); // $NON-NLS-1$ + notSigned.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + verifySignature.setEnabled(!notSigned.isSelected()); + } + }); + + panel.add(verifySignature); + panel.add(notSigned); + + return panel; + } + + private JPanel createSignerPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils + .getResString("smime_assertion_signer"))); // $NON-NLS-1$ + + panel.setLayout(new VerticalLayout(5, VerticalLayout.LEFT)); + + ButtonGroup buttonGroup = new ButtonGroup(); + buttonGroup.add(signerNoCheck); + buttonGroup.add(signerCheckConstraints); + buttonGroup.add(signerCheckByFile); + + panel.add(signerNoCheck); + + panel.add(signerCheckConstraints); + signerCheckConstraints.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + signerDnField.setEnabled(signerCheckConstraints.isSelected()); + signerSerialNumberField.setEnabled(signerCheckConstraints.isSelected()); + signerEmailField.setEnabled(signerCheckConstraints.isSelected()); + issuerDnField.setEnabled(signerCheckConstraints.isSelected()); + } + }); + Box box = Box.createHorizontalBox(); + box.add(new JLabel(JMeterUtils.getResString("smime_assertion_signer_dn"))); // $NON-NLS-1$ + box.add(Box.createHorizontalStrut(5)); + box.add(signerDnField); + panel.add(box); + + box = Box.createHorizontalBox(); + box.add(new JLabel(JMeterUtils.getResString("smime_assertion_signer_email"))); // $NON-NLS-1$ + box.add(Box.createHorizontalStrut(5)); + box.add(signerEmailField); + panel.add(box); + + box = Box.createHorizontalBox(); + box.add(new JLabel(JMeterUtils.getResString("smime_assertion_issuer_dn"))); // $NON-NLS-1$ + box.add(Box.createHorizontalStrut(5)); + box.add(issuerDnField); + panel.add(box); + + box = Box.createHorizontalBox(); + box.add(new JLabel(JMeterUtils.getResString("smime_assertion_signer_serial"))); // $NON-NLS-1$ + box.add(Box.createHorizontalStrut(5)); + box.add(signerSerialNumberField); + panel.add(box); + + // panel.add(signerCheckByFile); + signerCheckByFile.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + signerCertFile.setEnabled(signerCheckByFile.isSelected()); + } + }); + box = Box.createHorizontalBox(); + box.add(signerCheckByFile); + box.add(Box.createHorizontalStrut(5)); + box.add(signerCertFile); + panel.add(box); + + return panel; + } + + private JPanel createMessagePositionPanel(){ + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils + .getResString("smime_assertion_message_position"))); // $NON-NLS-1$ + panel.add(messagePositionTf); + return panel; + } + @Override + public void configure(TestElement el) { + super.configure(el); + SMIMEAssertionTestElement smimeAssertion = (SMIMEAssertionTestElement) el; + verifySignature.setSelected(smimeAssertion.isVerifySignature()); + notSigned.setSelected(smimeAssertion.isNotSigned()); + + if (smimeAssertion.isSignerNoCheck()) { + signerNoCheck.setSelected(true); + } + if (smimeAssertion.isSignerCheckConstraints()) { + signerCheckConstraints.setSelected(true); + } + if (smimeAssertion.isSignerCheckByFile()) { + signerCheckByFile.setSelected(true); + } + + issuerDnField.setText(smimeAssertion.getIssuerDn()); + signerDnField.setText(smimeAssertion.getSignerDn()); + signerSerialNumberField.setText(smimeAssertion.getSignerSerial()); + signerEmailField.setText(smimeAssertion.getSignerEmail()); + + signerCertFile.setText(smimeAssertion.getSignerCertFile()); + messagePositionTf.setText(smimeAssertion.getSpecificMessagePosition()); + } + + public void modifyTestElement(TestElement el) { + configureTestElement(el); + SMIMEAssertionTestElement smimeAssertion = (SMIMEAssertionTestElement) el; + smimeAssertion.setVerifySignature(verifySignature.isSelected()); + smimeAssertion.setNotSigned(notSigned.isSelected()); + + smimeAssertion.setIssuerDn(issuerDnField.getText()); + smimeAssertion.setSignerDn(signerDnField.getText()); + smimeAssertion.setSignerSerial(signerSerialNumberField.getText()); + smimeAssertion.setSignerEmail(signerEmailField.getText()); + + smimeAssertion.setSignerCertFile(signerCertFile.getText()); + + smimeAssertion.setSignerNoCheck(signerNoCheck.isSelected()); + smimeAssertion.setSignerCheckConstraints(signerCheckConstraints.isSelected()); + smimeAssertion.setSignerCheckByFile(signerCheckByFile.isSelected()); + smimeAssertion.setSpecificMessagePosition(messagePositionTf.getText()); + } + + public TestElement createTestElement() { + SMIMEAssertionTestElement smimeAssertion = new SMIMEAssertionTestElement(); + modifyTestElement(smimeAssertion); + return smimeAssertion; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/SizeAssertionGui.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/SizeAssertionGui.java new file mode 100644 index 0000000..6a31ce5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/SizeAssertionGui.java @@ -0,0 +1,276 @@ +/* + * 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.assertions.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import org.apache.jmeter.assertions.SizeAssertion; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * GUI for {@link SizeAssertion} + */ +public class SizeAssertionGui extends AbstractAssertionGui implements ActionListener { + + private static final long serialVersionUID = 241L; + + /** Radio button indicating that the body response should be tested. */ + private JRadioButton responseBodyButton; + + /** Radio button indicating that the network response size should be tested. */ + private JRadioButton responseNetworkButton; + + /** Radio button indicating that the responseMessage should be tested. */ + private JRadioButton responseMessageButton; + + /** Radio button indicating that the responseCode should be tested. */ + private JRadioButton responseCodeButton; + + /** Radio button indicating that the headers should be tested. */ + private JRadioButton responseHeadersButton; + + private JTextField size; + + private JRadioButton equalButton, notequalButton, greaterthanButton, lessthanButton, greaterthanequalButton, + lessthanequalButton; + + private int execState; // store the operator + + public SizeAssertionGui() { + init(); + } + + public String getLabelResource() { + return "size_assertion_title"; //$NON-NLS-1$ + } + + public TestElement createTestElement() { + SizeAssertion el = new SizeAssertion(); + modifyTestElement(el); + return el; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement el) { + configureTestElement(el); + SizeAssertion assertion = (SizeAssertion) el; + + if (responseHeadersButton.isSelected()) { + assertion.setTestFieldResponseHeaders(); + } else if (responseBodyButton.isSelected()) { + assertion.setTestFieldResponseBody(); + } else if (responseCodeButton.isSelected()) { + assertion.setTestFieldResponseCode(); + } else if (responseMessageButton.isSelected()) { + assertion.setTestFieldResponseMessage(); + } else { + assertion.setTestFieldNetworkSize(); + } + assertion.setAllowedSize(size.getText()); + assertion.setCompOper(getState()); + saveScopeSettings(assertion); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + responseNetworkButton.setSelected(true); // default + responseHeadersButton.setSelected(false); + responseBodyButton.setSelected(false); + responseCodeButton.setSelected(false); + responseMessageButton.setSelected(false); + + size.setText(""); //$NON-NLS-1$ + equalButton.setSelected(true); + notequalButton.setSelected(false); + greaterthanButton.setSelected(false); + lessthanButton.setSelected(false); + greaterthanequalButton.setSelected(false); + lessthanequalButton.setSelected(false); + execState = SizeAssertion.EQUAL; + } + + @Override + public void configure(TestElement el) { + super.configure(el); + SizeAssertion assertion = (SizeAssertion) el; + size.setText(assertion.getAllowedSize()); + setState(assertion.getCompOper()); + showScopeSettings(assertion, true); + + if (assertion.isTestFieldResponseHeaders()) { + responseHeadersButton.setSelected(true); + } else if (assertion.isTestFieldResponseBody()) { + responseBodyButton.setSelected(true); + } else if (assertion.isTestFieldResponseCode()) { + responseCodeButton.setSelected(true); + } else if (assertion.isTestFieldResponseMessage()) { + responseMessageButton.setSelected(true); + } else { + responseNetworkButton.setSelected(true); + } + } + + /** + * Set the state of the radio Button + */ + public void setState(int state) { + if (state == SizeAssertion.EQUAL) { + equalButton.setSelected(true); + execState = state; + } else if (state == SizeAssertion.NOTEQUAL) { + notequalButton.setSelected(true); + execState = state; + } else if (state == SizeAssertion.GREATERTHAN) { + greaterthanButton.setSelected(true); + execState = state; + } else if (state == SizeAssertion.LESSTHAN) { + lessthanButton.setSelected(true); + execState = state; + } else if (state == SizeAssertion.GREATERTHANEQUAL) { + greaterthanequalButton.setSelected(true); + execState = state; + } else if (state == SizeAssertion.LESSTHANEQUAL) { + lessthanequalButton.setSelected(true); + execState = state; + } + } + + /** + * Get the state of the radio Button + */ + public int getState() { + return execState; + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + + add(makeTitlePanel()); + + add(createScopePanel(true)); + add(createFieldPanel()); + + // USER_INPUT + JPanel sizePanel = new JPanel(); + sizePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("size_assertion_size_test"))); //$NON-NLS-1$ + + sizePanel.add(new JLabel(JMeterUtils.getResString("size_assertion_label"))); //$NON-NLS-1$ + size = new JTextField(12); + sizePanel.add(size); + + sizePanel.add(createComparatorButtonPanel()); + + add(sizePanel); + } + + /** + * Create a panel allowing the user to choose which response field should be + * tested. + * + * @return a new panel for selecting the response field + */ + private JPanel createFieldPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("assertion_resp_size_field"))); //$NON-NLS-1$ + + responseNetworkButton = new JRadioButton(JMeterUtils.getResString("assertion_network_size")); //$NON-NLS-1$ + responseHeadersButton = new JRadioButton(JMeterUtils.getResString("assertion_headers")); //$NON-NLS-1$ + responseBodyButton = new JRadioButton(JMeterUtils.getResString("assertion_body_resp")); //$NON-NLS-1$ + responseCodeButton = new JRadioButton(JMeterUtils.getResString("assertion_code_resp")); //$NON-NLS-1$ + responseMessageButton = new JRadioButton(JMeterUtils.getResString("assertion_message_resp")); //$NON-NLS-1$ + + ButtonGroup group = new ButtonGroup(); + group.add(responseNetworkButton); + group.add(responseHeadersButton); + group.add(responseBodyButton); + group.add(responseCodeButton); + group.add(responseMessageButton); + + panel.add(responseNetworkButton); + panel.add(responseHeadersButton); + panel.add(responseBodyButton); + panel.add(responseCodeButton); + panel.add(responseMessageButton); + + responseNetworkButton.setSelected(true); + + return panel; + } + + private Box createComparatorButtonPanel() { + ButtonGroup group = new ButtonGroup(); + + equalButton = createComparatorButton("=", SizeAssertion.EQUAL, group); //$NON-NLS-1$ + notequalButton = createComparatorButton("!=", SizeAssertion.NOTEQUAL, group); //$NON-NLS-1$ + greaterthanButton = createComparatorButton(">", SizeAssertion.GREATERTHAN, group); //$NON-NLS-1$ + lessthanButton = createComparatorButton("<", SizeAssertion.LESSTHAN, group); //$NON-NLS-1$ + greaterthanequalButton = createComparatorButton(">=", SizeAssertion.GREATERTHANEQUAL, group); //$NON-NLS-1$ + lessthanequalButton = createComparatorButton("<=", SizeAssertion.LESSTHANEQUAL, group); //$NON-NLS-1$ + + equalButton.setSelected(true); + execState = Integer.parseInt(equalButton.getActionCommand()); + + // Put the check boxes in a column in a panel + Box checkPanel = Box.createVerticalBox(); + JLabel compareLabel = new JLabel(JMeterUtils.getResString("size_assertion_comparator_label")); //$NON-NLS-1$ + checkPanel.add(compareLabel); + checkPanel.add(equalButton); + checkPanel.add(notequalButton); + checkPanel.add(greaterthanButton); + checkPanel.add(lessthanButton); + checkPanel.add(greaterthanequalButton); + checkPanel.add(lessthanequalButton); + return checkPanel; + } + + private JRadioButton createComparatorButton(String name, int value, ButtonGroup group) { + JRadioButton button = new JRadioButton(name); + button.setActionCommand(String.valueOf(value)); + button.addActionListener(this); + group.add(button); + return button; + } + + public void actionPerformed(ActionEvent e) { + int comparator = Integer.parseInt(e.getActionCommand()); + execState = comparator; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLAssertionGui.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLAssertionGui.java new file mode 100644 index 0000000..dad637a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLAssertionGui.java @@ -0,0 +1,76 @@ +/* + * 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.assertions.gui; + +import org.apache.jmeter.assertions.XMLAssertion; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class XMLAssertionGui extends AbstractAssertionGui { + private static final long serialVersionUID = 240L; + + /** + * The constructor. + */ + public XMLAssertionGui() { + init(); + } + + /** + * Returns the label to be shown within the JTree-Component. + */ + public String getLabelResource() { + return "xml_assertion_title"; // $NON-NLS-1$ + } + + public TestElement createTestElement() { + XMLAssertion el = new XMLAssertion(); + modifyTestElement(el); + return el; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement el) { + configureTestElement(el); + } + + /** + * Configures the associated test element. + * + * @param el + */ + @Override + public void configure(TestElement el) { + super.configure(el); + } + + /** + * Inits the GUI. + */ + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + + add(makeTitlePanel()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLConfPanel.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLConfPanel.java new file mode 100644 index 0000000..05cfa76 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLConfPanel.java @@ -0,0 +1,164 @@ +/* + * 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.assertions.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JPanel; + +import org.apache.jmeter.assertions.XPathAssertion; +import org.apache.jmeter.extractor.XPathExtractor; +import org.apache.jmeter.util.JMeterUtils; + +public class XMLConfPanel extends JPanel { + private static final long serialVersionUID = 240L; + + private JCheckBox validate, tolerant, whitespace, namespace; + + private JCheckBox quiet; // Should Tidy be quiet? + + private JCheckBox reportErrors; // Report Tidy errors as Assertion failure? + + private JCheckBox showWarnings; // Show Tidy warnings ? + + private JCheckBox downloadDTDs; // Should we download external DTDs? + + /** + * + */ + public XMLConfPanel() { + super(); + init(); + } + + private void init() { + quiet = new JCheckBox(JMeterUtils.getResString("xpath_tidy_quiet"),true);//$NON-NLS-1$ + reportErrors = new JCheckBox(JMeterUtils.getResString("xpath_tidy_report_errors"),true);//$NON-NLS-1$ + showWarnings = new JCheckBox(JMeterUtils.getResString("xpath_tidy_show_warnings"),true);//$NON-NLS-1$ + namespace = new JCheckBox(JMeterUtils.getResString("xml_namespace_button")); //$NON-NLS-1$ + whitespace = new JCheckBox(JMeterUtils.getResString("xml_whitespace_button")); //$NON-NLS-1$ + validate = new JCheckBox(JMeterUtils.getResString("xml_validate_button")); //$NON-NLS-1$ + tolerant = new JCheckBox(JMeterUtils.getResString("xml_tolerant_button")); //$NON-NLS-1$ + tolerant.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + tolerant(); + } + }); + downloadDTDs = new JCheckBox(JMeterUtils.getResString("xml_download_dtds")); //$NON-NLS-1$ + Box tidyOptions = Box.createHorizontalBox(); + tidyOptions.setBorder(BorderFactory.createEtchedBorder()); + tidyOptions.add(tolerant); + tidyOptions.add(quiet); + tidyOptions.add(reportErrors); + tidyOptions.add(showWarnings); + + Box untidyOptions = Box.createHorizontalBox(); + untidyOptions.setBorder(BorderFactory.createEtchedBorder()); + untidyOptions.add(namespace); + untidyOptions.add(validate); + untidyOptions.add(whitespace); + untidyOptions.add(downloadDTDs); + + Box options = Box.createVerticalBox(); + options.add(tidyOptions); + options.add(untidyOptions); + add(options); + setDefaultValues(); + } + + public void setDefaultValues() { + whitespace.setSelected(false); + validate.setSelected(false); + tolerant.setSelected(false); + namespace.setSelected(false); + quiet.setSelected(true); + reportErrors.setSelected(false); + showWarnings.setSelected(false); + downloadDTDs.setSelected(false); + tolerant(); + } + + // Process tolerant settings + private void tolerant() { + final boolean isTolerant = tolerant.isSelected(); + // Non-Tidy options + validate.setEnabled(!isTolerant); + whitespace.setEnabled(!isTolerant); + namespace.setEnabled(!isTolerant); + downloadDTDs.setEnabled(!isTolerant); + // Tidy options + quiet.setEnabled(isTolerant); + reportErrors.setEnabled(isTolerant); + showWarnings.setEnabled(isTolerant); + } + + // Called by XPathAssertionGui + public void modifyTestElement(XPathAssertion assertion) { + assertion.setValidating(validate.isSelected()); + assertion.setWhitespace(whitespace.isSelected()); + assertion.setTolerant(tolerant.isSelected()); + assertion.setNamespace(namespace.isSelected()); + assertion.setShowWarnings(showWarnings.isSelected()); + assertion.setReportErrors(reportErrors.isSelected()); + assertion.setQuiet(quiet.isSelected()); + assertion.setDownloadDTDs(downloadDTDs.isSelected()); + } + + // Called by XPathExtractorGui + public void modifyTestElement(XPathExtractor assertion) { + assertion.setValidating(validate.isSelected()); + assertion.setWhitespace(whitespace.isSelected()); + assertion.setTolerant(tolerant.isSelected()); + assertion.setNameSpace(namespace.isSelected()); + assertion.setShowWarnings(showWarnings.isSelected()); + assertion.setReportErrors(reportErrors.isSelected()); + assertion.setQuiet(quiet.isSelected()); + assertion.setDownloadDTDs(downloadDTDs.isSelected()); + } + + // Called by XPathAssertionGui + public void configure(XPathAssertion assertion) { + whitespace.setSelected(assertion.isWhitespace()); + validate.setSelected(assertion.isValidating()); + tolerant.setSelected(assertion.isTolerant()); + namespace.setSelected(assertion.isNamespace()); + quiet.setSelected(assertion.isQuiet()); + showWarnings.setSelected(assertion.showWarnings()); + reportErrors.setSelected(assertion.reportErrors()); + downloadDTDs.setSelected(assertion.isDownloadDTDs()); + tolerant(); + } + + // Called by XPathExtractorGui + public void configure(XPathExtractor assertion) { + whitespace.setSelected(assertion.isWhitespace()); + validate.setSelected(assertion.isValidating()); + tolerant.setSelected(assertion.isTolerant()); + namespace.setSelected(assertion.useNameSpace()); + quiet.setSelected(assertion.isQuiet()); + showWarnings.setSelected(assertion.showWarnings()); + reportErrors.setSelected(assertion.reportErrors()); + downloadDTDs.setSelected(assertion.isDownloadDTDs()); + tolerant(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLSchemaAssertionGUI.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLSchemaAssertionGUI.java new file mode 100644 index 0000000..021bf7d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XMLSchemaAssertionGUI.java @@ -0,0 +1,142 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +// import javax.swing.event.ChangeEvent; +import org.apache.jmeter.assertions.XMLSchemaAssertion; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +// See Bug 34383 + +/** + * XMLSchemaAssertionGUI.java author Dave Maung + * + */ + +public class XMLSchemaAssertionGUI extends AbstractAssertionGui { + // class attributes + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private JTextField xmlSchema; + + /** + * The constructor. + */ + public XMLSchemaAssertionGUI() { + init(); + } + + /** + * Returns the label to be shown within the JTree-Component. + */ + public String getLabelResource() { + return "xmlschema_assertion_title"; //$NON-NLS-1$ + } + + /** + * create Test Element + */ + public TestElement createTestElement() { + log.debug("XMLSchemaAssertionGui.createTestElement() called"); + XMLSchemaAssertion el = new XMLSchemaAssertion(); + modifyTestElement(el); + return el; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement inElement) { + + log.debug("XMLSchemaAssertionGui.modifyTestElement() called"); + configureTestElement(inElement); + ((XMLSchemaAssertion) inElement).setXsdFileName(xmlSchema.getText()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + xmlSchema.setText(""); //$NON-NLS-1$ + } + + /** + * Configures the GUI from the associated test element. + * + * @param el - + * the test element (should be XMLSchemaAssertion) + */ + @Override + public void configure(TestElement el) { + super.configure(el); + XMLSchemaAssertion assertion = (XMLSchemaAssertion) el; + xmlSchema.setText(assertion.getXsdFileName()); + } + + /** + * Inits the GUI. + */ + private void init() { + setLayout(new BorderLayout(0, 10)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + // USER_INPUT + VerticalPanel assertionPanel = new VerticalPanel(); + assertionPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "XML Schema")); + + // doctype + HorizontalPanel xmlSchemaPanel = new HorizontalPanel(); + + xmlSchemaPanel.add(new JLabel(JMeterUtils.getResString("xmlschema_assertion_label"))); //$NON-NLS-1$ + + xmlSchema = new JTextField(26); + xmlSchemaPanel.add(xmlSchema); + + assertionPanel.add(xmlSchemaPanel); + + mainPanel.add(assertionPanel, BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } + + // public void stateChanged(ChangeEvent e) { + // log.debug("XMLSchemaAssertionGui.stateChanged() called"); + // } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XPathAssertionGui.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XPathAssertionGui.java new file mode 100644 index 0000000..e9e321e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XPathAssertionGui.java @@ -0,0 +1,123 @@ +/* + * 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.assertions.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; + +import org.apache.jmeter.assertions.XPathAssertion; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class XPathAssertionGui extends AbstractAssertionGui { + + private static final long serialVersionUID = 240L; + + private XPathPanel xpath; + + private XMLConfPanel xml; + + public XPathAssertionGui() { + init(); + } + + /** + * Returns the label to be shown within the JTree-Component. + */ + public String getLabelResource() { + return "xpath_assertion_title"; //$NON-NLS-1$ + } + + /** + * Create test element + */ + public TestElement createTestElement() { + XPathAssertion el = new XPathAssertion(); + modifyTestElement(el); + return el; + } + + public String getXPathAttributesTitle() { + return JMeterUtils.getResString("xpath_assertion_test"); //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + XPathAssertion assertion = (XPathAssertion) el; + xpath.setXPath(assertion.getXPathString()); + xpath.setNegated(assertion.isNegated()); + + xml.configure(assertion); + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + + add(makeTitlePanel()); + + // USER_INPUT + JPanel sizePanel = new JPanel(new BorderLayout()); + sizePanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + sizePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + getXPathAttributesTitle())); + xpath = new XPathPanel(); + sizePanel.add(xpath); + + xml = new XMLConfPanel(); + xml.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("xpath_assertion_option"))); //$NON-NLS-1$ + add(xml); + + add(sizePanel); + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement el) { + super.configureTestElement(el); + if (el instanceof XPathAssertion) { + XPathAssertion assertion = (XPathAssertion) el; + assertion.setNegated(xpath.isNegated()); + assertion.setXPathString(xpath.getXPath()); + xml.modifyTestElement(assertion); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + xpath.setXPath("/"); //$NON-NLS-1$ + xpath.setNegated(false); + + xml.setDefaultValues(); + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XPathPanel.java b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XPathPanel.java new file mode 100644 index 0000000..c892596 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/assertions/gui/XPathPanel.java @@ -0,0 +1,215 @@ +/* + * 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.assertions.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.XPathUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class XPathPanel extends JPanel { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Lazily constructed. Does not matter if it is constructed more than once. + private static Document testDoc; + + private JCheckBox negated; + + private JTextField xpath; + + private JButton checkXPath; + + /** + * + */ + public XPathPanel() { + super(); + init(); + } + + private void init() { + Box hbox = Box.createHorizontalBox(); + hbox.add(Box.createHorizontalGlue()); + hbox.add(getXPathTextField()); + hbox.add(Box.createHorizontalGlue()); + hbox.add(getCheckXPathButton()); + + Box vbox = Box.createVerticalBox(); + vbox.add(hbox); + vbox.add(Box.createVerticalGlue()); + vbox.add(getNegatedCheckBox()); + + add(vbox); + + setDefaultValues(); + } + + public void setDefaultValues() { + setXPath("/"); //$NON-NLS-1$ + setNegated(false); + } + + /** + * Get the XPath String + * + * @return String + */ + public String getXPath() { + return this.xpath.getText(); + } + + /** + * Set the string that will be used in the xpath evaluation + * + * @param xpath + */ + public void setXPath(String xpath) { + this.xpath.setText(xpath); + } + + /** + * Does this negate the xpath results + * + * @return boolean + */ + public boolean isNegated() { + return this.negated.isSelected(); + } + + /** + * Set this to true, if you want success when the xpath does not match. + * + * @param negated + */ + public void setNegated(boolean negated) { + this.negated.setSelected(negated); + } + + /** + * Negated chechbox + * + * @return JCheckBox + */ + public JCheckBox getNegatedCheckBox() { + if (negated == null) { + negated = new JCheckBox(JMeterUtils.getResString("xpath_assertion_negate"), false); //$NON-NLS-1$ + } + + return negated; + } + + /** + * Check XPath button + * + * @return JButton + */ + public JButton getCheckXPathButton() { + if (checkXPath == null) { + checkXPath = new JButton(JMeterUtils.getResString("xpath_assertion_button")); //$NON-NLS-1$ + checkXPath.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + validXPath(xpath.getText(), true); + } + }); + } + return checkXPath; + } + + public JTextField getXPathTextField() { + if (xpath == null) { + xpath = new JTextField(50); + } + return xpath; + } + + /** + * @return Returns the showNegate. + */ + public boolean isShowNegated() { + return this.getNegatedCheckBox().isVisible(); + } + + /** + * @param showNegate + * The showNegate to set. + */ + public void setShowNegated(boolean showNegate) { + getNegatedCheckBox().setVisible(showNegate); + } + + /** + * Test whether an XPath is valid. It seems the Xalan has no easy way to + * check, so this creates a dummy test document, then tries to evaluate the xpath against it. + * + * @param xpathString + * XPath String to validate + * @param showDialog + * weather to show a dialog + * @return returns true if valid, valse otherwise. + */ + public static boolean validXPath(String xpathString, boolean showDialog) { + String ret = null; + boolean success = true; + try { + if (testDoc == null) { + Document doc = XPathUtil.makeDocumentBuilder(false, false, false, false).newDocument(); + testDoc = doc; + Element el = testDoc.createElement("root"); //$NON-NLS-1$ + doc.appendChild(el); + + } + XPathUtil.validateXPath(testDoc, xpathString); + } catch (IllegalArgumentException e) { + log.warn(e.getLocalizedMessage()); + success = false; + ret = e.getLocalizedMessage(); + } catch (ParserConfigurationException e) { + success = false; + ret = e.getLocalizedMessage(); + } catch (TransformerException e) { + success = false; + ret = e.getLocalizedMessage(); + } + + if (showDialog) { + JOptionPane.showMessageDialog(null, (success) ? JMeterUtils.getResString("xpath_assertion_valid") : ret, //$NON-NLS-1$ + (success) ? JMeterUtils.getResString("xpath_assertion_valid") : JMeterUtils //$NON-NLS-1$ + .getResString("xpath_assertion_failed"), (success) ? JOptionPane.INFORMATION_MESSAGE //$NON-NLS-1$ + : JOptionPane.ERROR_MESSAGE); + } + return success; + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/Argument.java b/ApacheJmeter/src/org/apache/jmeter/config/Argument.java new file mode 100644 index 0000000..fa5736b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/Argument.java @@ -0,0 +1,203 @@ +/* + * 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.config; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.StringProperty; + +/** + * Class representing an argument. Each argument consists of a name/value pair, + * as well as (optional) metadata. + * + */ +public class Argument extends AbstractTestElement implements Serializable { + private static final long serialVersionUID = 240L; + + /** Name used to store the argument's name. */ + public static final String ARG_NAME = "Argument.name"; // $NON-NLS-1$ + + /** Name used to store the argument's value. */ + public static final String VALUE = "Argument.value"; // $NON-NLS-1$ + + /** Name used to store the argument's description. */ + public static final String DESCRIPTION = "Argument.desc"; // $NON-NLS-1$ + + private static final String DFLT_DESCRIPTION = ""; // $NON-NLS-1$ + + /** Name used to store the argument's metadata. */ + public static final String METADATA = "Argument.metadata"; // $NON-NLS-1$ + + /** + * Create a new Argument without a name, value, or metadata. + */ + public Argument() { + } + + /** + * Create a new Argument with the specified name and value, and no metadata. + * + * @param name + * the argument name + * @param value + * the argument value + */ + public Argument(String name, String value) { + setProperty(new StringProperty(ARG_NAME, name)); + setProperty(new StringProperty(VALUE, value)); + } + + /** + * Create a new Argument with the specified name, value, and metadata. + * + * @param name + * the argument name + * @param value + * the argument value + * @param metadata + * the argument metadata + */ + public Argument(String name, String value, String metadata) { + setProperty(new StringProperty(ARG_NAME, name)); + setProperty(new StringProperty(VALUE, value)); + setProperty(new StringProperty(METADATA, metadata)); + } + + /** + * Create a new Argument with the specified name, value, and metadata. + * + * @param name + * the argument name + * @param value + * the argument value + * @param metadata + * the argument metadata + * @param description + * the argument description + */ + public Argument(String name, String value, String metadata, String description) { + setProperty(new StringProperty(ARG_NAME, name)); + setProperty(new StringProperty(VALUE, value)); + setProperty(new StringProperty(METADATA, metadata)); + setProperty(DESCRIPTION, description, DFLT_DESCRIPTION); + } + + /** + * Set the name of the Argument. + * + * @param newName + * the new name + */ + @Override + public void setName(String newName) { + setProperty(new StringProperty(ARG_NAME, newName)); + } + + /** + * Get the name of the Argument. + * + * @return the attribute's name + */ + @Override + public String getName() { + return getPropertyAsString(ARG_NAME); + } + + /** + * Sets the value of the Argument. + * + * @param newValue + * the new value + */ + public void setValue(String newValue) { + setProperty(new StringProperty(VALUE, newValue)); + } + + /** + * Gets the value of the Argument object. + * + * @return the attribute's value + */ + public String getValue() { + return getPropertyAsString(VALUE); + } + + /** + * Sets the Description attribute of the Argument. + * + * @param description + * the new description + */ + public void setDescription(String description) { + setProperty(DESCRIPTION, description, DFLT_DESCRIPTION); + } + + /** + * Gets the Meta Data attribute of the Argument. + * + * @return the MetaData value + */ + public String getDescription() { + return getPropertyAsString(DESCRIPTION, DFLT_DESCRIPTION); + } + + /** + * Sets the Meta Data attribute of the Argument. + * + * @param newMetaData + * the new metadata + */ + public void setMetaData(String newMetaData) { + setProperty(new StringProperty(METADATA, newMetaData)); + } + + /** + * Gets the Meta Data attribute of the Argument. + * + * @return the MetaData value + */ + public String getMetaData() { + return getPropertyAsString(METADATA); + } + + @Override + public String toString() { + return getName() + getMetaData() + getValue(); + } + + /** + * Is this parameter skippable, i.e. empty/blank string + * or it looks like an unrecognised variable. + * + * @param parameterName - parameter name + * @return true if parameter should be skipped + */ + public boolean isSkippable(String parameterName) { + if (parameterName.trim().length()==0){ + return true; // Skip parameters with a blank name (allows use of optional variables in parameter lists) + } + // TODO: improve this test + if (parameterName.trim().startsWith("${") && parameterName.endsWith("}")){// $NON-NLS-1$ $NON-NLS-2$ + return true; // Missing variable name + } + return false; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/Arguments.java b/ApacheJmeter/src/org/apache/jmeter/config/Arguments.java new file mode 100644 index 0000000..5bac6c8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/Arguments.java @@ -0,0 +1,259 @@ +/* + * 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.config; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; + +/** + * A set of Argument objects. + * + */ +public class Arguments extends ConfigTestElement implements Serializable { + private static final long serialVersionUID = 240L; + + /** The name of the property used to store the arguments. */ + public static final String ARGUMENTS = "Arguments.arguments"; //$NON-NLS-1$ + + /** + * Create a new Arguments object with no arguments. + */ + public Arguments() { + setProperty(new CollectionProperty(ARGUMENTS, new ArrayList())); + } + + /** + * Get the arguments. + * + * @return the arguments + */ + public CollectionProperty getArguments() { + return (CollectionProperty) getProperty(ARGUMENTS); + } + + /** + * Clear the arguments. + */ + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(ARGUMENTS, new ArrayList())); + } + + /** + * Set the list of arguments. Any existing arguments will be lost. + * + * @param arguments + * the new arguments + */ + public void setArguments(List arguments) { + setProperty(new CollectionProperty(ARGUMENTS, arguments)); + } + + /** + * Get the arguments as a Map. Each argument name is used as the key, and + * its value as the value. + * + * @return a new Map with String keys and values containing the arguments + */ + public Map getArgumentsAsMap() { + PropertyIterator iter = getArguments().iterator(); + Map argMap = new LinkedHashMap(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + // Because CollectionProperty.mergeIn will not prevent adding two + // properties of the same name, we need to select the first value so + // that this element's values prevail over defaults provided by + // configuration + // elements: + if (!argMap.containsKey(arg.getName())) { + argMap.put(arg.getName(), arg.getValue()); + } + } + return argMap; + } + + /** + * Add a new argument with the given name and value. + * + * @param name + * the name of the argument + * @param value + * the value of the argument + */ + public void addArgument(String name, String value) { + addArgument(new Argument(name, value, null)); + } + + /** + * Add a new argument. + * + * @param arg + * the new argument + */ + public void addArgument(Argument arg) { + TestElementProperty newArg = new TestElementProperty(arg.getName(), arg); + if (isRunningVersion()) { + this.setTemporary(newArg); + } + getArguments().addItem(newArg); + } + + /** + * Add a new argument with the given name, value, and metadata. + * + * @param name + * the name of the argument + * @param value + * the value of the argument + * @param metadata + * the metadata for the argument + */ + public void addArgument(String name, String value, String metadata) { + addArgument(new Argument(name, value, metadata)); + } + + /** + * Get a PropertyIterator of the arguments. + * + * @return an iteration of the arguments + */ + public PropertyIterator iterator() { + return getArguments().iterator(); + } + + /** + * Create a string representation of the arguments. + * + * @return the string representation of the arguments + */ + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + final String metaData = arg.getMetaData(); + str.append(arg.getName()); + if (metaData == null) { + str.append("="); //$NON-NLS-1$ + } else { + str.append(metaData); + } + str.append(arg.getValue()); + if (iter.hasNext()) { + str.append("&"); //$NON-NLS-1$ + } + } + return str.toString(); + } + + /** + * Remove the specified argument from the list. + * + * @param row + * the index of the argument to remove + */ + public void removeArgument(int row) { + if (row < getArguments().size()) { + getArguments().remove(row); + } + } + + /** + * Remove the specified argument from the list. + * + * @param arg + * the argument to remove + */ + public void removeArgument(Argument arg) { + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + Argument item = (Argument) iter.next().getObjectValue(); + if (arg.equals(item)) { + iter.remove(); + } + } + } + + /** + * Remove the argument with the specified name. + * + * @param argName + * the name of the argument to remove + */ + public void removeArgument(String argName) { + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + if (arg.getName().equals(argName)) { + iter.remove(); + } + } + } + + /** + * Remove all arguments from the list. + */ + public void removeAllArguments() { + getArguments().clear(); + } + + /** + * Add a new empty argument to the list. The new argument will have the + * empty string as its name and value, and null metadata. + */ + public void addEmptyArgument() { + addArgument(new Argument("", "", null)); + } + + /** + * Get the number of arguments in the list. + * + * @return the number of arguments + */ + public int getArgumentCount() { + return getArguments().size(); + } + + /** + * Get a single argument. + * + * @param row + * the index of the argument to return. + * @return the argument at the specified index, or null if no argument + * exists at that index. + */ + public Argument getArgument(int row) { + Argument argument = null; + + if (row < getArguments().size()) { + argument = (Argument) getArguments().get(row).getObjectValue(); + } + + return argument; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/CSVDataSet.java b/ApacheJmeter/src/org/apache/jmeter/config/CSVDataSet.java new file mode 100644 index 0000000..cbbe2d4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/CSVDataSet.java @@ -0,0 +1,270 @@ +/* + * 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.config; + +import java.io.IOException; +import java.util.List; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.engine.util.NoConfigMerge; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Read lines from a file and split int variables. + * + * The iterationStart() method is used to set up each set of values. + * + * By default, the same file is shared between all threads + * (and other thread groups, if they use the same file name). + * + * The shareMode can be set to: + *
    + *
  • All threads - default, as described above
  • + *
  • Current thread group
  • + *
  • Current thread
  • + *
  • Identifier - all threads sharing the same identifier
  • + *
+ * + * The class uses the FileServer alias mechanism to provide the different share modes. + * For all threads, the file alias is set to the file name. + * Otherwise, a suffix is appended to the filename to make it unique within the required context. + * For current thread group, the thread group identityHashcode is used; + * for individual threads, the thread hashcode is used as the suffix. + * Or the user can provide their own suffix, in which case the file is shared between all + * threads with the same suffix. + * + */ +public class CSVDataSet extends ConfigTestElement + implements TestBean, LoopIterationListener, NoConfigMerge { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + private static final String EOFVALUE = // value to return at EOF + JMeterUtils.getPropDefault("csvdataset.eofstring", ""); //$NON-NLS-1$ //$NON-NLS-2$ + + private transient String filename; + + private transient String fileEncoding; + + private transient String variableNames; + + private transient String delimiter; + + private transient boolean quoted; + + private transient boolean recycle = true; + + private transient boolean stopThread; + + private transient String[] vars; + + private transient String alias; + + private transient String shareMode; + + private boolean firstLineIsNames = false; + + private Object readResolve(){ + recycle = true; + return this; + } + + /** + * {@inheritDoc} + */ + public void iterationStart(LoopIterationEvent iterEvent) { + FileServer server = FileServer.getFileServer(); + final JMeterContext context = getThreadContext(); + String delim = getDelimiter(); + if (delim.equals("\\t")) { // $NON-NLS-1$ + delim = "\t";// Make it easier to enter a Tab // $NON-NLS-1$ + } else if (delim.length()==0){ + log.warn("Empty delimiter converted to ','"); + delim=","; + } + if (vars == null) { + String _fileName = getFilename(); + String mode = getShareMode(); + int modeInt = CSVDataSetBeanInfo.getShareModeAsInt(mode); + switch(modeInt){ + case CSVDataSetBeanInfo.SHARE_ALL: + alias = _fileName; + break; + case CSVDataSetBeanInfo.SHARE_GROUP: + alias = _fileName+"@"+System.identityHashCode(context.getThreadGroup()); + break; + case CSVDataSetBeanInfo.SHARE_THREAD: + alias = _fileName+"@"+System.identityHashCode(context.getThread()); + break; + default: + alias = _fileName+"@"+mode; // user-specified key + break; + } + final String names = getVariableNames(); + if (names == null || names.length()==0) { + String header = server.reserveFile(_fileName, getFileEncoding(), alias, true); + try { + vars = CSVSaveService.csvSplitString(header, delim.charAt(0)); + firstLineIsNames = true; + } catch (IOException e) { + log.warn("Could not split CSV header line",e); + } + } else { + server.reserveFile(_fileName, getFileEncoding(), alias); + vars = JOrphanUtils.split(names, ","); // $NON-NLS-1$ + } + } + + // TODO: fetch this once as per vars above? + JMeterVariables threadVars = context.getVariables(); + String line = null; + try { + line = server.readLine(alias, getRecycle(), firstLineIsNames); + } catch (IOException e) { // treat the same as EOF + log.error(e.toString()); + } + if (line!=null) {// i.e. not EOF + try { + String[] lineValues = getQuotedData() ? + CSVSaveService.csvSplitString(line, delim.charAt(0)) + : JOrphanUtils.split(line, delim, false); + for (int a = 0; a < vars.length && a < lineValues.length; a++) { + threadVars.put(vars[a], lineValues[a]); + } + } catch (IOException e) { // Should only happen for quoting errors + log.error("Unexpected error splitting '"+line+"' on '"+delim.charAt(0)+"'"); + } + // TODO - report unused columns? + // TODO - provide option to set unused variables ? + } else { + if (getStopThread()) { + throw new JMeterStopThreadException("End of file detected"); + } + for (int a = 0; a < vars.length ; a++) { + threadVars.put(vars[a], EOFVALUE); + } + } + } + + /** + * @return Returns the filename. + */ + public String getFilename() { + return filename; + } + + /** + * @param filename + * The filename to set. + */ + public void setFilename(String filename) { + this.filename = filename; + } + + /** + * @return Returns the file encoding. + */ + public String getFileEncoding() { + return fileEncoding; + } + + /** + * @param fileEncoding + * The fileEncoding to set. + */ + public void setFileEncoding(String fileEncoding) { + this.fileEncoding = fileEncoding; + } + + /** + * @return Returns the variableNames. + */ + public String getVariableNames() { + return variableNames; + } + + /** + * @param variableNames + * The variableNames to set. + */ + public void setVariableNames(String variableNames) { + this.variableNames = variableNames; + } + + public String getDelimiter() { + return delimiter; + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; + } + + public boolean getQuotedData() { + return quoted; + } + + public void setQuotedData(boolean quoted) { + this.quoted = quoted; + } + + public boolean getRecycle() { + return recycle; + } + + public void setRecycle(boolean recycle) { + this.recycle = recycle; + } + + public boolean getStopThread() { + return stopThread; + } + + public void setStopThread(boolean value) { + this.stopThread = value; + } + + public String getShareMode() { + return shareMode; + } + + public void setShareMode(String value) { + this.shareMode = value; + } + + /** + * {@inheritDoc}} + */ + @Override + public List getSearchableTokens() throws Exception { + List result = super.getSearchableTokens(); + result.add(getPropertyAsString("variableNames")); + return result; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/CSVDataSetBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/config/CSVDataSetBeanInfo.java new file mode 100644 index 0000000..275146a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/CSVDataSetBeanInfo.java @@ -0,0 +1,108 @@ +/* + * 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.config; + +import java.beans.PropertyDescriptor; +import java.util.ResourceBundle; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +public class CSVDataSetBeanInfo extends BeanInfoSupport { + + // These names must agree case-wise with the variable and property names + private static final String FILENAME = "filename"; //$NON-NLS-1$ + private static final String FILE_ENCODING = "fileEncoding"; //$NON-NLS-1$ + private static final String VARIABLE_NAMES = "variableNames"; //$NON-NLS-1$ + private static final String DELIMITER = "delimiter"; //$NON-NLS-1$ + private static final String RECYCLE = "recycle"; //$NON-NLS-1$ + private static final String STOPTHREAD = "stopThread"; //$NON-NLS-1$ + private static final String QUOTED_DATA = "quotedData"; //$NON-NLS-1$ + private static final String SHAREMODE = "shareMode"; //$NON-NLS-1$ + + private static final String[] SHARE_TAGS = new String[3]; + static final int SHARE_ALL = 0; + static final int SHARE_GROUP = 1; + static final int SHARE_THREAD = 2; + + + public CSVDataSetBeanInfo() { + super(CSVDataSet.class); + + ResourceBundle rb = (ResourceBundle) getBeanDescriptor().getValue(RESOURCE_BUNDLE); +// These must agree with the resources + SHARE_TAGS[SHARE_ALL] = rb.getString("shareMode.all"); //$NON-NLS-1$ + SHARE_TAGS[SHARE_GROUP] = rb.getString("shareMode.group"); //$NON-NLS-1$ + SHARE_TAGS[SHARE_THREAD] = rb.getString("shareMode.thread"); //$NON-NLS-1$ + + createPropertyGroup("csv_data", //$NON-NLS-1$ + new String[] { FILENAME, FILE_ENCODING, VARIABLE_NAMES, DELIMITER, QUOTED_DATA, RECYCLE, STOPTHREAD, SHAREMODE }); + + PropertyDescriptor p = property(FILENAME); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); //$NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + + p = property(FILE_ENCODING); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); //$NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + + p = property(VARIABLE_NAMES); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); //$NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + + p = property(DELIMITER); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ","); //$NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + + p = property(QUOTED_DATA); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + + p = property(RECYCLE); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + + p = property(STOPTHREAD); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + + p = property(SHAREMODE); //$NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, SHARE_TAGS[0]); + p.setValue(NOT_OTHER, Boolean.FALSE); + p.setValue(NOT_EXPRESSION, Boolean.FALSE); + p.setValue(TAGS, SHARE_TAGS); + } + + // TODO need to find better way to do this + public static int getShareModeAsInt(String mode) { + if (mode == null || mode.length() == 0){ + return SHARE_ALL; // default (e.g. if test plan does not have definition) + } + for (int i = 0; i < SHARE_TAGS.length; i++) { + if (SHARE_TAGS[i].equals(mode)) { + return i; + } + } + return -1; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/ConfigElement.java b/ApacheJmeter/src/org/apache/jmeter/config/ConfigElement.java new file mode 100644 index 0000000..f43586b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/ConfigElement.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.jmeter.config; + +public interface ConfigElement extends Cloneable { + + /** + * Add a configuration element to this one. This allows config elements to + * combine and give a "layered" effect. For example, + * HTTPConfigElements have properties for domain, path, method, and + * parameters. If element A has everything filled in, but null for domain, + * and element B is added, which has only domain filled in, then after + * adding B to A, A will have the domain from B. If A already had a domain, + * then the correct behavior is for A to ignore the addition of element B. + * + * @param config + * the element to be added to this ConfigElement + */ + void addConfigElement(ConfigElement config); + + /** + * If your config element expects to be modified in the process of a test + * run, and you want those modifications to carry over from sample to sample + * (as in a cookie manager - you want to save all cookies that get set + * throughout the test), then return true for this method. Your config + * element will not be cloned for each sample. If your config elements are + * more static in nature, return false. If in doubt, return false. + * + * @return true if the element expects to be modified over the course of a + * test run + */ + boolean expectsModification(); + + Object clone(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/ConfigTestElement.java b/ApacheJmeter/src/org/apache/jmeter/config/ConfigTestElement.java new file mode 100644 index 0000000..a2ae453 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/ConfigTestElement.java @@ -0,0 +1,56 @@ +/* + * 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.config; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; + +public class ConfigTestElement extends AbstractTestElement implements Serializable, ConfigElement { + private static final long serialVersionUID = 240L; + + public final static String USERNAME = "ConfigTestElement.username"; + + public final static String PASSWORD = "ConfigTestElement.password"; + + public ConfigTestElement() { + } + + @Override + public void addTestElement(TestElement parm1) { + if (parm1 instanceof ConfigTestElement) { + mergeIn(parm1); + } + } + + /** + * {@inheritDoc} + */ + public void addConfigElement(ConfigElement config) { + mergeIn((TestElement) config); + } + + /** + * {@inheritDoc} + */ + public boolean expectsModification() { + return false; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/KeystoreConfig.java b/ApacheJmeter/src/org/apache/jmeter/config/KeystoreConfig.java new file mode 100644 index 0000000..8ca534d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/KeystoreConfig.java @@ -0,0 +1,142 @@ +/* + * 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.config; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.SSLManager; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopTestException; +import org.apache.log.Logger; + +/** + * Configure Keystore + */ +public class KeystoreConfig extends ConfigTestElement implements TestBean, TestListener { + + private static final long serialVersionUID = -5781402012242794890L; + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String KEY_STORE_START_INDEX = "https.keyStoreStartIndex"; // $NON-NLS-1$ + private static final String KEY_STORE_END_INDEX = "https.keyStoreEndIndex"; // $NON-NLS-1$ + + private String startIndex; + private String endIndex; + private String preload; + + public KeystoreConfig() { + super(); + } + + public void testEnded() { + testEnded(null); + } + + public void testEnded(String host) { + log.info("Destroying Keystore"); + SSLManager.getInstance().destroyKeystore(); + } + + public void testIterationStart(LoopIterationEvent event) { + // NOOP + } + + public void testStarted() { + testStarted(null); + } + + public void testStarted(String host) { + String reuseSSLContext = JMeterUtils.getProperty("https.use.cached.ssl.context"); + if(StringUtils.isEmpty(reuseSSLContext)||"true".equals(reuseSSLContext)) { + log.warn("https.use.cached.ssl.context property must be set to false to ensure Multiple Certificates are used"); + } + int startIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_START_INDEX, 0); + int endIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_END_INDEX, 0); + + if(!StringUtils.isEmpty(this.startIndex)) { + try { + startIndexAsInt = Integer.parseInt(this.startIndex); + } catch(NumberFormatException e) { + log.warn("Failed parsing startIndex :'"+this.startIndex+"', will default to:'"+startIndexAsInt+"', error message:"+ e.getMessage(), e); + } + } + + if(!StringUtils.isEmpty(this.endIndex)) { + try { + endIndexAsInt = Integer.parseInt(this.endIndex); + } catch(NumberFormatException e) { + log.warn("Failed parsing endIndex :'"+this.endIndex+"', will default to:'"+endIndexAsInt+"', error message:"+ e.getMessage(), e); + } + } + if(startIndexAsInt>endIndexAsInt) { + throw new JMeterStopTestException("Keystore Config error : Alias start index must be lower than Alias end index"); + } + log.info("Configuring Keystore with (preload:"+preload+", startIndex:"+ + startIndexAsInt+", endIndex:"+endIndexAsInt+")"); + + SSLManager.getInstance().configureKeystore(Boolean.parseBoolean(preload), + startIndexAsInt, + endIndexAsInt); + } + + /** + * @return the endIndex + */ + public String getEndIndex() { + return endIndex; + } + + /** + * @param endIndex the endIndex to set + */ + public void setEndIndex(String endIndex) { + this.endIndex = endIndex; + } + + /** + * @return the startIndex + */ + public String getStartIndex() { + return startIndex; + } + + /** + * @param startIndex the startIndex to set + */ + public void setStartIndex(String startIndex) { + this.startIndex = startIndex; + } + + /** + * @return the preload + */ + public String getPreload() { + return preload; + } + + /** + * @param preload the preload to set + */ + public void setPreload(String preload) { + this.preload = preload; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/KeystoreConfigBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/config/KeystoreConfigBeanInfo.java new file mode 100644 index 0000000..f331b4e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/KeystoreConfigBeanInfo.java @@ -0,0 +1,59 @@ +/* + * 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.config; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +/** + * Keystore Configuration BeanInfo + */ +public class KeystoreConfigBeanInfo extends BeanInfoSupport { + + private static final String ALIASES_GROUP = "aliases"; + private static final String ALIAS_END_INDEX = "endIndex"; + private static final String ALIAS_START_INDEX = "startIndex"; + private static final String PRELOAD = "preload"; + + /** + * Constructor + */ + public KeystoreConfigBeanInfo() { + super(KeystoreConfig.class); + + createPropertyGroup(ALIASES_GROUP, new String[] { + PRELOAD, ALIAS_START_INDEX, ALIAS_END_INDEX }); + + PropertyDescriptor p = property(PRELOAD); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "true"); // $NON-NLS-1$ + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(TAGS, new String[]{"True", "False"}); // $NON-NLS-1$ $NON-NLS-2$ + + p = property(ALIAS_START_INDEX); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property(ALIAS_END_INDEX); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/LoginConfig.java b/ApacheJmeter/src/org/apache/jmeter/config/LoginConfig.java new file mode 100644 index 0000000..dd4ba45 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/LoginConfig.java @@ -0,0 +1,79 @@ +/* + * 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.config; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.property.StringProperty; + +public class LoginConfig extends ConfigTestElement implements Serializable +// TODO: move this to components -- the only reason why it's in core is because +// it's used as a guinea pig by a couple of tests. +{ + private static final long serialVersionUID = 240L; + + /** + * Constructor for the LoginConfig object. + */ + public LoginConfig() { + } + + /** + * Sets the Username attribute of the LoginConfig object. + * + * @param username + * the new Username value + */ + public void setUsername(String username) { + setProperty(new StringProperty(ConfigTestElement.USERNAME, username)); + } + + /** + * Sets the Password attribute of the LoginConfig object. + * + * @param password + * the new Password value + */ + public void setPassword(String password) { + setProperty(new StringProperty(ConfigTestElement.PASSWORD, password)); + } + + /** + * Gets the Username attribute of the LoginConfig object. + * + * @return the Username value + */ + public String getUsername() { + return getPropertyAsString(ConfigTestElement.USERNAME); + } + + /** + * Gets the Password attribute of the LoginConfig object. + * + * @return the Password value + */ + public String getPassword() { + return getPropertyAsString(ConfigTestElement.PASSWORD); + } + + @Override + public String toString() { + return getUsername() + "=" + getPassword(); //$NON-NLS-1$ + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/RandomVariableConfig.java b/ApacheJmeter/src/org/apache/jmeter/config/RandomVariableConfig.java new file mode 100644 index 0000000..9a596ef --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/RandomVariableConfig.java @@ -0,0 +1,239 @@ +/* + * 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.config; + +import java.text.DecimalFormat; +import java.util.Random; + +import org.apache.commons.lang.math.NumberUtils; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.engine.util.NoConfigMerge; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class RandomVariableConfig extends ConfigTestElement + implements TestBean, LoopIterationListener, NoThreadClone, NoConfigMerge +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + private String minimumValue; + + private String maximumValue; + + private String variableName; + + private String outputFormat; + + private String randomSeed; + + private boolean perThread; + + // This class is not cloned per thread, so this is shared + private Random globalRandom = null; + + // Used for per-thread/user numbers + // Cannot be static, as random numbers are not to be shared between instances + private transient ThreadLocal perThreadRandom = initThreadLocal(); + + private ThreadLocal initThreadLocal() { + return new ThreadLocal() { + @Override + protected Random initialValue() { + init(); + return new Random(getRandomSeedAsLong()); + }}; + } + + private int n; + private long minimum; + + private Object readResolve(){ + perThreadRandom = initThreadLocal(); + return this; + } + + /* + * nextInt(n) returns values in the range [0,n), + * so n must be set to max-min+1 + */ + private void init(){ + final String minAsString = getMinimumValue(); + minimum = NumberUtils.toLong(minAsString); + final String maxAsString = getMaximumValue(); + long maximum = NumberUtils.toLong(maxAsString); + long rangeL=maximum-minimum+1; // This can overflow + if (minimum >= maximum){ + log.error("maximum("+maxAsString+") must be > minimum"+minAsString+")"); + n=0;// This is used as an error indicator + return; + } + if (rangeL > Integer.MAX_VALUE || rangeL <= 0){// check for overflow too + log.warn("maximum("+maxAsString+") - minimum"+minAsString+") must be <="+Integer.MAX_VALUE); + rangeL=Integer.MAX_VALUE; + } + n = (int)rangeL; + } + + /** {@inheritDoc} */ + public void iterationStart(LoopIterationEvent iterEvent) { + Random randGen=null; + if (getPerThread()){ + randGen = perThreadRandom.get(); + } else { + synchronized(this){ + if (globalRandom == null){ + init(); + globalRandom = new Random(getRandomSeedAsLong()); + } + randGen=globalRandom; + } + } + if (n <=0){ + return; + } + long nextRand = minimum + randGen.nextInt(n); + // Cannot use getThreadContext() as we are not cloned per thread + JMeterVariables variables = JMeterContextService.getContext().getVariables(); + variables.put(getVariableName(), formatNumber(nextRand)); + } + + // Use format to create number; if it fails, use the default + private String formatNumber(long value){ + String format = getOutputFormat(); + if (format != null && format.length() > 0) { + try { + DecimalFormat myFormatter = new DecimalFormat(format); + return myFormatter.format(value); + } catch (NumberFormatException ignored) { + log.warn("Exception formatting value:"+value + " at format:"+format+", using default"); + } catch (IllegalArgumentException ignored) { + log.warn("Exception formatting value:"+value + " at format:"+format+", using default"); + } + } + return Long.toString(value); + } + + /** + * @return the minValue + */ + public synchronized String getMinimumValue() { + return minimumValue; + } + + /** + * @param minValue the minValue to set + */ + public synchronized void setMinimumValue(String minValue) { + this.minimumValue = minValue; + } + + /** + * @return the maxvalue + */ + public synchronized String getMaximumValue() { + return maximumValue; + } + + /** + * @param maxvalue the maxvalue to set + */ + public synchronized void setMaximumValue(String maxvalue) { + this.maximumValue = maxvalue; + } + + /** + * @return the variableName + */ + public synchronized String getVariableName() { + return variableName; + } + + /** + * @param variableName the variableName to set + */ + public synchronized void setVariableName(String variableName) { + this.variableName = variableName; + } + + /** + * @return the randomSeed + */ + public synchronized String getRandomSeed() { + return randomSeed; + } + + /** + * @return the randomSeed as a long + */ + private synchronized long getRandomSeedAsLong() { + long seed = 0; + if (randomSeed.length()==0){ + seed = System.currentTimeMillis(); + } else { + try { + seed = Long.parseLong(randomSeed); + } catch (NumberFormatException e) { + seed = System.currentTimeMillis(); + log.warn("Cannot parse seed "+e.getLocalizedMessage()); + } + } + return seed; + } + + /** + * @param randomSeed the randomSeed to set + */ + public synchronized void setRandomSeed(String randomSeed) { + this.randomSeed = randomSeed; + } + + /** + * @return the perThread + */ + public synchronized boolean getPerThread() { + return perThread; + } + + /** + * @param perThread the perThread to set + */ + public synchronized void setPerThread(boolean perThread) { + this.perThread = perThread; + } + /** + * @return the outputFormat + */ + public synchronized String getOutputFormat() { + return outputFormat; + } + /** + * @param outputFormat the outputFormat to set + */ + public synchronized void setOutputFormat(String outputFormat) { + this.outputFormat = outputFormat; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/RandomVariableConfigBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/config/RandomVariableConfigBeanInfo.java new file mode 100644 index 0000000..a1fe0da --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/RandomVariableConfigBeanInfo.java @@ -0,0 +1,78 @@ +/* + * 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.config; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +public class RandomVariableConfigBeanInfo extends BeanInfoSupport { + + // These group names must have .displayName properties + private static final String VARIABLE_GROUP = "variable"; // $NON-NLS-1$ + private static final String OPTIONS_GROUP = "options"; // $NON-NLS-1$ + private static final String RANDOM_GROUP = "random"; // $NON-NLS-1$ + + // These variable names must have .displayName properties and agree with the getXXX()/setXXX() methods + private static final String PER_THREAD = "perThread"; // $NON-NLS-1$ + private static final String RANDOM_SEED = "randomSeed"; // $NON-NLS-1$ + private static final String MAXIMUM_VALUE = "maximumValue"; // $NON-NLS-1$ + private static final String MINIMUM_VALUE = "minimumValue"; // $NON-NLS-1$ + private static final String OUTPUT_FORMAT = "outputFormat"; // $NON-NLS-1$ + private static final String VARIABLE_NAME = "variableName"; // $NON-NLS-1$ + + public RandomVariableConfigBeanInfo() { + super(RandomVariableConfig.class); + + PropertyDescriptor p; + + createPropertyGroup(VARIABLE_GROUP, new String[] { VARIABLE_NAME, OUTPUT_FORMAT, }); + + p = property(VARIABLE_NAME); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property(OUTPUT_FORMAT); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + createPropertyGroup(RANDOM_GROUP, + new String[] { MINIMUM_VALUE, MAXIMUM_VALUE, RANDOM_SEED, }); + + p = property(MINIMUM_VALUE); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "1"); // $NON-NLS-1$ + + p = property(MAXIMUM_VALUE); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + p = property(RANDOM_SEED); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + createPropertyGroup(OPTIONS_GROUP, new String[] { PER_THREAD, }); + + p = property(PER_THREAD); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/gui/AbstractConfigGui.java b/ApacheJmeter/src/org/apache/jmeter/config/gui/AbstractConfigGui.java new file mode 100644 index 0000000..a4dd93d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/gui/AbstractConfigGui.java @@ -0,0 +1,64 @@ +/* + * 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.config.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which provide configuration + * for some other component. + * + */ +public abstract class AbstractConfigGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most configuration + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultConfigElementMenu(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#CONFIG_ELEMENTS}, which is + * appropriate for most configuration components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.CONFIG_ELEMENTS }); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/gui/ArgumentsPanel.java b/ApacheJmeter/src/org/apache/jmeter/config/gui/ArgumentsPanel.java new file mode 100644 index 0000000..76dda91 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/gui/ArgumentsPanel.java @@ -0,0 +1,702 @@ +/* + * 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.config.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +/** + * A GUI panel allowing the user to enter name-value argument pairs. These + * arguments (or parameters) are usually used to provide configuration values + * for some other component. + * + */ +public class ArgumentsPanel extends AbstractConfigGui implements ActionListener { + + private static final long serialVersionUID = 240L; + + /** The title label for this component. */ + private JLabel tableLabel; + + /** The table containing the list of arguments. */ + private transient JTable table; + + /** The model for the arguments table. */ + protected transient ObjectTableModel tableModel; // will only contain Argument or HTTPArgument + + /** A button for adding new arguments to the table. */ + private JButton add; + + /** A button for adding new arguments to the table from the clipboard. */ + private JButton addFromClipboard; + + /** A button for removing arguments from the table. */ + private JButton delete; + + /** + * Added background support for reporting tool + */ + private Color background; + + /** + * Boolean indicating whether this component is a standalone component or it + * is intended to be used as a subpanel for another component. + */ + private final boolean standalone; + + /** Button to move a argument up*/ + private JButton up; + + /** Button to move a argument down*/ + private JButton down; + + private final boolean enableUpDown; + + private JButton showDetail; + + /** Command for adding a row to the table. */ + private static final String ADD = "add"; // $NON-NLS-1$ + + /** Command for adding rows from the clipboard */ + private static final String ADD_FROM_CLIPBOARD = "addFromClipboard"; // $NON-NLS-1$ + + /** Command for removing a row from the table. */ + private static final String DELETE = "delete"; // $NON-NLS-1$ + + /** Command for moving a row up in the table. */ + private static final String UP = "up"; // $NON-NLS-1$ + + /** Command for moving a row down in the table. */ + private static final String DOWN = "down"; // $NON-NLS-1$ + + /** Command for showing detail. */ + private static final String DETAIL = "detail"; // $NON-NLS-1$ + + public static final String COLUMN_RESOURCE_NAMES_0 = "name"; // $NON-NLS-1$ + + public static final String COLUMN_RESOURCE_NAMES_1 = "value"; // $NON-NLS-1$ + + public static final String COLUMN_RESOURCE_NAMES_2 = "description"; // $NON-NLS-1$ + + /** + * Create a new ArgumentsPanel as a standalone component. + */ + public ArgumentsPanel() { + this(JMeterUtils.getResString("user_defined_variables"),null, true, true);// $NON-NLS-1$ + } + + /** + * Create a new ArgumentsPanel as an embedded component, using the specified + * title. + * + * @param label + * the title for the component. + */ + public ArgumentsPanel(String label) { + this(label, null, true, false); + } + + /** + * Create a new ArgumentsPanel as an embedded component, using the specified + * title. + * + * @param label + * the title for the component. + * @param enableUpDown Add up/down buttons + */ + public ArgumentsPanel(String label, boolean enableUpDown) { + this(label, null, enableUpDown, false); + } + + /** + * Create a new ArgumentsPanel with a border and color background + * @param label text for label + * @param bkg background colour + */ + public ArgumentsPanel(String label, Color bkg) { + this(label, bkg, true, false); + } + + /** + * Create a new ArgumentsPanel with a border and color background + * @param label text for label + * @param bkg background colour + * @param enableUpDown Add up/down buttons + * @param standalone is standalone + */ + public ArgumentsPanel(String label, Color bkg, boolean enableUpDown, boolean standalone) { + this(label, bkg, enableUpDown, standalone, null); + } + + /** + * Create a new ArgumentsPanel with a border and color background + * @param label text for label + * @param bkg background colour + * @param enableUpDown Add up/down buttons + * @param standalone is standalone + * @param model the table model to use + */ + public ArgumentsPanel(String label, Color bkg, boolean enableUpDown, boolean standalone, ObjectTableModel model) { + tableLabel = new JLabel(label); + this.enableUpDown = enableUpDown; + this.background = bkg; + this.standalone = standalone; + this.tableModel = model; + init(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + if (standalone) { + return super.getMenuCategories(); + } + return null; + } + + public String getLabelResource() { + return "user_defined_variables"; // $NON-NLS-1$ + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + Arguments args = new Arguments(); + modifyTestElement(args); + return args; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement args) { + stopTableEditing(); + Arguments arguments = null; + if (args instanceof Arguments) { + arguments = (Arguments) args; + arguments.clear(); + @SuppressWarnings("unchecked") // only contains Argument (or HTTPArgument) + Iterator modelData = (Iterator) tableModel.iterator(); + while (modelData.hasNext()) { + Argument arg = modelData.next(); + if(StringUtils.isEmpty(arg.getName()) && StringUtils.isEmpty(arg.getValue())) { + continue; + } + arg.setMetaData("="); // $NON-NLS-1$ + arguments.addArgument(arg); + } + } + this.configureTestElement(args); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof Arguments) { + tableModel.clearData(); + PropertyIterator iter = ((Arguments) el).iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + tableModel.addRow(arg); + } + } + checkDeleteStatus(); + } + + /** + * Get the table used to enter arguments. + * + * @return the table used to enter arguments + */ + protected JTable getTable() { + return table; + } + + /** + * Get the title label for this component. + * + * @return the title label displayed with the table + */ + protected JLabel getTableLabel() { + return tableLabel; + } + + /** + * Get the button used to delete rows from the table. + * + * @return the button used to delete rows from the table + */ + protected JButton getDeleteButton() { + return delete; + } + + /** + * Get the button used to add rows to the table. + * + * @return the button used to add rows to the table + */ + protected JButton getAddButton() { + return add; + } + + /** + * Enable or disable the delete button depending on whether or not there is + * a row to be deleted. + */ + protected void checkDeleteStatus() { + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } else { + delete.setEnabled(true); + } + + if(enableUpDown && tableModel.getRowCount()>1) { + up.setEnabled(true); + down.setEnabled(true); + } + } + + @Override + public void clearGui(){ + super.clearGui(); + clear(); + } + + /** + * Clear all rows from the table. T.Elanjchezhiyan(chezhiyan@siptech.co.in) + */ + public void clear() { + stopTableEditing(); + tableModel.clearData(); + } + + /** + * Invoked when an action occurs. This implementation supports the add and + * delete buttons. + * + * @param e + * the event that has occurred + */ + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(DELETE)) { + deleteArgument(); + } else if (action.equals(ADD)) { + addArgument(); + } else if (action.equals(ADD_FROM_CLIPBOARD)) { + addFromClipboard(); + } else if (action.equals(UP)) { + moveUp(); + } else if (action.equals(DOWN)) { + moveDown(); + } else if (action.equals(DETAIL)) { + showDetail(); + } + } + + /** + * Cancel cell editing if it is being edited + */ + private void cancelEditing() { + // If a table cell is being edited, we must cancel the editing before + // deleting the row + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + } + + /** + * Move a row down + */ + private void moveDown() { + cancelEditing(); + + int[] rowsSelected = table.getSelectedRows(); + if (rowsSelected.length > 0 && rowsSelected[rowsSelected.length - 1] < table.getRowCount() - 1) { + table.clearSelection(); + for (int i = rowsSelected.length - 1; i >= 0; i--) { + int rowSelected = rowsSelected[i]; + tableModel.moveRow(rowSelected, rowSelected + 1, rowSelected + 1); + } + for (int rowSelected : rowsSelected) { + table.addRowSelectionInterval(rowSelected + 1, rowSelected + 1); + } + } + } + + /** + * Move a row down + */ + private void moveUp() { + cancelEditing(); + + int[] rowsSelected = table.getSelectedRows(); + if (rowsSelected.length > 0 && rowsSelected[0] > 0) { + table.clearSelection(); + for (int rowSelected : rowsSelected) { + tableModel.moveRow(rowSelected, rowSelected + 1, rowSelected - 1); + } + for (int rowSelected : rowsSelected) { + table.addRowSelectionInterval(rowSelected - 1, rowSelected - 1); + } + } + } + + /** + * Show Row Detail + */ + private void showDetail() { + cancelEditing(); + + int[] rowsSelected = table.getSelectedRows(); + if (rowsSelected.length == 1) { + table.clearSelection(); + RowDetailDialog detailDialog = new RowDetailDialog(tableModel, rowsSelected[0]); + detailDialog.setVisible(true); + } + } + + /** + * Remove the currently selected argument from the table. + */ + protected void deleteArgument() { + cancelEditing(); + + int[] rowsSelected = table.getSelectedRows(); + int anchorSelection = table.getSelectionModel().getAnchorSelectionIndex(); + table.clearSelection(); + if (rowsSelected.length > 0) { + for (int i = rowsSelected.length - 1; i >= 0; i--) { + tableModel.removeRow(rowsSelected[i]); + } + + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + else if (tableModel.getRowCount() > 0) { + if (anchorSelection >= tableModel.getRowCount()) { + anchorSelection = tableModel.getRowCount() - 1; + } + table.setRowSelectionInterval(anchorSelection, anchorSelection); + } + + if(enableUpDown && tableModel.getRowCount()>1) { + up.setEnabled(true); + down.setEnabled(true); + } + } + } + + /** + * Add a new argument row to the table. + */ + protected void addArgument() { + // If a table cell is being edited, we should accept the current value + // and stop the editing before adding a new row. + stopTableEditing(); + + tableModel.addRow(makeNewArgument()); + + // Enable DELETE (which may already be enabled, but it won't hurt) + delete.setEnabled(true); + if(enableUpDown && tableModel.getRowCount()>1) { + up.setEnabled(true); + down.setEnabled(true); + } + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + + /** + * Add values from the clipboard + */ + protected void addFromClipboard() { + stopTableEditing(); + int rowCount = table.getRowCount(); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + Transferable trans = clipboard.getContents(null); + DataFlavor[] flavourList = trans.getTransferDataFlavors(); + Collection flavours = new ArrayList(flavourList.length); + if (Collections.addAll(flavours, flavourList) && flavours.contains(DataFlavor.stringFlavor)) { + try { + String clipboardContent = (String) trans.getTransferData(DataFlavor.stringFlavor); + String[] clipboardLines = clipboardContent.split("\n"); + for (String clipboardLine : clipboardLines) { + String[] clipboardCols = clipboardLine.split("\t"); + if (clipboardCols.length > 0) { + Argument argument = makeNewArgument(); + argument.setName(clipboardCols[0]); + if (clipboardCols.length > 1) { + argument.setValue(clipboardCols[1]); + if (clipboardCols.length > 2) { + argument.setDescription(clipboardCols[2]); + } + } + tableModel.addRow(argument); + } + } + } catch (IOException ioe) { + JOptionPane.showMessageDialog(this, + "Could not add read arguments from clipboard:\n" + ioe.getLocalizedMessage(), "Error", + JOptionPane.ERROR_MESSAGE); + } catch (UnsupportedFlavorException ufe) { + JOptionPane.showMessageDialog(this, + "Could not add retrieve " + DataFlavor.stringFlavor.getHumanPresentableName() + + " from clipboard" + ufe.getLocalizedMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + if (table.getRowCount() > rowCount) { + // Enable DELETE (which may already be enabled, but it won't hurt) + delete.setEnabled(true); + + // Highlight (select) the appropriate rows. + int rowToSelect = tableModel.getRowCount() - 1; + table.setRowSelectionInterval(rowCount, rowToSelect); + } + } + } + + /** + * Create a new Argument object. + * + * @return a new Argument object + */ + protected Argument makeNewArgument() { + return new Argument("", ""); // $NON-NLS-1$ // $NON-NLS-2$ + } + + /** + * Stop any editing that is currently being done on the table. This will + * save any changes that have already been made. + */ + protected void stopTableEditing() { + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.stopCellEditing(); + } + } + + /** + * Initialize the table model used for the arguments table. + */ + protected void initializeTableModel() { + if (tableModel == null) { + if(standalone) { + tableModel = new ObjectTableModel(new String[] { COLUMN_RESOURCE_NAMES_0, COLUMN_RESOURCE_NAMES_1, COLUMN_RESOURCE_NAMES_2 }, + Argument.class, + new Functor[] { + new Functor("getName"), // $NON-NLS-1$ + new Functor("getValue"), // $NON-NLS-1$ + new Functor("getDescription") }, // $NON-NLS-1$ + new Functor[] { + new Functor("setName"), // $NON-NLS-1$ + new Functor("setValue"), // $NON-NLS-1$ + new Functor("setDescription") }, // $NON-NLS-1$ + new Class[] { String.class, String.class, String.class }); + } else { + tableModel = new ObjectTableModel(new String[] { COLUMN_RESOURCE_NAMES_0, COLUMN_RESOURCE_NAMES_1 }, + Argument.class, + new Functor[] { + new Functor("getName"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + new Functor("setName"), // $NON-NLS-1$ + new Functor("setValue") }, // $NON-NLS-1$ + new Class[] { String.class, String.class }); + } + } + } + + public static boolean testFunctors(){ + ArgumentsPanel instance = new ArgumentsPanel(); + instance.initializeTableModel(); + return instance.tableModel.checkFunctors(null,instance.getClass()); + } + + /** + * Resize the table columns to appropriate widths. + * + * @param _table + * the table to resize columns for + */ + protected void sizeColumns(JTable _table) { + } + + /** + * Create the main GUI panel which contains the argument table. + * + * @return the main GUI panel + */ + private Component makeMainPanel() { + initializeTableModel(); + table = new JTable(tableModel); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + if (this.background != null) { + table.setBackground(this.background); + } + return makeScrollPane(table); + } + + /** + * Create a panel containing the title label for the table. + * + * @return a panel containing the title label + */ + protected Component makeLabelPanel() { + JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + labelPanel.add(tableLabel); + if (this.background != null) { + labelPanel.setBackground(this.background); + } + return labelPanel; + } + + /** + * Create a panel containing the add and delete buttons. + * + * @return a GUI panel containing the buttons + */ + private JPanel makeButtonPanel() { + showDetail = new JButton(JMeterUtils.getResString("detail")); // $NON-NLS-1$ + showDetail.setActionCommand(DETAIL); + showDetail.setEnabled(true); + + add = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ + add.setActionCommand(ADD); + add.setEnabled(true); + + addFromClipboard = new JButton(JMeterUtils.getResString("add_from_clipboard")); // $NON-NLS-1$ + addFromClipboard.setActionCommand(ADD_FROM_CLIPBOARD); + addFromClipboard.setEnabled(true); + + delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + delete.setActionCommand(DELETE); + + if(enableUpDown) { + up = new JButton(JMeterUtils.getResString("up")); // $NON-NLS-1$ + up.setActionCommand(UP); + + down = new JButton(JMeterUtils.getResString("down")); // $NON-NLS-1$ + down.setActionCommand(DOWN); + } + checkDeleteStatus(); + + JPanel buttonPanel = new JPanel(); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); + if (this.background != null) { + buttonPanel.setBackground(this.background); + } + showDetail.addActionListener(this); + add.addActionListener(this); + addFromClipboard.addActionListener(this); + delete.addActionListener(this); + buttonPanel.add(showDetail); + buttonPanel.add(add); + buttonPanel.add(addFromClipboard); + buttonPanel.add(delete); + if(enableUpDown) { + up.addActionListener(this); + down.addActionListener(this); + buttonPanel.add(up); + buttonPanel.add(down); + } + return buttonPanel; + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + JPanel p = this; + + if (standalone) { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + p = new JPanel(); + } + + p.setLayout(new BorderLayout()); + + p.add(makeLabelPanel(), BorderLayout.NORTH); + p.add(makeMainPanel(), BorderLayout.CENTER); + // Force a minimum table height of 70 pixels + p.add(Box.createVerticalStrut(70), BorderLayout.WEST); + p.add(makeButtonPanel(), BorderLayout.SOUTH); + + if (standalone) { + add(p, BorderLayout.CENTER); + } + + table.revalidate(); + sizeColumns(table); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/gui/LoginConfigGui.java b/ApacheJmeter/src/org/apache/jmeter/config/gui/LoginConfigGui.java new file mode 100644 index 0000000..9ad9311 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/gui/LoginConfigGui.java @@ -0,0 +1,167 @@ +/* + * 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.config.gui; + +import java.awt.BorderLayout; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; + +/** + * A GUI component allowing the user to enter a username and password for a + * login. + * + */ +public class LoginConfigGui extends AbstractConfigGui { + private static final long serialVersionUID = 240L; + + /** Field allowing the user to enter a username. */ + private JTextField username = new JTextField(15); + + /** Field allowing the user to enter a password. */ + private JPasswordField password = new JPasswordField(15); + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private boolean displayName = true; + + /** + * Create a new LoginConfigGui as a standalone component. + */ + public LoginConfigGui() { + this(true); + } + + /** + * Create a new LoginConfigGui as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public LoginConfigGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + public String getLabelResource() { + return "login_config_element"; // $NON-NLS-1$ + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + username.setText(element.getPropertyAsString(ConfigTestElement.USERNAME)); + password.setText(element.getPropertyAsString(ConfigTestElement.PASSWORD)); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + ConfigTestElement element = new ConfigTestElement(); + modifyTestElement(element); + return element; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement element) { + configureTestElement(element); + element.setProperty(new StringProperty(ConfigTestElement.USERNAME, username.getText())); + + String passwordString = new String(password.getPassword()); + element.setProperty(new StringProperty(ConfigTestElement.PASSWORD, passwordString)); + } + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + username.setText(""); //$NON-NLS-1$ + password.setText(""); //$NON-NLS-1$ + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + + VerticalPanel mainPanel = new VerticalPanel(); + mainPanel.add(createUsernamePanel()); + mainPanel.add(createPasswordPanel()); + add(mainPanel, BorderLayout.CENTER); + } + + /** + * Create a panel containing the username field and corresponding label. + * + * @return a GUI panel containing the username field + */ + private JPanel createUsernamePanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("username")); // $NON-NLS-1$ + label.setLabelFor(username); + panel.add(label, BorderLayout.WEST); + panel.add(username, BorderLayout.CENTER); + return panel; + } + + /** + * Create a panel containing the password field and corresponding label. + * + * @return a GUI panel containing the password field + */ + private JPanel createPasswordPanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("password")); // $NON-NLS-1$ + label.setLabelFor(password); + panel.add(label, BorderLayout.WEST); + panel.add(password, BorderLayout.CENTER); + return panel; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/gui/ObsoleteGui.java b/ApacheJmeter/src/org/apache/jmeter/config/gui/ObsoleteGui.java new file mode 100644 index 0000000..d9d89dc --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/gui/ObsoleteGui.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.config.gui; + +import java.awt.BorderLayout; +import java.util.Collection; + +import javax.swing.JLabel; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Default config gui for Configuration Element. + */ +public class ObsoleteGui extends AbstractJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + private JLabel obsoleteMessage = + new JLabel(JMeterUtils.getResString("obsolete_test_element")); // $NON-NLS-1$ + + public ObsoleteGui(){ + init(); + } + + private void init() { + setLayout(new BorderLayout(0, 10)); + setBorder(makeBorder()); + //add(makeTitlePanel(), BorderLayout.NORTH); + add(obsoleteMessage,BorderLayout.WEST); + } + + public String getLabelResource() { + return "obsolete_test_element"; // $NON-NLS-1$ + } + + public TestElement createTestElement() { + return new ConfigTestElement(); + } + + public void modifyTestElement(TestElement element) { + } + + public JPopupMenu createPopupMenu() { + return null; + } + + public Collection getMenuCategories() { + return null; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/config/gui/RowDetailDialog.java b/ApacheJmeter/src/org/apache/jmeter/config/gui/RowDetailDialog.java new file mode 100644 index 0000000..aa226f8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/gui/RowDetailDialog.java @@ -0,0 +1,201 @@ +/* + * 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.config.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JPanel; + +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.gui.JLabeledTextArea; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.gui.ObjectTableModel; + +/** + * Show detail of a Row + */ +public class RowDetailDialog extends JDialog implements ActionListener { + + /** + * + */ + private static final long serialVersionUID = 6578889215615435475L; + + /** Command for moving a row up in the table. */ + private static final String NEXT = "next"; // $NON-NLS-1$ + + /** Command for moving a row down in the table. */ + private static final String PREVIOUS = "previous"; // $NON-NLS-1$ + + /** Command for CANCEL. */ + private static final String CLOSE = "close"; // $NON-NLS-1$ + + private static final String UPDATE = "update"; // $NON-NLS-1$ + + private JLabeledTextField nameTF; + + private JLabeledTextArea valueTA; + + private JButton updateButton; + + private JButton nextButton; + + private JButton previousButton; + + private JButton closeButton; + + private ObjectTableModel tableModel; + + private int selectedRow; + + + public RowDetailDialog() { + super(); + } + /** + * Hide Window on ESC + */ + private transient ActionListener enterActionListener = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + doUpdate(actionEvent); + setVisible(false); + } + }; + + /** + * Do search on Enter + */ + private transient ActionListener escapeActionListener = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + setVisible(false); + } + }; + + public RowDetailDialog(ObjectTableModel tableModel, int selectedRow) { + super((JFrame) null, JMeterUtils.getResString("detail"), true); //$NON-NLS-1$ + this.tableModel = tableModel; + this.selectedRow = selectedRow; + init(); + } + + private void init() { + this.getContentPane().setLayout(new BorderLayout(10,10)); + + nameTF = new JLabeledTextField(JMeterUtils.getResString("name"), 20); //$NON-NLS-1$ + valueTA = new JLabeledTextArea(JMeterUtils.getResString("value")); //$NON-NLS-1$ + valueTA.setPreferredSize(new Dimension(450, 300)); + setValues(selectedRow); + JPanel detailPanel = new JPanel(); + detailPanel.setLayout(new BorderLayout()); + //detailPanel.setBorder(BorderFactory.createEmptyBorder(7, 3, 3, 3)); + detailPanel.add(nameTF, BorderLayout.NORTH); + detailPanel.add(valueTA, BorderLayout.CENTER); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(7, 3, 3, 3)); + mainPanel.add(detailPanel, BorderLayout.CENTER); + + JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + + updateButton = new JButton(JMeterUtils.getResString("update")); //$NON-NLS-1$ + updateButton.setActionCommand(UPDATE); + updateButton.addActionListener(this); + closeButton = new JButton(JMeterUtils.getResString("close")); //$NON-NLS-1$ + closeButton.setActionCommand(CLOSE); + closeButton.addActionListener(this); + nextButton = new JButton(JMeterUtils.getResString("next")); //$NON-NLS-1$ + nextButton.setActionCommand(NEXT); + nextButton.addActionListener(this); + nextButton.setEnabled(selectedRow < tableModel.getRowCount()-1); + previousButton = new JButton(JMeterUtils.getResString("previous")); //$NON-NLS-1$ + previousButton.setActionCommand(PREVIOUS); + previousButton.addActionListener(this); + previousButton.setEnabled(selectedRow > 0); + + buttonsPanel.add(updateButton); + buttonsPanel.add(previousButton); + buttonsPanel.add(nextButton); + buttonsPanel.add(closeButton); + mainPanel.add(buttonsPanel, BorderLayout.SOUTH); + this.getContentPane().add(mainPanel); + mainPanel.registerKeyboardAction(enterActionListener, KeyStrokes.ENTER, JComponent.WHEN_IN_FOCUSED_WINDOW); + mainPanel.registerKeyboardAction(escapeActionListener, KeyStrokes.ESC, JComponent.WHEN_IN_FOCUSED_WINDOW); + nameTF.requestFocusInWindow(); + + this.pack(); + ComponentUtil.centerComponentInWindow(this); + } + + /** + * Do search + * @param e {@link ActionEvent} + */ + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + if(action.equals(CLOSE)) { + this.setVisible(false); + } + else if(action.equals(NEXT)) { + selectedRow++; + previousButton.setEnabled(true); + nextButton.setEnabled(selectedRow < tableModel.getRowCount()-1); + setValues(selectedRow); + } + else if(action.equals(PREVIOUS)) { + selectedRow--; + nextButton.setEnabled(true); + previousButton.setEnabled(selectedRow > 0); + setValues(selectedRow); + } + else if(action.equals(UPDATE)) { + doUpdate(e); + } + } + + /** + * Set TextField and TA values from model + * @param selectedRow Selected row + */ + private void setValues(int selectedRow) { + nameTF.setText((String)tableModel.getValueAt(selectedRow, 0)); + valueTA.setText((String)tableModel.getValueAt(selectedRow, 1)); + } + + /** + * Update model values + * @param actionEvent + */ + protected void doUpdate(ActionEvent actionEvent) { + tableModel.setValueAt(nameTF.getText(), selectedRow, 0); + tableModel.setValueAt(valueTA.getText(), selectedRow, 1); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/config/gui/SimpleConfigGui.java b/ApacheJmeter/src/org/apache/jmeter/config/gui/SimpleConfigGui.java new file mode 100644 index 0000000..63f2d7e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/config/gui/SimpleConfigGui.java @@ -0,0 +1,313 @@ +/* + * 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.config.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.PowerTableModel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.Data; + +/** + * Default config gui for Configuration Element. + */ +public class SimpleConfigGui extends AbstractConfigGui implements ActionListener { + /* This class created for enhancement Bug ID 9101. */ + + private static final long serialVersionUID = 240L; + + // TODO: This class looks a lot like ArgumentsPanel. What exactly is the + // difference? Could they be combined? + // Note: it seems that this class is not actually used ... + + /** The table of configuration parameters. */ + private JTable table; + + /** The model for the parameter table. */ + private PowerTableModel tableModel; + + /** A button for adding new parameters to the table. */ + private JButton add; + + /** A button for removing parameters from the table. */ + private JButton delete; + + /** Command for adding a row to the table. */ + private static final String ADD = "add"; + + /** Command for removing a row from the table. */ + private static final String DELETE = "delete"; + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private final boolean displayName; + + /** The resource names of the columns in the table. */ + private static final String COLUMN_NAMES_0 = "name"; // $NON-NLS-1$ + + private static final String COLUMN_NAMES_1 = "value"; // $NON-NLS-1$ + + /** + * Create a new standalone SimpleConfigGui. + */ + public SimpleConfigGui() { + this(true); + } + + /** + * Create a new SimpleConfigGui as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public SimpleConfigGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + public String getLabelResource() { + return "simple_config_element"; // $NON-NLS-1$ + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + *

+ * This implementation retrieves all key/value pairs from the TestElement + * object and sets these values in the GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + tableModel.clearData(); + PropertyIterator iter = el.propertyIterator(); + while (iter.hasNext()) { + JMeterProperty prop = iter.next(); + tableModel.addRow(new Object[] { prop.getName(), prop.getStringValue() }); + } + checkDeleteStatus(); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + TestElement el = new ConfigTestElement(); + modifyTestElement(el); + return el; + } + + /** + * Get all of the values from the GUI component and set them in the + * TestElement. + * + * @param el + * the TestElement to modify + */ + public void modifyTestElement(TestElement el) { + if (table.isEditing()) { + table.getCellEditor().stopCellEditing(); + } + Data model = tableModel.getData(); + model.reset(); + while (model.next()) { + el.setProperty(new StringProperty((String) model.getColumnValue(COLUMN_NAMES_0), (String) model + .getColumnValue(COLUMN_NAMES_1))); + } + super.configureTestElement(el); + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(0, 10)); + + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + + add(createTablePanel(), BorderLayout.CENTER); + // Force the table to be at least 70 pixels high + add(Box.createVerticalStrut(70), BorderLayout.WEST); + add(createButtonPanel(), BorderLayout.SOUTH); + } + + /** + * Invoked when an action occurs. This implementation supports the add and + * delete buttons. + * + * @param e + * the event that has occurred + */ + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(DELETE)) { + deleteArgument(); + } else if (action.equals(ADD)) { + addArgument(); + } + } + + /** + * Create a GUI panel containing the table of configuration parameters. + * + * @return a GUI panel containing the parameter table + */ + private Component createTablePanel() { + tableModel = new PowerTableModel( + new String[] { COLUMN_NAMES_0, COLUMN_NAMES_1 }, + new Class[] { String.class, String.class }); + + table = new JTable(tableModel); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + return makeScrollPane(table); + } + + /** + * Create a panel containing the add and delete buttons. + * + * @return a GUI panel containing the buttons + */ + private JPanel createButtonPanel() { + add = new JButton(JMeterUtils.getResString("add")); //$NON-NLS-1$ + add.setActionCommand(ADD); + add.addActionListener(this); + add.setEnabled(true); + + delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + delete.setActionCommand(DELETE); + delete.addActionListener(this); + + checkDeleteStatus(); + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(add); + buttonPanel.add(delete); + return buttonPanel; + } + + /** + * Enable or disable the delete button depending on whether or not there is + * a row to be deleted. + */ + protected void checkDeleteStatus() { + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } else { + delete.setEnabled(true); + } + } + + /** + * Add a new argument row to the table. + */ + protected void addArgument() { + // If a table cell is being edited, we should accept the current value + // and stop the editing before adding a new row. + stopTableEditing(); + + tableModel.addNewRow(); + tableModel.fireTableDataChanged(); + + // Enable DELETE (which may already be enabled, but it won't hurt) + delete.setEnabled(true); + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + + /** + * Stop any editing that is currently being done on the table. This will + * save any changes that have already been made. + */ + protected void stopTableEditing() { + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.stopCellEditing(); + } + } + + /** + * Remove the currently selected argument from the table. + */ + protected void deleteArgument() { + // If a table cell is being edited, we must cancel the editing before + // deleting the row + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = table.getSelectedRow(); + + if (rowSelected >= 0) { + + // removeProperty(tableModel.getValueAt ( + // table.getSelectedRow(),0).toString()); + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } else { + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/Controller.java b/ApacheJmeter/src/org/apache/jmeter/control/Controller.java new file mode 100644 index 0000000..dd20e6d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/Controller.java @@ -0,0 +1,70 @@ +/* + * 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.control; + +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; + +/** + * This interface is used by JMeterThread in the following manner: + * + * while (running && (sampler = controller.next()) != null) + */ +public interface Controller extends TestElement { + /** + * Delivers the next Sampler or null + * + * @return org.apache.jmeter.samplers.Sampler or null + */ + public Sampler next(); + + /** + * Indicates whether the Controller is done delivering Samplers for the rest + * of the test. + * + * When the top-level controller returns true to JMeterThread, + * the thread is complete. + * + * @return boolean + */ + public boolean isDone(); + + /** + * Controllers have to notify listeners of when they begin an iteration + * through their sub-elements. + */ + public void addIterationListener(LoopIterationListener listener); + + /** + * Called to initialize a controller at the beginning of a test iteration. + */ + public void initialize(); + + /** + * Unregister IterationListener + * @param iterationListener {@link LoopIterationListener} + */ + public void removeIterationListener(LoopIterationListener iterationListener); + + /** + * Trigger end of loop condition on controller (used by Start Next Loop feature) + */ + public void triggerEndOfLoop(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/ForeachController.java b/ApacheJmeter/src/org/apache/jmeter/control/ForeachController.java new file mode 100644 index 0000000..57a8324 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/ForeachController.java @@ -0,0 +1,194 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class ForeachController extends GenericController implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private final static String INPUTVAL = "ForeachController.inputVal";// $NON-NLS-1$ + + private final static String RETURNVAL = "ForeachController.returnVal";// $NON-NLS-1$ + + private final static String USE_SEPARATOR = "ForeachController.useSeparator";// $NON-NLS-1$ + + private int loopCount = 0; + + private static final String DEFAULT_SEPARATOR = "_";// $NON-NLS-1$ + + public ForeachController() { + } + + public void setInputVal(String inputValue) { + setProperty(new StringProperty(INPUTVAL, inputValue)); + } + + private String getInputVal() { + getProperty(INPUTVAL).recoverRunningVersion(null); + return getInputValString(); + } + + public String getInputValString() { + return getPropertyAsString(INPUTVAL); + } + + public void setReturnVal(String inputValue) { + setProperty(new StringProperty(RETURNVAL, inputValue)); + } + + private String getReturnVal() { + getProperty(RETURNVAL).recoverRunningVersion(null); + return getReturnValString(); + } + + public String getReturnValString() { + return getPropertyAsString(RETURNVAL); + } + + private String getSeparator() { + return getUseSeparator() ? DEFAULT_SEPARATOR : "";// $NON-NLS-1$ + } + + public void setUseSeparator(boolean b) { + setProperty(new BooleanProperty(USE_SEPARATOR, b)); + } + + public boolean getUseSeparator() { + return getPropertyAsBoolean(USE_SEPARATOR, true); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isDone() { + JMeterContext context = getThreadContext(); + String inputVariable = getInputVal() + getSeparator() + (loopCount + 1); + final JMeterVariables variables = context.getVariables(); + final Object currentVariable = variables.getObject(inputVariable); + if (currentVariable != null) { + variables.putObject(getReturnVal(), currentVariable); + if (log.isDebugEnabled()) { + log.debug("ForEach resultstring isDone=" + variables.get(getReturnVal())); + } + return false; + } + return super.isDone(); + } + + private boolean endOfArguments() { + JMeterContext context = getThreadContext(); + String inputVariable = getInputVal() + getSeparator() + (loopCount + 1); + if (context.getVariables().getObject(inputVariable) != null) { + log.debug("ForEach resultstring eofArgs= false"); + return false; + } + log.debug("ForEach resultstring eofArgs= true"); + return true; + } + + // Prevent entry if nothing to do + @Override + public Sampler next() { + if (emptyList()) { + reInitialize(); + resetLoopCount(); + return null; + } + return super.next(); + } + + /** + * Check if there are any matching entries + * + * @return whether any entries in the list + */ + private boolean emptyList() { + JMeterContext context = getThreadContext(); + String inputVariable = getInputVal() + getSeparator() + "1";// $NON-NLS-1$ + if (context.getVariables().getObject(inputVariable) != null) { + return false; + } + if (log.isDebugEnabled()) { + log.debug("No entries found - null first entry: " + inputVariable); + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + protected Sampler nextIsNull() throws NextIsNullException { + reInitialize(); + if (endOfArguments()) { + // setDone(true); + resetLoopCount(); + return null; + } + return next(); + } + + protected void incrementLoopCount() { + loopCount++; + } + + protected void resetLoopCount() { + loopCount = 0; + } + + /** + * {@inheritDoc} + */ + @Override + protected int getIterCount() { + return loopCount + 1; + } + + /** + * {@inheritDoc} + */ + @Override + protected void reInitialize() { + setFirst(true); + resetCurrent(); + incrementLoopCount(); + recoverRunningVersion(); + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEndOfLoop() { + super.triggerEndOfLoop(); + resetLoopCount(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/control/GenericController.java b/ApacheJmeter/src/org/apache/jmeter/control/GenericController.java new file mode 100644 index 0000000..fad5d56 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/GenericController.java @@ -0,0 +1,402 @@ +/* + * 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.control; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + *

+ * This class is the basis for all the controllers. + * It also implements SimpleController. + *

+ *

+ * The main entry point is next(), which is called by by JMeterThread as follows: + *

+ *

+ * while (running && (sampler = controller.next()) != null) + *

+ */ +public class GenericController extends AbstractTestElement implements Controller, Serializable { + + private static final long serialVersionUID = 234L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private transient LinkedList iterationListeners = + new LinkedList(); + + // May be replaced by RandomOrderController + protected transient List subControllersAndSamplers = + new ArrayList(); + + /** + * Index of current sub controller or sampler + */ + protected transient int current; + + /** + * TODO document this + */ + private transient int iterCount; + + /** + * Controller has ended + */ + private transient boolean done; + + /** + * First sampler or sub-controller + */ + private transient boolean first; + + /** + * Creates a Generic Controller + */ + public GenericController() { + } + + public void initialize() { + resetCurrent(); + resetIterCount(); + done = false; // TODO should this use setDone()? + first = true; // TODO should this use setFirst()? + TestElement elem; + for (int i = 0; i < subControllersAndSamplers.size(); i++) { + elem = subControllersAndSamplers.get(i); + if (elem instanceof Controller) { + ((Controller) elem).initialize(); + } + } + } + + /** + * Resets the controller: + *
    + *
  • resetCurrent() (i.e. current=0)
  • + *
  • increment iteration count
  • + *
  • sets first=true
  • + *
  • recoverRunningVersion() to set the controller back to the initial state
  • + *
+ * + */ + protected void reInitialize() { + resetCurrent(); + incrementIterCount(); + setFirst(true); + recoverRunningVersion(); + } + + /** + *

+ * Determines the next sampler to be processed. + *

+ * + *

+ * If isDone, returns null. + *

+ * + *

+ * Gets the list element using current pointer. + * If this is null, calls {@link #nextIsNull()}. + *

+ * + *

+ * If the list element is a sampler, calls {@link #nextIsASampler(Sampler)}, + * otherwise calls {@link #nextIsAController(Controller)} + *

+ * + *

+ * If any of the called methods throws NextIsNullException, returns null, + * otherwise the value obtained above is returned. + *

+ * + * @return the next sampler or null + */ + public Sampler next() { + fireIterEvents(); + if (log.isDebugEnabled()) { + log.debug("Calling next on: " + this.getClass().getName()); + } + if (isDone()) { + return null; + } + Sampler returnValue = null; + try { + TestElement currentElement = getCurrentElement(); + setCurrentElement(currentElement); + if (currentElement == null) { + // incrementCurrent(); + returnValue = nextIsNull(); + } else { + if (currentElement instanceof Sampler) { + returnValue = nextIsASampler((Sampler) currentElement); + } else { // must be a controller + returnValue = nextIsAController((Controller) currentElement); + } + } + } catch (NextIsNullException e) { + } + return returnValue; + } + + /** + * @see org.apache.jmeter.control.Controller#isDone() + */ + public boolean isDone() { + return done; + } + + protected void setDone(boolean done) { + this.done = done; + } + + protected boolean isFirst() { + return first; + } + + public void setFirst(boolean b) { + first = b; + } + + /** + * Called by next() if the element is a Controller, + * and returns the next sampler from the controller. + * If this is null, then updates the current pointer and makes recursive call to next(). + * @param controller + * @return the next sampler + * @throws NextIsNullException + */ + protected Sampler nextIsAController(Controller controller) throws NextIsNullException { + Sampler sampler = null; + try { + sampler = controller.next(); + } catch (StackOverflowError soe) { + // See bug 50618 Catches a StackOverflowError when a condition returns + // always false (after at least one iteration with return true) + log.warn("StackOverflowError detected"); // $NON-NLS-1$ + throw new NextIsNullException(); + } + if (sampler == null) { + currentReturnedNull(controller); + sampler = next(); + } + return sampler; + } + + /** + * Increment the current pointer and return the element. + * Called by next() if the element is a sampler. + * (May be overriden by sub-classes). + * + * @param element + * @return input element + * @throws NextIsNullException + */ + protected Sampler nextIsASampler(Sampler element) throws NextIsNullException { + incrementCurrent(); + return element; + } + + /** + * Called by next() when getCurrentElement() returns null. + * Reinitialises the controller. + * + * @return null (always, for this class) + * @throws NextIsNullException + */ + protected Sampler nextIsNull() throws NextIsNullException { + reInitialize(); + return null; + } + + /** + * {@inheritDoc} + */ + public void triggerEndOfLoop() { + reInitialize(); + } + + /** + * Called to re-initialize a index of controller's elements (Bug 50032) + * + */ + protected void reInitializeSubController() { + boolean wasFlagSet = getThreadContext().setIsReinitializingSubControllers(); + try { + TestElement currentElement = getCurrentElement(); + if (currentElement != null) { + if (currentElement instanceof Sampler) { + nextIsASampler((Sampler) currentElement); + } else { // must be a controller + if (nextIsAController((Controller) currentElement) != null) { + reInitializeSubController(); + } + } + } + } catch (NextIsNullException e) { + } finally { + if (wasFlagSet) { + getThreadContext().unsetIsReinitializingSubControllers(); + } + } + } + + /** + * If the controller is done, remove it from the list, + * otherwise increment to next entry in list. + * + * @param c controller + */ + protected void currentReturnedNull(Controller c) { + if (c.isDone()) { + removeCurrentElement(); + } else { + incrementCurrent(); + } + } + + /** + * Gets the SubControllers attribute of the GenericController object + * + * @return the SubControllers value + */ + protected List getSubControllers() { + return subControllersAndSamplers; + } + + private void addElement(TestElement child) { + subControllersAndSamplers.add(child); + } + + /** + * Empty implementation - does nothing. + * + * @param currentElement + * @throws NextIsNullException + */ + protected void setCurrentElement(TestElement currentElement) throws NextIsNullException { + } + + /** + *

+ * Gets the element indicated by the current index, if one exists, + * from the subControllersAndSamplers list. + *

+ *

+ * If the subControllersAndSamplers list is empty, + * then set done = true, and throw NextIsNullException. + *

+ * @return the current element - or null if current index too large + * @throws NextIsNullException if list is empty + */ + protected TestElement getCurrentElement() throws NextIsNullException { + if (current < subControllersAndSamplers.size()) { + return subControllersAndSamplers.get(current); + } + if (subControllersAndSamplers.size() == 0) { + setDone(true); + throw new NextIsNullException(); + } + return null; + } + + protected void removeCurrentElement() { + subControllersAndSamplers.remove(current); + } + + /** + * Increments the current pointer; called by currentReturnedNull to move the + * controller on to its next child. + */ + protected void incrementCurrent() { + current++; + } + + protected void resetCurrent() { + current = 0; + } + + @Override + public void addTestElement(TestElement child) { + if (child instanceof Controller || child instanceof Sampler) { + addElement(child); + } + } + + public void addIterationListener(LoopIterationListener lis) { + /* + * A little hack - add each listener to the start of the list - this + * ensures that the thread running the show is the first listener and + * can modify certain values before other listeners are called. + */ + iterationListeners.addFirst(lis); + } + + /** + * Remove listener + */ + public void removeIterationListener(LoopIterationListener iterationListener) { + for (Iterator iterator = iterationListeners.iterator(); iterator.hasNext();) { + LoopIterationListener listener = iterator.next(); + if(listener == iterationListener) + { + iterator.remove(); + break; // can only match once + } + } + } + + protected void fireIterEvents() { + if (isFirst()) { + fireIterationStart(); + first = false; // TODO - should this use setFirst() ? + } + } + + protected void fireIterationStart() { + LoopIterationEvent event = new LoopIterationEvent(this, getIterCount()); + for (LoopIterationListener item : iterationListeners) { + item.iterationStart(event); + } + } + + protected int getIterCount() { + return iterCount; + } + + protected void incrementIterCount() { + iterCount++; + } + + protected void resetIterCount() { + iterCount = 0; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/IfController.java b/ApacheJmeter/src/org/apache/jmeter/control/IfController.java new file mode 100644 index 0000000..37a3776 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/IfController.java @@ -0,0 +1,209 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +/** + * + * + * This is a Conditional Controller; it will execute the set of statements + * (samplers/controllers, etc) while the 'condition' is true. + *

+ * In a programming world - this is equivalant of : + *

+ * if (condition) {
+ *          statements ....
+ *          }
+ * 
+ * In JMeter you may have : + *
 
+ * Thread-Group (set to loop a number of times or indefinitely,
+ *    ... Samplers ... (e.g. Counter )
+ *    ... Other Controllers ....
+ *    ... IfController ( condition set to something like - ${counter}<10)
+ *       ... statements to perform if condition is true
+ *       ...
+ *    ... Other Controllers /Samplers }
+ * 
+ */ + +// for unit test code @see TestIfController + +public class IfController extends GenericController implements Serializable { + + private static final Logger logger = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private final static String CONDITION = "IfController.condition"; //$NON-NLS-1$ + + private final static String EVALUATE_ALL = "IfController.evaluateAll"; //$NON-NLS-1$ + + private static final String USE_EXPRESSION = "IfController.useExpression"; //$NON-NLS-1$ + + /** + * constructor + */ + public IfController() { + super(); + } + + /** + * constructor + */ + public IfController(String condition) { + super(); + this.setCondition(condition); + } + + /** + * Condition Accessor - this is gonna be like ${count}<10 + */ + public void setCondition(String condition) { + setProperty(new StringProperty(CONDITION, condition)); + } + + /** + * Condition Accessor - this is gonna be like ${count}<10 + */ + public String getCondition() { + return getPropertyAsString(CONDITION); + } + + /** + * evaluate the condition clause log error if bad condition + */ + private boolean evaluateCondition(String cond) { + logger.debug(" getCondition() : [" + cond + "]"); + + String resultStr = ""; + boolean result = false; + + // now evaluate the condition using JavaScript + Context cx = Context.enter(); + try { + Scriptable scope = cx.initStandardObjects(null); + Object cxResultObject = cx.evaluateString(scope, cond + /** * conditionString ** */ + , "", 1, null); + resultStr = Context.toString(cxResultObject); + + if (resultStr.equals("false")) { //$NON-NLS-1$ + result = false; + } else if (resultStr.equals("true")) { //$NON-NLS-1$ + result = true; + } else { + throw new Exception(" BAD CONDITION :: " + cond + " :: expected true or false"); + } + + logger.debug(" >> evaluate Condition - [ " + cond + "] results is [" + result + "]"); + } catch (Exception e) { + logger.error(getName()+": error while processing "+ "[" + cond + "]\n", e); + } finally { + Context.exit(); + } + + return result; + } + + private static boolean evaluateExpression(String cond) { + return cond.equalsIgnoreCase("true"); // $NON-NLS-1$ + } + + /** + * This is overriding the parent method. IsDone indicates whether the + * termination condition is reached. I.e. if the condition evaluates to + * False - then isDone() returns TRUE + */ + @Override + public boolean isDone() { + // boolean result = true; + // try { + // result = !evaluateCondition(); + // } catch (Exception e) { + // logger.error(e.getMessage(), e); + // } + // setDone(true); + // return result; + // setDone(false); + return false; + } + + /** + * @see org.apache.jmeter.control.Controller#next() + */ + @Override + public Sampler next() { + // We should only evalute the condition if it is the first + // time ( first "iteration" ) we are called. + // For subsequent calls, we are inside the IfControllerGroup, + // so then we just pass the control to the next item inside the if control + boolean result = true; + if(isEvaluateAll() || isFirst()) { + result = isUseExpression() ? + evaluateExpression(getCondition()) + : + evaluateCondition(getCondition()); + } + + if (result) { + return super.next(); + } + // If-test is false, need to re-initialize indexes + try { + reInitializeSubController(); // Bug 50032 - reinitialize current index element for all sub controller + return nextIsNull(); + } catch (NextIsNullException e1) { + return null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEndOfLoop() { + reInitializeSubController(); + super.triggerEndOfLoop(); + } + + public boolean isEvaluateAll() { + return getPropertyAsBoolean(EVALUATE_ALL,false); + } + + public void setEvaluateAll(boolean b) { + setProperty(EVALUATE_ALL,b); + } + + public boolean isUseExpression() { + return getPropertyAsBoolean(USE_EXPRESSION, false); + } + + public void setUseExpression(boolean selected) { + setProperty(USE_EXPRESSION, selected, false); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/IncludeController.java b/ApacheJmeter/src/org/apache/jmeter/control/IncludeController.java new file mode 100644 index 0000000..da65d31 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/IncludeController.java @@ -0,0 +1,212 @@ +/* + * 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.control; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import java.util.LinkedList; + +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.control.TestFragmentController; + +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class IncludeController extends GenericController implements ReplaceableController { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final String INCLUDE_PATH = "IncludeController.includepath"; //$NON-NLS-1$ + + private static final String prefix = + JMeterUtils.getPropDefault( + "includecontroller.prefix", //$NON-NLS-1$ + ""); //$NON-NLS-1$ + + private HashTree SUBTREE = null; + private TestElement SUB = null; + + /** + * No-arg constructor + * + * @see java.lang.Object#Object() + */ + public IncludeController() { + super(); + } + + @Override + public Object clone() { + // TODO - fix so that this is only called once per test, instead of at every clone + // Perhaps save previous filename, and only load if it has changed? + this.resolveReplacementSubTree(null); + IncludeController clone = (IncludeController) super.clone(); + clone.setIncludePath(this.getIncludePath()); + if (this.SUBTREE != null) { + if (this.SUBTREE.keySet().size() == 1) { + Iterator itr = this.SUBTREE.keySet().iterator(); + while (itr.hasNext()) { + this.SUB = (TestElement) itr.next(); + } + } + clone.SUBTREE = (HashTree)this.SUBTREE.clone(); + clone.SUB = this.SUB==null ? null : (TestElement) this.SUB.clone(); + } + return clone; + } + + /** + * In the event an user wants to include an external JMX test plan + * the GUI would call this. + * @param jmxfile + */ + public void setIncludePath(String jmxfile) { + this.setProperty(INCLUDE_PATH,jmxfile); + } + + /** + * return the JMX file path. + * @return the JMX file path + */ + public String getIncludePath() { + return this.getPropertyAsString(INCLUDE_PATH); + } + + /** + * The way ReplaceableController works is clone is called first, + * followed by replace(HashTree) and finally getReplacement(). + */ + public HashTree getReplacementSubTree() { + return SUBTREE; + } + + public TestElement getReplacementElement() { + return SUB; + } + + public void resolveReplacementSubTree(JMeterTreeNode context) { + this.SUBTREE = this.loadIncludedElements(); + } + + /** + * load the included elements using SaveService + */ + protected HashTree loadIncludedElements() { + // only try to load the JMX test plan if there is one + final String includePath = getIncludePath(); + InputStream reader = null; + HashTree tree = null; + if (includePath != null && includePath.length() > 0) { + try { + String fileName=prefix+includePath; + File file = new File(fileName); + final String absolutePath = file.getAbsolutePath(); + log.info("loadIncludedElements -- try to load included module: "+absolutePath); + if(!file.exists() && !file.isAbsolute()){ + log.info("loadIncludedElements -failed for: "+absolutePath); + file = new File(FileServer.getFileServer().getBaseDir(), includePath); + log.info("loadIncludedElements -Attempting to read it from: "+absolutePath); + if(!file.exists()){ + log.error("loadIncludedElements -failed for: "+absolutePath); + throw new IOException("loadIncludedElements -failed for: "+absolutePath); + } + } + + reader = new FileInputStream(file); + tree = SaveService.loadTree(reader); + // filter the tree for a TestFragment. + tree = getProperBranch(tree); + removeDisabledItems(tree); + return tree; + } catch (NoClassDefFoundError ex) // Allow for missing optional jars + { + String msg = ex.getMessage(); + if (msg == null) { + msg = "Missing jar file - see log for details"; + } + log.warn("Missing jar file", ex); + JMeterUtils.reportErrorToUser(msg); + } catch (FileNotFoundException ex) { + String msg = ex.getMessage(); + JMeterUtils.reportErrorToUser(msg); + log.warn(msg); + } catch (Exception ex) { + String msg = ex.getMessage(); + if (msg == null) { + msg = "Unexpected error - see log for details"; + } + JMeterUtils.reportErrorToUser(msg); + log.warn("Unexpected error", ex); + } + finally{ + JOrphanUtils.closeQuietly(reader); + } + } + return tree; + } + + private HashTree getProperBranch(HashTree tree) { + Iterator iter = new LinkedList(tree.list()).iterator(); + while (iter.hasNext()) { + TestElement item = (TestElement) iter.next(); + + //if we found a TestPlan, then we are on our way to the TestFragment + if (item instanceof TestPlan) + { + return getProperBranch(tree.getTree(item)); + } + + if (item instanceof TestFragmentController) + { + return tree.getTree(item); + } + } + //return the tree since we didn't find a TestFragment. This will mimic the + //old behavior to import an exact node. + return tree; + } + + + private void removeDisabledItems(HashTree tree) { + Iterator iter = new LinkedList(tree.list()).iterator(); + while (iter.hasNext()) { + TestElement item = (TestElement) iter.next(); + if (!item.isEnabled()) { + //log.info("Removing "+item.toString()); + tree.remove(item); + } else { + //log.info("Keeping "+item.toString()); + removeDisabledItems(tree.getTree(item));// Recursive call + } + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/InterleaveControl.java b/ApacheJmeter/src/org/apache/jmeter/control/InterleaveControl.java new file mode 100644 index 0000000..e8d2264 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/InterleaveControl.java @@ -0,0 +1,174 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.IntegerProperty; + +public class InterleaveControl extends GenericController implements Serializable { + private static final long serialVersionUID = 233L; + + private static final String STYLE = "InterleaveControl.style";// $NON-NLS-1$ + + public static final int IGNORE_SUB_CONTROLLERS = 0; + + public static final int USE_SUB_CONTROLLERS = 1; + + private boolean skipNext; + + private transient TestElement searchStart = null; + + private boolean currentReturnedAtLeastOne; + + private boolean stillSame = true; + + /*************************************************************************** + * Constructor for the InterleaveControl object + **************************************************************************/ + public InterleaveControl() { + } + + /** + * {@inheritDoc} + */ + @Override + public void reInitialize() { + setFirst(true); + currentReturnedAtLeastOne = false; + searchStart = null; + stillSame = true; + skipNext = false; + incrementIterCount(); + recoverRunningVersion(); + } + + public void setStyle(int style) { + setProperty(new IntegerProperty(STYLE, style)); + } + + public int getStyle() { + return getPropertyAsInt(STYLE); + } + + /** + * {@inheritDoc} + */ + @Override + public Sampler next() { + if (isSkipNext()) { + reInitialize(); + return null; + } + return super.next(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Sampler nextIsAController(Controller controller) throws NextIsNullException { + Sampler sampler = controller.next(); + if (sampler == null) { + currentReturnedNull(controller); + return next(); + } + currentReturnedAtLeastOne = true; + if (getStyle() == IGNORE_SUB_CONTROLLERS) { + incrementCurrent(); + skipNext = true; + } else { + searchStart = null; + } + return sampler; + } + + /** + * {@inheritDoc} + */ + @Override + protected Sampler nextIsASampler(Sampler element) throws NextIsNullException { + skipNext = true; + incrementCurrent(); + return element; + } + + /** + * If the current is null, reset and continue searching. The searchStart + * attribute will break us off when we start a repeat. + *

+ * {@inheritDoc} + */ + @Override + protected Sampler nextIsNull() { + resetCurrent(); + return next(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setCurrentElement(TestElement currentElement) throws NextIsNullException { + // Set the position when next is first called, and don't overwrite + // until reInitialize is called. + if (searchStart == null) { + searchStart = currentElement; + } else if (searchStart == currentElement && !stillSame) { + // We've gone through the whole list and are now back at the start + // point of our search. + reInitialize(); + throw new NextIsNullException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void currentReturnedNull(Controller c) { + if (c.isDone()) { + removeCurrentElement(); + } else if (getStyle() == USE_SUB_CONTROLLERS) { + incrementCurrent(); + } + } + + protected boolean isSkipNext() { + return skipNext; + } + + protected void setSkipNext(boolean skipNext) { + this.skipNext = skipNext; + } + + /** + * {@inheritDoc} + */ + @Override + protected void incrementCurrent() { + if (currentReturnedAtLeastOne) { + skipNext = true; + } + stillSame = false; + super.incrementCurrent(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/LoopController.java b/ApacheJmeter/src/org/apache/jmeter/control/LoopController.java new file mode 100644 index 0000000..09ae1e7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/LoopController.java @@ -0,0 +1,175 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; + +/** + * Class that implements the Loop Controller. + */ +public class LoopController extends GenericController implements Serializable { + + private final static String LOOPS = "LoopController.loops"; // $NON-NLS-1$ + + private static final long serialVersionUID = 232L; + + /* + * In spite of the name, this is actually used to determine if the loop controller is repeatable. + * + * The value is only used in nextIsNull() when the loop end condition has been detected: + * If forever==true, then it calls resetLoopCount(), otherwise it calls setDone(true). + * + * Loop Controllers always set forever=true, so that they will be executed next time + * the parent invokes them. + * + * Thread Group sets the value false, so nextIsNull() sets done, and the Thread Group will not be repeated. + * However, it's not clear that a Thread Group could ever be repeated. + */ + private final static String CONTINUE_FOREVER = "LoopController.continue_forever"; // $NON-NLS-1$ + + private transient int loopCount = 0; + + public LoopController() { + setContinueForever_private(true); + } + + public void setLoops(int loops) { + setProperty(new IntegerProperty(LOOPS, loops)); + } + + public void setLoops(String loopValue) { + setProperty(new StringProperty(LOOPS, loopValue)); + } + + public int getLoops() { + try { + JMeterProperty prop = getProperty(LOOPS); + return Integer.parseInt(prop.getStringValue()); + } catch (NumberFormatException e) { + return 0; + } + } + + public String getLoopString() { + return getPropertyAsString(LOOPS); + } + + /** + * Determines whether the loop will return any samples if it is rerun. + * + * @param forever + * true if the loop must be reset after ending a run + */ + public void setContinueForever(boolean forever) { + setContinueForever_private(forever); + } + + private void setContinueForever_private(boolean forever) { + setProperty(new BooleanProperty(CONTINUE_FOREVER, forever)); + } + + private boolean getContinueForever() { + return getPropertyAsBoolean(CONTINUE_FOREVER); + } + + /** + * {@inheritDoc} + */ + @Override + public Sampler next() { + if(endOfLoop()) { + if (!getContinueForever()) { + setDone(true); + } + return null; + } + return super.next(); + } + + private boolean endOfLoop() { + final int loops = getLoops(); + return (loops > -1) && (loopCount >= loops); + } + + /** + * {@inheritDoc} + */ + @Override + protected Sampler nextIsNull() throws NextIsNullException { + reInitialize(); + if (endOfLoop()) { + if (!getContinueForever()) { + setDone(true); + } else { + resetLoopCount(); + } + return null; + } + return next(); + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEndOfLoop() { + super.triggerEndOfLoop(); + resetLoopCount(); + } + + protected void incrementLoopCount() { + loopCount++; + } + + protected void resetLoopCount() { + loopCount = 0; + } + + /** + * {@inheritDoc} + */ + @Override + protected int getIterCount() { + return loopCount + 1; + } + + /** + * {@inheritDoc} + */ + @Override + protected void reInitialize() { + setFirst(true); + resetCurrent(); + incrementLoopCount(); + recoverRunningVersion(); + } + + /** + * Start next iteration + */ + public void startNextLoop() { + reInitialize(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/control/ModuleController.java b/ApacheJmeter/src/org/apache/jmeter/control/ModuleController.java new file mode 100644 index 0000000..634b79c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/ModuleController.java @@ -0,0 +1,201 @@ +/* + * 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.control; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.tree.TreeNode; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; + +/** + * The goal of ModuleController is to add modularity to JMeter. The general idea + * is that web applications consist of small units of functionality (i.e. Logon, + * Create Account, Logoff...) which consist of requests that implement the + * functionality. These small units of functionality can be stored in + * SimpleControllers as modules that can be linked together quickly to form + * tests. ModuleController facilitates this by acting as a pointer to any + * controller that sits under the WorkBench. The controller and it's subelements + * will be substituted in place of the ModuleController at runtime. Config + * elements can be attached to the ModuleController to alter the functionality + * (which user logs in, which account is created, etc.) of the module. + * + */ +public class ModuleController extends GenericController implements ReplaceableController { + + private static final long serialVersionUID = 240L; + + private static final String NODE_PATH = "ModuleController.node_path";// $NON-NLS-1$ + + private JMeterTreeNode selectedNode = null; + + /** + * No-arg constructor + * + * @see java.lang.Object#Object() + */ + public ModuleController() { + super(); + } + + @Override + public Object clone() { + ModuleController clone = (ModuleController) super.clone(); + if (selectedNode == null) { + this.restoreSelected(); + } + clone.selectedNode = selectedNode; // TODO ?? (JMeterTreeNode) selectedNode.clone(); + return clone; + } + + /** + * Sets the (@link JMeterTreeNode) which represents the controller which + * this object is pointing to. Used for building the test case upon + * execution. + * + * @param tn + * JMeterTreeNode + * @see org.apache.jmeter.gui.tree.JMeterTreeNode + */ + public void setSelectedNode(JMeterTreeNode tn) { + selectedNode = tn; + setNodePath(); + } + + /** + * Gets the (@link JMeterTreeNode) for the Controller + * + * @return JMeterTreeNode + */ + public JMeterTreeNode getSelectedNode() { + if (selectedNode == null){ + restoreSelected(); + } + return selectedNode; + } + + private void setNodePath() { + List nodePath = new ArrayList(); + if (selectedNode != null) { + TreeNode[] path = selectedNode.getPath(); + for (int i = 0; i < path.length; i++) { + nodePath.add(((JMeterTreeNode) path[i]).getName()); + } + // nodePath.add(selectedNode.getName()); + } + setProperty(new CollectionProperty(NODE_PATH, nodePath)); + } + + public List getNodePath() { + JMeterProperty prop = getProperty(NODE_PATH); + if (!(prop instanceof NullProperty)) { + return (List) ((CollectionProperty) prop).getObjectValue(); + } + return null; + } + + private void restoreSelected() { + GuiPackage gp = GuiPackage.getInstance(); + if (gp != null) { + JMeterTreeNode root = (JMeterTreeNode) gp.getTreeModel().getRoot(); + resolveReplacementSubTree(root); + } + } + + /** + * Compute the replacement tree. + * @param context + */ + public void resolveReplacementSubTree(JMeterTreeNode context) { + if (selectedNode == null) { + List nodePathList = getNodePath(); + if (nodePathList != null && nodePathList.size() > 0) { + traverse(context, nodePathList, 1); + } + } + } + + private void traverse(JMeterTreeNode node, List nodePath, int level) { + if (node != null && nodePath.size() > level) { + for (int i = 0; i < node.getChildCount(); i++) { + JMeterTreeNode cur = (JMeterTreeNode) node.getChildAt(i); + if (cur.getName().equals(nodePath.get(level).toString())) { + if (nodePath.size() == (level + 1)) { + selectedNode = cur; + } + traverse(cur, nodePath, level + 1); + } + } + } + } + + /** + * Copies the controller's subelements into the execution tree + * + */ + public HashTree getReplacementSubTree() { + HashTree tree = new ListedHashTree(); + if (selectedNode != null) { + if (!selectedNode.isEnabled()) { + selectedNode = cloneTreeNode(selectedNode); + selectedNode.setEnabled(true); + } + HashTree subtree = tree.add(selectedNode); + createSubTree(subtree, selectedNode); + } + return tree; + } + + private void createSubTree(HashTree tree, JMeterTreeNode node) { + Enumeration e = node.children(); + while (e.hasMoreElements()) { + JMeterTreeNode subNode = e.nextElement(); + tree.add(subNode); + createSubTree(tree.getTree(subNode), subNode); + } + } + + private static JMeterTreeNode cloneTreeNode(JMeterTreeNode node) { + JMeterTreeNode treeNode = (JMeterTreeNode) node.clone(); + treeNode.setUserObject(((TestElement) node.getUserObject()).clone()); + cloneChildren(treeNode, node); + return treeNode; + } + + private static void cloneChildren(JMeterTreeNode to, JMeterTreeNode from) { + Enumeration enumr = from.children(); + while (enumr.hasMoreElements()) { + JMeterTreeNode child = enumr.nextElement(); + JMeterTreeNode childClone = (JMeterTreeNode) child.clone(); + childClone.setUserObject(((TestElement) child.getUserObject()).clone()); + to.add(childClone); + cloneChildren((JMeterTreeNode) to.getLastChild(), child); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/NextIsNullException.java b/ApacheJmeter/src/org/apache/jmeter/control/NextIsNullException.java new file mode 100644 index 0000000..63fb23f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/NextIsNullException.java @@ -0,0 +1,30 @@ +/* + * 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. + * + */ + +/* + * Created on Apr 30, 2003 + */ +package org.apache.jmeter.control; + +/** + * Used by the Generic and Interleave controllers to signal the end of their samples + */ +public class NextIsNullException extends Exception { + private static final long serialVersionUID = 240L; + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/OnceOnlyController.java b/ApacheJmeter/src/org/apache/jmeter/control/OnceOnlyController.java new file mode 100644 index 0000000..8a50c1e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/OnceOnlyController.java @@ -0,0 +1,58 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.Sampler; + +/** + * Controller to run its children once per cycle. + */ +public class OnceOnlyController extends GenericController implements Serializable, LoopIterationListener { + + private static final long serialVersionUID = 240L; + + /** + * Constructor for the OnceOnlyController object. + */ + public OnceOnlyController() { + } + + /** + * @see LoopIterationListener#iterationStart(LoopIterationEvent) + */ + public void iterationStart(LoopIterationEvent event) { + int numIteration = 1; + // Bug 39509: iteration to 0 for all controller which not LoopController (and TG) + if (!(event.getSource() instanceof LoopController)) { + numIteration = 0; + } + if (event.getIteration() == numIteration) { + reInitialize(); + } + } + + @Override + protected Sampler nextIsNull() throws NextIsNullException { + return null; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/RandomController.java b/ApacheJmeter/src/org/apache/jmeter/control/RandomController.java new file mode 100644 index 0000000..81f51b9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/RandomController.java @@ -0,0 +1,53 @@ +/* + * 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.control; + +import java.io.Serializable; +import java.util.Random; + +public class RandomController extends InterleaveControl implements Serializable { + private static final long serialVersionUID = 240L; + + private static final Random RAND = new Random(); + + public RandomController() { + } + + /** + * @see org.apache.jmeter.control.GenericController#resetCurrent() + */ + @Override + protected void resetCurrent() { + if (getSubControllers().size() > 0) { + current = RAND.nextInt(this.getSubControllers().size()); + } else { + current = 0; + } + } + + /** + * @see org.apache.jmeter.control.GenericController#incrementCurrent() + */ + @Override + protected void incrementCurrent() { + super.incrementCurrent(); + current = RAND.nextInt(this.getSubControllers().size()); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/RandomOrderController.java b/ApacheJmeter/src/org/apache/jmeter/control/RandomOrderController.java new file mode 100644 index 0000000..b5210a5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/RandomOrderController.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jmeter.control; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.jmeter.testelement.TestElement; + +/** + * A controller that runs its children each at most once, but in a random order. + * + */ +public class RandomOrderController extends GenericController implements Serializable { + + private static final long serialVersionUID = 240L; + + /** + * Create a new RandomOrderController. + */ + public RandomOrderController() { + } + + /** + * @see GenericController#initialize() + */ + @Override + public void initialize() { + super.initialize(); + this.reorder(); + } + + /** + * @see GenericController#reInitialize() + */ + @Override + protected void reInitialize() { + super.reInitialize(); + this.reorder(); + } + + /** + * Replace the subControllersAndSamplers list with a reordered ArrayList. + */ + private void reorder() { + int numElements = this.subControllersAndSamplers.size(); + + // Create a new list containing numElements null elements. + List reordered = new ArrayList(this.subControllersAndSamplers.size()); + for (int i = 0; i < numElements; i++) { + reordered.add(null); + } + + // Insert the subControllersAndSamplers into random list positions. + for (Iterator i = this.subControllersAndSamplers.iterator(); i.hasNext();) { + int idx = (int) Math.floor(Math.random() * reordered.size()); + while (true) { + if (idx == numElements) { + idx = 0; + } + if (reordered.get(idx) == null) { + reordered.set(idx, i.next()); + break; + } + idx++; + } + } + + // Replace subControllersAndSamplers with reordered copy. + this.subControllersAndSamplers = reordered; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/ReplaceableController.java b/ApacheJmeter/src/org/apache/jmeter/control/ReplaceableController.java new file mode 100644 index 0000000..538d84a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/ReplaceableController.java @@ -0,0 +1,46 @@ +/* + * 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.control; + +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jorphan.collections.HashTree; + +/** + * This interface represents a controller that gets replaced during the + * compilation phase of test execution in an arbitrary way. + * + */ +public interface ReplaceableController { + + /** + * Used to replace the test execution tree (usually by adding the + * subelements of the TestElement that is replacing the + * ReplaceableController. + * + * @see org.apache.jorphan.collections.HashTree + */ + public HashTree getReplacementSubTree(); + + /** + * Compute the replacement tree. + * + * @param context + */ + public void resolveReplacementSubTree(JMeterTreeNode context); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/RunTime.java b/ApacheJmeter/src/org/apache/jmeter/control/RunTime.java new file mode 100644 index 0000000..e0d8d9e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/RunTime.java @@ -0,0 +1,134 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jmeter.testelement.property.StringProperty; + +public class RunTime extends GenericController implements Serializable { + + private static final long serialVersionUID = 240L; + + private final static String SECONDS = "RunTime.seconds"; //$NON-NLS-1$ + + private long startTime = 0; + + private int loopCount = 0; // for getIterCount + + public RunTime() { + } + + public void setRuntime(long seconds) { + setProperty(new LongProperty(SECONDS, seconds)); + } + + public void setRuntime(String seconds) { + setProperty(new StringProperty(SECONDS, seconds)); + } + + public long getRuntime() { + try { + return Long.parseLong(getPropertyAsString(SECONDS)); + } catch (NumberFormatException e) { + return 0L; + } + } + + public String getRuntimeString() { + return getPropertyAsString(SECONDS); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isDone() { + if (getRuntime() > 0 && getSubControllers().size() > 0) { + return super.isDone(); + } + return true; // Runtime is zero - no point staying around + } + + private boolean endOfLoop() { + return System.currentTimeMillis() - startTime >= 1000 * getRuntime(); + } + + @Override + public Sampler next() { + if (startTime == 0) { + startTime = System.currentTimeMillis(); + } + if (endOfLoop()) { + reInitialize();// ?? + resetLoopCount(); + return null; + } + return super.next(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Sampler nextIsNull() throws NextIsNullException { + reInitialize(); + if (endOfLoop()) { + resetLoopCount(); + return null; + } + return next(); + } + + protected void incrementLoopCount() { + loopCount++; + } + + protected void resetLoopCount() { + loopCount = 0; + startTime = 0; + } + + /* + * This is needed for OnceOnly to work like other Loop Controllers + */ + @Override + protected int getIterCount() { + return loopCount + 1; + } + + @Override + protected void reInitialize() { + setFirst(true); + resetCurrent(); + incrementLoopCount(); + recoverRunningVersion(); + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEndOfLoop() { + super.triggerEndOfLoop(); + resetLoopCount(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/control/SwitchController.java b/ApacheJmeter/src/org/apache/jmeter/control/SwitchController.java new file mode 100644 index 0000000..e5bc3d1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/SwitchController.java @@ -0,0 +1,126 @@ +/* + * 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.control; + +import java.io.Serializable; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.StringProperty; + +// For unit tests @see TestSwitchController + +/** + *

+ * Implements a controller which selects at most one of its children + * based on the condition value, which may be a number or a string. + *

+ *

+ * For numeric input, the controller processes the appropriate child, + * where the numbering starts from 0. + * If the number is out of range, then the first (0th) child is selected. + * If the condition is the empty string, then it is assumed to be 0. + *

+ *

+ * For non-empty non-numeric input, the child is selected by name. + * This may be the name of the controller or a sampler. + * If the string does not match any of the names, then the controller + * with the name "default" (any case) is processed. + * If there is no default entry, then unlike the numeric case, + * no child is selected. + *

+ */ +public class SwitchController extends GenericController implements Serializable { + private static final long serialVersionUID = 240L; + + // Package access for use by Test code + final static String SWITCH_VALUE = "SwitchController.value"; //$NON-NLS-1$ + + public SwitchController() { + super(); + } + + @Override + public Sampler next() { + if (isFirst()) { // Set the selection once per iteration + current = getSelectionAsInt(); + } + return super.next(); + } + + /** + * incrementCurrent is called when the current child (whether sampler or controller) + * has been processed. + *

+ * Setting it to int.max marks the controller as having processed all its + * children. Thus the controller processes one child per iteration. + *

+ * {@inheritDoc} + */ + @Override + protected void incrementCurrent() { + current=Integer.MAX_VALUE; + } + + public void setSelection(String inputValue) { + setProperty(new StringProperty(SWITCH_VALUE, inputValue)); + } + + /* + * Returns the selection value as a int, + * with the value set to zero if it is out of range. + */ + private int getSelectionAsInt() { + int ret; + getProperty(SWITCH_VALUE).recoverRunningVersion(null); + String sel = getSelection(); + try { + ret = Integer.parseInt(sel); + if (ret < 0 || ret >= getSubControllers().size()) { + ret = 0; + } + } catch (NumberFormatException e) { + if (sel.length()==0) { + ret = 0; + } else { + ret = scanControllerNames(sel); + } + } + return ret; + } + + private int scanControllerNames(String sel){ + int i = 0; + int default_pos = Integer.MAX_VALUE; + for(TestElement el : getSubControllers()) { + String name=el.getName(); + if (name.equals(sel)) { + return i; + } + if (name.equalsIgnoreCase("default")) { //$NON-NLS-1$ + default_pos = i; + } + i++; + } + return default_pos; + } + + public String getSelection() { + return getPropertyAsString(SWITCH_VALUE); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/TestFragmentController.java b/ApacheJmeter/src/org/apache/jmeter/control/TestFragmentController.java new file mode 100644 index 0000000..4989893 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/TestFragmentController.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.jmeter.control; + +import java.io.Serializable; + +public class TestFragmentController extends GenericController implements Serializable { + + private static final long serialVersionUID = 1L; + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/ThroughputController.java b/ApacheJmeter/src/org/apache/jmeter/control/ThroughputController.java new file mode 100644 index 0000000..7144224 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/ThroughputController.java @@ -0,0 +1,275 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.FloatProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class represents a controller that can control the number of times that + * it is executed, either by the total number of times the user wants the + * controller executed (BYNUMBER) or by the percentage of time it is called + * (BYPERCENT) + * + * The current implementation executes the first N samples (BYNUMBER) + * or the last N% of samples (BYPERCENT). + */ +public class ThroughputController extends GenericController implements Serializable, LoopIterationListener, + TestListener { + + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + public static final int BYNUMBER = 0; + + public static final int BYPERCENT = 1; + + private static final String STYLE = "ThroughputController.style";// $NON-NLS-1$ + + private static final String PERTHREAD = "ThroughputController.perThread";// $NON-NLS-1$ + + private static final String MAXTHROUGHPUT = "ThroughputController.maxThroughput";// $NON-NLS-1$ + + private static final String PERCENTTHROUGHPUT = "ThroughputController.percentThroughput";// $NON-NLS-1$ + + private static class MutableInteger{ + private int integer; + MutableInteger(int value){ + integer=value; + } + int incr(){ + return ++integer; + } + public int intValue() { + return integer; + } + } + + // These items are shared between threads in a group by the clone() method + // They are initialised by testStarted() so don't need to be serialised + private transient MutableInteger globalNumExecutions; + + private transient MutableInteger globalIteration; + + private String counterLock = ""; // ensure counts are updated correctly + // Need to use something that is serializable, so Object is no use + + /** + * Number of iterations on which we've chosen to deliver samplers. + */ + private int numExecutions = 0; + + /** + * Index of the current iteration. 0-based. + */ + private int iteration = -1; + + /** + * Whether to deliver samplers on this iteration. + */ + private boolean runThisTime; + + public ThroughputController() { + setStyle(BYNUMBER); + setPerThread(true); + setMaxThroughput(1); + setPercentThroughput(100); + runThisTime = false; + } + + public void setStyle(int style) { + setProperty(new IntegerProperty(STYLE, style)); + } + + public int getStyle() { + return getPropertyAsInt(STYLE); + } + + public void setPerThread(boolean perThread) { + setProperty(new BooleanProperty(PERTHREAD, perThread)); + } + + public boolean isPerThread() { + return getPropertyAsBoolean(PERTHREAD); + } + + public void setMaxThroughput(int maxThroughput) { + setProperty(new IntegerProperty(MAXTHROUGHPUT, maxThroughput)); + } + + public void setMaxThroughput(String maxThroughput) { + setProperty(new StringProperty(MAXTHROUGHPUT, maxThroughput)); + } + + public String getMaxThroughput() { + return getPropertyAsString(MAXTHROUGHPUT); + } + + protected int getMaxThroughputAsInt() { + JMeterProperty prop = getProperty(MAXTHROUGHPUT); + int retVal = 1; + if (prop instanceof IntegerProperty) { + retVal = (((IntegerProperty) prop).getIntValue()); + } else { + try { + retVal = Integer.parseInt(prop.getStringValue()); + } catch (NumberFormatException e) { + log.warn("Error parsing "+prop.getStringValue(),e); + } + } + return retVal; + } + + public void setPercentThroughput(float percentThroughput) { + setProperty(new FloatProperty(PERCENTTHROUGHPUT, percentThroughput)); + } + + public void setPercentThroughput(String percentThroughput) { + setProperty(new StringProperty(PERCENTTHROUGHPUT, percentThroughput)); + } + + public String getPercentThroughput() { + return getPropertyAsString(PERCENTTHROUGHPUT); + } + + protected float getPercentThroughputAsFloat() { + JMeterProperty prop = getProperty(PERCENTTHROUGHPUT); + float retVal = 100; + if (prop instanceof FloatProperty) { + retVal = (((FloatProperty) prop).getFloatValue()); + } else { + try { + retVal = Float.parseFloat(prop.getStringValue()); + } catch (NumberFormatException e) { + log.warn("Error parsing "+prop.getStringValue(),e); + } + } + return retVal; + } + + private int getExecutions() { + if (!isPerThread()) { + synchronized (counterLock) { + return globalNumExecutions.intValue(); + } + } + return numExecutions; + } + + /** + * @see org.apache.jmeter.control.Controller#next() + */ + @Override + public Sampler next() { + if (runThisTime) { + return super.next(); + } + return null; + } + + /** + * Decide whether to return any samplers on this iteration. + */ + private boolean decide(int executions, int iterations) { + if (getStyle() == BYNUMBER) { + return executions < getMaxThroughputAsInt(); + } + return (100.0 * executions + 50.0) / (iterations + 1) < getPercentThroughputAsFloat(); + } + + /** + * @see org.apache.jmeter.control.Controller#isDone() + */ + @Override + public boolean isDone() { + if (subControllersAndSamplers.size() == 0) { + return true; + } else if (getStyle() == BYNUMBER && getExecutions() >= getMaxThroughputAsInt() + && current >= getSubControllers().size()) { + return true; + } else { + return false; + } + } + + @Override + public Object clone() { + ThroughputController clone = (ThroughputController) super.clone(); + clone.numExecutions = numExecutions; + clone.iteration = iteration; + clone.runThisTime = false; + // Ensure global counters and lock are shared across threads in the group + clone.globalIteration = globalIteration; + clone.globalNumExecutions = globalNumExecutions; + clone.counterLock = counterLock; + return clone; + } + + public void iterationStart(LoopIterationEvent iterEvent) { + if (!isPerThread()) { + synchronized (counterLock) { + globalIteration.incr(); + runThisTime = decide(globalNumExecutions.intValue(), globalIteration.intValue()); + if (runThisTime) { + globalNumExecutions.incr(); + } + } + } else { + iteration++; + runThisTime = decide(numExecutions, iteration); + if (runThisTime) { + numExecutions++; + } + } + } + + public void testStarted() { + synchronized (counterLock) { + globalNumExecutions = new MutableInteger(0); + globalIteration = new MutableInteger(-1); + } + } + + public void testStarted(String host) { + testStarted(); + } + + public void testEnded() { + // NOOP + } + + public void testEnded(String host) { + // NOOP + } + + public void testIterationStart(LoopIterationEvent event) { + // NOOP + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/TransactionController.java b/ApacheJmeter/src/org/apache/jmeter/control/TransactionController.java new file mode 100644 index 0000000..9be9db2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/TransactionController.java @@ -0,0 +1,302 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterThread; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.threads.ListenerNotifier; +import org.apache.jmeter.threads.SamplePackage; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Transaction Controller to measure transaction times + * + * There are two different modes for the controller: + * - generate additional total sample after nested samples (as in JMeter 2.2) + * - generate parent sampler containing the nested samples + * + */ +public class TransactionController extends GenericController implements SampleListener, Controller, Serializable { + private static final long serialVersionUID = 233L; + + private static final String TRUE = Boolean.toString(true); // i.e. "true" + + private static final String PARENT = "TransactionController.parent";// $NON-NLS-1$ + + private final static String INCLUDE_TIMERS = "TransactionController.includeTimers";// $NON-NLS-1$ + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Only used in parent Mode + */ + private transient TransactionSampler transactionSampler; + + /** + * Only used in NON parent Mode + */ + private transient ListenerNotifier lnf; + + /** + * Only used in NON parent Mode + */ + private transient SampleResult res; + + /** + * Only used in NON parent Mode + */ + private transient int calls; + + /** + * Only used in NON parent Mode + */ + private transient int noFailingSamples; + + /** + * Cumulated pause time to excluse timer and post/pre processor times + * Only used in NON parent Mode + */ + private transient long pauseTime; + + /** + * Previous end time + * Only used in NON parent Mode + */ + private transient long prevEndTime; + + /** + * Creates a Transaction Controller + */ + public TransactionController() { + lnf = new ListenerNotifier(); + } + + private Object readResolve(){ + lnf = new ListenerNotifier(); + return this; + } + + public void setParent(boolean _parent){ + setProperty(new BooleanProperty(PARENT, _parent)); + } + + public boolean isParent(){ + return getPropertyAsBoolean(PARENT); + } + + /** + * @see org.apache.jmeter.control.Controller#next() + */ + @Override + public Sampler next(){ + if (isParent()){ + return next1(); + } + return next2(); + } + +///////////////// Transaction Controller - parent //////////////// + + private Sampler next1() { + // Check if transaction is done + if(transactionSampler != null && transactionSampler.isTransactionDone()) { + if (log.isDebugEnabled()) { + log.debug("End of transaction " + getName()); + } + // This transaction is done + transactionSampler = null; + return null; + } + + // Check if it is the start of a new transaction + if (isFirst()) // must be the start of the subtree + { + if (log.isDebugEnabled()) { + log.debug("Start of transaction " + getName()); + } + transactionSampler = new TransactionSampler(this, getName()); + } + + // Sample the children of the transaction + Sampler subSampler = super.next(); + transactionSampler.setSubSampler(subSampler); + // If we do not get any sub samplers, the transaction is done + if (subSampler == null) { + transactionSampler.setTransactionDone(); + } + return transactionSampler; + } + + @Override + protected Sampler nextIsAController(Controller controller) throws NextIsNullException { + if (!isParent()) { + return super.nextIsAController(controller); + } + Sampler returnValue; + Sampler sampler = controller.next(); + if (sampler == null) { + currentReturnedNull(controller); + // We need to call the super.next, instead of this.next, which is done in GenericController, + // because if we call this.next(), it will return the TransactionSampler, and we do not want that. + // We need to get the next real sampler or controller + returnValue = super.next(); + } else { + returnValue = sampler; + } + return returnValue; + } + +////////////////////// Transaction Controller - additional sample ////////////////////////////// + + private Sampler next2() { + if (isFirst()) // must be the start of the subtree + { + calls = 0; + noFailingSamples = 0; + res = new SampleResult(); + res.setSampleLabel(getName()); + // Assume success + res.setSuccessful(true); + res.sampleStart(); + prevEndTime = res.getStartTime();//??? + pauseTime = 0; + } + boolean isLast = current==super.subControllersAndSamplers.size(); + Sampler returnValue = super.next(); + if (returnValue == null && isLast) // Must be the end of the controller + { + if (res != null) { + res.setIdleTime(pauseTime+res.getIdleTime()); + res.sampleEnd(); + res.setResponseMessage("Number of samples in transaction : " + calls + ", number of failing samples : " + noFailingSamples); + if(res.isSuccessful()) { + res.setResponseCodeOK(); + } + notifyListeners(); + } + } + else { + // We have sampled one of our children + calls++; + } + + return returnValue; + } + + /** + * @see org.apache.jmeter.control.GenericController#triggerEndOfLoop() + */ + @Override + public void triggerEndOfLoop() { + if(!isParent()) { + if (res != null) { + res.setIdleTime(pauseTime+res.getIdleTime()); + res.sampleEnd(); + res.setSuccessful(TRUE.equals(JMeterContextService.getContext().getVariables().get(JMeterThread.LAST_SAMPLE_OK))); + res.setResponseMessage("Number of samples in transaction : " + calls + ", number of failing samples : " + noFailingSamples); + notifyListeners(); + } + } else { + transactionSampler.setTransactionDone(); + // This transaction is done + transactionSampler = null; + } + super.triggerEndOfLoop(); + } + + /** + * Create additional SampleEvent in NON Parent Mode + */ + protected void notifyListeners() { + // TODO could these be done earlier (or just once?) + JMeterContext threadContext = getThreadContext(); + JMeterVariables threadVars = threadContext.getVariables(); + SamplePackage pack = (SamplePackage) threadVars.getObject(JMeterThread.PACKAGE_OBJECT); + if (pack == null) { + // If child of TransactionController is a ThroughputController and TPC does + // not sample its children, then we will have this + // TODO Should this be at warn level ? + log.warn("Could not fetch SamplePackage"); + } else { + SampleEvent event = new SampleEvent(res, threadContext.getThreadGroup().getName(),threadVars, true); + // We must set res to null now, before sending the event for the transaction, + // so that we can ignore that event in our sampleOccured method + res = null; + // bug 50032 + if (!getThreadContext().isReinitializingSubControllers()) { + lnf.notifyListeners(event, pack.getSampleListeners()); + } + } + } + + public void sampleOccurred(SampleEvent se) { + if (!isParent()) { + // Check if we are still sampling our children + if(res != null && !se.isTransactionSampleEvent()) { + SampleResult sampleResult = se.getResult(); + res.setThreadName(sampleResult.getThreadName()); + res.setBytes(res.getBytes() + sampleResult.getBytes()); + if (!isIncludeTimers()) {// Accumulate waiting time for later + pauseTime += sampleResult.getEndTime() - sampleResult.getTime() - prevEndTime; + prevEndTime = sampleResult.getEndTime(); + } + if(!sampleResult.isSuccessful()) { + res.setSuccessful(false); + noFailingSamples++; + } + res.setAllThreads(sampleResult.getAllThreads()); + res.setGroupThreads(sampleResult.getGroupThreads()); + res.setLatency(res.getLatency() + sampleResult.getLatency()); + } + } + } + + public void sampleStarted(SampleEvent e) { + } + + public void sampleStopped(SampleEvent e) { + } + + /** + * Whether to include timers and pre/post processor time in overall sample. + * @param includeTimers + */ + public void setIncludeTimers(boolean includeTimers) { + setProperty(INCLUDE_TIMERS, includeTimers, true); // default true for compatibility + } + + /** + * Whether to include timer and pre/post processor time in overall sample. + * + * @return boolean (defaults to true for backwards compatibility) + */ + public boolean isIncludeTimers() { + return getPropertyAsBoolean(INCLUDE_TIMERS, true); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/TransactionSampler.java b/ApacheJmeter/src/org/apache/jmeter/control/TransactionSampler.java new file mode 100644 index 0000000..5251d3a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/TransactionSampler.java @@ -0,0 +1,149 @@ +/* + * 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. + * + */ + +/* + * N.B. Although this is a type of sampler, it is only used by the transaction controller, + * and so is in the control package +*/ +package org.apache.jmeter.control; + + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; + +/** + * Transaction Sampler class to measure transaction times + * (not exposed a a GUI class, as it is only used internally) + */ +public class TransactionSampler extends AbstractSampler { + private static final long serialVersionUID = 240L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private boolean transactionDone = false; + + private TransactionController transactionController; + + private Sampler subSampler; + + private SampleResult transactionSampleResult; + + private int calls = 0; + + private int noFailingSamples = 0; + + private int totalTime = 0; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public TransactionSampler(){ + //log.warn("Constructor only intended for use in testing"); + } + + public TransactionSampler(TransactionController controller, String name) { + transactionController = controller; + setName(name); // ensure name is available for debugging + transactionSampleResult = new SampleResult(); + transactionSampleResult.setSampleLabel(name); + // Assume success + transactionSampleResult.setSuccessful(true); + transactionSampleResult.sampleStart(); + } + + /** + * One cannot sample the TransactionSampler directly. + */ + public SampleResult sample(Entry e) { + throw new RuntimeException("Cannot sample TransactionSampler directly"); + // It is the JMeterThread which knows how to sample a real sampler + } + + public Sampler getSubSampler() { + return subSampler; + } + + public SampleResult getTransactionResult() { + return transactionSampleResult; + } + + public TransactionController getTransactionController() { + return transactionController; + } + + public boolean isTransactionDone() { + return transactionDone; + } + + public void addSubSamplerResult(SampleResult res) { + // Another subsample for the transaction + calls++; + // The transaction fails if any sub sample fails + if (!res.isSuccessful()) { + transactionSampleResult.setSuccessful(false); + noFailingSamples++; + } + // Add the sub result to the transaction result + transactionSampleResult.addSubResult(res); + // Add current time to total for later use (exclude pause time) + totalTime += res.getTime(); + } + + protected void setTransactionDone() { + this.transactionDone = true; + // Set the overall status for the transaction sample + // TODO: improve, e.g. by adding counts to the SampleResult class + transactionSampleResult.setResponseMessage("Number of samples in transaction : " + + calls + ", number of failing samples : " + + noFailingSamples); + if (transactionSampleResult.isSuccessful()) { + transactionSampleResult.setResponseCodeOK(); + } + // Bug 50080 (not include pause time when generate parent) + if (!transactionController.isIncludeTimers()) { + long end = transactionSampleResult.currentTimeInMillis(); + transactionSampleResult.setIdleTime(end + - transactionSampleResult.getStartTime() - totalTime); + transactionSampleResult.setEndTime(end); + } + } + + protected void setSubSampler(Sampler subSampler) { + this.subSampler = subSampler; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/WhileController.java b/ApacheJmeter/src/org/apache/jmeter/control/WhileController.java new file mode 100644 index 0000000..bb97e70 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/WhileController.java @@ -0,0 +1,128 @@ +/* + * 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.control; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterThread; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +// @see TestWhileController for unit tests + +public class WhileController extends GenericController implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + private static final String CONDITION = "WhileController.condition"; // $NON-NLS-1$ + + public WhileController() { + } + + /* + * Evaluate the condition, which can be: + * blank or LAST = was the last sampler OK? + * otherwise, evaluate the condition to see if it is not "false" + * If blank, only evaluate at the end of the loop + * + * Must only be called at start and end of loop + * + * @param loopEnd - are we at loop end? + * @return true means OK to continue + */ + private boolean endOfLoop(boolean loopEnd) { + String cnd = getCondition().trim(); + log.debug("Condition string:" + cnd+"."); + boolean res; + // If blank, only check previous sample when at end of loop + if ((loopEnd && cnd.length() == 0) || "LAST".equalsIgnoreCase(cnd)) {// $NON-NLS-1$ + JMeterVariables threadVars = JMeterContextService.getContext().getVariables(); + res = "false".equalsIgnoreCase(threadVars.get(JMeterThread.LAST_SAMPLE_OK));// $NON-NLS-1$ + } else { + // cnd may be null if next() called us + res = "false".equalsIgnoreCase(cnd);// $NON-NLS-1$ + } + log.debug("Condition value: " + res); + return res; + } + + /** + * Only called at End of Loop + *

+ * {@inheritDoc} + */ + @Override + protected Sampler nextIsNull() throws NextIsNullException { + reInitialize(); + if (endOfLoop(true)){ + return null; + } + return next(); + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEndOfLoop() { + super.triggerEndOfLoop(); + endOfLoop(true); + } + + /** + * This skips controller entirely if the condition is false on first entry. + *

+ * {@inheritDoc} + */ + @Override + public Sampler next(){ + if (isFirst()){ + if (endOfLoop(false)){ + return null; + } + } + return super.next(); + } + + /** + * @param string + * the condition to save + */ + public void setCondition(String string) { + log.debug("setCondition(" + string + ")"); + setProperty(new StringProperty(CONDITION, string)); + } + + /** + * @return the condition + */ + public String getCondition() { + String cnd; + JMeterProperty prop=getProperty(CONDITION); + prop.recoverRunningVersion(this); + cnd = prop.getStringValue(); + return cnd; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/AbstractControllerGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/AbstractControllerGui.java new file mode 100644 index 0000000..3aa978f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/AbstractControllerGui.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.control.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which manage controllers. + * + */ +public abstract class AbstractControllerGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most controller + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultControllerMenu(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#CONTROLLERS}, which is + * appropriate for most controller components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.CONTROLLERS }); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/ForeachControlPanel.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/ForeachControlPanel.java new file mode 100644 index 0000000..722b1f3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/ForeachControlPanel.java @@ -0,0 +1,217 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.ForeachController; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The user interface for a foreach controller which specifies that its + * subcomponents should be executed some number of times in a loop. This + * component can be used standalone or embedded into some other component. + */ + +public class ForeachControlPanel extends AbstractControllerGui { + + private static final long serialVersionUID = 240L; + + /** + * A field allowing the user to specify the input variable the controller + * should loop. + */ + private JTextField inputVal; + + /** + * A field allowing the user to specify output variable the controller + * should return. + */ + private JTextField returnVal; + + // Should we add the "_" separator? + private JCheckBox useSeparator; + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private boolean displayName = true; + + /** The name of the infinite checkbox component. */ + private static final String INPUTVAL = "Input Field"; // $NON-NLS-1$ + + /** The name of the loops field component. */ + private static final String RETURNVAL = "Return Field"; // $NON-NLS-1$ + + /** + * Create a new LoopControlPanel as a standalone component. + */ + public ForeachControlPanel() { + this(true); + } + + /** + * Create a new LoopControlPanel as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public ForeachControlPanel(boolean displayName) { + this.displayName = displayName; + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + inputVal.setText(((ForeachController) element).getInputValString()); + returnVal.setText(((ForeachController) element).getReturnValString()); + useSeparator.setSelected(((ForeachController) element).getUseSeparator()); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + ForeachController lc = new ForeachController(); + modifyTestElement(lc); + return lc; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement lc) { + configureTestElement(lc); + if (lc instanceof ForeachController) { + if (inputVal.getText().length() > 0) { + ((ForeachController) lc).setInputVal(inputVal.getText()); + } else { + ((ForeachController) lc).setInputVal(""); // $NON-NLS-1$ + } + if (returnVal.getText().length() > 0) { + ((ForeachController) lc).setReturnVal(returnVal.getText()); + } else { + ((ForeachController) lc).setReturnVal(""); // $NON-NLS-1$ + } + ((ForeachController) lc).setUseSeparator(useSeparator.isSelected()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + inputVal.setText(""); // $NON-NLS-1$ + returnVal.setText(""); // $NON-NLS-1$ + useSeparator.setSelected(true); + } + + + public String getLabelResource() { + return "foreach_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + // The Loop Controller panel can be displayed standalone or inside + // another panel. For standalone, we want to display the TITLE, NAME, + // etc. (everything). However, if we want to display it within another + // panel, we just display the Loop Count fields (not the TITLE and + // NAME). + + // Standalone + if (displayName) { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createLoopCountPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } else { + // Embedded + setLayout(new BorderLayout()); + add(createLoopCountPanel(), BorderLayout.NORTH); + } + } + + /** + * Create a GUI panel containing the components related to the number of + * loops which should be executed. + * + * @return a GUI panel containing the loop count components + */ + private JPanel createLoopCountPanel() { + // JPanel loopPanel = new JPanel(new BorderLayout(5, 0)); + VerticalPanel loopPanel = new VerticalPanel(); + + // LOOP LABEL + JLabel inputValLabel = new JLabel(JMeterUtils.getResString("foreach_input")); // $NON-NLS-1$ + JLabel returnValLabel = new JLabel(JMeterUtils.getResString("foreach_output")); // $NON-NLS-1$ + + // TEXT FIELD + JPanel inputValSubPanel = new JPanel(new BorderLayout(5, 0)); + inputVal = new JTextField("", 5); // $NON-NLS-1$ + inputVal.setName(INPUTVAL); + inputValLabel.setLabelFor(inputVal); + inputValSubPanel.add(inputValLabel, BorderLayout.WEST); + inputValSubPanel.add(inputVal, BorderLayout.CENTER); + + // TEXT FIELD + JPanel returnValSubPanel = new JPanel(new BorderLayout(5, 0)); + returnVal = new JTextField("", 5); // $NON-NLS-1$ + returnVal.setName(RETURNVAL); + returnValLabel.setLabelFor(returnVal); + returnValSubPanel.add(returnValLabel, BorderLayout.WEST); + returnValSubPanel.add(returnVal, BorderLayout.CENTER); + + // Checkbox + useSeparator = new JCheckBox(JMeterUtils.getResString("foreach_use_separator"), true); // $NON-NLS-1$ + + loopPanel.add(inputValSubPanel); + loopPanel.add(returnValSubPanel); + loopPanel.add(useSeparator); + + return loopPanel; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/IfControllerPanel.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/IfControllerPanel.java new file mode 100644 index 0000000..662ad9f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/IfControllerPanel.java @@ -0,0 +1,193 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.IfController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The user interface for a controller which specifies that its subcomponents + * should be executed while a condition holds. This component can be used + * standalone or embedded into some other component. + * + */ + +public class IfControllerPanel extends AbstractControllerGui { + + private static final long serialVersionUID = 240L; + + /** + * A field allowing the user to specify the number of times the controller + * should loop. + */ + private JTextField theCondition; + + private JCheckBox useExpression; + + private JCheckBox evaluateAll; + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private boolean displayName = true; + + /** + * Create a new LoopControlPanel as a standalone component. + */ + public IfControllerPanel() { + this(true); + } + + /** + * Create a new IfControllerPanel as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public IfControllerPanel(boolean displayName) { + this.displayName = displayName; + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + if (element instanceof IfController) { + IfController ifController = (IfController) element; + theCondition.setText(ifController.getCondition()); + evaluateAll.setSelected(ifController.isEvaluateAll()); + useExpression.setSelected(ifController.isUseExpression()); + } + + } + + /** + * Implements JMeterGUIComponent.createTestElement() + */ + public TestElement createTestElement() { + IfController controller = new IfController(); + modifyTestElement(controller); + return controller; + } + + /** + * Implements JMeterGUIComponent.modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement controller) { + configureTestElement(controller); + if (controller instanceof IfController) { + IfController ifController = (IfController) controller; + ifController.setCondition(theCondition.getText()); + ifController.setEvaluateAll(evaluateAll.isSelected()); + ifController.setUseExpression(useExpression.isSelected()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + theCondition.setText(""); // $NON-NLS-1$ + evaluateAll.setSelected(false); + } + + public String getLabelResource() { + return "if_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + // Standalone + if (displayName) { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createConditionPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + + } else { + // Embedded + setLayout(new BorderLayout()); + add(createConditionPanel(), BorderLayout.NORTH); + } + } + + /** + * Create a GUI panel containing the condition. + * + * @return a GUI panel containing the condition components + */ + private JPanel createConditionPanel() { + JPanel conditionPanel = new JPanel(new BorderLayout(5, 0)); + + // Condition LABEL + JLabel conditionLabel = new JLabel(JMeterUtils.getResString("if_controller_label")); // $NON-NLS-1$ + conditionPanel.add(conditionLabel, BorderLayout.WEST); + + // TEXT FIELD + theCondition = new JTextField(""); // $NON-NLS-1$ + conditionLabel.setLabelFor(theCondition); + conditionPanel.add(theCondition, BorderLayout.CENTER); + + conditionPanel.add(Box.createHorizontalStrut(conditionLabel.getPreferredSize().width + + theCondition.getPreferredSize().width), BorderLayout.NORTH); + + JPanel optionPanel = new JPanel(); + + // Use expression instead of Javascript + useExpression = new JCheckBox(JMeterUtils.getResString("if_controller_expression")); // $NON-NLS-1$ + optionPanel.add(useExpression); + + // Evaluate All checkbox + evaluateAll = new JCheckBox(JMeterUtils.getResString("if_controller_evaluate_all")); // $NON-NLS-1$ + optionPanel.add(evaluateAll); + + conditionPanel.add(optionPanel,BorderLayout.SOUTH); + return conditionPanel; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/IncludeControllerGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/IncludeControllerGui.java new file mode 100644 index 0000000..041ecc6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/IncludeControllerGui.java @@ -0,0 +1,112 @@ +/* + * 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.control.gui; + +import javax.swing.JMenu; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.control.IncludeController; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class IncludeControllerGui extends AbstractControllerGui + // implements UnsharedComponent +{ + + private static final long serialVersionUID = 240L; + + private FilePanel includePanel = + new FilePanel(JMeterUtils.getResString("include_path"), ".jmx"); //$NON-NLS-1$ //$NON-NLS-2$ + + /** + * Initializes the gui panel for the ModuleController instance. + */ + public IncludeControllerGui() { + init(); + } + + public String getLabelResource() { + return "include_controller";//$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement el) { + super.configure(el); + IncludeController controller = (IncludeController) el; + this.includePanel.setFilename(controller.getIncludePath()); + } + + /** + * {@inheritDoc} + */ + public TestElement createTestElement() { + IncludeController mc = new IncludeController(); + configureTestElement(mc); + return mc; + } + + /** + * {@inheritDoc} + */ + public void modifyTestElement(TestElement element) { + configureTestElement(element); + IncludeController controller = (IncludeController)element; + controller.setIncludePath(this.includePanel.getFilename()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + includePanel.clearGui(); + } + + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu menu = new JPopupMenu(); + JMenu addMenu = MenuFactory.makeMenus(new String[] { + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.ASSERTIONS, + MenuFactory.TIMERS, + MenuFactory.LISTENERS, + }, JMeterUtils.getResString("add"), // $NON-NLS-1$ + ActionNames.ADD); + menu.add(addMenu); + MenuFactory.addEditMenu(menu, true); + MenuFactory.addFileMenu(menu); + return menu; + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + + add(includePanel); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/InterleaveControlGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/InterleaveControlGui.java new file mode 100644 index 0000000..e5c8bc5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/InterleaveControlGui.java @@ -0,0 +1,89 @@ +/* + * 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.control.gui; + +import javax.swing.JCheckBox; + +import org.apache.jmeter.control.InterleaveControl; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class InterleaveControlGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + private JCheckBox style; + + public InterleaveControlGui() { + init(); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (((InterleaveControl) el).getStyle() == InterleaveControl.IGNORE_SUB_CONTROLLERS) { + style.setSelected(true); + } else { + style.setSelected(false); + } + } + + public TestElement createTestElement() { + InterleaveControl ic = new InterleaveControl(); + modifyTestElement(ic); + return ic; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement ic) { + configureTestElement(ic); + if (style.isSelected()) { + ((InterleaveControl) ic).setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + } else { + ((InterleaveControl) ic).setStyle(InterleaveControl.USE_SUB_CONTROLLERS); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + style.setSelected(false); + } + + public String getLabelResource() { + return "interleave_control_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + + add(makeTitlePanel()); + + style = new JCheckBox(JMeterUtils.getResString("ignore_subcontrollers")); // $NON-NLS-1$ + add(style); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/LogicControllerGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/LogicControllerGui.java new file mode 100644 index 0000000..4d5f188 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/LogicControllerGui.java @@ -0,0 +1,64 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; + +import org.apache.jmeter.control.GenericController; +import org.apache.jmeter.testelement.TestElement; + +/** + * A generic controller component. + * + */ +public class LogicControllerGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + /** + * Create a new LogicControllerGui instance. + */ + public LogicControllerGui() { + init(); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + GenericController lc = new GenericController(); + configureTestElement(lc); + return lc; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement el) { + configureTestElement(el); + } + + public String getLabelResource() { + return "logic_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/LoopControlPanel.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/LoopControlPanel.java new file mode 100644 index 0000000..def1435 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/LoopControlPanel.java @@ -0,0 +1,264 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.LoopController; +import org.apache.jmeter.gui.util.FocusRequester; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The user interface for a controller which specifies that its subcomponents + * should be executed some number of times in a loop. This component can be used + * standalone or embedded into some other component. + * + */ + +public class LoopControlPanel extends AbstractControllerGui implements ActionListener { + private static final long serialVersionUID = 240L; + + /** + * A checkbox allowing the user to specify whether or not the controller + * should loop forever. + */ + private JCheckBox infinite; + + /** + * A field allowing the user to specify the number of times the controller + * should loop. + */ + private JTextField loops; + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private boolean displayName = true; + + /** The name of the infinite checkbox component. */ + private static final String INFINITE = "Infinite Field"; // $NON-NLS-1$ + + /** The name of the loops field component. */ + private static final String LOOPS = "Loops Field"; // $NON-NLS-1$ + + /** + * Create a new LoopControlPanel as a standalone component. + */ + public LoopControlPanel() { + this(true); + } + + /** + * Create a new LoopControlPanel as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public LoopControlPanel(boolean displayName) { + this.displayName = displayName; + init(); + setState(1); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + if (element instanceof LoopController) { + setState(((LoopController) element).getLoopString()); + } else { + setState(1); + } + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + LoopController lc = new LoopController(); + modifyTestElement(lc); + return lc; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement lc) { + configureTestElement(lc); + if (lc instanceof LoopController) { + if (loops.getText().length() > 0) { + ((LoopController) lc).setLoops(loops.getText()); + } else { + ((LoopController) lc).setLoops(-1); + } + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + loops.setText("1"); // $NON-NLS-1$ + infinite.setSelected(false); + } + + /** + * Invoked when an action occurs. This implementation assumes that the + * target component is the infinite loops checkbox. + * + * @param event + * the event that has occurred + */ + public void actionPerformed(ActionEvent event) { + if (infinite.isSelected()) { + loops.setText(""); // $NON-NLS-1$ + loops.setEnabled(false); + } else { + loops.setEnabled(true); + new FocusRequester(loops); + } + } + + public String getLabelResource() { + return "loop_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + // The Loop Controller panel can be displayed standalone or inside + // another panel. For standalone, we want to display the TITLE, NAME, + // etc. (everything). However, if we want to display it within another + // panel, we just display the Loop Count fields (not the TITLE and + // NAME). + + // Standalone + if (displayName) { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createLoopCountPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } else { + // Embedded + setLayout(new BorderLayout()); + add(createLoopCountPanel(), BorderLayout.NORTH); + } + } + + /** + * Create a GUI panel containing the components related to the number of + * loops which should be executed. + * + * @return a GUI panel containing the loop count components + */ + private JPanel createLoopCountPanel() { + JPanel loopPanel = new JPanel(new BorderLayout(5, 0)); + + // LOOP LABEL + JLabel loopsLabel = new JLabel(JMeterUtils.getResString("iterator_num")); // $NON-NLS-1$ + loopPanel.add(loopsLabel, BorderLayout.WEST); + + JPanel loopSubPanel = new JPanel(new BorderLayout(5, 0)); + + // TEXT FIELD + loops = new JTextField("1", 5); // $NON-NLS-1$ + loops.setName(LOOPS); + loopsLabel.setLabelFor(loops); + loopSubPanel.add(loops, BorderLayout.CENTER); + + // FOREVER CHECKBOX + infinite = new JCheckBox(JMeterUtils.getResString("infinite")); // $NON-NLS-1$ + infinite.setActionCommand(INFINITE); + infinite.addActionListener(this); + loopSubPanel.add(infinite, BorderLayout.WEST); + + loopPanel.add(loopSubPanel, BorderLayout.CENTER); + + loopPanel.add(Box.createHorizontalStrut(loopsLabel.getPreferredSize().width + loops.getPreferredSize().width + + infinite.getPreferredSize().width), BorderLayout.NORTH); + + return loopPanel; + } + + /** + * Set the number of loops which should be reflected in the GUI. The + * loopCount parameter should contain the String representation of an + * integer. This integer will be treated as the number of loops. If this + * integer is less than 0, the number of loops will be assumed to be + * infinity. + * + * @param loopCount + * the String representation of the number of loops + */ + private void setState(String loopCount) { + if (loopCount.startsWith("-")) { // $NON-NLS-1$ + setState(-1); + } else { + loops.setText(loopCount); + infinite.setSelected(false); + loops.setEnabled(true); + } + } + + /** + * Set the number of loops which should be reflected in the GUI. If the + * loopCount is less than 0, the number of loops will be assumed to be + * infinity. + * + * @param loopCount + * the number of loops + */ + private void setState(int loopCount) { + if (loopCount <= -1) { + infinite.setSelected(true); + loops.setEnabled(false); + loops.setText(""); // $NON-NLS-1$ + } else { + infinite.setSelected(false); + loops.setEnabled(true); + loops.setText(Integer.toString(loopCount)); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/ModuleControllerGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/ModuleControllerGui.java new file mode 100644 index 0000000..e78fbfc --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/ModuleControllerGui.java @@ -0,0 +1,262 @@ +/* + * 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.control.gui; + +import java.awt.FlowLayout; +import java.util.Collection; +import java.util.Iterator; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.ModuleController; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * ModuleController Gui. + * + */ +public class ModuleControllerGui extends AbstractControllerGui +// implements UnsharedComponent +{ + + private static final long serialVersionUID = 240L; + + private JMeterTreeNode selected = null; + + private final JComboBox nodes; + + private final DefaultComboBoxModel nodesModel; + + private final JLabel warningLabel; + + /** + * Initializes the gui panel for the ModuleController instance. + */ + public ModuleControllerGui() { + nodesModel = new DefaultComboBoxModel(); + nodes = new JComboBox(nodesModel); + warningLabel = new JLabel(""); // $NON-NLS-1$ + init(); + } + + /** {@inheritDoc}} */ + public String getLabelResource() { + return "module_controller_title"; // $NON-NLS-1$ + } + /** {@inheritDoc}} */ + @Override + public void configure(TestElement el) { + super.configure(el); + ModuleController controller = (ModuleController) el; + this.selected = controller.getSelectedNode(); + if (selected == null && controller.getNodePath() != null) { + warningLabel.setText(JMeterUtils.getResString("module_controller_warning") // $NON-NLS-1$ + + renderPath(controller.getNodePath())); + } else { + warningLabel.setText(""); // $NON-NLS-1$ + } + reinitialize(); + } + + private String renderPath(Collection path) { + Iterator iter = path.iterator(); + StringBuilder buf = new StringBuilder(); + boolean first = true; + while (iter.hasNext()) { + if (first) { + first = false; + iter.next(); + continue; + } + buf.append(iter.next()); + if (iter.hasNext()) { + buf.append(" > "); // $NON-NLS-1$ + } + } + return buf.toString(); + } + + /** {@inheritDoc}} */ + public TestElement createTestElement() { + ModuleController mc = new ModuleController(); + configureTestElement(mc); + if (selected != null) { + mc.setSelectedNode(selected); + } + return mc; + } + + /** {@inheritDoc}} */ + public void modifyTestElement(TestElement element) { + configureTestElement(element); + TreeNodeWrapper tnw = (TreeNodeWrapper) nodesModel.getSelectedItem(); + if (tnw != null && tnw.getTreeNode() != null) { + selected = tnw.getTreeNode(); + if (selected != null) { + ((ModuleController) element).setSelectedNode(selected); + } + } + } + + /** {@inheritDoc}} */ + @Override + public void clearGui() { + super.clearGui(); + + nodes.setSelectedIndex(-1); + selected = null; + } + + + /** {@inheritDoc}} */ + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu menu = new JPopupMenu(); + JMenu addMenu = MenuFactory.makeMenus( + new String[] { + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.ASSERTIONS, + MenuFactory.TIMERS, + MenuFactory.LISTENERS, + }, + JMeterUtils.getResString("add"), // $NON-NLS-1$ + ActionNames.ADD); + menu.add(addMenu); + MenuFactory.addEditMenu(menu, true); + MenuFactory.addFileMenu(menu); + return menu; + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + + // DROP-DOWN MENU + JPanel modulesPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 20, 5)); + JLabel nodesLabel = new JLabel(JMeterUtils.getResString("module_controller_module_to_run")); // $NON-NLS-1$ + modulesPanel.add(nodesLabel); + nodesLabel.setLabelFor(nodes); + reinitialize(); + modulesPanel.add(nodes); + modulesPanel.add(warningLabel); + add(modulesPanel); + } + + private void reinitialize() { + TreeNodeWrapper current; + nodesModel.removeAllElements(); + GuiPackage gp = GuiPackage.getInstance(); + JMeterTreeNode root; + if (gp != null) { + root = (JMeterTreeNode) GuiPackage.getInstance().getTreeModel().getRoot(); + buildNodesModel(root, "", 0); // $NON-NLS-1$ + } + if (selected != null) { + for (int i = 0; i < nodesModel.getSize(); i++) { + current = (TreeNodeWrapper) nodesModel.getElementAt(i); + if (current.getTreeNode() != null && current.getTreeNode().equals(selected)) { + nodesModel.setSelectedItem(current); + break; + } + } + } + } + + private void buildNodesModel(JMeterTreeNode node, String parent_name, int level) { + if (level == 0 && (parent_name == null || parent_name.length() == 0)) { + nodesModel.addElement(new TreeNodeWrapper(null, "")); // $NON-NLS-1$ + } + String seperator = " > "; // $NON-NLS-1$ + if (node != null) { + for (int i = 0; i < node.getChildCount(); i++) { + StringBuilder name = new StringBuilder(); + JMeterTreeNode cur = (JMeterTreeNode) node.getChildAt(i); + TestElement te = cur.getTestElement(); + if (te instanceof AbstractThreadGroup) { + name.append(parent_name); + name.append(cur.getName()); + name.append(seperator); + buildNodesModel(cur, name.toString(), level); + } else if (te instanceof Controller && !(te instanceof ModuleController)) { + name.append(spaces(level)); + name.append(parent_name); + name.append(cur.getName()); + TreeNodeWrapper tnw = new TreeNodeWrapper(cur, name.toString()); + nodesModel.addElement(tnw); + name = new StringBuilder(); + name.append(cur.getName()); + name.append(seperator); + buildNodesModel(cur, name.toString(), level + 1); + } else if (te instanceof TestPlan || te instanceof WorkBench) { + name.append(cur.getName()); + name.append(seperator); + buildNodesModel(cur, name.toString(), 0); + } + } + } + } + + private String spaces(int level) { + int multi = 4; + StringBuilder spaces = new StringBuilder(level * multi); + for (int i = 0; i < level * multi; i++) { + spaces.append(" "); // $NON-NLS-1$ + } + return spaces.toString(); + } +} + +class TreeNodeWrapper { + + private final JMeterTreeNode tn; + + private final String label; + + public TreeNodeWrapper(JMeterTreeNode tn, String label) { + this.tn = tn; + this.label = label; + } + + public JMeterTreeNode getTreeNode() { + return tn; + } + + /** {@inheritDoc}} */ + @Override + public String toString() { + return label; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/OnceOnlyControllerGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/OnceOnlyControllerGui.java new file mode 100644 index 0000000..f8f6f06 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/OnceOnlyControllerGui.java @@ -0,0 +1,56 @@ +/* + * 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.control.gui; + +import org.apache.jmeter.control.OnceOnlyController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class OnceOnlyControllerGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + public OnceOnlyControllerGui() { + init(); + } + + public TestElement createTestElement() { + OnceOnlyController oc = new OnceOnlyController(); + modifyTestElement(oc); + return oc; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement oc) { + configureTestElement(oc); + } + + public String getLabelResource() { + return "once_only_controller_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/RandomControlGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/RandomControlGui.java new file mode 100644 index 0000000..9de6215 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/RandomControlGui.java @@ -0,0 +1,89 @@ +/* + * 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.control.gui; + +import javax.swing.JCheckBox; + +import org.apache.jmeter.control.InterleaveControl; +import org.apache.jmeter.control.RandomController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class RandomControlGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + private JCheckBox style; + + public RandomControlGui() { + init(); + } + + public TestElement createTestElement() { + RandomController ic = new RandomController(); + modifyTestElement(ic); + return ic; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement ic) { + configureTestElement(ic); + if (style.isSelected()) { + ((RandomController) ic).setStyle(InterleaveControl.IGNORE_SUB_CONTROLLERS); + } else { + ((RandomController) ic).setStyle(InterleaveControl.USE_SUB_CONTROLLERS); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + style.setSelected(false); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (((RandomController) el).getStyle() == InterleaveControl.IGNORE_SUB_CONTROLLERS) { + style.setSelected(true); + } else { + style.setSelected(false); + } + } + + public String getLabelResource() { + return "random_control_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + + style = new JCheckBox(JMeterUtils.getResString("ignore_subcontrollers")); // $NON-NLS-1$ + add(style); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/RandomOrderControllerGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/RandomOrderControllerGui.java new file mode 100644 index 0000000..2bc50af --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/RandomOrderControllerGui.java @@ -0,0 +1,53 @@ +/* + * 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.control.gui; + +import org.apache.jmeter.control.RandomOrderController; +import org.apache.jmeter.testelement.TestElement; + +/** + * GUI for RandomOrderController. + * + */ +public class RandomOrderControllerGui extends LogicControllerGui { + + private static final long serialVersionUID = 240L; + + @Override + public String getLabelResource() { + return "random_order_control_title"; // $NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public TestElement createTestElement() { + RandomOrderController ic = new RandomOrderController(); + modifyTestElement(ic); + return ic; + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyTestElement(TestElement ic) { + configureTestElement(ic); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/ReportGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/ReportGui.java new file mode 100644 index 0000000..0157e06 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/ReportGui.java @@ -0,0 +1,186 @@ +/* + * 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.control.gui; + +import java.awt.Color; +import java.awt.BorderLayout; +import java.awt.Container; +import java.util.Collection; + +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JTextField; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.DirectoryPanel; +import org.apache.jmeter.gui.util.ReportMenuFactory; +import org.apache.jmeter.report.gui.AbstractReportGui; +import org.apache.jmeter.report.gui.ReportPageGui; +import org.apache.jmeter.report.writers.gui.HTMLReportWriterGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.ReportPlan; +import org.apache.jmeter.util.JMeterUtils; + +/** + * JMeter GUI component representing the test plan which will be executed when + * the test is run. + * + * @version $Revision: 905034 $ + */ +public class ReportGui extends AbstractReportGui { + + private static final long serialVersionUID = 240L; + + /** A panel to contain comments on the test plan. */ + private JTextField commentPanel; + + private DirectoryPanel baseDir = + new DirectoryPanel(JMeterUtils.getResString("report_base_directory"), "", + Color.white); + + /** A panel allowing the user to define variables. */ + private ArgumentsPanel argsPanel; + + /** + * Create a new TestPlanGui. + */ + public ReportGui() { + init(); + } + + /** + * Need to update this to make the context popupmenu correct + * @return a JPopupMenu appropriate for the component. + */ + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu pop = new JPopupMenu(); + JMenu addMenu = new JMenu(JMeterUtils.getResString("Add")); + addMenu.add(ReportMenuFactory.makeMenuItem(new ReportPageGui().getStaticLabel(), + ReportPageGui.class.getName(), + "Add")); + addMenu.add(ReportMenuFactory.makeMenuItem(new HTMLReportWriterGui().getStaticLabel(), + HTMLReportWriterGui.class.getName(), + "Add")); + addMenu.add(ReportMenuFactory.makeMenu(ReportMenuFactory.CONFIG_ELEMENTS, "Add")); + pop.add(addMenu); + ReportMenuFactory.addFileMenu(pop); + ReportMenuFactory.addEditMenu(pop,true); + return pop; + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + ReportPlan tp = new ReportPlan(); + modifyTestElement(tp); + return tp; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement plan) { + super.configureTestElement(plan); + if (plan instanceof ReportPlan) { + ReportPlan rp = (ReportPlan) plan; + rp.setUserDefinedVariables((Arguments) argsPanel.createTestElement()); + rp.setProperty(ReportPlan.REPORT_COMMENTS, commentPanel.getText()); + rp.setBasedir(baseDir.getFilename()); + } + } + + @Override + public String getLabelResource() { + return "report_plan"; + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns null, since the TestPlan appears at + * the top level of the tree and cannot be added elsewhere. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + return null; + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + + if (el.getProperty(ReportPlan.USER_DEFINED_VARIABLES) != null) { + argsPanel.configure((Arguments) el.getProperty(ReportPlan.USER_DEFINED_VARIABLES).getObjectValue()); + } + commentPanel.setText(el.getPropertyAsString(ReportPlan.REPORT_COMMENTS)); + baseDir.setFilename(el.getPropertyAsString(ReportPlan.BASEDIR)); + } + + /** + * Create a panel allowing the user to define variables for the test. + * + * @return a panel for user-defined variables + */ + private JPanel createVariablePanel() { + argsPanel = + new ArgumentsPanel(JMeterUtils.getResString("user_defined_variables"), + Color.white); + return argsPanel; + } + + private Container createCommentPanel() { + JPanel panel = new JPanel(); + panel.setBackground(Color.white); + panel.setLayout(new BorderLayout(10, 10)); + Container title = makeTitlePanel(); + commentPanel = new JTextField(); + commentPanel.setBackground(Color.white); + JLabel label = new JLabel(JMeterUtils.getResString("testplan_comments")); + label.setBackground(Color.white); + label.setLabelFor(commentPanel); + title.add(label); + title.add(commentPanel); + panel.add(title,BorderLayout.NORTH); + panel.add(baseDir,BorderLayout.CENTER); + return panel; + } + + /** + * Initialize the components and layout of this component. + */ + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout(10, 10)); + setBorder(makeBorder()); + setBackground(Color.white); + add(createCommentPanel(), BorderLayout.NORTH); + add(createVariablePanel(), BorderLayout.CENTER); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/RunTimeGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/RunTimeGui.java new file mode 100644 index 0000000..9fc54e7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/RunTimeGui.java @@ -0,0 +1,224 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.Box; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.RunTime; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The user interface for a controller which specifies that its subcomponents + * should be executed some number of seconds in a loop. This component can be + * used standalone or embedded into some other component. + * + */ + +public class RunTimeGui extends AbstractControllerGui implements ActionListener { + private static final long serialVersionUID = 240L; + + /** + * A field allowing the user to specify the number of seconds the controller + * should loop. + */ + private JTextField seconds; + + /** + * Boolean indicating whether or not this component should display its name. + * If true, this is a standalone component. If false, this component is + * intended to be used as a subpanel for another component. + */ + private boolean displayName = true; + + /** + * Create a new LoopControlPanel as a standalone component. + */ + public RunTimeGui() { + this(true); + } + + /** + * Create a new LoopControlPanel as either a standalone or an embedded + * component. + * + * @param displayName + * indicates whether or not this component should display its + * name. If true, this is a standalone component. If false, this + * component is intended to be used as a subpanel for another + * component. + */ + public RunTimeGui(boolean displayName) { + this.displayName = displayName; + init(); + setState(1); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + if (element instanceof RunTime) { + setState(((RunTime) element).getRuntimeString()); + } else { + setState(1); + } + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + RunTime lc = new RunTime(); + modifyTestElement(lc); + return lc; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement lc) { + configureTestElement(lc); + if (lc instanceof RunTime) { + if (seconds.getText().length() > 0) { + ((RunTime) lc).setRuntime(seconds.getText()); + } else { + ((RunTime) lc).setRuntime(0); + } + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + seconds.setText("1"); // $NON-NLS-1$ + } + + /** + * Invoked when an action occurs. This implementation assumes that the + * target component is the infinite seconds checkbox. + * + * @param event + * the event that has occurred + */ + public void actionPerformed(ActionEvent event) { + seconds.setEnabled(true); + } + + public String getLabelResource() { + return "runtime_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + // The Loop Controller panel can be displayed standalone or inside + // another panel. For standalone, we want to display the TITLE, NAME, + // etc. (everything). However, if we want to display it within another + // panel, we just display the Loop Count fields (not the TITLE and + // NAME). + + // Standalone + if (displayName) { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createLoopCountPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } else { + // Embedded + setLayout(new BorderLayout()); + add(createLoopCountPanel(), BorderLayout.NORTH); + } + } + + /** + * Create a GUI panel containing the components related to the number of + * seconds which should be executed. + * + * @return a GUI panel containing the loop count components + */ + private JPanel createLoopCountPanel() { + JPanel loopPanel = new JPanel(new BorderLayout(5, 0)); + + // SECONDS LABEL + JLabel secondsLabel = new JLabel(JMeterUtils.getResString("runtime_seconds")); // $NON-NLS-1$ + loopPanel.add(secondsLabel, BorderLayout.WEST); + + JPanel loopSubPanel = new JPanel(new BorderLayout(5, 0)); + + // TEXT FIELD + seconds = new JTextField("1", 5); // $NON-NLS-1$ + secondsLabel.setLabelFor(seconds); + loopSubPanel.add(seconds, BorderLayout.CENTER); + + loopPanel.add(loopSubPanel, BorderLayout.CENTER); + + loopPanel.add(Box.createHorizontalStrut(secondsLabel.getPreferredSize().width + + seconds.getPreferredSize().width), BorderLayout.NORTH); + + return loopPanel; + } + + /** + * Set the number of seconds which should be reflected in the GUI. The + * secsCount parameter should contain the String representation of an + * integer. This integer will be treated as the number of seconds. If this + * integer is less than 0, the number of seconds will be assumed to be + * infinity. + * + * @param secsCount + * the String representation of the number of seconds + */ + private void setState(String secsCount) { + seconds.setText(secsCount); + seconds.setEnabled(true); + } + + /** + * Set the number of seconds which should be reflected in the GUI. If the + * secsCount is less than 0, the number of seconds will be assumed to be + * infinity. + * + * @param secsCount + * the number of seconds + */ + private void setState(long secsCount) { + seconds.setEnabled(true); + seconds.setText("" + secsCount); // $NON-NLS-1$ + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/SwitchControllerGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/SwitchControllerGui.java new file mode 100644 index 0000000..4ceb8af --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/SwitchControllerGui.java @@ -0,0 +1,96 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.SwitchController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class SwitchControllerGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + private static final String SWITCH_LABEL = "switch_controller_label"; // $NON-NLS-1$ + + private JTextField switchValue; + + public SwitchControllerGui() { + init(); + } + + public TestElement createTestElement() { + SwitchController ic = new SwitchController(); + modifyTestElement(ic); + return ic; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement ic) { + configureTestElement(ic); + ((SwitchController) ic).setSelection(switchValue.getText()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + switchValue.setText(""); // $NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + switchValue.setText(((SwitchController) el).getSelection()); + } + + public String getLabelResource() { + return "switch_controller_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createSwitchPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + } + + private JPanel createSwitchPanel() { + JPanel switchPanel = new JPanel(new BorderLayout(5, 0)); + JLabel selectionLabel = new JLabel(JMeterUtils.getResString(SWITCH_LABEL)); + switchValue = new JTextField(""); // $NON-NLS-1$ + selectionLabel.setLabelFor(switchValue); + switchPanel.add(selectionLabel, BorderLayout.WEST); + switchPanel.add(switchValue, BorderLayout.CENTER); + return switchPanel; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/TestFragmentControllerGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/TestFragmentControllerGui.java new file mode 100644 index 0000000..0c1a026 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/TestFragmentControllerGui.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.control.gui; + +import java.util.Collection; +import java.util.Arrays; +import java.awt.BorderLayout; + +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.control.TestFragmentController; +import org.apache.jmeter.testelement.TestElement; + +/** + * This defines a simple Test Fragment GUI that can be used instead of a Thread Group + * to allow for a non-execution part of the Test Plan that can be saved and referenced + * by a Module or Include Controller. + */ + +public class TestFragmentControllerGui extends AbstractControllerGui { + + private static final long serialVersionUID = 240L; + + public TestFragmentControllerGui() { + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + } + + /** + * Implements JMeterGUIComponent.createTestElement() + */ + public TestElement createTestElement() { + TestFragmentController controller = new TestFragmentController(); + modifyTestElement(controller); + return controller; + } + + /** + * Implements JMeterGUIComponent.modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement controller) { + configureTestElement(controller); + } + + public String getLabelResource() { + return "test_fragment_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + + /** + * Over-ride this so that we add ourselves to the Test Fragment Category instead. + */ + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.FRAGMENTS }); + } + + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/TestPlanGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/TestPlanGui.java new file mode 100644 index 0000000..afeed43 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/TestPlanGui.java @@ -0,0 +1,193 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; +import java.util.Collection; + +import javax.swing.JCheckBox; +import javax.swing.JMenu; +import javax.swing.JPopupMenu; +import javax.swing.JTextArea; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.util.FileListPanel; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.util.JMeterUtils; + +/** + * JMeter GUI component representing the test plan which will be executed when + * the test is run. + * + */ +public class TestPlanGui extends AbstractJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + /** + * A checkbox allowing the user to specify whether or not JMeter should do + * functional testing. + */ + private final JCheckBox functionalMode; + + private final JCheckBox serializedMode; + + /** A panel allowing the user to define variables. */ + private final ArgumentsPanel argsPanel; + + private final FileListPanel browseJar; + + /** + * Create a new TestPlanGui. + */ + public TestPlanGui() { + browseJar = new FileListPanel(JMeterUtils.getResString("test_plan_classpath_browse"), ".jar"); // $NON-NLS-1$ $NON-NLS-2$ + argsPanel = new ArgumentsPanel(JMeterUtils.getResString("user_defined_variables")); // $NON-NLS-1$ + serializedMode = new JCheckBox(JMeterUtils.getResString("testplan.serialized")); // $NON-NLS-1$ + functionalMode = new JCheckBox(JMeterUtils.getResString("functional_mode")); // $NON-NLS-1$ + init(); + } + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * The TestPlan will return a popup menu allowing you to add ThreadGroups, + * Listeners, Configuration Elements, Assertions, PreProcessors, + * PostProcessors, and Timers. + * + * @return a JPopupMenu appropriate for the component. + */ + public JPopupMenu createPopupMenu() { + JPopupMenu pop = new JPopupMenu(); + JMenu addMenu = new JMenu(JMeterUtils.getResString("add")); // $NON-NLS-1$ + addMenu.add(MenuFactory.makeMenu(MenuFactory.THREADS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.FRAGMENTS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.CONFIG_ELEMENTS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.TIMERS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.PRE_PROCESSORS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.POST_PROCESSORS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.ASSERTIONS, ActionNames.ADD)); + addMenu.add(MenuFactory.makeMenu(MenuFactory.LISTENERS, ActionNames.ADD)); + pop.add(addMenu); + MenuFactory.addPasteResetMenu(pop); + MenuFactory.addFileMenu(pop); + return pop; + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + TestPlan tp = new TestPlan(); + modifyTestElement(tp); + return tp; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement plan) { + super.configureTestElement(plan); + if (plan instanceof TestPlan) { + TestPlan tp = (TestPlan) plan; + tp.setFunctionalMode(functionalMode.isSelected()); + tp.setSerialized(serializedMode.isSelected()); + tp.setUserDefinedVariables((Arguments) argsPanel.createTestElement()); + tp.setTestPlanClasspathArray(browseJar.getFiles()); + } + } + + public String getLabelResource() { + return "test_plan"; // $NON-NLS-1$ + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns null, since the TestPlan appears at + * the top level of the tree and cannot be added elsewhere. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + public Collection getMenuCategories() { + return null; + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof TestPlan) { + TestPlan tp = (TestPlan) el; + functionalMode.setSelected(tp.isFunctionalMode()); + serializedMode.setSelected(tp.isSerialized()); + final JMeterProperty udv = tp.getUserDefinedVariablesAsProperty(); + if (udv != null) { + argsPanel.configure((Arguments) udv.getObjectValue()); + } + browseJar.setFiles(tp.getTestPlanClasspathArray()); + } + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(10, 10)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + add(argsPanel, BorderLayout.CENTER); + + VerticalPanel southPanel = new VerticalPanel(); + southPanel.add(serializedMode); + southPanel.add(functionalMode); + JTextArea explain = new JTextArea(JMeterUtils.getResString("functional_mode_explanation")); // $NON-NLS-1$ + explain.setEditable(false); + explain.setBackground(this.getBackground()); + southPanel.add(explain); + southPanel.add(browseJar); + + add(southPanel, BorderLayout.SOUTH); + } + + @Override + public void clearGui() { + super.clearGui(); + functionalMode.setSelected(false); + serializedMode.setSelected(false); + argsPanel.clear(); + browseJar.clearFiles(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/ThroughputControllerGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/ThroughputControllerGui.java new file mode 100644 index 0000000..bd90874 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/ThroughputControllerGui.java @@ -0,0 +1,169 @@ +/* + * 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.control.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.ThroughputController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class ThroughputControllerGui extends AbstractControllerGui { + private static final long serialVersionUID = 240L; + + private JComboBox styleBox; + + private int style; + + private JTextField throughput; + + private JCheckBox perthread; + + private boolean isPerThread = true; + + // These must not be static, otherwise Language change does not work + private final String BYNUMBER_LABEL = JMeterUtils.getResString("throughput_control_bynumber_label"); // $NON-NLS-1$ + + private final String BYPERCENT_LABEL = JMeterUtils.getResString("throughput_control_bypercent_label"); // $NON-NLS-1$ + + private final String THROUGHPUT_LABEL = JMeterUtils.getResString("throughput_control_tplabel"); // $NON-NLS-1$ + + private final String PERTHREAD_LABEL = JMeterUtils.getResString("throughput_control_perthread_label"); // $NON-NLS-1$ + + public ThroughputControllerGui() { + init(); + } + + public TestElement createTestElement() { + ThroughputController tc = new ThroughputController(); + modifyTestElement(tc); + return tc; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement tc) { + configureTestElement(tc); + ((ThroughputController) tc).setStyle(style); + ((ThroughputController) tc).setPerThread(isPerThread); + if (style == ThroughputController.BYNUMBER) { + try { + ((ThroughputController) tc).setMaxThroughput(Integer.parseInt(throughput.getText().trim())); + } catch (NumberFormatException e) { + // In case we are converting back from floating point, drop the decimal fraction + ((ThroughputController) tc).setMaxThroughput((throughput.getText().trim().split("\\.")[0])); // $NON-NLS-1$ + } + } else { + try { + ((ThroughputController) tc).setPercentThroughput(Float.parseFloat(throughput.getText().trim())); + } catch (NumberFormatException e) { + ((ThroughputController) tc).setPercentThroughput(throughput.getText()); + } + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + styleBox.setSelectedIndex(0); + throughput.setText("1"); // $NON-NLS-1$ + perthread.setSelected(true); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (((ThroughputController) el).getStyle() == ThroughputController.BYNUMBER) { + styleBox.getModel().setSelectedItem(BYNUMBER_LABEL); + throughput.setText(((ThroughputController) el).getMaxThroughput()); + } else { + styleBox.setSelectedItem(BYPERCENT_LABEL); + throughput.setText(((ThroughputController) el).getPercentThroughput()); + } + perthread.setSelected(((ThroughputController) el).isPerThread()); + } + + public String getLabelResource() { + return "throughput_control_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + + DefaultComboBoxModel styleModel = new DefaultComboBoxModel(); + styleModel.addElement(BYNUMBER_LABEL); + styleModel.addElement(BYPERCENT_LABEL); + styleBox = new JComboBox(styleModel); + styleBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (((String) styleBox.getSelectedItem()).equals(BYNUMBER_LABEL)) { + style = ThroughputController.BYNUMBER; + } else { + style = ThroughputController.BYPERCENT; + } + } + }); + add(styleBox); + + // TYPE FIELD + JPanel tpPanel = new JPanel(); + JLabel tpLabel = new JLabel(THROUGHPUT_LABEL); + tpPanel.add(tpLabel); + + // TEXT FIELD + throughput = new JTextField(5); + tpPanel.add(throughput); + throughput.setText("1"); // $NON-NLS-1$ + // throughput.addActionListener(this); + tpPanel.add(throughput); + add(tpPanel); + + // PERTHREAD FIELD + perthread = new JCheckBox(PERTHREAD_LABEL, isPerThread); + perthread.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent event) { + if (event.getStateChange() == ItemEvent.SELECTED) { + isPerThread = true; + } else { + isPerThread = false; + } + } + }); + add(perthread); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/TransactionControllerGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/TransactionControllerGui.java new file mode 100644 index 0000000..6294a24 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/TransactionControllerGui.java @@ -0,0 +1,86 @@ +/* + * 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.control.gui; + +import javax.swing.JCheckBox; + +import org.apache.jmeter.control.TransactionController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * A Transaction controller component. + * + */ +public class TransactionControllerGui extends AbstractControllerGui { + + private static final long serialVersionUID = 240L; + + private JCheckBox parent; // If selected, then generate parent sample, otherwise as per original controller + + private JCheckBox includeTimers; // if selected, add duration of timers to total runtime + + /** + * Create a new TransactionControllerGui instance. + */ + public TransactionControllerGui() { + init(); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + TransactionController lc = new TransactionController(); + configureTestElement(lc); + return lc; + } + + @Override + public void configure(TestElement el) { + super.configure(el); + parent.setSelected(((TransactionController) el).isParent()); + includeTimers.setSelected(((TransactionController) el).isIncludeTimers()); + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement el) { + configureTestElement(el); + ((TransactionController) el).setParent(parent.isSelected()); + TransactionController tc = ((TransactionController) el); + tc.setParent(parent.isSelected()); + tc.setIncludeTimers(includeTimers.isSelected()); + } + + public String getLabelResource() { + return "transaction_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + parent = new JCheckBox(JMeterUtils.getResString("transaction_controller_parent")); // $NON-NLS-1$ + add(parent); + includeTimers = new JCheckBox(JMeterUtils.getResString("transaction_controller_include_timers"), true); // $NON-NLS-1$ + add(includeTimers); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/WhileControllerGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/WhileControllerGui.java new file mode 100644 index 0000000..7173ad5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/WhileControllerGui.java @@ -0,0 +1,145 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.WhileController; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class WhileControllerGui extends AbstractControllerGui { + + private static final long serialVersionUID = 240L; + + private static final String CONDITION_LABEL = "while_controller_label"; // $NON-NLS-1$ + + /** + * A field allowing the user to specify the condition (not yet used). + */ + private JTextField theCondition; + + /** The name of the condition field component. */ + private static final String CONDITION = "While_Condition"; // $NON-NLS-1$ + + /** + * Create a new LoopControlPanel as a standalone component. + */ + public WhileControllerGui() { + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + if (element instanceof WhileController) { + theCondition.setText(((WhileController) element).getCondition()); + } + + } + + /** + * Implements JMeterGUIComponent.createTestElement() + */ + public TestElement createTestElement() { + WhileController controller = new WhileController(); + modifyTestElement(controller); + return controller; + } + + /** + * Implements JMeterGUIComponent.modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement controller) { + configureTestElement(controller); + if (controller instanceof WhileController) { + if (theCondition.getText().length() > 0) { + ((WhileController) controller).setCondition(theCondition.getText()); + } else { + ((WhileController) controller).setCondition(""); // $NON-NLS-1$ + } + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + theCondition.setText(""); // $NON-NLS-1$ + } + + public String getLabelResource() { + return "while_controller_title"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout for this component. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(createConditionPanel(), BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + + } + + /** + * Create a GUI panel containing the condition. TODO make use of the field + * + * @return a GUI panel containing the condition components + */ + private JPanel createConditionPanel() { + JPanel conditionPanel = new JPanel(new BorderLayout(5, 0)); + + // Condition LABEL + JLabel conditionLabel = new JLabel(JMeterUtils.getResString(CONDITION_LABEL)); + conditionPanel.add(conditionLabel, BorderLayout.WEST); + + // TEXT FIELD + // This means exit if last sample failed + theCondition = new JTextField(""); // $NON-NLS-1$ + theCondition.setName(CONDITION); + conditionLabel.setLabelFor(theCondition); + conditionPanel.add(theCondition, BorderLayout.CENTER); + + conditionPanel.add(Box.createHorizontalStrut(conditionLabel.getPreferredSize().width + + theCondition.getPreferredSize().width), BorderLayout.NORTH); + + return conditionPanel; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/control/gui/WorkBenchGui.java b/ApacheJmeter/src/org/apache/jmeter/control/gui/WorkBenchGui.java new file mode 100644 index 0000000..0676c09 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/control/gui/WorkBenchGui.java @@ -0,0 +1,118 @@ +/* + * 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.control.gui; + +import java.awt.BorderLayout; +import java.util.Collection; + +import javax.swing.JMenu; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.util.JMeterUtils; + +/** + * JMeter GUI component representing a work bench where users can make + * preparations for the test plan. + * + */ +public class WorkBenchGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + + /** + * Create a new WorkbenchGui. + */ + public WorkBenchGui() { + super(); + init(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns null, since the WorkBench appears at + * the top level of the tree and cannot be added elsewhere. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + public Collection getMenuCategories() { + return null; + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + WorkBench wb = new WorkBench(); + modifyTestElement(wb); + return wb; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement wb) { + super.configureTestElement(wb); + } + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * The WorkBench returns a popup menu allowing you to add anything. + * + * @return a JPopupMenu appropriate for the component. + */ + public JPopupMenu createPopupMenu() { + JPopupMenu menu = new JPopupMenu(); + JMenu addMenu = MenuFactory.makeMenus(new String[] { + MenuFactory.NON_TEST_ELEMENTS, + MenuFactory.CONTROLLERS, + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.TIMERS, + MenuFactory.PRE_PROCESSORS, + MenuFactory.SAMPLERS, + MenuFactory.POST_PROCESSORS, + MenuFactory.ASSERTIONS, + MenuFactory.LISTENERS, + }, + JMeterUtils.getResString("add"), // $NON-NLS-1$ + ActionNames.ADD); + menu.add(addMenu); + MenuFactory.addPasteResetMenu(menu); + MenuFactory.addFileMenu(menu); + return menu; + } + + public String getLabelResource() { + return "workbench_title"; // $NON-NLS-1$ + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/ClientJMeterEngine.java b/ApacheJmeter/src/org/apache/jmeter/engine/ClientJMeterEngine.java new file mode 100644 index 0000000..7804743 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/ClientJMeterEngine.java @@ -0,0 +1,195 @@ +/* + * 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.engine; + +import java.io.File; +import java.net.MalformedURLException; +import java.rmi.Naming; +import java.rmi.NotBoundException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.server.RemoteObject; +import java.util.Properties; + +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.SearchByClass; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Class to run remote tests from the client JMeter and collect remote samples + */ +public class ClientJMeterEngine implements JMeterEngine { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Object LOCK = new Object(); + + private RemoteJMeterEngine remote; + + private HashTree test; + + private final String host; + + private static RemoteJMeterEngine getEngine(String h) throws MalformedURLException, RemoteException, + NotBoundException { + final String name = "//" + h + "/" + RemoteJMeterEngineImpl.JMETER_ENGINE_RMI_NAME; // $NON-NLS-1$ $NON-NLS-2$ + Remote remobj = Naming.lookup(name); + if (remobj instanceof RemoteJMeterEngine){ + final RemoteJMeterEngine rje = (RemoteJMeterEngine) remobj; + if (remobj instanceof RemoteObject){ + RemoteObject robj = (RemoteObject) remobj; + System.out.println("Using remote object: "+robj.getRef().remoteToString()); + } + return rje; + } + throw new RemoteException("Could not find "+name); + } + + public ClientJMeterEngine(String host) throws MalformedURLException, NotBoundException, RemoteException { + this.remote = getEngine(host); + this.host = host; + } + + /** {@inheritDoc} */ + public void configure(HashTree testTree) { + TreeCloner cloner = new TreeCloner(false); + testTree.traverse(cloner); + test = cloner.getClonedTree(); + } + + /** {@inheritDoc} */ + public void stopTest(boolean now) { + log.info("about to "+(now ? "stop" : "shutdown")+" remote test on "+host); + try { + remote.rstopTest(now); + } catch (Exception ex) { + log.error("", ex); // $NON-NLS-1$ + } + } + + /** {@inheritDoc} */ + public void reset() { + try { + try { + remote.rreset(); + } catch (java.rmi.ConnectException e) { + log.info("Retry reset after: "+e); + remote = getEngine(host); + remote.rreset(); + } + } catch (Exception ex) { + log.error("Failed to reset remote engine", ex); // $NON-NLS-1$ + } + } + + public void runTest() throws JMeterEngineException { + log.info("running clientengine run method"); + SearchByClass testListeners = new SearchByClass(TestListener.class); + ConvertListeners sampleListeners = new ConvertListeners(); + HashTree testTree = test; + PreCompiler compiler = new PreCompiler(true); // limit the changes to client only test elements + synchronized(testTree) { + testTree.traverse(compiler); + testTree.traverse(new TurnElementsOn()); + testTree.traverse(testListeners); + testTree.traverse(sampleListeners); + } + + String methodName="unknown"; + try { + JMeterContextService.startTest(); + /* + * Add fix for Deadlocks, see: + * + * See https://issues.apache.org/bugzilla/show_bug.cgi?id=48350 + */ + File baseDirRelative = FileServer.getFileServer().getBaseDirRelative(); + String scriptName = FileServer.getFileServer().getScriptName(); + synchronized(LOCK) + { + methodName="rconfigure()"; + remote.rconfigure(testTree, host, baseDirRelative, scriptName); + } + log.info("sent test to " + host + " basedir='"+baseDirRelative+"'"); // $NON-NLS-1$ + if(savep == null) { + savep = new Properties(); + } + log.info("Sending properties "+savep); + try { + methodName="rsetProperties()"; + remote.rsetProperties(savep); + } catch (RemoteException e) { + log.warn("Could not set properties: " + e.toString()); + } + methodName="rrunTest()"; + remote.rrunTest(); + log.info("sent run command to "+ host); + } catch (IllegalStateException ex) { + log.error("Error in "+methodName+" method "+ex); // $NON-NLS-1$ $NON-NLS-2$ + tidyRMI(log); + throw ex; // Don't wrap this error - display it as is + } catch (Exception ex) { + log.error("Error in "+methodName+" method "+ex); // $NON-NLS-1$ $NON-NLS-2$ + tidyRMI(log); + throw new JMeterEngineException("Error in "+methodName+" method "+ex, ex); // $NON-NLS-1$ $NON-NLS-2$ + } + } + + /** + * Tidy up RMI access to allow JMeter client to exit. + * Currently just interrups the "RMI Reaper" thread. + * @param logger where to log the information + */ + public static void tidyRMI(Logger logger) { + for(Thread t : Thread.getAllStackTraces().keySet()){ + String reaperRE = JMeterUtils.getPropDefault("rmi.thread.name", "^RMI Reaper$"); + String name = t.getName(); + if (name.matches(reaperRE)) { + logger.info("Interrupting "+name); + t.interrupt(); + } + } + } + + /** {@inheritDoc} */ + // Called by JMeter ListenToTest if remoteStop is true + public void exit() { + log.info("about to exit remote server on "+host); + try { + remote.rexit(); + } catch (RemoteException e) { + log.warn("Could not perform remote exit: " + e.toString()); + } + } + + private Properties savep; + /** {@inheritDoc} */ + public void setProperties(Properties p) { + savep = p; + // Sent later + } + + public boolean isActive() { + return true; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/ConvertListeners.java b/ApacheJmeter/src/org/apache/jmeter/engine/ConvertListeners.java new file mode 100644 index 0000000..7a0af1e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/ConvertListeners.java @@ -0,0 +1,94 @@ +/* + * 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.engine; + +import java.rmi.RemoteException; + +import org.apache.jmeter.samplers.RemoteListenerWrapper; +import org.apache.jmeter.samplers.RemoteSampleListener; +import org.apache.jmeter.samplers.RemoteSampleListenerImpl; +import org.apache.jmeter.samplers.RemoteSampleListenerWrapper; +import org.apache.jmeter.samplers.RemoteTestListenerWrapper; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Converts the Remoteable Test and Sample Listeners in the test tree by wrapping + * them with RemoteSampleListeners so that the samples are returned to the client. + * + * N.B. Does not handle ThreadListeners. + * + */ +public class ConvertListeners implements HashTreeTraverser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * {@inheritDoc} + */ + public void addNode(Object node, HashTree subTree) { + for (Object item : subTree.list()) { + if (item instanceof AbstractThreadGroup) { + log.debug("num threads = " + ((AbstractThreadGroup) item).getNumThreads()); + } + if (item instanceof Remoteable) { + if (item instanceof ThreadListener){ + log.error("Cannot handle ThreadListener Remotable item "+item.getClass().getName()); + continue; + } + try { + RemoteSampleListener rtl = new RemoteSampleListenerImpl(item); + if (item instanceof TestListener && item instanceof SampleListener) { + RemoteListenerWrapper wrap = new RemoteListenerWrapper(rtl); + subTree.replace(item, wrap); + } else if (item instanceof TestListener) { + RemoteTestListenerWrapper wrap = new RemoteTestListenerWrapper(rtl); + subTree.replace(item, wrap); + } else if (item instanceof SampleListener) { + RemoteSampleListenerWrapper wrap = new RemoteSampleListenerWrapper(rtl); + subTree.replace(item, wrap); + } else { + log.warn("Could not replace Remotable item "+item.getClass().getName()); + } + } catch (RemoteException e) { + log.error("", e); // $NON-NLS-1$ + } + } + } + } + + /** + * {@inheritDoc} + */ + public void subtractNode() { + } + + /** + * {@inheritDoc} + */ + public void processPath() { + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/JMeterEngine.java b/ApacheJmeter/src/org/apache/jmeter/engine/JMeterEngine.java new file mode 100644 index 0000000..0a9e390 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/JMeterEngine.java @@ -0,0 +1,42 @@ +/* + * 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.engine; + +import java.util.Properties; + +import org.apache.jorphan.collections.HashTree; + +/** + * This interface is implemented by classes that can run JMeter tests. + */ +public interface JMeterEngine { + void configure(HashTree testPlan); + + void runTest() throws JMeterEngineException; + + void stopTest(boolean now); + + void reset(); + + void setProperties(Properties p); + + void exit(); + + boolean isActive(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/JMeterEngineException.java b/ApacheJmeter/src/org/apache/jmeter/engine/JMeterEngineException.java new file mode 100644 index 0000000..92a36e9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/JMeterEngineException.java @@ -0,0 +1,44 @@ +/* + * 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.engine; + +import java.io.Serializable; + +/** + * Exception class for use by {@link JMeterEngine#runTest()} and {@link RemoteJMeterEngine#rrunTest()} + */ +public class JMeterEngineException extends Exception implements Serializable { + private static final long serialVersionUID = 240L; + + public JMeterEngineException() { + super(); + } + + public JMeterEngineException(String msg) { + super(msg); + } + + public JMeterEngineException(Throwable t) { + super(t); + } + + public JMeterEngineException(String msg, Throwable t) { + super(msg, t); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/PreCompiler.java b/ApacheJmeter/src/org/apache/jmeter/engine/PreCompiler.java new file mode 100644 index 0000000..fbaac5a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/PreCompiler.java @@ -0,0 +1,106 @@ +/* + * 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.engine; + +import java.util.Map; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.engine.util.ValueReplacer; +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Class to replace function and variable references in the test tree. + * + */ +public class PreCompiler implements HashTreeTraverser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final ValueReplacer replacer; + +// Used by both StandardJMeterEngine and ClientJMeterEngine. +// In the latter case, only ResultCollectors are updated, +// as only these are relevant to the client, and updating +// other elements causes all sorts of problems. + private final boolean isRemote; // skip certain processing for remote tests + + public PreCompiler() { + replacer = new ValueReplacer(); + isRemote = false; + } + + public PreCompiler(boolean remote) { + replacer = new ValueReplacer(); + isRemote = remote; + } + + /** {@inheritDoc} */ + public void addNode(Object node, HashTree subTree) { + if(isRemote && node instanceof ResultCollector) + { + try { + replacer.replaceValues((TestElement) node); + } catch (InvalidVariableException e) { + log.error("invalid variables", e); + } + } + if (isRemote) { + return; + } + if(node instanceof TestElement) + { + try { + replacer.replaceValues((TestElement) node); + } catch (InvalidVariableException e) { + log.error("invalid variables", e); + } + } + if (node instanceof TestPlan) { + ((TestPlan)node).prepareForPreCompile(); //A hack to make user-defined variables in the testplan element more dynamic + Map args = ((TestPlan) node).getUserDefinedVariables(); + replacer.setUserDefinedVariables(args); + JMeterVariables vars = new JMeterVariables(); + vars.putAll(args); + JMeterContextService.getContext().setVariables(vars); + } + + if (node instanceof Arguments) { + ((Arguments)node).setRunningVersion(true); + Map args = ((Arguments) node).getArgumentsAsMap(); + replacer.addVariables(args); + JMeterContextService.getContext().getVariables().putAll(args); + } + } + + /** {@inheritDoc} */ + public void subtractNode() { + } + + /** {@inheritDoc} */ + public void processPath() { + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/RemoteJMeterEngine.java b/ApacheJmeter/src/org/apache/jmeter/engine/RemoteJMeterEngine.java new file mode 100644 index 0000000..c1b2c1a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/RemoteJMeterEngine.java @@ -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.engine; + +import java.io.File; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.util.Properties; + +import org.apache.jorphan.collections.HashTree; + +/** + * This is the interface for the RMI server engine, i.e. {@link RemoteJMeterEngineImpl} + */ +public interface RemoteJMeterEngine extends Remote { + void rconfigure(HashTree testTree, String host, File jmxBase, String scriptName) throws RemoteException; + + void rrunTest() throws RemoteException, JMeterEngineException; + + void rstopTest(boolean now) throws RemoteException; + + void rreset() throws RemoteException; + + void rsetProperties(Properties p) throws RemoteException; + + void rexit() throws RemoteException; +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java b/ApacheJmeter/src/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java new file mode 100644 index 0000000..e5b7cb8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java @@ -0,0 +1,227 @@ +/* + * 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.engine; + +import java.io.File; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.rmi.NotBoundException; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.util.Iterator; +import java.util.Properties; + +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This is the JMeter server main code. + */ +public class RemoteJMeterEngineImpl extends java.rmi.server.UnicastRemoteObject implements RemoteJMeterEngine { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + static final String JMETER_ENGINE_RMI_NAME = "JMeterEngine"; // $NON-NLS-1$ + + private transient JMeterEngine backingEngine; + + private transient Thread ownerThread; + + private static final int DEFAULT_RMI_PORT = + JMeterUtils.getPropDefault("server.rmi.port", 1099); // $NON-NLS-1$ + + private static final int DEFAULT_LOCAL_PORT = + JMeterUtils.getPropDefault("server.rmi.localport", 0); // $NON-NLS-1$ + + static{ + if (DEFAULT_LOCAL_PORT != 0){ + System.out.println("Using local port: "+DEFAULT_LOCAL_PORT); + } + } + + // Should we create our own copy of the RMI registry? + private static final boolean createServer = + JMeterUtils.getPropDefault("server.rmi.create", true); // $NON-NLS-1$ + + private final Object LOCK = new Object(); + + private final int rmiPort; + + private Properties remotelySetProperties; + + private RemoteJMeterEngineImpl(int localPort, int rmiPort) throws RemoteException { + super(localPort); // Create this object using the specified port (0 means anonymous) + this.rmiPort = rmiPort; + System.out.println("Created remote object: "+this.getRef().remoteToString()); + } + + public static void startServer(int rmiPort) throws RemoteException { + RemoteJMeterEngineImpl engine = new RemoteJMeterEngineImpl(DEFAULT_LOCAL_PORT, rmiPort == 0 ? DEFAULT_RMI_PORT : rmiPort); + engine.init(); + } + + private void init() throws RemoteException { + log.info("Starting backing engine on " + this.rmiPort); + InetAddress localHost=null; + // Bug 47980 - allow override of local hostname + String host = System.getProperties().getProperty("java.rmi.server.hostname"); // $NON-NLS-1$ + try { + if( host==null ) { + localHost = InetAddress.getLocalHost(); + } else { + localHost = InetAddress.getByName(host); + } + } catch (UnknownHostException e1) { + throw new RemoteException("Cannot start. Unable to get local host IP address."); + } + log.info("IP address="+localHost.getHostAddress()); + String hostName = localHost.getHostName(); + // BUG 52469 : Allow loopback address for SSH Tunneling of RMI traffic + if (localHost.isLoopbackAddress() && host == null){ + throw new RemoteException("Cannot start. "+hostName+" is a loopback address."); + } + if (localHost.isSiteLocalAddress()){ + // should perhaps be log.warn, but this causes the client-server test to fail + log.info("IP address is a site-local address; this may cause problems with remote access.\n" + + "\tCan be overridden by defining the system property 'java.rmi.server.hostname' - see jmeter-server script file"); + } + log.debug("This = " + this); + if (createServer){ + log.info("Creating RMI registry (server.rmi.create=true)"); + try { + LocateRegistry.createRegistry(this.rmiPort); + } catch (RemoteException e){ + String msg="Problem creating registry: "+e; + log.warn(msg); + System.err.println(msg); + System.err.println("Continuing..."); + } + } + try { + Registry reg = LocateRegistry.getRegistry(this.rmiPort); + reg.rebind(JMETER_ENGINE_RMI_NAME, this); + log.info("Bound to registry on port " + this.rmiPort); + } catch (Exception ex) { + log.error("rmiregistry needs to be running to start JMeter in server " + "mode\n\t" + ex.toString()); + // Throw an Exception to ensure caller knows ... + throw new RemoteException("Cannot start. See server log file."); + } + } + + /** + * Adds a feature to the ThreadGroup attribute of the RemoteJMeterEngineImpl + * object. + * + * @param testTree + * the feature to be added to the ThreadGroup attribute + */ + public void rconfigure(HashTree testTree, String host, File jmxBase, String scriptName) throws RemoteException { + log.info("Creating JMeter engine on host "+host+" base '"+jmxBase+"'"); + synchronized(LOCK) { // close window where another remote client might jump in + if (backingEngine != null && backingEngine.isActive()) { + log.warn("Engine is busy - cannot create JMeter engine"); + throw new IllegalStateException("Engine is busy - please try later"); + } + ownerThread = Thread.currentThread(); + backingEngine = new StandardJMeterEngine(host); + backingEngine.configure(testTree); // sets active = true + } + FileServer.getFileServer().setScriptName(scriptName); + FileServer.getFileServer().setBase(jmxBase); + } + + public void rrunTest() throws RemoteException, JMeterEngineException, IllegalStateException { + log.info("Running test"); + checkOwner("runTest"); + backingEngine.runTest(); + } + + public void rreset() throws RemoteException, IllegalStateException { + // Mail on userlist reported NPE here - looks like only happens if there are network errors, but check anyway + if (backingEngine != null) { + log.info("Reset"); + checkOwner("reset"); + backingEngine.reset(); + } else { + log.warn("Backing engine is null, ignoring reset"); + } + } + + public void rstopTest(boolean now) throws RemoteException { + if (now) { + log.info("Stopping test ..."); + } else { + log.info("Shutting test ..."); + } + backingEngine.stopTest(now); + log.info("... stopped"); + } + + /* + * Called by: + * - ClientJMeterEngine.exe() which is called on remoteStop + */ + public void rexit() throws RemoteException { + log.info("Exitting"); + backingEngine.exit(); + // Tidy up any objects we created + Registry reg = LocateRegistry.getRegistry(this.rmiPort); + try { + reg.unbind(JMETER_ENGINE_RMI_NAME); + } catch (NotBoundException e) { + log.warn(JMETER_ENGINE_RMI_NAME+" is not bound",e); + } + log.info("Unbound from registry"); + // Help with garbage control + System.gc(); + System.runFinalization(); + } + + public void rsetProperties(Properties p) throws RemoteException, IllegalStateException { + checkOwner("setProperties"); + if(remotelySetProperties != null) { + Properties jmeterProperties = JMeterUtils.getJMeterProperties(); + log.info("Cleaning previously set properties "+remotelySetProperties); + for (Iterator iterator = remotelySetProperties.keySet().iterator(); iterator.hasNext();) { + String key = (String) iterator.next(); + jmeterProperties.remove(key); + } + } + backingEngine.setProperties(p); + this.remotelySetProperties = p; + } + + /** + * Check if the caller owns the engine. + * @param methodName the name of the method for the log message + * @throws IllegalStateException if the caller is not the owner. + */ + private void checkOwner(String methodName) throws IllegalStateException { + if (ownerThread != null && ownerThread != Thread.currentThread()){ + String msg = "The engine is not owned by this thread - cannot call "+methodName; + log.warn(msg); + throw new IllegalStateException(msg); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/StandardJMeterEngine.java b/ApacheJmeter/src/org/apache/jmeter/engine/StandardJMeterEngine.java new file mode 100644 index 0000000..fe605fd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/StandardJMeterEngine.java @@ -0,0 +1,616 @@ +/* + * 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.engine; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.jmeter.JMeter; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.TestBeanHelper; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterThread; +import org.apache.jmeter.threads.JMeterThreadMonitor; +import org.apache.jmeter.threads.ListenerNotifier; +import org.apache.jmeter.threads.TestCompiler; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jmeter.threads.SetupThreadGroup; +import org.apache.jmeter.threads.PostThreadGroup; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; +import org.apache.jorphan.collections.SearchByClass; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Runs JMeter tests, either directly for local GUI and non-GUI invocations, + * or started by {@link RemoteJMeterEngineImpl} when running in server mode. + */ +public class StandardJMeterEngine implements JMeterEngine, JMeterThreadMonitor, Runnable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long WAIT_TO_DIE = JMeterUtils.getPropDefault("jmeterengine.threadstop.wait", 5 * 1000); // 5 seconds + + // Should we exit at end of the test? (only applies to server, because host is non-null) + private static final boolean exitAfterTest = + JMeterUtils.getPropDefault("server.exitaftertest", false); // $NON-NLS-1$ + + private static final boolean startListenersLater = + JMeterUtils.getPropDefault("jmeterengine.startlistenerslater", true); // $NON-NLS-1$ + + static { + if (startListenersLater){ + log.info("Listeners will be started after enabling running version"); + log.info("To revert to the earlier behaviour, define jmeterengine.startlistenerslater=false"); + } + } + + // Allow engine and threads to be stopped from outside a thread + // e.g. from beanshell server + // Assumes that there is only one instance of the engine + // at any one time so it is not guaranteed to work ... + private volatile static StandardJMeterEngine engine; + + /* + * Allow functions etc to register for testStopped notification. + * Only used by the function parser so far. + * The list is merged with the testListeners and then cleared. + */ + private static final List testList = new ArrayList(); + + /** Whether to call System.exit(0) in exit after stopping RMI */ + private static final boolean REMOTE_SYSTEM_EXIT = JMeterUtils.getPropDefault("jmeterengine.remote.system.exit", false); + + /** Whether to call System.exit(1) if threads won't stop */ + private static final boolean SYSTEM_EXIT_ON_STOP_FAIL = JMeterUtils.getPropDefault("jmeterengine.stopfail.system.exit", true); + + /** JMeterThread => its JVM thread */ + private final Map allThreads; + + /** Flag to show whether test is running. Set to false to stop creating more threads. */ + private volatile boolean running = false; + + /** Flag to show whether engine is active. Set to false at end of test. */ + private volatile boolean active = false; + + /** Thread Groups run sequentially */ + private volatile boolean serialized = false; + + private HashTree test; + + private volatile SearchByClass testListenersSave; + + private final String host; + + public static void stopEngineNow() { + if (engine != null) {// May be null if called from Unit test + engine.stopTest(true); + } + } + + public static void stopEngine() { + if (engine != null) { // May be null if called from Unit test + engine.stopTest(false); + } + } + + public static synchronized void register(TestListener tl) { + testList.add(tl); + } + + public static boolean stopThread(String threadName) { + return stopThread(threadName, false); + } + + public static boolean stopThreadNow(String threadName) { + return stopThread(threadName, true); + } + + private static boolean stopThread(String threadName, boolean now) { + if (engine == null) { + return false;// e.g. not yet started + } + // ConcurrentHashMap does not need synch. here + for(Entry entry : engine.allThreads.entrySet()){ + JMeterThread thrd = entry.getKey(); + if (thrd.getThreadName().equals(threadName)){ + thrd.stop(); + thrd.interrupt(); + if (now) { + Thread t = entry.getValue(); + if (t != null) { + t.interrupt(); + } + } + return true; + } + } + return false; + } + + // End of code to allow engine to be controlled remotely + + public StandardJMeterEngine() { + this(null); + } + + public StandardJMeterEngine(String host) { + this.host = host; + this.allThreads = new ConcurrentHashMap(); + // Hack to allow external control + engine = this; + } + + public void configure(HashTree testTree) { + // Is testplan serialised? + SearchByClass testPlan = new SearchByClass(TestPlan.class); + testTree.traverse(testPlan); + Object[] plan = testPlan.getSearchResults().toArray(); + if (plan.length == 0) { + throw new RuntimeException("Could not find the TestPlan class!"); + } + if (((TestPlan) plan[0]).isSerialized()) { + serialized = true; + } + active = true; + test = testTree; + } + + public void runTest() throws JMeterEngineException { + if (host != null){ + long now=System.currentTimeMillis(); + System.out.println("Starting the test on host " + host + " @ "+new Date(now)+" ("+now+")"); + } + try { + Thread runningThread = new Thread(this, "StandardJMeterEngine"); + runningThread.start(); + } catch (Exception err) { + stopTest(); + StringWriter string = new StringWriter(); + PrintWriter writer = new PrintWriter(string); + err.printStackTrace(writer); + throw new JMeterEngineException(err); + } + } + + private void removeThreadGroups(List elements) { + Iterator iter = elements.iterator(); + while (iter.hasNext()) { // Can't use for loop here because we remove elements + Object item = iter.next(); + if (item instanceof AbstractThreadGroup) { + iter.remove(); + } else if (!(item instanceof TestElement)) { + iter.remove(); + } + } + } + + @SuppressWarnings("deprecation") // Deliberate use of deprecated method + private void notifyTestListenersOfStart(SearchByClass testListeners) { + for (TestListener tl : testListeners.getSearchResults()) { + if (tl instanceof TestBean) { + TestBeanHelper.prepare((TestElement) tl); + } + if (host == null) { + tl.testStarted(); + } else { + tl.testStarted(host); + } + } + } + + private void notifyTestListenersOfEnd(SearchByClass testListeners) { + log.info("Notifying test listeners of end of test"); + for (TestListener tl : testListeners.getSearchResults()) { + try { + if (host == null) { + tl.testEnded(); + } else { + tl.testEnded(host); + } + } catch (Exception e) { + log.warn("Error encountered during shutdown of "+tl.toString(),e); + } + } + log.info("Test has ended on host "+host); + if (host != null) { + long now=System.currentTimeMillis(); + System.out.println("Finished the test on host " + host + " @ "+new Date(now)+" ("+now+")" + +(exitAfterTest ? " - exit requested." : "")); + if (exitAfterTest){ + exit(); + } + } + active=false; + } + + private ListedHashTree cloneTree(ListedHashTree tree) { + TreeCloner cloner = new TreeCloner(true); + tree.traverse(cloner); + return cloner.getClonedTree(); + } + + public void reset() { + if (running) { + stopTest(); + } + } + + // Called by JMeter thread when it finishes + public synchronized void threadFinished(JMeterThread thread) { + log.info("Ending thread " + thread.getThreadName()); + allThreads.remove(thread); + } + + public synchronized void stopTest() { + stopTest(true); + } + + public synchronized void stopTest(boolean b) { + Thread stopThread = new Thread(new StopTest(b)); + stopThread.start(); + } + + private class StopTest implements Runnable { + private final boolean now; + + private StopTest(boolean b) { + now = b; + } + + public void run() { + running = false; + engine = null; + if (now) { + tellThreadsToStop(); + pause(10 * allThreads.size()); + boolean stopped = verifyThreadsStopped(); + if (!stopped) { // we totally failed to stop the test + if (JMeter.isNonGUI()) { + // TODO should we call test listeners? That might hang too ... + log.fatalError(JMeterUtils.getResString("stopping_test_failed")); + if (SYSTEM_EXIT_ON_STOP_FAIL) { // default is true + log.fatalError("Exitting"); + System.out.println("Fatal error, could not stop test, exitting"); + System.exit(1); + } else { + System.out.println("Fatal error, could not stop test"); + } + } else { + JMeterUtils.reportErrorToUser( + JMeterUtils.getResString("stopping_test_failed"), + JMeterUtils.getResString("stopping_test_title")); + } + } // else will be done by threadFinished() + } else { + stopAllThreads(); + } + } + } + + public void run() { + log.info("Running the test!"); + running = true; + + JMeterContextService.startTest(); + try { + PreCompiler compiler = new PreCompiler(); + test.traverse(compiler); + } catch (RuntimeException e) { + log.error("Error occurred compiling the tree:",e); + JMeterUtils.reportErrorToUser("Error occurred compiling the tree: - see log file"); + return; // no point continuing + } + /** + * Notification of test listeners needs to happen after function + * replacement, but before setting RunningVersion to true. + */ + SearchByClass testListeners = new SearchByClass(TestListener.class); + test.traverse(testListeners); + + // Merge in any additional test listeners + // currently only used by the function parser + testListeners.getSearchResults().addAll(testList); + testList.clear(); // no longer needed + + testListenersSave = testListeners; + + if (!startListenersLater ) { notifyTestListenersOfStart(testListeners); } + test.traverse(new TurnElementsOn()); + if (startListenersLater) { notifyTestListenersOfStart(testListeners); } + + List testLevelElements = new LinkedList(test.list(test.getArray()[0])); + removeThreadGroups(testLevelElements); + + SearchByClass setupSearcher = new SearchByClass(SetupThreadGroup.class); + SearchByClass searcher = new SearchByClass(AbstractThreadGroup.class); + SearchByClass postSearcher = new SearchByClass(PostThreadGroup.class); + + test.traverse(setupSearcher); + test.traverse(searcher); + test.traverse(postSearcher); + + TestCompiler.initialize(); + // for each thread group, generate threads + // hand each thread the sampler controller + // and the listeners, and the timer + Iterator setupIter = setupSearcher.getSearchResults().iterator(); + Iterator iter = searcher.getSearchResults().iterator(); + Iterator postIter = postSearcher.getSearchResults().iterator(); + + ListenerNotifier notifier = new ListenerNotifier(); + + int groupCount = 0; + JMeterContextService.clearTotalThreads(); + + if (setupIter.hasNext()) { + log.info("Starting setup thread groups"); + while (running && setupIter.hasNext()) {//for each setup thread group + AbstractThreadGroup group = setupIter.next(); + groupCount++; + log.info("Starting Setup Thread: " + groupCount); + String groupName = startThreadGroup(group, groupCount, setupSearcher, testLevelElements, notifier); + if (serialized && setupIter.hasNext()) { + log.info("Waiting for setup thread group: "+groupName+" to finish before starting next setup group"); + while (running && allThreads.size() > 0) { + pause(1000); + } + } + } + log.info("Waiting for all setup thread groups To Exit"); + //wait for all Setup Threads To Exit + waitThreadsStopped(); + log.info("All Setup Threads have ended"); + groupCount=0; + JMeterContextService.clearTotalThreads(); + } + + /* + * Here's where the test really starts. Run a Full GC now: it's no harm + * at all (just delays test start by a tiny amount) and hitting one too + * early in the test can impair results for short tests. + */ + System.gc(); + + JMeterContextService.getContext().setSamplingStarted(true); + while (running && iter.hasNext()) {// for each thread group + AbstractThreadGroup group = iter.next(); + //ignore Setup and Post here. We could have filtered the searcher. but then + //future Thread Group objects wouldn't execute. + if (group instanceof SetupThreadGroup) + continue; + if (group instanceof PostThreadGroup) + continue; + groupCount++; + String groupName=startThreadGroup(group, groupCount, searcher, testLevelElements, notifier); + if (serialized && iter.hasNext()) { + log.info("Waiting for thread group: "+groupName+" to finish before starting next group"); + while (running && allThreads.size() > 0) { + pause(1000); + } + } + } // end of thread groups + if (groupCount == 0){ // No TGs found + log.info("No enabled thread groups found"); + } else { + if (running) { + log.info("All threads have been started"); + } else { + log.info("Test stopped - no more threads will be started"); + } + } + + //wait for all Test Threads To Exit + waitThreadsStopped(); + + if (postIter.hasNext()){ + groupCount = 0; + JMeterContextService.clearTotalThreads(); + log.info("Starting post thread groups"); + while (running && postIter.hasNext()) {//for each setup thread group + AbstractThreadGroup group = postIter.next(); + groupCount++; + log.info("Starting Post Thread: " + groupCount); + String groupName = startThreadGroup(group, groupCount, postSearcher, testLevelElements, notifier); + if (serialized && postIter.hasNext()) { + log.info("Waiting for post thread group: "+groupName+" to finish before starting next post group"); + while (running && allThreads.size() > 0) { + pause(1000); + } + } + } + waitThreadsStopped(); // wait for Post threads to stop + } + + notifyTestListenersOfEnd(testListenersSave); + } + + private String startThreadGroup(AbstractThreadGroup group, int groupCount, SearchByClass searcher, List testLevelElements, ListenerNotifier notifier) + { + int numThreads = group.getNumThreads(); + JMeterContextService.addTotalThreads(numThreads); + boolean onErrorStopTest = group.getOnErrorStopTest(); + boolean onErrorStopTestNow = group.getOnErrorStopTestNow(); + boolean onErrorStopThread = group.getOnErrorStopThread(); + boolean onErrorStartNextLoop = group.getOnErrorStartNextLoop(); + String groupName = group.getName(); + log.info("Starting " + numThreads + " threads for group " + groupName + "."); + + if (onErrorStopTest) { + log.info("Test will stop on error"); + } else if (onErrorStopTestNow) { + log.info("Test will stop abruptly on error"); + } else if (onErrorStopThread) { + log.info("Thread will stop on error"); + } else if (onErrorStartNextLoop) { + log.info("Thread will start next loop on error"); + } else { + log.info("Thread will continue on error"); + } + ListedHashTree threadGroupTree = (ListedHashTree) searcher.getSubTree(group); + threadGroupTree.add(group, testLevelElements); + for (int i = 0; running && i < numThreads; i++) { + final JMeterThread jmeterThread = new JMeterThread(cloneTree(threadGroupTree), this, notifier); + jmeterThread.setThreadNum(i); + jmeterThread.setThreadGroup(group); + jmeterThread.setInitialContext(JMeterContextService.getContext()); + final String threadName = groupName + " " + (groupCount) + "-" + (i + 1); + jmeterThread.setThreadName(threadName); + jmeterThread.setEngine(this); + jmeterThread.setOnErrorStopTest(onErrorStopTest); + jmeterThread.setOnErrorStopTestNow(onErrorStopTestNow); + jmeterThread.setOnErrorStopThread(onErrorStopThread); + jmeterThread.setOnErrorStartNextLoop(onErrorStartNextLoop); + + group.scheduleThread(jmeterThread); + + Thread newThread = new Thread(jmeterThread); + newThread.setName(threadName); + allThreads.put(jmeterThread, newThread); + newThread.start(); + } // end of thread startup for this thread group + return groupName; + } + + private boolean verifyThreadsStopped() { + boolean stoppedAll = true; + // ConcurrentHashMap does not need synch. here + for (Thread t : allThreads.values()) { + if (t != null) { + if (t.isAlive()) { + try { + t.join(WAIT_TO_DIE); + } catch (InterruptedException e) { + } + if (t.isAlive()) { + stoppedAll = false; + log.warn("Thread won't exit: " + t.getName()); + } + } + } + } + return stoppedAll; + } + + private void waitThreadsStopped() { + // ConcurrentHashMap does not need synch. here + for (Thread t : allThreads.values()) { + if (t != null) { + while (t.isAlive()) { + try { + t.join(WAIT_TO_DIE); + } catch (InterruptedException e) { + } + } + } + } + } + + /** + * For each thread, invoke: + *
    + *
  • {@link JMeterThread#stop()} - set stop flag
  • + *
  • {@link JMeterThread#interrupt()} - interrupt sampler
  • + *
  • {@link Thread#interrupt()} - interrupt JVM thread
  • + *
+ */ + private void tellThreadsToStop() { + // ConcurrentHashMap does not need protecting + for (Entry entry : allThreads.entrySet()) { + JMeterThread item = entry.getKey(); + item.stop(); // set stop flag + item.interrupt(); // interrupt sampler if possible + Thread t = entry.getValue(); + if (t != null ) { // Bug 49734 + t.interrupt(); // also interrupt JVM thread + } + } + } + + public void askThreadsToStop() { + if (engine != null) { // Will be null if StopTest thread has started + engine.stopTest(false); + } + } + + /** + * For each thread, invoke: + *
    + *
  • {@link JMeterThread#stop()} - set stop flag
  • + *
+ */ + private void stopAllThreads() { + // ConcurrentHashMap does not need synch. here + for (JMeterThread item : allThreads.keySet()) { + item.stop(); + } + } + + // Remote exit + // Called by RemoteJMeterEngineImpl.rexit() + // and by notifyTestListenersOfEnd() iff exitAfterTest is true; + // in turn that is called by the run() method and the StopTest class + // also called + public void exit() { + ClientJMeterEngine.tidyRMI(log); // This should be enough to allow server to exit. + if (REMOTE_SYSTEM_EXIT) { // default is false + log.warn("About to run System.exit(0) on "+host); + // Needs to be run in a separate thread to allow RMI call to return OK + Thread t = new Thread() { + @Override + public void run() { + pause(1000); // Allow RMI to complete + log.info("Bye from "+host); + System.out.println("Bye from "+host); + System.exit(0); + } + }; + t.start(); + } + } + + private void pause(long ms){ + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + } + } + + public void setProperties(Properties p) { + log.info("Applying properties "+p); + JMeterUtils.getJMeterProperties().putAll(p); + } + + public boolean isActive() { + return active; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/TreeCloner.java b/ApacheJmeter/src/org/apache/jmeter/engine/TreeCloner.java new file mode 100644 index 0000000..6b4c1ab --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/TreeCloner.java @@ -0,0 +1,103 @@ +/* + * 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.engine; + +import java.util.LinkedList; + +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.collections.ListedHashTree; + +/** + * Clones the test tree, skipping test elements that implement {@link NoThreadClone} by default. + */ +public class TreeCloner implements HashTreeTraverser { + + private final ListedHashTree newTree; + + private final LinkedList objects = new LinkedList(); + + private final boolean honourNoThreadClone; + + /** + * Clone the test tree, honouring NoThreadClone markers. + * + */ + public TreeCloner() { + this(true); + } + + /** + * Clone the test tree. + * + * @param honourNoThreadClone set false to clone NoThreadClone nodes as well + */ + public TreeCloner(boolean honourNoThreadClone) { + newTree = new ListedHashTree(); + this.honourNoThreadClone = honourNoThreadClone; + } + + /** + * @param node + * @param subTree {@link HashTree} + */ + public final void addNode(Object node, HashTree subTree) { + node = addNodeToTree(node); + addLast(node); + } + + /** + * @param node Node to add to tree or not + * @return Object node (clone or not) + */ + protected Object addNodeToTree(Object node) { + if ( (node instanceof TestElement) // Check can cast for clone + // Don't clone NoThreadClone unless honourNoThreadClone == false + && (!honourNoThreadClone || !(node instanceof NoThreadClone)) + ) { + node = ((TestElement) node).clone(); + newTree.add(objects, node); + } else { + newTree.add(objects, node); + } + return node; + } + + /** + * add node to objects LinkedList + * @param node Object + */ + private final void addLast(Object node) { + objects.addLast(node); + } + + public void subtractNode() { + objects.removeLast(); + } + + public ListedHashTree getClonedTree() { + return newTree; + } + + public void processPath() { + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/TreeClonerNoTimer.java b/ApacheJmeter/src/org/apache/jmeter/engine/TreeClonerNoTimer.java new file mode 100644 index 0000000..943fe7a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/TreeClonerNoTimer.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.jmeter.engine; + +import org.apache.jmeter.timers.Timer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Clones the test tree, skipping test elements that implement {@link Timer} by default. + */ +public class TreeClonerNoTimer extends TreeCloner{ + private Logger logger = LoggingManager.getLoggerForClass(); + + public TreeClonerNoTimer() { + super(); + } + + public TreeClonerNoTimer(boolean honourNoThreadClone) { + super(honourNoThreadClone); + } + + /** + * Doesn't add Timer to tree + * @see org.apache.jmeter.engine.TreeCloner#addNodeToTree(java.lang.Object) + */ + @Override + protected Object addNodeToTree(Object node) { + if(node instanceof Timer) { + if(logger.isDebugEnabled()) { + logger.debug("Ignoring timer node:"+ node); + } + return node; // don't add the timer + } else { + return super.addNodeToTree(node); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/TurnElementsOn.java b/ApacheJmeter/src/org/apache/jmeter/engine/TurnElementsOn.java new file mode 100644 index 0000000..46179e4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/TurnElementsOn.java @@ -0,0 +1,53 @@ +/* + * 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.engine; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; + +/** + * Invokes {@link TestElement#setRunningVersion(boolean) setRunningVersion(true)} for all matched nodes + */ +public class TurnElementsOn implements HashTreeTraverser { + + /** + * {@inheritDoc} + */ + public void addNode(Object node, HashTree subTree) { + if (node instanceof TestElement && !(node instanceof TestPlan)) { + ((TestElement) node).setRunningVersion(true); + } + + } + + /** + * {@inheritDoc} + */ + public void subtractNode() { + } + + /** + * {@inheritDoc} + */ + public void processPath() { + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/event/LoopIterationEvent.java b/ApacheJmeter/src/org/apache/jmeter/engine/event/LoopIterationEvent.java new file mode 100644 index 0000000..b1d1e18 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/event/LoopIterationEvent.java @@ -0,0 +1,55 @@ +/* + * 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.engine.event; + +import org.apache.jmeter.testelement.TestElement; + +/** + * An iteration event provides information about the iteration number and the + * source of the event. + */ +public class LoopIterationEvent { + private final int iteration; + + private final TestElement source; + + public LoopIterationEvent(TestElement source, int iter) { + iteration = iter; + this.source = source; + } + + /** + * Returns the iteration. + * + * @return int + */ + public int getIteration() { + return iteration; + } + + /** + * Returns the source. + * + * @return TestElement + */ + public TestElement getSource() { + return source; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/event/LoopIterationListener.java b/ApacheJmeter/src/org/apache/jmeter/engine/event/LoopIterationListener.java new file mode 100644 index 0000000..1c5d602 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/event/LoopIterationListener.java @@ -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.engine.event; + +/** + * Allows a class to receive loop iteration start events. + */ +public interface LoopIterationListener { + /** + * Called when a loop iteration is about to start. + * + * @param iterEvent the event + */ + public void iterationStart(LoopIterationEvent iterEvent); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/package-info.java b/ApacheJmeter/src/org/apache/jmeter/engine/package-info.java new file mode 100644 index 0000000..bf41a97 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + * + */ + +/** + * This package contains the interfaces and classes that are used to run JMeter tests. + */ + +package org.apache.jmeter.engine; \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/AbstractTransformer.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/AbstractTransformer.java new file mode 100644 index 0000000..3510a43 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/AbstractTransformer.java @@ -0,0 +1,49 @@ +/* + * 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. + * + */ + +/* + * Created on May 4, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.Map; + +abstract class AbstractTransformer implements ValueTransformer { + + private CompoundVariable masterFunction; + + private Map variables; + + /** {@inheritDoc} */ + public void setMasterFunction(CompoundVariable variable) { + masterFunction = variable; + } + + protected CompoundVariable getMasterFunction() { + return masterFunction; + } + + public Map getVariables() { + return variables; + } + + /** {@inheritDoc} */ + public void setVariables(Map map) { + variables = map; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/CompoundVariable.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/CompoundVariable.java new file mode 100644 index 0000000..a48ec09 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/CompoundVariable.java @@ -0,0 +1,225 @@ +/* + * 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.engine.util; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.functions.Function; +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; + +/** + * CompoundFunction. + * + */ +public class CompoundVariable implements Function { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private String rawParameters; + + private static final FunctionParser functionParser = new FunctionParser(); + + // Created during class init; not modified thereafter + private static final Map> functions = + new HashMap>(); + + private boolean hasFunction, isDynamic; + + private String permanentResults = ""; // $NON-NLS-1$ + + private LinkedList compiledComponents = new LinkedList(); + + static { + try { + final String contain = // Classnames must contain this string [.functions.] + JMeterUtils.getProperty("classfinder.functions.contain"); // $NON-NLS-1$ + final String notContain = // Classnames must not contain this string [.gui.] + JMeterUtils.getProperty("classfinder.functions.notContain"); // $NON-NLS-1$ + if (contain!=null){ + log.info("Note: Function class names must contain the string: '"+contain+"'"); + } + if (notContain!=null){ + 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); + Iterator iter = classes.iterator(); + while (iter.hasNext()) { + Function tempFunc = (Function) Class.forName(iter.next()).newInstance(); + String referenceKey = tempFunc.getReferenceKey(); + if (referenceKey.length() > 0) { // ignore self + functions.put(referenceKey, tempFunc.getClass()); + // Add alias for original StringFromFile name (had only one underscore) + if (referenceKey.equals("__StringFromFile")){//$NON-NLS-1$ + functions.put("_StringFromFile", tempFunc.getClass());//$NON-NLS-1$ + } + } + } + final int functionCount = functions.size(); + if (functionCount == 0){ + log.warn("Did not find any functions"); + } else { + log.debug("Function count: "+functionCount); + } + } catch (Exception err) { + log.error("", err); + } + } + + public CompoundVariable() { + super(); + isDynamic = true; + hasFunction = false; + } + + public CompoundVariable(String parameters) { + this(); + try { + setParameters(parameters); + } catch (InvalidVariableException e) { + } + } + + public String execute() { + if (isDynamic) { + JMeterContext context = JMeterContextService.getContext(); + SampleResult previousResult = context.getPreviousResult(); + Sampler currentSampler = context.getCurrentSampler(); + return execute(previousResult, currentSampler); + } + return permanentResults; // $NON-NLS-1$ + } + + /** + * Allows the retrieval of the original String prior to it being compiled. + * + * @return String + */ + public String getRawParameters() { + return rawParameters; + } + + /** {@inheritDoc} */ + public String execute(SampleResult previousResult, Sampler currentSampler) { + if (compiledComponents == null || compiledComponents.size() == 0) { + return ""; // $NON-NLS-1$ + } + boolean testDynamic = false; + StringBuilder results = new StringBuilder(); + for (Object item : compiledComponents) { + if (item instanceof Function) { + testDynamic = true; + try { + results.append(((Function) item).execute(previousResult, currentSampler)); + } catch (InvalidVariableException e) { + } + } else if (item instanceof SimpleVariable) { + testDynamic = true; + results.append(((SimpleVariable) item).toString()); + } else { + results.append(item); + } + } + if (!testDynamic) { + isDynamic = false; + permanentResults = results.toString(); + } + return results.toString(); + } + + @SuppressWarnings("unchecked") // clone will produce correct type + public CompoundVariable getFunction() { + CompoundVariable func = new CompoundVariable(); + func.compiledComponents = (LinkedList) compiledComponents.clone(); + func.rawParameters = rawParameters; + return func; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return new LinkedList(); + } + + public void clear() { + // TODO should this also clear isDynamic, rawParameters, permanentResults? + hasFunction = false; + compiledComponents.clear(); + } + + public void setParameters(String parameters) throws InvalidVariableException { + this.rawParameters = parameters; + if (parameters == null || parameters.length() == 0) { + return; + } + + compiledComponents = functionParser.compileString(parameters); + if (compiledComponents.size() > 1 || !(compiledComponents.get(0) instanceof String)) { + hasFunction = true; + } + } + + static Object getNamedFunction(String functionName) throws InvalidVariableException { + if (functions.containsKey(functionName)) { + try { + return ((Class) functions.get(functionName)).newInstance(); + } catch (Exception e) { + log.error("", e); // $NON-NLS-1$ + throw new InvalidVariableException(); + } + } + return new SimpleVariable(functionName); + } + + // For use by FunctionHelper + public static Class getFunctionClass(String className) { + return functions.get(className); + } + + // For use by FunctionHelper + public static String[] getFunctionNames() { + return functions.keySet().toArray(new String[functions.size()]); + } + + public boolean hasFunction() { + return hasFunction; + } + + // Dummy methods needed by Function interface + + /** {@inheritDoc} */ + public String getReferenceKey() { + return ""; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + public void setParameters(Collection parameters) throws InvalidVariableException { + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/ConfigMergabilityIndicator.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/ConfigMergabilityIndicator.java new file mode 100644 index 0000000..d289139 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/ConfigMergabilityIndicator.java @@ -0,0 +1,37 @@ +/* + * 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.engine.util; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.threads.TestCompiler; + +/** + * Interface that gives a hint about the merge policy to apply between Samplers and Config elements + * @see TestCompiler#configureWithConfigElements + * @since 2.7 + */ +public interface ConfigMergabilityIndicator { + + /** + * Does configElement apply to Sampler + * @param configElement {@link ConfigTestElement} + * @return boolean + */ + public boolean applies(ConfigTestElement configElement); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/FunctionParser.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/FunctionParser.java new file mode 100644 index 0000000..317c842 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/FunctionParser.java @@ -0,0 +1,246 @@ +/* + * 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. + * + */ + +/* + * Created on Jul 25, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.io.IOException; +import java.io.StringReader; +import java.util.LinkedList; + +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.functions.Function; +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Parses function / variable references of the form + * ${functionName[([var[,var...]])]} + * and + * ${variableName} + */ +class FunctionParser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Compile a general string into a list of elements for a CompoundVariable. + * + * Calls {@link #makeFunction(StringReader)} if it detects an unescaped "${". + * + * Removes escapes from '$', ',' and '\'. + * + * @param value string containing the function / variable references (if any) + * + * @return list of Strings or Objects representing functions + */ + LinkedList compileString(String value) throws InvalidVariableException { + StringReader reader = new StringReader(value); + LinkedList result = new LinkedList(); + StringBuilder buffer = new StringBuilder(); + char previous = ' '; // TODO - why use space? + char[] current = new char[1]; + try { + while (reader.read(current) == 1) { + if (current[0] == '\\') { // Handle escapes + previous = current[0]; + if (reader.read(current) == 0) { + break; + } + // Keep the '\' unless it is one of the escapable chars '$' ',' or '\' + // N.B. This method is used to parse function parameters, so must treat ',' as special + if (current[0] != '$' && current[0] != ',' && current[0] != '\\') { + buffer.append(previous); // i.e. '\\' + } + previous = ' '; + buffer.append(current[0]); + continue; + } else if (current[0] == '{' && previous == '$') {// found "${" + buffer.deleteCharAt(buffer.length() - 1); + if (buffer.length() > 0) {// save leading text + result.add(buffer.toString()); + buffer.setLength(0); + } + result.add(makeFunction(reader)); + previous = ' '; + } else { + buffer.append(current[0]); + previous = current[0]; + } + } + if (buffer.length() > 0) { + result.add(buffer.toString()); + } + } catch (IOException e) { + log.error("Error parsing function: " + value, e); + result.clear(); + result.add(value); + } + if (result.size() == 0) { + result.add(""); + } + return result; + } + + /** + * Compile a string into a function or SimpleVariable. + * + * Called by {@link #compileString(String)} when that has detected "${". + * + * Calls {@link CompoundVariable#getNamedFunction(String)} if it detects: + * '(' - start of parameter list + * '}' - end of function call + * + * @param reader points to input after the "${" + * @return the function or variable object (or a String) + */ + Object makeFunction(StringReader reader) throws InvalidVariableException { + char[] current = new char[1]; + char previous = ' '; // TODO - why use space? + StringBuilder buffer = new StringBuilder(); + Object function; + try { + while (reader.read(current) == 1) { + if (current[0] == '\\') { + if (reader.read(current) == 0) { + break; + } + previous = ' '; + buffer.append(current[0]); + continue; + } else if (current[0] == '(' && previous != ' ') { + String funcName = buffer.toString(); + function = CompoundVariable.getNamedFunction(funcName); + if (function instanceof Function) { + ((Function) function).setParameters(parseParams(reader)); + if (reader.read(current) == 0 || current[0] != '}') { + reader.reset();// set to start of string + char []cb = new char[100]; + int nbRead = reader.read(cb); + throw new InvalidVariableException + ("Expected } after "+funcName+" function call in "+new String(cb, 0, nbRead)); + } + if (function instanceof TestListener) { + StandardJMeterEngine.register((TestListener) function); + } + return function; + } else { // Function does not exist, so treat as per missing variable + buffer.append(current[0]); + } + continue; + } else if (current[0] == '}') {// variable, or function with no parameter list + function = CompoundVariable.getNamedFunction(buffer.toString()); + if (function instanceof Function){// ensure that setParameters() is called. + ((Function) function).setParameters(new LinkedList()); + } + buffer.setLength(0); + return function; + } else { + buffer.append(current[0]); + previous = current[0]; + } + } + } catch (IOException e) { + log.error("Error parsing function: " + buffer.toString(), e); + return null; + } + log.warn("Probably an invalid function string: " + buffer.toString()); + return buffer.toString(); + } + + /** + * Compile a String into a list of parameters, each made into a + * CompoundVariable. + * + * Parses strings of the following form: + *
    + *
  • text)
  • + *
  • text,text)
  • + *
  • + *
+ * @param reader a StringReader pointing to the current input location, just after "(" + * @return a list of CompoundVariable elements + */ + LinkedList parseParams(StringReader reader) throws InvalidVariableException { + LinkedList result = new LinkedList(); + StringBuilder buffer = new StringBuilder(); + char[] current = new char[1]; + char previous = ' '; + int functionRecursion = 0; + int parenRecursion = 0; + try { + while (reader.read(current) == 1) { + if (current[0] == '\\') { // Process escaped characters + buffer.append(current[0]); // Store the \ + if (reader.read(current) == 0) { + break; // end of buffer + } + previous = ' '; + buffer.append(current[0]); // store the following character + continue; + } else if (current[0] == ',' && functionRecursion == 0) { + CompoundVariable param = new CompoundVariable(); + param.setParameters(buffer.toString()); + buffer.setLength(0); + result.add(param); + } else if (current[0] == ')' && functionRecursion == 0 && parenRecursion == 0) { + // Detect functionName() so this does not generate empty string as the parameter + if (buffer.length() == 0 && result.isEmpty()){ + return result; + } + // Normal exit occurs here + CompoundVariable param = new CompoundVariable(); + param.setParameters(buffer.toString()); + buffer.setLength(0); + result.add(param); + return result; + } else if (current[0] == '{' && previous == '$') { + buffer.append(current[0]); + previous = current[0]; + functionRecursion++; + } else if (current[0] == '}' && functionRecursion > 0) { + buffer.append(current[0]); + previous = current[0]; + functionRecursion--; + } else if (current[0] == ')' && functionRecursion == 0 && parenRecursion > 0) { + buffer.append(current[0]); + previous = current[0]; + parenRecursion--; + } else if (current[0] == '(' && functionRecursion == 0) { + buffer.append(current[0]); + previous = current[0]; + parenRecursion++; + } else { + buffer.append(current[0]); + previous = current[0]; + } + } + } catch (IOException e) {// Should not happen with StringReader + log.error("Error parsing function: " + buffer.toString(), e); + } + // Dropped out, i.e. did not find closing ')' + log.warn("Probably an invalid function string: " + buffer.toString()); + CompoundVariable var = new CompoundVariable(); + var.setParameters(buffer.toString()); + result.add(var); + return result; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/NoConfigMerge.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/NoConfigMerge.java new file mode 100644 index 0000000..4c7225f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/NoConfigMerge.java @@ -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.engine.util; + +import org.apache.jmeter.threads.TestCompiler; +import org.apache.jmeter.samplers.Sampler; + +/** + * Implement this method-less interface to indicate that this ConfigElement should not be merged. + * Otherwise, the default behavior is to merge the element with every sampler in scope. + * + * @see TestCompiler#configureSampler(Sampler) + * @version $Revision: 1310743 $ + * @since 2.7 + */ +public interface NoConfigMerge { +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/NoThreadClone.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/NoThreadClone.java new file mode 100644 index 0000000..4c649ff --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/NoThreadClone.java @@ -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. + * + */ + +/* + * Created on Apr 23, 2003 + */ +package org.apache.jmeter.engine.util; + +/** + * Implement this method-less interface to indicate your test element should not + * be cloned for each thread in a test run. Otherwise, the default behavior is + * to clone every test element for each thread. + * + * @version $Revision: 493779 $ + */ +public interface NoThreadClone { +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/ReplaceFunctionsWithStrings.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/ReplaceFunctionsWithStrings.java new file mode 100644 index 0000000..290e4c2a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/ReplaceFunctionsWithStrings.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/* + * Created on May 4, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.StringUtilities; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.regex.MalformedPatternException; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternCompiler; +import org.apache.oro.text.regex.PatternMatcher; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.StringSubstitution; +import org.apache.oro.text.regex.Util; + +/** + * Transforms strings into variable references (in spite of the name, which + * suggests the opposite!) + * + */ +public class ReplaceFunctionsWithStrings extends AbstractTransformer { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Functions are wrapped in ${ and } + private static final String FUNCTION_REF_PREFIX = "${"; //$NON-NLS-1$ + + private static final String FUNCTION_REF_SUFFIX = "}"; //$NON-NLS-1$ + + private boolean regexMatch;// Should we match using regexes? + + public ReplaceFunctionsWithStrings(CompoundVariable masterFunction, Map variables) { + this(masterFunction, variables, false); + } + + public ReplaceFunctionsWithStrings(CompoundVariable masterFunction, Map variables, boolean regexMatch) { + super(); + setMasterFunction(masterFunction); + setVariables(variables); + this.regexMatch = regexMatch; + } + + public JMeterProperty transformValue(JMeterProperty prop) throws InvalidVariableException { + PatternMatcher pm = JMeterUtils.getMatcher(); + Pattern pattern = null; + PatternCompiler compiler = new Perl5Compiler(); + String input = prop.getStringValue(); + if(input == null) { + return prop; + } + for(Entry entry : getVariables().entrySet()){ + String key = entry.getKey(); + String value = entry.getValue(); + if (regexMatch) { + try { + pattern = compiler.compile("\\b("+value+")\\b"); + input = Util.substitute(pm, pattern, + new StringSubstitution(FUNCTION_REF_PREFIX + key + FUNCTION_REF_SUFFIX), + input, Util.SUBSTITUTE_ALL); + } catch (MalformedPatternException e) { + log.warn("Malformed pattern " + value); + } + } else { + input = StringUtilities.substitute(input, value, FUNCTION_REF_PREFIX + key + FUNCTION_REF_SUFFIX); + } + } + StringProperty newProp = new StringProperty(prop.getName(), input); + return newProp; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/ReplaceStringWithFunctions.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/ReplaceStringWithFunctions.java new file mode 100644 index 0000000..1fd8eb3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/ReplaceStringWithFunctions.java @@ -0,0 +1,47 @@ +/* + * 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. + * + */ + +/* + * Created on May 4, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.Map; + +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.property.FunctionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; + +public class ReplaceStringWithFunctions extends AbstractTransformer { + public ReplaceStringWithFunctions(CompoundVariable masterFunction, Map variables) { + super(); + setMasterFunction(masterFunction); + setVariables(variables); + } + + public JMeterProperty transformValue(JMeterProperty prop) throws InvalidVariableException { + JMeterProperty newValue = prop; + getMasterFunction().clear(); + getMasterFunction().setParameters(prop.getStringValue()); + if (getMasterFunction().hasFunction()) { + newValue = new FunctionProperty(prop.getName(), getMasterFunction().getFunction()); + } + return newValue; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/SimpleVariable.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/SimpleVariable.java new file mode 100644 index 0000000..c560a52 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/SimpleVariable.java @@ -0,0 +1,69 @@ +/* + * 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.engine.util; + +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +public class SimpleVariable { + + private String name; + + public SimpleVariable(String name) { + this.name = name; + } + + public SimpleVariable() { + this.name = ""; //$NON-NLS-1$ + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * @see org.apache.jmeter.functions.Function#execute + */ + @Override + public String toString() { + String ret = null; + JMeterVariables vars = getVariables(); + + if (vars != null) { + ret = vars.get(name); + } + + if (ret == null) { + return "${" + name + "}"; + } + + return ret; + } + + private JMeterVariables getVariables() { + JMeterContext context = JMeterContextService.getContext(); + return context.getVariables(); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/UndoVariableReplacement.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/UndoVariableReplacement.java new file mode 100644 index 0000000..02b3971 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/UndoVariableReplacement.java @@ -0,0 +1,48 @@ +/* + * 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. + * + */ + +/* + * Created on May 4, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.Map; + +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.StringUtilities; + +public class UndoVariableReplacement extends AbstractTransformer { + public UndoVariableReplacement(CompoundVariable masterFunction, Map variables) { + super(); + setMasterFunction(masterFunction); + setVariables(variables); + } + + public JMeterProperty transformValue(JMeterProperty prop) throws InvalidVariableException { + String input = prop.getStringValue(); + for (Map.Entry entry : getVariables().entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + input = StringUtilities.substitute(input, "${" + key + "}", value); + } + StringProperty newProp = new StringProperty(prop.getName(), input); + return newProp; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/ValueReplacer.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/ValueReplacer.java new file mode 100644 index 0000000..bc06a90 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/ValueReplacer.java @@ -0,0 +1,142 @@ +/* + * 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.engine.util; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.MultiProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Perfom replacement of ${variable} references. + */ +public class ValueReplacer { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final CompoundVariable masterFunction = new CompoundVariable(); + + private Map variables = new HashMap(); + + public ValueReplacer() { + } + + public ValueReplacer(TestPlan tp) { + setUserDefinedVariables(tp.getUserDefinedVariables()); + } + + boolean containsKey(String k){ + return variables.containsKey(k); + } + + public void setUserDefinedVariables(Map variables) { + this.variables = variables; + } + + public void replaceValues(TestElement el) throws InvalidVariableException { + Collection newProps = replaceValues(el.propertyIterator(), new ReplaceStringWithFunctions(masterFunction, + variables)); + setProperties(el, newProps); + } + + private void setProperties(TestElement el, Collection newProps) { + el.clear(); + for (JMeterProperty jmp : newProps) { + el.setProperty(jmp); + } + } + + public void reverseReplace(TestElement el) throws InvalidVariableException { + Collection newProps = replaceValues(el.propertyIterator(), new ReplaceFunctionsWithStrings(masterFunction, + variables)); + setProperties(el, newProps); + } + + public void reverseReplace(TestElement el, boolean regexMatch) throws InvalidVariableException { + Collection newProps = replaceValues(el.propertyIterator(), new ReplaceFunctionsWithStrings(masterFunction, + variables, regexMatch)); + setProperties(el, newProps); + } + + public void undoReverseReplace(TestElement el) throws InvalidVariableException { + Collection newProps = replaceValues(el.propertyIterator(), new UndoVariableReplacement(masterFunction, + variables)); + setProperties(el, newProps); + } + + public void addVariable(String name, String value) { + variables.put(name, value); + } + + /** + * Add all the given variables to this replacer's variables map. + * + * @param vars + * A map of variable name-value pairs (String-to-String). + */ + public void addVariables(Map vars) { + variables.putAll(vars); + } + + private Collection replaceValues(PropertyIterator iter, ValueTransformer transform) throws InvalidVariableException { + List props = new LinkedList(); + while (iter.hasNext()) { + JMeterProperty val = iter.next(); + if (log.isDebugEnabled()) { + log.debug("About to replace in property of type: " + val.getClass() + ": " + val); + } + if (val instanceof StringProperty) { + // Must not convert TestElement.gui_class etc + if (!val.getName().equals(TestElement.GUI_CLASS) && + !val.getName().equals(TestElement.TEST_CLASS)) { + val = transform.transformValue(val); + if (log.isDebugEnabled()) { + log.debug("Replacement result: " + val); + } + } + } else if (val instanceof MultiProperty) { + MultiProperty multiVal = (MultiProperty) val; + Collection newValues = replaceValues(multiVal.iterator(), transform); + multiVal.clear(); + for (JMeterProperty jmp : newValues) { + multiVal.addProperty(jmp); + } + if (log.isDebugEnabled()) { + log.debug("Replacement result: " + multiVal); + } + } else { + if (log.isDebugEnabled()) { + log.debug("Won't replace " + val); + } + } + props.add(val); + } + return props; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/engine/util/ValueTransformer.java b/ApacheJmeter/src/org/apache/jmeter/engine/util/ValueTransformer.java new file mode 100644 index 0000000..8b677c8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/engine/util/ValueTransformer.java @@ -0,0 +1,53 @@ +/* + * 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. + * + */ + +/* + * Created on May 4, 2003 + */ +package org.apache.jmeter.engine.util; + +import java.util.Map; + +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.property.JMeterProperty; + +interface ValueTransformer { + /** + * Transform the given property and return the new version. + * + * @param property + * @return the transformed property + */ + public JMeterProperty transformValue(JMeterProperty property) throws InvalidVariableException; + + /** + * Set the master function for the value transformer. This handles + * converting strings to functions. + * + * @param masterFunction + */ + public void setMasterFunction(CompoundVariable masterFunction); + + /** + * Set the variable names and values used to reverse replace functions with + * strings, and undo functions to raw values. + * + * @param vars + */ + public void setVariables(Map vars); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/examples/sampler/ExampleSampler.java b/ApacheJmeter/src/org/apache/jmeter/examples/sampler/ExampleSampler.java new file mode 100644 index 0000000..034d274 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/examples/sampler/ExampleSampler.java @@ -0,0 +1,125 @@ +/* + * 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.examples.sampler; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Example Sampler (non-Bean version) + * + * JMeter creates an instance of a sampler class for every occurrence of the + * element in every thread. [some additional copies may be created before the + * test run starts] + * + * Thus each sampler is guaranteed to be called by a single thread - there is no + * need to synchronize access to instance variables. + * + * However, access to class fields must be synchronized. + * + * @version $Revision: 937663 $ + */ +public class ExampleSampler extends AbstractSampler { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // The name of the property used to hold our data + public final static String DATA = "ExampleSampler.data"; //$NON-NLS-1$ + + private static int classCount = 0; // keep track of classes created + + // (for instructional purposes only!) + + public ExampleSampler() { + classCount++; + trace("ExampleSampler()"); + } + + /** + * {@inheritDoc} + */ + public SampleResult sample(Entry e) { + trace("sample()"); + SampleResult res = new SampleResult(); + boolean isOK = false; // Did sample succeed? + String data = getData(); // Sampler data + String response = null; + + res.setSampleLabel(getTitle()); + /* + * Perform the sampling + */ + res.sampleStart(); // Start timing + try { + + // Do something here ... + + response = Thread.currentThread().getName(); + + /* + * Set up the sample result details + */ + res.setSamplerData(data); + res.setResponseData(response, null); + res.setDataType(SampleResult.TEXT); + + res.setResponseCodeOK(); + res.setResponseMessage("OK");// $NON-NLS-1$ + isOK = true; + } catch (Exception ex) { + log.debug("", ex); + res.setResponseCode("500");// $NON-NLS-1$ + res.setResponseMessage(ex.toString()); + } + res.sampleEnd(); // End timimg + + res.setSuccessful(isOK); + + return res; + } + + /** + * @return a string for the sampleResult Title + */ + private String getTitle() { + return this.getName(); + } + + /** + * @return the data for the sample + */ + public String getData() { + return getPropertyAsString(DATA); + } + + /* + * Helper method + */ + private void trace(String s) { + String tl = getTitle(); + String tn = Thread.currentThread().getName(); + String th = this.toString(); + log.debug(tn + " (" + classCount + ") " + tl + " " + s + " " + th); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/examples/sampler/gui/ExampleSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/examples/sampler/gui/ExampleSamplerGui.java new file mode 100644 index 0000000..6b4a70e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/examples/sampler/gui/ExampleSamplerGui.java @@ -0,0 +1,131 @@ +/* + * 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. + * + */ + +/* + * Example Sampler GUI (non-beans version) + */ + +package org.apache.jmeter.examples.sampler.gui; + +import java.awt.BorderLayout; +import java.awt.Component; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import org.apache.jmeter.examples.sampler.ExampleSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Example Sampler (non-Bean version) + * + * This class is responsible for ensuring that the Sampler data is kept in step + * with the GUI. + * + * The GUI class is not invoked in non-GUI mode, so it should not perform any + * additional setup that a test would need at run-time + * + */ +public class ExampleSamplerGui extends AbstractSamplerGui { + + private static final long serialVersionUID = 240L; + + private JTextArea data; + + public ExampleSamplerGui() { + init(); + } + + /** + * {@inheritDoc} + */ + public String getLabelResource() { + return "example_title"; // $NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement element) { + data.setText(element.getPropertyAsString(ExampleSampler.DATA)); + super.configure(element); + } + + /** + * {@inheritDoc} + */ + public TestElement createTestElement() { + ExampleSampler sampler = new ExampleSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * {@inheritDoc} + */ + public void modifyTestElement(TestElement te) { + te.clear(); + configureTestElement(te); + te.setProperty(ExampleSampler.DATA, data.getText()); + } + + /* + * Helper method to set up the GUI screen + */ + private void init() { + // Standard setup + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); // Add the standard title + + // Specific setup + add(createDataPanel(), BorderLayout.CENTER); + } + + /* + * Create a data input text field + * + * @return the panel for entering the data + */ + private Component createDataPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("example_data")); //$NON-NLS-1$ + + data = new JTextArea(); + data.setName(ExampleSampler.DATA); + label.setLabelFor(data); + + JPanel dataPanel = new JPanel(new BorderLayout(5, 0)); + dataPanel.add(label, BorderLayout.WEST); + dataPanel.add(data, BorderLayout.CENTER); + + return dataPanel; + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + data.setText(""); // $NON-NLS-1$ + + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example1/Example1.java b/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example1/Example1.java new file mode 100644 index 0000000..e622bc6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example1/Example1.java @@ -0,0 +1,61 @@ +/* + * 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.examples.testbeans.example1; + +import java.util.Locale; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; + +/** + * This TestBean is just an example about how to write testbeans. The intent is + * to demonstrate usage of the TestBean features to podential TestBean + * developers. Note that only the class's introspector view matters: the methods + * do nothing -- nothing useful, in any case. + */ +public class Example1 extends AbstractSampler implements TestBean { + + private static final long serialVersionUID = 240L; + + public SampleResult sample(Entry e) { + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.setSamplerData(myStringProperty); + res.sampleStart(); + // Do something ... + res.setResponseData(myStringProperty.toUpperCase(Locale.ENGLISH), null); + res.setDataType(SampleResult.TEXT); + res.sampleEnd(); + res.setSuccessful(true); + return res; + } + private String myStringProperty; + + // A String property: + public void setMyStringProperty(String s) { + myStringProperty=s; + } + + public String getMyStringProperty() { + return myStringProperty; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example2/Example2.java b/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example2/Example2.java new file mode 100644 index 0000000..ad393f6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example2/Example2.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.examples.testbeans.example2; + +import java.util.Locale; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; + +/** + * This TestBean is just an example about how to write testbeans. The intent is + * to demonstrate usage of the TestBean features to podential TestBean + * developers. Note that only the class's introspector view matters: the methods + * do nothing -- nothing useful, in any case. + */ +public class Example2 extends AbstractSampler implements TestBean { + + private static final long serialVersionUID = 240L; + + public SampleResult sample(Entry e) { + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.setSamplerData(myStringProperty); + res.sampleStart(); + // Do something ... + res.setResponseData(myStringProperty.toLowerCase(Locale.ENGLISH), null); + res.setDataType(SampleResult.TEXT); + res.sampleEnd(); + res.setSuccessful(true); + return res; + } + + private String myStringProperty; + + // A TestBean is a Java Bean. Just define some properties and they will + // automagically show up in the GUI. + // A String property: + public void setMyStringProperty(String s) { + myStringProperty=s; + } + + public String getMyStringProperty() { + return myStringProperty; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example2/Example2BeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example2/Example2BeanInfo.java new file mode 100644 index 0000000..d8233db --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example2/Example2BeanInfo.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.jmeter.examples.testbeans.example2; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +public class Example2BeanInfo extends BeanInfoSupport { + public Example2BeanInfo() { + super(Example2.class); + // ... + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example3/Example3.java b/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example3/Example3.java new file mode 100644 index 0000000..6217bb0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example3/Example3.java @@ -0,0 +1,156 @@ +/* + * 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.examples.testbeans.example3; + +import java.io.File; +import java.lang.reflect.Field; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; + +/** + * This TestBean is just an example of the use of different TestBean types. + */ +public class Example3 extends AbstractSampler implements TestBean { + + private static final long serialVersionUID = 240L; + + private boolean mybool; + private Boolean myBoolean1, myBoolean2; + private int myInt; + private Integer myInteger1, myInteger2; + private long mylong; + private Long myLong1, myLong2; + private String myString1, myString2; + private File myFile1; + private String myFile2; + + public SampleResult sample(Entry ignored) { + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.sampleStart(); + StringBuilder bld = new StringBuilder(); + for (Field field : this.getClass().getDeclaredFields()) { + try { + String name = field.getName(); + if (name.startsWith("my")) { + Object value = field.get(this); + bld.append(name).append('='); + bld.append(value); + bld.append(" ("); + bld.append(field.getType().getCanonicalName()); + bld.append(")\n"); + } + } catch (IllegalAccessException e) { + bld.append(e.toString()); + } + } + res.setResponseData(bld.toString(), null); + res.setDataType(SampleResult.TEXT); + res.sampleEnd(); + res.setSuccessful(true); + return res; + } + + public boolean isMybool() { + return mybool; + } + public void setMybool(boolean mybool) { + this.mybool = mybool; + } + public Boolean getMyBoolean1() { + return myBoolean1; + } + public void setMyBoolean1(Boolean myBoolean1) { + this.myBoolean1 = myBoolean1; + } + public Boolean getMyBoolean2() { + return myBoolean2; + } + public void setMyBoolean2(Boolean myBoolean2) { + this.myBoolean2 = myBoolean2; + } + public int getMyInt() { + return myInt; + } + public void setMyInt(int myInt) { + this.myInt = myInt; + } + public Integer getMyInteger1() { + return myInteger1; + } + public void setMyInteger1(Integer myInteger1) { + this.myInteger1 = myInteger1; + } + public Integer getMyInteger2() { + return myInteger2; + } + public void setMyInteger2(Integer myInteger2) { + this.myInteger2 = myInteger2; + } + public long getMylong() { + return mylong; + } + public void setMylong(long mylong) { + this.mylong = mylong; + } + public Long getMyLong1() { + return myLong1; + } + public void setMyLong1(Long myLong1) { + this.myLong1 = myLong1; + } + public Long getMyLong2() { + return myLong2; + } + public void setMyLong2(Long myLong2) { + this.myLong2 = myLong2; + } + public String getMyString1() { + return myString1; + } + public void setMyString1(String myString1) { + this.myString1 = myString1; + } + public String getMyString2() { + return myString2; + } + public void setMyString2(String myString2) { + this.myString2 = myString2; + } + + public File getMyFile1() { + return myFile1; + } + + public void setMyFile1(File myFile) { + this.myFile1 = myFile; + } + + public String getMyFile2() { + return myFile2; + } + + public void setMyFile2(String myFile) { + this.myFile2 = myFile; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example3/Example3BeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example3/Example3BeanInfo.java new file mode 100644 index 0000000..abbc759 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/examples/testbeans/example3/Example3BeanInfo.java @@ -0,0 +1,55 @@ +/* + * 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.examples.testbeans.example3; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.TypeEditor; + +public class Example3BeanInfo extends BeanInfoSupport { + + private PropertyDescriptor getprop(String name) { + PropertyDescriptor p = property(name); + return p; + } + + private PropertyDescriptor getprop(String name, Object deflt) { + PropertyDescriptor p = property(name); + p.setValue(DEFAULT, deflt); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + return p; + } + + public Example3BeanInfo() { + super(Example3.class); + getprop("mybool"); + getprop("myBoolean1"); + getprop("myBoolean2", "True"); + getprop("myInt", "77"); + getprop("myInteger1"); + getprop("myInteger2", Integer.valueOf(123)); + getprop("mylong", "99"); + getprop("myLong1"); + getprop("myLong2", Long.valueOf(456)); + getprop("myString1"); + getprop("myString2","abcd"); + property("myFile2", TypeEditor.FileEditor); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/exceptions/IllegalUserActionException.java b/ApacheJmeter/src/org/apache/jmeter/exceptions/IllegalUserActionException.java new file mode 100644 index 0000000..eb69977 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/exceptions/IllegalUserActionException.java @@ -0,0 +1,42 @@ +/* + * 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.exceptions; + +/** + */ +public class IllegalUserActionException extends Exception { + private static final long serialVersionUID = 240L; + + /** + * @deprecated - use IllegalUserActionException(String) + */ + @Deprecated // Needed for serialisation testing + public IllegalUserActionException() { + super(); + } + + public IllegalUserActionException(String name) { + super(name); + } + + public IllegalUserActionException(String name, Throwable t) { + super(name, t); + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/BSFPostProcessor.java b/ApacheJmeter/src/org/apache/jmeter/extractor/BSFPostProcessor.java new file mode 100644 index 0000000..08c20ad --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/BSFPostProcessor.java @@ -0,0 +1,48 @@ +/* + * 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.extractor; + +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class BSFPostProcessor extends BSFTestElement implements Cloneable, PostProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + public void process(){ + BSFManager mgr =null; + try { + mgr = getManager(); + processFileOrScript(mgr); + } catch (BSFException e) { + log.warn("Problem in BSF script "+e); + } finally { + if (mgr != null) { + mgr.terminate(); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/BSFPostProcessorBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/extractor/BSFPostProcessorBeanInfo.java new file mode 100644 index 0000000..dbd3d7e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/BSFPostProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.extractor; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +public class BSFPostProcessorBeanInfo extends BSFBeanInfoSupport { + + public BSFPostProcessorBeanInfo() { + super(BSFPostProcessor.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/BeanShellPostProcessor.java b/ApacheJmeter/src/org/apache/jmeter/extractor/BeanShellPostProcessor.java new file mode 100644 index 0000000..1afb737 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/BeanShellPostProcessor.java @@ -0,0 +1,68 @@ +/* + * 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.extractor; + +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +public class BeanShellPostProcessor extends BeanShellTestElement + implements Cloneable, PostProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + // can be specified in jmeter.properties + private static final String INIT_FILE = "beanshell.postprocessor.init"; //$NON-NLS-1$ + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + public void process() { + JMeterContext jmctx = JMeterContextService.getContext(); + + SampleResult prev = jmctx.getPreviousResult(); + if (prev == null) { + return; // TODO - should we skip processing here? + } + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + log.error("BeanShell not found"); + return; + } + + try { + // Add variables for access to context and variables + bshInterpreter.set("data", prev.getResponseData());//$NON-NLS-1$ + processFileOrScript(bshInterpreter); + } catch (JMeterException e) { + log.warn("Problem in BeanShell script "+e); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/BeanShellPostProcessorBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/extractor/BeanShellPostProcessorBeanInfo.java new file mode 100644 index 0000000..6af801b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/BeanShellPostProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.extractor; + +import org.apache.jmeter.util.BeanShellBeanInfoSupport; + +public class BeanShellPostProcessorBeanInfo extends BeanShellBeanInfoSupport { + + public BeanShellPostProcessorBeanInfo() { + super(BeanShellPostProcessor.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/DebugPostProcessor.java b/ApacheJmeter/src/org/apache/jmeter/extractor/DebugPostProcessor.java new file mode 100644 index 0000000..d72003f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/DebugPostProcessor.java @@ -0,0 +1,154 @@ +/* + * 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.extractor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; +import java.util.HashMap; +import java.util.Set; + +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Debugging Post-Processor: creates a subSample containing the variables defined in the previous sampler. + */ +public class DebugPostProcessor extends AbstractTestElement implements PostProcessor, TestBean { + + private static final long serialVersionUID = 260L; + + private boolean displaySamplerProperties; + + private boolean displayJMeterVariables; + + private boolean displayJMeterProperties; + + private boolean displaySystemProperties; + + public void process(){ + StringBuilder sb = new StringBuilder(100); + StringBuilder rd = new StringBuilder(20); // for request Data + SampleResult sr = new SampleResult(); + sr.setSampleLabel(getName()); + sr.sampleStart(); + JMeterContext threadContext = getThreadContext(); + if (isDisplaySamplerProperties()){ + rd.append("SamplerProperties\n"); + sb.append("SamplerProperties:\n"); + formatPropertyIterator(sb, threadContext.getCurrentSampler().propertyIterator()); + sb.append("\n"); + } + + if (isDisplayJMeterVariables()){ + rd.append("JMeterVariables\n"); + sb.append("JMeterVariables:\n"); + formatSet(sb, threadContext.getVariables().entrySet()); + sb.append("\n"); + } + + if (isDisplayJMeterProperties()){ + rd.append("JMeterProperties\n"); + sb.append("JMeterProperties:\n"); + formatSet(sb, JMeterUtils.getJMeterProperties().entrySet()); + sb.append("\n"); + } + + if (isDisplaySystemProperties()){ + rd.append("SystemProperties\n"); + sb.append("SystemProperties:\n"); + formatSet(sb, System.getProperties().entrySet()); + sb.append("\n"); + } + + sr.setResponseData(sb.toString(), null); + sr.setDataType(SampleResult.TEXT); + sr.setSamplerData(rd.toString()); + sr.setResponseOK(); + sr.sampleEnd(); + threadContext.getPreviousResult().addSubResult(sr); + } + + private void formatPropertyIterator(StringBuilder sb, PropertyIterator iter) { + Map map = new HashMap(); + while (iter.hasNext()) { + JMeterProperty item = iter.next(); + map.put(item.getName(), item.getStringValue()); + } + formatSet(sb, map.entrySet()); + } + + private void formatSet(StringBuilder sb, @SuppressWarnings("rawtypes") Set s) { + @SuppressWarnings("unchecked") + ArrayList> al = new ArrayList>(s); + Collections.sort(al, new Comparator>(){ + public int compare(Map.Entry o1, Map.Entry o2) { + String m1,m2; + m1=(String)o1.getKey(); + m2=(String)o2.getKey(); + return m1.compareTo(m2); + } + }); + for(Map.Entry me : al){ + sb.append(me.getKey()); + sb.append("="); + sb.append(me.getValue()); + sb.append("\n"); + } + } + + public boolean isDisplayJMeterVariables() { + return displayJMeterVariables; + } + + public void setDisplayJMeterVariables(boolean displayJMeterVariables) { + this.displayJMeterVariables = displayJMeterVariables; + } + + public boolean isDisplayJMeterProperties() { + return displayJMeterProperties; + } + + public void setDisplayJMeterProperties(boolean displayJMeterPropterties) { + this.displayJMeterProperties = displayJMeterPropterties; + } + + public boolean isDisplaySamplerProperties() { + return displaySamplerProperties; + } + + public void setDisplaySamplerProperties(boolean displaySamplerProperties) { + this.displaySamplerProperties = displaySamplerProperties; + } + + public boolean isDisplaySystemProperties() { + return displaySystemProperties; + } + + public void setDisplaySystemProperties(boolean displaySystemProperties) { + this.displaySystemProperties = displaySystemProperties; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/DebugPostProcessorBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/extractor/DebugPostProcessorBeanInfo.java new file mode 100644 index 0000000..651c437 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/DebugPostProcessorBeanInfo.java @@ -0,0 +1,57 @@ +/* + * 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.extractor; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +public class DebugPostProcessorBeanInfo extends BeanInfoSupport { + + public DebugPostProcessorBeanInfo() { + super(DebugPostProcessor.class); + + PropertyDescriptor p; + + p = property("displaySamplerProperties"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + + p = property("displayJMeterVariables"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + + p = property("displayJMeterProperties"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + + p = property("displaySystemProperties"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/JSR223PostProcessor.java b/ApacheJmeter/src/org/apache/jmeter/extractor/JSR223PostProcessor.java new file mode 100644 index 0000000..c5051e1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/JSR223PostProcessor.java @@ -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. + * + */ + +package org.apache.jmeter.extractor; + +import java.io.IOException; + +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223PostProcessor extends JSR223TestElement implements Cloneable, PostProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + public void process() { + + try { + ScriptEngineManager sem = getManager(); + if(sem == null) { return; } + processFileOrScript(sem); + } catch (ScriptException e) { + log.warn("Problem in JSR223 script "+e); + } catch (IOException e) { + log.warn("Problem in JSR223 script "+e); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/JSR223PostProcessorBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/extractor/JSR223PostProcessorBeanInfo.java new file mode 100644 index 0000000..4de3ecd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/JSR223PostProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.extractor; + +import org.apache.jmeter.util.JSR223BeanInfoSupport; + +public class JSR223PostProcessorBeanInfo extends JSR223BeanInfoSupport { + + public JSR223PostProcessorBeanInfo() { + super(JSR223PostProcessor.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/RegexExtractor.java b/ApacheJmeter/src/org/apache/jmeter/extractor/RegexExtractor.java new file mode 100644 index 0000000..ddb8cb2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/RegexExtractor.java @@ -0,0 +1,468 @@ +/* + * 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.extractor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedTestElement; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcher; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +// @see org.apache.jmeter.extractor.TestRegexExtractor for unit tests + +public class RegexExtractor extends AbstractScopedTestElement implements PostProcessor, Serializable { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // What to match against. N.B. do not change the string value or test plans will break! + private static final String MATCH_AGAINST = "RegexExtractor.useHeaders"; // $NON-NLS-1$ + /* + * Permissible values: + * true - match against headers + * false or absent - match against body (this was the original default) + * URL - match against URL + * These are passed to the setUseField() method + * + * Do not change these values! + */ + public static final String USE_HDRS = "true"; // $NON-NLS-1$ + public static final String USE_BODY = "false"; // $NON-NLS-1$ + public static final String USE_BODY_UNESCAPED = "unescaped"; // $NON-NLS-1$ + public static final String USE_URL = "URL"; // $NON-NLS-1$ + public static final String USE_CODE = "code"; // $NON-NLS-1$ + public static final String USE_MESSAGE = "message"; // $NON-NLS-1$ + + + private static final String REGEX = "RegexExtractor.regex"; // $NON-NLS-1$ + + private static final String REFNAME = "RegexExtractor.refname"; // $NON-NLS-1$ + + private static final String MATCH_NUMBER = "RegexExtractor.match_number"; // $NON-NLS-1$ + + private static final String DEFAULT = "RegexExtractor.default"; // $NON-NLS-1$ + + private static final String TEMPLATE = "RegexExtractor.template"; // $NON-NLS-1$ + + private static final String REF_MATCH_NR = "_matchNr"; // $NON-NLS-1$ + + private static final String UNDERSCORE = "_"; // $NON-NLS-1$ + + private transient List template; + + /** + * Parses the response data using regular expressions and saving the results + * into variables for use later in the test. + * + * @see org.apache.jmeter.processor.PostProcessor#process() + */ + public void process() { + initTemplate(); + JMeterContext context = getThreadContext(); + SampleResult previousResult = context.getPreviousResult(); + if (previousResult == null) { + return; + } + log.debug("RegexExtractor processing result"); + + // Fetch some variables + JMeterVariables vars = context.getVariables(); + String refName = getRefName(); + int matchNumber = getMatchNumber(); + + final String defaultValue = getDefaultValue(); + if (defaultValue.length() > 0){// Only replace default if it is provided + vars.put(refName, defaultValue); + } + + + String regex = getRegex(); + try { + List matches = processMatches(regex, previousResult, matchNumber, vars); + int prevCount = 0; + String prevString = vars.get(refName + REF_MATCH_NR); + if (prevString != null) { + vars.remove(refName + REF_MATCH_NR);// ensure old value is not left defined + try { + prevCount = Integer.parseInt(prevString); + } catch (NumberFormatException e1) { + log.warn("Could not parse "+prevString+" "+e1); + } + } + int matchCount=0;// Number of refName_n variable sets to keep + try { + MatchResult match; + if (matchNumber >= 0) {// Original match behaviour + match = getCorrectMatch(matches, matchNumber); + if (match != null) { + vars.put(refName, generateResult(match)); + saveGroups(vars, refName, match); + } else { + // refname has already been set to the default (if present) + removeGroups(vars, refName); + } + } else // < 0 means we save all the matches + { + removeGroups(vars, refName); // remove any single matches + matchCount = matches.size(); + vars.put(refName + REF_MATCH_NR, Integer.toString(matchCount));// Save the count + for (int i = 1; i <= matchCount; i++) { + match = getCorrectMatch(matches, i); + if (match != null) { + final String refName_n = new StringBuilder(refName).append(UNDERSCORE).append(i).toString(); + vars.put(refName_n, generateResult(match)); + saveGroups(vars, refName_n, match); + } + } + } + // Remove any left-over variables + for (int i = matchCount + 1; i <= prevCount; i++) { + final String refName_n = new StringBuilder(refName).append(UNDERSCORE).append(i).toString(); + vars.remove(refName_n); + removeGroups(vars, refName_n); + } + } catch (RuntimeException e) { + log.warn("Error while generating result"); + } + } catch (MalformedCachePatternException e) { + log.warn("Error in pattern: " + regex); + } + } + + private String getInputString(SampleResult result) { + String inputString = useUrl() ? result.getUrlAsString() // Bug 39707 + : useHeaders() ? result.getResponseHeaders() + : useCode() ? result.getResponseCode() // Bug 43451 + : useMessage() ? result.getResponseMessage() // Bug 43451 + : useUnescapedBody() ? StringEscapeUtils.unescapeHtml(result.getResponseDataAsString()) + : result.getResponseDataAsString() // Bug 36898 + ; + if (log.isDebugEnabled()) { + log.debug("Input = " + inputString); + } + return inputString; + } + + private List processMatches(String regex, SampleResult result, int matchNumber, JMeterVariables vars) { + if (log.isDebugEnabled()) { + log.debug("Regex = " + regex); + } + + Perl5Matcher matcher = JMeterUtils.getMatcher(); + Pattern pattern = JMeterUtils.getPatternCache().getPattern(regex, Perl5Compiler.READ_ONLY_MASK); + List matches = new ArrayList(); + int found = 0; + + if (isScopeVariable()){ + String inputString=vars.get(getVariableName()); + matchStrings(matchNumber, matcher, pattern, matches, found, + inputString); + } else { + List sampleList = getSampleList(result); + for (SampleResult sr : sampleList) { + String inputString = getInputString(sr); + found = matchStrings(matchNumber, matcher, pattern, matches, found, + inputString); + if (matchNumber > 0 && found == matchNumber){// no need to process further + break; + } + } + } + return matches; + } + + private int matchStrings(int matchNumber, Perl5Matcher matcher, + Pattern pattern, List matches, int found, + String inputString) { + PatternMatcherInput input = new PatternMatcherInput(inputString); + while (matchNumber <=0 || found != matchNumber) { + if (matcher.contains(input, pattern)) { + log.debug("RegexExtractor: Match found!"); + matches.add(matcher.getMatch()); + found++; + } else { + break; + } + } + return found; + } + + /** + * Creates the variables:
+ * basename_gn, where n=0...# of groups
+ * basename_g = number of groups (apart from g0) + */ + private void saveGroups(JMeterVariables vars, String basename, MatchResult match) { + StringBuilder buf = new StringBuilder(); + buf.append(basename); + buf.append("_g"); // $NON-NLS-1$ + int pfxlen=buf.length(); + String prevString=vars.get(buf.toString()); + int previous=0; + if (prevString!=null){ + try { + previous=Integer.parseInt(prevString); + } catch (NumberFormatException e) { + log.warn("Could not parse "+prevString+" "+e); + } + } + //Note: match.groups() includes group 0 + final int groups = match.groups(); + for (int x = 0; x < groups; x++) { + buf.append(x); + vars.put(buf.toString(), match.group(x)); + buf.setLength(pfxlen); + } + vars.put(buf.toString(), Integer.toString(groups-1)); + for (int i = groups; i <= previous; i++){ + buf.append(i); + vars.remove(buf.toString());// remove the remaining _gn vars + buf.setLength(pfxlen); + } + } + + /** + * Removes the variables:
+ * basename_gn, where n=0...# of groups
+ * basename_g = number of groups (apart from g0) + */ + private void removeGroups(JMeterVariables vars, String basename) { + StringBuilder buf = new StringBuilder(); + buf.append(basename); + buf.append("_g"); // $NON-NLS-1$ + int pfxlen=buf.length(); + // How many groups are there? + int groups; + try { + groups=Integer.parseInt(vars.get(buf.toString())); + } catch (NumberFormatException e) { + groups=0; + } + vars.remove(buf.toString());// Remove the group count + for (int i = 0; i <= groups; i++) { + buf.append(i); + vars.remove(buf.toString());// remove the g0,g1...gn vars + buf.setLength(pfxlen); + } + } + + private String generateResult(MatchResult match) { + StringBuilder result = new StringBuilder(); + for (Object obj : template) { + if (log.isDebugEnabled()) { + log.debug("RegexExtractor: Template piece " + obj + " (" + obj.getClass().getSimpleName() + ")"); + } + if (obj instanceof Integer) { + result.append(match.group(((Integer) obj).intValue())); + } else { + result.append(obj); + } + } + if (log.isDebugEnabled()) { + log.debug("Regex Extractor result = " + result.toString()); + } + return result.toString(); + } + + private void initTemplate() { + if (template != null) { + return; + } + // Contains Strings and Integers + List combined = new ArrayList(); + String rawTemplate = getTemplate(); + PatternMatcher matcher = JMeterUtils.getMatcher(); + Pattern templatePattern = JMeterUtils.getPatternCache().getPattern("\\$(\\d+)\\$" // $NON-NLS-1$ + , Perl5Compiler.READ_ONLY_MASK + & Perl5Compiler.SINGLELINE_MASK); + if (log.isDebugEnabled()) { + log.debug("Pattern = " + templatePattern.getPattern()); + log.debug("template = " + rawTemplate); + } + int beginOffset = 0; + MatchResult currentResult; + PatternMatcherInput pinput = new PatternMatcherInput(rawTemplate); + while(matcher.contains(pinput, templatePattern)) { + currentResult = matcher.getMatch(); + final int beginMatch = currentResult.beginOffset(0); + if (beginMatch > beginOffset) { // string is not empty + combined.add(rawTemplate.substring(beginOffset, beginMatch)); + } + combined.add(Integer.valueOf(currentResult.group(1)));// add match as Integer + beginOffset = currentResult.endOffset(0); + } + + if (beginOffset < rawTemplate.length()) { // trailing string is not empty + combined.add(rawTemplate.substring(beginOffset, rawTemplate.length())); + } + if (log.isDebugEnabled()){ + log.debug("Template item count: "+combined.size()); + for(Object o : combined){ + log.debug(o.getClass().getSimpleName()+" '"+o.toString()+"'"); + } + } + template = combined; + } + + /** + * Grab the appropriate result from the list. + * + * @param matches + * list of matches + * @param entry + * the entry number in the list + * @return MatchResult + */ + private MatchResult getCorrectMatch(List matches, int entry) { + int matchSize = matches.size(); + + if (matchSize <= 0 || entry > matchSize){ + return null; + } + + if (entry == 0) // Random match + { + return matches.get(JMeterUtils.getRandomInt(matchSize)); + } + + return matches.get(entry - 1); + } + + public void setRegex(String regex) { + setProperty(REGEX, regex); + } + + public String getRegex() { + return getPropertyAsString(REGEX); + } + + public void setRefName(String refName) { + setProperty(REFNAME, refName); + } + + public String getRefName() { + return getPropertyAsString(REFNAME); + } + + /** + * Set which Match to use. This can be any positive number, indicating the + * exact match to use, or 0, which is interpreted as meaning random. + * + * @param matchNumber + */ + public void setMatchNumber(int matchNumber) { + setProperty(new IntegerProperty(MATCH_NUMBER, matchNumber)); + } + + public void setMatchNumber(String matchNumber) { + setProperty(MATCH_NUMBER, matchNumber); + } + + public int getMatchNumber() { + return getPropertyAsInt(MATCH_NUMBER); + } + + public String getMatchNumberAsString() { + return getPropertyAsString(MATCH_NUMBER); + } + + /** + * Sets the value of the variable if no matches are found + * + * @param defaultValue + */ + public void setDefaultValue(String defaultValue) { + setProperty(DEFAULT, defaultValue); + } + + public String getDefaultValue() { + return getPropertyAsString(DEFAULT); + } + + public void setTemplate(String template) { + setProperty(TEMPLATE, template); + } + + public String getTemplate() { + return getPropertyAsString(TEMPLATE); + } + + public boolean useHeaders() { + return USE_HDRS.equalsIgnoreCase( getPropertyAsString(MATCH_AGAINST)); + } + + // Allow for property not yet being set (probably only applies to Test cases) + public boolean useBody() { + String prop = getPropertyAsString(MATCH_AGAINST); + return prop.length()==0 || USE_BODY.equalsIgnoreCase(prop);// $NON-NLS-1$ + } + + public boolean useUnescapedBody() { + String prop = getPropertyAsString(MATCH_AGAINST); + return USE_BODY_UNESCAPED.equalsIgnoreCase(prop);// $NON-NLS-1$ + } + + public boolean useUrl() { + String prop = getPropertyAsString(MATCH_AGAINST); + return USE_URL.equalsIgnoreCase(prop); + } + + public boolean useCode() { + String prop = getPropertyAsString(MATCH_AGAINST); + return USE_CODE.equalsIgnoreCase(prop); + } + + public boolean useMessage() { + String prop = getPropertyAsString(MATCH_AGAINST); + return USE_MESSAGE.equalsIgnoreCase(prop); + } + + public void setUseField(String actionCommand) { + setProperty(MATCH_AGAINST,actionCommand); + } + + /** + * {@inheritDoc}} + */ + @Override + public List getSearchableTokens() throws Exception { + List result = super.getSearchableTokens(); + result.add(getRefName()); + result.add(getDefaultValue()); + result.add(getRegex()); + return result; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/XPathExtractor.java b/ApacheJmeter/src/org/apache/jmeter/extractor/XPathExtractor.java new file mode 100644 index 0000000..909e09f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/XPathExtractor.java @@ -0,0 +1,347 @@ +/* + * 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.extractor; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedTestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.TidyException; +import org.apache.jmeter.util.XPathUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +//@see org.apache.jmeter.extractor.TestXPathExtractor for unit tests + +/** + * Extracts text from (X)HTML response using XPath query language + * Example XPath queries: + *
+ *
/html/head/title
+ *
extracts Title from HTML response
+ *
//form[@name='countryForm']//select[@name='country']/option[text()='Czech Republic'])/@value + *
extracts value attribute of option element that match text 'Czech Republic' + * inside of select element with name attribute 'country' inside of + * form with name attribute 'countryForm'
+ *
//head
+ *
extracts the XML fragment for head node.
+ *
//head/text()
+ *
extracts the text content for head node.
+ *
+ */ + /* This file is inspired by RegexExtractor. + * author Henryk Paluch + * of Gitus a.s. + * + * See Bugzilla: 37183 + */ +public class XPathExtractor extends AbstractScopedTestElement implements + PostProcessor, Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final String MATCH_NR = "matchNr"; // $NON-NLS-1$ + + //+ JMX file attributes + private static final String XPATH_QUERY = "XPathExtractor.xpathQuery"; // $NON-NLS-1$ + private static final String REFNAME = "XPathExtractor.refname"; // $NON-NLS-1$ + private static final String DEFAULT = "XPathExtractor.default"; // $NON-NLS-1$ + private static final String TOLERANT = "XPathExtractor.tolerant"; // $NON-NLS-1$ + private static final String NAMESPACE = "XPathExtractor.namespace"; // $NON-NLS-1$ + private static final String QUIET = "XPathExtractor.quiet"; // $NON-NLS-1$ + private static final String REPORT_ERRORS = "XPathExtractor.report_errors"; // $NON-NLS-1$ + private static final String SHOW_WARNINGS = "XPathExtractor.show_warnings"; // $NON-NLS-1$ + private static final String DOWNLOAD_DTDS = "XPathExtractor.download_dtds"; // $NON-NLS-1$ + private static final String WHITESPACE = "XPathExtractor.whitespace"; // $NON-NLS-1$ + private static final String VALIDATE = "XPathExtractor.validate"; // $NON-NLS-1$ + private static final String FRAGMENT = "XPathExtractor.fragment"; // $NON-NLS-1$ + //- JMX file attributes + + + private String concat(String s1,String s2){ + return new StringBuilder(s1).append("_").append(s2).toString(); // $NON-NLS-1$ + } + + private String concat(String s1, int i){ + return new StringBuilder(s1).append("_").append(i).toString(); // $NON-NLS-1$ + } + + /** + * Do the job - extract value from (X)HTML response using XPath Query. + * Return value as variable defined by REFNAME. Returns DEFAULT value + * if not found. + */ + public void process() { + JMeterContext context = getThreadContext(); + final SampleResult previousResult = context.getPreviousResult(); + if (previousResult == null){ + return; + } + JMeterVariables vars = context.getVariables(); + String refName = getRefName(); + vars.put(refName, getDefaultValue()); + final String matchNR = concat(refName,MATCH_NR); + int prevCount=0; // number of previous matches + try { + prevCount=Integer.parseInt(vars.get(matchNR)); + } catch (NumberFormatException e) { + // ignored + } + vars.put(matchNR, "0"); // In case parse fails // $NON-NLS-1$ + vars.remove(concat(refName,"1")); // In case parse fails // $NON-NLS-1$ + + List matches = new ArrayList(); + try{ + if (isScopeVariable()){ + String inputString=vars.get(getVariableName()); + Document d = parseResponse(inputString); + getValuesForXPath(d,getXPathQuery(),matches); + } else { + List samples = getSampleList(previousResult); + for (SampleResult res : samples) { + Document d = parseResponse(res.getResponseDataAsString()); + getValuesForXPath(d,getXPathQuery(),matches); + } + } + final int matchCount = matches.size(); + vars.put(matchNR, String.valueOf(matchCount)); + if (matchCount > 0){ + String value = matches.get(0); + if (value != null) { + vars.put(refName, value); + } + for(int i=0; i < matchCount; i++){ + value = matches.get(i); + if (value != null) { + vars.put(concat(refName,i+1),matches.get(i)); + } + } + } + vars.remove(concat(refName,matchCount+1)); // Just in case + // Clear any other remaining variables + for(int i=matchCount+2; i <= prevCount; i++) { + vars.remove(concat(refName,i)); + } + }catch(IOException e){// e.g. DTD not reachable + final String errorMessage = "IOException on ("+getXPathQuery()+")"; + log.error(errorMessage,e); + AssertionResult ass = new AssertionResult(getName()); + ass.setError(true); + ass.setFailureMessage(new StringBuilder("IOException: ").append(e.getLocalizedMessage()).toString()); + previousResult.addAssertionResult(ass); + previousResult.setSuccessful(false); + } catch (ParserConfigurationException e) {// Should not happen + final String errrorMessage = "ParserConfigurationException while processing ("+getXPathQuery()+")"; + log.error(errrorMessage,e); + throw new JMeterError(errrorMessage,e); + } catch (SAXException e) {// Can happen for bad input document + log.warn("SAXException while processing ("+getXPathQuery()+") "+e.getLocalizedMessage()); + addAssertionFailure(previousResult, e, false); // Should this also fail the sample? + } catch (TransformerException e) {// Can happen for incorrect XPath expression + log.warn("TransformerException while processing ("+getXPathQuery()+") "+e.getLocalizedMessage()); + addAssertionFailure(previousResult, e, false); + } catch (TidyException e) { + // Will already have been logged by XPathUtil + addAssertionFailure(previousResult, e, true); // fail the sample + } + } + + private void addAssertionFailure(final SampleResult previousResult, + final Throwable thrown, final boolean setFailed) { + AssertionResult ass = new AssertionResult(thrown.getClass().getSimpleName()); // $NON-NLS-1$ + ass.setFailure(true); + ass.setFailureMessage(thrown.getLocalizedMessage()+"\nSee log file for further details."); + previousResult.addAssertionResult(ass); + if (setFailed){ + previousResult.setSuccessful(false); + } + } + + /*============= object properties ================*/ + public void setXPathQuery(String val){ + setProperty(XPATH_QUERY,val); + } + + public String getXPathQuery(){ + return getPropertyAsString(XPATH_QUERY); + } + + public void setRefName(String refName) { + setProperty(REFNAME, refName); + } + + public String getRefName() { + return getPropertyAsString(REFNAME); + } + + public void setDefaultValue(String val) { + setProperty(DEFAULT, val); + } + + public String getDefaultValue() { + return getPropertyAsString(DEFAULT); + } + + public void setTolerant(boolean val) { + setProperty(new BooleanProperty(TOLERANT, val)); + } + + public boolean isTolerant() { + return getPropertyAsBoolean(TOLERANT); + } + + public void setNameSpace(boolean val) { + setProperty(new BooleanProperty(NAMESPACE, val)); + } + + public boolean useNameSpace() { + return getPropertyAsBoolean(NAMESPACE); + } + + public void setReportErrors(boolean val) { + setProperty(REPORT_ERRORS, val, false); + } + + public boolean reportErrors() { + return getPropertyAsBoolean(REPORT_ERRORS, false); + } + + public void setShowWarnings(boolean val) { + setProperty(SHOW_WARNINGS, val, false); + } + + public boolean showWarnings() { + return getPropertyAsBoolean(SHOW_WARNINGS, false); + } + + public void setQuiet(boolean val) { + setProperty(QUIET, val, true); + } + + public boolean isQuiet() { + return getPropertyAsBoolean(QUIET, true); + } + + /** + * Should we return fragment as text, rather than text of fragment? + * @return true if we should return fragment rather than text + */ + public boolean getFragment() { + return getPropertyAsBoolean(FRAGMENT, false); + } + + /** + * Should we return fragment as text, rather than text of fragment? + * @param selected true to return fragment. + */ + public void setFragment(boolean selected) { + setProperty(FRAGMENT, selected, false); + } + + /*================= internal business =================*/ + /** + * Converts (X)HTML response to DOM object Tree. + * This version cares of charset of response. + * @param unicodeData + * @return + * + */ + private Document parseResponse(String unicodeData) + throws UnsupportedEncodingException, IOException, ParserConfigurationException,SAXException,TidyException + { + //TODO: validate contentType for reasonable types? + + // NOTE: responseData encoding is server specific + // Therefore we do byte -> unicode -> byte conversion + // to ensure UTF-8 encoding as required by XPathUtil + // convert unicode String -> UTF-8 bytes + byte[] utf8data = unicodeData.getBytes("UTF-8"); // $NON-NLS-1$ + ByteArrayInputStream in = new ByteArrayInputStream(utf8data); + boolean isXML = JOrphanUtils.isXML(utf8data); + // this method assumes UTF-8 input data + return XPathUtil.makeDocument(in,false,false,useNameSpace(),isTolerant(),isQuiet(),showWarnings(),reportErrors() + ,isXML, isDownloadDTDs()); + } + + /** + * Extract value from Document d by XPath query. + * @param d the document + * @param query the query to execute + * @param matchStrings list of matched strings (may include nulls) + * + * @throws TransformerException + */ + private void getValuesForXPath(Document d,String query, List matchStrings) + throws TransformerException { + XPathUtil.putValuesForXPathInList(d, query, matchStrings, getFragment()); + } + + public void setWhitespace(boolean selected) { + setProperty(WHITESPACE, selected, false); + } + + public boolean isWhitespace() { + return getPropertyAsBoolean(WHITESPACE, false); + } + + public void setValidating(boolean selected) { + setProperty(VALIDATE, selected); + } + + public boolean isValidating() { + return getPropertyAsBoolean(VALIDATE, false); + } + + public void setDownloadDTDs(boolean selected) { + setProperty(DOWNLOAD_DTDS, selected, false); + } + + public boolean isDownloadDTDs() { + return getPropertyAsBoolean(DOWNLOAD_DTDS, false); + } + + /** + * {@inheritDoc}} + */ + @Override + public List getSearchableTokens() throws Exception { + List result = super.getSearchableTokens(); + result.add(getRefName()); + result.add(getDefaultValue()); + result.add(getXPathQuery()); + return result; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/gui/RegexExtractorGui.java b/ApacheJmeter/src/org/apache/jmeter/extractor/gui/RegexExtractorGui.java new file mode 100644 index 0000000..a6b5718 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/gui/RegexExtractorGui.java @@ -0,0 +1,244 @@ +/* + * 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.extractor.gui; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +import org.apache.jmeter.extractor.RegexExtractor; +import org.apache.jmeter.processor.gui.AbstractPostProcessorGui; +import org.apache.jmeter.testelement.AbstractScopedTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * Regular Expression Extractor Post-Processor GUI + */ +public class RegexExtractorGui extends AbstractPostProcessorGui { + private static final long serialVersionUID = 240L; + + private JLabeledTextField regexField; + + private JLabeledTextField templateField; + + private JLabeledTextField defaultField; + + private JLabeledTextField matchNumberField; + + private JLabeledTextField refNameField; + + private JRadioButton useBody; + + private JRadioButton useUnescapedBody; + + private JRadioButton useHeaders; + + private JRadioButton useURL; + + private JRadioButton useCode; + + private JRadioButton useMessage; + + private ButtonGroup group; + + public RegexExtractorGui() { + super(); + init(); + } + + public String getLabelResource() { + return "regex_extractor_title"; //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof RegexExtractor){ + RegexExtractor re = (RegexExtractor) el; + showScopeSettings(re, true); + useHeaders.setSelected(re.useHeaders()); + useBody.setSelected(re.useBody()); + useUnescapedBody.setSelected(re.useUnescapedBody()); + useURL.setSelected(re.useUrl()); + useCode.setSelected(re.useCode()); + useMessage.setSelected(re.useMessage()); + regexField.setText(re.getRegex()); + templateField.setText(re.getTemplate()); + defaultField.setText(re.getDefaultValue()); + matchNumberField.setText(re.getMatchNumberAsString()); + refNameField.setText(re.getRefName()); + } + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + AbstractScopedTestElement extractor = new RegexExtractor(); + modifyTestElement(extractor); + return extractor; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement extractor) { + super.configureTestElement(extractor); + if (extractor instanceof RegexExtractor) { + RegexExtractor regex = (RegexExtractor) extractor; + saveScopeSettings(regex); + regex.setUseField(group.getSelection().getActionCommand()); + regex.setRefName(refNameField.getText()); + regex.setRegex(regexField.getText()); + regex.setTemplate(templateField.getText()); + regex.setDefaultValue(defaultField.getText()); + regex.setMatchNumber(matchNumberField.getText()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + useBody.setSelected(true); + + regexField.setText(""); //$NON-NLS-1$ + templateField.setText(""); //$NON-NLS-1$ + defaultField.setText(""); //$NON-NLS-1$ + refNameField.setText(""); //$NON-NLS-1$ + matchNumberField.setText(""); //$NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createScopePanel(true)); + box.add(makeSourcePanel()); + add(box, BorderLayout.NORTH); + add(makeParameterPanel(), BorderLayout.CENTER); + } + + private JPanel makeSourcePanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("regex_source"))); //$NON-NLS-1$ + + useBody = new JRadioButton(JMeterUtils.getResString("regex_src_body")); //$NON-NLS-1$ + useUnescapedBody = new JRadioButton(JMeterUtils.getResString("regex_src_body_unescaped")); //$NON-NLS-1$ + useHeaders = new JRadioButton(JMeterUtils.getResString("regex_src_hdrs")); //$NON-NLS-1$ + useURL = new JRadioButton(JMeterUtils.getResString("regex_src_url")); //$NON-NLS-1$ + useCode = new JRadioButton(JMeterUtils.getResString("assertion_code_resp")); //$NON-NLS-1$ + useMessage = new JRadioButton(JMeterUtils.getResString("assertion_message_resp")); //$NON-NLS-1$ + + group = new ButtonGroup(); + group.add(useBody); + group.add(useUnescapedBody); + group.add(useHeaders); + group.add(useURL); + group.add(useCode); + group.add(useMessage); + + panel.add(useBody); + panel.add(useUnescapedBody); + panel.add(useHeaders); + panel.add(useURL); + panel.add(useCode); + panel.add(useMessage); + + useBody.setSelected(true); + + // So we know which button is selected + useBody.setActionCommand(RegexExtractor.USE_BODY); + useUnescapedBody.setActionCommand(RegexExtractor.USE_BODY_UNESCAPED); + useHeaders.setActionCommand(RegexExtractor.USE_HDRS); + useURL.setActionCommand(RegexExtractor.USE_URL); + useCode.setActionCommand(RegexExtractor.USE_CODE); + useMessage.setActionCommand(RegexExtractor.USE_MESSAGE); + + return panel; + } + + private JPanel makeParameterPanel() { + regexField = new JLabeledTextField(JMeterUtils.getResString("regex_field")); //$NON-NLS-1$ + templateField = new JLabeledTextField(JMeterUtils.getResString("template_field")); //$NON-NLS-1$ + defaultField = new JLabeledTextField(JMeterUtils.getResString("default_value_field")); //$NON-NLS-1$ + refNameField = new JLabeledTextField(JMeterUtils.getResString("ref_name_field")); //$NON-NLS-1$ + matchNumberField = new JLabeledTextField(JMeterUtils.getResString("match_num_field")); //$NON-NLS-1$ + + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + initConstraints(gbc); + addField(panel, refNameField, gbc); + resetContraints(gbc); + addField(panel, regexField, gbc); + resetContraints(gbc); + addField(panel, templateField, gbc); + resetContraints(gbc); + addField(panel, matchNumberField, gbc); + resetContraints(gbc); + gbc.weighty = 1; + addField(panel, defaultField, gbc); + return panel; + } + + private void addField(JPanel panel, JLabeledTextField field, GridBagConstraints gbc) { + List item = field.getComponentList(); + panel.add(item.get(0), gbc.clone()); + gbc.gridx++; + gbc.weightx = 1; + gbc.fill=GridBagConstraints.HORIZONTAL; + panel.add(item.get(1), gbc.clone()); + } + + // Next line + private void resetContraints(GridBagConstraints gbc) { + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0; + gbc.fill=GridBagConstraints.NONE; + } + + private void initConstraints(GridBagConstraints gbc) { + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.NONE; + gbc.gridheight = 1; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0; + gbc.weighty = 0; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/extractor/gui/XPathExtractorGui.java b/ApacheJmeter/src/org/apache/jmeter/extractor/gui/XPathExtractorGui.java new file mode 100644 index 0000000..f820e28 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/extractor/gui/XPathExtractorGui.java @@ -0,0 +1,171 @@ +/* + * 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.extractor.gui; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JPanel; + +import org.apache.jmeter.assertions.gui.XMLConfPanel; +import org.apache.jmeter.extractor.XPathExtractor; +import org.apache.jmeter.processor.gui.AbstractPostProcessorGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; +/** + * GUI for XPathExtractor class. + */ + /* This file is inspired by RegexExtractor. + * See Bugzilla: 37183 + */ +public class XPathExtractorGui extends AbstractPostProcessorGui { + + private static final long serialVersionUID = 240L; + + private final JLabeledTextField defaultField = + new JLabeledTextField(JMeterUtils.getResString("default_value_field"));//$NON-NLS-1$ + + private final JLabeledTextField xpathQueryField = + new JLabeledTextField(JMeterUtils.getResString("xpath_extractor_query"));//$NON-NLS-1$ + + private final JLabeledTextField refNameField = + new JLabeledTextField(JMeterUtils.getResString("ref_name_field"));//$NON-NLS-1$ + + // Should we return fragment as text, rather than text of fragment? + private final JCheckBox getFragment = + new JCheckBox(JMeterUtils.getResString("xpath_extractor_fragment"));//$NON-NLS-1$ + + private final XMLConfPanel xml = new XMLConfPanel(); + + public String getLabelResource() { + return "xpath_extractor_title"; //$NON-NLS-1$ + } + + public XPathExtractorGui(){ + super(); + init(); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + XPathExtractor xpe = (XPathExtractor) el; + showScopeSettings(xpe,true); + xpathQueryField.setText(xpe.getXPathQuery()); + defaultField.setText(xpe.getDefaultValue()); + refNameField.setText(xpe.getRefName()); + getFragment.setSelected(xpe.getFragment()); + xml.configure(xpe); + } + + + public TestElement createTestElement() { + XPathExtractor extractor = new XPathExtractor(); + modifyTestElement(extractor); + return extractor; + } + + public void modifyTestElement(TestElement extractor) { + super.configureTestElement(extractor); + if ( extractor instanceof XPathExtractor){ + XPathExtractor xpath = (XPathExtractor)extractor; + saveScopeSettings(xpath); + xpath.setDefaultValue(defaultField.getText()); + xpath.setRefName(refNameField.getText()); + xpath.setXPathQuery(xpathQueryField.getText()); + xpath.setFragment(getFragment.isSelected()); + xml.modifyTestElement(xpath); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + xpathQueryField.setText(""); // $NON-NLS-1$ + defaultField.setText(""); // $NON-NLS-1$ + refNameField.setText(""); // $NON-NLS-1$ + xml.setDefaultValues(); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createScopePanel(true)); + xml.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("xpath_assertion_option"))); //$NON-NLS-1$ + box.add(xml); + box.add(getFragment); + box.add(makeParameterPanel()); + add(box, BorderLayout.NORTH); + } + + + private JPanel makeParameterPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + initConstraints(gbc); + addField(panel, refNameField, gbc); + resetContraints(gbc); + addField(panel, xpathQueryField, gbc); + resetContraints(gbc); + gbc.weighty = 1; + addField(panel, defaultField, gbc); + return panel; + } + + private void addField(JPanel panel, JLabeledTextField field, GridBagConstraints gbc) { + List item = field.getComponentList(); + panel.add(item.get(0), gbc.clone()); + gbc.gridx++; + gbc.weightx = 1; + gbc.fill=GridBagConstraints.HORIZONTAL; + panel.add(item.get(1), gbc.clone()); + } + + private void resetContraints(GridBagConstraints gbc) { + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0; + gbc.fill=GridBagConstraints.NONE; + } + + private void initConstraints(GridBagConstraints gbc) { + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.NONE; + gbc.gridheight = 1; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0; + gbc.weighty = 0; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/AbstractFunction.java b/ApacheJmeter/src/org/apache/jmeter/functions/AbstractFunction.java new file mode 100644 index 0000000..a488af2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/AbstractFunction.java @@ -0,0 +1,144 @@ +/* + * 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 java.util.Collection; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; + +/** + * Provides common methods for all functions + */ +public abstract class AbstractFunction implements Function { + + /** + *

+ * N.B. setParameters() and execute() are called from different threads, + * so both must be synchronized unless there are no parameters to save + *

+ * @see Function#execute(SampleResult, Sampler) + */ + abstract public String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException; + + public String execute() throws InvalidVariableException { + JMeterContext context = JMeterContextService.getContext(); + SampleResult previousResult = context.getPreviousResult(); + Sampler currentSampler = context.getCurrentSampler(); + return execute(previousResult, currentSampler); + } + + /** + * + *

+ * N.B. setParameters() and execute() are called from different threads, + * so both must be synchronized unless there are no parameters to save + *

+ * + * @see Function#setParameters(Collection) + *
+ * Note: This is always called even if no parameters are provided + * (versions of JMeter after 2.3.1) + */ + abstract public void setParameters(Collection parameters) throws InvalidVariableException; + + /** + * @see Function#getReferenceKey() + */ + abstract public String getReferenceKey(); + + /** + * Gives access to the JMeter variables for the current thread. + * + * @return a pointer to the JMeter variables. + */ + protected JMeterVariables getVariables() { + return JMeterContextService.getContext().getVariables(); + } + + /** + * Utility method to check parameter counts. + * + * @param parameters collection of parameters + * @param min minimum number of parameters allowed + * @param max maximum number of parameters allowed + * + * @throws InvalidVariableException if the number of parameters is incorrect + */ + protected void checkParameterCount(Collection parameters, int min, int max) + throws InvalidVariableException + { + int num = parameters.size(); + if ((num > max) || (num < min)) { + throw new InvalidVariableException( + getReferenceKey() + + " called with wrong number of parameters. Actual: "+num+ + ( + min==max ? + ". Expected: "+min+"." + : ". Expected: >= "+min+" and <= "+max + ) + ); + } + } + + /** + * Utility method to check parameter counts. + * + * @param parameters collection of parameters + * @param count number of parameters expected + * + * @throws InvalidVariableException if the number of parameters is incorrect + */ + protected void checkParameterCount(Collection parameters, int count) + throws InvalidVariableException + { + int num = parameters.size(); + if (num != count) { + throw new InvalidVariableException( + getReferenceKey() + + " called with wrong number of parameters. Actual: "+num+". Expected: "+count+"." + ); + } + } + + /** + * Utility method to check parameter counts. + * + * @param parameters collection of parameters + * @param minimum number of parameters expected + * + * @throws InvalidVariableException if the number of parameters is incorrect + */ + protected void checkMinParameterCount(Collection parameters, int minimum) + throws InvalidVariableException + { + int num = parameters.size(); + if (num < minimum) { + throw new InvalidVariableException( + getReferenceKey() + + " called with wrong number of parameters. Actual: "+num+". Expected at least: "+minimum+"." + ); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/AbstractHostIPName.java b/ApacheJmeter/src/org/apache/jmeter/functions/AbstractHostIPName.java new file mode 100644 index 0000000..34d94ab --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/AbstractHostIPName.java @@ -0,0 +1,83 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +abstract class AbstractHostIPName extends AbstractFunction { + + private static final List desc = new LinkedList(); + + static { + // desc.add("Use fully qualified host name: TRUE/FALSE (Default FALSE)"); + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private Object[] values; + + public AbstractHostIPName() { + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + /* + * boolean fullHostName = false; if (((CompoundFunction) values[0]) + * .execute() .toLowerCase() .equals("true")) { fullHostName = true; } + */ + + String value = compute(); + + if (values.length >= 1){// we have a variable name + JMeterVariables vars = getVariables(); + if (vars != null) {// May be null if function is used on TestPlan + String varName = ((CompoundVariable) values[0]).execute().trim(); + if (varName.length() > 0) { + vars.put(varName, value); + } + } + } + return value; + + } + + abstract protected String compute(); + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 0, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/BeanShell.java b/ApacheJmeter/src/org/apache/jmeter/functions/BeanShell.java new file mode 100644 index 0000000..9f4f397 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/BeanShell.java @@ -0,0 +1,154 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A function which understands BeanShell + * @since 1.X + */ +public class BeanShell extends AbstractFunction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__BeanShell"; //$NON-NLS-1$ + + public static final String INIT_FILE = "beanshell.function.init"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("bsh_function_expression"));// $NON-NLS1$ + desc.add(JMeterUtils.getResString("function_name_paropt"));// $NON-NLS1$ + } + + private Object[] values; + + private BeanShellInterpreter bshInterpreter = null; + + public BeanShell() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + if (bshInterpreter == null) // did we find BeanShell? + { + throw new InvalidVariableException("BeanShell not found"); + } + + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + + String script = ((CompoundVariable) values[0]).execute(); + String varName = ""; //$NON-NLS-1$ + if (values.length > 1) { + varName = ((CompoundVariable) values[1]).execute().trim(); + } + + String resultStr = ""; //$NON-NLS-1$ + + log.debug("Script=" + script); + + try { + + // Pass in some variables + if (currentSampler != null) { + bshInterpreter.set("Sampler", currentSampler); //$NON-NLS-1$ + } + + if (previousResult != null) { + bshInterpreter.set("SampleResult", previousResult); //$NON-NLS-1$ + } + + // Allow access to context and variables directly + bshInterpreter.set("ctx", jmctx); //$NON-NLS-1$ + bshInterpreter.set("vars", vars); //$NON-NLS-1$ + bshInterpreter.set("props", JMeterUtils.getJMeterProperties()); //$NON-NLS-1$ + bshInterpreter.set("threadName", Thread.currentThread().getName()); //$NON-NLS-1$ + + // Execute the script + Object bshOut = bshInterpreter.eval(script); + if (bshOut != null) { + resultStr = bshOut.toString(); + } + if (vars != null && varName.length() > 0) {// vars will be null on TestPlan + vars.put(varName, resultStr); + } + } catch (Exception ex) // Mainly for bsh.EvalError + { + log.warn("Error running BSH script", ex); + } + + log.debug("Output=" + resultStr); + return resultStr; + + } + + /* + * Helper method for use by scripts + * + */ + public void log_info(String s) { + log.info(s); + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + + checkParameterCount(parameters, 1, 2); + + values = parameters.toArray(); + + try { + bshInterpreter = new BeanShellInterpreter(JMeterUtils.getProperty(INIT_FILE), log); + } catch (ClassNotFoundException e) { + throw new InvalidVariableException("BeanShell not found"); + } + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/CSVRead.java b/ApacheJmeter/src/org/apache/jmeter/functions/CSVRead.java new file mode 100644 index 0000000..b1e6fd6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/CSVRead.java @@ -0,0 +1,157 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * 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 + * line-thru the data in the CSV file - one line per each test. E.g. inserting + * the following in the test scripts : + * + * ${_CSVRead(c:/BOF/abcd.csv,0)} // read (first) line of 'c:/BOF/abcd.csv' , + * return the 1st column ( represented by the '0'), + * ${_CSVRead(c:/BOF/abcd.csv,1)} // read (first) line of 'c:/BOF/abcd.csv' , + * return the 2nd column ( represented by the '1'), + * ${_CSVRead(c:/BOF/abcd.csv,next())} // Go to next line of 'c:/BOF/abcd.csv' + * + * NOTE: A single instance of each different file is opened and used for all + * threads. + * + * To open the same file twice, use the alias function: __CSVRead(abc.csv,*ONE); + * __CSVRead(abc.csv,*TWO); + * + * __CSVRead(*ONE,1); etc + * @since 1.9 + */ +public class CSVRead extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String KEY = "__CSVRead"; // Function name //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + private Object[] values; // Parameter list + + static { + desc.add(JMeterUtils.getResString("csvread_file_file_name")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("column_number")); //$NON-NLS-1$ + } + + public CSVRead() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String myValue = ""; //$NON-NLS-1$ + + String fileName = ((org.apache.jmeter.engine.util.CompoundVariable) values[0]).execute(); + String columnOrNext = ((org.apache.jmeter.engine.util.CompoundVariable) values[1]).execute(); + + log.debug("execute (" + fileName + " , " + columnOrNext + ") "); + + // Process __CSVRead(filename,*ALIAS) + if (columnOrNext.startsWith("*")) { //$NON-NLS-1$ + FileWrapper.open(fileName, columnOrNext); + /* + * All done, so return + */ + return ""; //$NON-NLS-1$ + } + + // if argument is 'next' - go to the next line + if (columnOrNext.equals("next()") || columnOrNext.equals("next")) { //$NON-NLS-1$ //$NON-NLS-2$ + FileWrapper.endRow(fileName); + + /* + * All done now ,so return the empty string - this allows the caller + * to append __CSVRead(file,next) to the last instance of + * __CSVRead(file,col) + * + * N.B. It is important not to read any further lines at this point, + * otherwise the wrong line can be retrieved when using multiple + * threads. + */ + return ""; //$NON-NLS-1$ + } + + try { + int columnIndex = Integer.parseInt(columnOrNext); // what column + // is wanted? + myValue = FileWrapper.getColumn(fileName, columnIndex); + } catch (NumberFormatException e) { + log.warn(Thread.currentThread().getName() + " - can't parse column number: " + columnOrNext + " " + + e.toString()); + } catch (IndexOutOfBoundsException e) { + log.warn(Thread.currentThread().getName() + " - invalid column number: " + columnOrNext + " at row " + + FileWrapper.getCurrentRow(fileName) + " " + e.toString()); + } + + log.debug("execute value: " + myValue); + + return myValue; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + log.debug("setParameter - Collection.size=" + parameters.size()); + + values = parameters.toArray(); + + if (log.isDebugEnabled()) { + for (int i = 0; i < parameters.size(); i++) { + log.debug("i:" + ((CompoundVariable) values[i]).execute()); + } + } + + checkParameterCount(parameters, 2); + + /* + * Need to reset the containers for repeated runs; about the only way + * for functions to detect that a run is starting seems to be the + * setParameters() call. + */ + FileWrapper.clearAll();// TODO only clear the relevant entry - if possible... + + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/CharFunction.java b/ApacheJmeter/src/org/apache/jmeter/functions/CharFunction.java new file mode 100644 index 0000000..d39fab3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/CharFunction.java @@ -0,0 +1,90 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Function to generate chars from a list of decimal or hex values + * @since 2.3.3 + */ +public class CharFunction extends AbstractFunction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__char"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("char_value")); //$NON-NLS-1$ + } + + private Object[] values; + + public CharFunction() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + StringBuilder sb = new StringBuilder(values.length); + for (int i=0; i < values.length; i++){ + String numberString = ((CompoundVariable) values[i]).execute().trim(); + try { + long value=Long.decode(numberString).longValue(); + char ch = (char) value; + sb.append(ch); + } catch (NumberFormatException e){ + log.warn("Could not parse "+numberString+" : "+e); + } + } + return sb.toString(); + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkMinParameterCount(parameters, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/EscapeHtml.java b/ApacheJmeter/src/org/apache/jmeter/functions/EscapeHtml.java new file mode 100644 index 0000000..f4199c7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/EscapeHtml.java @@ -0,0 +1,92 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; + +/** + *

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

+ * + *

+ * For example: + *

+ *

"bread" & "butter"

+ * becomes: + *

+ * &quot;bread&quot; &amp; &quot;butter&quot;. + *

+ * + *

Supports all known HTML 4.0 entities. + * Note that the commonly used apostrophe escape character (&apos;) + * is not a legal entity and so is not supported).

+ * + * @see StringEscapeUtils#escapeHtml(String) (Commons Lang) + * @since 2.3.3 + */ +public class EscapeHtml extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__escapeHtml"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("escape_html_string")); //$NON-NLS-1$ + } + + private Object[] values; + + public EscapeHtml() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + String rawString = ((CompoundVariable) values[0]).execute(); + return StringEscapeUtils.escapeHtml(rawString); + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/EvalFunction.java b/ApacheJmeter/src/org/apache/jmeter/functions/EvalFunction.java new file mode 100644 index 0000000..4b57094 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/EvalFunction.java @@ -0,0 +1,86 @@ +/* + * 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; + +// @see PackageTest for unit tests + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Function to evaluate a string which may contain variable or function references. + * + * Parameter: string to be evaluated + * + * Returns: the evaluated value + * @since 2.3.1 + */ +public class EvalFunction extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__eval"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + private static final int MAX_PARAMETER_COUNT = 1; + + static { + desc.add(JMeterUtils.getResString("eval_name_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public EvalFunction() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String parameter = ((CompoundVariable) values[0]).execute(); + CompoundVariable cv = new CompoundVariable(parameter); + return cv.execute(); + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/EvalVarFunction.java b/ApacheJmeter/src/org/apache/jmeter/functions/EvalVarFunction.java new file mode 100644 index 0000000..1de28c2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/EvalVarFunction.java @@ -0,0 +1,97 @@ +/* + * 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; + +// @see PackageTest for unit tests + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Function to evaluate a string which may contain variable or function references. + * + * Parameter: string to be evaluated + * + * Returns: the evaluated value + * @since 2.3.1 + */ +public class EvalVarFunction extends AbstractFunction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__evalVar"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + private static final int MAX_PARAMETER_COUNT = 1; + + static { + desc.add(JMeterUtils.getResString("evalvar_name_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public EvalVarFunction() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String variableName = ((CompoundVariable) values[0]).execute(); + final JMeterVariables vars = getVariables(); + if (vars == null){ + log.error("Variables have not yet been defined"); + return "**ERROR - see log file**"; + } + String variableValue = vars.get(variableName); + CompoundVariable cv = new CompoundVariable(variableValue); + return cv.execute(); + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/FileRowColContainer.java b/ApacheJmeter/src/org/apache/jmeter/functions/FileRowColContainer.java new file mode 100644 index 0000000..2e269e0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/FileRowColContainer.java @@ -0,0 +1,191 @@ +/* + * 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 java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.StringTokenizer; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * File data container for CSV (and similar delimited) files Data is accessible + * via row and column number + * + */ +public class FileRowColContainer { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final ArrayList> fileData; // Lines in the file, split into columns + + private final String fileName; // name of the file + + public static final String DELIMITER + = JMeterUtils.getPropDefault("csvread.delimiter", // $NON-NLS-1$ + ","); // $NON-NLS-1$ + + /** Keeping track of which row is next to be read. */ + private int nextRow; + + /** Delimiter for this file */ + private final String delimiter; + + public FileRowColContainer(String file, String delim) throws IOException, FileNotFoundException { + log.debug("FRCC(" + file + "," + delim + ")"); + fileName = file; + delimiter = delim; + nextRow = 0; + fileData = new ArrayList>(); + load(); + } + + public FileRowColContainer(String file) throws IOException, FileNotFoundException { + log.debug("FRCC(" + file + ")[" + DELIMITER + "]"); + fileName = file; + delimiter = DELIMITER; + nextRow = 0; + fileData = new ArrayList>(); + load(); + } + + private void load() throws IOException, FileNotFoundException { + + BufferedReader myBread = null; + try { + FileReader fis = new FileReader(fileName); + myBread = new BufferedReader(fis); + String line = myBread.readLine(); + /* + * N.B. Stop reading the file if we get a blank line: This allows + * for trailing comments in the file + */ + while (line != null && line.length() > 0) { + fileData.add(splitLine(line, delimiter)); + line = myBread.readLine(); + } + } catch (FileNotFoundException e) { + fileData.clear(); + log.warn(e.toString()); + throw e; + } catch (IOException e) { + fileData.clear(); + log.warn(e.toString()); + throw e; + } finally { + if (myBread != null) { + myBread.close(); + } + } + } + + /** + * Get the string for the column from the current row + * + * @param row + * row number (from 0) + * @param col + * column number (from 0) + * @return the string (empty if out of bounds) + * @throws IndexOutOfBoundsException + * if the column number is out of bounds + */ + public String getColumn(int row, int col) throws IndexOutOfBoundsException { + String colData; + colData = fileData.get(row).get(col); + log.debug(fileName + "(" + row + "," + col + "): " + colData); + return colData; + } + + /** + * Returns the next row to the caller, and updates it, allowing for wrap + * round + * + * @return the first free (unread) row + * + */ + public int nextRow() { + int row = nextRow; + nextRow++; + if (nextRow >= fileData.size())// 0-based + { + nextRow = 0; + } + log.debug("Row: " + row); + return row; + } + + /** + * Splits the line according to the specified delimiter + * + * @return an ArrayList of Strings containing one element for each value in + * the line + */ + private static ArrayList splitLine(String theLine, String delim) { + ArrayList result = new ArrayList(); + StringTokenizer tokener = new StringTokenizer(theLine, delim, true); + /* + * the beginning of the line is a "delimiter" so that ,a,b,c returns "" + * "a" "b" "c" + */ + boolean lastWasDelim = true; + while (tokener.hasMoreTokens()) { + String token = tokener.nextToken(); + if (token.equals(delim)) { + if (lastWasDelim) { + // two delimiters in a row; add an empty String + result.add(""); + } + lastWasDelim = true; + } else { + lastWasDelim = false; + result.add(token); + } + } + if (lastWasDelim) // Catch the trailing delimiter + { + result.add(""); // $NON-NLS-1$ + } + return result; + } + + /** + * @return the file name for this class + */ + public String getFileName() { + return fileName; + } + + /** + * @return Returns the delimiter. + */ + final String getDelimiter() { + return delimiter; + } + + // Added to support external testing + public int getSize(){ + return fileData.size(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/FileToString.java b/ApacheJmeter/src/org/apache/jmeter/functions/FileToString.java new file mode 100644 index 0000000..d94f3e9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/FileToString.java @@ -0,0 +1,142 @@ +/* + * 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 java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.log.Logger; + +/** + * FileToString Function to read a complete file into a String. + * + * Parameters: + * - file name + * - file encoding (optional) + * - variable name (optional) + * + * Returns: + * - the whole text from a file + * - or **ERR** if an error occurs + * - value is also optionally saved in the variable for later re-use. + * @since 2.4 + */ +public class FileToString extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__FileToString";//$NON-NLS-1$ + + static final String ERR_IND = "**ERR**";//$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("string_from_file_file_name"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("string_from_file_encoding"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt"));//$NON-NLS-1$ + } + + private static final int MIN_PARAM_COUNT = 1; + + private static final int MAX_PARAM_COUNT = 3; + + private static final int ENCODING = 2; + + private static final int PARAM_NAME = 3; + + private Object[] values; + + public FileToString() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + String fileName = ((CompoundVariable) values[0]).execute(); + + String encoding = null;//means platform default + if (values.length >= ENCODING) { + encoding = ((CompoundVariable) values[ENCODING - 1]).execute().trim(); + if (encoding.length() <= 0) { // empty encoding, return to platorm default + encoding = null; + } + } + + String myName = "";//$NON-NLS-1$ + if (values.length >= PARAM_NAME) { + myName = ((CompoundVariable) values[PARAM_NAME - 1]).execute().trim(); + } + + String myValue = ERR_IND; + + try { + myValue = FileUtils.readFileToString(new File(fileName), encoding); + } catch (IOException e) { + log.warn("Could not read file: "+fileName+" "+e.getMessage()); + throw new JMeterStopThreadException("End of sequence"); + } + + if (myName.length() > 0) { + JMeterVariables vars = getVariables(); + if (vars != null) {// Can be null if called from Config item testEnded() method + vars.put(myName, myValue); + } + } + + if (log.isDebugEnabled()) { + String tn = Thread.currentThread().getName(); + log.debug(tn + " name:" //$NON-NLS-1$ + + myName + " value:" + myValue);//$NON-NLS-1$ + } + + return myValue; + } + + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAM_COUNT, MAX_PARAM_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/FileWrapper.java b/ApacheJmeter/src/org/apache/jmeter/functions/FileWrapper.java new file mode 100644 index 0000000..5f65914 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/FileWrapper.java @@ -0,0 +1,209 @@ +/* + * 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 java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class wraps the FileRowColContainer for use across multiple threads. + * + * It does this by maintaining a list of open files, keyed by file name (or + * alias, if used). A list of open files is also maintained for each thread, + * together with the current line number. + * + */ +public class FileWrapper { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int NO_LINE = -1; + + private static volatile String defaultFile = ""; // for omitted file names //$NON-NLS-1$ + + /* + * This Map serves two purposes: + * - maps file names to containers + * - ensures only one container per file across all threads + */ + private static final Map fileContainers = + new HashMap(); + + /* The cache of file packs - used to improve thread access */ + private static final ThreadLocal> filePacks = + new ThreadLocal>() { + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + private final FileRowColContainer container; + + private int currentRow; + + /* + * Only needed locally + */ + private FileWrapper(FileRowColContainer fdc) { + super(); + container = fdc; + currentRow = -1; + } + + private static String checkDefault(String file) { + if (file.length() == 0) { + if (fileContainers.size() == 1 && defaultFile.length() > 0) { + log.warn("Using default: " + defaultFile); + file = defaultFile; + } else { + log.error("Cannot determine default file name"); + } + } + return file; + } + + /* + * called by CSVRead(file,alias) + */ + public static synchronized void open(String file, String alias) { + log.info("Opening " + file + " as " + alias); + file = checkDefault(file); + if (alias.length() == 0) { + log.error("Alias cannot be empty"); + return; + } + Map m = filePacks.get(); + if (m.get(alias) == null) { + FileRowColContainer frcc; + try { + frcc = getFile(file, alias); + log.info("Stored " + file + " as " + alias); + m.put(alias, new FileWrapper(frcc)); + } catch (FileNotFoundException e) { + // Already logged + } catch (IOException e) { + // Already logged + } + } + } + + private static FileRowColContainer getFile(String file, String alias) throws FileNotFoundException, IOException { + FileRowColContainer frcc; + if ((frcc = fileContainers.get(alias)) == null) { + frcc = new FileRowColContainer(file); + fileContainers.put(alias, frcc); + log.info("Saved " + file + " as " + alias + " delimiter=<" + frcc.getDelimiter() + ">"); + if (defaultFile.length() == 0) { + defaultFile = file;// Save in case needed later + } + } + return frcc; + } + + /* + * Called by CSVRead(x,next) - sets the row to nil so the next row will be + * picked up the next time round + * + */ + public static void endRow(String file) { + file = checkDefault(file); + Map my = filePacks.get(); + FileWrapper fw = my.get(file); + if (fw == null) { + log.warn("endRow(): no entry for " + file); + } else { + fw.endRow(); + } + } + + private void endRow() { + if (currentRow == NO_LINE) { + log.warn("endRow() called twice in succession"); + } + currentRow = NO_LINE; + } + + public static String getColumn(String file, int col) { + Map my = filePacks.get(); + FileWrapper fw = my.get(file); + if (fw == null) // First call + { + if (file.startsWith("*")) { //$NON-NLS-1$ + log.warn("Cannot perform initial open using alias " + file); + } else { + file = checkDefault(file); + log.info("Attaching " + file); + open(file, file); + fw = my.get(file); + } + // TODO improve the error handling + if (fw == null) { + return ""; //$NON-NLS-1$ + } + } + return fw.getColumn(col); + } + + private String getColumn(int col) { + if (currentRow == NO_LINE) { + currentRow = container.nextRow(); + + } + return container.getColumn(currentRow, col); + } + + /** + * Gets the current row number (mainly for error reporting) + * + * @param file + * @return the current row number for this thread + */ + public static int getCurrentRow(String file) { + + Map my = filePacks.get(); + FileWrapper fw = my.get(file); + if (fw == null) // Not yet open + { + return -1; + } + return fw.currentRow; + } + + /** + * + */ + public static void clearAll() { + log.debug("clearAll()"); + Map my = filePacks.get(); + for (Iterator> i = my.entrySet().iterator(); i.hasNext();) { + Map.Entry fw = i.next(); + log.info("Removing " + fw.toString()); + i.remove(); + } + fileContainers.clear(); + defaultFile = ""; //$NON-NLS-1$ + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/Function.java b/ApacheJmeter/src/org/apache/jmeter/functions/Function.java new file mode 100644 index 0000000..32f4899 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/Function.java @@ -0,0 +1,70 @@ +/* + * 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 java.util.Collection; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; + +/** + * Methods that a function must implement + */ +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. + */ + public String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException; + + /** + * A collection of the parameters used to configure your function. Each + * parameter is a CompoundVariable and can be resolved by calling the + * execute() method of the CompoundVariable (which should be done at + * execution.) + * + * @param parameters + * @throws InvalidVariableException + */ + public void setParameters(Collection parameters) throws InvalidVariableException; + + /** + * Return the name of your function. Convention is to prepend "__" to the + * name (ie "__regexFunction") + */ + public String getReferenceKey(); + + /** + * Return a list of strings briefly describing each parameter your function + * takes. Please use JMeterUtils.getResString(resource_name) to grab a + * resource string. Otherwise, your help text will be difficult to + * internationalize. + * + * This list is not optional. If you don't wish to write help, you must at + * least return a List containing the correct number of blank strings, one + * for each argument. + */ + public List getArgumentDesc(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/IntSum.java b/ApacheJmeter/src/org/apache/jmeter/functions/IntSum.java new file mode 100644 index 0000000..d61c256 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/IntSum.java @@ -0,0 +1,103 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Provides an intSum function that adds two or more integer values. + * + * @see LongSum + * @since 1.8.1 + */ +public class IntSum extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__intSum"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("intsum_param_1")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("intsum_param_2")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private Object[] values; + + /** + * No-arg constructor. + */ + public IntSum() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + JMeterVariables vars = getVariables(); + + int sum = 0; + String varName = ((CompoundVariable) values[values.length - 1]).execute(); + + for (int i = 0; i < values.length - 1; i++) { + sum += Integer.parseInt(((CompoundVariable) values[i]).execute()); + } + + try { + sum += Integer.parseInt(varName); + varName = null; // there is no variable name + } catch (NumberFormatException ignored) { + } + + String totalString = Integer.toString(sum); + if (vars != null && varName != null){// vars will be null on TestPlan + vars.put(varName.trim(), totalString); + } + + return totalString; + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkMinParameterCount(parameters, 2); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/InvalidVariableException.java b/ApacheJmeter/src/org/apache/jmeter/functions/InvalidVariableException.java new file mode 100644 index 0000000..d6eda13 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/InvalidVariableException.java @@ -0,0 +1,30 @@ +/* + * 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; + +public class InvalidVariableException extends Exception { + private static final long serialVersionUID = 240L; + + public InvalidVariableException() { + } + + public InvalidVariableException(String msg) { + super(msg); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/IterationCounter.java b/ApacheJmeter/src/org/apache/jmeter/functions/IterationCounter.java new file mode 100644 index 0000000..f144ee3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/IterationCounter.java @@ -0,0 +1,120 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Counter that can be referenced anywhere in the Thread Group. It can be configured per User (Thread Local) + * or globally. + * @since 1.X + */ +public class IterationCounter extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__counter"; //$NON-NLS-1$ + + private ThreadLocal perThreadInt; + + private Object[] variables; + + private int globalCounter;//MAXINT = 2,147,483,647 + + private void init(){ + synchronized(this){ + globalCounter=0; + } + perThreadInt = new ThreadLocal(){ + @Override + protected Integer initialValue() { + return Integer.valueOf(0); + } + }; + } + + static { + desc.add(JMeterUtils.getResString("iteration_counter_arg_1")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + public IterationCounter() { + init(); + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + globalCounter++; + + JMeterVariables vars = getVariables(); + + boolean perThread = Boolean.valueOf(((CompoundVariable) variables[0]).execute()).booleanValue(); + + String varName = ""; //$NON-NLS-1$ + if (variables.length >=2) {// Ensure variable has been provided + varName = ((CompoundVariable) variables[1]).execute().trim(); + } + + String counterString = ""; //$NON-NLS-1$ + + if (perThread) { + int threadCounter; + threadCounter = perThreadInt.get().intValue() + 1; + perThreadInt.set(Integer.valueOf(threadCounter)); + counterString = String.valueOf(threadCounter); + } else { + counterString = String.valueOf(globalCounter); + } + + // vars will be null on Test Plan + if (vars != null && varName.length() > 0) { + vars.put(varName, counterString); + } + return counterString; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1, 2); + variables = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/JavaScript.java b/ApacheJmeter/src/org/apache/jmeter/functions/JavaScript.java new file mode 100644 index 0000000..4d71e36 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/JavaScript.java @@ -0,0 +1,124 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.RhinoException; +import org.mozilla.javascript.Scriptable; + +/** + * javaScript function implementation that executes a piece of JavaScript (not Java!) code and returns its value + * @since 1.9 + */ +public class JavaScript extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__javaScript"; //$NON-NLS-1$ + + private static final Logger log = LoggingManager.getLoggerForClass(); + + static { + desc.add(JMeterUtils.getResString("javascript_expression"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private Object[] values; + + public JavaScript() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + + String script = ((CompoundVariable) values[0]).execute(); + // Allow variable to be omitted + String varName = values.length < 2 ? null : ((CompoundVariable) values[1]).execute().trim(); + String resultStr = ""; + + Context cx = Context.enter(); + try { + + Scriptable scope = cx.initStandardObjects(null); + + // Set up some objects for the script to play with + scope.put("log", scope, log); //$NON-NLS-1$ + scope.put("ctx", scope, jmctx); //$NON-NLS-1$ + scope.put("vars", scope, vars); //$NON-NLS-1$ + scope.put("props", scope, JMeterUtils.getJMeterProperties()); //$NON-NLS-1$ + // Previously mis-spelt as theadName + scope.put("threadName", scope, Thread.currentThread().getName()); //$NON-NLS-1$ + scope.put("sampler", scope, currentSampler); //$NON-NLS-1$ + scope.put("sampleResult", scope, previousResult); //$NON-NLS-1$ + + Object result = cx.evaluateString(scope, script, "", 1, null); //$NON-NLS-1$ + + resultStr = Context.toString(result); + if (varName != null && vars != null) {// vars can be null if run from TestPlan + vars.put(varName, resultStr); + } + + } catch (RhinoException e) { + log.error("Error processing Javascript: [" + script + "]\n", e); + throw new InvalidVariableException(); + } finally { + Context.exit(); + } + + return resultStr; + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1, 2); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/Jexl2Function.java b/ApacheJmeter/src/org/apache/jmeter/functions/Jexl2Function.java new file mode 100644 index 0000000..030be32 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/Jexl2Function.java @@ -0,0 +1,138 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.jexl2.Expression; +import org.apache.commons.jexl2.JexlContext; +import org.apache.commons.jexl2.JexlEngine; +import org.apache.commons.jexl2.MapContext; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A function which understands Commons JEXL2 + * @since 2.6 + */ +// For unit tests, see TestJexlFunction +public class Jexl2Function extends AbstractFunction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String KEY = "__jexl2"; //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + // TODO should the engine be static? + private static final JexlEngine jexl = new JexlEngine(); + static { + jexl.setCache(512); + jexl.setLenient(false); + jexl.setSilent(false); + } + + static + { + desc.add(JMeterUtils.getResString("jexl_expression")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt"));// $NON-NLS1$ + } + + private Object[] values; + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException + { + String str = ""; //$NON-NLS-1$ + + CompoundVariable var = (CompoundVariable) values[0]; + String exp = var.execute(); + + String varName = ""; //$NON-NLS-1$ + if (values.length > 1) { + varName = ((CompoundVariable) values[1]).execute().trim(); + } + + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + + try + { + JexlContext jc = new MapContext(); + jc.set("log", log); //$NON-NLS-1$ + jc.set("ctx", jmctx); //$NON-NLS-1$ + jc.set("vars", vars); //$NON-NLS-1$ + jc.set("props", JMeterUtils.getJMeterProperties()); //$NON-NLS-1$ + // Previously mis-spelt as theadName + jc.set("threadName", Thread.currentThread().getName()); //$NON-NLS-1$ + jc.set("sampler", currentSampler); //$NON-NLS-1$ (may be null) + jc.set("sampleResult", previousResult); //$NON-NLS-1$ (may be null) + jc.set("OUT", System.out);//$NON-NLS-1$ + + // Now evaluate the script, getting the result + Expression e = jexl.createExpression( exp ); + Object o = e.evaluate(jc); + if (o != null) + { + str = o.toString(); + } + if (vars != null && varName.length() > 0) {// vars will be null on TestPlan + vars.put(varName, str); + } + } catch (Exception e) + { + log.error("An error occurred while evaluating the expression \"" + + exp + "\"\n",e); + } + return str; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() + { + return desc; + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() + { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) + throws InvalidVariableException + { + checkParameterCount(parameters, 1, 2); + values = parameters.toArray(); + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/JexlFunction.java b/ApacheJmeter/src/org/apache/jmeter/functions/JexlFunction.java new file mode 100644 index 0000000..e63be9f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/JexlFunction.java @@ -0,0 +1,132 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.jexl.JexlContext; +import org.apache.commons.jexl.JexlHelper; +import org.apache.commons.jexl.Script; +import org.apache.commons.jexl.ScriptFactory; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A function which understands Commons JEXL + * @since 2.2 + */ +// For unit tests, see TestJexlFunction +public class JexlFunction extends AbstractFunction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String KEY = "__jexl"; //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + static + { + desc.add(JMeterUtils.getResString("jexl_expression")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt"));// $NON-NLS1$ + } + + private Object[] values; + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException + { + String str = ""; //$NON-NLS-1$ + + CompoundVariable var = (CompoundVariable) values[0]; + String exp = var.execute(); + + String varName = ""; //$NON-NLS-1$ + if (values.length > 1) { + varName = ((CompoundVariable) values[1]).execute().trim(); + } + + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + + try + { + Script script = ScriptFactory.createScript(exp); + JexlContext jc = JexlHelper.createContext(); + @SuppressWarnings("unchecked") + final Map jexlVars = jc.getVars(); + jexlVars.put("log", log); //$NON-NLS-1$ + jexlVars.put("ctx", jmctx); //$NON-NLS-1$ + jexlVars.put("vars", vars); //$NON-NLS-1$ + jexlVars.put("props", JMeterUtils.getJMeterProperties()); //$NON-NLS-1$ + // Previously mis-spelt as theadName + jexlVars.put("threadName", Thread.currentThread().getName()); //$NON-NLS-1$ + jexlVars.put("sampler", currentSampler); //$NON-NLS-1$ (may be null) + jexlVars.put("sampleResult", previousResult); //$NON-NLS-1$ (may be null) + jexlVars.put("OUT", System.out);//$NON-NLS-1$ + + // Now evaluate the script, getting the result + Object o = script.execute(jc); + if (o != null) + { + str = o.toString(); + } + if (vars != null && varName.length() > 0) {// vars will be null on TestPlan + vars.put(varName, str); + } + } catch (Exception e) + { + log.error("An error occurred while evaluating the expression \"" + + exp + "\"\n",e); + } + return str; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() + { + return desc; + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() + { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) + throws InvalidVariableException + { + checkParameterCount(parameters, 1, 2); + values = parameters.toArray(); + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/LogFunction.java b/ApacheJmeter/src/org/apache/jmeter/functions/LogFunction.java new file mode 100644 index 0000000..3ac3836 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/LogFunction.java @@ -0,0 +1,181 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.log.Priority; + +/** + *

+ * Function to log a message. + *

+ * + *

+ * Parameters: + *

    + *
  • string value
  • + *
  • log level (optional; defaults to INFO; or DEBUG if unrecognised; or can use OUT or ERR)
  • + *
  • throwable message (optional)
  • + *
  • comment (optional)
  • + *
+ *

+ * Returns: - the input string + * @since 2.2 + */ +public class LogFunction extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__log"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + + private static final int MAX_PARAMETER_COUNT = 4; + static { + desc.add(JMeterUtils.getResString("log_function_string_ret")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("log_function_level")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("log_function_throwable")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("log_function_comment")); //$NON-NLS-1$ + } + + private static final String DEFAULT_PRIORITY = "INFO"; //$NON-NLS-1$ + + private static final String DEFAULT_SEPARATOR = " : "; //$NON-NLS-1$ + + private Object[] values; + + public LogFunction() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String stringToLog = ((CompoundVariable) values[0]).execute(); + + String priorityString; + if (values.length > 1) { // We have a default + priorityString = ((CompoundVariable) values[1]).execute(); + if (priorityString.length() == 0) { + priorityString = DEFAULT_PRIORITY; + } + } else { + priorityString = DEFAULT_PRIORITY; + } + + Throwable t = null; + if (values.length > 2) { // Throwable wanted + String value = ((CompoundVariable) values[2]).execute(); + if (value.length() > 0) { + t = new Throwable(value); + } + } + + String comment = ""; + if (values.length > 3) { // Comment wanted + comment = ((CompoundVariable) values[3]).execute(); + } + + logDetails(log, stringToLog, priorityString, t, comment); + + return stringToLog; + + } + + // Common output function + private static void printDetails(java.io.PrintStream ps, String s, Throwable t, String c) { + String tn = Thread.currentThread().getName(); + + StringBuilder sb = new StringBuilder(80); + sb.append("Log: "); + sb.append(tn); + if (c.length()>0){ + sb.append(" "); + sb.append(c); + } else { + sb.append(DEFAULT_SEPARATOR); + } + sb.append(s); + if (t != null) { + sb.append(" "); + ps.print(sb.toString()); + t.printStackTrace(ps); + } else { + ps.println(sb.toString()); + } + } + + // Routine to perform the output (also used by __logn() function) + static void logDetails(Logger l, String s, String prio, Throwable t, String c) { + if (prio.equalsIgnoreCase("OUT")) //$NON-NLS-1 + { + printDetails(System.out, s, t, c); + } else if (prio.equalsIgnoreCase("ERR")) //$NON-NLS-1 + { + printDetails(System.err, s, t, c); + } else { + // N.B. if the string is not recognised, DEBUG is assumed + Priority p = Priority.getPriorityForName(prio); + if (log.isPriorityEnabled(p)) {// Thread method is potentially expensive + String tn = Thread.currentThread().getName(); + StringBuilder sb = new StringBuilder(40); + sb.append(tn); + if (c.length()>0){ + sb.append(" "); + sb.append(c); + } else { + sb.append(DEFAULT_SEPARATOR); + } + sb.append(s); + log.log(p, sb.toString(), t); + } + } + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/LogFunction2.java b/ApacheJmeter/src/org/apache/jmeter/functions/LogFunction2.java new file mode 100644 index 0000000..7a0ca73 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/LogFunction2.java @@ -0,0 +1,117 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + *

+ * Function to log a message. + *

+ * + *

+ * Parameters: + *

    + *
  • string value
  • + *
  • log level (optional; defaults to INFO; or DEBUG if unrecognised; or can use OUT or ERR)
  • + *
  • throwable message (optional)
  • + *
+ *

+ * Returns: - Empty String (so can be used where return value would be a nuisance) + * @since 2.2 + */ +public class LogFunction2 extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__logn"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + + private static final int MAX_PARAMETER_COUNT = 3; + static { + desc.add(JMeterUtils.getResString("log_function_string")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("log_function_level")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("log_function_throwable")); //$NON-NLS-1$ + } + + private static final String DEFAULT_PRIORITY = "INFO"; //$NON-NLS-1$ + + private Object[] values; + + public LogFunction2() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String stringToLog = ((CompoundVariable) values[0]).execute(); + + String priorityString; + if (values.length > 1) { // We have a default + priorityString = ((CompoundVariable) values[1]).execute(); + if (priorityString.length() == 0) { + priorityString = DEFAULT_PRIORITY; + } + } else { + priorityString = DEFAULT_PRIORITY; + } + + Throwable t = null; + if (values.length > 2) { // Throwable wanted + t = new Throwable(((CompoundVariable) values[2]).execute()); + } + + LogFunction.logDetails(log, stringToLog, priorityString, t, ""); + + return ""; + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/LongSum.java b/ApacheJmeter/src/org/apache/jmeter/functions/LongSum.java new file mode 100644 index 0000000..4e2b44a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/LongSum.java @@ -0,0 +1,102 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Provides a longSum function that adds two or more long values. + * @see IntSum + * @since 2.3.2 + */ +public class LongSum extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__longSum"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("longsum_param_1")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("longsum_param_2")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private Object[] values; + + /** + * No-arg constructor. + */ + public LongSum() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + JMeterVariables vars = getVariables(); + + long sum = 0; + String varName = ((CompoundVariable) values[values.length - 1]).execute().trim(); + + for (int i = 0; i < values.length - 1; i++) { + sum += Long.parseLong(((CompoundVariable) values[i]).execute()); + } + + try { + sum += Long.parseLong(varName); + varName = null; // there is no variable name + } catch (NumberFormatException ignored) { + } + + String totalString = Long.toString(sum); + if (vars != null && varName != null && varName.length() > 0){// vars will be null on TestPlan + vars.put(varName, totalString); + } + + return totalString; + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkMinParameterCount(parameters, 2); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/MachineIP.java b/ApacheJmeter/src/org/apache/jmeter/functions/MachineIP.java new file mode 100644 index 0000000..e58ab47 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/MachineIP.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * Return Machine IP + * @since 2.6 + */ +public class MachineIP extends AbstractHostIPName { + + private static final String KEY = "__machineIP"; //$NON-NLS-1$ + + public MachineIP() { + } + + @Override + protected String compute() { + return JMeterUtils.getLocalHostIP(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/MachineName.java b/ApacheJmeter/src/org/apache/jmeter/functions/MachineName.java new file mode 100644 index 0000000..661e2c5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/MachineName.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * Return Machine Host + * @since 1.X + */ +public class MachineName extends AbstractHostIPName { + + private static final String KEY = "__machineName"; //$NON-NLS-1$ + + public MachineName() { + } + + @Override + protected String compute() { + return JMeterUtils.getLocalHostName(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/Property.java b/ApacheJmeter/src/org/apache/jmeter/functions/Property.java new file mode 100644 index 0000000..7e2aba5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/Property.java @@ -0,0 +1,107 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Function to get a JMeter property, and optionally store it + * + * Parameters: + * - property name + * - variable name (optional) + * - default value (optional) + * + * Returns: + * - the property value, but if not found: + * - the default value, but if not defined: + * - the property name itself + * @since 2.0 + */ +public class Property extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__property"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + private static final int MAX_PARAMETER_COUNT = 3; + + static { + desc.add(JMeterUtils.getResString("property_name_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("property_default_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public Property() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String propertyName = ((CompoundVariable) values[0]).execute(); + String propertyDefault = propertyName; + if (values.length > 2) { // We have a 3rd parameter + propertyDefault = ((CompoundVariable) values[2]).execute(); + } + String propertyValue = JMeterUtils.getPropDefault(propertyName, propertyDefault); + if (values.length > 1) { + String variableName = ((CompoundVariable) values[1]).execute(); + if (variableName.length() > 0) {// Allow for empty name + final JMeterVariables variables = getVariables(); + if (variables != null) { + variables.put(variableName, propertyValue); + } + } + } + return propertyValue; + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/Property2.java b/ApacheJmeter/src/org/apache/jmeter/functions/Property2.java new file mode 100644 index 0000000..0b561e0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/Property2.java @@ -0,0 +1,105 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; + +/** + * 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 + * specialisation of the __property() function to make it simpler to use for + * ThreadGroup GUI etc. The name is also shorter. + * + * Parameters: - property name - default value (optional; defaults to "1") + * + * Usage: + * + * Define the property in jmeter.properties, or on the command-line: java ... + * -Jpropname=value + * + * Retrieve the value in the appropriate GUI by using the string: + * ${__P(propname)} $(__P(propname,default)} + * + * Returns: - the property value, but if not found - the default value, but if + * not present - "1" (suitable for use in ThreadGroup GUI) + * @since 2.0 + */ +public class Property2 extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__P"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + + private static final int MAX_PARAMETER_COUNT = 2; + static { + desc.add(JMeterUtils.getResString("property_name_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("property_default_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public Property2() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String propertyName = ((CompoundVariable) values[0]).execute(); + + String propertyDefault = "1"; //$NON-NLS-1$ + if (values.length > 1) { // We have a default + propertyDefault = ((CompoundVariable) values[1]).execute(); + } + + String propertyValue = JMeterUtils.getPropDefault(propertyName, propertyDefault); + + return propertyValue; + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/Random.java b/ApacheJmeter/src/org/apache/jmeter/functions/Random.java new file mode 100644 index 0000000..5c32b82 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/Random.java @@ -0,0 +1,108 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Provides a Random function which returns a random long integer between a min + * (first argument) and a max (second argument). + * @since 1.9 + */ +public class Random extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__Random"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("minimum_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("maximum_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private CompoundVariable varName, minimum, maximum; + + /** + * No-arg constructor. + */ + public Random() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + + long min = Long.parseLong(minimum.execute().trim()); + long max = Long.parseLong(maximum.execute().trim()); + + long rand = min + (long) (Math.random() * (max - min + 1)); + + String randString = Long.toString(rand); + + if (varName != null) { + JMeterVariables vars = getVariables(); + final String varTrim = varName.execute().trim(); + if (vars != null && varTrim.length() > 0){// vars will be null on TestPlan + vars.put(varTrim, randString); + } + } + + return randString; + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 2, 3); + Object[] values = parameters.toArray(); + + minimum = (CompoundVariable) values[0]; + maximum = (CompoundVariable) values[1]; + if (values.length>2){ + varName = (CompoundVariable) values[2]; + } else { + varName = null; + } + + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/RandomString.java b/ApacheJmeter/src/org/apache/jmeter/functions/RandomString.java new file mode 100644 index 0000000..a0badcd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/RandomString.java @@ -0,0 +1,130 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Provides a RandomString function which returns a random String of length (first argument) + * using characters (second argument) + * @since 2.6 + */ +public class RandomString extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__RandomString"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("random_string_length")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("random_string_chars_to_use")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private CompoundVariable[] values; + + private static final int MAX_PARAM_COUNT = 3; + + private static final int MIN_PARAM_COUNT = 1; + + private static final int CHARS = 2; + + private static final int PARAM_NAME = 3; + + /** + * No-arg constructor. + */ + public RandomString() { + super(); + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + int length = Integer.parseInt(values[0].execute()); + + String charsToUse = null;//means no restriction + if (values.length >= CHARS) { + charsToUse = (values[CHARS - 1]).execute().trim(); + if (charsToUse.length() <= 0) { // empty chars, return to null + charsToUse = null; + } + } + + String myName = "";//$NON-NLS-1$ + if (values.length >= PARAM_NAME) { + myName = (values[PARAM_NAME - 1]).execute().trim(); + } + + String myValue = null; + if(StringUtils.isEmpty(charsToUse)) { + myValue = RandomStringUtils.random(length); + } else { + myValue = RandomStringUtils.random(length, charsToUse); + } + + if (myName.length() > 0) { + JMeterVariables vars = getVariables(); + if (vars != null) {// Can be null if called from Config item testEnded() method + vars.put(myName, myValue); + } + } + + if (log.isDebugEnabled()) { + String tn = Thread.currentThread().getName(); + log.debug(tn + " name:" //$NON-NLS-1$ + + myName + " value:" + myValue);//$NON-NLS-1$ + } + + return myValue; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAM_COUNT, MAX_PARAM_COUNT); + values = parameters.toArray(new CompoundVariable[parameters.size()]); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/RegexFunction.java b/ApacheJmeter/src/org/apache/jmeter/functions/RegexFunction.java new file mode 100644 index 0000000..66751a5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/RegexFunction.java @@ -0,0 +1,287 @@ +/* + * 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 java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcher; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Util; +/** + * Implements regular expression parsing of sample results and variables + * @since 1.X + */ + +// @see TestRegexFunction for unit tests + +public class RegexFunction extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String ALL = "ALL"; //$NON-NLS-1$ + + public static final String RAND = "RAND"; //$NON-NLS-1$ + + public static final String KEY = "__regexFunction"; //$NON-NLS-1$ + + private Object[] values;// Parameters are stored here + + private static final Random rand = new Random(); + + private static final List desc = new LinkedList(); + + private static final String TEMPLATE_PATTERN = "\\$(\\d+)\\$"; //$NON-NLS-1$ + /** initialised to the regex \$(\d+)\$ */ + private final Pattern templatePattern; + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 2; + + private static final int MAX_PARAMETER_COUNT = 7; + static { + desc.add(JMeterUtils.getResString("regexfunc_param_1"));// regex //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("regexfunc_param_2"));// template //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("regexfunc_param_3"));// which match //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("regexfunc_param_4"));// between text //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("regexfunc_param_5"));// default text //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); // output variable name //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("regexfunc_param_7"));// input variable //$NON-NLS-1$ + } + + public RegexFunction() { + templatePattern = JMeterUtils.getPatternCache().getPattern(TEMPLATE_PATTERN, + Perl5Compiler.READ_ONLY_MASK); + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String valueIndex = "", defaultValue = "", between = ""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + String name = ""; //$NON-NLS-1$ + String inputVariable = ""; //$NON-NLS-1$ + Pattern searchPattern; + Object[] tmplt; + try { + searchPattern = JMeterUtils.getPatternCache().getPattern(((CompoundVariable) values[0]).execute(), + Perl5Compiler.READ_ONLY_MASK); + tmplt = generateTemplate(((CompoundVariable) values[1]).execute()); + + if (values.length > 2) { + valueIndex = ((CompoundVariable) values[2]).execute(); + } + if (valueIndex.length() == 0) { + valueIndex = "1"; //$NON-NLS-1$ + } + + if (values.length > 3) { + between = ((CompoundVariable) values[3]).execute(); + } + + if (values.length > 4) { + String dv = ((CompoundVariable) values[4]).execute(); + if (dv.length() != 0) { + defaultValue = dv; + } + } + + if (values.length > 5) { + name = ((CompoundVariable) values[5]).execute(); + } + + if (values.length > 6) { + inputVariable = ((CompoundVariable) values[6]).execute(); + } + } catch (MalformedCachePatternException e) { + throw new InvalidVariableException(e.toString()); + } + + // Relatively expensive operation, so do it once + JMeterVariables vars = getVariables(); + + if (vars == null){// Can happen if called during test closedown + return defaultValue; + } + + if (name.length() > 0) { + vars.put(name, defaultValue); + } + + String textToMatch=null; + + if (inputVariable.length() > 0){ + textToMatch=vars.get(inputVariable); + } else if (previousResult != null){ + textToMatch = previousResult.getResponseDataAsString(); + } + + if (textToMatch == null || textToMatch.length() == 0) { + return defaultValue; + } + + List collectAllMatches = new ArrayList(); + try { + PatternMatcher matcher = JMeterUtils.getMatcher(); + PatternMatcherInput input = new PatternMatcherInput(textToMatch); + while (matcher.contains(input, searchPattern)) { + MatchResult match = matcher.getMatch(); + collectAllMatches.add(match); + } + } catch (NumberFormatException e) {//TODO: can this occur? + log.error("", e); //$NON-NLS-1$ + return defaultValue; + } finally { + if (name.length() > 0){ + vars.put(name + "_matchNr", "" + collectAllMatches.size()); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + if (collectAllMatches.size() == 0) { + return defaultValue; + } + + if (valueIndex.equals(ALL)) { + StringBuilder value = new StringBuilder(); + Iterator it = collectAllMatches.iterator(); + boolean first = true; + while (it.hasNext()) { + if (!first) { + value.append(between); + } else { + first = false; + } + value.append(generateResult(it.next(), name, tmplt, vars)); + } + return value.toString(); + } else if (valueIndex.equals(RAND)) { + MatchResult result = collectAllMatches.get(rand.nextInt(collectAllMatches.size())); + return generateResult(result, name, tmplt, vars); + } else { + try { + int index = Integer.parseInt(valueIndex) - 1; + MatchResult result = collectAllMatches.get(index); + return generateResult(result, name, tmplt, vars); + } catch (NumberFormatException e) { + float ratio = Float.parseFloat(valueIndex); + MatchResult result = collectAllMatches + .get((int) (collectAllMatches.size() * ratio + .5) - 1); + return generateResult(result, name, tmplt, vars); + } catch (IndexOutOfBoundsException e) { + return defaultValue; + } + } + + } + + private void saveGroups(MatchResult result, String namep, JMeterVariables vars) { + if (result != null) { + for (int x = 0; x < result.groups(); x++) { + vars.put(namep + "_g" + x, result.group(x)); //$NON-NLS-1$ + } + } + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + + private String generateResult(MatchResult match, String namep, Object[] template, JMeterVariables vars) { + saveGroups(match, namep, vars); + StringBuilder result = new StringBuilder(); + for (int a = 0; a < template.length; a++) { + if (template[a] instanceof String) { + result.append(template[a]); + } else { + result.append(match.group(((Integer) template[a]).intValue())); + } + } + if (namep.length() > 0){ + vars.put(namep, result.toString()); + } + return result.toString(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + private Object[] generateTemplate(String rawTemplate) { + List pieces = new ArrayList(); + // String or Integer + List combined = new LinkedList(); + PatternMatcher matcher = JMeterUtils.getMatcher(); + Util.split(pieces, matcher, templatePattern, rawTemplate); + PatternMatcherInput input = new PatternMatcherInput(rawTemplate); + boolean startsWith = isFirstElementGroup(rawTemplate); + if (startsWith) { + pieces.remove(0);// Remove initial empty entry + } + Iterator iter = pieces.iterator(); + while (iter.hasNext()) { + boolean matchExists = matcher.contains(input, templatePattern); + if (startsWith) { + if (matchExists) { + combined.add(Integer.valueOf(matcher.getMatch().group(1))); + } + combined.add(iter.next()); + } else { + combined.add(iter.next()); + if (matchExists) { + combined.add(Integer.valueOf(matcher.getMatch().group(1))); + } + } + } + if (matcher.contains(input, templatePattern)) { + combined.add(Integer.valueOf(matcher.getMatch().group(1))); + } + return combined.toArray(); + } + + private boolean isFirstElementGroup(String rawData) { + Pattern pattern = JMeterUtils.getPatternCache().getPattern("^\\$\\d+\\$", //$NON-NLS-1$ + Perl5Compiler.READ_ONLY_MASK); + return JMeterUtils.getMatcher().contains(rawData, pattern); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/SamplerName.java b/ApacheJmeter/src/org/apache/jmeter/functions/SamplerName.java new file mode 100644 index 0000000..8fe4181 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/SamplerName.java @@ -0,0 +1,87 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Function to return the name of the current sampler. + * @since 2.5 + */ +public class SamplerName extends AbstractFunction { + + private static final String KEY = "__samplerName"; //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + static { + // desc.add("Use fully qualified host name: TRUE/FALSE (Default FALSE)"); + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + } + + private Object[] values; + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + // return JMeterContextService.getContext().getCurrentSampler().getName(); + String name = ""; + if (currentSampler != null) { // will be null if function is used on TestPlan + name = currentSampler.getName(); + } + if (values.length > 0){ + JMeterVariables vars = getVariables(); + if (vars != null) {// May be null if function is used on TestPlan + String varName = ((CompoundVariable) values[0]).execute().trim(); + if (varName.length() > 0) { + vars.put(varName, name); + } + } + } + return name; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) + throws InvalidVariableException { + checkParameterCount(parameters, 0, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/SetProperty.java b/ApacheJmeter/src/org/apache/jmeter/functions/SetProperty.java new file mode 100644 index 0000000..2b765a7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/SetProperty.java @@ -0,0 +1,104 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Function to set a JMeter property + * + * Parameters: - property name - value + * + * Usage: + * + * Set the property value in the appropriate GUI by using the string: + * ${__setProperty(propname,propvalue[,returnvalue?])} + * + * Returns: nothing or original value if the 3rd parameter is true + * @since 2.1 + */ +public class SetProperty extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__setProperty"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 2; + + private static final int MAX_PARAMETER_COUNT = 3; + static { + desc.add(JMeterUtils.getResString("property_name_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("property_value_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("property_returnvalue_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public SetProperty() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String propertyName = ((CompoundVariable) values[0]).execute(); + + String propertyValue = ((CompoundVariable) values[1]).execute(); + + boolean returnValue = false;// should we return original value? + if (values.length > 2) { + returnValue = ((CompoundVariable) values[2]).execute().equalsIgnoreCase("true"); //$NON-NLS-1$ + } + + if (returnValue) { // Only obtain and cast the return if needed + return (String) JMeterUtils.setProperty(propertyName, propertyValue); + } else { + JMeterUtils.setProperty(propertyName, propertyValue); + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/SplitFunction.java b/ApacheJmeter/src/org/apache/jmeter/functions/SplitFunction.java new file mode 100644 index 0000000..3278727 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/SplitFunction.java @@ -0,0 +1,129 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +// @see org.apache.jmeter.functions.PackageTest for unit tests + +/** + * Function to split a string into variables + *

+ * Parameters: + *

    + *
  • String to split
  • + *
  • Variable name prefix
  • + *
  • String to split on (optional, default is comma)
  • + *
+ *

+ *

+ * Returns: the input string + *

+ * Also sets the variables: + *
    + *
  • VARNAME - the input string
  • + *
  • VARNAME_n - number of fields found
  • + *
  • VARNAME_1..n - fields
  • + *
+ * @since 2.0.2 + */ +public class SplitFunction extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final List desc = new LinkedList(); + + private static final String KEY = "__split";// $NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 2; + + private static final int MAX_PARAMETER_COUNT = 3; + static { + desc.add(JMeterUtils.getResString("split_function_string")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_param")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("split_function_separator"));//$NON-NLS-1$ + } + + private Object[] values; + + public SplitFunction() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + JMeterVariables vars = getVariables(); + + String stringToSplit = ((CompoundVariable) values[0]).execute(); + String varNamePrefix = ((CompoundVariable) values[1]).execute().trim(); + String splitString = ","; + + if (values.length > 2) { // Split string provided + splitString = ((CompoundVariable) values[2]).execute(); + } + if (log.isDebugEnabled()){ + log.debug("Split "+stringToSplit+ " using "+ splitString+ " into "+varNamePrefix); + } + String parts[] = JOrphanUtils.split(stringToSplit, splitString, "?");// $NON-NLS-1$ + + vars.put(varNamePrefix, stringToSplit); + vars.put(varNamePrefix + "_n", "" + parts.length);// $NON-NLS-1$ // $NON-NLS-2$ + for (int i = 1; i <= parts.length; i++) { + if (log.isDebugEnabled()){ + log.debug(parts[i-1]); + } + vars.put(varNamePrefix + "_" + i, parts[i - 1]);// $NON-NLS-1$ + } + vars.remove(varNamePrefix + "_" + (parts.length+1)); + return stringToSplit; + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/StringFromFile.java b/ApacheJmeter/src/org/apache/jmeter/functions/StringFromFile.java new file mode 100644 index 0000000..4e9c863 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/StringFromFile.java @@ -0,0 +1,337 @@ +/* + * 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 java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.log.Logger; + +/** + * StringFromFile Function to read a String from a text file. + * + * Parameters: + * - file name + * - variable name (optional - defaults to StringFromFile_) + * + * Returns: + * - the next line from the file + * - or **ERR** if an error occurs + * - value is also saved in the variable for later re-use. + * + * Ensure that different variable names are used for each call to the function + * + * + * Notes: + *
    + *
  • JMeter instantiates a single copy of each function for every reference in the test plan
  • + *
  • Function instances are shared between threads.
  • + *
  • Each StringFromFile instance reads the file independently. The output variable can be used to save the + * value for later use in the same thread.
  • + *
  • The file name is resolved at file (re-)open time; the file is initially opened on first execution (which could be any thread)
  • + *
  • the output variable name is resolved every time the function is invoked
  • + *
+ * Because function instances are shared, it does not make sense to use the thread number as part of the file name. + * @since 1.9 + */ +public class StringFromFile extends AbstractFunction implements TestListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Only modified by static block so no need to synchronize subsequent read-only access + private static final List desc = new LinkedList(); + + private static final String KEY = "__StringFromFile";//$NON-NLS-1$ + + static final String ERR_IND = "**ERR**";//$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("string_from_file_file_name"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("string_from_file_seq_start"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("string_from_file_seq_final"));//$NON-NLS-1$ + } + + private static final int MIN_PARAM_COUNT = 1; + + private static final int PARAM_NAME = 2; + + private static final int PARAM_START = 3; + + private static final int PARAM_END = 4; + + private static final int MAX_PARAM_COUNT = 4; + + private static final int COUNT_UNUSED = -2; + + // @GuardedBy("this") + private Object[] values; + + // @GuardedBy("this") + private BufferedReader myBread = null; // Buffered reader + + // @GuardedBy("this") + private boolean firstTime = false; // should we try to open the file? + + // @GuardedBy("this") + private String fileName; // needed for error messages + + // @GuardedBy("this") + private int myStart = COUNT_UNUSED; + + // @GuardedBy("this") + private int myCurrent = COUNT_UNUSED; + + // @GuardedBy("this") + private int myEnd = COUNT_UNUSED; + + public StringFromFile() { + if (log.isDebugEnabled()) { + log.debug("++++++++ Construct " + this); + } + } + + /** + * Close file and log + */ + private synchronized void closeFile() { + if (myBread == null) { + return; + } + String tn = Thread.currentThread().getName(); + log.info(tn + " closing file " + fileName);//$NON-NLS-1$ + try { + myBread.close(); + } catch (IOException e) { + log.error("closeFile() error: " + e.toString(), e);//$NON-NLS-1$ + } + } + + private synchronized void openFile() { + String tn = Thread.currentThread().getName(); + fileName = ((CompoundVariable) values[0]).execute(); + + String start = ""; + if (values.length >= PARAM_START) { + start = ((CompoundVariable) values[PARAM_START - 1]).execute(); + try { + myStart = Integer.valueOf(start).intValue(); + } catch (NumberFormatException e) { + myStart = COUNT_UNUSED;// Don't process invalid numbers + } + } + // Have we used myCurrent yet? + // Set to 1 if start number is missing (to allow for end without start) + if (myCurrent == COUNT_UNUSED) { + myCurrent = myStart == COUNT_UNUSED ? 1 : myStart; + } + + if (values.length >= PARAM_END) { + String tmp = ((CompoundVariable) values[PARAM_END - 1]).execute(); + try { + myEnd = Integer.valueOf(tmp).intValue(); + } catch (NumberFormatException e) { + myEnd = COUNT_UNUSED;// Don't process invalid numbers + // (including "") + } + + } + + if (values.length >= PARAM_START) { + log.info(tn + " Start = " + myStart + " Current = " + myCurrent + " End = " + myEnd);//$NON-NLS-1$ + if (myEnd != COUNT_UNUSED) { + if (myCurrent > myEnd) { + log.info(tn + " No more files to process, " + myCurrent + " > " + myEnd);//$NON-NLS-1$ + myBread = null; + return; + } + } + /* + * DecimalFormat adds the number to the end of the format if there + * are no formatting characters, so we need a way to prevent this + * from messing up the file name. + * + */ + if (myStart != COUNT_UNUSED) // Only try to format if there is a + // number + { + log.info(tn + " using format " + fileName); + try { + DecimalFormat myFormatter = new DecimalFormat(fileName); + fileName = myFormatter.format(myCurrent); + } catch (NumberFormatException e) { + log.warn("Bad file name format ", e); + } + } + myCurrent++;// for next time + } + + log.info(tn + " opening file " + fileName);//$NON-NLS-1$ + try { + myBread = new BufferedReader(new FileReader(fileName)); + } catch (Exception e) { + log.error("openFile() error: " + e.toString());//$NON-NLS-1$ + myBread = null; + } + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String myValue = ERR_IND; + String myName = "StringFromFile_";//$NON-NLS-1$ + if (values.length >= PARAM_NAME) { + myName = ((CompoundVariable) values[PARAM_NAME - 1]).execute().trim(); + } + + /* + * To avoid re-opening the file repeatedly after an error, only try to + * open it in the first execute() call (It may be re=opened at EOF, but + * that will cause at most one failure.) + */ + if (firstTime) { + openFile(); + firstTime = false; + } + + if (null != myBread) { // Did we open the file? + try { + String line = myBread.readLine(); + if (line == null) { // EOF, re-open file + String tn = Thread.currentThread().getName(); + log.info(tn + " EOF on file " + fileName);//$NON-NLS-1$ + closeFile(); + openFile(); + if (myBread != null) { + line = myBread.readLine(); + } else { + line = ERR_IND; + if (myEnd != COUNT_UNUSED) {// Are we processing a file + // sequence? + log.info(tn + " Detected end of sequence."); + throw new JMeterStopThreadException("End of sequence"); + } + } + } + myValue = line; + } catch (IOException e) { + String tn = Thread.currentThread().getName(); + log.error(tn + " error reading file " + e.toString());//$NON-NLS-1$ + } + } else { // File was not opened successfully + if (myEnd != COUNT_UNUSED) {// Are we processing a file sequence? + String tn = Thread.currentThread().getName(); + log.info(tn + " Detected end of sequence."); + throw new JMeterStopThreadException("End of sequence"); + } + } + + if (myName.length() > 0) { + JMeterVariables vars = getVariables(); + if (vars != null) {// Can be null if called from Config item testEnded() method + vars.put(myName, myValue); + } + } + + if (log.isDebugEnabled()) { + String tn = Thread.currentThread().getName(); + log.debug(tn + " name:" //$NON-NLS-1$ + + myName + " value:" + myValue);//$NON-NLS-1$ + } + + return myValue; + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + + log.debug(this + "::StringFromFile.setParameters()");//$NON-NLS-1$ + checkParameterCount(parameters, MIN_PARAM_COUNT, MAX_PARAM_COUNT); + values = parameters.toArray(); + + StringBuilder sb = new StringBuilder(40); + sb.append("setParameters(");//$NON-NLS-1$ + for (int i = 0; i < values.length; i++) { + if (i > 0) { + sb.append(","); + } + sb.append(((CompoundVariable) values[i]).getRawParameters()); + } + sb.append(")");//$NON-NLS-1$ + log.info(sb.toString()); + + // N.B. setParameters is called before the test proper is started, + // and thus variables are not interpreted at this point + // So defer the file open until later to allow variable file names to be + // used. + firstTime = true; + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + + /** {@inheritDoc} */ + public void testStarted() { + // + } + + /** {@inheritDoc} */ + public void testStarted(String host) { + // + } + + /** {@inheritDoc} */ + public void testEnded() { + this.testEnded(""); //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + public void testEnded(String host) { + closeFile(); + } + + /** {@inheritDoc} */ + public void testIterationStart(LoopIterationEvent event) { + // + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/TestPlanName.java b/ApacheJmeter/src/org/apache/jmeter/functions/TestPlanName.java new file mode 100644 index 0000000..26e522d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/TestPlanName.java @@ -0,0 +1,71 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.services.FileServer; + +/** + * Returns Test Plan name + * @since 2.6 + */ +public class TestPlanName extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__TestPlanName"; //$NON-NLS-1$ + + /** + * No-arg constructor. + */ + public TestPlanName() { + super(); + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String testPlanFile = FileServer.getFileServer().getScriptName(); + return testPlanFile; + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 0); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/ThreadNumber.java b/ApacheJmeter/src/org/apache/jmeter/functions/ThreadNumber.java new file mode 100644 index 0000000..d34db05 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/ThreadNumber.java @@ -0,0 +1,61 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; + +/** + * Function to return the current thread number. + * @since 1.X + */ +public class ThreadNumber extends AbstractFunction { + + private static final String KEY = "__threadNum"; //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException { + return Thread.currentThread().getName().substring(Thread.currentThread().getName().lastIndexOf("-") + 1); //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters,0,0); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/TimeFunction.java b/ApacheJmeter/src/org/apache/jmeter/functions/TimeFunction.java new file mode 100644 index 0000000..29c6c64 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/TimeFunction.java @@ -0,0 +1,130 @@ +/* + * 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 java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; + +// See org.apache.jmeter.functions.TestTimeFunction for unit tests + +/** + * __time() function - returns the current time in milliseconds + * @since 2.2 + */ +public class TimeFunction extends AbstractFunction { + + private static final String KEY = "__time"; // $NON-NLS-1$ + + private static final List desc = new LinkedList(); + + // Only modified in class init + private static final Map aliases = new HashMap(); + + static { + desc.add(JMeterUtils.getResString("time_format")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$ + aliases.put("YMD", //$NON-NLS-1$ + JMeterUtils.getPropDefault("time.YMD", //$NON-NLS-1$ + "yyyyMMdd")); //$NON-NLS-1$ + aliases.put("HMS", //$NON-NLS-1$ + JMeterUtils.getPropDefault("time.HMS", //$NON-NLS-1$ + "HHmmss")); //$NON-NLS-1$ + aliases.put("YMDHMS", //$NON-NLS-1$ + JMeterUtils.getPropDefault("time.YMDHMS", //$NON-NLS-1$ + "yyyyMMdd-HHmmss")); //$NON-NLS-1$ + aliases.put("USER1", //$NON-NLS-1$ + JMeterUtils.getPropDefault("time.USER1","")); //$NON-NLS-1$ + aliases.put("USER2", //$NON-NLS-1$ + JMeterUtils.getPropDefault("time.USER2","")); //$NON-NLS-1$ + } + + // Ensure that these are set, even if no paramters are provided + private String format = ""; //$NON-NLS-1$ + private String variable = ""; //$NON-NLS-1$ + + public TimeFunction(){ + super(); + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException { + String datetime; + if (format.length() == 0){// Default to milliseconds + datetime = Long.toString(System.currentTimeMillis()); + } else { + // Resolve any aliases + String fmt = aliases.get(format); + if (fmt == null) { + fmt = format;// Not found + } + SimpleDateFormat df = new SimpleDateFormat(fmt);// Not synchronised, so can't be shared + datetime = df.format(new Date()); + } + + if (variable.length() > 0) { + JMeterVariables vars = getVariables(); + if (vars != null){// vars will be null on TestPlan + vars.put(variable, datetime); + } + } + return datetime; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + + checkParameterCount(parameters, 0, 2); + + Object []values = parameters.toArray(); + int count = values.length; + + if (count > 0) { + format = ((CompoundVariable) values[0]).execute(); + } + + if (count > 1) { + variable = ((CompoundVariable)values[1]).execute().trim(); + } + + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/UnEscape.java b/ApacheJmeter/src/org/apache/jmeter/functions/UnEscape.java new file mode 100644 index 0000000..61b0c47 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/UnEscape.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.functions; + + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Function to unescape any Java literals found in the String. + * For example, it will turn a sequence of '\' and 'n' into a newline character, + * unless the '\' is preceded by another '\'. + * + * @see StringEscapeUtils#unescapeJava(String) + * @since 2.3.3 + */ +public class UnEscape extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__unescape"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("unescape_string")); //$NON-NLS-1$ + } + + private Object[] values; + + public UnEscape() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + String rawString = ((CompoundVariable) values[0]).execute(); + return StringEscapeUtils.unescapeJava(rawString); + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/UnEscapeHtml.java b/ApacheJmeter/src/org/apache/jmeter/functions/UnEscapeHtml.java new file mode 100644 index 0000000..bdb5487 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/UnEscapeHtml.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.functions; + + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Function to unescape a string containing entity escapes + * to a string containing the actual Unicode characters corresponding to the escapes. + * Supports HTML 4.0 entities. + *

+ * For example, the string "&lt;Fran&ccedil;ais&gt;" will become "<Français>" + *

+ *

+ * If an entity is unrecognized, it is left alone, and inserted verbatim into the result string. + * e.g. "&gt;&zzzz;x" will become ">&zzzz;x". + *

+ * @see org.apache.commons.lang.StringEscapeUtils#unescapeHtml(String) + * @since 2.3.3 + */ +public class UnEscapeHtml extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__unescapeHtml"; //$NON-NLS-1$ + + static { + desc.add(JMeterUtils.getResString("unescape_html_string")); //$NON-NLS-1$ + } + + private Object[] values; + + public UnEscapeHtml() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + + String escapedString = ((CompoundVariable) values[0]).execute(); + return StringEscapeUtils.unescapeHtml(escapedString); + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, 1); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/Variable.java b/ApacheJmeter/src/org/apache/jmeter/functions/Variable.java new file mode 100644 index 0000000..5cb551e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/Variable.java @@ -0,0 +1,89 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Function to get a JMeter Variable + * + * Parameters: + * - variable name + * + * Returns: + * - the variable value, but if not found + * - the variable name itself + * @since 2.3RC3 + */ +public class Variable extends AbstractFunction { + + private static final List desc = new LinkedList(); + + private static final String KEY = "__V"; //$NON-NLS-1$ + + // Number of parameters expected - used to reject invalid calls + private static final int MIN_PARAMETER_COUNT = 1; + private static final int MAX_PARAMETER_COUNT = 1; + + static { + desc.add(JMeterUtils.getResString("variable_name_param")); //$NON-NLS-1$ + } + + private Object[] values; + + public Variable() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String variableName = ((CompoundVariable) values[0]).execute(); + String variableValue = getVariables().get(variableName); + return variableValue == null? variableName : variableValue; + + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/XPath.java b/ApacheJmeter/src/org/apache/jmeter/functions/XPath.java new file mode 100644 index 0000000..5cfda4e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/XPath.java @@ -0,0 +1,125 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +// @see org.apache.jmeter.functions.PackageTest for unit tests + +/** + * The function represented by this class allows data to be read from XML files. + * Syntax is similar to the CVSRead function. The function allows the test to + * line-thru the nodes in the XML file - one node per each test. E.g. inserting + * the following in the test scripts : + * + * ${_XPath(c:/BOF/abcd.xml,/xpath/)} // match the (first) node + * ${_XPath(c:/BOF/abcd.xml,/xpath/)} // Go to next match of '/xpath/' expression + * + * NOTE: A single instance of each different file/expression combination + * is opened and used for all threads. + * @since 2.0.3 + */ +public class XPath extends AbstractFunction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // static { + // LoggingManager.setPriority("DEBUG","jmeter"); + // LoggingManager.setTarget(new java.io.PrintWriter(System.out)); + // } + private static final String KEY = "__XPath"; // Function name //$NON-NLS-1$ + + private static final List desc = new LinkedList(); + + private Object[] values; // Parameter list + + static { + desc.add(JMeterUtils.getResString("xpath_file_file_name")); //$NON-NLS-1$ + desc.add(JMeterUtils.getResString("xpath_expression")); //$NON-NLS-1$ + } + + public XPath() { + } + + /** {@inheritDoc} */ + @Override + public synchronized String execute(SampleResult previousResult, Sampler currentSampler) + throws InvalidVariableException { + String myValue = ""; //$NON-NLS-1$ + + String fileName = ((CompoundVariable) values[0]).execute(); + String xpathString = ((CompoundVariable) values[1]).execute(); + + if (log.isDebugEnabled()){ + log.debug("execute (" + fileName + " " + xpathString + ") "); + } + + myValue = XPathWrapper.getXPathString(fileName, xpathString); + + if (log.isDebugEnabled()){ + log.debug("execute value: " + myValue); + } + + return myValue; + } + + /** {@inheritDoc} */ + public List getArgumentDesc() { + return desc; + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setParameters(Collection parameters) throws InvalidVariableException { + log.debug("setParameter - Collection.size=" + parameters.size()); + + values = parameters.toArray(); + + if (log.isDebugEnabled()) { + for (int i = 0; i < parameters.size(); i++) { + log.debug("i:" + ((CompoundVariable) values[i]).execute()); + } + } + + checkParameterCount(parameters, 2); + + /* + * Need to reset the containers for repeated runs; about the only way + * for functions to detect that a run is starting seems to be the + * setParameters() call. + */ + XPathWrapper.clearAll();// TODO only clear the relevant entry - if possible... + + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/XPathFileContainer.java b/ApacheJmeter/src/org/apache/jmeter/functions/XPathFileContainer.java new file mode 100644 index 0000000..e347710 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/XPathFileContainer.java @@ -0,0 +1,134 @@ +/* + * 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 java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.jmeter.util.XPathUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +//@see org.apache.jmeter.functions.PackageTest for unit tests + +/** + * File data container for XML files Data is accessible via XPath + * + */ +public class XPathFileContainer { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final NodeList nodeList; + + private final String fileName; // name of the file + + private final String xpath; + + /** Keeping track of which row is next to be read. */ + private int nextRow;// probably does not need to be synch (always accessed through ThreadLocal?) + int getNextRow(){// give access to Test code + return nextRow; + } + + public XPathFileContainer(String file, String xpath) throws FileNotFoundException, IOException, + ParserConfigurationException, SAXException, TransformerException { + log.debug("XPath(" + file + ") xpath " + xpath + ""); + fileName = file; + this.xpath = xpath; + nextRow = 0; + nodeList=load(); + } + + private NodeList load() throws IOException, FileNotFoundException, ParserConfigurationException, SAXException, + TransformerException { + InputStream fis = null; + NodeList nl = null; + try { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + fis = new FileInputStream(fileName); + nl = XPathUtil.selectNodeList(builder.parse(fis), xpath); + log.debug("found " + nl.getLength()); + + } catch (FileNotFoundException e) { + log.warn(e.toString()); + throw e; + } catch (IOException e) { + log.warn(e.toString()); + throw e; + } catch (ParserConfigurationException e) { + log.warn(e.toString()); + throw e; + } catch (SAXException e) { + log.warn(e.toString()); + throw e; + } catch (TransformerException e) { + log.warn(e.toString()); + throw e; + } finally { + JOrphanUtils.closeQuietly(fis); + } + return nl; + } + + public String getXPathString(int num) { + return nodeList.item(num).getNodeValue(); + } + + /** + * Returns the next row to the caller, and updates it, allowing for wrap + * round + * + * @return the first free (unread) row + * + */ + public int nextRow() { + int row = nextRow; + nextRow++; + if (nextRow >= size())// 0-based + { + nextRow = 0; + } + log.debug(new StringBuilder("Row: ").append(row).toString()); + return row; + } + + public int size() { + return (nodeList == null) ? -1 : nodeList.getLength(); + } + + /** + * @return the file name for this class + */ + public String getFileName() { + return fileName; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/XPathWrapper.java b/ApacheJmeter/src/org/apache/jmeter/functions/XPathWrapper.java new file mode 100644 index 0000000..79bcbf1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/XPathWrapper.java @@ -0,0 +1,133 @@ +/* + * 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 java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.xml.sax.SAXException; + +/** + * This class wraps the XPathFileContainer for use across multiple threads. + * + * It maintains a list of nodelist containers, one for each file/xpath combination + * + */ +public class XPathWrapper { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /* + * This Map serves two purposes: + * - maps names to containers + * - ensures only one container per file across all threads + * The key is the concatenation of the file name and the XPath string + */ + //@GuardedBy("fileContainers") + private static final Map fileContainers = + new HashMap(); + + /* The cache of file packs - for faster local access */ + private static final ThreadLocal> filePacks = + new ThreadLocal>() { + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + private XPathWrapper() {// Prevent separate instantiation + super(); + } + + private static XPathFileContainer open(String file, String xpathString) { + String tname = Thread.currentThread().getName(); + log.info(tname+": Opening " + file); + XPathFileContainer frcc=null; + try { + frcc = new XPathFileContainer(file, xpathString); + } catch (FileNotFoundException e) { + log.warn(e.getLocalizedMessage()); + } catch (IOException e) { + log.warn(e.getLocalizedMessage()); + } catch (ParserConfigurationException e) { + log.warn(e.getLocalizedMessage()); + } catch (SAXException e) { + log.warn(e.getLocalizedMessage()); + } catch (TransformerException e) { + log.warn(e.getLocalizedMessage()); + } + return frcc; + } + + /** + * Not thread-safe - must be called from a synchronized method. + * + * @param file + * @param xpathString + * @return the next row from the file container + */ + public static String getXPathString(String file, String xpathString) { + Map my = filePacks.get(); + String key = file+xpathString; + XPathFileContainer xpfc = my.get(key); + if (xpfc == null) // We don't have a local copy + { + synchronized(fileContainers){ + xpfc = fileContainers.get(key); + if (xpfc == null) { // There's no global copy either + xpfc=open(file, xpathString); + } + if (xpfc != null) { + fileContainers.put(key, xpfc);// save the global copy + } + } + // TODO improve the error handling + if (xpfc == null) { + log.error("XPathFileContainer is null!"); + return ""; //$NON-NLS-1$ + } + my.put(key,xpfc); // save our local copy + } + if (xpfc.size()==0){ + log.warn("XPathFileContainer has no nodes: "+file+" "+xpathString); + return ""; //$NON-NLS-1$ + } + int currentRow = xpfc.nextRow(); + log.debug("getting match number " + currentRow); + return xpfc.getXPathString(currentRow); + } + + public static void clearAll() { + log.debug("clearAll()"); + filePacks.get().clear(); + String tname = Thread.currentThread().getName(); + log.info(tname+": clearing container"); + synchronized (fileContainers) { + fileContainers.clear(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/gui/FunctionHelper.java b/ApacheJmeter/src/org/apache/jmeter/functions/gui/FunctionHelper.java new file mode 100644 index 0000000..a15a99f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/gui/FunctionHelper.java @@ -0,0 +1,148 @@ +/* + * 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.gui; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.functions.Function; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.Help; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.JLabeledTextField; + +public class FunctionHelper extends JDialog implements ActionListener, ChangeListener, LocaleChangeListener { + private static final long serialVersionUID = 240L; + + private JLabeledChoice functionList; + + private ArgumentsPanel parameterPanel; + + private JLabeledTextField cutPasteFunction; + + private JButton generateButton; + + public FunctionHelper() { + super((JFrame) null, JMeterUtils.getResString("function_helper_title"), false); //$NON-NLS-1$ + init(); + JMeterUtils.addLocaleChangeListener(this); + } + + private void init() { + parameterPanel = new ArgumentsPanel(JMeterUtils.getResString("function_params"), false); //$NON-NLS-1$ + initializeFunctionList(); + this.getContentPane().setLayout(new BorderLayout(10, 10)); + JPanel comboPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + comboPanel.add(functionList); + JButton helpButton = new JButton(JMeterUtils.getResString("help")); //$NON-NLS-1$ + helpButton.addActionListener(new HelpListener()); + comboPanel.add(helpButton); + this.getContentPane().add(comboPanel, BorderLayout.NORTH); + this.getContentPane().add(parameterPanel, BorderLayout.CENTER); + JPanel resultsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + cutPasteFunction = new JLabeledTextField(JMeterUtils.getResString("cut_paste_function"), 35); //$NON-NLS-1$ + resultsPanel.add(cutPasteFunction); + generateButton = new JButton(JMeterUtils.getResString("generate")); //$NON-NLS-1$ + generateButton.addActionListener(this); + resultsPanel.add(generateButton); + this.getContentPane().add(resultsPanel, BorderLayout.SOUTH); + this.pack(); + ComponentUtil.centerComponentInWindow(this); + } + + private void initializeFunctionList() { + functionList = new JLabeledChoice(JMeterUtils.getResString("choose_function"), CompoundVariable.getFunctionNames()); //$NON-NLS-1$ + functionList.addChangeListener(this); + } + + public void stateChanged(ChangeEvent event) { + try { + Arguments args = new Arguments(); + Function function = CompoundVariable.getFunctionClass(functionList.getText()).newInstance(); + List argumentDesc = function.getArgumentDesc(); + for (String help : argumentDesc) { + args.addArgument(help, ""); //$NON-NLS-1$ + } + parameterPanel.configure(args); + parameterPanel.revalidate(); + getContentPane().remove(parameterPanel); + this.pack(); + getContentPane().add(parameterPanel, BorderLayout.CENTER); + this.pack(); + this.validate(); + this.repaint(); + } catch (InstantiationException e) { + } catch (IllegalAccessException e) { + } + } + + public void actionPerformed(ActionEvent e) { + StringBuilder functionCall = new StringBuilder("${"); + functionCall.append(functionList.getText()); + Arguments args = (Arguments) parameterPanel.createTestElement(); + if (args.getArguments().size() > 0) { + functionCall.append("("); + PropertyIterator iter = args.iterator(); + boolean first = true; + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + if (!first) { + functionCall.append(","); + } + functionCall.append(arg.getValue()); + first = false; + } + functionCall.append(")"); + } + functionCall.append("}"); + cutPasteFunction.setText(functionCall.toString()); + } + + private class HelpListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + String[] source = new String[] { Help.HELP_FUNCTIONS, functionList.getText() }; + ActionEvent helpEvent = new ActionEvent(source, e.getID(), "help"); //$NON-NLS-1$ + ActionRouter.getInstance().actionPerformed(helpEvent); + } + } + + public void localeChanged(LocaleChangeEvent event) { + setTitle(JMeterUtils.getResString("function_helper_title")); + this.getContentPane().removeAll(); // so we can add them again in init + init(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/util/ArgumentDecoder.java b/ApacheJmeter/src/org/apache/jmeter/functions/util/ArgumentDecoder.java new file mode 100644 index 0000000..11cfa79 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/util/ArgumentDecoder.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.functions.util; + +import org.apache.oro.text.perl.Perl5Util; + +/** + * Decodes an Argument by replacing '\x' with 'x' + */ +public final class ArgumentDecoder { + private static final Perl5Util util = new Perl5Util(); + + private static final String expression = "s#[\\\\](.)#$1#g"; // $NON-NLS-1$ + +// TODO does not appear to be used + public static String decode(String s) { + return util.substitute(expression, s); + } + + /** + * Prevent instantiation of utility class. + */ + private ArgumentDecoder() { + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/functions/util/ArgumentEncoder.java b/ApacheJmeter/src/org/apache/jmeter/functions/util/ArgumentEncoder.java new file mode 100644 index 0000000..c549722 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/functions/util/ArgumentEncoder.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.functions.util; + +import org.apache.oro.text.perl.Perl5Util; + +/** + * Encode an Argument + */ +public final class ArgumentEncoder { + private static final Perl5Util util = new Perl5Util(); + + private static final String expression = "s#([${}(),\\\\])#\\$1#g"; + + // TODO does not appear to be used + public static String encode(String s) { + return util.substitute(expression, s); + } + + /** + * Prevent instantiation of utility class. + */ + private ArgumentEncoder() { + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/AbstractJMeterGuiComponent.java b/ApacheJmeter/src/org/apache/jmeter/gui/AbstractJMeterGuiComponent.java new file mode 100644 index 0000000..c601d1f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/AbstractJMeterGuiComponent.java @@ -0,0 +1,337 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Font; +import java.util.Locale; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.border.Border; + +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Printable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This abstract class takes care of the most basic functions necessary to + * create a viable JMeter GUI component. It extends JPanel and implements + * JMeterGUIComponent. This abstract class is, in turn, extended by several + * other abstract classes that create different classes of GUI components for + * JMeter (Visualizers, Timers, Samplers, Modifiers, Controllers, etc). + * + * @see org.apache.jmeter.gui.JMeterGUIComponent + * @see org.apache.jmeter.config.gui.AbstractConfigGui + * @see org.apache.jmeter.assertions.gui.AbstractAssertionGui + * @see org.apache.jmeter.control.gui.AbstractControllerGui + * @see org.apache.jmeter.timers.gui.AbstractTimerGui + * @see org.apache.jmeter.visualizers.gui.AbstractVisualizer + * @see org.apache.jmeter.samplers.gui.AbstractSamplerGui + * + */ +public abstract class AbstractJMeterGuiComponent extends JPanel implements JMeterGUIComponent, Printable { + private static final long serialVersionUID = 240L; + + /** Logging */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** Flag indicating whether or not this component is enabled. */ + private boolean enabled = true; + + /** A GUI panel containing the name of this component. */ + protected NamePanel namePanel; + // used by AbstractReportGui + + private CommentPanel commentPanel; + + /** + * When constructing a new component, this takes care of basic tasks like + * setting up the Name Panel and assigning the class's static label as the + * name to start. + */ + public AbstractJMeterGuiComponent() { + namePanel = new NamePanel(); + commentPanel=new CommentPanel(); + initGui(); + } + + /** + * Provides a default implementation for setting the name property. It's unlikely + * developers will need to override. + */ + @Override + public void setName(String name) { + namePanel.setName(name); + } + + /** + * Provides a default implementation for setting the comment property. It's unlikely + * developers will need to override. + */ + public void setComment(String comment) { + commentPanel.setText(comment); + } + + /** + * Provides a default implementation for the enabled property. It's unlikely + * developers will need to override. + */ + @Override + public boolean isEnabled() { + return enabled; + } + + /** + * Provides a default implementation for the enabled property. It's unlikely + * developers will need to override. + */ + @Override + public void setEnabled(boolean e) { + log.debug("Setting enabled: " + e); + enabled = e; + } + + /** + * Provides a default implementation for the name property. It's unlikely + * developers will need to override. + */ + @Override + public String getName() { + if (getNamePanel() != null) { + return getNamePanel().getName(); + } + return ""; // $NON-NLS-1$ + } + + /** + * Provides a default implementation for the comment property. It's unlikely + * developers will need to override. + */ + public String getComment() { + if (getCommentPanel() != null) { + return getCommentPanel().getText(); + } + return ""; // $NON-NLS-1$ + } + + /** + * Provides the Name Panel for extending classes. Extending classes are free + * to place it as desired within the component, or not at all. Most + * components place the NamePanel automatically by calling + * {@link #makeTitlePanel()} instead of directly calling this method. + * + * @return a NamePanel containing the name of this component + */ + protected NamePanel getNamePanel() { + return namePanel; + } + + private CommentPanel getCommentPanel(){ + return commentPanel; + } + /** + * Provides a label containing the title for the component. Subclasses + * typically place this label at the top of their GUI. The title is set to + * the name returned from the component's + * {@link JMeterGUIComponent#getStaticLabel() getStaticLabel()} method. Most + * components place this label automatically by calling + * {@link #makeTitlePanel()} instead of directly calling this method. + * + * @return a JLabel which subclasses can add to their GUI + */ + protected Component createTitleLabel() { + JLabel titleLabel = new JLabel(getStaticLabel()); + Font curFont = titleLabel.getFont(); + titleLabel.setFont(curFont.deriveFont((float) curFont.getSize() + 4)); + return titleLabel; + } + + /** + * A newly created gui component can be initialized with the contents of a + * Test Element object by calling this method. The component is responsible + * for querying the Test Element object for the relevant information to + * display in its GUI. + *

+ * AbstractJMeterGuiComponent provides a partial implementation of this + * method, setting the name of the component and its enabled status. + * Subclasses should override this method, performing their own + * configuration as needed, but also calling this super-implementation. + * + * @param element + * the TestElement to configure + */ + public void configure(TestElement element) { + setName(element.getName()); + if (element.getProperty(TestElement.ENABLED) instanceof NullProperty) { + enabled = true; + } else { + enabled = element.getPropertyAsBoolean(TestElement.ENABLED); + } + getCommentPanel().setText(element.getComment()); + } + + /** + * Provides a default implementation that resets the name field to the value of + * getStaticLabel(), reset comment and sets enabled to true. Your GUI may need more things + * cleared, in which case you should override, clear the extra fields, and + * still call super.clearGui(). + */ + public void clearGui() { + initGui(); + enabled = true; + } + + // helper method - also used by constructor + private void initGui() { + setName(getStaticLabel()); + commentPanel.clearGui(); + } + + /** + * This provides a convenience for extenders when they implement the + * {@link JMeterGUIComponent#modifyTestElement(TestElement)} method. This + * method will set the name, gui class, and test class for the created Test + * Element. It should be called by every extending class when + * creating/modifying Test Elements, as that will best assure consistent + * behavior. + * + * @param mc + * the TestElement being created. + */ + protected void configureTestElement(TestElement mc) { + mc.setName(getName()); + + mc.setProperty(new StringProperty(TestElement.GUI_CLASS, this.getClass().getName())); + + mc.setProperty(new StringProperty(TestElement.TEST_CLASS, mc.getClass().getName())); + + // This stores the state of the TestElement + log.debug("setting element to enabled: " + enabled); + mc.setProperty(new BooleanProperty(TestElement.ENABLED, enabled)); + mc.setComment(getComment()); + } + + /** + * Create a standard title section for JMeter components. This includes the + * title for the component and the Name Panel allowing the user to change + * the name for the component. This method is typically added to the top of + * the component at the beginning of the component's init method. + * + * @return a panel containing the component title and name panel + */ + protected Container makeTitlePanel() { + VerticalPanel titlePanel = new VerticalPanel(); + titlePanel.add(createTitleLabel()); + VerticalPanel contentPanel = new VerticalPanel(); + contentPanel.setBorder(BorderFactory.createEtchedBorder()); + contentPanel.add(getNamePanel()); + contentPanel.add(getCommentPanel()); + titlePanel.add(contentPanel); + return titlePanel; + } + + /** + * Create a top-level Border which can be added to JMeter components. + * Components typically set this as their border in their init method. It + * simply provides a nice spacing between the GUI components used and the + * edges of the window in which they appear. + * + * @return a Border for JMeter components + */ + protected Border makeBorder() { + return BorderFactory.createEmptyBorder(10, 10, 5, 10); + } + + /** + * Create a scroll panel that sets it's preferred size to it's minimum size. + * Explicitly for scroll panes that live inside other scroll panes, or + * within containers that stretch components to fill the area they exist in. + * Use this for any component you would put in a scroll pane (such as + * TextAreas, tables, JLists, etc). It is here for convenience and to avoid + * duplicate code. JMeter displays best if you follow this custom. + * + * @param comp + * the component which should be placed inside the scroll pane + * @return a JScrollPane containing the specified component + */ + protected JScrollPane makeScrollPane(Component comp) { + JScrollPane pane = new JScrollPane(comp); + pane.setPreferredSize(pane.getMinimumSize()); + return pane; + } + + /** + * Create a scroll panel that sets it's preferred size to it's minimum size. + * Explicitly for scroll panes that live inside other scroll panes, or + * within containers that stretch components to fill the area they exist in. + * Use this for any component you would put in a scroll pane (such as + * TextAreas, tables, JLists, etc). It is here for convenience and to avoid + * duplicate code. JMeter displays best if you follow this custom. + * + * @see javax.swing.ScrollPaneConstants + * + * @param comp + * the component which should be placed inside the scroll pane + * @param verticalPolicy + * the vertical scroll policy + * @param horizontalPolicy + * the horizontal scroll policy + * @return a JScrollPane containing the specified component + */ + protected JScrollPane makeScrollPane(Component comp, int verticalPolicy, int horizontalPolicy) { + JScrollPane pane = new JScrollPane(comp, verticalPolicy, horizontalPolicy); + pane.setPreferredSize(pane.getMinimumSize()); + return pane; + } + + public String getStaticLabel() { + return JMeterUtils.getResString(getLabelResource()); + } + + /** + * Compute Anchor value to find reference in documentation for a particular component + * @return String anchor + */ + public String getDocAnchor() { + // Ensure we use default bundle + String label = JMeterUtils.getResString(getLabelResource(), new Locale("","")); + return label.replace(' ', '_'); + } + + /** + * Subclasses need to over-ride this method, if they wish to return + * something other than the Visualizer itself. + * + * @return this object + */ + public JComponent getPrintableComponent() { + return this; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/AbstractScopedJMeterGuiComponent.java b/ApacheJmeter/src/org/apache/jmeter/gui/AbstractScopedJMeterGuiComponent.java new file mode 100644 index 0000000..1880ce0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/AbstractScopedJMeterGuiComponent.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import javax.swing.JPanel; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.AbstractScopedTestElement; +import org.apache.jmeter.util.ScopePanel; + + +public abstract class AbstractScopedJMeterGuiComponent extends AbstractJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + private ScopePanel scopePanel; + + @Override + public void clearGui(){ + super.clearGui(); + if (scopePanel != null) { + scopePanel.clearGui(); + } + } + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most assertion + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultAssertionMenu(); + } + + /** + * Create the scope settings panel. + * + * @return the scope settings panel + */ + protected JPanel createScopePanel() { + return createScopePanel(false); + } + + /** + * Create the scope settings panel. + * @param enableVariable set true to enable the variable panel + * @return the scope settings panel + */ + protected JPanel createScopePanel(boolean enableVariable) { + scopePanel = new ScopePanel(enableVariable); + return scopePanel; + } + + /** + * Save the scope settings in the test element. + * + * @param testElement + */ + protected void saveScopeSettings(AbstractScopedTestElement testElement) { + if (scopePanel.isScopeParent()){ + testElement.setScopeParent(); + } else if (scopePanel.isScopeChildren()){ + testElement.setScopeChildren(); + } else if (scopePanel.isScopeAll()) { + testElement.setScopeAll(); + } else if (scopePanel.isScopeVariable()) { + testElement.setScopeVariable(scopePanel.getVariable()); + } else { + throw new IllegalArgumentException("Unexpected scope panel state"); + } + } + + /** + * Show the scope settings from the test element. + * + * @param testElement + */ + protected void showScopeSettings(AbstractScopedTestElement testElement) { + showScopeSettings(testElement, false); + } + + /** + * Show the scope settings from the test element with variable scope + * + * @param testElement + * @param enableVariableButton + */ + protected void showScopeSettings(AbstractScopedTestElement testElement, + boolean enableVariableButton) { + String scope = testElement.fetchScope(); + if (testElement.isScopeParent(scope)) { + scopePanel.setScopeParent(enableVariableButton); + } else if (testElement.isScopeChildren(scope)){ + scopePanel.setScopeChildren(enableVariableButton); + } else if (testElement.isScopeAll(scope)){ + scopePanel.setScopeAll(enableVariableButton); + } else if (testElement.isScopeVariable(scope)){ + scopePanel.setScopeVariable(testElement.getVariableName()); + } else { + throw new IllegalArgumentException("Invalid scope: "+scope); + } + } + + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/CommentPanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/CommentPanel.java new file mode 100644 index 0000000..74d308e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/CommentPanel.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.awt.BorderLayout; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextArea; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Generic comment panel for Test Elements + * + */ +public class CommentPanel extends JPanel { + private static final long serialVersionUID = 240L; + + /** A text field containing the comment. */ + private JTextArea commentField; + + /** + * Create a new NamePanel with the default name. + */ + public CommentPanel() { + init(); + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout(5, 0)); + + commentField = new JTextArea(); + JLabel commentLabel = new JLabel(JMeterUtils.getResString("testplan_comments")); //$NON-NLS-1$ + commentLabel.setLabelFor(commentField); + + JPanel commentPanel = new JPanel(); + commentPanel.setLayout(new BorderLayout(0, 5)); + commentPanel.add(commentLabel,BorderLayout.WEST); + commentPanel.add(commentField,BorderLayout.CENTER); + add(commentPanel); + } + + public void setText(String comment) { + this.commentField.setText(comment); + } + + public String getText() { + return this.commentField.getText(); + } + + public void clearGui() { + commentField.setText(""); // $NON-NLS-1$ + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/GUIFactory.java b/ApacheJmeter/src/org/apache/jmeter/gui/GUIFactory.java new file mode 100644 index 0000000..c6e6d21 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/GUIFactory.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.util.HashMap; +import java.util.Map; + +import javax.swing.ImageIcon; +import javax.swing.JComponent; + +import org.apache.jmeter.testbeans.gui.TestBeanGUI; + +/** + * Provides a way to register and retrieve GUI classes and icons. + * + */ +public final class GUIFactory { + /** A Map from String to JMeterGUIComponent of registered GUI classes. */ + private static final Map GUI_MAP = new HashMap(); + + /** A Map from String to ImageIcon of registered icons. */ + private static final Map ICON_MAP = new HashMap(); + + /** A Map from String to ImageIcon of registered icons. */ + private static final Map DISABLED_ICON_MAP = new HashMap(); + + /** + * Prevent instantiation since this is a static utility class. + */ + private GUIFactory() { + } + + /** + * Get an icon which has previously been registered for this class object. + * + * @param elementClass + * the class object which we want to get an icon for + * + * @return the associated icon, or null if this class or its superclass has + * not been registered + */ + public static ImageIcon getIcon(Class elementClass) { + return getIcon(elementClass, true); + + } + + /** + * Get icon/disabledicon which has previously been registered for this class + * object. + * + * @param elementClass + * the class object which we want to get an icon for + * @param enabled - + * is icon enabled + * + * @return the associated icon, or null if this class or its superclass has + * not been registered + */ + public static ImageIcon getIcon(Class elementClass, boolean enabled) { + String key = elementClass.getName(); + ImageIcon icon = (enabled ? ICON_MAP.get(key) : DISABLED_ICON_MAP.get(key)); + + if (icon != null) { + return icon; + } + + if (elementClass.getSuperclass() != null) { + return getIcon(elementClass.getSuperclass(), enabled); + } + + return null; + } + + /** + * Get a component instance which has previously been registered for this + * class object. + * + * @param elementClass + * the class object which we want to get an instance of + * + * @return an instance of the class, or null if this class or its superclass + * has not been registered + */ + public static JComponent getGUI(Class elementClass) { + // TODO: This method doesn't appear to be used. + String key = elementClass.getName(); + JComponent gui = (JComponent) GUI_MAP.get(key); + + if (gui != null) { + return gui; + } + + if (elementClass.getSuperclass() != null) { + return getGUI(elementClass.getSuperclass()); + } + + return null; + } + + /** + * Register an icon so that it can later be retrieved via + * {@link #getIcon(Class)}. The key should match the fully-qualified class + * name for the class used as the parameter when retrieving the icon. + * + * @param key + * the name which can be used to retrieve this icon later + * @param icon + * the icon to store + */ + public static void registerIcon(String key, ImageIcon icon) { + ICON_MAP.put(key, icon); + } + + /** + * Register an icon so that it can later be retrieved via + * {@link #getIcon(Class)}. The key should match the fully-qualified class + * name for the class used as the parameter when retrieving the icon. + * + * @param key + * the name which can be used to retrieve this icon later + * @param icon + * the icon to store + */ + public static void registerDisabledIcon(String key, ImageIcon icon) { + DISABLED_ICON_MAP.put(key, icon); + } + + /** + * Register a GUI class so that it can later be retrieved via + * {@link #getGUI(Class)}. The key should match the fully-qualified class + * name for the class used as the parameter when retrieving the GUI. + * + * @param key + * the name which can be used to retrieve this GUI later + * @param guiClass + * the class object for the GUI component + * @param testClass + * the class of the objects edited by this GUI + * + * @throws InstantiationException + * if an instance of the GUI class can not be instantiated + * @throws IllegalAccessException + * if access rights do not permit an instance of the GUI class + * to be created + */ + public static void registerGUI(String key, Class guiClass, Class testClass) throws InstantiationException, + IllegalAccessException { + // TODO: This method doesn't appear to be used. + JMeterGUIComponent gui; + + if (guiClass == TestBeanGUI.class) { + gui = new TestBeanGUI(testClass); + } else { + gui = (JMeterGUIComponent) guiClass.newInstance(); + } + GUI_MAP.put(key, gui); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/GuiPackage.java b/ApacheJmeter/src/org/apache/jmeter/gui/GuiPackage.java new file mode 100644 index 0000000..1e0bcbc --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/GuiPackage.java @@ -0,0 +1,774 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.awt.Component; +import java.awt.event.MouseEvent; +import java.beans.Introspector; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPopupMenu; +import javax.swing.JToolBar; +import javax.swing.SwingUtilities; + +import org.apache.jmeter.engine.util.ValueReplacer; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.TestBeanGUI; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * GuiPackage is a static class that provides convenient access to information + * about the current state of JMeter's GUI. Any GUI class can grab a handle to + * GuiPackage by calling the static method {@link #getInstance()} and then use + * it to query the GUI about it's state. When actions, for instance, need to + * affect the GUI, they typically use GuiPackage to get access to different + * parts of the GUI. + * + */ +public final class GuiPackage implements LocaleChangeListener { + /** Logging. */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** Singleton instance. */ + private static GuiPackage guiPack; + + /** + * Flag indicating whether or not parts of the tree have changed since they + * were last saved. + */ + private boolean dirty = false; + + /** + * Map from TestElement to JMeterGUIComponent, mapping the nodes in the tree + * to their corresponding GUI components. + */ + private Map nodesToGui = new HashMap(); + + /** + * Map from Class to JMeterGUIComponent, mapping the Class of a GUI + * component to an instance of that component. + */ + private Map, JMeterGUIComponent> guis = new HashMap, JMeterGUIComponent>(); + + /** + * Map from Class to TestBeanGUI, mapping the Class of a TestBean to an + * instance of TestBeanGUI to be used to edit such components. + */ + private Map, JMeterGUIComponent> testBeanGUIs = new HashMap, JMeterGUIComponent>(); + + /** The currently selected node in the tree. */ + private JMeterTreeNode currentNode = null; + + private boolean currentNodeUpdated = false; + + /** The model for JMeter's test tree. */ + private final JMeterTreeModel treeModel; + + /** The listener for JMeter's test tree. */ + private final JMeterTreeListener treeListener; + + /** The main JMeter frame. */ + private MainFrame mainFrame; + + /** The main JMeter toolbar. */ + private JToolBar toolbar; + + /** The menu item toolbar. */ + private JCheckBoxMenuItem menuToolBar; + + /** + * The LoggerPanel menu item + */ + private JCheckBoxMenuItem menuItemLoggerPanel; + + /** + * Logger Panel reference + */ + private LoggerPanel loggerPanel; + + + /** + * Private constructor to permit instantiation only from within this class. + * Use {@link #getInstance()} to retrieve a singleton instance. + */ + private GuiPackage(JMeterTreeModel treeModel, JMeterTreeListener treeListener) { + this.treeModel = treeModel; + this.treeListener = treeListener; + JMeterUtils.addLocaleChangeListener(this); + } + + /** + * Retrieve the singleton GuiPackage instance. + * + * @return the GuiPackage instance (may be null, e.g in non-Gui mode) + */ + public static GuiPackage getInstance() { + return guiPack; + } + + /** + * When GuiPackage is requested for the first time, it should be given + * handles to JMeter's Tree Listener and TreeModel. + * + * @param listener + * the TreeListener for JMeter's test tree + * @param treeModel + * the model for JMeter's test tree + * + * @return GuiPackage + */ + public static GuiPackage getInstance(JMeterTreeListener listener, JMeterTreeModel treeModel) { + if (guiPack == null) { + guiPack = new GuiPackage(treeModel, listener); + } + return guiPack; + } + + /** + * Get a JMeterGUIComponent for the specified test element. If the GUI has + * already been created, that instance will be returned. Otherwise, if a GUI + * component of the same type has been created, and the component is not + * marked as an {@link UnsharedComponent}, that shared component will be + * returned. Otherwise, a new instance of the component will be created. The + * TestElement's GUI_CLASS property will be used to determine the + * appropriate type of GUI component to use. + * + * @param node + * the test element which this GUI is being created for + * + * @return the GUI component corresponding to the specified test element + */ + public JMeterGUIComponent getGui(TestElement node) { + String testClassName = node.getPropertyAsString(TestElement.TEST_CLASS); + String guiClassName = node.getPropertyAsString(TestElement.GUI_CLASS); + try { + Class testClass; + if (testClassName.equals("")) { // $NON-NLS-1$ + testClass = node.getClass(); + } else { + testClass = Class.forName(testClassName); + } + Class guiClass = null; + if (!guiClassName.equals("")) { // $NON-NLS-1$ + guiClass = Class.forName(guiClassName); + } + return getGui(node, guiClass, testClass); + } catch (ClassNotFoundException e) { + log.error("Could not get GUI for " + node, e); + return null; + } + } + + /** + * Get a JMeterGUIComponent for the specified test element. If the GUI has + * already been created, that instance will be returned. Otherwise, if a GUI + * component of the same type has been created, and the component is not + * marked as an {@link UnsharedComponent}, that shared component will be + * returned. Otherwise, a new instance of the component will be created. + * + * @param node + * the test element which this GUI is being created for + * @param guiClass + * the fully qualifed class name of the GUI component which will + * be created if it doesn't already exist + * @param testClass + * the fully qualifed class name of the test elements which have + * to be edited by the returned GUI component + * + * @return the GUI component corresponding to the specified test element + */ + public JMeterGUIComponent getGui(TestElement node, Class guiClass, Class testClass) { + try { + JMeterGUIComponent comp = nodesToGui.get(node); + if (comp == null) { + comp = getGuiFromCache(guiClass, testClass); + nodesToGui.put(node, comp); + } + log.debug("Gui retrieved = " + comp); + return comp; + } catch (Exception e) { + log.error("Problem retrieving gui", e); + return null; + } + } + + /** + * Remove a test element from the tree. This removes the reference to any + * associated GUI component. + * + * @param node + * the test element being removed + */ + public void removeNode(TestElement node) { + nodesToGui.remove(node); + } + + /** + * Convenience method for grabbing the gui for the current node. + * + * @return the GUI component associated with the currently selected node + */ + public JMeterGUIComponent getCurrentGui() { + try { + updateCurrentNode(); + TestElement curNode = treeListener.getCurrentNode().getTestElement(); + JMeterGUIComponent comp = getGui(curNode); + comp.clearGui(); + log.debug("Updating gui to new node"); + comp.configure(curNode); + currentNodeUpdated = false; + return comp; + } catch (Exception e) { + log.error("Problem retrieving gui", e); + return null; + } + } + + /** + * Find the JMeterTreeNode for a certain TestElement object. + * + * @param userObject + * the test element to search for + * @return the tree node associated with the test element + */ + public JMeterTreeNode getNodeOf(TestElement userObject) { + return treeModel.getNodeOf(userObject); + } + + /** + * Create a TestElement corresponding to the specified GUI class. + * + * @param guiClass + * the fully qualified class name of the GUI component or a + * TestBean class for TestBeanGUIs. + * @param testClass + * the fully qualified class name of the test elements edited by + * this GUI component. + * @return the test element corresponding to the specified GUI class. + */ + public TestElement createTestElement(Class guiClass, Class testClass) { + try { + JMeterGUIComponent comp = getGuiFromCache(guiClass, testClass); + comp.clearGui(); + TestElement node = comp.createTestElement(); + nodesToGui.put(node, comp); + return node; + } catch (Exception e) { + log.error("Problem retrieving gui", e); + return null; + } + } + + /** + * Create a TestElement for a GUI or TestBean class. + *

+ * This is a utility method to help actions do with one single String + * parameter. + * + * @param objClass + * the fully qualified class name of the GUI component or of the + * TestBean subclass for which a TestBeanGUI is wanted. + * @return the test element corresponding to the specified GUI class. + */ + public TestElement createTestElement(String objClass) { + JMeterGUIComponent comp; + Class c; + try { + c = Class.forName(objClass); + if (TestBean.class.isAssignableFrom(c)) { + comp = getGuiFromCache(TestBeanGUI.class, c); + } else { + comp = getGuiFromCache(c, null); + } + comp.clearGui(); + TestElement node = comp.createTestElement(); + nodesToGui.put(node, comp); + return node; + } catch (NoClassDefFoundError e) { + log.error("Problem retrieving gui for " + objClass, e); + String msg="Cannot find class: "+e.getMessage(); + JOptionPane.showMessageDialog(null, + msg, + "Missing jar? See log file." , + JOptionPane.ERROR_MESSAGE); + throw new RuntimeException(e.toString()); // Probably a missing + // jar + } catch (ClassNotFoundException e) { + log.error("Problem retrieving gui for " + objClass, e); + throw new RuntimeException(e.toString()); // Programming error: + // bail out. + } catch (InstantiationException e) { + log.error("Problem retrieving gui for " + objClass, e); + throw new RuntimeException(e.toString()); // Programming error: + // bail out. + } catch (IllegalAccessException e) { + log.error("Problem retrieving gui for " + objClass, e); + throw new RuntimeException(e.toString()); // Programming error: + // bail out. + } + } + + /** + * Get an instance of the specified JMeterGUIComponent class. If an instance + * of the GUI class has previously been created and it is not marked as an + * {@link UnsharedComponent}, that shared instance will be returned. + * Otherwise, a new instance of the component will be created, and shared + * components will be cached for future retrieval. + * + * @param guiClass + * the fully qualified class name of the GUI component. This + * class must implement JMeterGUIComponent. + * @param testClass + * the fully qualified class name of the test elements edited by + * this GUI component. This class must implement TestElement. + * @return an instance of the specified class + * + * @throws InstantiationException + * if an instance of the object cannot be created + * @throws IllegalAccessException + * if access rights do not allow the default constructor to be + * called + * @throws ClassNotFoundException + * if the specified GUI class cannot be found + */ + private JMeterGUIComponent getGuiFromCache(Class guiClass, Class testClass) throws InstantiationException, + IllegalAccessException { + JMeterGUIComponent comp; + if (guiClass == TestBeanGUI.class) { + comp = testBeanGUIs.get(testClass); + if (comp == null) { + comp = new TestBeanGUI(testClass); + testBeanGUIs.put(testClass, comp); + } + } else { + comp = guis.get(guiClass); + if (comp == null) { + comp = (JMeterGUIComponent) guiClass.newInstance(); + if (!(comp instanceof UnsharedComponent)) { + guis.put(guiClass, comp); + } + } + } + return comp; + } + + /** + * Update the GUI for the currently selected node. The GUI component is + * configured to reflect the settings in the current tree node. + * + */ + public void updateCurrentGui() { + updateCurrentNode(); + currentNode = treeListener.getCurrentNode(); + TestElement element = currentNode.getTestElement(); + JMeterGUIComponent comp = getGui(element); + comp.configure(element); + currentNodeUpdated = false; + } + + /** + * This method should be called in order for GuiPackage to change the + * current node. This will save any changes made to the earlier node before + * choosing the new node. + */ + public void updateCurrentNode() { + try { + if (currentNode != null && !currentNodeUpdated) { + log.debug("Updating current node " + currentNode.getName()); + JMeterGUIComponent comp = getGui(currentNode.getTestElement()); + TestElement el = currentNode.getTestElement(); + comp.modifyTestElement(el); + currentNode.nameChanged(); // Bug 50221 - ensure label is updated + } + // The current node is now updated + currentNodeUpdated = true; + currentNode = treeListener.getCurrentNode(); + } catch (Exception e) { + log.error("Problem retrieving gui", e); + } + } + + public JMeterTreeNode getCurrentNode() { + return treeListener.getCurrentNode(); + } + + public TestElement getCurrentElement() { + return getCurrentNode().getTestElement(); + } + + /** + * The dirty property is a flag that indicates whether there are parts of + * JMeter's test tree that the user has not saved since last modification. + * Various (@link Command actions) set this property when components are + * modified/created/saved. + * + * @param dirty + * the new value of the dirty flag + */ + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + /** + * Retrieves the state of the 'dirty' property, a flag that indicates if + * there are test tree components that have been modified since they were + * last saved. + * + * @return true if some tree components have been modified since they were + * last saved, false otherwise + */ + public boolean isDirty() { + return dirty; + } + + /** + * Add a subtree to the currently selected node. + * + * @param subTree + * the subtree to add. + * + * @return the resulting subtree starting with the currently selected node + * + * @throws IllegalUserActionException + * if a subtree cannot be added to the currently selected node + */ + public HashTree addSubTree(HashTree subTree) throws IllegalUserActionException { + return treeModel.addSubTree(subTree, treeListener.getCurrentNode()); + } + + /** + * Get the currently selected subtree. + * + * @return the subtree of the currently selected node + */ + public HashTree getCurrentSubTree() { + return treeModel.getCurrentSubTree(treeListener.getCurrentNode()); + } + + /** + * Get the model for JMeter's test tree. + * + * @return the JMeter tree model + */ + /* + * TODO consider removing this method, and providing method wrappers instead. + * This would allow the Gui package to do any additional clearups if required, + * as has been done with clearTestPlan() + */ + public JMeterTreeModel getTreeModel() { + return treeModel; + } + + /** + * Get a ValueReplacer for the test tree. + * + * @return a ValueReplacer configured for the test tree + */ + public ValueReplacer getReplacer() { + return new ValueReplacer((TestPlan) ((JMeterTreeNode) getTreeModel().getTestPlan().getArray()[0]) + .getTestElement()); + } + + /** + * Set the main JMeter frame. + * + * @param newMainFrame + * the new JMeter main frame + */ + public void setMainFrame(MainFrame newMainFrame) { + mainFrame = newMainFrame; + } + + /** + * Get the main JMeter frame. + * + * @return the main JMeter frame + */ + public MainFrame getMainFrame() { + return mainFrame; + } + + /** + * Get the listener for JMeter's test tree. + * + * @return the JMeter test tree listener + */ + public JMeterTreeListener getTreeListener() { + return treeListener; + } + + /** + * Set the main JMeter toolbar. + * + * @param newToolbar + * the new JMeter main toolbar + */ + public void setMainToolbar(JToolBar newToolbar) { + toolbar = newToolbar; + } + + /** + * Get the main JMeter toolbar. + * + * @return the main JMeter toolbar + */ + public JToolBar getMainToolbar() { + return toolbar; + } + + /** + * Set the menu item toolbar. + * + * @param newMenuToolBar + * the new menu item toolbar + */ + public void setMenuItemToolbar(JCheckBoxMenuItem newMenuToolBar) { + menuToolBar = newMenuToolBar; + } + + /** + * Get the menu item toolbar. + * + * @return the menu item toolbar + */ + public JCheckBoxMenuItem getMenuItemToolbar() { + return menuToolBar; + } + + /** + * Display the specified popup menu with the source component and location + * from the specified mouse event. + * + * @param e + * the mouse event causing this popup to be displayed + * @param popup + * the popup menu to display + */ + public void displayPopUp(MouseEvent e, JPopupMenu popup) { + displayPopUp((Component) e.getSource(), e, popup); + } + + /** + * Display the specified popup menu at the location specified by a mouse + * event with the specified source component. + * + * @param invoker + * the source component + * @param e + * the mouse event causing this popup to be displayed + * @param popup + * the popup menu to display + */ + public void displayPopUp(Component invoker, MouseEvent e, JPopupMenu popup) { + if (popup != null) { + log.debug("Showing pop up for " + invoker + " at x,y = " + e.getX() + "," + e.getY()); + + popup.pack(); + popup.show(invoker, e.getX(), e.getY()); + popup.setVisible(true); + popup.requestFocus(); + } + } + + /** + * {@inheritDoc} + */ + public void localeChanged(LocaleChangeEvent event) { + // FIrst make sure we save the content of the current GUI (since we + // will flush it away): + updateCurrentNode(); + + // Forget about all GUIs we've created so far: we'll need to re-created + // them all! + guis = new HashMap, JMeterGUIComponent>(); + nodesToGui = new HashMap(); + testBeanGUIs = new HashMap, JMeterGUIComponent>(); + + // BeanInfo objects also contain locale-sensitive data -- flush them + // away: + Introspector.flushCaches(); + + // Now put the current GUI in place. [This code was copied from the + // EditCommand action -- we can't just trigger the action because that + // would populate the current node with the contents of the new GUI -- + // which is empty.] + MainFrame mf = getMainFrame(); // Fetch once + if (mf == null) // Probably caused by unit testing on headless system + { + log.warn("Mainframe is null"); + } else { + mf.setMainPanel((javax.swing.JComponent) getCurrentGui()); + mf.setEditMenu(getTreeListener().getCurrentNode().createPopupMenu()); + } + } + + private String testPlanFile; + + private final List stoppables = Collections.synchronizedList(new ArrayList()); + + /** + * Sets the filepath of the current test plan. It's shown in the main frame + * title and used on saving. + * + * @param f + */ + public void setTestPlanFile(String f) { + testPlanFile = f; + getMainFrame().setExtendedFrameTitle(testPlanFile); + // Enable file revert action if a file is used + getMainFrame().setFileRevertEnabled(f != null); + getMainFrame().setProjectFileLoaded(f); + + try { + FileServer.getFileServer().setBasedir(testPlanFile); + } catch (IllegalStateException e1) { + log.error("Failure setting file server's base dir", e1); + } + } + + public String getTestPlanFile() { + return testPlanFile; + } + + /** + * Clears the test plan and associated objects. + * Clears the test plan file name. + */ + public void clearTestPlan() { + getTreeModel().clearTestPlan(); + nodesToGui.clear(); + setTestPlanFile(null); + } + + /** + * Clears the test plan element and associated object + * + * @param element to clear + */ + public void clearTestPlan(TestElement element) { + getTreeModel().clearTestPlan(element); + removeNode(element); + } + + public static void showErrorMessage(final String message, final String title){ + showMessage(message,title,JOptionPane.ERROR_MESSAGE); + } + + public static void showInfoMessage(final String message, final String title){ + showMessage(message,title,JOptionPane.INFORMATION_MESSAGE); + } + + public static void showWarningMessage(final String message, final String title){ + showMessage(message,title,JOptionPane.WARNING_MESSAGE); + } + + public static void showMessage(final String message, final String title, final int type){ + if (guiPack == null) { + return ; + } + SwingUtilities.invokeLater(new Runnable() { + public void run() { + JOptionPane.showMessageDialog(null,message,title,type); + } + }); + + } + + /** + * Unregister stoppable + * @param stoppable Stoppable + */ + public void unregister(Stoppable stoppable) { + for (Iterator iterator = stoppables .iterator(); iterator.hasNext();) { + Stoppable stopable = iterator.next(); + if(stopable == stoppable) + { + iterator.remove(); + } + } + } + + /** + * Register process to stop on reload + * @param stoppable + */ + public void register(Stoppable stoppable) { + stoppables.add(stoppable); + } + + /** + * + * @return List Copy of IStoppable + */ + public List getStoppables() { + ArrayList list = new ArrayList(); + list.addAll(stoppables); + return list; + } + + /** + * Set the menu item LoggerPanel. + * @param menuItemLoggerPanel + */ + public void setMenuItemLoggerPanel(JCheckBoxMenuItem menuItemLoggerPanel) { + this.menuItemLoggerPanel = menuItemLoggerPanel; + } + + /** + * Get the menu item LoggerPanel. + * + * @return the menu item LoggerPanel + */ + public JCheckBoxMenuItem getMenuItemLoggerPanel() { + return menuItemLoggerPanel; + } + + /** + * @param loggerPanel LoggerPanel + */ + public void setLoggerPanel(LoggerPanel loggerPanel) { + this.loggerPanel = loggerPanel; + } + + /** + * @return the loggerPanel + */ + public LoggerPanel getLoggerPanel() { + return loggerPanel; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/JMeterFileFilter.java b/ApacheJmeter/src/org/apache/jmeter/gui/JMeterFileFilter.java new file mode 100644 index 0000000..90de197 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/JMeterFileFilter.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.io.File; +import java.util.Arrays; + +/** + * A file filter which allows files to be filtered based on a list of allowed + * extensions. + * + * Optionally returns directories. + * + */ +public class JMeterFileFilter extends javax.swing.filechooser.FileFilter implements java.io.FileFilter { + /** The list of extensions allowed by this filter. */ + private final String[] exts; + + private final boolean allowDirs; // Should we allow directories? + + /** + * Create a new JMeter file filter which allows the specified extensions. If + * the array of extensions contains no elements, any file will be allowed. + * + * This constructor will also return all directories + * + * @param extensions + * non-null array of allowed file extensions + */ + public JMeterFileFilter(String[] extensions) { + this(extensions,true); + } + + /** + * Create a new JMeter file filter which allows the specified extensions. If + * the array of extensions contains no elements, any file will be allowed. + * + * @param extensions non-null array of allowed file extensions + * @param allow should directories be returned ? + */ + public JMeterFileFilter(String[] extensions, boolean allow) { + exts = extensions; + allowDirs = allow; + } + + /** + * Determine if the specified file is allowed by this filter. The file will + * be allowed if it is a directory, or if the end of the filename matches + * one of the extensions allowed by this filter. The filename is converted + * to lower-case before making the comparison. + * + * @param f + * the File being tested + * + * @return true if the file should be allowed, false otherwise + */ + @Override + public boolean accept(File f) { + return (allowDirs && f.isDirectory()) || accept(f.getName().toLowerCase()); + // TODO - why lower case? OK to use the default Locale? + } + + /** + * Determine if the specified filename is allowed by this filter. The file + * will be allowed if the end of the filename matches one of the extensions + * allowed by this filter. The comparison is case-sensitive. If no + * extensions were provided for this filter, the file will always be + * allowed. + * + * @param filename + * the filename to test + * @return true if the file should be allowed, false otherwise + */ + public boolean accept(String filename) { + if (exts.length == 0) { + return true; + } + + for (int i = 0; i < exts.length; i++) { + if (filename.endsWith(exts[i])) { + return true; + } + } + + return false; + } + + /** + * Get a description for this filter. + * + * @return a description for this filter + */ + @Override + public String getDescription() { + return "JMeter " + Arrays.asList(exts).toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/JMeterGUIComponent.java b/ApacheJmeter/src/org/apache/jmeter/gui/JMeterGUIComponent.java new file mode 100644 index 0000000..96d89f6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/JMeterGUIComponent.java @@ -0,0 +1,189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.testelement.TestElement; + +/** + * Implementing this interface indicates that the class is a JMeter GUI + * Component. A JMeter GUI Component is essentially the GUI display code + * associated with a JMeter Test Element. The writer of the component must take + * care to make the component be consistent with the rest of JMeter's GUI look + * and feel and behavior. Use of the provided abstract classes is highly + * recommended to make this task easier. + * + * @see AbstractJMeterGuiComponent + * @see org.apache.jmeter.config.gui.AbstractConfigGui + * @see org.apache.jmeter.assertions.gui.AbstractAssertionGui + * @see org.apache.jmeter.control.gui.AbstractControllerGui + * @see org.apache.jmeter.timers.gui.AbstractTimerGui + * @see org.apache.jmeter.visualizers.gui.AbstractVisualizer + * @see org.apache.jmeter.samplers.gui.AbstractSamplerGui + * + */ + +public interface JMeterGUIComponent { + + /** + * Sets the name of the JMeter GUI Component. The name of the component is + * used in the Test Tree as the name of the tree node. + * + * @param name + * the name of the component + */ + void setName(String name); + + /** + * Gets the name of the JMeter GUI component. The name of the component is + * used in the Test Tree as the name of the tree node. + * + * @return the name of the component + */ + String getName(); + + /** + * Get the component's label. This label is used in drop down lists that + * give the user the option of choosing one type of component in a list of + * many. It should therefore be a descriptive name for the end user to see. + * It must be unique to the class. + * + * It is also used by Help to find the appropriate location in the + * documentation. + * + * Normally getLabelResource() should be overridden instead of + * this method; the definition of this method in AbstractJMeterGuiComponent + * is intended for general use. + * + * @see #getLabelResource() + * @return GUI label for the component. + */ + String getStaticLabel(); + + /** + * Get the component's resource name, which getStaticLabel uses to derive + * the component's label in the local language. The resource name is fixed, + * and does not vary with the selected language. + * + * Normally this method should be overriden in preference to overriding + * getStaticLabel(). However where the resource name is not available or required, + * getStaticLabel() may be overridden instead. + * + * @return the resource name + */ + String getLabelResource(); + + /** + * Get the component's document anchor name. Used by Help to find the + * appropriate location in the documentation + * + * @return Document anchor (#ref) for the component. + */ + String getDocAnchor(); + + /** + * JMeter test components are separated into a model and a GUI + * representation. The model holds the data and the GUI displays it. The GUI + * class is responsible for knowing how to create and initialize with data + * the model class that it knows how to display, and this method is called + * when new test elements are created. + * + * @return the Test Element object that the GUI component represents. + */ + TestElement createTestElement(); + + /** + * GUI components are responsible for populating TestElements they create + * with the data currently held in the GUI components. This method should + * overwrite whatever data is currently in the TestElement as it is called + * after a user has filled out the form elements in the gui with new + * information. + * + * @param element + * the TestElement to modify + */ + void modifyTestElement(TestElement element); + + /** + * Test GUI elements can be disabled, in which case they do not become part + * of the test when run. + * + * @return true if the element should be part of the test run, false + * otherwise + */ + boolean isEnabled(); + + /** + * Set whether this component is enabled. + * + * @param enabled + * true for enabled, false for disabled. + */ + void setEnabled(boolean enabled); + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + * + * @return a JPopupMenu appropriate for the component. + */ + JPopupMenu createPopupMenu(); + + /** + * The GUI must be able to extract the data from the TestElement and update + * all GUI fields to represent those data. This method is called to allow + * JMeter to show the user the GUI that represents the test element's data. + * + * @param element + * the TestElement to configure + */ + void configure(TestElement element); + + /** + * This is the list of add menu categories this gui component will be + * available under. For instance, if this represents a Controller, then the + * MenuFactory.CONTROLLERS category should be in the returned collection. + * When a user right-clicks on a tree element and looks through the "add" + * menu, which category your GUI component shows up in is determined by + * which categories are returned by this method. Most GUI's belong to only + * one category, but it is possible for a component to exist in multiple + * categories. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + * + * @see org.apache.jmeter.gui.util.MenuFactory + */ + Collection getMenuCategories(); + + /** + * Clear the gui and return it to initial default values. This is necessary + * because most gui classes are instantiated just once and re-used for + * multiple test element objects and thus they need to be cleared between + * use. + */ + public void clearGui(); + // N.B. originally called clear() + // @see also Clearable +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/LoggerPanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/LoggerPanel.java new file mode 100644 index 0000000..fb93a8e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/LoggerPanel.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.awt.BorderLayout; +import java.awt.Insets; + +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.LogEvent; +import org.apache.log.LogTarget; +import org.apache.log.format.PatternFormatter; + +/** + * Panel that shows log events + */ +public class LoggerPanel extends JPanel implements LogTarget { + + private static final long serialVersionUID = 6911128494402594429L; + + private JTextArea textArea; + + private PatternFormatter format; + + // Limit length of log content + private static final int LOGGER_PANEL_MAX_LENGTH = + JMeterUtils.getPropDefault("jmeter.loggerpanel.maxlength", 80000); // $NON-NLS-1$ + + /** + * Pane for display JMeter log file + */ + public LoggerPanel() { + init(); + format = new PatternFormatter(LoggingManager.DEFAULT_PATTERN + "\n"); // $NON-NLS-1$ + } + + private void init() { + this.setLayout(new BorderLayout()); + + // TEXTAREA + textArea = new JTextArea(); + textArea.setEditable(false); + textArea.setLineWrap(false); + textArea.setMargin(new Insets(2, 2, 2, 2)); // space between borders and text + JScrollPane areaScrollPane = new JScrollPane(textArea); + + areaScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + areaScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + this.add(areaScrollPane, BorderLayout.CENTER); + } + + /* (non-Javadoc) + * @see org.apache.log.LogTarget#processEvent(org.apache.log.LogEvent) + */ + public void processEvent(final LogEvent logEvent) { + if(!GuiPackage.getInstance().getMenuItemLoggerPanel().getModel().isSelected()) { + return; + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + synchronized (textArea) { + textArea.append(format.format(logEvent)); + int currentLength = textArea.getText().length(); + // If LOGGER_PANEL_MAX_LENGTH is 0, it means all log events are kept + if(LOGGER_PANEL_MAX_LENGTH != 0 && currentLength> LOGGER_PANEL_MAX_LENGTH) { + textArea.setText(textArea.getText().substring(Math.max(0, currentLength-LOGGER_PANEL_MAX_LENGTH), + currentLength)); + } + textArea.setCaretPosition(textArea.getText().length()); + } + } + }); + } + + /** + * Clear panel content + */ + public void clear() { + this.textArea.setText(""); // $NON-NLS-1$ + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/MainFrame.java b/ApacheJmeter/src/org/apache/jmeter/gui/MainFrame.java new file mode 100644 index 0000000..736e05f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/MainFrame.java @@ -0,0 +1,779 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Insets; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.dnd.DropTargetEvent; +import java.awt.dnd.DropTargetListener; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTree; +import javax.swing.MenuElement; +import javax.swing.SwingUtilities; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.LoadDraggedFile; +import org.apache.jmeter.gui.tree.JMeterCellRenderer; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.util.JMeterMenuBar; +import org.apache.jmeter.gui.util.JMeterToolBar; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.LogEvent; +import org.apache.log.LogTarget; +import org.apache.log.Logger; +import org.apache.log.Priority; + +/** + * The main JMeter frame, containing the menu bar, test tree, and an area for + * JMeter component GUIs. + * + */ +public class MainFrame extends JFrame implements TestListener, Remoteable, DropTargetListener, Clearable, ActionListener { + + private static final long serialVersionUID = 240L; + + // This is used to keep track of local (non-remote) tests + // The name is chosen to be an unlikely host-name + private static final String LOCAL = "*local*"; // $NON-NLS-1$ + + // The default title for the Menu bar + private static final String DEFAULT_TITLE = + "Apache JMeter ("+JMeterUtils.getJMeterVersion()+")"; // $NON-NLS-1$ $NON-NLS-2$ + + // Allow display/hide toolbar + private static final boolean DISPLAY_TOOLBAR = + JMeterUtils.getPropDefault("jmeter.toolbar.display", true); // $NON-NLS-1$ + + // Allow display/hide LoggerPanel + private static final boolean DISPLAY_LOGGER_PANEL = + JMeterUtils.getPropDefault("jmeter.loggerpanel.display", false); // $NON-NLS-1$ + + // Allow display/hide Log Error/Fatal counter + private static final boolean DISPLAY_ERROR_FATAL_COUNTER = + JMeterUtils.getPropDefault("jmeter.errorscounter.display", true); // $NON-NLS-1$ + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** The menu bar. */ + private JMeterMenuBar menuBar; + + /** The main panel where components display their GUIs. */ + private JScrollPane mainPanel; + + /** The panel where the test tree is shown. */ + private JScrollPane treePanel; + + /** The LOG panel. */ + private LoggerPanel logPanel; + + /** The test tree. */ + private JTree tree; + + /** An image which is displayed when a test is running. */ + private ImageIcon runningIcon = JMeterUtils.getImage("thread.enabled.gif");// $NON-NLS-1$ + + /** An image which is displayed when a test is not currently running. */ + private ImageIcon stoppedIcon = JMeterUtils.getImage("thread.disabled.gif");// $NON-NLS-1$ + + /** An image which is displayed to indicate FATAL, ERROR or WARNING. */ + private ImageIcon warningIcon = JMeterUtils.getImage("warning.png");// $NON-NLS-1$ + + /** The button used to display the running/stopped image. */ + private JButton runningIndicator; + + /** The x coordinate of the last location where a component was dragged. */ + private int previousDragXLocation = 0; + + /** The y coordinate of the last location where a component was dragged. */ + private int previousDragYLocation = 0; + + /** The set of currently running hosts. */ + private Set hosts = new HashSet(); + + /** A message dialog shown while JMeter threads are stopping. */ + private JDialog stoppingMessage; + + private JLabel totalThreads; + private JLabel activeThreads; + + private JMeterToolBar toolbar; + + /** + * Indicator for Log errors and Fatals + */ + private JButton warnIndicator; + /** + * Counter + */ + private JLabel errorsOrFatalsLabel; + /** + * LogTarget that receives ERROR or FATAL + */ + private ErrorsAndFatalsCounterLogTarget errorsAndFatalsCounterLogTarget; + + /** + * Create a new JMeter frame. + * + * @param actionHandler + * this parameter is not used + * @param treeModel + * the model for the test tree + * @param treeListener + * the listener for the test tree + */ + public MainFrame(ActionListener actionHandler, TreeModel treeModel, JMeterTreeListener treeListener) { + // TODO: actionHandler isn't used -- remove it from the parameter list + // this.actionHandler = actionHandler; + + // TODO: Make the running indicator its own class instead of a JButton + runningIndicator = new JButton(stoppedIcon); + runningIndicator.setMargin(new Insets(0, 0, 0, 0)); + runningIndicator.setBorder(BorderFactory.createEmptyBorder()); + + totalThreads = new JLabel("0"); // $NON-NLS-1$ + activeThreads = new JLabel("0"); // $NON-NLS-1$ + + warnIndicator = new JButton(warningIcon); + warnIndicator.setMargin(new Insets(0, 0, 0, 0)); + // Transparent JButton with no border + warnIndicator.setOpaque(false); + warnIndicator.setContentAreaFilled(false); + warnIndicator.setBorderPainted(false); + + warnIndicator.setToolTipText(JMeterUtils.getResString("error_indicator_tooltip")); // $NON-NLS-1$ + warnIndicator.addActionListener(this); + errorsOrFatalsLabel = new JLabel("0"); // $NON-NLS-1$ + errorsOrFatalsLabel.setToolTipText(JMeterUtils.getResString("error_indicator_tooltip")); // $NON-NLS-1$ + + tree = makeTree(treeModel, treeListener); + + GuiPackage.getInstance().setMainFrame(this); + init(); + initTopLevelDndHandler(); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + } + + /** + * Default constructor for the JMeter frame. This constructor will not + * properly initialize the tree, so don't use it. + * + * @deprecated Do not use - only needed for JUnit tests + */ + @Deprecated + public MainFrame() { + } + + // MenuBar related methods + // TODO: Do we really need to have all these menubar methods duplicated + // here? Perhaps we can make the menu bar accessible through GuiPackage? + + /** + * Specify whether or not the File|Load menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setFileLoadEnabled(boolean enabled) { + menuBar.setFileLoadEnabled(enabled); + } + + /** + * Specify whether or not the File|Save menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setFileSaveEnabled(boolean enabled) { + menuBar.setFileSaveEnabled(enabled); + } + + /** + * Specify whether or not the File|Revert item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setFileRevertEnabled(boolean enabled) { + menuBar.setFileRevertEnabled(enabled); + } + + /** + * Specify the project file that was just loaded + * + * @param file - the full path to the file that was loaded + */ + public void setProjectFileLoaded(String file) { + menuBar.setProjectFileLoaded(file); + } + + /** + * Set the menu that should be used for the Edit menu. + * + * @param menu + * the new Edit menu + */ + public void setEditMenu(JPopupMenu menu) { + menuBar.setEditMenu(menu); + } + + /** + * Specify whether or not the Edit menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setEditEnabled(boolean enabled) { + menuBar.setEditEnabled(enabled); + } + + /** + * Set the menu that should be used for the Edit|Add menu. + * + * @param menu + * the new Edit|Add menu + */ + public void setEditAddMenu(JMenu menu) { + menuBar.setEditAddMenu(menu); + } + + /** + * Specify whether or not the Edit|Add menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setEditAddEnabled(boolean enabled) { + menuBar.setEditAddEnabled(enabled); + } + + /** + * Close the currently selected menu. + */ + public void closeMenu() { + if (menuBar.isSelected()) { + MenuElement[] menuElement = menuBar.getSubElements(); + if (menuElement != null) { + for (int i = 0; i < menuElement.length; i++) { + JMenu menu = (JMenu) menuElement[i]; + if (menu.isSelected()) { + menu.setPopupMenuVisible(false); + menu.setSelected(false); + break; + } + } + } + } + } + + /** + * Show a dialog indicating that JMeter threads are stopping on a particular + * host. + * + * @param host + * the host where JMeter threads are stopping + */ + public void showStoppingMessage(String host) { + if (stoppingMessage != null){ + stoppingMessage.dispose(); + } + stoppingMessage = new JDialog(this, JMeterUtils.getResString("stopping_test_title"), true); //$NON-NLS-1$ + JLabel stopLabel = new JLabel(JMeterUtils.getResString("stopping_test") + ": " + host); //$NON-NLS-1$$NON-NLS-2$ + stopLabel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + stoppingMessage.getContentPane().add(stopLabel); + stoppingMessage.pack(); + ComponentUtil.centerComponentInComponent(this, stoppingMessage); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (stoppingMessage != null) {// TODO - how can this be null? + stoppingMessage.setVisible(true); + } + } + }); + } + + public void updateCounts() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + activeThreads.setText(Integer.toString(JMeterContextService.getNumberOfThreads())); + totalThreads.setText(Integer.toString(JMeterContextService.getTotalThreads())); + } + }); + } + + public void setMainPanel(JComponent comp) { + mainPanel.setViewportView(comp); + } + + public JTree getTree() { + return tree; + } + + // TestListener implementation + + /** + * Called when a test is started on the local system. This implementation + * sets the running indicator and ensures that the menubar is enabled and in + * the running state. + */ + public void testStarted() { + testStarted(LOCAL); + menuBar.setEnabled(true); + } + + /** + * Called when a test is started on a specific host. This implementation + * sets the running indicator and ensures that the menubar is in the running + * state. + * + * @param host + * the host where the test is starting + */ + public void testStarted(String host) { + hosts.add(host); + runningIndicator.setIcon(runningIcon); + activeThreads.setText("0"); // $NON-NLS-1$ + totalThreads.setText("0"); // $NON-NLS-1$ + menuBar.setRunning(true, host); + toolbar.setTestStarted(true); + } + + /** + * Called when a test is ended on the local system. This implementation + * disables the menubar, stops the running indicator, and closes the + * stopping message dialog. + */ + public void testEnded() { + testEnded(LOCAL); + menuBar.setEnabled(false); + } + + /** + * Called when a test is ended on the remote system. This implementation + * stops the running indicator and closes the stopping message dialog. + * + * @param host + * the host where the test is ending + */ + public void testEnded(String host) { + hosts.remove(host); + if (hosts.size() == 0) { + runningIndicator.setIcon(stoppedIcon); + JMeterContextService.endTest(); + } + menuBar.setRunning(false, host); + toolbar.setTestStarted(false); + if (stoppingMessage != null) { + stoppingMessage.dispose(); + stoppingMessage = null; + } + } + + /* Implements TestListener#testIterationStart(LoopIterationEvent) */ + public void testIterationStart(LoopIterationEvent event) { + } + + /** + * Create the GUI components and layout. + */ + private void init() { + menuBar = new JMeterMenuBar(); + setJMenuBar(menuBar); + JPanel all = new JPanel(new BorderLayout()); + all.add(createToolBar(), BorderLayout.NORTH); + + JSplitPane treeAndMain = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + + treePanel = createTreePanel(); + treeAndMain.setLeftComponent(treePanel); + + JSplitPane topAndDown = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + topAndDown.setOneTouchExpandable(true); + topAndDown.setDividerLocation(0.8); + topAndDown.setResizeWeight(.8); + topAndDown.setContinuousLayout(true); + topAndDown.setBorder(null); // see bug jdk 4131528 + if (!DISPLAY_LOGGER_PANEL) { + topAndDown.setDividerSize(0); + } + mainPanel = createMainPanel(); + + logPanel = createLoggerPanel(); + if (DISPLAY_ERROR_FATAL_COUNTER) { + errorsAndFatalsCounterLogTarget = new ErrorsAndFatalsCounterLogTarget(); + LoggingManager.addLogTargetToRootLogger(new LogTarget[]{ + logPanel, + errorsAndFatalsCounterLogTarget + }); + } else { + LoggingManager.addLogTargetToRootLogger(new LogTarget[]{ + logPanel + }); + } + + topAndDown.setTopComponent(mainPanel); + topAndDown.setBottomComponent(logPanel); + + treeAndMain.setRightComponent(topAndDown); + + treeAndMain.setResizeWeight(.2); + treeAndMain.setContinuousLayout(true); + all.add(treeAndMain, BorderLayout.CENTER); + + getContentPane().add(all); + + tree.setSelectionRow(1); + addWindowListener(new WindowHappenings()); + + setTitle(DEFAULT_TITLE); + setIconImage(JMeterUtils.getImage("jmeter.jpg").getImage());// $NON-NLS-1$ + } + + + /** + * Support for Test Plan Dnd + * see BUG 52281 (when JDK6 will be minimum JDK target) + */ + public void initTopLevelDndHandler() { + new DropTarget(this, this); + } + + public void setExtendedFrameTitle(String fname) { + // file New operation may set to null, so just return app name + if (fname == null) { + setTitle(DEFAULT_TITLE); + return; + } + + // allow for windows / chars in filename + String temp = fname.replace('\\', '/'); // $NON-NLS-1$ // $NON-NLS-2$ + String simpleName = temp.substring(temp.lastIndexOf("/") + 1);// $NON-NLS-1$ + setTitle(simpleName + " (" + fname + ") - " + DEFAULT_TITLE); // $NON-NLS-1$ // $NON-NLS-2$ + } + + /** + * Create the JMeter tool bar pane containing the running indicator. + * + * @return a panel containing the running indicator + */ + private Component createToolBar() { + Box toolPanel = new Box(BoxLayout.X_AXIS); + // add the toolbar + this.toolbar = JMeterToolBar.createToolbar(DISPLAY_TOOLBAR); + GuiPackage guiInstance = GuiPackage.getInstance(); + guiInstance.setMainToolbar(toolbar); + guiInstance.getMenuItemToolbar().getModel().setSelected(DISPLAY_TOOLBAR); + toolPanel.add(toolbar); + + toolPanel.add(Box.createRigidArea(new Dimension(10, 15))); + toolPanel.add(Box.createGlue()); + + if (DISPLAY_ERROR_FATAL_COUNTER) { + toolPanel.add(errorsOrFatalsLabel); + toolPanel.add(warnIndicator); + toolPanel.add(Box.createRigidArea(new Dimension(20, 15))); + } + toolPanel.add(activeThreads); + toolPanel.add(new JLabel(" / ")); + toolPanel.add(totalThreads); + toolPanel.add(Box.createRigidArea(new Dimension(10, 15))); + toolPanel.add(runningIndicator); + return toolPanel; + } + + /** + * Create the panel where the GUI representation of the test tree is + * displayed. The tree should already be created before calling this method. + * + * @return a scroll pane containing the test tree GUI + */ + private JScrollPane createTreePanel() { + JScrollPane treeP = new JScrollPane(tree); + treeP.setMinimumSize(new Dimension(100, 0)); + return treeP; + } + + /** + * Create the main panel where components can display their GUIs. + * + * @return the main scroll pane + */ + private JScrollPane createMainPanel() { + return new JScrollPane(); + } + + /** + * Create at the down of the left a Console for Log events + * @return {@link LoggerPanel} + */ + private LoggerPanel createLoggerPanel() { + LoggerPanel loggerPanel = new LoggerPanel(); + loggerPanel.setMinimumSize(new Dimension(0, 100)); + loggerPanel.setPreferredSize(new Dimension(0, 150)); + GuiPackage guiInstance = GuiPackage.getInstance(); + guiInstance.setLoggerPanel(loggerPanel); + guiInstance.getMenuItemLoggerPanel().getModel().setSelected(DISPLAY_LOGGER_PANEL); + loggerPanel.setVisible(DISPLAY_LOGGER_PANEL); + return loggerPanel; + } + + /** + * Create and initialize the GUI representation of the test tree. + * + * @param treeModel + * the test tree model + * @param treeListener + * the test tree listener + * + * @return the initialized test tree GUI + */ + private JTree makeTree(TreeModel treeModel, JMeterTreeListener treeListener) { + JTree treevar = new JTree(treeModel) { + private static final long serialVersionUID = 240L; + + @Override + public String getToolTipText(MouseEvent event) { + TreePath path = this.getPathForLocation(event.getX(), event.getY()); + if (path != null) { + Object treeNode = path.getLastPathComponent(); + if (treeNode instanceof DefaultMutableTreeNode) { + Object testElement = ((DefaultMutableTreeNode) treeNode).getUserObject(); + if (testElement instanceof TestElement) { + String comment = ((TestElement) testElement).getComment(); + if (comment != null && comment.length() > 0) { + return comment; + } + } + } + } + return null; + } + }; + treevar.setToolTipText(""); + treevar.setCellRenderer(getCellRenderer()); + treevar.setRootVisible(false); + treevar.setShowsRootHandles(true); + + treeListener.setJTree(treevar); + treevar.addTreeSelectionListener(treeListener); + treevar.addMouseListener(treeListener); + treevar.addMouseMotionListener(treeListener); + treevar.addKeyListener(treeListener); + + return treevar; + } + + /** + * Create the tree cell renderer used to draw the nodes in the test tree. + * + * @return a renderer to draw the test tree nodes + */ + private TreeCellRenderer getCellRenderer() { + DefaultTreeCellRenderer rend = new JMeterCellRenderer(); + rend.setFont(new Font("Dialog", Font.PLAIN, 11)); + return rend; + } + + /** + * Repaint pieces of the GUI as needed while dragging. This method should + * only be called from the Swing event thread. + * + * @param dragIcon + * the component being dragged + * @param x + * the current mouse x coordinate + * @param y + * the current mouse y coordinate + */ + public void drawDraggedComponent(Component dragIcon, int x, int y) { + Dimension size = dragIcon.getPreferredSize(); + treePanel.paintImmediately(previousDragXLocation, previousDragYLocation, size.width, size.height); + this.getLayeredPane().setLayer(dragIcon, 400); + SwingUtilities.paintComponent(treePanel.getGraphics(), dragIcon, treePanel, x, y, size.width, size.height); + previousDragXLocation = x; + previousDragYLocation = y; + } + + /** + * A window adapter used to detect when the main JMeter frame is being + * closed. + */ + private static class WindowHappenings extends WindowAdapter { + /** + * Called when the main JMeter frame is being closed. Sends a + * notification so that JMeter can react appropriately. + * + * @param event + * the WindowEvent to handle + */ + @Override + public void windowClosing(WindowEvent event) { + ActionRouter.getInstance().actionPerformed(new ActionEvent(this, event.getID(), ActionNames.EXIT)); + } + } + + public void dragEnter(DropTargetDragEvent dtde) { + // NOOP + } + + public void dragExit(DropTargetEvent dte) { + // NOOP + } + + public void dragOver(DropTargetDragEvent dtde) { + // NOOP + } + + /** + * Handler of Top level Dnd + */ + public void drop(DropTargetDropEvent dtde) { + try { + Transferable tr = dtde.getTransferable(); + DataFlavor[] flavors = tr.getTransferDataFlavors(); + for (int i = 0; i < flavors.length; i++) { + // Check for file lists specifically + if (flavors[i].isFlavorJavaFileListType()) { + dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); + try { + @SuppressWarnings("unchecked") + List files = (List) + tr.getTransferData(DataFlavor.javaFileListFlavor); + if(files.isEmpty()) { + return; + } + File file = files.get(0); + if(!file.getName().endsWith(".jmx")) { + log.warn("Importing file:" + file.getName()+ "from DnD failed because file extension does not end with .jmx"); + return; + } + + ActionEvent fakeEvent = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ActionNames.OPEN); + LoadDraggedFile.loadProject(fakeEvent, file); + } finally { + dtde.dropComplete(true); + } + return; + } + } + } catch (UnsupportedFlavorException e) { + log.warn("Dnd failed" , e); + } catch (IOException e) { + log.warn("Dnd failed" , e); + } + + } + + public void dropActionChanged(DropTargetDragEvent dtde) { + // NOOP + } + + /** + * + */ + public final class ErrorsAndFatalsCounterLogTarget implements LogTarget, Clearable { + public AtomicInteger errorOrFatal = new AtomicInteger(0); + + public void processEvent(LogEvent event) { + if(event.getPriority().equals(Priority.ERROR) || + event.getPriority().equals(Priority.FATAL_ERROR)) { + final int newValue = errorOrFatal.incrementAndGet(); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + errorsOrFatalsLabel.setText(Integer.toString(newValue)); + } + }); + } + } + + public void clearData() { + errorOrFatal.set(0); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + errorsOrFatalsLabel.setText(Integer.toString(errorOrFatal.get())); + } + }); + } + } + + + public void clearData() { + logPanel.clear(); + if(DISPLAY_ERROR_FATAL_COUNTER) { + errorsAndFatalsCounterLogTarget.clearData(); + } + } + + /** + * Handles click on warnIndicator + */ + public void actionPerformed(ActionEvent event) { + if(event.getSource()==warnIndicator) { + ActionRouter.getInstance().doActionNow(new ActionEvent(event.getSource(), event.getID(), ActionNames.LOGGER_PANEL_ENABLE_DISABLE)); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/NamePanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/NamePanel.java new file mode 100644 index 0000000..526ec9c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/NamePanel.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.awt.BorderLayout; +import java.util.Collection; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JTextField; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; + +public class NamePanel extends JPanel implements JMeterGUIComponent { + private static final long serialVersionUID = 240L; + + /** A text field containing the name. */ + private JTextField nameField = new JTextField(15); + + /** The label for the text field. */ + private JLabel nameLabel; + + /** + * Create a new NamePanel with the default name. + */ + public NamePanel() { + setName(getStaticLabel()); + init(); + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout(5, 0)); + + nameLabel = new JLabel(JMeterUtils.getResString("name")); // $NON-NLS-1$ + nameLabel.setName("name"); + nameLabel.setLabelFor(nameField); + + add(nameLabel, BorderLayout.WEST); + add(nameField, BorderLayout.CENTER); + } + + public void clearGui() { + setName(getStaticLabel()); + } + + /** + * Get the currently displayed name. + * + * @return the current name + */ + @Override + public String getName() { + if (nameField != null) { + return nameField.getText(); + } + return ""; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void setName(String name) { + super.setName(name); + nameField.setText(name); + } + + /** {@inheritDoc} */ + public void configure(TestElement testElement) { + setName(testElement.getName()); + } + + /** {@inheritDoc} */ + public JPopupMenu createPopupMenu() { + return null; + } + + /** {@inheritDoc} */ + public String getStaticLabel() { + return JMeterUtils.getResString(getLabelResource()); + } + + /** {@inheritDoc} */ + public String getLabelResource() { + return "root"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + public Collection getMenuCategories() { + return null; + } + + /** {@inheritDoc} */ + public TestElement createTestElement() { + WorkBench wb = new WorkBench(); + modifyTestElement(wb); + return wb; + } + + /** {@inheritDoc} */ + public void modifyTestElement(TestElement wb) { + wb.setName(getName()); + wb.setProperty(new StringProperty(TestElement.GUI_CLASS, this.getClass().getName())); + wb.setProperty(new StringProperty(TestElement.TEST_CLASS, WorkBench.class.getName())); + } + + /** + * {@inheritDoc} + */ + public String getDocAnchor() { + return null; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/OnErrorPanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/OnErrorPanel.java new file mode 100644 index 0000000..46b7d6b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/OnErrorPanel.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +import org.apache.jmeter.testelement.OnErrorTestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class OnErrorPanel extends JPanel { + private static final long serialVersionUID = 240L; + + // Sampler error action buttons + private JRadioButton continueBox; + + private JRadioButton stopThrdBox; + + private JRadioButton stopTestBox; + + private JRadioButton stopTestNowBox; + + private JPanel createOnErrorPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("sampler_on_error_action"))); //$NON-NLS-1$ + + ButtonGroup group = new ButtonGroup(); + + continueBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_continue")); //$NON-NLS-1$ + group.add(continueBox); + continueBox.setSelected(true); + panel.add(continueBox); + + stopThrdBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_thread")); //$NON-NLS-1$ + group.add(stopThrdBox); + panel.add(stopThrdBox); + + stopTestBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test")); //$NON-NLS-1$ + group.add(stopTestBox); + panel.add(stopTestBox); + + stopTestNowBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test_now")); //$NON-NLS-1$ + group.add(stopTestNowBox); + panel.add(stopTestNowBox); + + return panel; + } + + /** + * Create a new NamePanel with the default name. + */ + public OnErrorPanel() { + init(); + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout(5, 0)); + add(createOnErrorPanel()); + } + + public void configure(int errorAction) { + stopTestNowBox.setSelected(errorAction == OnErrorTestElement.ON_ERROR_STOPTEST_NOW); + stopTestBox.setSelected(errorAction == OnErrorTestElement.ON_ERROR_STOPTEST); + stopThrdBox.setSelected(errorAction == OnErrorTestElement.ON_ERROR_STOPTHREAD); + continueBox.setSelected(errorAction == OnErrorTestElement.ON_ERROR_CONTINUE); + } + + public int getOnErrorSetting() { + if (stopTestNowBox.isSelected()) { + return OnErrorTestElement.ON_ERROR_STOPTEST_NOW; + } + if (stopTestBox.isSelected()) { + return OnErrorTestElement.ON_ERROR_STOPTEST; + } + if (stopThrdBox.isSelected()) { + return OnErrorTestElement.ON_ERROR_STOPTHREAD; + } + + // Defaults to continue + return OnErrorTestElement.ON_ERROR_CONTINUE; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/ReportGuiPackage.java b/ApacheJmeter/src/org/apache/jmeter/gui/ReportGuiPackage.java new file mode 100644 index 0000000..b89ab74 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/ReportGuiPackage.java @@ -0,0 +1,617 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.awt.Component; +import java.awt.event.MouseEvent; +import java.beans.Introspector; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.report.engine.ValueReplacer; +import org.apache.jmeter.report.gui.tree.ReportTreeListener; +import org.apache.jmeter.report.gui.tree.ReportTreeModel; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.TestBeanGUI; +import org.apache.jmeter.testelement.ReportPlan; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * ReportGuiPackage is based on GuiPackage, but with changes for + * the reporting tool. Because of how the gui components work, it + * was safer to just make a new class, rather than braking existing + * JMeter gui code. + * + */ +public final class ReportGuiPackage implements LocaleChangeListener { + /** Logging. */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Object LOCK = new Object(); + + /** Singleton instance. */ + private static volatile ReportGuiPackage guiPack; + + /** + * Flag indicating whether or not parts of the tree have changed since they + * were last saved. + */ + private boolean dirty = false; + + /** + * Map from TestElement to JMeterGUIComponent, mapping the nodes in the tree + * to their corresponding GUI components. + */ + private Map nodesToGui = new HashMap(); + + /** + * Map from Class to JMeterGUIComponent, mapping the Class of a GUI + * component to an instance of that component. + */ + private Map, JMeterGUIComponent> guis = new HashMap, JMeterGUIComponent>(); + + /** + * Map from Class to TestBeanGUI, mapping the Class of a TestBean to an + * instance of TestBeanGUI to be used to edit such components. + */ + private Map, JMeterGUIComponent> testBeanGUIs = new HashMap, JMeterGUIComponent>(); + + /** The currently selected node in the tree. */ + private ReportTreeNode currentNode = null; + + private boolean currentNodeUpdated = false; + + /** The model for JMeter's test tree. */ + private ReportTreeModel treeModel; + + /** The listener for JMeter's test tree. */ + private ReportTreeListener treeListener; + + /** The main JMeter frame. */ + private ReportMainFrame mainFrame; + + /** + * Private constructor to permit instantiation only from within this class. + * Use {@link #getInstance()} to retrieve a singleton instance. + */ + private ReportGuiPackage() { + JMeterUtils.addLocaleChangeListener(this); + } + + /** + * Retrieve the singleton GuiPackage instance. + * + * @return the GuiPackage instance + */ + public static ReportGuiPackage getInstance() { + if (guiPack == null){ + log.error("ReportGuiPackage is null"); + } + return guiPack; + } + + /** + * When GuiPackage is requested for the first time, it should be given + * handles to JMeter's Tree Listener and TreeModel. + * + * @param listener + * the TreeListener for JMeter's test tree + * @param treeModel + * the model for JMeter's test tree + * + * @return GuiPackage + */ + public static ReportGuiPackage getInstance(ReportTreeListener listener, ReportTreeModel treeModel) { + if (guiPack == null) { + synchronized (LOCK) { + if(guiPack== null) { + guiPack = new ReportGuiPackage(); + guiPack.setTreeListener(listener); + guiPack.setTreeModel(treeModel); + } + } + } + return guiPack; + } + + /** + * Get a JMeterGUIComponent for the specified test element. If the GUI has + * already been created, that instance will be returned. Otherwise, if a GUI + * component of the same type has been created, and the component is not + * marked as an {@link UnsharedComponent}, that shared component will be + * returned. Otherwise, a new instance of the component will be created. The + * TestElement's GUI_CLASS property will be used to determine the + * appropriate type of GUI component to use. + * + * @param node + * the test element which this GUI is being created for + * + * @return the GUI component corresponding to the specified test element + */ + public JMeterGUIComponent getGui(TestElement node) { + String testClassName = node.getPropertyAsString(TestElement.TEST_CLASS); + String guiClassName = node.getPropertyAsString(TestElement.GUI_CLASS); + try { + Class testClass; + if (testClassName.equals("")) { + testClass = node.getClass(); + } else { + testClass = Class.forName(testClassName); + } + Class guiClass = null; + if (!guiClassName.equals("")) { + guiClass = Class.forName(guiClassName); + } + return getGui(node, guiClass, testClass); + } catch (ClassNotFoundException e) { + log.error("Could not get GUI for " + node, e); + return null; + } + } + + /** + * Get a JMeterGUIComponent for the specified test element. If the GUI has + * already been created, that instance will be returned. Otherwise, if a GUI + * component of the same type has been created, and the component is not + * marked as an {@link UnsharedComponent}, that shared component will be + * returned. Otherwise, a new instance of the component will be created. + * + * @param node + * the test element which this GUI is being created for + * @param guiClass + * the fully qualifed class name of the GUI component which will + * be created if it doesn't already exist + * @param testClass + * the fully qualifed class name of the test elements which have + * to be edited by the returned GUI component + * + * @return the GUI component corresponding to the specified test element + */ + public JMeterGUIComponent getGui(TestElement node, Class guiClass, Class testClass) { + try { + JMeterGUIComponent comp = nodesToGui.get(node); + if (comp == null) { + comp = getGuiFromCache(guiClass, testClass); + nodesToGui.put(node, comp); + } + log.debug("Gui retrieved = " + comp); + return comp; + } catch (Exception e) { + log.error("Problem retrieving gui", e); + return null; + } + } + + /** + * Remove a test element from the tree. This removes the reference to any + * associated GUI component. + * + * @param node + * the test element being removed + */ + public void removeNode(TestElement node) { + nodesToGui.remove(node); + } + + /** + * Convenience method for grabbing the gui for the current node. + * + * @return the GUI component associated with the currently selected node + */ + public JMeterGUIComponent getCurrentGui() { + try { + updateCurrentNode(); + TestElement curNode = treeListener.getCurrentNode().getTestElement(); + JMeterGUIComponent comp = getGui(curNode); + comp.clearGui(); + log.debug("Updating gui to new node"); + comp.configure(curNode); + currentNodeUpdated = false; + return comp; + } catch (Exception e) { + log.error("Problem retrieving gui", e); + return null; + } + } + + /** + * Find the JMeterTreeNode for a certain TestElement object. + * + * @param userObject + * the test element to search for + * @return the tree node associated with the test element + */ + public ReportTreeNode getNodeOf(TestElement userObject) { + return treeModel.getNodeOf(userObject); + } + + /** + * Create a TestElement corresponding to the specified GUI class. + * + * @param guiClass + * the fully qualified class name of the GUI component or a + * TestBean class for TestBeanGUIs. + * @param testClass + * the fully qualified class name of the test elements edited by + * this GUI component. + * @return the test element corresponding to the specified GUI class. + */ + public TestElement createTestElement(Class guiClass, Class testClass) { + try { + JMeterGUIComponent comp = getGuiFromCache(guiClass, testClass); + comp.clearGui(); + TestElement node = comp.createTestElement(); + nodesToGui.put(node, comp); + return node; + } catch (Exception e) { + log.error("Problem retrieving gui", e); + return null; + } + } + + /** + * Create a TestElement for a GUI or TestBean class. + *

+ * This is a utility method to help actions do with one single String + * parameter. + * + * @param objClass + * the fully qualified class name of the GUI component or of the + * TestBean subclass for which a TestBeanGUI is wanted. + * @return the test element corresponding to the specified GUI class. + */ + public TestElement createTestElement(String objClass) { + JMeterGUIComponent comp; + Class c; + try { + c = Class.forName(objClass); + if (TestBean.class.isAssignableFrom(c)) { + comp = getGuiFromCache(TestBeanGUI.class, c); + } else { + comp = getGuiFromCache(c, null); + } + comp.clearGui(); + TestElement node = comp.createTestElement(); + nodesToGui.put(node, comp); + return node; + } catch (NoClassDefFoundError e) { + log.error("Problem retrieving gui for " + objClass, e); + throw new RuntimeException(e.toString()); // Probably a missing + // jar + } catch (ClassNotFoundException e) { + log.error("Problem retrieving gui for " + objClass, e); + throw new RuntimeException(e.toString()); // Programming error: + // bail out. + } catch (InstantiationException e) { + log.error("Problem retrieving gui for " + objClass, e); + throw new RuntimeException(e.toString()); // Programming error: + // bail out. + } catch (IllegalAccessException e) { + log.error("Problem retrieving gui for " + objClass, e); + throw new RuntimeException(e.toString()); // Programming error: + // bail out. + } + } + + /** + * Get an instance of the specified JMeterGUIComponent class. If an instance + * of the GUI class has previously been created and it is not marked as an + * {@link UnsharedComponent}, that shared instance will be returned. + * Otherwise, a new instance of the component will be created, and shared + * components will be cached for future retrieval. + * + * @param guiClass + * the fully qualified class name of the GUI component. This + * class must implement JMeterGUIComponent. + * @param testClass + * the fully qualified class name of the test elements edited by + * this GUI component. This class must implement TestElement. + * @return an instance of the specified class + * + * @throws InstantiationException + * if an instance of the object cannot be created + * @throws IllegalAccessException + * if access rights do not allow the default constructor to be + * called + * @throws ClassNotFoundException + * if the specified GUI class cannot be found + */ + private JMeterGUIComponent getGuiFromCache(Class guiClass, Class testClass) throws InstantiationException, + IllegalAccessException { + JMeterGUIComponent comp; + if (guiClass == TestBeanGUI.class) { + comp = testBeanGUIs.get(testClass); + if (comp == null) { + comp = new TestBeanGUI(testClass); + testBeanGUIs.put(testClass, comp); + } + } else { + comp = guis.get(guiClass); + if (comp == null) { + comp = (JMeterGUIComponent) guiClass.newInstance(); + if (!(comp instanceof UnsharedComponent)) { + guis.put(guiClass, comp); + } + } + } + return comp; + } + + /** + * Update the GUI for the currently selected node. The GUI component is + * configured to reflect the settings in the current tree node. + * + */ + public void updateCurrentGui() { + updateCurrentNode(); + currentNode = treeListener.getCurrentNode(); + TestElement element = currentNode.getTestElement(); + JMeterGUIComponent comp = getGui(element); + comp.configure(element); + currentNodeUpdated = false; + } + + /** + * This method should be called in order for GuiPackage to change the + * current node. This will save any changes made to the earlier node before + * choosing the new node. + */ + public void updateCurrentNode() { + try { + if (currentNode != null && !currentNodeUpdated) { + log.debug("Updating current node " + currentNode.getName()); + JMeterGUIComponent comp = getGui(currentNode.getTestElement()); + TestElement el = currentNode.getTestElement(); + comp.modifyTestElement(el); + } + if (currentNode != treeListener.getCurrentNode()) { + currentNodeUpdated = true; + } + currentNode = treeListener.getCurrentNode(); + } catch (Exception e) { + log.error("Problem retrieving gui", e); + } + } + + public ReportTreeNode getCurrentNode() { + return treeListener.getCurrentNode(); + } + + public TestElement getCurrentElement() { + return getCurrentNode().getTestElement(); + } + + /** + * The dirty property is a flag that indicates whether there are parts of + * JMeter's test tree that the user has not saved since last modification. + * Various (@link Command actions) set this property when components are + * modified/created/saved. + * + * @param dirty + * the new value of the dirty flag + */ + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + /** + * Retrieves the state of the 'dirty' property, a flag that indicates if + * there are test tree components that have been modified since they were + * last saved. + * + * @return true if some tree components have been modified since they were + * last saved, false otherwise + */ + public boolean isDirty() { + return dirty; + } + + /** + * Add a subtree to the currently selected node. + * + * @param subTree + * the subtree to add. + * + * @return the resulting subtree starting with the currently selected node + * + * @throws IllegalUserActionException + * if a subtree cannot be added to the currently selected node + */ + public HashTree addSubTree(HashTree subTree) throws IllegalUserActionException { + return treeModel.addSubTree(subTree, treeListener.getCurrentNode()); + } + + /** + * Get the currently selected subtree. + * + * @return the subtree of the currently selected node + */ + public HashTree getCurrentSubTree() { + return treeModel.getCurrentSubTree(treeListener.getCurrentNode()); + } + + /** + * Get the model for JMeter's test tree. + * + * @return the JMeter tree model + */ + public ReportTreeModel getTreeModel() { + return treeModel; + } + + /** + * Set the model for JMeter's test tree. + * + * @param newTreeModel + * the new JMeter tree model + */ + public void setTreeModel(ReportTreeModel newTreeModel) { + treeModel = newTreeModel; + } + + /** + * Get a ValueReplacer for the test tree. + * + * @return a ValueReplacer configured for the test tree + */ + public ValueReplacer getReplacer() { + return new ValueReplacer((ReportPlan) ((ReportTreeNode) getTreeModel().getReportPlan().getArray()[0]) + .getTestElement()); + } + + /** + * Set the main JMeter frame. + * + * @param newMainFrame + * the new JMeter main frame + */ + public void setMainFrame(ReportMainFrame newMainFrame) { + this.mainFrame = newMainFrame; + } + + /** + * Get the main JMeter frame. + * + * @return the main JMeter frame + */ + public ReportMainFrame getMainFrame() { + return this.mainFrame; + } + + /** + * Set the listener for JMeter's test tree. + * + * @param newTreeListener + * the new JMeter test tree listener + */ + public void setTreeListener(ReportTreeListener newTreeListener) { + treeListener = newTreeListener; + } + + /** + * Get the listener for JMeter's test tree. + * + * @return the JMeter test tree listener + */ + public ReportTreeListener getTreeListener() { + return treeListener; + } + + /** + * Display the specified popup menu with the source component and location + * from the specified mouse event. + * + * @param e + * the mouse event causing this popup to be displayed + * @param popup + * the popup menu to display + */ + public void displayPopUp(MouseEvent e, JPopupMenu popup) { + displayPopUp((Component) e.getSource(), e, popup); + } + + /** + * Display the specified popup menu at the location specified by a mouse + * event with the specified source component. + * + * @param invoker + * the source component + * @param e + * the mouse event causing this popup to be displayed + * @param popup + * the popup menu to display + */ + public void displayPopUp(Component invoker, MouseEvent e, JPopupMenu popup) { + if (popup != null) { + log.debug("Showing pop up for " + invoker + " at x,y = " + e.getX() + "," + e.getY()); + + popup.pack(); + popup.show(invoker, e.getX(), e.getY()); + popup.setVisible(true); + popup.requestFocus(); + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.jmeter.util.LocaleChangeListener#localeChanged(org.apache.jmeter.util.LocaleChangeEvent) + */ + public void localeChanged(LocaleChangeEvent event) { + // FIrst make sure we save the content of the current GUI (since we + // will flush it away): + updateCurrentNode(); + + // Forget about all GUIs we've created so far: we'll need to re-created + // them all! + guis = new HashMap, JMeterGUIComponent>(); + nodesToGui = new HashMap(); + testBeanGUIs = new HashMap, JMeterGUIComponent>(); + + // BeanInfo objects also contain locale-sensitive data -- flush them + // away: + Introspector.flushCaches(); + + // Now put the current GUI in place. [This code was copied from the + // EditCommand action -- we can't just trigger the action because that + // would populate the current node with the contents of the new GUI -- + // which is empty.] + ReportMainFrame mf = getMainFrame(); // Fetch once + if (mf == null) // Probably caused by unit testing on headless system + { + log.warn("Mainframe is null"); + } else { + mf.setMainPanel((javax.swing.JComponent) getCurrentGui()); + mf.setEditMenu(getTreeListener().getCurrentNode().createPopupMenu()); + } + } + + private String reportPlanFile; + + /** + * Sets the filepath of the current test plan. It's shown in the main frame + * title and used on saving. + * + * @param f + */ + public void setReportPlanFile(String f) { + reportPlanFile = f; + ReportGuiPackage.getInstance().getMainFrame().setExtendedFrameTitle(reportPlanFile); + try { + FileServer.getFileServer().setBasedir(reportPlanFile); + } catch (IllegalStateException e1) { + log.error("Failure setting file server's base dir", e1); + } + } + + public String getReportPlanFile() { + return reportPlanFile; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/ReportMainFrame.java b/ApacheJmeter/src/org/apache/jmeter/gui/ReportMainFrame.java new file mode 100644 index 0000000..4fa2649 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/ReportMainFrame.java @@ -0,0 +1,453 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTree; +import javax.swing.MenuElement; +import javax.swing.SwingUtilities; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreeModel; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.gui.util.ReportMenuBar; +import org.apache.jmeter.report.gui.action.ReportActionRouter; +import org.apache.jmeter.report.gui.tree.ReportCellRenderer; +import org.apache.jmeter.report.gui.tree.ReportTreeListener; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * ReportMainFrame is based on MainFrame. it uses the same basic structure, + * but with changes for the report gui. + * + */ +public class ReportMainFrame extends JFrame implements TestListener, Remoteable { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // The default title for the Menu bar + private static final String DEFAULT_TITLE = + "Apache JMeter ("+JMeterUtils.getJMeterVersion()+")"; // $NON-NLS-1$ $NON-NLS-2$ + + /** The menu bar. */ + protected ReportMenuBar menuBar; + + /** The main panel where components display their GUIs. */ + protected JScrollPane mainPanel; + + /** The panel where the test tree is shown. */ + protected JScrollPane treePanel; + + /** The test tree. */ + protected JTree tree; + + /** An image which is displayed when a test is running. */ + //private ImageIcon runningIcon = JMeterUtils.getImage("thread.enabled.gif"); + + /** An image which is displayed when a test is not currently running. */ + private ImageIcon stoppedIcon = JMeterUtils.getImage("thread.disabled.gif");// $NON-NLS-1$ + + /** The x coordinate of the last location where a component was dragged. */ + private int previousDragXLocation = 0; + + /** The y coordinate of the last location where a component was dragged. */ + private int previousDragYLocation = 0; + + /** The button used to display the running/stopped image. */ + private JButton runningIndicator; + + /** The set of currently running hosts. */ + //private Set hosts = new HashSet(); + + /** A message dialog shown while JMeter threads are stopping. */ + private JDialog stoppingMessage; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public ReportMainFrame(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + /** + * Create a new JMeter frame. + * + * @param actionHandler + * this parameter is not used + * @param treeModel + * the model for the test tree + * @param treeListener + * the listener for the test tree + */ + public ReportMainFrame(ActionListener actionHandler, TreeModel treeModel, + ReportTreeListener treeListener) { + runningIndicator = new JButton(stoppedIcon); + runningIndicator.setMargin(new Insets(0, 0, 0, 0)); + runningIndicator.setBorder(BorderFactory.createEmptyBorder()); + + this.tree = this.makeTree(treeModel,treeListener); + + ReportGuiPackage.getInstance().setMainFrame(this); + init(); + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + } + + // MenuBar related methods + // TODO: Do we really need to have all these menubar methods duplicated + // here? Perhaps we can make the menu bar accessible through GuiPackage? + + /** + * Specify whether or not the File|Load menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setFileLoadEnabled(boolean enabled) { + menuBar.setFileLoadEnabled(enabled); + } + + /** + * Specify whether or not the File|Save menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setFileSaveEnabled(boolean enabled) { + menuBar.setFileSaveEnabled(enabled); + } + + /** + * Set the menu that should be used for the Edit menu. + * + * @param menu + * the new Edit menu + */ + public void setEditMenu(JPopupMenu menu) { + menuBar.setEditMenu(menu); + } + + /** + * Specify whether or not the Edit menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setEditEnabled(boolean enabled) { + menuBar.setEditEnabled(enabled); + } + + /** + * Set the menu that should be used for the Edit|Add menu. + * + * @param menu + * the new Edit|Add menu + */ + public void setEditAddMenu(JMenu menu) { + menuBar.setEditAddMenu(menu); + } + + /** + * Specify whether or not the Edit|Add menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setEditAddEnabled(boolean enabled) { + menuBar.setEditAddEnabled(enabled); + } + + /** + * Specify whether or not the Edit|Remove menu item should be enabled. + * + * @param enabled + * true if the menu item should be enabled, false otherwise + */ + public void setEditRemoveEnabled(boolean enabled) { + menuBar.setEditRemoveEnabled(enabled); + } + + /** + * Close the currently selected menu. + */ + public void closeMenu() { + if (menuBar.isSelected()) { + MenuElement[] menuElement = menuBar.getSubElements(); + if (menuElement != null) { + for (int i = 0; i < menuElement.length; i++) { + JMenu menu = (JMenu) menuElement[i]; + if (menu.isSelected()) { + menu.setPopupMenuVisible(false); + menu.setSelected(false); + break; + } + } + } + } + } + /** + * Show a dialog indicating that JMeter threads are stopping on a particular + * host. + * + * @param host + * the host where JMeter threads are stopping + */ + public void showStoppingMessage(String host) { + stoppingMessage = new JDialog(this, JMeterUtils.getResString("stopping_test_title"), true);// $NON-NLS-1$ + JLabel stopLabel = new JLabel(JMeterUtils.getResString("stopping_test") + ": " + host);// $NON-NLS-1$ + stopLabel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + stoppingMessage.getContentPane().add(stopLabel); + stoppingMessage.pack(); + ComponentUtil.centerComponentInComponent(this, stoppingMessage); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (stoppingMessage != null) { + stoppingMessage.setVisible(true); + } + } + }); + } + + public void setMainPanel(JComponent comp) { + mainPanel.setViewportView(comp); + } + + public JTree getTree() { + return this.tree; + } + + // TestListener implementation + + /** + * Not sure if this should be in the ReportMainFrame, since the + * report component doesn't really test, it generates reports. for + * now, I will use it to trigger reporting. Later we can refactor + * MainFrame and create an abstract base class. + */ + public void testStarted() { + + // super.testStarted(); + } + + /** + * Not sure if this should be in the ReportMainFrame, since the + * report component doesn't really test, it generates reports. for + * now, I will use it to trigger reporting. Later we can refactor + * MainFrame and create an abstract base class. + */ + public void testStarted(String host) { + // super.testStarted(host); + } + + /** + * Not sure if this should be in the ReportMainFrame, since the + * report component doesn't really test, it generates reports. for + * now, I will use it to trigger reporting. Later we can refactor + * MainFrame and create an abstract base class. + */ + public void testEnded() { + // super.testEnded(); + } + + /** + * Not sure if this should be in the ReportMainFrame, since the + * report component doesn't really test, it generates reports. for + * now, I will use it to trigger reporting. Later we can refactor + * MainFrame and create an abstract base class. + */ + public void testEnded(String host) { + // super.testEnded(host); + } + + /* Implements TestListener#testIterationStart(LoopIterationEvent) */ + public void testIterationStart(LoopIterationEvent event) { + } + + /** + * Create the GUI components and layout. + */ + private void init() {// called from ctor, so must not be overridable + menuBar = new ReportMenuBar(); + setJMenuBar(menuBar); + + JPanel all = new JPanel(new BorderLayout()); + all.add(createToolBar(), BorderLayout.NORTH); + + JSplitPane treeAndMain = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + + treePanel = createTreePanel(); + treeAndMain.setLeftComponent(treePanel); + + mainPanel = createMainPanel(); + treeAndMain.setRightComponent(mainPanel); + + treeAndMain.setResizeWeight(.2); + treeAndMain.setContinuousLayout(true); + all.add(treeAndMain, BorderLayout.CENTER); + + getContentPane().add(all); + + tree.setSelectionRow(1); + addWindowListener(new WindowHappenings()); + + setTitle(DEFAULT_TITLE); + setIconImage(JMeterUtils.getImage("jmeter.jpg").getImage());// $NON-NLS-1$ + } + + public void setExtendedFrameTitle(String fname) { + // file New operation may set to null, so just return app name + if (fname == null) { + setTitle(DEFAULT_TITLE); + return; + } + + // allow for windows / chars in filename + String temp = fname.replace('\\', '/'); // $NON-NLS-1$ // $NON-NLS-2$ + String simpleName = temp.substring(temp.lastIndexOf("/") + 1);// $NON-NLS-1$ + setTitle(simpleName + " (" + fname + ") - " + DEFAULT_TITLE); // $NON-NLS-1$ // $NON-NLS-2$ + } + + /** + * Create the JMeter tool bar pane containing the running indicator. + * + * @return a panel containing the running indicator + */ + protected Component createToolBar() { + Box toolPanel = new Box(BoxLayout.X_AXIS); + toolPanel.add(Box.createRigidArea(new Dimension(10, 15))); + toolPanel.add(Box.createGlue()); + toolPanel.add(runningIndicator); + return toolPanel; + } + + /** + * Create the panel where the GUI representation of the test tree is + * displayed. The tree should already be created before calling this method. + * + * @return a scroll pane containing the test tree GUI + */ + protected JScrollPane createTreePanel() { + JScrollPane treeP = new JScrollPane(tree); + treeP.setMinimumSize(new Dimension(100, 0)); + return treeP; + } + + /** + * Create the main panel where components can display their GUIs. + * + * @return the main scroll pane + */ + protected JScrollPane createMainPanel() { + return new JScrollPane(); + } + + /** + * Create and initialize the GUI representation of the test tree. + * + * @param treeModel + * the test tree model + * @param treeListener + * the test tree listener + * + * @return the initialized test tree GUI + */ + private JTree makeTree(TreeModel treeModel, ReportTreeListener treeListener) { + JTree treevar = new JTree(treeModel); + treevar.setCellRenderer(getCellRenderer()); + treevar.setRootVisible(false); + treevar.setShowsRootHandles(true); + + treeListener.setJTree(treevar); + treevar.addTreeSelectionListener(treeListener); + treevar.addMouseListener(treeListener); + treevar.addMouseMotionListener(treeListener); + treevar.addKeyListener(treeListener); + + return treevar; + } + + /** + * Create the tree cell renderer used to draw the nodes in the test tree. + * + * @return a renderer to draw the test tree nodes + */ + protected TreeCellRenderer getCellRenderer() { + DefaultTreeCellRenderer rend = new ReportCellRenderer(); + rend.setFont(new Font("Dialog", Font.PLAIN, 11)); + return rend; + } + + public void drawDraggedComponent(Component dragIcon, int x, int y) { + Dimension size = dragIcon.getPreferredSize(); + treePanel.paintImmediately(previousDragXLocation, previousDragYLocation, size.width, size.height); + this.getLayeredPane().setLayer(dragIcon, 400); + SwingUtilities.paintComponent(treePanel.getGraphics(), dragIcon, treePanel, x, y, size.width, size.height); + previousDragXLocation = x; + previousDragYLocation = y; + } + + /** + * A window adapter used to detect when the main JMeter frame is being + * closed. + */ + protected static class WindowHappenings extends WindowAdapter { + /** + * Called when the main JMeter frame is being closed. Sends a + * notification so that JMeter can react appropriately. + * + * @param event + * the WindowEvent to handle + */ + @Override + public void windowClosing(WindowEvent event) { + ReportActionRouter.getInstance().actionPerformed(new ActionEvent(this, event.getID(), "exit")); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/SavePropertyDialog.java b/ApacheJmeter/src/org/apache/jmeter/gui/SavePropertyDialog.java new file mode 100644 index 0000000..e0d2de0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/SavePropertyDialog.java @@ -0,0 +1,154 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 15, 2004 + */ +package org.apache.jmeter.gui; + +import java.awt.BorderLayout; +import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JPanel; + +import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.log.Logger; + +/** + * Generates Configure pop-up dialogue for Listeners from all methods in SampleSaveConfiguration + * with the signature "boolean saveXXX()". + * There must be a corresponding "void setXXX(boolean)" method, and a property save_XXX which is + * used to name the field on the dialogue. + * + */ +public class SavePropertyDialog extends JDialog implements ActionListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + private static final Map functors = new HashMap(); + + private static final String NAME_SAVE_PFX = "save"; // $NON-NLS-1$ i.e. boolean saveXXX() + private static final String NAME_SET_PREFIX = "set"; // $NON-NLS-1$ i.e. void setXXX(boolean) + private static final String RESOURCE_PREFIX = "save_"; // $NON-NLS-1$ e.g. save_XXX property + private static final int NAME_SAVE_PFX_LEN = NAME_SAVE_PFX.length(); + + private SampleSaveConfiguration saveConfig; + + public SavePropertyDialog(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + /** + * @param owner + * @param title + * @param modal + * @throws java.awt.HeadlessException + */ + public SavePropertyDialog(Frame owner, String title, boolean modal, SampleSaveConfiguration s) + // throws HeadlessException + { + super(owner, title, modal); + saveConfig = s; + log.debug("SampleSaveConfiguration = " + saveConfig);// $NON-NLS-1$ + initDialog(); + } + + private int countMethods(Method[] m) { + int count = 0; + for (int i = 0; i < m.length; i++) { + if (m[i].getName().startsWith(NAME_SAVE_PFX)) { + count++; + } + } + return count; + } + + private void initDialog() { + this.getContentPane().setLayout(new BorderLayout()); + Method[] methods = SampleSaveConfiguration.class.getMethods(); + int x = (countMethods(methods) / 3) + 1; + log.debug("grid panel is " + 3 + " by " + x); + JPanel checkPanel = new JPanel(new GridLayout(x, 3)); + for (int i = 0; i < methods.length; i++) { + String name = methods[i].getName(); + if (name.startsWith(NAME_SAVE_PFX) && methods[i].getParameterTypes().length == 0) { + try { + name = name.substring(NAME_SAVE_PFX_LEN); + JCheckBox check = new JCheckBox( + JMeterUtils.getResString(RESOURCE_PREFIX + name)// $NON-NLS-1$ + ,((Boolean) methods[i].invoke(saveConfig, new Object[0])).booleanValue()); + checkPanel.add(check, BorderLayout.NORTH); + check.addActionListener(this); + String actionCommand = NAME_SET_PREFIX + name; // $NON-NLS-1$ + check.setActionCommand(actionCommand); + if (!functors.containsKey(actionCommand)) { + functors.put(actionCommand, new Functor(actionCommand)); + } + } catch (IllegalAccessException e) { + log.warn("Problem creating save config dialog", e); + } catch (InvocationTargetException e) { + log.warn("Problem creating save config dialog", e); + } + } + } + getContentPane().add(checkPanel, BorderLayout.NORTH); + JButton exit = new JButton(JMeterUtils.getResString("done")); // $NON-NLS-1$ + this.getContentPane().add(exit, BorderLayout.SOUTH); + exit.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dispose(); + } + }); + } + + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + Functor f = functors.get(action); + f.invoke(saveConfig, new Object[] { + Boolean.valueOf(((JCheckBox) e.getSource()).isSelected()) }); + } + + /** + * @return Returns the saveConfig. + */ + public SampleSaveConfiguration getSaveConfig() { + return saveConfig; + } + + /** + * @param saveConfig + * The saveConfig to set. + */ + public void setSaveConfig(SampleSaveConfiguration saveConfig) { + this.saveConfig = saveConfig; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/Searchable.java b/ApacheJmeter/src/org/apache/jmeter/gui/Searchable.java new file mode 100644 index 0000000..f6e5e1e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/Searchable.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.util.List; + +/** + * Interface for nodes that are searchable + */ +public interface Searchable { + /** + * @return List of searchable tokens + * @throws Exception + */ + List getSearchableTokens() + throws Exception; +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/ServerPanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/ServerPanel.java new file mode 100644 index 0000000..595eb26 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/ServerPanel.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Common server panel implementation for use with HTTP, TCP etc samplers + */ +public class ServerPanel extends JPanel { + + private static final long serialVersionUID = -2749091243070619669L; + + private JTextField domain; + + private JTextField port; + + private JTextField connectTimeOut; + + private JTextField responseTimeOut; + + /** + * create the target server panel. + *

    + *
  • Server IP
  • + *
  • Server Port
  • + *
  • Connect Timeout
  • + *
  • Response Timeout
  • + *
+ */ + public ServerPanel() { + init(); + } + + /** + * clear all the fields + */ + public void clear() { + domain.setText(""); + port.setText(""); + connectTimeOut.setText(""); + responseTimeOut.setText(""); + } + + public String getServer(){ + return domain.getText(); + } + + public void setServer(String value){ + domain.setText(value); + } + + public String getPort(){ + return port.getText(); + } + + public void setPort(String value){ + port.setText(value); + } + + public String getConnectTimeout(){ + return connectTimeOut.getText(); + } + + public void setConnectTimeout(String value){ + connectTimeOut.setText(value); + } + + public String getResponseTimeout(){ + return responseTimeOut.getText(); + } + + public void setResponseTimeout(String value){ + responseTimeOut.setText(value); + } + + private void init() { + setLayout(new BorderLayout(5, 0)); + // Target server panel + JPanel webServerPanel = new HorizontalPanel(); + webServerPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("target_server"))); // $NON-NLS-1$ + final JPanel domainPanel = getDomainPanel(); + final JPanel portPanel = getPortPanel(); + webServerPanel.add(domainPanel, BorderLayout.CENTER); + webServerPanel.add(portPanel, BorderLayout.EAST); + + JPanel timeOut = new HorizontalPanel(); + timeOut.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_server_timeout_title"))); // $NON-NLS-1$ + final JPanel connPanel = getConnectTimeOutPanel(); + final JPanel reqPanel = getResponseTimeOutPanel(); + timeOut.add(connPanel); + timeOut.add(reqPanel); + + JPanel webServerTimeoutPanel = new VerticalPanel(); + webServerTimeoutPanel.add(webServerPanel, BorderLayout.CENTER); + webServerTimeoutPanel.add(timeOut, BorderLayout.EAST); + + JPanel bigPanel = new VerticalPanel(); + bigPanel.add(webServerTimeoutPanel); + + add(bigPanel); + } + + private JPanel getDomainPanel() { + domain = new JTextField(20); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_domain")); // $NON-NLS-1$ + label.setLabelFor(domain); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(domain, BorderLayout.CENTER); + return panel; + } + + private JPanel getPortPanel() { + port = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_port")); // $NON-NLS-1$ + label.setLabelFor(port); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(port, BorderLayout.CENTER); + + return panel; + } + + private JPanel getConnectTimeOutPanel() { + connectTimeOut = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_timeout_connect")); // $NON-NLS-1$ + label.setLabelFor(connectTimeOut); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(connectTimeOut, BorderLayout.CENTER); + + return panel; + } + + private JPanel getResponseTimeOutPanel() { + responseTimeOut = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_timeout_response")); // $NON-NLS-1$ + label.setLabelFor(responseTimeOut); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(responseTimeOut, BorderLayout.CENTER); + + return panel; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/Stoppable.java b/ApacheJmeter/src/org/apache/jmeter/gui/Stoppable.java new file mode 100644 index 0000000..fa9fa0e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/Stoppable.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui; + +/** + * Interface that identifies processes to stop on close or load of new project files + */ +public interface Stoppable { + + /** + * Stop server + */ + public void stopServer(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/UnsharedComponent.java b/ApacheJmeter/src/org/apache/jmeter/gui/UnsharedComponent.java new file mode 100644 index 0000000..952fb01 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/UnsharedComponent.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.jmeter.gui; + +/** + * Marker interface indicating that an instance of a component cannot be shared. + * The GUI instance will be shared among all test elements of a given type if + * the GUI component class does not implement this interface. + * + */ +public interface UnsharedComponent { +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/AboutCommand.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/AboutCommand.java new file mode 100644 index 0000000..4cb7c2f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/AboutCommand.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; + +/** + * About Command. It may be extended in the future to add a list of installed + * protocols, config options, etc. + * + */ +public class AboutCommand implements Command { + private static final Set commandSet; + + private static JDialog about; + + static { + HashSet commands = new HashSet(); + commands.add(ActionNames.ABOUT); + commandSet = Collections.unmodifiableSet(commands); + } + + /** + * Handle the "about" action by displaying the "About Apache JMeter..." + * dialog box. The Dialog Box is NOT modal, because those should be avoided + * if at all possible. + */ + public void doAction(ActionEvent e) { + if (e.getActionCommand().equals(ActionNames.ABOUT)) { + this.about(); + } + } + + /** + * Provide the list of Action names that are available in this command. + */ + public Set getActionNames() { + return AboutCommand.commandSet; + } + + /** + * Called by about button. Raises about dialog. Currently the about box has + * the product image and the copyright notice. The dialog box is centered + * over the MainFrame. + */ + void about() { + JFrame mainFrame = GuiPackage.getInstance().getMainFrame(); + if (about == null) { + about = new JDialog(mainFrame, "About Apache JMeter...", false); + about.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + about.setVisible(false); + } + }); + + JLabel jmeter = new JLabel(JMeterUtils.getImage("jmeter.jpg")); + JLabel copyright = new JLabel(JMeterUtils.getJMeterCopyright(), JLabel.CENTER); + JLabel rights = new JLabel("All Rights Reserved.", JLabel.CENTER); + JLabel version = new JLabel("Apache JMeter Version " + JMeterUtils.getJMeterVersion(), JLabel.CENTER); + JPanel infos = new JPanel(); + infos.setOpaque(false); + infos.setLayout(new GridLayout(0, 1)); + infos.setBorder(new EmptyBorder(5, 5, 5, 5)); + infos.add(copyright); + infos.add(rights); + infos.add(version); + Container panel = about.getContentPane(); + panel.setLayout(new BorderLayout()); + panel.setBackground(Color.white); + panel.add(jmeter, BorderLayout.NORTH); + panel.add(infos, BorderLayout.SOUTH); + } + + // NOTE: these lines center the about dialog in the + // current window. Some older Swing versions have + // a bug in getLocationOnScreen() and they may not + // make this behave properly. + Point p = mainFrame.getLocationOnScreen(); + Dimension d1 = mainFrame.getSize(); + Dimension d2 = about.getSize(); + about.setLocation(p.x + (d1.width - d2.width) / 2, p.y + (d1.height - d2.height) / 2); + about.pack(); + about.setVisible(true); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/AbstractAction.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/AbstractAction.java new file mode 100644 index 0000000..fd979a2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/AbstractAction.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.Set; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public abstract class AbstractAction implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * @see Command#doAction(ActionEvent) + */ + public void doAction(ActionEvent e) { + } + + /** + * @see Command#getActionNames() + */ + abstract public Set getActionNames(); + + /** + * @param e + */ + protected void popupShouldSave(ActionEvent e) { + log.debug("popupShouldSave"); + if (GuiPackage.getInstance().getTestPlanFile() == null) { + if (JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("should_save"), //$NON-NLS-1$ + JMeterUtils.getResString("warning"), //$NON-NLS-1$ + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) { + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(),ActionNames.SAVE)); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/ActionNames.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/ActionNames.java new file mode 100644 index 0000000..f9bca86 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/ActionNames.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +/* + * Collect all the action names together in one place. + * This helps to ensure that there are no duplicates + * + * + */ +public class ActionNames { + + public static final String ABOUT = "about"; // $NON-NLS-1$ + public static final String ACTION_SHUTDOWN = "shutdown"; // $NON-NLS-1$ + public static final String ACTION_START = "start"; // $NON-NLS-1$ + public static final String ACTION_START_NO_TIMERS = "start_no_timers"; // $NON-NLS-1$ + public static final String ACTION_STOP = "stop"; // $NON-NLS-1$ + public static final String ADD = "Add"; // $NON-NLS-1$ + public static final String ADD_ALL = "add_all"; // $NON-NLS-1$ + public static final String ADD_PARENT = "Add Parent"; // $NON-NLS-1$ + public static final String ANALYZE_FILE = "Analyze File"; // $NON-NLS-1$ + public static final String CHANGE_LANGUAGE = "change_language"; // $NON-NLS-1$ + public static final String CHANGE_PARENT = "Change Parent"; // $NON-NLS-1$ + public static final String CHECK_DIRTY = "check_dirty"; // $NON-NLS-1$ + public static final String CHECK_REMOVE = "check_remove"; // $NON-NLS-1$ + public static final String CLEAR = "action.clear"; // $NON-NLS-1$ + public static final String CLEAR_ALL = "action.clear_all"; // $NON-NLS-1$ + public static final String CLOSE = "close"; // $NON-NLS-1$ + public static final String COLLAPSE_ALL = "collapse all"; // $NON-NLS-1$ + public static final String COPY = "Copy"; // $NON-NLS-1$ + public static final String CUT = "Cut"; // $NON-NLS-1$ + public static final String DEBUG_ON = "debug_on"; // $NON-NLS-1$ + public static final String DEBUG_OFF = "debug_off"; // $NON-NLS-1$ + public static final String DISABLE = "disable"; // $NON-NLS-1$ + public static final String DRAG_ADD = "drag_n_drop.add";//$NON-NLS-1$ + /** Copy, then paste afterwards */ + public static final String DUPLICATE = "duplicate"; // $NON-NLS-1$ + public static final String EDIT = "edit"; // $NON-NLS-1$ + public static final String ENABLE = "enable"; // $NON-NLS-1$ + public static final String EXIT = "exit"; // $NON-NLS-1$ + public static final String EXPAND_ALL = "expand all"; // $NON-NLS-1$ + public static final String FUNCTIONS = "functions"; // $NON-NLS-1$ + public static final String HELP = "help"; // $NON-NLS-1$ + public static final String INSERT_AFTER = "drag_n_drop.insert_after";//$NON-NLS-1$ + public static final String INSERT_BEFORE = "drag_n_drop.insert_before";//$NON-NLS-1$ + public static final String LAF_PREFIX = "laf:"; // Look and Feel prefix + public static final String LOGGER_PANEL_ENABLE_DISABLE = "logger_panel_enable_disable"; // $NON-NLS-1$ + public static final String MERGE = "merge"; // $NON-NLS-1$ + public static final String OPEN = "open"; // $NON-NLS-1$ + public static final String OPEN_RECENT = "open_recent"; // $NON-NLS-1$ + public static final String PASTE = "Paste"; // $NON-NLS-1$ + public static final String REMOTE_EXIT = "remote_exit"; // $NON-NLS-1$ + public static final String REMOTE_EXIT_ALL = "remote_exit_all"; // $NON-NLS-1$ + public static final String REMOTE_SHUT = "remote_shut"; // $NON-NLS-1$ + public static final String REMOTE_SHUT_ALL = "remote_shut_all"; // $NON-NLS-1$ + public static final String REMOTE_START = "remote_start"; // $NON-NLS-1$ + public static final String REMOTE_START_ALL = "remote_start_all"; // $NON-NLS-1$ + public static final String REMOTE_STOP = "remote_stop"; // $NON-NLS-1$ + public static final String REMOTE_STOP_ALL = "remote_stop_all"; // $NON-NLS-1$ + public static final String REMOVE = "remove"; // $NON-NLS-1$ + public static final String RESET_GUI = "reset_gui"; // $NON-NLS-1$ + public static final String REVERT_PROJECT = "revert_project"; // $NON-NLS-1$ + public static final String SAVE = "save"; // $NON-NLS-1$ + public static final String SAVE_ALL_AS = "save_all_as"; // $NON-NLS-1$ + public static final String SAVE_AS = "save_as"; // $NON-NLS-1$ + public static final String SAVE_GRAPHICS = "save_graphics"; // $NON-NLS-1$ + public static final String SAVE_GRAPHICS_ALL= "save_graphics_all"; // $NON-NLS-1$ + public static final String SSL_MANAGER = "sslManager"; // $NON-NLS-1$ + public static final String STOP_THREAD = "stop_thread"; // $NON-NLS-1$ + public static final String SUB_TREE_LOADED = "sub_tree_loaded"; // $NON-NLS-1$ + public static final String SUB_TREE_MERGED = "sub_tree_merged"; // $NON-NLS-1$ + public static final String SUB_TREE_SAVED = "sub_tree_saved"; // $NON-NLS-1$ + public static final String TOGGLE = "toggle"; // $NON-NLS-1$ enable/disable + public static final String TOOLBAR = "toolbar"; // $NON-NLS-1$ + public static final String WHAT_CLASS = "what_class"; // $NON-NLS-1$ + public static final String SEARCH_TREE = "search_tree"; // $NON-NLS-1$ + public static final String SEARCH_RESET = "search_reset"; // $NON-NLS-1$ + + // Prevent instantiation + private ActionNames(){ + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/ActionRouter.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/ActionRouter.java new file mode 100644 index 0000000..0e2f7f9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/ActionRouter.java @@ -0,0 +1,307 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.HeadlessException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.SwingUtilities; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JMeterError; +import org.apache.log.Logger; + +public final class ActionRouter implements ActionListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Object LOCK = new Object(); + + private static volatile ActionRouter router; + + private Map> commands = new HashMap>(); + + private final Map> preActionListeners = + new HashMap>(); + + private final Map> postActionListeners = + new HashMap>(); + + private ActionRouter() { + } + + public void actionPerformed(final ActionEvent e) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + performAction(e); + } + + }); + } + + private void performAction(final ActionEvent e) { + String actionCommand = e.getActionCommand(); + try { + try { + GuiPackage.getInstance().updateCurrentGui(); + } catch (Exception err){ + log.error("performAction(" + actionCommand + ") updateCurrentGui() on" + e.toString() + " caused", err); + JMeterUtils.reportErrorToUser("Problem updating GUI - see log file for details"); + } + for (Command c : commands.get(actionCommand)) { + try { + preActionPerformed(c.getClass(), e); + c.doAction(e); + postActionPerformed(c.getClass(), e); + } catch (IllegalUserActionException err) { + String msg = err.getMessage(); + if (msg == null) { + msg = err.toString(); + } + Throwable t = err.getCause(); + if (t != null) { + String cause = t.getMessage(); + if (cause == null) { + cause = t.toString(); + } + msg = msg + "\n" + cause; + } + JMeterUtils.reportErrorToUser(msg); + } catch (Exception err) { + log.error("Error processing "+c.toString(), err); + } + } + } catch (NullPointerException er) { + log.error("performAction(" + actionCommand + ") " + e.toString() + " caused", er); + JMeterUtils.reportErrorToUser("Sorry, this feature (" + actionCommand + ") not yet implemented"); + } + } + + /** + * To execute an action immediately in the current thread. + * + * @param e + * the action to execute + */ + public void doActionNow(ActionEvent e) { + performAction(e); + } + + public Set getAction(String actionName) { + Set set = new HashSet(); + for (Command c : commands.get(actionName)) { + try { + set.add(c); + } catch (Exception err) { + log.error("Could not add Command", err); + } + } + return set; + } + + public Command getAction(String actionName, Class actionClass) { + for (Command com : commands.get(actionName)) { + if (com.getClass().equals(actionClass)) { + return com; + } + } + return null; + } + + public Command getAction(String actionName, String className) { + for (Command com : commands.get(actionName)) { + if (com.getClass().getName().equals(className)) { + return com; + } + } + return null; + } + + /** + * Allows an ActionListener to receive notification of a command being + * executed prior to the actual execution of the command. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.gui.action.Command. + * @param listener + * the ActionListener to receive the notifications + */ + public void addPreActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = preActionListeners.get(action.getName()); + if (set == null) { + set = new HashSet(); + } + set.add(listener); + preActionListeners.put(action.getName(), set); + } + } + + /** + * Allows an ActionListener to be removed from receiving notifications of a + * command being executed prior to the actual execution of the command. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.gui.action.Command. + * @param listener + * the ActionListener to receive the notifications + */ + public void removePreActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = preActionListeners.get(action.getName()); + if (set != null) { + set.remove(listener); + preActionListeners.put(action.getName(), set); + } + } + } + + /** + * Allows an ActionListener to receive notification of a command being + * executed after the command has executed. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.gui.action.Command. + * @param listener + */ + public void addPostActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = postActionListeners.get(action.getName()); + if (set == null) { + set = new HashSet(); + } + set.add(listener); + postActionListeners.put(action.getName(), set); + } + } + + /** + * Allows an ActionListener to be removed from receiving notifications of a + * command being executed after the command has executed. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.gui.action.Command. + * @param listener + */ + public void removePostActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = postActionListeners.get(action.getName()); + if (set != null) { + set.remove(listener); + postActionListeners.put(action.getName(), set); + } + } + } + + protected void preActionPerformed(Class action, ActionEvent e) { + if (action != null) { + HashSet listenerSet = preActionListeners.get(action.getName()); + if (listenerSet != null && listenerSet.size() > 0) { + ActionListener[] listeners = listenerSet.toArray(new ActionListener[listenerSet.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].actionPerformed(e); + } + } + } + } + + protected void postActionPerformed(Class action, ActionEvent e) { + if (action != null) { + HashSet listenerSet = postActionListeners.get(action.getName()); + if (listenerSet != null && listenerSet.size() > 0) { + ActionListener[] listeners = listenerSet.toArray(new ActionListener[listenerSet.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].actionPerformed(e); + } + } + } + } + + private void populateCommandMap() { + try { + List listClasses = ClassFinder.findClassesThatExtend( + JMeterUtils.getSearchPaths(), // strPathsOrJars - pathnames or jarfiles to search for classes + // classNames - required parent class(es) or annotations + new Class[] {Class.forName("org.apache.jmeter.gui.action.Command") }, // $NON-NLS-1$ + false, // innerClasses - should we include inner classes? + // contains - classname should contain this string + // This was added in r325814 as part of changes for the reporting tool + "org.apache.jmeter.gui", // $NON-NLS-1$ + null, // notContains - classname should not contain this string + false); // annotations - true if classnames are annotations + commands = new HashMap>(listClasses.size()); + if (listClasses.isEmpty()) { + log.fatalError("!!!!!Uh-oh, didn't find any action handlers!!!!!"); + throw new JMeterError("No action handlers found - check JMeterHome and libraries"); + } + for (String strClassName : listClasses) { + Class commandClass = Class.forName(strClassName); + Command command = (Command) commandClass.newInstance(); + for (String commandName : command.getActionNames()) { + Set commandObjects = commands.get(commandName); + if (commandObjects == null) { + commandObjects = new HashSet(); + commands.put(commandName, commandObjects); + } + commandObjects.add(command); + } + } + } catch (HeadlessException e){ + log.warn(e.toString()); + } catch (JMeterError e) { + throw e; + } catch (Exception e) { + log.error("exception finding action handlers", e); + } + } + + /** + * Gets the Instance attribute of the ActionRouter class + * + * @return The Instance value + */ + public static ActionRouter getInstance() { + if (router == null) { + synchronized (LOCK) { + if(router == null) { + router = new ActionRouter(); + router.populateCommandMap(); + } + } + } + return router; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/AddParent.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/AddParent.java new file mode 100644 index 0000000..76c6bcb --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/AddParent.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements the Add Parent menu command + */ +public class AddParent implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.ADD_PARENT); + } + + public AddParent() { + } + + public void doAction(ActionEvent e) { + String name = ((Component) e.getSource()).getName(); + GuiPackage guiPackage = GuiPackage.getInstance(); + try { + guiPackage.updateCurrentNode(); + TestElement controller = guiPackage.createTestElement(name); + addParentToTree(controller); + } catch (Exception err) { + log.error("", err); + } + + } + + public Set getActionNames() { + return commands; + } + + protected void addParentToTree(TestElement newParent) { + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeNode newNode = new JMeterTreeNode(newParent, guiPackage.getTreeModel()); + JMeterTreeNode currentNode = guiPackage.getTreeListener().getCurrentNode(); + JMeterTreeNode parentNode = (JMeterTreeNode) currentNode.getParent(); + int index = parentNode.getIndex(currentNode); + guiPackage.getTreeModel().insertNodeInto(newNode, parentNode, index); + JMeterTreeNode[] nodes = guiPackage.getTreeListener().getSelectedNodes(); + for (int i = 0; i < nodes.length; i++) { + moveNode(guiPackage, nodes[i], newNode); + } + } + + private void moveNode(GuiPackage guiPackage, JMeterTreeNode node, JMeterTreeNode newParentNode) { + guiPackage.getTreeModel().removeNodeFromParent(node); + guiPackage.getTreeModel().insertNodeInto(node, newParentNode, newParentNode.getChildCount()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/AddToTree.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/AddToTree.java new file mode 100644 index 0000000..4ce4646 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/AddToTree.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JComponent; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class AddToTree implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commandSet; + + static { + HashSet commands = new HashSet(); + commands.add(ActionNames.ADD); + commandSet = Collections.unmodifiableSet(commands); + } + + + public AddToTree() { + } + + /** + * Gets the Set of actions this Command class responds to. + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commandSet; + } + + /** + * Adds the specified class to the current node of the tree. + */ + public void doAction(ActionEvent e) { + GuiPackage guiPackage = GuiPackage.getInstance(); + try { + guiPackage.updateCurrentNode(); + TestElement testElement = guiPackage.createTestElement(((JComponent) e.getSource()).getName()); + JMeterTreeNode parentNode = guiPackage.getCurrentNode(); + JMeterTreeNode node = guiPackage.getTreeModel().addComponent(testElement, parentNode); + guiPackage.getMainFrame().getTree().setSelectionPath(new TreePath(node.getPath())); + } + catch (IllegalUserActionException err) { + log.error("", err); // $NON-NLS-1$ + String msg = err.getMessage(); + if (msg == null) { + msg=err.toString(); + } + JMeterUtils.reportErrorToUser(msg); + } + catch (Exception err) { + log.error("", err); // $NON-NLS-1$ + String msg = err.getMessage(); + if (msg == null) { + msg=err.toString(); + } + JMeterUtils.reportErrorToUser(msg); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Analyze.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Analyze.java new file mode 100644 index 0000000..4e9b2c8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Analyze.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JFileChooser; + +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.reporters.FileReporter; +import org.apache.jmeter.util.JMeterUtils; + +public class Analyze implements Command { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.ANALYZE_FILE); + } + + public Analyze() { + } + + public Set getActionNames() { + return commands; + } + + public void doAction(ActionEvent e) { + FileReporter analyzer = new FileReporter(); + final JFileChooser chooser = FileDialoger.promptToOpenFile(new String[] { ".jtl" }); //$NON-NLS-1$ + if (chooser != null) { + try { + analyzer.init(chooser.getSelectedFile().getPath()); + } catch (IOException err) { + JMeterUtils.reportErrorToUser("The file you selected could not be analyzed"); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/ChangeLanguage.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/ChangeLanguage.java new file mode 100644 index 0000000..acaf1cb --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/ChangeLanguage.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.log.Logger; + +/** + * @version $Revision: 804421 $ + */ +public class ChangeLanguage implements Command { + private static final Set commands = new HashSet(); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + static { + commands.add(ActionNames.CHANGE_LANGUAGE); + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + public void doAction(ActionEvent e) { + String locale = ((Component) e.getSource()).getName(); + Locale loc; + + int sep = locale.indexOf('_'); + if (sep > 0) { + loc = new Locale(locale.substring(0, sep), locale.substring(sep + 1)); + } else { + loc = new Locale(locale, ""); + } + log.debug("Changing locale to " + loc.toString()); + try { + JMeterUtils.setLocale(loc); + } catch (JMeterError err) { + JMeterUtils.reportErrorToUser(err.toString()); + } + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/ChangeParent.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/ChangeParent.java new file mode 100644 index 0000000..a315600 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/ChangeParent.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.Component; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements the Add Parent menu command + */ +public class ChangeParent implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.CHANGE_PARENT); + } + + public ChangeParent() { + } + + public void doAction(ActionEvent e) { + String name = ((Component) e.getSource()).getName(); + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeNode currentNode = guiPackage.getTreeListener().getCurrentNode(); + if (!(currentNode.getUserObject() instanceof Controller)) { + Toolkit.getDefaultToolkit().beep(); + return; + } + try { + guiPackage.updateCurrentNode(); + TestElement controller = guiPackage.createTestElement(name); + changeParent(controller, guiPackage, currentNode); + } catch (Exception err) { + Toolkit.getDefaultToolkit().beep(); + log.error("Failed to change parent", err); + } + + } + + public Set getActionNames() { + return commands; + } + + private void changeParent(TestElement newParent, GuiPackage guiPackage, JMeterTreeNode currentNode) { + JMeterTreeModel treeModel = guiPackage.getTreeModel(); + JMeterTreeNode newNode = new JMeterTreeNode(newParent, treeModel); + JMeterTreeNode parentNode = (JMeterTreeNode) currentNode.getParent(); + int index = parentNode.getIndex(currentNode); + treeModel.insertNodeInto(newNode, parentNode, index); + treeModel.removeNodeFromParent(currentNode); + int childCount = currentNode.getChildCount(); + for (int i = 0; i < childCount; i++) { + // Using index 0 is voluntary as child is removed in next step and added to new parent + JMeterTreeNode node = (JMeterTreeNode) currentNode.getChildAt(0); + treeModel.removeNodeFromParent(node); + treeModel.insertNodeInto(node, newNode, newNode.getChildCount()); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/CheckDirty.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/CheckDirty.java new file mode 100644 index 0000000..0768998 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/CheckDirty.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.collections.ListedHashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Check if the TestPlan has been changed since it was last saved + * + */ +public class CheckDirty extends AbstractAction implements HashTreeTraverser, ActionListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Map previousGuiItems; + + private boolean checkMode = false; + + private boolean removeMode = false; + + private boolean dirty = false; + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.CHECK_DIRTY); + commands.add(ActionNames.SUB_TREE_SAVED); + commands.add(ActionNames.SUB_TREE_MERGED); + commands.add(ActionNames.SUB_TREE_LOADED); + commands.add(ActionNames.ADD_ALL); + commands.add(ActionNames.CHECK_REMOVE); + } + + public CheckDirty() { + previousGuiItems = new HashMap(); + ActionRouter.getInstance().addPreActionListener(ExitCommand.class, this); + } + + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals(ActionNames.EXIT)) { + doAction(e); + } + } + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(ActionNames.SUB_TREE_SAVED)) { + HashTree subTree = (HashTree) e.getSource(); + subTree.traverse(this); + } else if (action.equals(ActionNames.SUB_TREE_LOADED)) { + ListedHashTree addTree = (ListedHashTree) e.getSource(); + addTree.traverse(this); + } else if (action.equals(ActionNames.ADD_ALL)) { + previousGuiItems.clear(); + GuiPackage.getInstance().getTreeModel().getTestPlan().traverse(this); + } else if (action.equals(ActionNames.CHECK_REMOVE)) { + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeNode[] nodes = guiPackage.getTreeListener().getSelectedNodes(); + removeMode = true; + for (int i = nodes.length - 1; i >= 0; i--) { + guiPackage.getTreeModel().getCurrentSubTree(nodes[i]).traverse(this); + } + removeMode = false; + } + // If we are merging in another test plan, we know the test plan is dirty now + if(action.equals(ActionNames.SUB_TREE_MERGED)) { + dirty = true; + } + else { + dirty = false; + checkMode = true; + HashTree wholeTree = GuiPackage.getInstance().getTreeModel().getTestPlan(); + wholeTree.traverse(this); + checkMode = false; + } + GuiPackage.getInstance().setDirty(dirty); + } + + /** + * The tree traverses itself depth-first, calling processNode for each + * object it encounters as it goes. + */ + public void addNode(Object node, HashTree subTree) { + log.debug("Node is class:" + node.getClass()); + JMeterTreeNode treeNode = (JMeterTreeNode) node; + if (checkMode) { + // Only check if we have not found any differences so far + if(!dirty) { + if (previousGuiItems.containsKey(treeNode)) { + if (!previousGuiItems.get(treeNode).equals(treeNode.getTestElement())) { + dirty = true; + } + } else { + dirty = true; + } + } + } else if (removeMode) { + previousGuiItems.remove(treeNode); + } else { + previousGuiItems.put(treeNode, (TestElement) treeNode.getTestElement().clone()); + } + } + + /** + * Indicates traversal has moved up a step, and the visitor should remove + * the top node from it's stack structure. + */ + public void subtractNode() { + } + + /** + * Process path is called when a leaf is reached. If a visitor wishes to + * generate Lists of path elements to each leaf, it should keep a Stack data + * structure of nodes passed to it with addNode, and removing top items for + * every subtractNode() call. + */ + public void processPath() { + } + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Clear.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Clear.java new file mode 100644 index 0000000..a9a784f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Clear.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Handles the following actions: + * - Clear (Data) + * - Clear All (Data) + * - Reset (Clear GUI) + */ +public class Clear implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.CLEAR); + commands.add(ActionNames.CLEAR_ALL); + commands.add(ActionNames.RESET_GUI); + } + + public Clear() { + } + + public Set getActionNames() { + return commands; + } + + public void doAction(ActionEvent e) { + GuiPackage guiPackage = GuiPackage.getInstance(); + final String actionCommand = e.getActionCommand(); + if (actionCommand.equals(ActionNames.CLEAR)) { + JMeterGUIComponent guiComp = guiPackage.getCurrentGui(); + if (guiComp instanceof Clearable){ + ((Clearable) guiComp).clearData(); + } + } else if (actionCommand.equals(ActionNames.RESET_GUI)) { + JMeterGUIComponent guiComp = guiPackage.getCurrentGui(); + guiComp.clearGui(); + } else { + guiPackage.getMainFrame().clearData(); + for (JMeterTreeNode node : guiPackage.getTreeModel().getNodesOfType(Clearable.class)) { + JMeterGUIComponent guiComp = guiPackage.getGui(node.getTestElement()); + if (guiComp instanceof Clearable){ + Clearable item = (Clearable) guiComp; + try { + item.clearData(); + } catch (Exception ex) { + log.error("Can't clear: "+node+" "+guiComp, ex); + } + } + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Close.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Close.java new file mode 100644 index 0000000..67e0df1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Close.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JOptionPane; +import javax.swing.JTree; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.util.FocusRequester; +import org.apache.jmeter.util.JMeterUtils; + +/** + * This command clears the existing test plan, allowing the creation of a New + * test plan. + * + */ +public class Close implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.CLOSE); + } + + /** + * Constructor for the Close object. + */ + public Close() { + } + + /** + * Gets the ActionNames attribute of the Close object. + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commands; + } + + /** + * This method performs the actual command processing. + * + * @param e + * the generic UI action event + */ + public void doAction(ActionEvent e) { + performAction(e); + } + + /** + * Helper routine to allow action to be shared by LOAD. + * + * @param e event + * @return true if Close was not cancelled + */ + static boolean performAction(ActionEvent e){ + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.CHECK_DIRTY)); + GuiPackage guiPackage = GuiPackage.getInstance(); + if (guiPackage.isDirty()) { + int response; + if ((response=JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("cancel_new_to_save"), // $NON-NLS-1$ + JMeterUtils.getResString("save?"), // $NON-NLS-1$ + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE)) == JOptionPane.YES_OPTION) { + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.SAVE)); + } + if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.CANCEL_OPTION) { + return false; // Don't clear the plan + } + } + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.STOP_THREAD)); + closeProject(e); + return true; + } + + static void closeProject(ActionEvent e) { + GuiPackage guiPackage = GuiPackage.getInstance(); + + guiPackage.clearTestPlan(); + JTree tree = guiPackage.getTreeListener().getJTree(); + tree.setSelectionRow(0); + new FocusRequester(tree); + ActionRouter.getInstance().actionPerformed(new ActionEvent(e.getSource(), e.getID(), ActionNames.ADD_ALL)); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/CollapseExpand.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/CollapseExpand.java new file mode 100644 index 0000000..cc1645a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/CollapseExpand.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JTree; + +import org.apache.jmeter.gui.GuiPackage; + +/** + * Processes the Collapse All and Expand All options. + * + */ +public class CollapseExpand implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.COLLAPSE_ALL); + commands.add(ActionNames.EXPAND_ALL); + } + + /** + * Constructor for the Close object. + */ + public CollapseExpand() { + } + + /** + * Gets the ActionNames attribute of the Close object. + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commands; + } + + /** + * This method performs the actual command processing. + * + * @param e + * the generic UI action event + */ + public void doAction(ActionEvent e) { + boolean collapse=ActionNames.COLLAPSE_ALL.equals(e.getActionCommand()); + GuiPackage guiInstance = GuiPackage.getInstance(); + JTree jTree = guiInstance.getMainFrame().getTree(); + if (collapse){ + for (int i = jTree.getRowCount() - 1; i >= 0; i--) { + jTree.collapseRow(i); + } + return; + } + for(int i = 0; i < jTree.getRowCount(); i++) { + jTree.expandRow(i); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Command.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Command.java new file mode 100644 index 0000000..a88082f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Command.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.Set; + +import org.apache.jmeter.exceptions.IllegalUserActionException; + +public interface Command { + public void doAction(ActionEvent e) throws IllegalUserActionException; + + public Set getActionNames(); +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Copy.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Copy.java new file mode 100644 index 0000000..eaad6ab --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Copy.java @@ -0,0 +1,120 @@ +/* + * 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. + * + */ + +/* + * Created on Apr 9, 2003 + * + * Clones a JMeterTreeNode + */ +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; + +/** + * Implements the Copy menu command + */ +public class Copy extends AbstractAction { + private static JMeterTreeNode copiedNode = null; + + private static JMeterTreeNode copiedNodes[] = null; + + private static final HashSet commands = new HashSet(); + + static { + commands.add(ActionNames.COPY); + } + + /* + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + JMeterTreeListener treeListener = GuiPackage.getInstance().getTreeListener(); + JMeterTreeNode[] nodes = treeListener.getSelectedNodes(); + setCopiedNodes(nodes); + } + + public static JMeterTreeNode[] getCopiedNodes() { + if (copiedNodes == null) { // can be null if Copy has yet to be used + return null; + } + for (int i = 0; i < copiedNodes.length; i++) { + if (copiedNodes[i] == null) { + return null; + } + } + return cloneTreeNodes(copiedNodes); + } + + public static JMeterTreeNode getCopiedNode() { + if (copiedNode == null) { + return null; + } + return cloneTreeNode(copiedNode); + } + + public static void setCopiedNode(JMeterTreeNode node) { + copiedNode = cloneTreeNode(node); + } + + public static JMeterTreeNode cloneTreeNode(JMeterTreeNode node) { + JMeterTreeNode treeNode = (JMeterTreeNode) node.clone(); + treeNode.setUserObject(((TestElement) node.getUserObject()).clone()); + cloneChildren(treeNode, node); + return treeNode; + } + + public static void setCopiedNodes(JMeterTreeNode nodes[]) { + copiedNodes = new JMeterTreeNode[nodes.length]; + for (int i = 0; i < nodes.length; i++) { + copiedNodes[i] = cloneTreeNode(nodes[i]); + } + } + + public static JMeterTreeNode[] cloneTreeNodes(JMeterTreeNode nodes[]) { + JMeterTreeNode treeNodes[] = new JMeterTreeNode[nodes.length]; + for (int i = 0; i < nodes.length; i++) { + treeNodes[i] = cloneTreeNode(nodes[i]); + } + return treeNodes; + } + + private static void cloneChildren(JMeterTreeNode to, JMeterTreeNode from) { + Enumeration enumFrom = from.children(); + while (enumFrom.hasMoreElements()) { + JMeterTreeNode child = (JMeterTreeNode) enumFrom.nextElement(); + JMeterTreeNode childClone = (JMeterTreeNode) child.clone(); + childClone.setUserObject(((TestElement) child.getUserObject()).clone()); + to.add(childClone); + cloneChildren((JMeterTreeNode) to.getLastChild(), child); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/CreateFunctionDialog.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/CreateFunctionDialog.java new file mode 100644 index 0000000..c375a63 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/CreateFunctionDialog.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.functions.gui.FunctionHelper; + +public class CreateFunctionDialog extends AbstractAction { + private final FunctionHelper helper; + + private static final Set commands; + static { + commands = new HashSet(); + commands.add(ActionNames.FUNCTIONS); + } + + public CreateFunctionDialog() { + helper = new FunctionHelper(); + } + + /** + * Provide the list of Action names that are available in this command. + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent arg0) { + helper.setVisible(true); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Cut.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Cut.java new file mode 100644 index 0000000..7d226f2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Cut.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; + +/** + * Implements the Cut menu item command + */ +public class Cut extends AbstractAction { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.CUT); + } + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + GuiPackage guiPack = GuiPackage.getInstance(); + JMeterTreeNode[] currentNodes = guiPack.getTreeListener().getSelectedNodes(); + + Copy.setCopiedNodes(currentNodes); + for (int i = 0; i < currentNodes.length; i++) { + guiPack.getTreeModel().removeNodeFromParent(currentNodes[i]); + } + guiPack.getMainFrame().repaint(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/DragNDrop.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/DragNDrop.java new file mode 100644 index 0000000..bfc70f3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/DragNDrop.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; + +public class DragNDrop extends AbstractAction { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.DRAG_ADD); + commands.add(ActionNames.INSERT_BEFORE); + commands.add(ActionNames.INSERT_AFTER); + } + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + String action = e.getActionCommand(); + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeNode[] draggedNodes = guiPackage.getTreeListener().getDraggedNodes(); + JMeterTreeListener treeListener = guiPackage.getTreeListener(); + JMeterTreeNode currentNode = treeListener.getCurrentNode(); + JMeterTreeNode parentNode = (JMeterTreeNode) currentNode.getParent(); + TestElement te = currentNode.getTestElement(); + if (te instanceof TestPlan || te instanceof WorkBench) { + parentNode = null; // So elements can only be added as children + } + + if (ActionNames.DRAG_ADD.equals(action) && canAddTo(currentNode,draggedNodes)) { + removeNodesFromParents(draggedNodes); + for (int i = 0; i < draggedNodes.length; i++) { + GuiPackage.getInstance().getTreeModel().insertNodeInto(draggedNodes[i], currentNode, + currentNode.getChildCount()); + } + } else if (parentNode != null) { + if (ActionNames.INSERT_BEFORE.equals(action) && canAddTo(parentNode,draggedNodes)) { + removeNodesFromParents(draggedNodes); + for (int i = 0; i < draggedNodes.length; i++) { + int index = parentNode.getIndex(currentNode); + GuiPackage.getInstance().getTreeModel().insertNodeInto(draggedNodes[i], parentNode, index); + } + } else if (ActionNames.INSERT_AFTER.equals(action) && canAddTo(parentNode,draggedNodes)) { + removeNodesFromParents(draggedNodes); + for (int i = 0; i < draggedNodes.length; i++) { + int index = parentNode.getIndex(currentNode) + 1; + GuiPackage.getInstance().getTreeModel().insertNodeInto(draggedNodes[i], parentNode, index); + } + } + } + GuiPackage.getInstance().getMainFrame().repaint(); + } + + private static boolean canAddTo(JMeterTreeNode parentNode, JMeterTreeNode[] draggedNodes) { + boolean ok = MenuFactory.canAddTo(parentNode, draggedNodes); + if (!ok){ + Toolkit.getDefaultToolkit().beep(); + } + return ok; + } + + private void removeNodesFromParents(JMeterTreeNode[] nodes) { + for (int i = 0; i < nodes.length; i++) { + GuiPackage.getInstance().getTreeModel().removeNodeFromParent(nodes[i]); + } + } + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Duplicate.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Duplicate.java new file mode 100644 index 0000000..5331dea --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Duplicate.java @@ -0,0 +1,68 @@ +/* + * 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. + * + */ + +/* + * Created on Apr 9, 2003 + * + * Clones a JMeterTreeNode + */ +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; + +/** + * Implements the Duplicate menu command + */ +public class Duplicate extends AbstractAction { + + private static final HashSet commands = new HashSet(); + + static { + commands.add(ActionNames.DUPLICATE); + } + + /* + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + GuiPackage instance = GuiPackage.getInstance(); + JMeterTreeListener treeListener = instance.getTreeListener(); + JMeterTreeNode[] copiedNodes = Copy.cloneTreeNodes(treeListener.getSelectedNodes()); + JMeterTreeNode currentNode = treeListener.getCurrentNode(); + JMeterTreeNode parentNode = (JMeterTreeNode) currentNode.getParent(); + JMeterTreeModel treeModel = instance.getTreeModel(); + for (int i = 0; i < copiedNodes.length; i++) { + int index = parentNode.getIndex(currentNode) + 1; + treeModel.insertNodeInto(copiedNodes[i], parentNode, index); + } + instance.getMainFrame().repaint(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/EditCommand.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/EditCommand.java new file mode 100644 index 0000000..f9c3eea --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/EditCommand.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.NamePanel; + +/** + * Implements the Edit menu item. + */ +public class EditCommand implements Command { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.EDIT); + } + + public EditCommand() { + } + + public void doAction(ActionEvent e) { + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterGUIComponent currentGui = guiPackage.getCurrentGui(); + guiPackage.getMainFrame().setMainPanel((javax.swing.JComponent) currentGui); + guiPackage.getMainFrame().setEditMenu(guiPackage.getTreeListener().getCurrentNode().createPopupMenu()); + // TODO: I believe the following code (to the end of the method) is + // obsolete, + // since NamePanel no longer seems to be the GUI for any component: + if (!(currentGui instanceof NamePanel)) { + guiPackage.getMainFrame().setFileLoadEnabled(true); + guiPackage.getMainFrame().setFileSaveEnabled(true); + } else { + guiPackage.getMainFrame().setFileLoadEnabled(false); + guiPackage.getMainFrame().setFileSaveEnabled(false); + } + } + + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/EnableComponent.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/EnableComponent.java new file mode 100644 index 0000000..c813724 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/EnableComponent.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements the Enable menu item. + */ +public class EnableComponent implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.ENABLE); + commands.add(ActionNames.DISABLE); + commands.add(ActionNames.TOGGLE); + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + public void doAction(ActionEvent e) { + JMeterTreeNode[] nodes = GuiPackage.getInstance().getTreeListener().getSelectedNodes(); + + if (e.getActionCommand().equals(ActionNames.ENABLE)) { + log.debug("enabling currently selected gui objects"); + enableComponents(nodes, true); + } else if (e.getActionCommand().equals(ActionNames.DISABLE)) { + log.debug("disabling currently selected gui objects"); + enableComponents(nodes, false); + } else if (e.getActionCommand().equals(ActionNames.TOGGLE)) { + log.debug("toggling currently selected gui objects"); + toggleComponents(nodes); + } + } + + private void enableComponents(JMeterTreeNode[] nodes, boolean enable) { + GuiPackage pack = GuiPackage.getInstance(); + for (int i = 0; i < nodes.length; i++) { + nodes[i].setEnabled(enable); + pack.getGui(nodes[i].getTestElement()).setEnabled(enable); + } + } + + private void toggleComponents(JMeterTreeNode[] nodes) { + GuiPackage pack = GuiPackage.getInstance(); + for (int i = 0; i < nodes.length; i++) { + boolean enable = !nodes[i].isEnabled(); + nodes[i].setEnabled(enable); + pack.getGui(nodes[i].getTestElement()).setEnabled(enable); + } + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/ExitCommand.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/ExitCommand.java new file mode 100644 index 0000000..b6793df --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/ExitCommand.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; + +public class ExitCommand implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.EXIT); + } + + /** + * Constructor for the ExitCommand object + */ + public ExitCommand() { + } + + /** + * Gets the ActionNames attribute of the ExitCommand object + * + * @return The ActionNames value + */ + public Set getActionNames() { + return commands; + } + + /** + * Description of the Method + * + * @param e + * Description of Parameter + */ + public void doAction(ActionEvent e) { + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.CHECK_DIRTY)); + if (GuiPackage.getInstance().isDirty()) { + int chosenOption = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), JMeterUtils + .getResString("cancel_exit_to_save"), // $NON-NLS-1$ + JMeterUtils.getResString("save?"), // $NON-NLS-1$ + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); + if (chosenOption == JOptionPane.NO_OPTION) { + System.exit(0); + } else if (chosenOption == JOptionPane.YES_OPTION) { + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.SAVE)); + if (!GuiPackage.getInstance().isDirty()) { + System.exit(0); + } + } + } else { + System.exit(0); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Help.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Help.java new file mode 100644 index 0000000..fcacb3b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Help.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JDialog; +import javax.swing.JScrollPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.swing.HtmlPane; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements the Help menu item. + */ +public class Help implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + private static final String HELP_DOCS = "file:///" // $NON-NLS-1$ + + JMeterUtils.getJMeterHome() + + "/printable_docs/usermanual/"; // $NON-NLS-1$ + + private static final String HELP_PAGE = HELP_DOCS + "component_reference.html"; // $NON-NLS-1$ + + public static final String HELP_FUNCTIONS = HELP_DOCS + "functions.html"; // $NON-NLS-1$ + + private static JDialog helpWindow; + + private static final HtmlPane helpDoc; + + private static final JScrollPane scroller; + + static { + commands.add(ActionNames.HELP); + helpDoc = new HtmlPane(); + scroller = new JScrollPane(helpDoc); + helpDoc.setEditable(false); + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + public void doAction(ActionEvent e) { + if (helpWindow == null) { + helpWindow = new JDialog(new Frame(),// independent frame to + // allow it to be overlaid + // by the main frame + JMeterUtils.getResString("help"),//$NON-NLS-1$ + false); + helpWindow.getContentPane().setLayout(new GridLayout(1, 1)); + helpWindow.getContentPane().removeAll(); + helpWindow.getContentPane().add(scroller); + ComponentUtil.centerComponentInWindow(helpWindow, 60); + } + helpWindow.setVisible(true); // set the window visible immediately + /* + * This means that a new page will be shown before rendering is complete, + * however the correct location will be displayed. + * Attempts to use a "page" PropertyChangeListener to detect when the page + * has been loaded failed to work any better. + */ + StringBuilder url=new StringBuilder(); + if (e.getSource() instanceof String[]) { + String[] source = (String[]) e.getSource(); + url.append(source[0]).append('#').append(source[1]); + } else { + url.append(HELP_PAGE).append('#').append(GuiPackage.getInstance().getTreeListener().getCurrentNode().getDocAnchor()); + } + try { + helpDoc.setPage(url.toString()); // N.B. this only reloads if necessary (ignores the reference) + } catch (IOException ioe) { + log.error(ioe.toString()); + JMeterUtils.reportErrorToUser("Problem loading a help page - see log for details"); + } + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/KeyStrokes.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/KeyStrokes.java new file mode 100644 index 0000000..b79e702 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/KeyStrokes.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.Toolkit; +import java.awt.event.KeyEvent; + +import javax.swing.KeyStroke; + +/* + * Collect all the keystrokes together in one place. + * This helps to ensure that there are no duplicates. + */ +public final class KeyStrokes { + // Prevent instantiation + private KeyStrokes(){ + } + + // Bug 47064 - fixes for Mac LAF + private static final int CONTROL_MASK =Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + + public static final KeyStroke COPY = KeyStroke.getKeyStroke(KeyEvent.VK_C, CONTROL_MASK); + public static final KeyStroke DUPLICATE = KeyStroke.getKeyStroke(KeyEvent.VK_C, CONTROL_MASK | KeyEvent.SHIFT_DOWN_MASK); + public static final KeyStroke DEBUG_OFF = KeyStroke.getKeyStroke(KeyEvent.VK_D, CONTROL_MASK); + public static final KeyStroke DEBUG_ON = KeyStroke.getKeyStroke(KeyEvent.VK_D, CONTROL_MASK | KeyEvent.SHIFT_DOWN_MASK); + public static final KeyStroke CLEAR_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_E, CONTROL_MASK); + public static final KeyStroke CLEAR = KeyStroke.getKeyStroke(KeyEvent.VK_E, CONTROL_MASK | KeyEvent.SHIFT_DOWN_MASK); + public static final KeyStroke ESC = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + public static final KeyStroke ENTER = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); + public static final KeyStroke FUNCTIONS = KeyStroke.getKeyStroke(KeyEvent.VK_F, CONTROL_MASK); + public static final KeyStroke SAVE_GRAPHICS = KeyStroke.getKeyStroke(KeyEvent.VK_G, CONTROL_MASK); + public static final KeyStroke SAVE_GRAPHICS_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_G, CONTROL_MASK | KeyEvent.SHIFT_DOWN_MASK); + public static final KeyStroke HELP = KeyStroke.getKeyStroke(KeyEvent.VK_H, CONTROL_MASK); + public static final KeyStroke CLOSE = KeyStroke.getKeyStroke(KeyEvent.VK_L, CONTROL_MASK); + public static final KeyStroke SSL_MANAGER = KeyStroke.getKeyStroke(KeyEvent.VK_M, CONTROL_MASK); + public static final KeyStroke OPEN = KeyStroke.getKeyStroke(KeyEvent.VK_O, CONTROL_MASK); + public static final KeyStroke EXIT = KeyStroke.getKeyStroke(KeyEvent.VK_Q, CONTROL_MASK); + public static final KeyStroke ACTION_START = KeyStroke.getKeyStroke(KeyEvent.VK_R, CONTROL_MASK); + public static final KeyStroke REMOTE_START_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_R, CONTROL_MASK | KeyEvent.SHIFT_DOWN_MASK); + public static final KeyStroke SAVE = KeyStroke.getKeyStroke(KeyEvent.VK_S, CONTROL_MASK); + public static final KeyStroke SAVE_ALL_AS = KeyStroke.getKeyStroke(KeyEvent.VK_S, CONTROL_MASK | KeyEvent.SHIFT_DOWN_MASK); + public static final KeyStroke TOGGLE = KeyStroke.getKeyStroke(KeyEvent.VK_T, CONTROL_MASK); + public static final KeyStroke PASTE = KeyStroke.getKeyStroke(KeyEvent.VK_V, CONTROL_MASK); + public static final KeyStroke WHAT_CLASS = KeyStroke.getKeyStroke(KeyEvent.VK_W, CONTROL_MASK); + public static final KeyStroke CUT = KeyStroke.getKeyStroke(KeyEvent.VK_X, CONTROL_MASK); + public static final KeyStroke REMOTE_STOP_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.ALT_DOWN_MASK); + public static final KeyStroke REMOTE_SHUT_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_Z, KeyEvent.ALT_DOWN_MASK); + + public static final KeyStroke REMOVE = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0); + public static final KeyStroke ACTION_STOP = KeyStroke.getKeyStroke(KeyEvent.VK_PERIOD, CONTROL_MASK); + public static final KeyStroke ACTION_SHUTDOWN = KeyStroke.getKeyStroke(KeyEvent.VK_COMMA, CONTROL_MASK); + public static final KeyStroke COLLAPSE_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, CONTROL_MASK); + // VK_PLUS + CTRL_DOWN_MASK did not work... + public static final KeyStroke EXPAND_ALL = KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, CONTROL_MASK | KeyEvent.SHIFT_DOWN_MASK); + + /** + * Check if an event matches the KeyStroke definition. + * + * @param e event + * @param k keystroke + * @return true if event matches the keystroke definition + */ + public static boolean matches(KeyEvent e, KeyStroke k){ + final int modifiersEx = e.getModifiersEx() | e.getModifiers();// Hack to get full modifier value + return e.getKeyCode() == k.getKeyCode() && modifiersEx == k.getModifiers(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Load.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Load.java new file mode 100644 index 0000000..9c6dd3b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Load.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JFileChooser; +import javax.swing.JTree; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.FocusRequester; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.converters.ConversionException; + +/** + * Handles the Open (load a new file) and Merge commands. + * + */ +public class Load implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final boolean expandTree = JMeterUtils.getPropDefault("onload.expandtree", false); //$NON-NLS-1$ + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.OPEN); + commands.add(ActionNames.MERGE); + } + + public Load() { + super(); + } + + public Set getActionNames() { + return commands; + } + + public void doAction(ActionEvent e) { + JFileChooser chooser = FileDialoger.promptToOpenFile(new String[] { ".jmx" }); //$NON-NLS-1$ + if (chooser == null) { + return; + } + File selectedFile = chooser.getSelectedFile(); + if(selectedFile != null) { + boolean merging = e.getActionCommand().equals(ActionNames.MERGE); + // We must ask the user if it is ok to close current project + if(!merging) { + if (!Close.performAction(e)) { + return; + } + } + loadProjectFile(e, selectedFile, merging); + } + } + + static void loadProjectFile(ActionEvent e, File f, boolean merging) { + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.STOP_THREAD)); + + GuiPackage guiPackage = GuiPackage.getInstance(); + InputStream reader = null; + try { + if (f != null) { + boolean isTestPlan = false; + + if (merging) { + log.info("Merging file: " + f); + } else { + log.info("Loading file: " + f); + FileServer.getFileServer().setBaseForScript(f); + } + reader = new FileInputStream(f); + HashTree tree = SaveService.loadTree(reader); + isTestPlan = insertLoadedTree(e.getID(), tree, merging); + + // don't change name if merging + if (!merging && isTestPlan) { + guiPackage.setTestPlanFile(f.getAbsolutePath()); + } + } + } catch (NoClassDefFoundError ex) // Allow for missing optional jars + { + log.warn("Missing jar file", ex); + String msg = ex.getMessage(); + if (msg == null) { + msg = "Missing jar file - see log for details"; + } + JMeterUtils.reportErrorToUser(msg); + } catch (ConversionException ex) { + log.warn("Could not convert file "+ex); + JMeterUtils.reportErrorToUser(SaveService.CEtoString(ex)); + } catch (IOException ex) { + log.warn("Error reading file: "+ex); + String msg = ex.getMessage(); + if (msg == null) { + msg = "Unexpected error - see log for details"; + } + JMeterUtils.reportErrorToUser(msg); + } catch (Exception ex) { + log.warn("Unexpected error", ex); + String msg = ex.getMessage(); + if (msg == null) { + msg = "Unexpected error - see log for details"; + } + JMeterUtils.reportErrorToUser(msg); + } finally { + JOrphanUtils.closeQuietly(reader); + guiPackage.updateCurrentGui(); + guiPackage.getMainFrame().repaint(); + } + } + + /** + * Returns a boolean indicating whether the loaded tree was a full test plan + */ + public static boolean insertLoadedTree(int id, HashTree tree, boolean merging) throws Exception, IllegalUserActionException { + // convertTree(tree); + if (tree == null) { + throw new Exception("Error in TestPlan - see log file"); + } + boolean isTestPlan = tree.getArray()[0] instanceof TestPlan; + + // If we are loading a new test plan, initialize the tree with the testplan node we are loading + GuiPackage guiInstance = GuiPackage.getInstance(); + if(isTestPlan && !merging) { + guiInstance.clearTestPlan((TestElement)tree.getArray()[0]); + } + + if (merging){ // Check if target of merge is reasonable + TestElement te = (TestElement)tree.getArray()[0]; + if (!(te instanceof WorkBench || te instanceof TestPlan)){// These are handled specially by addToTree + boolean ok = MenuFactory.canAddTo(guiInstance.getCurrentNode(), te); + if (!ok){ + String name = te.getName(); + String className = te.getClass().getName(); + className = className.substring(className.lastIndexOf(".")+1); + throw new IllegalUserActionException("Can't merge "+name+" ("+className+") here"); + } + } + } + HashTree newTree = guiInstance.addSubTree(tree); + guiInstance.updateCurrentGui(); + guiInstance.getMainFrame().getTree().setSelectionPath( + new TreePath(((JMeterTreeNode) newTree.getArray()[0]).getPath())); + tree = guiInstance.getCurrentSubTree(); + // Send different event wether we are merging a test plan into another test plan, + // or loading a testplan from scratch + ActionEvent actionEvent = null; + if(!merging) { + actionEvent = new ActionEvent(tree.get(tree.getArray()[tree.size() - 1]), id, ActionNames.SUB_TREE_LOADED); + } + else { + actionEvent = new ActionEvent(tree.get(tree.getArray()[tree.size() - 1]), id, ActionNames.SUB_TREE_MERGED); + } + + ActionRouter.getInstance().actionPerformed(actionEvent); + JTree jTree = guiInstance.getMainFrame().getTree(); + if (expandTree && !merging) { // don't automatically expand when merging + for(int i = 0; i < jTree.getRowCount(); i++) { + jTree.expandRow(i); + } + } else { + jTree.expandRow(0); + } + TreePath path = jTree.getPathForRow(1); + jTree.setSelectionPath(path); + new FocusRequester(jTree); + return isTestPlan; + } + + public static boolean insertLoadedTree(int id, HashTree tree) throws Exception, IllegalUserActionException { + return insertLoadedTree(id, tree, false); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/LoadDraggedFile.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/LoadDraggedFile.java new file mode 100644 index 0000000..a93f2fd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/LoadDraggedFile.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.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.io.File; + +/** + * Handles the loading of a file from a Drag and Drop action. + */ +public class LoadDraggedFile { + + /** + * Loads dragged file asking before for save if current open file is dirty. + * @param e {@link ActionEvent} + * @param file File to Load + */ + public static void loadProject(ActionEvent e, File file) { + if(!Close.performAction(e)) { + return; + } + Load.loadProjectFile(e, file, false); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/LoadRecentProject.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/LoadRecentProject.java new file mode 100644 index 0000000..d46c9b8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/LoadRecentProject.java @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.io.File; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.prefs.Preferences; + +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import javax.swing.JSeparator; + +/** + * Handles the loading of recent files, and also the content and + * visibility of menu items for loading the recent files + */ +public class LoadRecentProject extends Load { + /** Prefix for the user preference key */ + private static final String USER_PREFS_KEY = "recent_file_"; //$NON-NLS-1$ + /** The number of menu items used for recent files */ + private static final int NUMBER_OF_MENU_ITEMS = 9; + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.OPEN_RECENT); + } + + private static final Preferences prefs = Preferences.userNodeForPackage(LoadRecentProject.class); + // Note: Windows user preferences are stored relative to: HKEY_CURRENT_USER\Software\JavaSoft\Prefs + + public LoadRecentProject() { + super(); + } + + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + // We must ask the user if it is ok to close current project + if (!Close.performAction(e)) { + return; + } + // Load the file for this recent file command + loadProjectFile(e, getRecentFile(e), false); + } + + /** + * Get the recent file for the menu item + */ + private File getRecentFile(ActionEvent e) { + JMenuItem menuItem = (JMenuItem)e.getSource(); + // Get the preference for the recent files + return new File(getRecentFile(Integer.parseInt(menuItem.getName()))); + } + + /** + * Get the menu items to add to the menu bar, to get recent file functionality + * + * @return a List of JMenuItem and a JSeparator, representing recent files + */ + public static List getRecentFileMenuItems() { + LinkedList menuItems = new LinkedList(); + // Get the preference for the recent files + for(int i = 0; i < NUMBER_OF_MENU_ITEMS; i++) { + // Create the menu item + JMenuItem recentFile = new JMenuItem(); + // Use the index as the name, used when processing the action + recentFile.setName(Integer.toString(i)); + recentFile.addActionListener(ActionRouter.getInstance()); + recentFile.setActionCommand(ActionNames.OPEN_RECENT); + // Set the KeyStroke to use + int shortKey = getShortcutKey(i); + if(shortKey >= 0) { + recentFile.setMnemonic(shortKey); + } + // Add the menu item + menuItems.add(recentFile); + } + // Add separator as the last item + JSeparator separator = new JSeparator(); + separator.setVisible(false); + menuItems.add(separator); + + // Update menu items to reflect recent files + updateMenuItems(menuItems); + + return menuItems; + } + + /** + * Update the content and visibility of the menu items for recent files + * + * @param menuItems the JMenuItem and JSeparator to update + * @param loadedFileName the file name of the project file that has just + * been loaded + */ + public static void updateRecentFileMenuItems(List menuItems, String loadedFileName) { + // Get the preference for the recent files + + LinkedList newRecentFiles = new LinkedList(); + // Check if the new file is already in the recent list + boolean alreadyExists = false; + for(int i = 0; i < NUMBER_OF_MENU_ITEMS; i++) { + String recentFilePath = getRecentFile(i); + if(!loadedFileName.equals(recentFilePath)) { + newRecentFiles.add(recentFilePath); + } + else { + alreadyExists = true; + } + } + // Add the new file at the start of the list + newRecentFiles.add(0, loadedFileName); + // Remove the last item from the list if it was a brand new file + if(!alreadyExists) { + newRecentFiles.removeLast(); + } + // Store the recent files + for(int i = 0; i < NUMBER_OF_MENU_ITEMS; i++) { + String fileName = newRecentFiles.get(i); + if(fileName != null) { + setRecentFile(i, fileName); + } + } + // Update menu items to reflect recent files + updateMenuItems(menuItems); + } + + /** + * Set the content and visibility of menu items and menu separator, + * based on the recent file stored user preferences. + */ + private static void updateMenuItems(List menuItems) { + // Assume no recent files + boolean someRecentFiles = false; + // Update the menu items + for(int i = 0; i < NUMBER_OF_MENU_ITEMS; i++) { + // Get the menu item + JMenuItem recentFile = (JMenuItem)menuItems.get(i); + + // Find and set the file for this recent file command + String recentFilePath = getRecentFile(i); + if(recentFilePath != null) { + File file = new File(recentFilePath); + StringBuilder sb = new StringBuilder(60); + if (i<9) { + sb.append(i+1).append(" "); //$NON-NLS-1$ + } + sb.append(getMenuItemDisplayName(file)); + recentFile.setText(sb.toString()); + recentFile.setToolTipText(recentFilePath); + recentFile.setEnabled(true); + recentFile.setVisible(true); + // At least one recent file menu item is visible + someRecentFiles = true; + } + else { + recentFile.setEnabled(false); + recentFile.setVisible(false); + } + } + // If there are some recent files, we must make the separator visisble + // The separator is the last item in the list + JSeparator separator = (JSeparator)menuItems.get(menuItems.size() - 1); + separator.setVisible(someRecentFiles); + } + + /** + * Get the name to display in the menu item, it will chop the file name + * if it is too long to display in the menu bar + */ + private static String getMenuItemDisplayName(File file) { + // Limit the length of the menu text if needed + final int maxLength = 40; + String menuText = file.getName(); + if(menuText.length() > maxLength) { + menuText = "..." + menuText.substring(menuText.length() - maxLength, menuText.length()); //$NON-NLS-1$ + } + return menuText; + } + + /** + * Get the KeyEvent to use as shortcut key for menu item + */ + private static int getShortcutKey(int index) { + int shortKey = -1; + switch(index+1) { + case 1: + shortKey = KeyEvent.VK_1; + break; + case 2: + shortKey = KeyEvent.VK_2; + break; + case 3: + shortKey = KeyEvent.VK_3; + break; + case 4: + shortKey = KeyEvent.VK_4; + break; + case 5: + shortKey = KeyEvent.VK_5; + break; + case 6: + shortKey = KeyEvent.VK_6; + break; + case 7: + shortKey = KeyEvent.VK_7; + break; + case 8: + shortKey = KeyEvent.VK_8; + break; + case 9: + shortKey = KeyEvent.VK_9; + break; + default: + break; + } + return shortKey; + } + + /** + * Get the full path to the recent file where index 0 is the most recent + */ + public static String getRecentFile(int index) { + return prefs.get(USER_PREFS_KEY + index, null); + } + + /** + * Set the full path to the recent file where index 0 is the most recent + */ + private static void setRecentFile(int index, String fileName) { + prefs.put(USER_PREFS_KEY + index, fileName); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java new file mode 100644 index 0000000..4d58a68 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/LoggerPanelEnableDisable.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JSplitPane; +import javax.swing.UIManager; + +import org.apache.jmeter.gui.GuiPackage; + +/** + * Hide / unhide LoggerPanel. + * + */ +public class LoggerPanelEnableDisable implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.LOGGER_PANEL_ENABLE_DISABLE); + } + + /** + * Constructor for object. + */ + public LoggerPanelEnableDisable() { + } + + /** + * Gets the ActionNames attribute of the action + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commands; + } + + /** + * This method performs the actual command processing. + * + * @param e + * the generic UI action event + */ + public void doAction(ActionEvent e) { + GuiPackage guiInstance = GuiPackage.getInstance(); + JSplitPane splitPane = ((JSplitPane)guiInstance.getLoggerPanel().getParent()); + if (ActionNames.LOGGER_PANEL_ENABLE_DISABLE.equals(e.getActionCommand())) { + if (!guiInstance.getLoggerPanel().isVisible()) { + splitPane.setDividerSize(UIManager.getInt("SplitPane.dividerSize")); + guiInstance.getLoggerPanel().setVisible(true); + splitPane.setDividerLocation(0.8); + guiInstance.getMenuItemLoggerPanel().getModel().setSelected(true); + } else { + guiInstance.getLoggerPanel().clear(); + guiInstance.getLoggerPanel().setVisible(false); + splitPane.setDividerSize(0); + guiInstance.getMenuItemLoggerPanel().getModel().setSelected(false); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/LookAndFeelCommand.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/LookAndFeelCommand.java new file mode 100644 index 0000000..1692540 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/LookAndFeelCommand.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.prefs.Preferences; + +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.util.JMeterMenuBar; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements the Look and Feel menu item. + */ +public class LookAndFeelCommand implements Command { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String JMETER_LAF = "jmeter.laf"; // $NON-NLS-1$ + + private static final Set commands = new HashSet(); + + private static final Preferences PREFS = Preferences.userNodeForPackage(LookAndFeelCommand.class); + // Note: Windows user preferences are stored relative to: HKEY_CURRENT_USER\Software\JavaSoft\Prefs + + /** Prefix for the user preference key */ + private static final String USER_PREFS_KEY = "laf"; //$NON-NLS-1$ + + static { + UIManager.LookAndFeelInfo[] lfs = JMeterMenuBar.getAllLAFs(); + for (int i = 0; i < lfs.length; i++) { + commands.add(ActionNames.LAF_PREFIX + lfs[i].getClassName()); + } + + try { + String jMeterLaf = getJMeterLaf(); + UIManager.setLookAndFeel(jMeterLaf); + if (log.isInfoEnabled()) { + ArrayList names=new ArrayList(); + for(UIManager.LookAndFeelInfo laf : lfs) { + if (laf.getClassName().equals(jMeterLaf)) { + names.add(laf.getName()); + } + } + if (names.size() > 0) { + log.info("Using look and feel: "+jMeterLaf+ " " +names.toString()); + } else { + log.info("Using look and feel: "+jMeterLaf); + } + } + } catch (IllegalAccessException e) { + } catch (ClassNotFoundException e) { + } catch (InstantiationException e) { + } catch (UnsupportedLookAndFeelException e) { + } + } + + /** + * Get LookAndFeel classname from the following properties: + *
    + *
  • jmeter.laf.<os.name> - lowercased; spaces replaced by '_'
  • + *
  • jmeter.laf.<os.family> - lowercased.
  • + *
  • jmeter.laf
  • + *
  • UIManager.getCrossPlatformLookAndFeelClassName()
  • + *
+ * @return LAF classname + */ + private static String getJMeterLaf(){ + String laf; + + laf = PREFS.get(USER_PREFS_KEY, null); + if (laf != null) { + return checkLafName(laf); + } + + String osName = System.getProperty("os.name") // $NON-NLS-1$ + .toLowerCase(Locale.ENGLISH); + // Spaces are not allowed in property names read from files + laf = JMeterUtils.getProperty(JMETER_LAF+"."+osName.replace(' ', '_')); + if (laf != null) { + return checkLafName(laf); + } + String[] osFamily = osName.split("\\s"); // e.g. windows xp => windows + laf = JMeterUtils.getProperty(JMETER_LAF+"."+osFamily[0]); + if (laf != null) { + return checkLafName(laf); + } + laf = JMeterUtils.getProperty(JMETER_LAF); + if (laf != null) { + return checkLafName(laf); + } + return UIManager.getCrossPlatformLookAndFeelClassName(); + } + + // Check if LAF is a built-in one + private static String checkLafName(String laf){ + if (JMeterMenuBar.SYSTEM_LAF.equalsIgnoreCase(laf)){ + return UIManager.getSystemLookAndFeelClassName(); + } + if (JMeterMenuBar.CROSS_PLATFORM_LAF.equalsIgnoreCase(laf)){ + return UIManager.getCrossPlatformLookAndFeelClassName(); + } + return laf; + } + + public LookAndFeelCommand() { + } + + public void doAction(ActionEvent ev) { + try { + String className = ev.getActionCommand().substring(ActionNames.LAF_PREFIX.length()).replace('/', '.'); + UIManager.setLookAndFeel(className); + SwingUtilities.updateComponentTreeUI(GuiPackage.getInstance().getMainFrame()); + PREFS.put(USER_PREFS_KEY, className); + } catch (javax.swing.UnsupportedLookAndFeelException e) { + JMeterUtils.reportErrorToUser("Look and Feel unavailable:" + e.toString()); + } catch (InstantiationException e) { + JMeterUtils.reportErrorToUser("Look and Feel unavailable:" + e.toString()); + } catch (ClassNotFoundException e) { + JMeterUtils.reportErrorToUser("Look and Feel unavailable:" + e.toString()); + } catch (IllegalAccessException e) { + JMeterUtils.reportErrorToUser("Look and Feel unavailable:" + e.toString()); + } + } + + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Paste.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Paste.java new file mode 100644 index 0000000..627be1a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Paste.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeListener; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Places a copied JMeterTreeNode under the selected node. + * + */ +public class Paste extends AbstractAction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.PASTE); + } + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + JMeterTreeNode draggedNodes[] = Copy.getCopiedNodes(); + if (draggedNodes == null) { + Toolkit.getDefaultToolkit().beep(); + return; + } + JMeterTreeListener treeListener = GuiPackage.getInstance().getTreeListener(); + JMeterTreeNode currentNode = treeListener.getCurrentNode(); + if (MenuFactory.canAddTo(currentNode, draggedNodes)) { + for (int i = 0; i < draggedNodes.length; i++) { + if (draggedNodes[i] != null) { + addNode(currentNode, draggedNodes[i]); + } + } + } else { + Toolkit.getDefaultToolkit().beep(); + } + GuiPackage.getInstance().getMainFrame().repaint(); + } + + private void addNode(JMeterTreeNode parent, JMeterTreeNode node) { + try { + // Add this node + JMeterTreeNode newNode = GuiPackage.getInstance().getTreeModel().addComponent(node.getTestElement(), parent); + // Add all the child nodes of the node we are adding + for(int i = 0; i < node.getChildCount(); i++) { + addNode(newNode, (JMeterTreeNode)node.getChildAt(i)); + } + } + catch (IllegalUserActionException iuae) { + log.error("", iuae); // $NON-NLS-1$ + JMeterUtils.reportErrorToUser(iuae.getMessage()); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/RawTextSearcher.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/RawTextSearcher.java new file mode 100644 index 0000000..9f9ad3c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/RawTextSearcher.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +/** + * Searcher implementation that searches text as is + */ +public class RawTextSearcher implements Searcher { + private boolean caseSensitive; + private String textToSearch; + + + /** + * Constructor + * @param caseSensitive is search case sensitive + * @param textToSearch Text to search + */ + public RawTextSearcher(boolean caseSensitive, String textToSearch) { + super(); + this.caseSensitive = caseSensitive; + if(caseSensitive) { + this.textToSearch = textToSearch; + } else { + this.textToSearch = textToSearch.toLowerCase(); + } + } + + /** + * {@inheritDoc} + */ + public boolean search(List textTokens) { + boolean result = false; + for (String searchableToken : textTokens) { + if(!StringUtils.isEmpty(searchableToken)) { + if(caseSensitive) { + result = searchableToken.indexOf(textToSearch)>=0; + } else { + result = searchableToken.toLowerCase().indexOf(textToSearch)>=0; + } + if (result) { + return result; + } + } + } + return false; + } + + /** + * Returns true if searchedTextLowerCase is in value + * @param value + * @param searchedTextLowerCase + * @return true if searchedTextLowerCase is in value + */ + protected boolean testField(String value, String searchedTextLowerCase) { + if(!StringUtils.isEmpty(value)) { + return value.toLowerCase().indexOf(searchedTextLowerCase)>=0; + } + return false; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/RegexpSearcher.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/RegexpSearcher.java new file mode 100644 index 0000000..097039e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/RegexpSearcher.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang.StringUtils; + +/** + * Regexp search implementation + */ +public class RegexpSearcher implements Searcher { + private boolean caseSensitive; + private Pattern pattern; + + + /** + * Constructor + * @param caseSensitive is search case sensitive + * @param regexp Regexp to search + */ + public RegexpSearcher(boolean caseSensitive, String regexp) { + super(); + this.caseSensitive = caseSensitive; + String newRegexp = ".*"+regexp+".*"; + if(caseSensitive) { + pattern = Pattern.compile(newRegexp); + } else { + pattern = Pattern.compile(newRegexp.toLowerCase()); + } + } + + + /** + * {@inheritDoc} + */ + public boolean search(List textTokens) { + for (String searchableToken : textTokens) { + if(!StringUtils.isEmpty(searchableToken)) { + Matcher matcher = null; + if(caseSensitive) { + matcher = pattern.matcher(searchableToken); + } else { + matcher = pattern.matcher(searchableToken.toLowerCase()); + } + if(matcher.find()) { + return true; + } + } + } + return false; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/RemoteStart.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/RemoteStart.java new file mode 100644 index 0000000..d2c04c4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/RemoteStart.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.jmeter.JMeter; +import org.apache.jmeter.engine.ClientJMeterEngine; +import org.apache.jmeter.engine.JMeterEngine; +import org.apache.jmeter.engine.JMeterEngineException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class RemoteStart extends AbstractAction { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String LOCAL_HOST = "127.0.0.1"; // $NON-NLS-1$ + + private static final String REMOTE_HOSTS = "remote_hosts"; // $NON-NLS-1$ jmeter.properties + + private static final String REMOTE_HOSTS_SEPARATOR = ","; // $NON-NLS-1$ + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.REMOTE_START); + commands.add(ActionNames.REMOTE_STOP); + commands.add(ActionNames.REMOTE_SHUT); + commands.add(ActionNames.REMOTE_START_ALL); + commands.add(ActionNames.REMOTE_STOP_ALL); + commands.add(ActionNames.REMOTE_SHUT_ALL); + commands.add(ActionNames.REMOTE_EXIT); + commands.add(ActionNames.REMOTE_EXIT_ALL); + } + + private final Map remoteEngines = new HashMap(); + + public RemoteStart() { + } + + @Override + public void doAction(ActionEvent e) { + String name = ((Component) e.getSource()).getName(); + if (name != null) { + name = name.trim(); + } + String action = e.getActionCommand(); + if (action.equals(ActionNames.REMOTE_STOP)) { + doRemoteStop(name, true); + } else if (action.equals(ActionNames.REMOTE_SHUT)) { + doRemoteStop(name, false); + } else if (action.equals(ActionNames.REMOTE_START)) { + popupShouldSave(e); + doRemoteInit(name); + doRemoteStart(name); + } else if (action.equals(ActionNames.REMOTE_START_ALL)) { + popupShouldSave(e); + String remote_hosts_string = JMeterUtils.getPropDefault(REMOTE_HOSTS, LOCAL_HOST); + java.util.StringTokenizer st = new java.util.StringTokenizer(remote_hosts_string, REMOTE_HOSTS_SEPARATOR); + while (st.hasMoreElements()) { + String el = (String) st.nextElement(); + doRemoteInit(el.trim()); + } + st = new java.util.StringTokenizer(remote_hosts_string, REMOTE_HOSTS_SEPARATOR); + while (st.hasMoreElements()) { + String el = (String) st.nextElement(); + doRemoteStart(el.trim()); + } + } else if (action.equals(ActionNames.REMOTE_STOP_ALL)) { + doRemoteStopAll(true); + } else if (action.equals(ActionNames.REMOTE_SHUT_ALL)) { + doRemoteStopAll(false); + } else if (action.equals(ActionNames.REMOTE_EXIT)) { + doRemoteExit(name); + } else if (action.equals(ActionNames.REMOTE_EXIT_ALL)) { + String remote_hosts_string = JMeterUtils.getPropDefault(REMOTE_HOSTS, LOCAL_HOST); + java.util.StringTokenizer st = new java.util.StringTokenizer(remote_hosts_string, REMOTE_HOSTS_SEPARATOR); + while (st.hasMoreElements()) { + String el = (String) st.nextElement(); + doRemoteExit(el.trim()); + } + } + } + + private void doRemoteStopAll(boolean now) { + String remote_hosts_string = JMeterUtils.getPropDefault(REMOTE_HOSTS, LOCAL_HOST); + java.util.StringTokenizer st = new java.util.StringTokenizer(remote_hosts_string, REMOTE_HOSTS_SEPARATOR); + while (st.hasMoreElements()) { + String el = (String) st.nextElement(); + doRemoteStop(el.trim(), now); + } + } + + /** + * Stops a remote testing engine + * + * @param name + * the DNS name or IP address of the remote testing engine + * + */ + private void doRemoteStop(String name, boolean now) { + GuiPackage.getInstance().getMainFrame().showStoppingMessage(name); + JMeterEngine engine = remoteEngines.get(name); + engine.stopTest(now); + } + + /** + * Exits a remote testing engine + * + * @param name + * the DNS name or IP address of the remote testing engine + * + */ + private void doRemoteExit(String name) { + JMeterEngine engine = remoteEngines.get(name); + if (engine == null) { + return; + } + // GuiPackage.getInstance().getMainFrame().showStoppingMessage(name); + engine.exit(); + } + + /** + * Starts a remote testing engine + * + * @param name + * the DNS name or IP address of the remote testing engine + * + */ + private void doRemoteStart(String name) { + JMeterEngine engine = remoteEngines.get(name); + if (engine != null) { + try { + engine.runTest(); + } catch (IllegalStateException e) { + JMeterUtils.reportErrorToUser(e.getMessage(),JMeterUtils.getResString("remote_error_starting")); // $NON-NLS-1$ + } catch (JMeterEngineException e) { + JMeterUtils.reportErrorToUser(e.getMessage(),JMeterUtils.getResString("remote_error_starting")); // $NON-NLS-1$ + } + } + } + + /** + * Initializes remote engines + */ + private void doRemoteInit(String name) { + JMeterEngine engine = remoteEngines.get(name); + if (engine == null) { + try { + log.info("Initialising remote engine: "+name); + engine = new ClientJMeterEngine(name); + remoteEngines.put(name, engine); + } catch (Exception ex) { + log.error("Failed to initialise remote engine", ex); + JMeterUtils.reportErrorToUser(ex.getMessage(), + JMeterUtils.getResString("remote_error_init") + ": " + name); // $NON-NLS-1$ $NON-NLS-2$ + return; + } + } else { + engine.reset(); + } + initEngine(engine, name); + } + + @Override + public Set getActionNames() { + return commands; + } + + /** + * Initializes test on engine. + * + * @param engine + * remote engine object + * @param host + * host the engine will run on + */ + private void initEngine(JMeterEngine engine, String host) { + GuiPackage gui = GuiPackage.getInstance(); + HashTree testTree = gui.getTreeModel().getTestPlan(); + JMeter.convertSubTree(testTree); + testTree.add(testTree.getArray()[0], gui.getMainFrame()); + engine.configure(testTree); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Remove.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Remove.java new file mode 100644 index 0000000..6097074 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Remove.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JOptionPane; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Implements the Remove menu item. + */ +public class Remove implements Command { + + private static final Set commands = new HashSet(); + + // Whether to skip the delete confirmation dialogue + private static final boolean SKIP_CONFIRM = JMeterUtils.getPropDefault("confirm.delete.skip", false); // $NON-NLS-1$ + + static { + commands.add(ActionNames.REMOVE); + } + + /** + * Constructor for the Remove object + */ + public Remove() { + } + + /** + * Gets the ActionNames attribute of the Remove object. + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commands; + } + + public void doAction(ActionEvent e) { + + int isConfirm = SKIP_CONFIRM ? JOptionPane.YES_OPTION : + JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("remove_confirm_msg"),// $NON-NLS-1$ + JMeterUtils.getResString("remove_confirm_title"), // $NON-NLS-1$ + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (isConfirm == JOptionPane.YES_OPTION) { + // TODO - removes the nodes from the CheckDirty map - should it be done later, in case some can't be removed? + ActionRouter.getInstance().actionPerformed(new ActionEvent(e.getSource(), e.getID(), ActionNames.CHECK_REMOVE)); + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeNode[] nodes = guiPackage.getTreeListener().getSelectedNodes(); + TreePath newTreePath = // Save parent node for later + guiPackage.getTreeListener().removedSelectedNode(); + for (int i = nodes.length - 1; i >= 0; i--) { + removeNode(nodes[i]); + } + guiPackage.getTreeListener().getJTree().setSelectionPath(newTreePath); + guiPackage.updateCurrentGui(); + } + } + + private static void removeNode(JMeterTreeNode node) { + TestElement testElement = node.getTestElement(); + if (testElement.canRemove()) { + GuiPackage.getInstance().getTreeModel().removeNodeFromParent(node); + GuiPackage.getInstance().removeNode(testElement); + } else { + String message = testElement.getClass().getName() + " is busy"; + JOptionPane.showMessageDialog(null, message, "Cannot remove item", JOptionPane.ERROR_MESSAGE); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/ResetSearchCommand.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/ResetSearchCommand.java new file mode 100644 index 0000000..c907726 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/ResetSearchCommand.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.Searchable; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; + +/** + * Reset Search + */ +public class ResetSearchCommand extends AbstractAction { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.SEARCH_RESET); + } + + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeModel jMeterTreeModel = guiPackage.getTreeModel(); + for (JMeterTreeNode jMeterTreeNode : jMeterTreeModel.getNodesOfType(Searchable.class)) { + if (jMeterTreeNode.getUserObject() instanceof Searchable){ + List matchingNodes = jMeterTreeNode.getPathToThreadGroup(); + for (JMeterTreeNode jMeterTreeNode2 : matchingNodes) { + jMeterTreeNode2.setMarkedBySearch(false); + } + } + } + GuiPackage.getInstance().getMainFrame().repaint(); + } + + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/RevertProject.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/RevertProject.java new file mode 100644 index 0000000..2806acf --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/RevertProject.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.HashSet; +import java.util.Set; +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Handles the Revert Project command. + * + */ +public class RevertProject implements Command { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.REVERT_PROJECT); + } + + public RevertProject() { + super(); + } + + public Set getActionNames() { + return commands; + } + + public void doAction(ActionEvent e) { + // Get the file name of the current project + String projectFile = GuiPackage.getInstance().getTestPlanFile(); + // Check if the user has loaded any file + if(projectFile == null) { + return; + } + + // Check if the user wants to drop any changes + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.CHECK_DIRTY)); + GuiPackage guiPackage = GuiPackage.getInstance(); + if (guiPackage.isDirty()) { + // Check if the user wants to revert + int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("cancel_revert_project"), // $NON-NLS-1$ + JMeterUtils.getResString("revert_project?"), // $NON-NLS-1$ + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if(response == JOptionPane.YES_OPTION) { + // Close the current project + Close.closeProject(e); + // Reload the project + Load.loadProjectFile(e, new File(projectFile), false); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/SSLManagerCommand.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/SSLManagerCommand.java new file mode 100644 index 0000000..7843efa --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/SSLManagerCommand.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileFilter; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.SSLManager; + +// +/** + * SSL Manager Command. The SSL Manager provides a mechanism to change your + * client authentication if required by the server. If you have JSSE 1.0.2 + * installed, you can select your client identity from a list of installed keys. + * You can also change your keystore. JSSE 1.0.2 allows you to export a PKCS#12 + * key from Netscape 4.04 or higher and use it in a read only format. You must + * supply a password that is greater than six characters due to limitations in + * the keytool program--and possibly the rest of the system. + *

+ * By selecting a *.p12 file as your keystore (your PKCS#12) format file, you + * can have a whopping one key keystore. The advantage is that you can test a + * connection using the assigned Certificate from a Certificate Authority. + *

+ * TODO ? + * N.B. The present implementation does not seem to allow selection of keys, + * it only allows a change of keystore at run-time, or to provide one if not + * already defined via the property. + * + */ +public class SSLManagerCommand implements Command { + private static final Set commandSet; + static { + HashSet commands = new HashSet(); + commands.add(ActionNames.SSL_MANAGER); + commandSet = Collections.unmodifiableSet(commands); + } + + private JFileChooser keyStoreChooser; + + /** + * Handle the "sslmanager" action by displaying the "SSL CLient Manager" + * dialog box. The Dialog Box is NOT modal, because those should be avoided + * if at all possible. + */ + public void doAction(ActionEvent e) { + if (e.getActionCommand().equals(ActionNames.SSL_MANAGER)) { + this.sslManager(); + } + } + + /** + * Provide the list of Action names that are available in this command. + */ + public Set getActionNames() { + return SSLManagerCommand.commandSet; + } + + /** + * Called by sslManager button. Raises sslManager dialog. + * I.e. a FileChooser for PCSI12 (.p12|.P12) files. + */ + private void sslManager() { + SSLManager.reset(); + + keyStoreChooser = new JFileChooser(System.getProperty("user.dir")); //$NON-NLS-1$ + keyStoreChooser.addChoosableFileFilter(new AcceptPKCS12FileFilter()); + keyStoreChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + int retVal = keyStoreChooser.showOpenDialog(GuiPackage.getInstance().getMainFrame()); + + if (JFileChooser.APPROVE_OPTION == retVal) { + File selectedFile = keyStoreChooser.getSelectedFile(); + try { + System.setProperty(SSLManager.JAVAX_NET_SSL_KEY_STORE, selectedFile.getCanonicalPath()); + } catch (IOException e) { + //Ignored + } + } + + keyStoreChooser = null; + SSLManager.getInstance(); + } + + /** + * Internal class to add a PKCS12 file format filter for JFileChooser. + */ + static private class AcceptPKCS12FileFilter extends FileFilter { + /** + * Get the description that shows up in JFileChooser filter menu. + * + * @return description + */ + @Override + public String getDescription() { + return JMeterUtils.getResString("pkcs12_desc"); //$NON-NLS-1$ + } + + /** + * Tests to see if the file ends with "*.p12" or "*.P12". + * + * @param testFile + * file to test + * @return true if file is accepted, false otherwise + */ + @Override + public boolean accept(File testFile) { + return testFile.isDirectory() + || testFile.getName().endsWith(".p12") //$NON-NLS-1$ + || testFile.getName().endsWith(".P12"); //$NON-NLS-1$ + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Save.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Save.java new file mode 100644 index 0000000..4dc0163 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Save.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileOutputStream; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Set; + +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; + +import org.apache.commons.io.FilenameUtils; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Save the current test plan; implements: + * Save + * Save TestPlan As + * Save (Selection) As + */ +public class Save implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String JMX_FILE_EXTENSION = ".jmx"; // $NON-NLS-1$ + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.SAVE_AS); // Save (Selection) As + commands.add(ActionNames.SAVE_ALL_AS); // Save TestPlan As + commands.add(ActionNames.SAVE); // Save + } + + /** + * Constructor for the Save object. + */ + public Save() { + } + + /** + * Gets the ActionNames attribute of the Save object. + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commands; + } + + public void doAction(ActionEvent e) throws IllegalUserActionException { + HashTree subTree = null; + boolean fullSave = false; // are we saving the whole tree? + if (!commands.contains(e.getActionCommand())) { + throw new IllegalUserActionException("Invalid user command:" + e.getActionCommand()); + } + if (e.getActionCommand().equals(ActionNames.SAVE_AS)) { + JMeterTreeNode[] nodes = GuiPackage.getInstance().getTreeListener().getSelectedNodes(); + if (nodes.length > 1){ + JMeterUtils.reportErrorToUser( + JMeterUtils.getResString("save_as_error"), // $NON-NLS-1$ + JMeterUtils.getResString("save_as")); // $NON-NLS-1$ + return; + } + subTree = GuiPackage.getInstance().getCurrentSubTree(); + } else { + fullSave = true; + subTree = GuiPackage.getInstance().getTreeModel().getTestPlan(); + } + + String updateFile = GuiPackage.getInstance().getTestPlanFile(); + if (!ActionNames.SAVE.equals(e.getActionCommand()) || updateFile == null) { + JFileChooser chooser = FileDialoger.promptToSaveFile(updateFile == null ? GuiPackage.getInstance().getTreeListener() + .getCurrentNode().getName() + + JMX_FILE_EXTENSION : updateFile); + if (chooser == null) { + return; + } + updateFile = chooser.getSelectedFile().getAbsolutePath(); + // Make sure the file ends with proper extension + if(FilenameUtils.getExtension(updateFile).equals("")) { + updateFile = updateFile + JMX_FILE_EXTENSION; + } + // Check if the user is trying to save to an existing file + File f = new File(updateFile); + if(f.exists()) { + int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("save_overwrite_existing_file"), // $NON-NLS-1$ + JMeterUtils.getResString("save?"), // $NON-NLS-1$ + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.NO_OPTION) { + return ; // Do not save, user does not want to overwrite + } + } + + if (!e.getActionCommand().equals(ActionNames.SAVE_AS)) { + GuiPackage.getInstance().setTestPlanFile(updateFile); + } + } + + try { + convertSubTree(subTree); + } catch (Exception err) { + log.warn("Error converting subtree "+err); + } + + FileOutputStream ostream = null; + try { + ostream = new FileOutputStream(updateFile); + SaveService.saveTree(subTree, ostream); + if (fullSave) { // Only update the stored copy of the tree for a full save + subTree = GuiPackage.getInstance().getTreeModel().getTestPlan(); // refetch, because convertSubTree affects it + ActionRouter.getInstance().doActionNow(new ActionEvent(subTree, e.getID(), ActionNames.SUB_TREE_SAVED)); + } + } catch (Throwable ex) { + log.error("Error saving tree:", ex); + if (ex instanceof Error){ + throw (Error) ex; + } + if (ex instanceof RuntimeException){ + throw (RuntimeException) ex; + } + throw new IllegalUserActionException("Couldn't save test plan to file: " + updateFile, ex); + } finally { + JOrphanUtils.closeQuietly(ostream); + } + GuiPackage.getInstance().updateCurrentGui(); + } + + // package protected to allow access from test code + void convertSubTree(HashTree tree) { + Iterator iter = new LinkedList(tree.list()).iterator(); + while (iter.hasNext()) { + JMeterTreeNode item = (JMeterTreeNode) iter.next(); + convertSubTree(tree.getTree(item)); + TestElement testElement = item.getTestElement(); // requires JMeterTreeNode + tree.replace(item, testElement); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/SaveGraphics.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/SaveGraphics.java new file mode 100644 index 0000000..31d0ae7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/SaveGraphics.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.save.SaveGraphicsService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Printable; + +/** + * 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 + * element implements Printable interface. If it does, it call getPrintable() to + * get the JComponent. By default, it will use SaveGraphicsService to save a PNG + * file if no extension is provided. If either .png or .tif is in the filename, + * it will call SaveGraphicsService to save in the format. + */ +public class SaveGraphics implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.SAVE_GRAPHICS); + commands.add(ActionNames.SAVE_GRAPHICS_ALL); + } + + private static final String[] extensions + = { SaveGraphicsService.TIFF_EXTENSION, SaveGraphicsService.PNG_EXTENSION }; + + /** + * Constructor for the Save object. + */ + public SaveGraphics() { + } + + /** + * Gets the ActionNames attribute of the Save object. + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commands; + } + + public void doAction(ActionEvent e) throws IllegalUserActionException { + if (!commands.contains(e.getActionCommand())) { + throw new IllegalUserActionException("Invalid user command:" + e.getActionCommand()); + } + if (e.getActionCommand().equals(ActionNames.SAVE_GRAPHICS)) { + JMeterGUIComponent component = GuiPackage.getInstance().getCurrentGui(); + // get the JComponent from the visualizer + if (component instanceof Printable) { + JComponent comp = ((Printable) component).getPrintableComponent(); + saveImage(comp); + } + } + if (e.getActionCommand().equals(ActionNames.SAVE_GRAPHICS_ALL)) { + JMeterGUIComponent component = GuiPackage.getInstance().getCurrentGui(); + JComponent comp=((JComponent) component).getRootPane(); + saveImage(comp); + } + } + + private void saveImage(JComponent comp){ + + String filename; + JFileChooser chooser = FileDialoger.promptToSaveFile(GuiPackage.getInstance().getTreeListener() + .getCurrentNode().getName(), extensions); + if (chooser == null) { + return; + } + // Get the string given from the choose and check + // the file extension. + filename = chooser.getSelectedFile().getAbsolutePath(); + if (filename != null) { + File f = new File(filename); + if(f.exists()) { + int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("save_overwrite_existing_file"), // $NON-NLS-1$ + JMeterUtils.getResString("save?"), // $NON-NLS-1$ + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.NO_OPTION) { + return ; // Do not save, user does not want to overwrite + } + } + SaveGraphicsService save = new SaveGraphicsService(); + String ext = filename.substring(filename.length() - 4); + String name = filename.substring(0, filename.length() - 4); + if (ext.equals(SaveGraphicsService.PNG_EXTENSION)) { + save.saveJComponent(name, SaveGraphicsService.PNG, comp); + } else if (ext.equals(SaveGraphicsService.TIFF_EXTENSION)) { + save.saveJComponent(name, SaveGraphicsService.TIFF, comp); + } else { + save.saveJComponent(filename, SaveGraphicsService.PNG, comp); + } + } + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/SearchTreeCommand.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/SearchTreeCommand.java new file mode 100644 index 0000000..91e3c04 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/SearchTreeCommand.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.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +/** + * Search nodes for a text + * TODO Enhance search dialog to select kind of nodes .... + */ +public class SearchTreeCommand extends AbstractAction { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.SEARCH_TREE); + } + + private SearchTreeDialog dialog = new SearchTreeDialog(); + /** + * @see Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + dialog.setVisible(true); + } + + + /** + * @see Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/SearchTreeDialog.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/SearchTreeDialog.java new file mode 100644 index 0000000..e2c1d00 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/SearchTreeDialog.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JPanel; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.Searchable; +import org.apache.jmeter.gui.tree.JMeterTreeModel; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * + */ +public class SearchTreeDialog extends JDialog implements ActionListener { + /** + * + */ + private static final long serialVersionUID = -4436834972710248247L; + + private Logger logger = LoggingManager.getLoggerForClass(); + + private JButton searchButton; + + private JLabeledTextField searchTF; + + private JCheckBox isRegexpCB; + + private JCheckBox isCaseSensitiveCB; + + private JButton cancelButton; + + /** + * Store last search + */ + private transient String lastSearch = null; + + /** + * Hide Window on ESC + */ + private transient ActionListener enterActionListener = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + doSearch(actionEvent); + } + }; + + /** + * Do search on Enter + */ + private transient ActionListener escapeActionListener = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + setVisible(false); + } + }; + + public SearchTreeDialog() { + super((JFrame) null, JMeterUtils.getResString("search_tree_title"), true); //$NON-NLS-1$ + init(); + } + + private void init() { + this.getContentPane().setLayout(new BorderLayout(10,10)); + + searchTF = new JLabeledTextField(JMeterUtils.getResString("search_text_field"), 20); //$NON-NLS-1$ + if(!StringUtils.isEmpty(lastSearch)) { + searchTF.setText(lastSearch); + } + isRegexpCB = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_regexp"), false); //$NON-NLS-1$ + isCaseSensitiveCB = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_case"), false); //$NON-NLS-1$ + Font font = new Font("SansSerif", Font.PLAIN, 10); // reduce font + isRegexpCB.setFont(font); + isCaseSensitiveCB.setFont(font); + + JPanel searchCriterionPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + searchCriterionPanel.add(isCaseSensitiveCB); + searchCriterionPanel.add(isRegexpCB); + + JPanel searchPanel = new JPanel(); + searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.Y_AXIS)); + searchPanel.setBorder(BorderFactory.createEmptyBorder(7, 3, 3, 3)); + searchPanel.add(searchTF, BorderLayout.NORTH); + searchPanel.add(searchCriterionPanel, BorderLayout.CENTER); + JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + + searchButton = new JButton(JMeterUtils.getResString("search")); //$NON-NLS-1$ + searchButton.addActionListener(this); + cancelButton = new JButton(JMeterUtils.getResString("cancel")); //$NON-NLS-1$ + cancelButton.addActionListener(this); + buttonsPanel.add(searchButton); + buttonsPanel.add(cancelButton); + searchPanel.add(buttonsPanel, BorderLayout.SOUTH); + this.getContentPane().add(searchPanel); + searchPanel.registerKeyboardAction(enterActionListener, KeyStrokes.ENTER, JComponent.WHEN_IN_FOCUSED_WINDOW); + searchPanel.registerKeyboardAction(escapeActionListener, KeyStrokes.ESC, JComponent.WHEN_IN_FOCUSED_WINDOW); + searchTF.requestFocusInWindow(); + + this.pack(); + ComponentUtil.centerComponentInWindow(this); + } + + /** + * Do search + * @param e {@link ActionEvent} + */ + public void actionPerformed(ActionEvent e) { + if(e.getSource()==cancelButton) { + this.setVisible(false); + return; + } + doSearch(e); + } + + /** + * @param e {@link ActionEvent} + */ + private void doSearch(ActionEvent e) { + String wordToSearch = searchTF.getText(); + if(StringUtils.isEmpty(wordToSearch)) { + return; + } else { + this.lastSearch = wordToSearch; + } + + // reset previous result + ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.SEARCH_RESET)); + // do search + Searcher searcher = null; + if(isRegexpCB.isSelected()) { + searcher = new RegexpSearcher(isCaseSensitiveCB.isSelected(), searchTF.getText()); + } else { + searcher = new RawTextSearcher(isCaseSensitiveCB.isSelected(), searchTF.getText()); + } + GuiPackage guiPackage = GuiPackage.getInstance(); + JMeterTreeModel jMeterTreeModel = guiPackage.getTreeModel(); + Set nodes = new HashSet(); + for (JMeterTreeNode jMeterTreeNode : jMeterTreeModel.getNodesOfType(Searchable.class)) { + try { + if (jMeterTreeNode.getUserObject() instanceof Searchable){ + Searchable searchable = (Searchable) jMeterTreeNode.getUserObject(); + List matchingNodes = jMeterTreeNode.getPathToThreadGroup(); + List searchableTokens = searchable.getSearchableTokens(); + boolean result = searcher.search(searchableTokens); + if(result) { + nodes.addAll(matchingNodes); + } + } + } catch (Exception ex) { + logger.error("Error occured searching for word:"+ wordToSearch, ex); + } + } + for (Iterator iterator = nodes.iterator(); iterator.hasNext();) { + JMeterTreeNode jMeterTreeNode = iterator.next(); + jMeterTreeNode.setMarkedBySearch(true); + } + GuiPackage.getInstance().getMainFrame().repaint(); + this.setVisible(false); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Searcher.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Searcher.java new file mode 100644 index 0000000..2972711 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Searcher.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.util.List; + +/** + * Search algorithm + */ +public interface Searcher { + + /** + * Implements the search + * @param textTokens List content to be searched + * @return true if search on textTokens is successful + */ + boolean search(List textTokens); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/Start.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/Start.java new file mode 100644 index 0000000..baf8d8a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/Start.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.JMeter; +import org.apache.jmeter.engine.JMeterEngineException; +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.engine.TreeCloner; +import org.apache.jmeter.engine.TreeClonerNoTimer; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.timers.Timer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class Start extends AbstractAction { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.ACTION_START); + commands.add(ActionNames.ACTION_START_NO_TIMERS); + commands.add(ActionNames.ACTION_STOP); + commands.add(ActionNames.ACTION_SHUTDOWN); + } + + private StandardJMeterEngine engine; + + /** + * Constructor for the Start object. + */ + public Start() { + } + + /** + * Gets the ActionNames attribute of the Start object. + * + * @return the ActionNames value + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + if (e.getActionCommand().equals(ActionNames.ACTION_START)) { + popupShouldSave(e); + startEngine(false); + } else if (e.getActionCommand().equals(ActionNames.ACTION_START_NO_TIMERS)) { + popupShouldSave(e); + startEngine(true); + } else if (e.getActionCommand().equals(ActionNames.ACTION_STOP)) { + if (engine != null) { + log.info("Stopping test"); + GuiPackage.getInstance().getMainFrame().showStoppingMessage(""); + engine.stopTest(); + } + } else if (e.getActionCommand().equals(ActionNames.ACTION_SHUTDOWN)) { + if (engine != null) { + log.info("Shutting test down"); + GuiPackage.getInstance().getMainFrame().showStoppingMessage(""); + engine.askThreadsToStop(); + } + } + } + + /** + * Start JMeter engine + * @param noTimer ignore timers + */ + private void startEngine(boolean ignoreTimer) { + GuiPackage gui = GuiPackage.getInstance(); + HashTree testTree = gui.getTreeModel().getTestPlan(); + JMeter.convertSubTree(testTree); + testTree.add(testTree.getArray()[0], gui.getMainFrame()); + log.debug("test plan before cloning is running version: " + + ((TestPlan) testTree.getArray()[0]).isRunningVersion()); + TreeCloner cloner = cloneTree(testTree, ignoreTimer); + engine = new StandardJMeterEngine(); + engine.configure(cloner.getClonedTree()); + try { + engine.runTest(); + } catch (JMeterEngineException e) { + JOptionPane.showMessageDialog(gui.getMainFrame(), e.getMessage(), JMeterUtils + .getResString("Error Occurred"), JOptionPane.ERROR_MESSAGE); + } + log.debug("test plan after cloning and running test is running version: " + + ((TestPlan) testTree.getArray()[0]).isRunningVersion()); + } + + /** + * Create a Cloner that ignores {@link Timer} if removeTimers is true + * @param testTree {@link HashTree} + * @param removeTimers boolean remove timers + * @return {@link TreeCloner} + */ + private TreeCloner cloneTree(HashTree testTree, boolean removeTimers) { + TreeCloner cloner = null; + if(removeTimers) { + cloner = new TreeClonerNoTimer(false); + } else { + cloner = new TreeCloner(false); + } + testTree.traverse(cloner); + return cloner; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/StopStoppables.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/StopStoppables.java new file mode 100644 index 0000000..bddabdd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/StopStoppables.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.Stoppable; + +/** + * Stops stopables (Proxy, Mirror) + * @since 2.5.1 + */ +public class StopStoppables extends AbstractAction implements ActionListener { + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.STOP_THREAD); + } + + /** + * + */ + public StopStoppables() { + super(); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.action.AbstractAction#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + /* (non-Javadoc) + * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + public void actionPerformed(ActionEvent e) { + + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.action.AbstractAction#doAction(java.awt.event.ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + GuiPackage instance = GuiPackage.getInstance(); + List stopables = instance.getStoppables(); + for (Stoppable element : stopables) { + instance.unregister(element); + element.stopServer(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/ToolBar.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/ToolBar.java new file mode 100644 index 0000000..9927cb3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/ToolBar.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.GuiPackage; + +/** + * Hide / unhide toolbar. + * + */ +public class ToolBar implements Command { + + private static final Set commands = new HashSet(); + + static { + commands.add(ActionNames.TOOLBAR); + } + + /** + * Constructor for object. + */ + public ToolBar() { + } + + /** + * Gets the ActionNames attribute of the action + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commands; + } + + /** + * This method performs the actual command processing. + * + * @param e + * the generic UI action event + */ + public void doAction(ActionEvent e) { + if (ActionNames.TOOLBAR.equals(e.getActionCommand())) { + GuiPackage guiInstance = GuiPackage.getInstance(); + if (guiInstance.getMenuItemToolbar().getModel().isSelected()) { + guiInstance.getMainToolbar().setVisible(true); + } else { + guiInstance.getMainToolbar().setVisible(false); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/action/What.java b/ApacheJmeter/src/org/apache/jmeter/gui/action/What.java new file mode 100644 index 0000000..2e2ea49 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/action/What.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.action; + +import java.awt.event.ActionEvent; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * + * Debug class to show details of the currently selected object + * Currently shows TestElement and GUI class names + * + * Also enables/disables debug for the test element. + * + */ +public class What implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commandSet; + + static { + HashSet commands = new HashSet(); + commands.add(ActionNames.WHAT_CLASS); + commands.add(ActionNames.DEBUG_ON); + commands.add(ActionNames.DEBUG_OFF); + commandSet = Collections.unmodifiableSet(commands); + } + + + public void doAction(ActionEvent e) throws IllegalUserActionException { + JMeterTreeNode node= GuiPackage.getInstance().getTreeListener().getCurrentNode(); + TestElement te = (TestElement)node.getUserObject(); + if (ActionNames.WHAT_CLASS.equals(e.getActionCommand())){ + String guiClassName = te.getPropertyAsString(TestElement.GUI_CLASS); + System.out.println(te.getClass().getName()); + System.out.println(guiClassName); + log.info("TestElement:"+te.getClass().getName()+", guiClassName:"+guiClassName); + } else if (ActionNames.DEBUG_ON.equals(e.getActionCommand())){ + LoggingManager.setPriorityFullName("DEBUG",te.getClass().getName());//$NON-NLS-1$ + } else if (ActionNames.DEBUG_OFF.equals(e.getActionCommand())){ + LoggingManager.setPriorityFullName("INFO",te.getClass().getName());//$NON-NLS-1$ + } + } + + /** + * Provide the list of Action names that are available in this command. + */ + public Set getActionNames() { + return commandSet; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterCellRenderer.java b/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterCellRenderer.java new file mode 100644 index 0000000..c7f198d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterCellRenderer.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.tree; + +import java.awt.Color; +import java.awt.Component; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JTree; +import javax.swing.tree.DefaultTreeCellRenderer; + +/** + * Class to render the test tree - sets the enabled/disabled versions of the icons + */ +public class JMeterCellRenderer extends DefaultTreeCellRenderer { + private static final long serialVersionUID = 240L; + + public JMeterCellRenderer() { + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, + boolean leaf, int row, boolean p_hasFocus) { + JMeterTreeNode node = (JMeterTreeNode) value; + super.getTreeCellRendererComponent(tree, node.getName(), sel, expanded, leaf, row, + p_hasFocus); + boolean enabled = node.isEnabled(); + ImageIcon ic = node.getIcon(enabled); + if (ic != null) { + if (enabled) { + setIcon(ic); + } else { + setDisabledIcon(ic); + } + } else { + if (!enabled)// i.e. no disabled icon found + { + // Must therefore set the enabled icon so there is at least some + // icon + ic = node.getIcon(); + if (ic != null) { + setIcon(ic); + } + } + } + this.setEnabled(enabled); + if(node.isMarkedBySearch()) { + setBorder(BorderFactory.createLineBorder(Color.red)); + } + else { + setBorder(null); + } + return this; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeListener.java b/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeListener.java new file mode 100644 index 0000000..e9be11b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeListener.java @@ -0,0 +1,326 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.tree; + +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; + +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JTree; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.control.gui.TestPlanGui; +import org.apache.jmeter.control.gui.WorkBenchGui; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.MainFrame; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JMeterTreeListener implements TreeSelectionListener, MouseListener, KeyListener, MouseMotionListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Container endWindow; + // JPopupMenu pop; + private TreePath currentPath; + + private ActionListener actionHandler; + + private JMeterTreeModel model; + + private JTree tree; + + private boolean dragging = false; + + private JMeterTreeNode[] draggedNodes; + + private JLabel dragIcon = new JLabel(JMeterUtils.getImage("leafnode.gif")); // $NON-NLS-1$ + + /** + * Constructor for the JMeterTreeListener object. + */ + public JMeterTreeListener(JMeterTreeModel model) { + this.model = model; + dragIcon.validate(); + dragIcon.setVisible(true); + } + + public JMeterTreeListener() { + dragIcon.validate(); + dragIcon.setVisible(true); + } + + public void setModel(JMeterTreeModel m) { + model = m; + } + + /** + * Sets the ActionHandler attribute of the JMeterTreeListener object. + * + * @param ah + * the new ActionHandler value + */ + public void setActionHandler(ActionListener ah) { + actionHandler = ah; + } + + /** + * Sets the JTree attribute of the JMeterTreeListener object. + * + * @param tree + * the new JTree value + */ + public void setJTree(JTree tree) { + this.tree = tree; + } + + /** + * Sets the EndWindow attribute of the JMeterTreeListener object. + * + * @param window + * the new EndWindow value + */ + public void setEndWindow(Container window) { + // endWindow = window; + } + + /** + * Gets the JTree attribute of the JMeterTreeListener object. + * + * @return tree the current JTree value. + */ + public JTree getJTree() { + return tree; + } + + /** + * Gets the CurrentNode attribute of the JMeterTreeListener object. + * + * @return the CurrentNode value + */ + public JMeterTreeNode getCurrentNode() { + if (currentPath != null) { + if (currentPath.getLastPathComponent() != null) { + return (JMeterTreeNode) currentPath.getLastPathComponent(); + } + return (JMeterTreeNode) currentPath.getParentPath().getLastPathComponent(); + } + return (JMeterTreeNode) model.getRoot(); + } + + public JMeterTreeNode[] getSelectedNodes() { + TreePath[] paths = tree.getSelectionPaths(); + if (paths == null) { + return new JMeterTreeNode[] { getCurrentNode() }; + } + JMeterTreeNode[] nodes = new JMeterTreeNode[paths.length]; + for (int i = 0; i < paths.length; i++) { + nodes[i] = (JMeterTreeNode) paths[i].getLastPathComponent(); + } + + return nodes; + } + + public TreePath removedSelectedNode() { + currentPath = currentPath.getParentPath(); + return currentPath; + } + + public void valueChanged(TreeSelectionEvent e) { + log.debug("value changed, updating currentPath"); + currentPath = e.getNewLeadSelectionPath(); + actionHandler.actionPerformed(new ActionEvent(this, 3333, "edit")); // $NON-NLS-1$ + } + + public void mouseClicked(MouseEvent ev) { + } + + public void mouseReleased(MouseEvent e) { + if (dragging && isValidDragAction(draggedNodes, getCurrentNode())) { + dragging = false; + JPopupMenu dragNdrop = new JPopupMenu(); + JMenuItem item = new JMenuItem(JMeterUtils.getResString("insert_before")); // $NON-NLS-1$ + item.addActionListener(actionHandler); + item.setActionCommand(ActionNames.INSERT_BEFORE); + dragNdrop.add(item); + item = new JMenuItem(JMeterUtils.getResString("insert_after")); // $NON-NLS-1$ + item.addActionListener(actionHandler); + item.setActionCommand(ActionNames.INSERT_AFTER); + dragNdrop.add(item); + if (MenuFactory.canAddTo(getCurrentNode(), draggedNodes)){ + item = new JMenuItem(JMeterUtils.getResString("add_as_child")); // $NON-NLS-1$ + item.addActionListener(actionHandler); + item.setActionCommand(ActionNames.DRAG_ADD); + dragNdrop.add(item); + } + dragNdrop.addSeparator(); + item = new JMenuItem(JMeterUtils.getResString("cancel")); // $NON-NLS-1$ + dragNdrop.add(item); + displayPopUp(e, dragNdrop); + } else { + GuiPackage.getInstance().getMainFrame().repaint(); + } + dragging = false; + } + + public JMeterTreeNode[] getDraggedNodes() { + return draggedNodes; + } + + /** + * Tests if the node is being dragged into one of it's own sub-nodes, or + * into itself. + */ + private boolean isValidDragAction(JMeterTreeNode[] source, JMeterTreeNode dest) { + boolean isValid = true; + TreeNode[] path = dest.getPath(); + for (int i = 0; i < path.length; i++) { + if (contains(source, path[i])) { + isValid = false; + } + } + return isValid; + } + + public void mouseEntered(MouseEvent e) { + } + + private void changeSelectionIfDragging(MouseEvent e) { + if (dragging) { + GuiPackage.getInstance().getMainFrame().drawDraggedComponent(dragIcon, e.getX(), e.getY()); + if (tree.getPathForLocation(e.getX(), e.getY()) != null) { + currentPath = tree.getPathForLocation(e.getX(), e.getY()); + if (!contains(draggedNodes, getCurrentNode())) { + tree.setSelectionPath(currentPath); + } + } + } + } + + private boolean contains(Object[] container, Object item) { + for (int i = 0; i < container.length; i++) { + if (container[i] == item) { + return true; + } + } + return false; + } + + public void mousePressed(MouseEvent e) { + // Get the Main Frame. + MainFrame mainFrame = GuiPackage.getInstance().getMainFrame(); + // Close any Main Menu that is open + mainFrame.closeMenu(); + int selRow = tree.getRowForLocation(e.getX(), e.getY()); + if (tree.getPathForLocation(e.getX(), e.getY()) != null) { + log.debug("mouse pressed, updating currentPath"); + currentPath = tree.getPathForLocation(e.getX(), e.getY()); + } + if (selRow != -1) { + // updateMainMenu(((JMeterGUIComponent) + // getCurrentNode().getUserObject()).createPopupMenu()); + if (isRightClick(e)) { + if (tree.getSelectionCount() < 2) { + tree.setSelectionPath(currentPath); + } + log.debug("About to display pop-up"); + displayPopUp(e); + } + } + } + + public void mouseDragged(MouseEvent e) { + if (!dragging) { + dragging = true; + draggedNodes = getSelectedNodes(); + if (draggedNodes[0].getUserObject() instanceof TestPlanGui + || draggedNodes[0].getUserObject() instanceof WorkBenchGui) { + dragging = false; + } + + } + changeSelectionIfDragging(e); + } + + public void mouseMoved(MouseEvent e) { + } + + public void mouseExited(MouseEvent ev) { + } + + public void keyPressed(KeyEvent e) { + if (KeyStrokes.matches(e,KeyStrokes.COPY)) { + ActionRouter actionRouter = ActionRouter.getInstance(); + actionRouter.doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.COPY)); + } else if (KeyStrokes.matches(e,KeyStrokes.PASTE)) { + ActionRouter actionRouter = ActionRouter.getInstance(); + actionRouter.doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.PASTE)); + } else if (KeyStrokes.matches(e,KeyStrokes.CUT)) { + ActionRouter actionRouter = ActionRouter.getInstance(); + actionRouter.doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.CUT)); +// If the following lines are included, then pressing the DUPLICATE key results in calling the action twice. +// Without the code below, it still works. +// Odd, the other keypresses do need to be handled above or they do not work at all... +// } else if (KeyStrokes.matches(e,KeyStrokes.DUPLICATE)) { +// ActionRouter actionRouter = ActionRouter.getInstance(); +// actionRouter.doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.DUPLICATE)); + } + } + + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + } + + private boolean isRightClick(MouseEvent e) { + return e.isPopupTrigger() || (MouseEvent.BUTTON2_MASK & e.getModifiers()) > 0 || (MouseEvent.BUTTON3_MASK == e.getModifiers()); + } + + private void displayPopUp(MouseEvent e) { + JPopupMenu pop = getCurrentNode().createPopupMenu(); + GuiPackage.getInstance().displayPopUp(e, pop); + } + + private void displayPopUp(MouseEvent e, JPopupMenu popup) { + // See Bug 46108 - this log message is unnecessary and misleading + // log.warn("Shouldn't be here"); + if (popup != null) { + popup.pack(); + popup.show(tree, e.getX(), e.getY()); + popup.setVisible(true); + popup.requestFocus(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeModel.java b/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeModel.java new file mode 100644 index 0000000..fc4bff8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeModel.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.tree; + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.tree.DefaultTreeModel; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.control.gui.TestPlanGui; +import org.apache.jmeter.control.gui.WorkBenchGui; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; + +public class JMeterTreeModel extends DefaultTreeModel { + + private static final long serialVersionUID = 240L; + + public JMeterTreeModel(TestElement tp, TestElement wb) { + super(new JMeterTreeNode(wb, null)); + initTree(tp,wb); + } + + public JMeterTreeModel() { + this(new TestPlanGui().createTestElement(),new WorkBenchGui().createTestElement()); +// super(new JMeterTreeNode(new WorkBenchGui().createTestElement(), null)); +// TestElement tp = new TestPlanGui().createTestElement(); +// initTree(tp); + } + + /** + * Hack to allow TreeModel to be used in non-GUI and headless mode. + * + * @deprecated - only for use by JMeter class! + * @param o - dummy + */ + @Deprecated + public JMeterTreeModel(Object o) { + this(new TestPlan(),new WorkBench()); +// super(new JMeterTreeNode(new WorkBench(), null)); +// TestElement tp = new TestPlan(); +// initTree(tp, new WorkBench()); + } + + /** + * Returns a list of tree nodes that hold objects of the given class type. + * If none are found, an empty list is returned. + */ + public List getNodesOfType(Class type) { + List nodeList = new LinkedList(); + traverseAndFind(type, (JMeterTreeNode) this.getRoot(), nodeList); + return nodeList; + } + + /** + * Get the node for a given TestElement object. + */ + public JMeterTreeNode getNodeOf(TestElement userObject) { + return traverseAndFind(userObject, (JMeterTreeNode) getRoot()); + } + + /** + * Adds the sub tree at the given node. Returns a boolean indicating whether + * the added sub tree was a full test plan. + */ + public HashTree addSubTree(HashTree subTree, JMeterTreeNode current) throws IllegalUserActionException { + Iterator iter = subTree.list().iterator(); + while (iter.hasNext()) { + TestElement item = (TestElement) iter.next(); + if (item instanceof TestPlan) { + TestPlan tp = (TestPlan) item; + current = (JMeterTreeNode) ((JMeterTreeNode) getRoot()).getChildAt(0); + final TestPlan userObject = (TestPlan) current.getUserObject(); + userObject.addTestElement(item); + userObject.setName(item.getName()); + userObject.setFunctionalMode(tp.isFunctionalMode()); + userObject.setSerialized(tp.isSerialized()); + addSubTree(subTree.getTree(item), current); + } else if (item instanceof WorkBench) { + current = (JMeterTreeNode) ((JMeterTreeNode) getRoot()).getChildAt(1); + final TestElement testElement = ((TestElement) current.getUserObject()); + testElement.addTestElement(item); + testElement.setName(item.getName()); + addSubTree(subTree.getTree(item), current); + } else { + addSubTree(subTree.getTree(item), addComponent(item, current)); + } + } + return getCurrentSubTree(current); + } + + public JMeterTreeNode addComponent(TestElement component, JMeterTreeNode node) throws IllegalUserActionException { + if (node.getUserObject() instanceof AbstractConfigGui) { + throw new IllegalUserActionException("This node cannot hold sub-elements"); + } + + GuiPackage guiPackage = GuiPackage.getInstance(); + if (guiPackage != null) { + // The node can be added in non GUI mode at startup + guiPackage.updateCurrentNode(); + JMeterGUIComponent guicomp = guiPackage.getGui(component); + guicomp.configure(component); + guicomp.modifyTestElement(component); + guiPackage.getCurrentGui(); // put the gui object back + // to the way it was. + } + JMeterTreeNode newNode = new JMeterTreeNode(component, this); + + // This check the state of the TestElement and if returns false it + // disable the loaded node + try { + if (component.getProperty(TestElement.ENABLED) instanceof NullProperty + || component.getPropertyAsBoolean(TestElement.ENABLED)) { + newNode.setEnabled(true); + } else { + newNode.setEnabled(false); + } + } catch (Exception e) { + newNode.setEnabled(true); + } + + this.insertNodeInto(newNode, node, node.getChildCount()); + return newNode; + } + + public void removeNodeFromParent(JMeterTreeNode node) { + if (!(node.getUserObject() instanceof TestPlan) && !(node.getUserObject() instanceof WorkBench)) { + super.removeNodeFromParent(node); + } + } + + private void traverseAndFind(Class type, JMeterTreeNode node, List nodeList) { + if (type.isInstance(node.getUserObject())) { + nodeList.add(node); + } + Enumeration enumNode = node.children(); + while (enumNode.hasMoreElements()) { + JMeterTreeNode child = enumNode.nextElement(); + traverseAndFind(type, child, nodeList); + } + } + + private JMeterTreeNode traverseAndFind(TestElement userObject, JMeterTreeNode node) { + if (userObject == node.getUserObject()) { + return node; + } + Enumeration enumNode = node.children(); + while (enumNode.hasMoreElements()) { + JMeterTreeNode child = enumNode.nextElement(); + JMeterTreeNode result = traverseAndFind(userObject, child); + if (result != null) { + return result; + } + } + return null; + } + + public HashTree getCurrentSubTree(JMeterTreeNode node) { + ListedHashTree hashTree = new ListedHashTree(node); + Enumeration enumNode = node.children(); + while (enumNode.hasMoreElements()) { + JMeterTreeNode child = enumNode.nextElement(); + hashTree.add(node, getCurrentSubTree(child)); + } + return hashTree; + } + + public HashTree getTestPlan() { + return getCurrentSubTree((JMeterTreeNode) ((JMeterTreeNode) this.getRoot()).getChildAt(0)); + } + + /** + * Clear the test plan, and use default node for test plan and workbench. + * + * N.B. Should only be called by {@link GuiPackage#clearTestPlan()} + */ + public void clearTestPlan() { + TestElement tp = new TestPlanGui().createTestElement(); + clearTestPlan(tp); + } + + /** + * Clear the test plan, and use specified node for test plan and default node for workbench + * + * N.B. Should only be called by {@link GuiPackage#clearTestPlan(TestElement)} + * + * @param testPlan the node to use as the testplan top node + */ + public void clearTestPlan(TestElement testPlan) { + // Remove the workbench and testplan nodes + int children = getChildCount(getRoot()); + while (children > 0) { + JMeterTreeNode child = (JMeterTreeNode)getChild(getRoot(), 0); + super.removeNodeFromParent(child); + children = getChildCount(getRoot()); + } + // Init the tree + initTree(testPlan,new WorkBenchGui().createTestElement()); // Assumes this is only called from GUI mode + } + + /** + * Initialize the model with nodes for testplan and workbench. + * + * @param tp the element to use as testplan + * @param wb the element to use as workbench + */ + private void initTree(TestElement tp, TestElement wb) { + // Insert the test plan node + insertNodeInto(new JMeterTreeNode(tp, this), (JMeterTreeNode) getRoot(), 0); + // Insert the workbench node + insertNodeInto(new JMeterTreeNode(wb, this), (JMeterTreeNode) getRoot(), 1); + // Let others know that the tree content has changed. + // This should not be necessary, but without it, nodes are not shown when the user + // uses the Close menu item + nodeStructureChanged((JMeterTreeNode)getRoot()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeNode.java b/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeNode.java new file mode 100644 index 0000000..03abffc --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/tree/JMeterTreeNode.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.tree; + +import java.awt.Image; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.ImageIcon; +import javax.swing.JPopupMenu; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; + +import org.apache.jmeter.gui.GUIFactory; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JMeterTreeNode extends DefaultMutableTreeNode implements NamedTreeNode { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int TEST_PLAN_LEVEL = 1; + + private final JMeterTreeModel treeModel; + + private boolean markedBySearch; + + public JMeterTreeNode() {// Allow serializable test to work + // TODO: is the serializable test necessary now that JMeterTreeNode is + // no longer a GUI component? + this(null, null); + } + + public JMeterTreeNode(TestElement userObj, JMeterTreeModel treeModel) { + super(userObj); + this.treeModel = treeModel; + } + + public boolean isEnabled() { + return ((AbstractTestElement) getTestElement()).getPropertyAsBoolean(TestElement.ENABLED); + } + + public void setEnabled(boolean enabled) { + getTestElement().setProperty(new BooleanProperty(TestElement.ENABLED, enabled)); + treeModel.nodeChanged(this); + } + + /** + * Return nodes to level 2 + * @return {@link List} + */ + public List getPathToThreadGroup() { + List nodes = new ArrayList(); + if(treeModel != null) { + TreeNode[] nodesToRoot = treeModel.getPathToRoot(this); + for (int i = 0; i < nodesToRoot.length; i++) { + JMeterTreeNode jMeterTreeNode = (JMeterTreeNode) nodesToRoot[i]; + int level = jMeterTreeNode.getLevel(); + if(level testClass = testElement.getClass(); + try { + Image img = Introspector.getBeanInfo(testClass).getIcon(BeanInfo.ICON_COLOR_16x16); + // If icon has not been defined, then use GUI_CLASS property + if (img == null) { + Object clazz = Introspector.getBeanInfo(testClass).getBeanDescriptor() + .getValue(TestElement.GUI_CLASS); + if (clazz == null) { + log.warn("getIcon(): Can't obtain GUI class from " + testClass.getName()); + return null; + } + return GUIFactory.getIcon(Class.forName((String) clazz), enabled); + } + return new ImageIcon(img); + } catch (IntrospectionException e1) { + log.error("Can't obtain icon for class "+testElement, e1); + throw new org.apache.jorphan.util.JMeterError(e1); + } + } + return GUIFactory.getIcon(Class.forName(testElement.getPropertyAsString(TestElement.GUI_CLASS)), + enabled); + } catch (ClassNotFoundException e) { + log.warn("Can't get icon for class " + testElement, e); + return null; + } + } + + public Collection getMenuCategories() { + try { + return GuiPackage.getInstance().getGui(getTestElement()).getMenuCategories(); + } catch (Exception e) { + log.error("Can't get popup menu for gui", e); + return null; + } + } + + public JPopupMenu createPopupMenu() { + try { + return GuiPackage.getInstance().getGui(getTestElement()).createPopupMenu(); + } catch (Exception e) { + log.error("Can't get popup menu for gui", e); + return null; + } + } + + public TestElement getTestElement() { + return (TestElement) getUserObject(); + } + + public String getStaticLabel() { + return GuiPackage.getInstance().getGui((TestElement) getUserObject()).getStaticLabel(); + } + + public String getDocAnchor() { + return GuiPackage.getInstance().getGui((TestElement) getUserObject()).getDocAnchor(); + } + + /** {@inheritDoc} */ + public void setName(String name) { + ((TestElement) getUserObject()).setName(name); + } + + /** {@inheritDoc} */ + public String getName() { + return ((TestElement) getUserObject()).getName(); + } + + /** {@inheritDoc} */ + public void nameChanged() { + if (treeModel != null) { // may be null during startup + treeModel.nodeChanged(this); + } + } + + // Override in order to provide type safety + @Override + @SuppressWarnings("unchecked") + public Enumeration children() { + return super.children(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/tree/NamedTreeNode.java b/ApacheJmeter/src/org/apache/jmeter/gui/tree/NamedTreeNode.java new file mode 100644 index 0000000..bd3b81f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/tree/NamedTreeNode.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.tree; + +public interface NamedTreeNode { + + void setName(String name); + String getName(); + void nameChanged(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/ButtonPanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/ButtonPanel.java new file mode 100644 index 0000000..f4ea383 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/ButtonPanel.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JPanel; + +import org.apache.jmeter.util.JMeterUtils; + +// TODO - does not appear to be used + +public class ButtonPanel extends JPanel { + private static final long serialVersionUID = 240L; + + public final static int ADD_BUTTON = 1; + + public final static int EDIT_BUTTON = 2; + + public final static int DELETE_BUTTON = 3; + + public final static int LOAD_BUTTON = 4; + + public final static int SAVE_BUTTON = 5; + + private JButton add, delete, edit, load, save; + + public ButtonPanel() { + init(); + } + + public void addButtonListener(int button, ActionListener listener) { + switch (button) { + case ADD_BUTTON: + add.addActionListener(listener); + break; + case EDIT_BUTTON: + edit.addActionListener(listener); + break; + case DELETE_BUTTON: + delete.addActionListener(listener); + break; + case LOAD_BUTTON: + load.addActionListener(listener); + break; + case SAVE_BUTTON: + save.addActionListener(listener); + break; + } + } + + /* + * NOTUSED private void initButtonMap() { } + */ + private void init() { + add = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ + add.setActionCommand("Add"); + edit = new JButton(JMeterUtils.getResString("edit")); // $NON-NLS-1$ + edit.setActionCommand("Edit"); + delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + delete.setActionCommand("Delete"); + load = new JButton(JMeterUtils.getResString("load")); // $NON-NLS-1$ + load.setActionCommand("Load"); + save = new JButton(JMeterUtils.getResString("save")); // $NON-NLS-1$ + save.setActionCommand("Save"); + Dimension d = delete.getPreferredSize(); + add.setPreferredSize(d); + edit.setPreferredSize(d); + // close.setPreferredSize(d); + load.setPreferredSize(d); + save.setPreferredSize(d); + GridBagLayout g = new GridBagLayout(); + this.setLayout(g); + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.gridwidth = 1; + c.gridheight = 1; + c.gridx = 1; + c.gridy = 1; + g.setConstraints(add, c); + this.add(add); + c.gridx = 2; + c.gridy = 1; + g.setConstraints(edit, c); + this.add(edit); + c.gridx = 3; + c.gridy = 1; + g.setConstraints(delete, c); + this.add(delete); + /* + * c.gridx = 1; c.gridy = 2; g.setConstraints(close, c); + * panel.add(close); + */ + c.gridx = 2; + c.gridy = 2; + g.setConstraints(load, c); + this.add(load); + c.gridx = 3; + c.gridy = 2; + g.setConstraints(save, c); + this.add(save); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/DirectoryDialoger.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/DirectoryDialoger.java new file mode 100644 index 0000000..6e20a03 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/DirectoryDialoger.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.io.File; + +import javax.swing.JFileChooser; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.util.JMeterUtils; + +public final class DirectoryDialoger { + /** + * The last directory visited by the user while choosing Files. + */ + private static String lastJFCDirectory = null; + + private static final JFileChooser jfc = new JFileChooser(); + + /** + * Prevent instantiation of utility class. + */ + private DirectoryDialoger() { + } + + public static JFileChooser promptToOpenFile() { + + if (lastJFCDirectory == null) { + String start = System.getProperty("user.dir", ""); + + if (!start.equals("")) { + jfc.setCurrentDirectory(new File(start)); + } + } + jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int retVal = jfc.showDialog(ReportGuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("report_select")); + lastJFCDirectory = jfc.getCurrentDirectory().getAbsolutePath(); + + if (retVal == JFileChooser.APPROVE_OPTION) { + return jfc; + } else { + return null; + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/DirectoryPanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/DirectoryPanel.java new file mode 100644 index 0000000..d9780aa --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/DirectoryPanel.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.util.JMeterUtils; + +public class DirectoryPanel extends HorizontalPanel implements ActionListener { + + private static final long serialVersionUID = 240L; + + private static final String ACTION_BROWSE = "browse"; // $NON-NSL-1$ + + private final JTextField filename = new JTextField(20); + + private final JButton browse = new JButton(JMeterUtils.getResString("browse")); + + private final List listeners = new LinkedList(); + + private final String title; + +// private final String filetype; // NOT USED + + private final Color background; + + /** + * Constructor for the FilePanel object. + */ + public DirectoryPanel() { + this("", null, null); + } + + public DirectoryPanel(String title) { + this(title, null, null); + } + + public DirectoryPanel(String title, String filetype, Color bk) { + this.title = title; +// this.filetype = filetype; + this.background = bk; + init(); + } + + public DirectoryPanel(String title, String filetype) { + this(title, filetype, null); + } + + /** + * Constructor for the FilePanel object. + */ + public DirectoryPanel(ChangeListener l, String title) { + this(title); + listeners.add(l); + } + + public void addChangeListener(ChangeListener l) { + listeners.add(l); + } + + private void init() { + setBackground(this.background); + setBorder(BorderFactory.createTitledBorder(title)); + add(Box.createHorizontalStrut(5)); + add(filename); + add(Box.createHorizontalStrut(5)); + filename.addActionListener(this); + add(browse); + browse.setActionCommand(ACTION_BROWSE); + browse.addActionListener(this); + } + + /** + * If the gui needs to enable/disable the FilePanel, call the method. + * + * @param enable + */ + public void enableFile(boolean enable) { + browse.setEnabled(enable); + filename.setEnabled(enable); + } + + /** + * Gets the filename attribute of the FilePanel object. + * + * @return the filename value + */ + public String getFilename() { + return filename.getText(); + } + + /** + * Sets the filename attribute of the FilePanel object. + * + * @param f + * the new filename value + */ + public void setFilename(String f) { + filename.setText(f); + } + + private void fireFileChanged() { + Iterator iter = listeners.iterator(); + while (iter.hasNext()) { + iter.next().stateChanged(new ChangeEvent(this)); + } + } + + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals(ACTION_BROWSE)) { + JFileChooser chooser = DirectoryDialoger.promptToOpenFile(); + if (chooser.getSelectedFile() != null) { + filename.setText(chooser.getSelectedFile().getPath()); + fireFileChanged(); + } + } else { + fireFileChanged(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/FileDialoger.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/FileDialoger.java new file mode 100644 index 0000000..750ef66 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/FileDialoger.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.io.File; + +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileFilter; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterFileFilter; + +/** + * Class implementing a file open dialogue + */ +public final class FileDialoger { + /** + * The last directory visited by the user while choosing Files. + */ + private static String lastJFCDirectory = null; + + private static JFileChooser jfc = new JFileChooser(); + + /** + * Prevent instantiation of utility class. + */ + private FileDialoger() { + } + + /** + * Prompts the user to choose a file from their filesystems for our own + * devious uses. This method maintains the last directory the user visited + * before dismissing the dialog. This does NOT imply they actually chose a + * file from that directory, only that they closed the dialog there. It is + * the caller's responsibility to check to see if the selected file is + * non-null. + * + * @return the JFileChooser that interacted with the user, after they are + * finished using it - null if no file was chosen + */ + public static JFileChooser promptToOpenFile(String[] exts) { + // JFileChooser jfc = null; + + if (lastJFCDirectory == null) { + String start = System.getProperty("user.dir", ""); //$NON-NLS-1$//$NON-NLS-2$ + + if (start.length() > 0) { + jfc.setCurrentDirectory(new File(start)); + } + } + clearFileFilters(); + if(exts != null && exts.length > 0) { + JMeterFileFilter currentFilter = new JMeterFileFilter(exts); + jfc.addChoosableFileFilter(currentFilter); + jfc.setAcceptAllFileFilterUsed(true); + jfc.setFileFilter(currentFilter); + } + int retVal = jfc.showOpenDialog(GuiPackage.getInstance().getMainFrame()); + lastJFCDirectory = jfc.getCurrentDirectory().getAbsolutePath(); + + if (retVal == JFileChooser.APPROVE_OPTION) { + return jfc; + } + return null; + } + + private static void clearFileFilters() { + FileFilter[] filters = jfc.getChoosableFileFilters(); + for (int x = 0; x < filters.length; x++) { + jfc.removeChoosableFileFilter(filters[x]); + } + } + + public static JFileChooser promptToOpenFile() { + return promptToOpenFile(new String[0]); + } + + /** + * Prompts the user to choose a file from their filesystems for our own + * devious uses. This method maintains the last directory the user visited + * before dismissing the dialog. This does NOT imply they actually chose a + * file from that directory, only that they closed the dialog there. It is + * the caller's responsibility to check to see if the selected file is + * non-null. + * + * @return the JFileChooser that interacted with the user, after they are + * finished using it - null if no file was chosen + * @see #promptToOpenFile() + */ + public static JFileChooser promptToSaveFile(String filename) { + return promptToSaveFile(filename, null); + } + + /** + * Get a JFileChooser with a new FileFilter. + * + * @param filename file name + * @param extensions list of extensions + * @return the FileChooser - null if no file was chosen + */ + public static JFileChooser promptToSaveFile(String filename, String[] extensions) { + if (lastJFCDirectory == null) { + String start = System.getProperty("user.dir", "");//$NON-NLS-1$//$NON-NLS-2$ + if (start.length() > 0) { + jfc = new JFileChooser(new File(start)); + } + lastJFCDirectory = jfc.getCurrentDirectory().getAbsolutePath(); + } + String ext = ".jmx";//$NON-NLS-1$ + if (filename != null) { + jfc.setDialogTitle(filename); + jfc.setSelectedFile(filename.lastIndexOf(System.getProperty("file.separator")) > 0 ? + new File(filename) : + new File(lastJFCDirectory, filename)); + int i = -1; + if ((i = filename.lastIndexOf(".")) > -1) {//$NON-NLS-1$ + ext = filename.substring(i); + } + } + clearFileFilters(); + if (extensions != null) { + jfc.addChoosableFileFilter(new JMeterFileFilter(extensions)); + } else { + jfc.addChoosableFileFilter(new JMeterFileFilter(new String[] { ext })); + } + + int retVal = jfc.showSaveDialog(GuiPackage.getInstance().getMainFrame()); + jfc.setDialogTitle(null); + lastJFCDirectory = jfc.getCurrentDirectory().getAbsolutePath(); + if (retVal == JFileChooser.APPROVE_OPTION) { + return jfc; + } + return null; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/FileListPanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/FileListPanel.java new file mode 100644 index 0000000..5fdf1a9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/FileListPanel.java @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterFileFilter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +public class FileListPanel extends JPanel implements ActionListener { + + private static final long serialVersionUID = 1L; + + private JTable files = null; + + private transient ObjectTableModel tableModel = null; + + private static final String ACTION_BROWSE = "browse"; // $NON-NLS-1$ + + private static final String LABEL_LIBRARY = "library"; // $NON-NLS-1$ + + private JButton browse = new JButton(JMeterUtils.getResString(ACTION_BROWSE)); + + private JButton clear = new JButton(JMeterUtils.getResString("clear")); // $NON-NLS-1$ + + private JButton delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + + private List listeners = new LinkedList(); + + private String title; + + private String filetype; + + /** + * Constructor for the FilePanel object. + */ + public FileListPanel() { + title = ""; // $NON-NLS-1$ + init(); + } + + public FileListPanel(String title) { + this.title = title; + init(); + } + + public FileListPanel(String title, String filetype) { + this.title = title; + this.filetype = filetype; + init(); + } + + /** + * Constructor for the FilePanel object. + */ + public FileListPanel(ChangeListener l, String title) { + this.title = title; + init(); + listeners.add(l); + } + + public void addChangeListener(ChangeListener l) { + listeners.add(l); + } + + private void init() { + this.setLayout(new BorderLayout(0, 5)); + setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 5)); + JLabel jtitle = new JLabel(title); + + HorizontalPanel buttons = new HorizontalPanel(); + buttons.add(jtitle); + buttons.add(browse); + buttons.add(delete); + buttons.add(clear); + add(buttons,BorderLayout.NORTH); + + this.initializeTableModel(); + files = new JTable(tableModel); + files.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + files.revalidate(); + + JScrollPane scrollpane = new JScrollPane(files); + scrollpane.setPreferredSize(new Dimension(100,80)); + add(scrollpane,BorderLayout.CENTER); + + browse.setActionCommand(ACTION_BROWSE); // $NON-NLS-1$ + browse.addActionListener(this); + clear.addActionListener(this); + delete.addActionListener(this); + //this.setPreferredSize(new Dimension(400,150)); + } + + /** + * If the gui needs to enable/disable the FilePanel, call the method. + * + * @param enable + */ + public void enableFile(boolean enable) { + browse.setEnabled(enable); + files.setEnabled(false); + } + + /** + * Add a single file to the table + * @param f + */ + public void addFilename(String f) { + tableModel.addRow(f); + } + + /** + * clear the files from the table + */ + public void clearFiles() { + tableModel.clearData(); + } + + public void setFiles(String[] files) { + this.clearFiles(); + for (int idx=0; idx < files.length; idx++) { + addFilename(files[idx]); + } + } + + public String[] getFiles() { + String[] _files = new String[tableModel.getRowCount()]; + for (int idx=0; idx < _files.length; idx++) { + _files[idx] = (String)tableModel.getValueAt(idx,0); + } + return _files; + } + + protected void deleteFile() { + // If a table cell is being edited, we must cancel the editing before + // deleting the row + + int rowSelected = files.getSelectedRow(); + if (rowSelected >= 0) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + } + } + + private void fireFileChanged() { + for (ChangeListener cl : listeners) { + cl.stateChanged(new ChangeEvent(this)); + } + } + + protected void initializeTableModel() { + tableModel = new ObjectTableModel(new String[] { JMeterUtils.getResString(LABEL_LIBRARY) }, + new Functor[0] , new Functor[0] , // i.e. bypass the Functors + new Class[] { String.class }); + } + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == clear) { + this.clearFiles(); + } else if (e.getActionCommand().equals(ACTION_BROWSE)) { + JFileChooser chooser = new JFileChooser(); + String start = System.getProperty("user.dir", ""); // $NON-NLS-1$ // $NON-NLS-2$ + chooser.setCurrentDirectory(new File(start)); + chooser.setFileFilter(new JMeterFileFilter(new String[] { filetype })); + chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + chooser.setMultiSelectionEnabled(true); + chooser.showOpenDialog(GuiPackage.getInstance().getMainFrame()); + File[] cfiles = chooser.getSelectedFiles(); + if (cfiles != null) { + for (int idx=0; idx < cfiles.length; idx++) { + this.addFilename(cfiles[idx].getPath()); + } + fireFileChanged(); + } + } else if (e.getSource() == delete) { + this.deleteFile(); + } else { + fireFileChanged(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/FilePanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/FilePanel.java new file mode 100644 index 0000000..34f1cd3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/FilePanel.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * author Michael Stover Created April 18, 2002 + */ +public class FilePanel extends HorizontalPanel implements ActionListener { + private static final long serialVersionUID = 240L; + + private final JTextField filename = new JTextField(10); + + private final JLabel label = new JLabel(JMeterUtils.getResString("file_visualizer_filename")); //$NON-NLS-1$ + + private final JButton browse = new JButton(JMeterUtils.getResString("browse")); //$NON-NLS-1$ + + private static final String ACTION_BROWSE = "browse"; //$NON-NLS-1$ + + private final List listeners = new LinkedList(); + + private final String title; + + private final String[] filetypes; + + /** + * Constructor for the FilePanel object. + */ + public FilePanel() { + this("", (String) null); + } + + public FilePanel(String title) { + this(title, (String) null); + } + + public FilePanel(String title, String filetype) { + this.title = title; + if (filetype == null){ + this.filetypes = null; + } else { + this.filetypes = new String[]{ filetype }; + } + init(); + } + + /** + * Constructor for the FilePanel object. + */ + public FilePanel(ChangeListener l, String title) { + this(title, (String) null); + listeners.add(l); + } + + public FilePanel(String resString, String[] exts) { + title = resString; + this.filetypes = new String[exts.length]; + System.arraycopy(exts, 0, this.filetypes, 0, exts.length); + init(); + } + + public void addChangeListener(ChangeListener l) { + listeners.add(l); + } + + private void init() { + setBorder(BorderFactory.createTitledBorder(title)); + add(label); + add(Box.createHorizontalStrut(5)); + add(filename); + add(Box.createHorizontalStrut(5)); + filename.addActionListener(this); + add(browse); + browse.setActionCommand(ACTION_BROWSE); + browse.addActionListener(this); + + } + + public void clearGui(){ + filename.setText(""); // $NON-NLS-1$ + } + + /** + * If the gui needs to enable/disable the FilePanel, call the method. + * + * @param enable + */ + public void enableFile(boolean enable) { + browse.setEnabled(enable); + filename.setEnabled(enable); + } + + /** + * Gets the filename attribute of the FilePanel object. + * + * @return the filename value + */ + public String getFilename() { + return filename.getText(); + } + + /** + * Sets the filename attribute of the FilePanel object. + * + * @param f + * the new filename value + */ + public void setFilename(String f) { + filename.setText(f); + } + + private void fireFileChanged() { + for (ChangeListener cl : listeners) { + cl.stateChanged(new ChangeEvent(this)); + } + } + + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals(ACTION_BROWSE)) { + JFileChooser chooser; + if(filetypes == null || filetypes.length == 0){ + chooser = FileDialoger.promptToOpenFile(); + } else { + chooser = FileDialoger.promptToOpenFile(filetypes); + } + if (chooser != null && chooser.getSelectedFile() != null) { + filename.setText(chooser.getSelectedFile().getPath()); + fireFileChanged(); + } + } else { + fireFileChanged(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/FocusRequester.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/FocusRequester.java new file mode 100644 index 0000000..35df973 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/FocusRequester.java @@ -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. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.Component; + +import javax.swing.SwingUtilities; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/* + * Note: This helper class appeared in JavaWorld in June 2001 + * (http://www.javaworld.com) and was written by Michael Daconta. + * + */ +public class FocusRequester implements Runnable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Component comp; + + public FocusRequester(Component comp) { + this.comp = comp; + try { + SwingUtilities.invokeLater(this); + } catch (Exception e) { + log.error("", e); // $NON-NLS-1$ + } + } + + public void run() { + comp.requestFocus(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java new file mode 100644 index 0000000..758761c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.Component; + +import javax.swing.JLabel; +import javax.swing.JTable; +import javax.swing.UIManager; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.JTableHeader; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Renders items in a JTable by converting from resource names. + */ +public class HeaderAsPropertyRenderer extends DefaultTableCellRenderer { + + private static final long serialVersionUID = 240L; + + public HeaderAsPropertyRenderer() { + super(); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, int row, int column) { + if (table != null) { + JTableHeader header = table.getTableHeader(); + if (header != null){ + setForeground(header.getForeground()); + setBackground(header.getBackground()); + setFont(header.getFont()); + } + setText(getText(value, row, column)); + setBorder(UIManager.getBorder("TableHeader.cellBorder")); + setHorizontalAlignment(JLabel.CENTER); + } + return this; + } + + /** + * Get the text for the value as the translation of the resource name. + * + * @param value + * @param column + * @param row + * @return the text + */ + protected String getText(Object value, int row, int column) { + if (value == null){ + return ""; + } + return JMeterUtils.getResString(value.toString()); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/HorizontalPanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/HorizontalPanel.java new file mode 100644 index 0000000..1973b5d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/HorizontalPanel.java @@ -0,0 +1,75 @@ +/* + * 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. + * + */ + +/* + * Created on Apr 25, 2003 + * + */ +package org.apache.jmeter.gui.util; + +import java.awt.Color; +import java.awt.BorderLayout; +import java.awt.Component; + +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JPanel; + +public class HorizontalPanel extends JPanel { + private static final long serialVersionUID = 240L; + + private final Box subPanel = Box.createHorizontalBox(); + + private final float verticalAlign; + + private final int hgap; + + public HorizontalPanel() { + this(5, CENTER_ALIGNMENT); + } + + public HorizontalPanel(Color bk) { + this(); + subPanel.setBackground(bk); + this.setBackground(bk); + } + + public HorizontalPanel(int hgap, float verticalAlign) { + super(new BorderLayout()); + add(subPanel, BorderLayout.CENTER); + this.hgap = hgap; + this.verticalAlign = verticalAlign; + } + + /** + * {@inheritDoc} + */ + @Override + public Component add(Component c) { + // This won't work right if we remove components. But we don't, so I'm + // not going to worry about it right now. + if (hgap > 0 && subPanel.getComponentCount() > 0) { + subPanel.add(Box.createHorizontalStrut(hgap)); + } + + if (c instanceof JComponent) { + ((JComponent) c).setAlignmentY(verticalAlign); + } + return subPanel.add(c); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/IconToolbarBean.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/IconToolbarBean.java new file mode 100644 index 0000000..bf82df0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/IconToolbarBean.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.gui.util; + +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +public final class IconToolbarBean { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ICON_FIELD_SEP = ","; //$NON-NLS-1$ + + private final String i18nKey; + + private final String actionName; + + private final String iconPath; + + private final String iconPathPressed; + + /** + * Constructor to transform a line value (from icon set file) to a icon bean for toolbar. + * @param strToSplit - the line value (i18n key, ActionNames ID, icon path, optional icon pressed path) + * @throws JMeterException if error in parsing. + */ + IconToolbarBean(final String strToSplit) throws NullPointerException, IllegalArgumentException { + if (strToSplit == null) { + throw new NullPointerException("Icon definition must not be null"); //$NON-NLS-1$ + } + final String tmp[] = strToSplit.split(ICON_FIELD_SEP); + if (tmp.length > 2) { + this.i18nKey = tmp[0]; + this.actionName = tmp[1]; + this.iconPath = tmp[2]; + this.iconPathPressed = (tmp.length > 3) ? tmp[3] : tmp[2]; + } else { + throw new IllegalArgumentException("Incorrect argument format - expected at least 2 fields separated by " + ICON_FIELD_SEP); + } + } + + /** + * Resolve action name ID declared in icon set file to ActionNames value + * @return the resolve actionName + */ + public String getActionNameResolve() { + final String aName; + try { + aName = (String) (ActionNames.class.getField(this.actionName).get(null)); + } catch (Exception e) { + log.warn("Toolbar icon Action names error: " + this.actionName + ", use unknown action."); //$NON-NLS-1$ + return this.actionName; // return unknown action names for display error msg + } + return aName; + } + + /** + * @return the i18nKey + */ + public String getI18nKey() { + return i18nKey; + } + + /** + * @return the actionName + */ + public String getActionName() { + return actionName; + } + + /** + * @return the iconPath + */ + public String getIconPath() { + return iconPath; + } + + /** + * @return the iconPathPressed + */ + public String getIconPathPressed() { + return iconPathPressed; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/JDateField.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/JDateField.java new file mode 100644 index 0000000..58d773c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/JDateField.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +import javax.swing.JTextField; + +/** + * This is Date mask control. Using this control we can pop up our date in the + * text field. And this control is Devloped basically for JDK1.3 and lower + * version support. This control is similer to JSpinner control this is + * available in JDK1.4 and above only. + *

+ * This will set the date "yyyy/MM/dd HH:mm:ss" in this format only. + *

+ * + */ +public class JDateField extends JTextField { + + private static final long serialVersionUID = 240L; + + // Datefields are not thread-safe + private final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); // $NON-NLS-1$ + + /* + * The following array must agree with dateFormat + * + * It is used to translate the positions in the buffer to the values used by + * the Calendar class for the field id. + * + * Current format: MM/DD/YYYY HH:MM:SS 01234567890123456789 ^buffer + * positions + */ + private static final int fieldPositions[] = { + Calendar.YEAR, // Y + Calendar.YEAR, // Y + Calendar.YEAR, // Y + Calendar.YEAR, // Y + Calendar.YEAR, // sp + Calendar.MONTH, // M + Calendar.MONTH, // M + Calendar.MONTH, // / + Calendar.DAY_OF_MONTH, // D + Calendar.DAY_OF_MONTH, // D + Calendar.DAY_OF_MONTH, // / + Calendar.HOUR_OF_DAY, // H + Calendar.HOUR_OF_DAY, // H + Calendar.HOUR_OF_DAY, // : + Calendar.MINUTE, // M + Calendar.MINUTE, // M + Calendar.MINUTE, // : + Calendar.SECOND, // S + Calendar.SECOND, // S + Calendar.SECOND // end + }; + + /** + * Create a DateField with the specified date. + */ + public JDateField(Date date) { + super(20); + this.addKeyListener(new KeyFocus()); + this.addFocusListener(new FocusClass()); + String myString = dateFormat.format(date); + setText(myString); + } + + // Dummy constructor to allo JUnit tests to work + public JDateField() { + this(new Date()); + } + + /** + * Set the date to the Date mask control. + */ + public void setDate(Date date) { + setText(dateFormat.format(date)); + } + + /** + * Get the date from the Date mask control. + */ + public Date getDate() { + try { + return dateFormat.parse(getText()); + } catch (ParseException e) { + return new Date(); + } catch (Exception e) { + // DateFormat.parse has some bugs (up to JDK 1.4.2) by which it + // throws unchecked exceptions. E.g. see: + // http://developer.java.sun.com/developer/bugParade/bugs/4699765.html + // + // To avoid problems with such situations, we'll catch all + // exceptions here and act just as for ParseException above: + return new Date(); + } + } + + /* + * Convert position in buffer to Calendar type Assumes that pos >=0 (which + * is true for getCaretPosition()) + */ + private static int posToField(int pos) { + if (pos >= fieldPositions.length) { // if beyond the end + pos = fieldPositions.length - 1; // then set to the end + } + return fieldPositions[pos]; + } + + /** + * Converts a date/time to a calendar using the defined format + */ + private Calendar parseDate(String datetime) { + Calendar c = Calendar.getInstance(); + try { + Date dat = dateFormat.parse(datetime); + c.setTime(dat); + } catch (ParseException e) { + // Do nothing; the current time will be returned + } + return c; + } + + /* + * Update the current field. The addend is only expected to be +1/-1, but + * other values will work. N.B. the roll() method only supports changes by a + * single unit - up or down + */ + private void update(int addend, boolean shifted) { + Calendar c = parseDate(getText()); + int pos = getCaretPosition(); + int field = posToField(pos); + if (shifted) { + c.roll(field, true); + } else { + c.add(field, addend); + } + String newDate = dateFormat.format(c.getTime()); + setText(newDate); + if (pos > newDate.length()) { + pos = newDate.length(); + } + setCaretPosition(pos);// Restore position + + } + + class KeyFocus extends KeyAdapter { + KeyFocus() { + } + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_UP) { + update(1, e.isShiftDown()); + } else if (e.getKeyCode() == KeyEvent.VK_DOWN) { + update(-1, e.isShiftDown()); + } + } + } + + class FocusClass implements FocusListener { + FocusClass() { + } + + public void focusGained(FocusEvent e) { + } + + public void focusLost(FocusEvent e) { + try { + dateFormat.parse(getText()); + } catch (ParseException e1) { + requestFocus(); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/JLabeledRadioI18N.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/JLabeledRadioI18N.java new file mode 100644 index 0000000..0fdb6f3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/JLabeledRadioI18N.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.AbstractButton; +import javax.swing.ButtonGroup; +import javax.swing.ButtonModel; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledField; + +/** + * JLabeledRadioI18N creates a set of Radio buttons with a label. + * This is a version of the original JLabelledRadio class (now removed), but modified + * to accept resource names rather than language strings. + * + */ +public class JLabeledRadioI18N extends JPanel implements JLabeledField, ActionListener { + + private static final long serialVersionUID = 240L; + + private final JLabel mLabel = new JLabel(); + + private final ButtonGroup bGroup = new ButtonGroup(); + + private final ArrayList mChangeListeners = new ArrayList(3); + + /** + * + * @param label_resouce text resource name for group label + * @param item_resources list of resource names for individual buttons + * @param selectedItem button to be selected (if not null) + */ + public JLabeledRadioI18N(String label_resouce, String[] item_resources, String selectedItem) { + setLabel(label_resouce); + init(item_resources, selectedItem); + } + + /** + * @deprecated - only for use in testing + */ + @Deprecated + public JLabeledRadioI18N() { + super(); + } + + /** + * Method is responsible for creating the JRadioButtons and adding them to + * the ButtonGroup. + * + * The resource name is used as the action command for the button model, + * and the reource value is used to set the button label. + * + * @param resouces list of resource names + * @param selected initially selected resource (if not null) + * + */ + private void init(String[] resouces, String selected) { + this.add(mLabel); + for (int idx = 0; idx < resouces.length; idx++) { + JRadioButton btn = new JRadioButton(JMeterUtils.getResString(resouces[idx])); + btn.setActionCommand(resouces[idx]); + btn.addActionListener(this); + // add the button to the button group + this.bGroup.add(btn); + // add the button + this.add(btn); + if (selected != null && selected.equals(resouces[idx])) { + btn.setSelected(true); + } + } + } + + /** + * The implementation will get the resource name from the selected radio button + * in the JButtonGroup. + */ + public String getText() { + return this.bGroup.getSelection().getActionCommand(); + } + + /** + * The implementation will iterate through the radio buttons and find the + * match. It then sets it to selected and sets all other radio buttons as + * not selected. + * @param resourcename name of resource whose button is to be selected + */ + public void setText(String resourcename) { + Enumeration en = this.bGroup.getElements(); + while (en.hasMoreElements()) { + ButtonModel model = en.nextElement().getModel(); + if (model.getActionCommand().equals(resourcename)) { + this.bGroup.setSelected(model, true); + } else { + this.bGroup.setSelected(model, false); + } + } + } + + /** + * Set the group label from the resource name. + * + * @param label_resource + */ + public final void setLabel(String label_resource) { + this.mLabel.setText(JMeterUtils.getResString(label_resource)); + } + + /** {@inheritDoc} */ + public void addChangeListener(ChangeListener pChangeListener) { + this.mChangeListeners.add(pChangeListener); + } + + /** + * Notify all registered change listeners that the text in the text field + * has changed. + */ + private void notifyChangeListeners() { + ChangeEvent ce = new ChangeEvent(this); + for (int index = 0; index < mChangeListeners.size(); index++) { + mChangeListeners.get(index).stateChanged(ce); + } + } + + /** + * Method will return all the label and JRadioButtons. ButtonGroup is + * excluded from the list. + */ + public List getComponentList() { + List comps = new LinkedList(); + comps.add(mLabel); + Enumeration en = this.bGroup.getElements(); + while (en.hasMoreElements()) { + comps.add(en.nextElement()); + } + return comps; + } + + /** + * When a radio button is clicked, an ActionEvent is triggered. + */ + public void actionPerformed(ActionEvent e) { + this.notifyChangeListeners(); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterColor.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterColor.java new file mode 100644 index 0000000..2923b3f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterColor.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.jmeter.gui.util; + +import java.awt.Color; + +public class JMeterColor extends Color { + private static final long serialVersionUID = 240L; + + public final static Color dark_green = new JMeterColor(0F, .5F, 0F); + + public final static Color LAVENDER = new JMeterColor(206F / 255F, 207F / 255F, 1F); + + public final static Color purple = new JMeterColor(150 / 255F, 0, 150 / 255F); + + public JMeterColor(float r, float g, float b) { + super(r, g, b); + } + + public JMeterColor() { + super(0, 0, 0); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterMenuBar.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterMenuBar.java new file mode 100644 index 0000000..e7f9bc1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterMenuBar.java @@ -0,0 +1,748 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.Component; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; +import javax.swing.MenuElement; +import javax.swing.UIManager; +import javax.swing.UIManager.LookAndFeelInfo; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.gui.action.LoadRecentProject; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jmeter.util.SSLManager; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class JMeterMenuBar extends JMenuBar implements LocaleChangeListener { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private JMenu fileMenu; + + private JMenuItem file_save_as; + + private JMenuItem file_selection_as; + + private JMenuItem file_revert; + + private JMenuItem file_load; + + private List file_load_recent_files; + + private JMenuItem file_merge; + + private JMenuItem file_exit; + + private JMenuItem file_close; + + private JMenu editMenu; + + private JMenu edit_add; + + private JMenu runMenu; + + private JMenuItem run_start; + + private JMenuItem run_start_no_timers; + + private JMenu remote_start; + + private JMenuItem remote_start_all; + + private Collection remote_engine_start; + + private JMenuItem run_stop; + + private JMenuItem run_shut; + + private JMenu remote_stop; + + private JMenu remote_shut; + + private JMenuItem remote_stop_all; + + private JMenuItem remote_shut_all; + + private Collection remote_engine_stop; + + private Collection remote_engine_shut; + + private JMenuItem run_clear; + + private JMenuItem run_clearAll; + + // JMenu reportMenu; + // JMenuItem analyze; + private JMenu optionsMenu; + + private JMenu lafMenu; + + private JMenuItem sslManager; + + private JMenu helpMenu; + + private JMenuItem help_about; + + private String[] remoteHosts; + + private JMenu remote_exit; + + private JMenuItem remote_exit_all; + + private Collection remote_engine_exit; + + private JMenu searchMenu; + + public static final String SYSTEM_LAF = "System"; // $NON-NLS-1$ + + public static final String CROSS_PLATFORM_LAF = "CrossPlatform"; // $NON-NLS-1$ + + public JMeterMenuBar() { + // List for recent files menu items + file_load_recent_files = new LinkedList(); + // Lists for remote engines menu items + remote_engine_start = new LinkedList(); + remote_engine_stop = new LinkedList(); + remote_engine_shut = new LinkedList(); + remote_engine_exit = new LinkedList(); + remoteHosts = JOrphanUtils.split(JMeterUtils.getPropDefault("remote_hosts", ""), ","); //$NON-NLS-1$ + if (remoteHosts.length == 1 && remoteHosts[0].equals("")) { + remoteHosts = new String[0]; + } + this.getRemoteItems(); + createMenuBar(); + JMeterUtils.addLocaleChangeListener(this); + } + + public void setFileSaveEnabled(boolean enabled) { + if(file_save_as != null) { + file_save_as.setEnabled(enabled); + } + } + + public void setFileLoadEnabled(boolean enabled) { + if (file_load != null) { + file_load.setEnabled(enabled); + } + if (file_merge != null) { + file_merge.setEnabled(enabled); + } + } + + public void setFileRevertEnabled(boolean enabled) { + if(file_revert != null) { + file_revert.setEnabled(enabled); + } + } + + public void setProjectFileLoaded(String file) { + if(file_load_recent_files != null && file != null) { + LoadRecentProject.updateRecentFileMenuItems(file_load_recent_files, file); + } + } + + public void setEditEnabled(boolean enabled) { + if (editMenu != null) { + editMenu.setEnabled(enabled); + } + } + + // Does not appear to be used; called by MainFrame#setEditAddMenu() but that is not called + public void setEditAddMenu(JMenu menu) { + // If the Add menu already exists, remove it. + if (edit_add != null) { + editMenu.remove(edit_add); + } + // Insert the Add menu as the first menu item in the Edit menu. + edit_add = menu; + editMenu.insert(edit_add, 0); + } + + // Called by MainFrame#setEditMenu() which is called by EditCommand#doAction and GuiPackage#localeChanged + public void setEditMenu(JPopupMenu menu) { + if (menu != null) { + editMenu.removeAll(); + Component[] comps = menu.getComponents(); + for (int i = 0; i < comps.length; i++) { + editMenu.add(comps[i]); + } + editMenu.setEnabled(true); + } else { + editMenu.setEnabled(false); + } + } + + public void setEditAddEnabled(boolean enabled) { + // There was a NPE being thrown without the null check here.. JKB + if (edit_add != null) { + edit_add.setEnabled(enabled); + } + // If we are enabling the Edit-->Add menu item, then we also need to + // enable the Edit menu. The Edit menu may already be enabled, but + // there's no harm it trying to enable it again. + setEditEnabled(enabled); + } + + /** + * Creates the MenuBar for this application. I believe in my heart that this + * should be defined in a file somewhere, but that is for later. + */ + public void createMenuBar() { + makeFileMenu(); + makeEditMenu(); + makeRunMenu(); + makeOptionsMenu(); + makeHelpMenu(); + makeSearchMenu(); + this.add(fileMenu); + this.add(editMenu); + this.add(searchMenu); + this.add(runMenu); + this.add(optionsMenu); + this.add(helpMenu); + } + + private void makeHelpMenu() { + // HELP MENU + helpMenu = makeMenuRes("help",'H'); //$NON-NLS-1$ + + JMenuItem contextHelp = makeMenuItemRes("help", 'H', ActionNames.HELP, KeyStrokes.HELP); //$NON-NLS-1$ + + JMenuItem whatClass = makeMenuItemRes("help_node", 'W', ActionNames.WHAT_CLASS, KeyStrokes.WHAT_CLASS);//$NON-NLS-1$ + + JMenuItem setDebug = makeMenuItemRes("debug_on", ActionNames.DEBUG_ON, KeyStrokes.DEBUG_ON);//$NON-NLS-1$ + + JMenuItem resetDebug = makeMenuItemRes("debug_off", ActionNames.DEBUG_OFF, KeyStrokes.DEBUG_OFF);//$NON-NLS-1$ + + help_about = makeMenuItemRes("about", 'A', ActionNames.ABOUT); //$NON-NLS-1$ + + helpMenu.add(contextHelp); + helpMenu.addSeparator(); + helpMenu.add(whatClass); + helpMenu.add(setDebug); + helpMenu.add(resetDebug); + helpMenu.addSeparator(); + helpMenu.add(help_about); + } + + private void makeOptionsMenu() { + // OPTIONS MENU + optionsMenu = makeMenuRes("option",'O'); //$NON-NLS-1$ + JMenuItem functionHelper = makeMenuItemRes("function_dialog_menu_item", 'F', ActionNames.FUNCTIONS, KeyStrokes.FUNCTIONS); //$NON-NLS-1$ + + lafMenu = makeMenuRes("appearance",'L'); //$NON-NLS-1$ + UIManager.LookAndFeelInfo lafs[] = getAllLAFs(); + for (int i = 0; i < lafs.length; ++i) { + JMenuItem laf = new JMenuItem(lafs[i].getName()); + laf.addActionListener(ActionRouter.getInstance()); + laf.setActionCommand(ActionNames.LAF_PREFIX + lafs[i].getClassName()); + laf.setToolTipText(lafs[i].getClassName()); // show the classname to the user + lafMenu.add(laf); + } + optionsMenu.add(functionHelper); + optionsMenu.add(lafMenu); + + JCheckBoxMenuItem menuToolBar = makeCheckBoxMenuItemRes("menu_toolbar", ActionNames.TOOLBAR); //$NON-NLS-1$ + JCheckBoxMenuItem menuLoggerPanel = makeCheckBoxMenuItemRes("menu_logger_panel", ActionNames.LOGGER_PANEL_ENABLE_DISABLE); //$NON-NLS-1$ + GuiPackage guiInstance = GuiPackage.getInstance(); + if (guiInstance != null) { //avoid error in ant task tests (good way?) + guiInstance.setMenuItemToolbar(menuToolBar); + guiInstance.setMenuItemLoggerPanel(menuLoggerPanel); + } + optionsMenu.add(menuToolBar); + optionsMenu.add(menuLoggerPanel); + + if (SSLManager.isSSLSupported()) { + sslManager = makeMenuItemRes("sslmanager", 'S', ActionNames.SSL_MANAGER, KeyStrokes.SSL_MANAGER); //$NON-NLS-1$ + optionsMenu.add(sslManager); + } + optionsMenu.add(makeLanguageMenu()); + + JMenuItem collapse = makeMenuItemRes("menu_collapse_all", ActionNames.COLLAPSE_ALL, KeyStrokes.COLLAPSE_ALL); //$NON-NLS-1$ + optionsMenu.add(collapse); + + JMenuItem expand = makeMenuItemRes("menu_expand_all", ActionNames.EXPAND_ALL, KeyStrokes.EXPAND_ALL); //$NON-NLS-1$ + optionsMenu.add(expand); + } + + private static class LangMenuHelper{ + final ActionRouter actionRouter = ActionRouter.getInstance(); + final JMenu languageMenu; + + LangMenuHelper(JMenu _languageMenu){ + languageMenu = _languageMenu; + } + + /** + * Create a language entry from the locale name. + * + * @param locale - must also be a valid resource name + */ + void addLang(String locale){ + String localeString = JMeterUtils.getLocaleString(locale); + JMenuItem language = new JMenuItem(localeString); + language.addActionListener(actionRouter); + language.setActionCommand(ActionNames.CHANGE_LANGUAGE); + language.setName(locale); // This is used by the ChangeLanguage class to define the Locale + languageMenu.add(language); + } + + } + + /** + * Generate the list of supported languages. + * + * @return list of languages + */ + // Also used by org.apache.jmeter.resources.PackageTest + public static String[] getLanguages(){ + List lang = new ArrayList(20); + lang.add(Locale.ENGLISH.toString()); // en + lang.add(Locale.FRENCH.toString()); // fr + lang.add(Locale.GERMAN.toString()); // de + lang.add("no"); // $NON-NLS-1$ + lang.add("pl"); // $NON-NLS-1$ + lang.add("pt_BR"); // $NON-NLS-1$ + lang.add("es"); // $NON-NLS-1$ + lang.add("tr"); // $NON-NLS-1$ + lang.add(Locale.JAPANESE.toString()); // ja + lang.add(Locale.SIMPLIFIED_CHINESE.toString()); // zh_CN + lang.add(Locale.TRADITIONAL_CHINESE.toString()); // zh_TW + final String addedLocales = JMeterUtils.getProperty("locales.add"); + if (addedLocales != null){ + String [] addLanguages =addedLocales.split(","); // $NON-NLS-1$ + for(String newLang : addLanguages){ + log.info("Adding locale "+newLang); + lang.add(newLang); + } + } + return lang.toArray(new String[lang.size()]); + } + + static JMenu makeLanguageMenu() { + final JMenu languageMenu = makeMenuRes("choose_language",'C'); //$NON-NLS-1$ + + LangMenuHelper langMenu = new LangMenuHelper(languageMenu); + + /* + * Note: the item name is used by ChangeLanguage to create a Locale for + * that language, so need to ensure that the language strings are valid + * If they exist, use the Locale language constants. + * Also, need to ensure that the names are valid resource entries too. + */ + + for(String lang : getLanguages()){ + langMenu.addLang(lang); + } + return languageMenu; + } + + private void makeRunMenu() { + // RUN MENU + runMenu = makeMenuRes("run",'R'); //$NON-NLS-1$ + + run_start = makeMenuItemRes("start", 'S', ActionNames.ACTION_START, KeyStrokes.ACTION_START); //$NON-NLS-1$ + + run_start_no_timers = makeMenuItemRes("start_no_timers", ActionNames.ACTION_START_NO_TIMERS); //$NON-NLS-1$ + + run_stop = makeMenuItemRes("stop", 'T', ActionNames.ACTION_STOP, KeyStrokes.ACTION_STOP); //$NON-NLS-1$ + run_stop.setEnabled(false); + + run_shut = makeMenuItemRes("shutdown", 'Y', ActionNames.ACTION_SHUTDOWN, KeyStrokes.ACTION_SHUTDOWN); //$NON-NLS-1$ + run_shut.setEnabled(false); + + run_clear = makeMenuItemRes("clear", 'C', ActionNames.CLEAR, KeyStrokes.CLEAR); //$NON-NLS-1$ + + run_clearAll = makeMenuItemRes("clear_all", 'a', ActionNames.CLEAR_ALL, KeyStrokes.CLEAR_ALL); //$NON-NLS-1$ + + runMenu.add(run_start); + runMenu.add(run_start_no_timers); + if (remote_start != null) { + runMenu.add(remote_start); + } + remote_start_all = makeMenuItemRes("remote_start_all", ActionNames.REMOTE_START_ALL, KeyStrokes.REMOTE_START_ALL); //$NON-NLS-1$ + + runMenu.add(remote_start_all); + runMenu.add(run_stop); + runMenu.add(run_shut); + if (remote_stop != null) { + runMenu.add(remote_stop); + } + remote_stop_all = makeMenuItemRes("remote_stop_all", 'X', ActionNames.REMOTE_STOP_ALL, KeyStrokes.REMOTE_STOP_ALL); //$NON-NLS-1$ + runMenu.add(remote_stop_all); + + if (remote_shut != null) { + runMenu.add(remote_shut); + } + remote_shut_all = makeMenuItemRes("remote_shut_all", 'X', ActionNames.REMOTE_SHUT_ALL, KeyStrokes.REMOTE_SHUT_ALL); //$NON-NLS-1$ + runMenu.add(remote_shut_all); + + if (remote_exit != null) { + runMenu.add(remote_exit); + } + remote_exit_all = makeMenuItemRes("remote_exit_all", ActionNames.REMOTE_EXIT_ALL); //$NON-NLS-1$ + runMenu.add(remote_exit_all); + + runMenu.addSeparator(); + runMenu.add(run_clear); + runMenu.add(run_clearAll); + } + + private void makeEditMenu() { + // EDIT MENU + editMenu = makeMenuRes("edit",'E'); //$NON-NLS-1$ + + // From the Java Look and Feel Guidelines: If all items in a menu + // are disabled, then disable the menu. Makes sense. + editMenu.setEnabled(false); + } + + private void makeFileMenu() { + // FILE MENU + fileMenu = makeMenuRes("file",'F'); //$NON-NLS-1$ + + JMenuItem file_save = makeMenuItemRes("save", 'S', ActionNames.SAVE, KeyStrokes.SAVE); //$NON-NLS-1$ + file_save.setEnabled(true); + + file_save_as = makeMenuItemRes("save_all_as", 'A', ActionNames.SAVE_ALL_AS, KeyStrokes.SAVE_ALL_AS); //$NON-NLS-1$ + file_save_as.setEnabled(true); + + file_selection_as = makeMenuItemRes("save_as", ActionNames.SAVE_AS); //$NON-NLS-1$ + file_selection_as.setEnabled(true); + + file_revert = makeMenuItemRes("revert_project", 'R', ActionNames.REVERT_PROJECT); //$NON-NLS-1$ + file_revert.setEnabled(false); + + file_load = makeMenuItemRes("menu_open", 'O', ActionNames.OPEN, KeyStrokes.OPEN); //$NON-NLS-1$ + // Set default SAVE menu item to disabled since the default node that + // is selected is ROOT, which does not allow items to be inserted. + file_load.setEnabled(false); + + file_close = makeMenuItemRes("menu_close", 'C', ActionNames.CLOSE, KeyStrokes.CLOSE); //$NON-NLS-1$ + + file_exit = makeMenuItemRes("exit", 'X', ActionNames.EXIT, KeyStrokes.EXIT); //$NON-NLS-1$ + + file_merge = makeMenuItemRes("menu_merge", 'M', ActionNames.MERGE); //$NON-NLS-1$ + // file_merge.setAccelerator( + // KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK)); + // Set default SAVE menu item to disabled since the default node that + // is selected is ROOT, which does not allow items to be inserted. + file_merge.setEnabled(false); + + fileMenu.add(file_close); + fileMenu.add(file_load); + fileMenu.add(file_merge); + fileMenu.addSeparator(); + fileMenu.add(file_save); + fileMenu.add(file_save_as); + fileMenu.add(file_selection_as); + fileMenu.add(file_revert); + fileMenu.addSeparator(); + // Add the recent files, which will also add a separator that is + // visible when needed + file_load_recent_files = LoadRecentProject.getRecentFileMenuItems(); + for(JComponent jc : file_load_recent_files){ + fileMenu.add(jc); + } + fileMenu.add(file_exit); + } + + private void makeSearchMenu() { + // Search MENU + searchMenu = makeMenuRes("menu_search"); //$NON-NLS-1$ + + JMenuItem search = makeMenuItemRes("menu_search", ActionNames.SEARCH_TREE); //$NON-NLS-1$ + searchMenu.add(search); + searchMenu.setEnabled(true); + + JMenuItem searchReset = makeMenuItemRes("menu_search_reset", ActionNames.SEARCH_RESET); //$NON-NLS-1$ + searchMenu.add(searchReset); + searchMenu.setEnabled(true); + } + + public void setRunning(boolean running, String host) { + log.info("setRunning(" + running + "," + host + ")"); + + Iterator iter = remote_engine_start.iterator(); + Iterator iter2 = remote_engine_stop.iterator(); + Iterator iter3 = remote_engine_exit.iterator(); + Iterator iter4 = remote_engine_shut.iterator(); + while (iter.hasNext() && iter2.hasNext() && iter3.hasNext() &&iter4.hasNext()) { + JMenuItem start = iter.next(); + JMenuItem stop = iter2.next(); + JMenuItem exit = iter3.next(); + JMenuItem shut = iter4.next(); + if (start.getText().equals(host)) { + log.debug("Found start host: " + start.getText()); + start.setEnabled(!running); + } + if (stop.getText().equals(host)) { + log.debug("Found stop host: " + stop.getText()); + stop.setEnabled(running); + } + if (exit.getText().equals(host)) { + log.debug("Found exit host: " + exit.getText()); + exit.setEnabled(true); + } + if (shut.getText().equals(host)) { + log.debug("Found exit host: " + exit.getText()); + shut.setEnabled(running); + } + } + } + + /** {@inheritDoc} */ + @Override + public void setEnabled(boolean enable) { + run_start.setEnabled(!enable); + run_start_no_timers.setEnabled(!enable); + run_stop.setEnabled(enable); + run_shut.setEnabled(enable); + } + + private void getRemoteItems() { + if (remoteHosts.length > 0) { + remote_start = makeMenuRes("remote_start"); //$NON-NLS-1$ + remote_stop = makeMenuRes("remote_stop"); //$NON-NLS-1$ + remote_shut = makeMenuRes("remote_shut"); //$NON-NLS-1$ + remote_exit = makeMenuRes("remote_exit"); //$NON-NLS-1$ + + for (int i = 0; i < remoteHosts.length; i++) { + remoteHosts[i] = remoteHosts[i].trim(); + + JMenuItem item = makeMenuItemNoRes(remoteHosts[i], ActionNames.REMOTE_START); + remote_engine_start.add(item); + remote_start.add(item); + + item = makeMenuItemNoRes(remoteHosts[i], ActionNames.REMOTE_STOP); + item.setEnabled(false); + remote_engine_stop.add(item); + remote_stop.add(item); + + item = makeMenuItemNoRes(remoteHosts[i], ActionNames.REMOTE_SHUT); + item.setEnabled(false); + remote_engine_shut.add(item); + remote_shut.add(item); + + item = makeMenuItemNoRes(remoteHosts[i],ActionNames.REMOTE_EXIT); + item.setEnabled(false); + remote_engine_exit.add(item); + remote_exit.add(item); + } + } + } + + /** {@inheritDoc} */ + public void localeChanged(LocaleChangeEvent event) { + updateMenuElement(fileMenu); + updateMenuElement(editMenu); + updateMenuElement(searchMenu); + updateMenuElement(runMenu); + updateMenuElement(optionsMenu); + updateMenuElement(helpMenu); + } + + /** + * Get a list of all installed LAFs plus CrossPlatform and System. + */ + // This is also used by LookAndFeelCommand + public static LookAndFeelInfo[] getAllLAFs() { + UIManager.LookAndFeelInfo lafs[] = UIManager.getInstalledLookAndFeels(); + int i = lafs.length; + UIManager.LookAndFeelInfo lafsAll[] = new UIManager.LookAndFeelInfo[i+2]; + System.arraycopy(lafs, 0, lafsAll, 0, i); + lafsAll[i++]=new UIManager.LookAndFeelInfo(CROSS_PLATFORM_LAF,UIManager.getCrossPlatformLookAndFeelClassName()); + lafsAll[i++]=new UIManager.LookAndFeelInfo(SYSTEM_LAF,UIManager.getSystemLookAndFeelClassName()); + return lafsAll; + } + /** + *

Refreshes all texts in the menu and all submenus to a new locale.

+ * + *

Assumes that the item name is set to the resource key, so the resource can be retrieved. + * Certain action types do not follow this rule, @see JMeterMenuBar#isNotResource(String)

+ * + * The Language Change event assumes that the name is the same as the locale name, + * so this additionally means that all supported locales must be defined as resources. + * + */ + private void updateMenuElement(MenuElement menu) { + Component component = menu.getComponent(); + final String compName = component.getName(); + if (compName != null) { + if (component instanceof JMenu) { + final JMenu jMenu = (JMenu) component; + if (isResource(jMenu.getActionCommand())){ + jMenu.setText(JMeterUtils.getResString(compName)); + } + } else { + final JMenuItem jMenuItem = (JMenuItem) component; + if (isResource(jMenuItem.getActionCommand())){ + jMenuItem.setText(JMeterUtils.getResString(compName)); + } else if (ActionNames.CHANGE_LANGUAGE.equals(jMenuItem.getActionCommand())){ + jMenuItem.setText(JMeterUtils.getLocaleString(compName)); + } + } + } + + MenuElement[] subelements = menu.getSubElements(); + + for (int i = 0; i < subelements.length; i++) { + updateMenuElement(subelements[i]); + } + } + + /** + * Return true if component name is a resource.
+ * i.e it is not a hostname:
+ * + * ActionNames.REMOTE_START
+ * ActionNames.REMOTE_STOP
+ * ActionNames.REMOTE_EXIT
+ * + * nor a filename:
+ * ActionNames.OPEN_RECENT + * + * nor a look and feel prefix:
+ * ActionNames.LAF_PREFIX + */ + private static boolean isResource(String actionCommand) { + if (ActionNames.CHANGE_LANGUAGE.equals(actionCommand)){// + return false; + } + if (ActionNames.ADD.equals(actionCommand)){// + return false; + } + if (ActionNames.REMOTE_START.equals(actionCommand)){// + return false; + } + if (ActionNames.REMOTE_STOP.equals(actionCommand)){// + return false; + } + if (ActionNames.REMOTE_SHUT.equals(actionCommand)){// + return false; + } + if (ActionNames.REMOTE_EXIT.equals(actionCommand)){// + return false; + } + if (ActionNames.OPEN_RECENT.equals(actionCommand)){// + return false; + } + if (actionCommand != null && actionCommand.startsWith(ActionNames.LAF_PREFIX)){ + return false; + } + return true; + } + + /** + * Make a menu from a resource string. + * @param resource used to name menu and set text. + * @return the menu + */ + private static JMenu makeMenuRes(String resource) { + JMenu menu = new JMenu(JMeterUtils.getResString(resource)); + menu.setName(resource); + return menu; + } + + /** + * Make a menu from a resource string and set its mnemonic. + * + * @param resource + * @param mnemonic + * @return the menu + */ + private static JMenu makeMenuRes(String resource, int mnemonic){ + JMenu menu = makeMenuRes(resource); + menu.setMnemonic(mnemonic); + return menu; + } + + /** + * Make a menuItem using a fixed label which is also used as the item name. + * This is used for items such as recent files and hostnames which are not resources + * @param label (this is not used as a resource key) + * @param actionCommand + * @return the menu item + */ + private static JMenuItem makeMenuItemNoRes(String label, String actionCommand) { + JMenuItem menuItem = new JMenuItem(label); + menuItem.setName(label); + menuItem.setActionCommand(actionCommand); + menuItem.addActionListener(ActionRouter.getInstance()); + return menuItem; + } + + private static JMenuItem makeMenuItemRes(String resource, String actionCommand) { + return makeMenuItemRes(resource, KeyEvent.VK_UNDEFINED, actionCommand, null); + } + + private static JMenuItem makeMenuItemRes(String resource, String actionCommand, KeyStroke keyStroke) { + return makeMenuItemRes(resource, KeyEvent.VK_UNDEFINED, actionCommand, keyStroke); + } + + private static JMenuItem makeMenuItemRes(String resource, int mnemonic, String actionCommand) { + return makeMenuItemRes(resource, mnemonic, actionCommand, null); + } + + private static JMenuItem makeMenuItemRes(String resource, int mnemonic, String actionCommand, KeyStroke keyStroke){ + JMenuItem menuItem = new JMenuItem(JMeterUtils.getResString(resource), mnemonic); + menuItem.setName(resource); + menuItem.setActionCommand(actionCommand); + menuItem.setAccelerator(keyStroke); + menuItem.addActionListener(ActionRouter.getInstance()); + return menuItem; + } + + private static JCheckBoxMenuItem makeCheckBoxMenuItemRes(String resource, String actionCommand) { + return makeCheckBoxMenuItemRes(resource, actionCommand, null); + } + + private static JCheckBoxMenuItem makeCheckBoxMenuItemRes(String resource, + String actionCommand, KeyStroke keyStroke){ + JCheckBoxMenuItem cbkMenuItem = new JCheckBoxMenuItem(JMeterUtils.getResString(resource)); + cbkMenuItem.setName(resource); + cbkMenuItem.setActionCommand(actionCommand); + cbkMenuItem.setAccelerator(keyStroke); + cbkMenuItem.addActionListener(ActionRouter.getInstance()); + return cbkMenuItem; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterToolBar.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterToolBar.java new file mode 100644 index 0000000..e7e102b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/JMeterToolBar.java @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.gui.util; + +import java.awt.Component; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JOptionPane; +import javax.swing.JToolBar; + +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The JMeter main toolbar class + * + */ +public class JMeterToolBar extends JToolBar implements LocaleChangeListener { + + /** + * + */ + private static final long serialVersionUID = -4591210341986068907L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String TOOLBAR_ENTRY_SEP = ","; //$NON-NLS-1$ + + private static final String TOOLBAR_PROP_NAME = "toolbar"; //$NON-NLS-1$ + + // protected fields: JMeterToolBar class can be use to create another toolbar (plugin, etc.) + protected static final String DEFAULT_TOOLBAR_PROPERTY_FILE = "org/apache/jmeter/images/toolbar/icons-toolbar.properties"; //$NON-NLS-1$ + + protected static final String USER_DEFINED_TOOLBAR_PROPERTY_FILE = "jmeter.toolbar.icons"; //$NON-NLS-1$ + + private static final String TOOLBAR_LIST = "jmeter.toolbar"; + + /** + * Create the default JMeter toolbar + * @return the JMeter toolbar + */ + public static JMeterToolBar createToolbar(boolean visible) { + JMeterToolBar toolBar = new JMeterToolBar(); + toolBar.setFloatable(false); + toolBar.setVisible(visible); + + setupToolbarContent(toolBar); + JMeterUtils.addLocaleChangeListener(toolBar); + // implicit return empty toolbar if icons == null + return toolBar; + } + + /** + * Setup toolbar content + * @param toolBar {@link JMeterToolBar} + */ + private static void setupToolbarContent(JMeterToolBar toolBar) { + List icons = getIconMappings(); + if (icons != null) { + for (IconToolbarBean iconToolbarBean : icons) { + if (iconToolbarBean == null) { + toolBar.addSeparator(); + } else { + toolBar.add(makeButtonItemRes(iconToolbarBean)); + } + } + toolBar.setTestStarted(false); + } + } + + /** + * Generate a button component from icon bean + * @param iconBean contains I18N key, ActionNames, icon path, optional icon path pressed + * @return a button for toolbar + */ + private static JButton makeButtonItemRes(IconToolbarBean iconBean) { + final URL imageURL = JMeterUtils.class.getClassLoader().getResource(iconBean.getIconPath()); + JButton button = new JButton(new ImageIcon(imageURL)); + button.setToolTipText(JMeterUtils.getResString(iconBean.getI18nKey())); + final URL imageURLPressed = JMeterUtils.class.getClassLoader().getResource(iconBean.getIconPathPressed()); + button.setPressedIcon(new ImageIcon(imageURLPressed)); + button.addActionListener(ActionRouter.getInstance()); + button.setActionCommand(iconBean.getActionNameResolve()); + return button; + } + + /** + * Parse icon set file. + * @return List of icons/action definition + */ + private static List getIconMappings() { + // Get the standard toolbar properties + Properties defaultProps = JMeterUtils.loadProperties(DEFAULT_TOOLBAR_PROPERTY_FILE); + if (defaultProps == null) { + JOptionPane.showMessageDialog(null, + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + return null; + } + Properties p; + String userProp = JMeterUtils.getProperty(USER_DEFINED_TOOLBAR_PROPERTY_FILE); + if (userProp != null){ + p = JMeterUtils.loadProperties(userProp, defaultProps); + } else { + p=defaultProps; + } + + String order = JMeterUtils.getPropDefault(TOOLBAR_LIST, p.getProperty(TOOLBAR_PROP_NAME)); + + if (order == null) { + log.warn("Could not find toolbar definition list"); + JOptionPane.showMessageDialog(null, + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JMeterUtils.getResString("toolbar_icon_set_not_found"), // $NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + return null; + } + + String[] oList = order.split(TOOLBAR_ENTRY_SEP); + + List listIcons = new ArrayList(); + for (String key : oList) { + log.debug("Toolbar icon key: " + key); //$NON-NLS-1$ + String trimmed = key.trim(); + if (trimmed.equals("|")) { //$NON-NLS-1$ + listIcons.add(null); + } else { + String property = p.getProperty(trimmed); + if (property == null) { + log.warn("No definition for toolbar entry: " + key); + } else { + try { + IconToolbarBean itb = new IconToolbarBean(property); + listIcons.add(itb); + } catch (IllegalArgumentException e) { + // already reported by IconToolbarBean + } + } + } + } + return listIcons; + } + + /** + * {@inheritDoc} + */ + public void localeChanged(LocaleChangeEvent event) { + this.removeAll(); + setupToolbarContent(this); + } + + /** + * Change state of buttons + * @param started + */ + public void setTestStarted(boolean started) { + Map buttonStates = new HashMap(); + buttonStates.put(ActionNames.ACTION_START,Boolean.valueOf(!started)); + buttonStates.put(ActionNames.ACTION_START_NO_TIMERS,Boolean.valueOf(!started)); + buttonStates.put(ActionNames.ACTION_STOP,Boolean.valueOf(started)); + buttonStates.put(ActionNames.ACTION_SHUTDOWN,Boolean.valueOf(started)); + buttonStates.put(ActionNames.REMOTE_START_ALL,Boolean.valueOf(!started)); + buttonStates.put(ActionNames.REMOTE_STOP_ALL,Boolean.valueOf(started)); + buttonStates.put(ActionNames.REMOTE_SHUT_ALL,Boolean.valueOf(started)); + Component[] components = getComponents(); + for (int i = 0; i < components.length; i++) { + if(components[i]instanceof JButton) { + JButton button = (JButton) components[i]; + Boolean enabled = buttonStates.get(button.getActionCommand()); + if(enabled != null) { + button.setEnabled(enabled.booleanValue()); + } + } + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/MenuFactory.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/MenuFactory.java new file mode 100644 index 0000000..bc8ba30 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/MenuFactory.java @@ -0,0 +1,700 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.Component; +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; +import javax.swing.MenuElement; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.TestBeanGUI; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Printable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public final class MenuFactory { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /* + * Predefined strings for makeMenu(). + * These are used as menu categories in the menuMap Hashmap, + * and also for resource lookup in messages.properties + */ + public static final String THREADS = "menu_threads"; //$NON-NLS-1$ + + public static final String FRAGMENTS = "menu_fragments"; //$NON-NLS-1$ + + public static final String TIMERS = "menu_timer"; //$NON-NLS-1$ + + public static final String CONTROLLERS = "menu_logic_controller"; //$NON-NLS-1$ + + public static final String SAMPLERS = "menu_generative_controller"; //$NON-NLS-1$ + + public static final String CONFIG_ELEMENTS = "menu_config_element"; //$NON-NLS-1$ + + public static final String POST_PROCESSORS = "menu_post_processors"; //$NON-NLS-1$ + + public static final String PRE_PROCESSORS = "menu_pre_processors"; //$NON-NLS-1$ + + public static final String ASSERTIONS = "menu_assertions"; //$NON-NLS-1$ + + public static final String NON_TEST_ELEMENTS = "menu_non_test_elements"; //$NON-NLS-1$ + + public static final String LISTENERS = "menu_listener"; //$NON-NLS-1$ + + private static final Map> menuMap = + new HashMap>(); + + private static final Set elementsToSkip = new HashSet(); + + // MENU_ADD_xxx - controls which items are in the ADD menu + // MENU_PARENT_xxx - controls which items are in the Insert Parent menu + private static final String[] MENU_ADD_CONTROLLER = new String[] { + MenuFactory.CONTROLLERS, + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.TIMERS, + MenuFactory.PRE_PROCESSORS, + MenuFactory.SAMPLERS, + MenuFactory.POST_PROCESSORS, + MenuFactory.ASSERTIONS, + MenuFactory.LISTENERS, + }; + + private static final String[] MENU_PARENT_CONTROLLER = new String[] { + MenuFactory.CONTROLLERS }; + + private static final String[] MENU_ADD_SAMPLER = new String[] { + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.TIMERS, + MenuFactory.PRE_PROCESSORS, + MenuFactory.POST_PROCESSORS, + MenuFactory.ASSERTIONS, + MenuFactory.LISTENERS, + }; + + private static final String[] MENU_PARENT_SAMPLER = new String[] { + MenuFactory.CONTROLLERS }; + + private static final List timers, controllers, samplers, threads, + fragments,configElements, assertions, listeners, nonTestElements, + postProcessors, preProcessors; + + static { + threads = new LinkedList(); + fragments = new LinkedList(); + timers = new LinkedList(); + controllers = new LinkedList(); + samplers = new LinkedList(); + configElements = new LinkedList(); + assertions = new LinkedList(); + listeners = new LinkedList(); + postProcessors = new LinkedList(); + preProcessors = new LinkedList(); + nonTestElements = new LinkedList(); + menuMap.put(THREADS, threads); + menuMap.put(FRAGMENTS, fragments); + menuMap.put(TIMERS, timers); + menuMap.put(ASSERTIONS, assertions); + menuMap.put(CONFIG_ELEMENTS, configElements); + menuMap.put(CONTROLLERS, controllers); + menuMap.put(LISTENERS, listeners); + menuMap.put(NON_TEST_ELEMENTS, nonTestElements); + menuMap.put(SAMPLERS, samplers); + menuMap.put(POST_PROCESSORS, postProcessors); + menuMap.put(PRE_PROCESSORS, preProcessors); + try { + String[] classesToSkip = + JOrphanUtils.split(JMeterUtils.getPropDefault("not_in_menu", ""), ","); //$NON-NLS-1$ + for (int i = 0; i < classesToSkip.length; i++) { + elementsToSkip.add(classesToSkip[i].trim()); + } + + initializeMenus(); + sortPluginMenus(); + } catch (Throwable e) { + log.error("", e); + if (e instanceof Error){ + throw (Error) e; + } + if (e instanceof RuntimeException){ + throw (RuntimeException) e; + } + } + } + + /** + * Private constructor to prevent instantiation. + */ + private MenuFactory() { + } + + public static void addEditMenu(JPopupMenu menu, boolean removable) { + addSeparator(menu); + if (removable) { + menu.add(makeMenuItemRes("cut", ActionNames.CUT, KeyStrokes.CUT)); //$NON-NLS-1$ + } + menu.add(makeMenuItemRes("copy", ActionNames.COPY, KeyStrokes.COPY)); //$NON-NLS-1$ + menu.add(makeMenuItemRes("paste", ActionNames.PASTE, KeyStrokes.PASTE)); //$NON-NLS-1$ + menu.add(makeMenuItemRes("duplicate", ActionNames.DUPLICATE, KeyStrokes.DUPLICATE)); //$NON-NLS-1$ + menu.add(makeMenuItemRes("reset_gui", ActionNames.RESET_GUI )); //$NON-NLS-1$ + if (removable) { + menu.add(makeMenuItemRes("remove", ActionNames.REMOVE, KeyStrokes.REMOVE)); //$NON-NLS-1$ + } + } + + public static void addPasteResetMenu(JPopupMenu menu) { + addSeparator(menu); + menu.add(makeMenuItemRes("paste", ActionNames.PASTE, KeyStrokes.PASTE)); //$NON-NLS-1$ + menu.add(makeMenuItemRes("reset_gui", ActionNames.RESET_GUI )); //$NON-NLS-1$ + } + + public static void addFileMenu(JPopupMenu menu) { + addSeparator(menu); + menu.add(makeMenuItemRes("open", ActionNames.OPEN));// $NON-NLS-1$ + menu.add(makeMenuItemRes("menu_merge", ActionNames.MERGE));// $NON-NLS-1$ + menu.add(makeMenuItemRes("save_as", ActionNames.SAVE_AS));// $NON-NLS-1$ + + addSeparator(menu); + JMenuItem savePicture = makeMenuItemRes("save_as_image",// $NON-NLS-1$ + ActionNames.SAVE_GRAPHICS, + KeyStrokes.SAVE_GRAPHICS); + menu.add(savePicture); + if (!(GuiPackage.getInstance().getCurrentGui() instanceof Printable)) { + savePicture.setEnabled(false); + } + + JMenuItem savePictureAll = makeMenuItemRes("save_as_image_all",// $NON-NLS-1$ + ActionNames.SAVE_GRAPHICS_ALL, + KeyStrokes.SAVE_GRAPHICS_ALL); + menu.add(savePictureAll); + + addSeparator(menu); + + JMenuItem disabled = makeMenuItemRes("disable", ActionNames.DISABLE);// $NON-NLS-1$ + JMenuItem enabled = makeMenuItemRes("enable", ActionNames.ENABLE);// $NON-NLS-1$ + boolean isEnabled = GuiPackage.getInstance().getTreeListener().getCurrentNode().isEnabled(); + if (isEnabled) { + disabled.setEnabled(true); + enabled.setEnabled(false); + } else { + disabled.setEnabled(false); + enabled.setEnabled(true); + } + menu.add(enabled); + menu.add(disabled); + JMenuItem toggle = makeMenuItemRes("toggle", ActionNames.TOGGLE, KeyStrokes.TOGGLE);// $NON-NLS-1$ + menu.add(toggle); + addSeparator(menu); + menu.add(makeMenuItemRes("help", ActionNames.HELP));// $NON-NLS-1$ + } + + public static JMenu makeMenus(String[] categories, String label, String actionCommand) { + JMenu addMenu = new JMenu(label); + for (int i = 0; i < categories.length; i++) { + addMenu.add(makeMenu(categories[i], actionCommand)); + } + return addMenu; + } + + public static JPopupMenu getDefaultControllerMenu() { + JPopupMenu pop = new JPopupMenu(); + pop.add(MenuFactory.makeMenus(MENU_ADD_CONTROLLER, + JMeterUtils.getResString("add"),// $NON-NLS-1$ + ActionNames.ADD)); + pop.add(makeMenus(MENU_PARENT_CONTROLLER, + JMeterUtils.getResString("insert_parent"),// $NON-NLS-1$ + ActionNames.ADD_PARENT)); + pop.add(makeMenus(MENU_PARENT_CONTROLLER, + JMeterUtils.getResString("change_parent"),// $NON-NLS-1$ + ActionNames.CHANGE_PARENT)); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultSamplerMenu() { + JPopupMenu pop = new JPopupMenu(); + pop.add(MenuFactory.makeMenus(MENU_ADD_SAMPLER, + JMeterUtils.getResString("add"),// $NON-NLS-1$ + ActionNames.ADD)); + pop.add(makeMenus(MENU_PARENT_SAMPLER, + JMeterUtils.getResString("insert_parent"),// $NON-NLS-1$ + ActionNames.ADD_PARENT)); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultConfigElementMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultVisualizerMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultTimerMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultAssertionMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultExtractorMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultMenu() { // if type is unknown + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + /** + * Create a menu from a menu category. + * + * @param category - predefined string (used as key for menuMap HashMap and messages.properties lookup) + * @param actionCommand - predefined string, e.g. ActionNames.ADD + * @see org.apache.jmeter.gui.action.ActionNames + * @return the menu + */ + public static JMenu makeMenu(String category, String actionCommand) { + return makeMenu(menuMap.get(category), actionCommand, JMeterUtils.getResString(category)); + } + + /** + * Create a menu from a collection of items. + * + * @param menuInfo - collection of MenuInfo items + * @param actionCommand - predefined string, e.g. ActionNames.ADD + * @see org.apache.jmeter.gui.action.ActionNames + * @param menuName + * @return the menu + */ + public static JMenu makeMenu(Collection menuInfo, String actionCommand, String menuName) { + JMenu menu = new JMenu(menuName); + for (MenuInfo info : menuInfo) { + menu.add(makeMenuItem(info, actionCommand)); + } + return menu; + } + + public static void setEnabled(JMenu menu) { + if (menu.getSubElements().length == 0) { + menu.setEnabled(false); + } + } + + /** + * Create a single menu item + * + * @param label for the MenuItem + * @param name for the MenuItem + * @param actionCommand - predefined string, e.g. ActionNames.ADD + * @see org.apache.jmeter.gui.action.ActionNames + * @return the menu item + */ + public static JMenuItem makeMenuItem(String label, String name, String actionCommand) { + JMenuItem newMenuChoice = new JMenuItem(label); + newMenuChoice.setName(name); + newMenuChoice.addActionListener(ActionRouter.getInstance()); + if (actionCommand != null) { + newMenuChoice.setActionCommand(actionCommand); + } + + return newMenuChoice; + } + + /** + * Create a single menu item from the resource name. + * + * @param resource for the MenuItem + * @param actionCommand - predefined string, e.g. ActionNames.ADD + * @see org.apache.jmeter.gui.action.ActionNames + * @return the menu item + */ + public static JMenuItem makeMenuItemRes(String resource, String actionCommand) { + JMenuItem newMenuChoice = new JMenuItem(JMeterUtils.getResString(resource)); + newMenuChoice.setName(resource); + newMenuChoice.addActionListener(ActionRouter.getInstance()); + if (actionCommand != null) { + newMenuChoice.setActionCommand(actionCommand); + } + + return newMenuChoice; + } + + /** + * Create a single menu item from a MenuInfo object + * + * @param info the MenuInfo object + * @param actionCommand - predefined string, e.g. ActionNames.ADD + * @see org.apache.jmeter.gui.action.ActionNames + * @return the menu item + */ + public static Component makeMenuItem(MenuInfo info, String actionCommand) { + JMenuItem newMenuChoice = new JMenuItem(info.getLabel()); + newMenuChoice.setName(info.getClassName()); + newMenuChoice.addActionListener(ActionRouter.getInstance()); + if (actionCommand != null) { + newMenuChoice.setActionCommand(actionCommand); + } + + return newMenuChoice; + } + + public static JMenuItem makeMenuItemRes(String resource, String actionCommand, KeyStroke accel) { + JMenuItem item = makeMenuItemRes(resource, actionCommand); + item.setAccelerator(accel); + return item; + } + + public static JMenuItem makeMenuItem(String label, String name, String actionCommand, KeyStroke accel) { + JMenuItem item = makeMenuItem(label, name, actionCommand); + item.setAccelerator(accel); + return item; + } + + private static void initializeMenus() { + try { + List guiClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { + JMeterGUIComponent.class, TestBean.class }); + Collections.sort(guiClasses); + for (String name : guiClasses) { + + /* + * JMeterTreeNode and TestBeanGUI are special GUI classes, and + * aren't intended to be added to menus + * + * TODO: find a better way of checking this + */ + if (name.endsWith("JMeterTreeNode") // $NON-NLS-1$ + || name.endsWith("TestBeanGUI")) {// $NON-NLS-1$ + continue;// Don't try to instantiate these + } + + if (elementsToSkip.contains(name)) { // No point instantiating class + log.info("Skipping " + name); + continue; + } + + boolean hideBean = false; // Should the TestBean be hidden? + + JMeterGUIComponent item; + try { + Class c = Class.forName(name); + if (TestBean.class.isAssignableFrom(c)) { + TestBeanGUI tbgui = new TestBeanGUI(c); + hideBean = tbgui.isHidden() || (tbgui.isExpert() && !JMeterUtils.isExpertMode()); + item = tbgui; + } else { + item = (JMeterGUIComponent) c.newInstance(); + } + } catch (NoClassDefFoundError e) { + log.warn("Missing jar? Could not create " + name + ". " + e); + continue; + } catch (Throwable e) { + log.warn("Could not instantiate " + name, e); + if (e instanceof Error){ + throw (Error) e; + } + if (e instanceof RuntimeException){ + throw (RuntimeException) e; + } + continue; + } + if (hideBean || elementsToSkip.contains(item.getStaticLabel())) { + log.info("Skipping " + name); + continue; + } else { + elementsToSkip.add(name); // Don't add it again + } + Collection categories = item.getMenuCategories(); + if (categories == null) { + log.debug(name + " participates in no menus."); + continue; + } + if (categories.contains(THREADS)) { + threads.add(new MenuInfo(item, name)); + } + if (categories.contains(FRAGMENTS)) { + fragments.add(new MenuInfo(item, name)); + } + if (categories.contains(TIMERS)) { + timers.add(new MenuInfo(item, name)); + } + + if (categories.contains(POST_PROCESSORS)) { + postProcessors.add(new MenuInfo(item, name)); + } + + if (categories.contains(PRE_PROCESSORS)) { + preProcessors.add(new MenuInfo(item, name)); + } + + if (categories.contains(CONTROLLERS)) { + controllers.add(new MenuInfo(item, name)); + } + + if (categories.contains(SAMPLERS)) { + samplers.add(new MenuInfo(item, name)); + } + + if (categories.contains(NON_TEST_ELEMENTS)) { + nonTestElements.add(new MenuInfo(item, name)); + } + + if (categories.contains(LISTENERS)) { + listeners.add(new MenuInfo(item, name)); + } + + if (categories.contains(CONFIG_ELEMENTS)) { + configElements.add(new MenuInfo(item, name)); + } + if (categories.contains(ASSERTIONS)) { + assertions.add(new MenuInfo(item, name)); + } + + } + } catch (IOException e) { + log.error("", e); + } + } + + private static void addSeparator(JPopupMenu menu) { + MenuElement[] elements = menu.getSubElements(); + if ((elements.length > 0) && !(elements[elements.length - 1] instanceof JPopupMenu.Separator)) { + menu.addSeparator(); + } + } + + /** + * Determine whether or not nodes can be added to this parent. + * + * Used by Merge + * + * @param parentNode + * @param element - top-level test element to be added + * + * @return whether it is OK to add the element to this parent + */ + public static boolean canAddTo(JMeterTreeNode parentNode, TestElement element) { + JMeterTreeNode node = new JMeterTreeNode(element, null); + return canAddTo(parentNode, new JMeterTreeNode[]{node}); + } + + /** + * Determine whether or not nodes can be added to this parent. + * + * Used by DragNDrop and Paste. + * + * @param parentNode + * @param nodes - array of nodes that are to be added + * + * @return whether it is OK to add the dragged nodes to this parent + */ + public static boolean canAddTo(JMeterTreeNode parentNode, JMeterTreeNode nodes[]) { + if (null == parentNode) { + return false; + } + if (foundClass(nodes, new Class[]{WorkBench.class})){// Can't add a Workbench anywhere + return false; + } + if (foundClass(nodes, new Class[]{TestPlan.class})){// Can't add a TestPlan anywhere + return false; + } + TestElement parent = parentNode.getTestElement(); + + // Force TestFragment to only be pastable under a Test Plan + if (foundClass(nodes, new Class[]{org.apache.jmeter.control.TestFragmentController.class})){ + if (parent instanceof TestPlan) + return true; + return false; + } + + if (parent instanceof WorkBench) {// allow everything else + return true; + } + if (parent instanceof TestPlan) { + if (foundClass(nodes, + new Class[]{Sampler.class, Controller.class}, // Samplers and Controllers need not apply ... + org.apache.jmeter.threads.AbstractThreadGroup.class) // but AbstractThreadGroup (Controller) is OK + ){ + return false; + } + return true; + } + // AbstractThreadGroup is only allowed under a TestPlan + if (foundClass(nodes, new Class[]{org.apache.jmeter.threads.AbstractThreadGroup.class})){ + return false; + } + if (parent instanceof Controller) {// Includes thread group; anything goes + return true; + } + if (parent instanceof Sampler) {// Samplers and Controllers need not apply ... + if (foundClass(nodes, new Class[]{Sampler.class, Controller.class})){ + return false; + } + return true; + } + // All other + return false; + } + + // Is any node an instance of one of the classes? + private static boolean foundClass(JMeterTreeNode nodes[],Class classes[]){ + for (int i = 0; i < nodes.length; i++) { + JMeterTreeNode node = nodes[i]; + for (int j=0; j < classes.length; j++) { + if (classes[j].isInstance(node.getUserObject())){ + return true; + } + } + } + return false; + } + + // Is any node an instance of one of the classes, but not an exception? + private static boolean foundClass(JMeterTreeNode nodes[],Class classes[], Class except){ + for (int i = 0; i < nodes.length; i++) { + JMeterTreeNode node = nodes[i]; + Object userObject = node.getUserObject(); + if (!except.isInstance(userObject)) { + for (int j=0; j < classes.length; j++) { + if (classes[j].isInstance(userObject)){ + return true; + } + } + } + } + return false; + } + + // Methods used for Test cases + static int menuMap_size() { + return menuMap.size(); + } + static int assertions_size() { + return assertions.size(); + } + static int configElements_size() { + return configElements.size(); + } + static int controllers_size() { + return controllers.size(); + } + static int listeners_size() { + return listeners.size(); + } + static int nonTestElements_size() { + return nonTestElements.size(); + } + static int postProcessors_size() { + return postProcessors.size(); + } + static int preProcessors_size() { + return preProcessors.size(); + } + static int samplers_size() { + return samplers.size(); + } + static int timers_size() { + return timers.size(); + } + static int elementsToSkip_size() { + return elementsToSkip.size(); + } + + /** + * Menu sort helper class + */ + private static class MenuInfoComparator implements Comparator, Serializable { + private static final long serialVersionUID = 1L; + private final boolean caseBlind; + MenuInfoComparator(boolean caseBlind){ + this.caseBlind = caseBlind; + } + public int compare(MenuInfo o1, MenuInfo o2) { + String lab1 = o1.getLabel(); + String lab2 = o2.getLabel(); + if (caseBlind) { + return lab1.toLowerCase(Locale.ENGLISH).compareTo(lab2.toLowerCase(Locale.ENGLISH)); + } + return lab1.compareTo(lab2); + } + } + + /** + * Sort loaded menus; all but THREADS are sorted case-blind. + * [This is so Thread Group appears before setUp and tearDown] + */ + private static void sortPluginMenus() { + for(Entry> me : menuMap.entrySet()){ + Collections.sort(me.getValue(), new MenuInfoComparator(!me.getKey().equals(THREADS))); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/MenuInfo.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/MenuInfo.java new file mode 100644 index 0000000..874214c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/MenuInfo.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import org.apache.jmeter.gui.JMeterGUIComponent; + +/** + * Class to hold additional information needed when building the GUI lists + */ +public class MenuInfo { + + private final String label; + + private final String className; + + private final JMeterGUIComponent guiComp; + + public MenuInfo(String displayLabel, String classFullName) { + label = displayLabel; + className = classFullName; + guiComp = null; + } + + public MenuInfo(JMeterGUIComponent item, String classFullName) { + label = item.getStaticLabel(); + className = classFullName; + guiComp = item; + } + + public String getLabel(){ + if (guiComp != null) { + return guiComp.getStaticLabel(); + } + return label; + } + + public String getClassName(){ + return className; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/NumberFieldErrorListener.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/NumberFieldErrorListener.java new file mode 100644 index 0000000..e67958a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/NumberFieldErrorListener.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.Component; +import java.awt.TextComponent; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; + +import javax.swing.JOptionPane; +import javax.swing.text.JTextComponent; + +import org.apache.jmeter.util.JMeterUtils; + +public class NumberFieldErrorListener extends FocusAdapter { + + private static final NumberFieldErrorListener listener = new NumberFieldErrorListener(); + + public static NumberFieldErrorListener getNumberFieldErrorListener() { + return listener; + } + + @Override + public void focusLost(FocusEvent e) { + Component source = (Component) e.getSource(); + String text = ""; + if (source instanceof JTextComponent) { + text = ((JTextComponent) source).getText(); + } else if (source instanceof TextComponent) { + text = ((TextComponent) source).getText(); + } + try { + Integer.parseInt(text); + } catch (NumberFormatException nfe) { + JOptionPane.showMessageDialog(source, + JMeterUtils.getResString("you_must_enter_a_valid_number"), //$NON-NLS-1$ + JMeterUtils.getResString("invalid_data"), //$NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + new FocusRequester(source); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/PowerTableModel.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/PowerTableModel.java new file mode 100644 index 0000000..ae15455 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/PowerTableModel.java @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +import javax.swing.table.DefaultTableModel; + +import org.apache.jorphan.collections.Data; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class PowerTableModel extends DefaultTableModel { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + private Data model = new Data(); + + private Class[] columnClasses; + + public PowerTableModel(String[] headers, Class[] classes) { + if (headers.length != classes.length){ + throw new IllegalArgumentException("Header and column array sizes differ"); + } + model.setHeaders(headers); + columnClasses = classes; + } + + public PowerTableModel() { + } + + public void setRowValues(int row, Object[] values) { + if (values.length != model.getHeaderCount()){ + throw new IllegalArgumentException("Incorrect number of data items"); + } + model.setCurrentPos(row); + for (int i = 0; i < values.length; i++) { + model.addColumnValue(model.getHeaders()[i], values[i]); + } + } + + public Data getData() { + return model; + } + + public void addNewColumn(String colName, Class colClass) { + model.addHeader(colName); + Class[] newClasses = new Class[columnClasses.length + 1]; + System.arraycopy(columnClasses, 0, newClasses, 0, columnClasses.length); + newClasses[newClasses.length - 1] = colClass; + columnClasses = newClasses; + Object defaultValue = createDefaultValue(columnClasses.length - 1); + model.setColumnData(colName, defaultValue); + this.fireTableStructureChanged(); + } + + @Override + public void removeRow(int row) { + log.debug("remove row: " + row); + if (model.size() > row) { + log.debug("Calling remove row on Data"); + model.removeRow(row); + } + } + + public void removeColumn(int col) { + model.removeColumn(col); + this.fireTableStructureChanged(); + } + + public void setColumnData(int col, List data) { + model.setColumnData(col, data); + } + + public List getColumnData(String colName) { + return model.getColumnAsObjectArray(colName); + } + + public void clearData() { + String[] headers = model.getHeaders(); + model = new Data(); + model.setHeaders(headers); + this.fireTableDataChanged(); + } + + @Override + public void addRow(Object data[]) { + if (data.length != model.getHeaderCount()){ + throw new IllegalArgumentException("Incorrect number of data items"); + } + model.setCurrentPos(model.size()); + for (int i = 0; i < data.length; i++) { + model.addColumnValue(model.getHeaders()[i], data[i]); + } + } + + public void addNewRow() { + addRow(createDefaultRow()); + } + + private Object[] createDefaultRow() { + Object[] rowData = new Object[getColumnCount()]; + for (int i = 0; i < rowData.length; i++) { + rowData[i] = createDefaultValue(i); + } + return rowData; + } + + public Object[] getRowData(int row) { + Object[] rowData = new Object[getColumnCount()]; + for (int i = 0; i < rowData.length; i++) { + rowData[i] = model.getColumnValue(i, row); + } + return rowData; + } + + private Object createDefaultValue(int i) { + Class colClass = getColumnClass(i); + try { + return colClass.newInstance(); + } catch (Exception e) { + try { + Constructor constr = colClass.getConstructor(new Class[] { String.class }); + return constr.newInstance(new Object[] { "" }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Integer.TYPE }); + return constr.newInstance(new Object[] { Integer.valueOf(0) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Long.TYPE }); + return constr.newInstance(new Object[] { Long.valueOf(0L) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Boolean.TYPE }); + return constr.newInstance(new Object[] { Boolean.FALSE }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Float.TYPE }); + return constr.newInstance(new Object[] { Float.valueOf(0F) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Double.TYPE }); + return constr.newInstance(new Object[] { Double.valueOf(0D) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Character.TYPE }); + return constr.newInstance(new Object[] { Character.valueOf(' ') }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Byte.TYPE }); + return constr.newInstance(new Object[] { Byte.valueOf(Byte.MIN_VALUE) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + try { + Constructor constr = colClass.getConstructor(new Class[] { Short.TYPE }); + return constr.newInstance(new Object[] { Short.valueOf(Short.MIN_VALUE) }); + } catch (NoSuchMethodException err) { + } catch (InstantiationException err) { + } catch (IllegalAccessException err) { + } catch (InvocationTargetException err) { + } + } + return ""; + } + + /** + * Required by table model interface. + * + * @return the RowCount value + */ + @Override + public int getRowCount() { + if (model == null) { + return 0; + } + return model.size(); + } + + /** + * Required by table model interface. + * + * @return the ColumnCount value + */ + @Override + public int getColumnCount() { + return model.getHeaders().length; + } + + /** + * Required by table model interface. + * + * @return the ColumnName value + */ + @Override + public String getColumnName(int column) { + return model.getHeaders()[column]; + } + + @Override + public boolean isCellEditable(int row, int column) { + // all table cells are editable + return true; + } + + @Override + public Class getColumnClass(int column) { + return columnClasses[column]; + } + + /** + * Required by table model interface. return the ValueAt value + */ + @Override + public Object getValueAt(int row, int column) { + return model.getColumnValue(column, row); + } + + /** + * Sets the ValueAt attribute of the Arguments object. + * + * @param value + * the new ValueAt value + */ + @Override + public void setValueAt(Object value, int row, int column) { + if (row < model.size()) { + model.setCurrentPos(row); + model.addColumnValue(model.getHeaders()[column], value); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportFileDialoger.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportFileDialoger.java new file mode 100644 index 0000000..2ebbea5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportFileDialoger.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.io.File; + +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileFilter; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.JMeterFileFilter; + +public final class ReportFileDialoger { + /** + * The last directory visited by the user while choosing Files. + */ + private static String lastJFCDirectory = null; + + private static JFileChooser jfc = new JFileChooser(); + + /** + * Prevent instantiation of utility class. + */ + private ReportFileDialoger() { + } + + /** + * Prompts the user to choose a file from their filesystems for our own + * devious uses. This method maintains the last directory the user visited + * before dismissing the dialog. This does NOT imply they actually chose a + * file from that directory, only that they closed the dialog there. It is + * the caller's responsibility to check to see if the selected file is + * non-null. + * + * @return the JFileChooser that interacted with the user, after they are + * finished using it (accept or otherwise). + */ + public static JFileChooser promptToOpenFile(String[] exts) { + // JFileChooser jfc = null; + + if (lastJFCDirectory == null) { + String start = System.getProperty("user.dir", ""); + + if (!start.equals("")) { + jfc.setCurrentDirectory(new File(start)); + } + } + clearFileFilters(); + jfc.addChoosableFileFilter(new JMeterFileFilter(exts)); + int retVal = jfc.showOpenDialog(ReportGuiPackage.getInstance().getMainFrame()); + lastJFCDirectory = jfc.getCurrentDirectory().getAbsolutePath(); + + if (retVal == JFileChooser.APPROVE_OPTION) { + return jfc; + } else { + return null; + } + } + + private static void clearFileFilters() { + FileFilter[] filters = jfc.getChoosableFileFilters(); + for (int x = 0; x < filters.length; x++) { + jfc.removeChoosableFileFilter(filters[x]); + } + } + + public static JFileChooser promptToOpenFile() { + return promptToOpenFile(new String[0]); + } + + /** + * Prompts the user to choose a file from their filesystems for our own + * devious uses. This method maintains the last directory the user visited + * before dismissing the dialog. This does NOT imply they actually chose a + * file from that directory, only that they closed the dialog there. It is + * the caller's responsibility to check to see if the selected file is + * non-null. + * + * @return the JFileChooser that interacted with the user, after they are + * finished using it (accept or otherwise). + * @see #promptToOpenFile() + */ + public static JFileChooser promptToSaveFile(String filename) { + return promptToSaveFile(filename, null); + } + + /** + * Get a JFileChooser with a new FileFilter. + * + * @param filename + * @param extensions + * @return JFileChooser + */ + public static JFileChooser promptToSaveFile(String filename, String[] extensions) { + if (lastJFCDirectory == null) { + String start = System.getProperty("user.dir", ""); + if (!start.equals("")) { + jfc = new JFileChooser(new File(start)); + } + lastJFCDirectory = jfc.getCurrentDirectory().getAbsolutePath(); + } + String ext = ".jmx"; + if (filename != null) { + jfc.setSelectedFile(new File(lastJFCDirectory, filename)); + int i = -1; + if ((i = filename.lastIndexOf(".")) > -1) { + ext = filename.substring(i); + } + } + clearFileFilters(); + if (extensions != null) { + jfc.addChoosableFileFilter(new JMeterFileFilter(extensions)); + } else { + jfc.addChoosableFileFilter(new JMeterFileFilter(new String[] { ext })); + } + + int retVal = jfc.showSaveDialog(ReportGuiPackage.getInstance().getMainFrame()); + lastJFCDirectory = jfc.getCurrentDirectory().getAbsolutePath(); + if (retVal == JFileChooser.APPROVE_OPTION) { + return jfc; + } else { + return null; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportFilePanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportFilePanel.java new file mode 100644 index 0000000..a0ddbed --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportFilePanel.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.util.JMeterUtils; + +public class ReportFilePanel extends HorizontalPanel implements ActionListener { + private static final long serialVersionUID = 240L; + + private final JTextField filename = new JTextField(20); + + private final JLabel label = new JLabel(JMeterUtils.getResString("file_visualizer_filename")); + + private final JButton browse = new JButton(JMeterUtils.getResString("browse")); + + private final List listeners = new LinkedList(); + + private final String title; + + private final String filetype; + + /** + * Constructor for the FilePanel object. + */ + public ReportFilePanel() { + this(""); + } + + public ReportFilePanel(String title) { + this(title, null); + } + + public ReportFilePanel(String title, String filetype) { + this.title = title; + this.filetype = filetype; + init(); + } + + /** + * Constructor for the FilePanel object. + */ + public ReportFilePanel(ChangeListener l, String title) { + this(title); + listeners.add(l); + } + + public void addChangeListener(ChangeListener l) { + listeners.add(l); + } + + private void init() { + setBorder(BorderFactory.createTitledBorder(title)); + add(label); + add(Box.createHorizontalStrut(5)); + add(filename); + add(Box.createHorizontalStrut(5)); + filename.addActionListener(this); + add(browse); + browse.setActionCommand("browse"); + browse.addActionListener(this); + + } + + /** + * If the gui needs to enable/disable the FilePanel, call the method. + * + * @param enable + */ + public void enableFile(boolean enable) { + browse.setEnabled(enable); + filename.setEnabled(enable); + } + + /** + * Gets the filename attribute of the FilePanel object. + * + * @return the filename value + */ + public String getFilename() { + return filename.getText(); + } + + /** + * Sets the filename attribute of the FilePanel object. + * + * @param f + * the new filename value + */ + public void setFilename(String f) { + filename.setText(f); + } + + private void fireFileChanged() { + Iterator iter = listeners.iterator(); + while (iter.hasNext()) { + iter.next().stateChanged(new ChangeEvent(this)); + } + } + + /** {@inheritDoc} */ + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("browse")) { + JFileChooser chooser = ReportFileDialoger.promptToOpenFile(new String[] { filetype }); + if (chooser != null && chooser.getSelectedFile() != null) { + filename.setText(chooser.getSelectedFile().getPath()); + fireFileChanged(); + } + } else { + fireFileChanged(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportMenuBar.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportMenuBar.java new file mode 100644 index 0000000..4406ff8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportMenuBar.java @@ -0,0 +1,488 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.Component; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; + +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.MenuElement; +import javax.swing.UIManager; + +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.report.gui.action.ReportActionRouter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jmeter.util.SSLManager; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * This is a version of the MenuBar for the reporting tool. I started + * with the existing jmeter menubar. + */ +public class ReportMenuBar extends JMenuBar implements LocaleChangeListener { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private JMenu fileMenu; + + private JMenuItem file_save_as; + + private JMenuItem file_load; + + private JMenuItem file_merge; + + private JMenuItem file_exit; + + private JMenuItem file_close; + + private JMenu editMenu; + + private JMenu edit_add; + + private JMenu runMenu; + + private JMenuItem run_start; + + private JMenu remote_start; + + private JMenuItem remote_start_all; + + private final Collection remote_engine_start; + + private JMenuItem run_stop; + + private JMenuItem run_shut; // all the others could be private too? + + private JMenu remote_stop; + + private JMenuItem remote_stop_all; + + private final Collection remote_engine_stop; + + private JMenuItem run_clear; + + private JMenuItem run_clearAll; + + // JMenu reportMenu; + // JMenuItem analyze; + private JMenu optionsMenu; + + private JMenu lafMenu; + + private JMenuItem sslManager; + + private JMenu helpMenu; + + private JMenuItem help_about; + + private String[] remoteHosts; + + private JMenu remote_exit; + + private JMenuItem remote_exit_all; + + private final Collection remote_engine_exit; + + public ReportMenuBar() { + remote_engine_start = new LinkedList(); + remote_engine_stop = new LinkedList(); + remote_engine_exit = new LinkedList(); + remoteHosts = JOrphanUtils.split(JMeterUtils.getPropDefault("remote_hosts", ""), ","); + if (remoteHosts.length == 1 && remoteHosts[0].equals("")) { + remoteHosts = new String[0]; + } + this.getRemoteItems(); + createMenuBar(); + } + + public void setFileSaveEnabled(boolean enabled) { + file_save_as.setEnabled(enabled); + } + + public void setFileLoadEnabled(boolean enabled) { + if (file_load != null) { + file_load.setEnabled(enabled); + } + if (file_merge != null) { + file_merge.setEnabled(enabled); + } + } + + public void setEditEnabled(boolean enabled) { + if (editMenu != null) { + editMenu.setEnabled(enabled); + } + } + + public void setEditAddMenu(JMenu menu) { + // If the Add menu already exists, remove it. + if (edit_add != null) { + editMenu.remove(edit_add); + } + // Insert the Add menu as the first menu item in the Edit menu. + edit_add = menu; + editMenu.insert(edit_add, 0); + } + + public void setEditMenu(JPopupMenu menu) { + if (menu != null) { + editMenu.removeAll(); + Component[] comps = menu.getComponents(); + for (int i = 0; i < comps.length; i++) { + editMenu.add(comps[i]); + } + editMenu.setEnabled(true); + } else { + // editMenu.setEnabled(false); + } + } + + public void setEditAddEnabled(boolean enabled) { + // There was a NPE being thrown without the null check here.. JKB + if (edit_add != null) { + edit_add.setEnabled(enabled); + } + // If we are enabling the Edit-->Add menu item, then we also need to + // enable the Edit menu. The Edit menu may already be enabled, but + // there's no harm it trying to enable it again. + setEditEnabled(enabled); + } + + public void setEditRemoveEnabled(boolean enabled) { + // If we are enabling the Edit-->Remove menu item, then we also need to + // enable the Edit menu. The Edit menu may already be enabled, but + // there's no harm it trying to enable it again. + if (enabled) { + setEditEnabled(true); + } else { + // If we are disabling the Edit-->Remove menu item and the + // Edit-->Add menu item is disabled, then we also need to disable + // the Edit menu. + // The Java Look and Feel Guidelines say to disable a menu if all + // menu items are disabled. + if (!edit_add.isEnabled()) { + editMenu.setEnabled(false); + } + } + } + + /** + * Creates the MenuBar for this application. I believe in my heart that this + * should be defined in a file somewhere, but that is for later. + */ + public void createMenuBar() { + makeFileMenu(); + makeEditMenu(); + makeRunMenu(); + makeOptionsMenu(); + makeHelpMenu(); + this.add(fileMenu); + this.add(editMenu); + this.add(runMenu); + this.add(optionsMenu); + this.add(helpMenu); + } + + private void makeHelpMenu() { + // HELP MENU + helpMenu = new JMenu(JMeterUtils.getResString("help")); + helpMenu.setMnemonic('H'); + JMenuItem contextHelp = new JMenuItem(JMeterUtils.getResString("help"), 'H'); + contextHelp.setActionCommand("help"); + contextHelp.setAccelerator(KeyStrokes.HELP); + contextHelp.addActionListener(ReportActionRouter.getInstance()); + help_about = new JMenuItem(JMeterUtils.getResString("about"), 'A'); + help_about.setActionCommand("about"); + help_about.addActionListener(ReportActionRouter.getInstance()); + helpMenu.add(contextHelp); + helpMenu.add(help_about); + } + + private void makeOptionsMenu() { + // OPTIONS MENU + optionsMenu = new JMenu(JMeterUtils.getResString("option")); + JMenuItem functionHelper = new JMenuItem(JMeterUtils.getResString("function_dialog_menu_item"), 'F'); + functionHelper.addActionListener(ReportActionRouter.getInstance()); + functionHelper.setActionCommand("functions"); + functionHelper.setAccelerator(KeyStrokes.FUNCTIONS); + lafMenu = new JMenu(JMeterUtils.getResString("appearance")); + UIManager.LookAndFeelInfo lafs[] = UIManager.getInstalledLookAndFeels(); + for (int i = 0; i < lafs.length; ++i) { + JMenuItem laf = new JMenuItem(lafs[i].getName()); + laf.addActionListener(ReportActionRouter.getInstance()); + laf.setActionCommand("laf:" + lafs[i].getClassName()); + lafMenu.setMnemonic('L'); + lafMenu.add(laf); + } + optionsMenu.setMnemonic('O'); + optionsMenu.add(functionHelper); + optionsMenu.add(lafMenu); + if (SSLManager.isSSLSupported()) { + sslManager = new JMenuItem(JMeterUtils.getResString("sslManager")); + sslManager.addActionListener(ReportActionRouter.getInstance()); + sslManager.setActionCommand("sslManager"); + sslManager.setMnemonic('S'); + sslManager.setAccelerator(KeyStrokes.SSL_MANAGER); + optionsMenu.add(sslManager); + } + optionsMenu.add(makeLanguageMenu()); + } + + // TODO fetch list of languages from a file? + // N.B. Changes to language list need to be reflected in + // resources/PackageTest.java + private JMenu makeLanguageMenu() { + return JMeterMenuBar.makeLanguageMenu(); + } + + /* + * Strings used to set up and process actions in this menu The strings need + * to agree with the those in the Action routines + */ + public static final String ACTION_SHUTDOWN = "shutdown"; + + public static final String ACTION_STOP = "stop"; + + public static final String ACTION_START = "start"; + + private void makeRunMenu() { + // RUN MENU + runMenu = new JMenu(JMeterUtils.getResString("run")); + runMenu.setMnemonic('R'); + run_start = new JMenuItem(JMeterUtils.getResString("start"), 'S'); + run_start.setAccelerator(KeyStrokes.ACTION_START); + run_start.addActionListener(ReportActionRouter.getInstance()); + run_start.setActionCommand(ACTION_START); + run_stop = new JMenuItem(JMeterUtils.getResString("stop"), 'T'); + run_stop.setAccelerator(KeyStrokes.ACTION_STOP); + run_stop.setEnabled(false); + run_stop.addActionListener(ReportActionRouter.getInstance()); + run_stop.setActionCommand(ACTION_STOP); + + run_shut = new JMenuItem(JMeterUtils.getResString("shutdown"), 'Y'); + run_shut.setAccelerator(KeyStrokes.ACTION_SHUTDOWN); + run_shut.setEnabled(false); + run_shut.addActionListener(ReportActionRouter.getInstance()); + run_shut.setActionCommand(ACTION_SHUTDOWN); + + run_clear = new JMenuItem(JMeterUtils.getResString("clear"), 'C'); + run_clear.addActionListener(ReportActionRouter.getInstance()); + run_clear.setActionCommand(ActionNames.CLEAR); + run_clearAll = new JMenuItem(JMeterUtils.getResString("clear_all"), 'a'); + run_clearAll.addActionListener(ReportActionRouter.getInstance()); + run_clearAll.setActionCommand(ActionNames.CLEAR_ALL); + run_clearAll.setAccelerator(KeyStrokes.CLEAR_ALL); + runMenu.add(run_start); + if (remote_start != null) { + runMenu.add(remote_start); + } + remote_start_all = new JMenuItem(JMeterUtils.getResString("remote_start_all"), 'Z'); + remote_start_all.setName("remote_start_all"); + remote_start_all.setAccelerator(KeyStrokes.REMOTE_START_ALL); + remote_start_all.addActionListener(ReportActionRouter.getInstance()); + remote_start_all.setActionCommand("remote_start_all"); + runMenu.add(remote_start_all); + runMenu.add(run_stop); + runMenu.add(run_shut); + if (remote_stop != null) { + runMenu.add(remote_stop); + } + remote_stop_all = new JMenuItem(JMeterUtils.getResString("remote_stop_all"), 'X'); + remote_stop_all.setAccelerator(KeyStrokes.REMOTE_STOP_ALL); + remote_stop_all.addActionListener(ReportActionRouter.getInstance()); + remote_stop_all.setActionCommand("remote_stop_all"); + runMenu.add(remote_stop_all); + + if (remote_exit != null) { + runMenu.add(remote_exit); + } + remote_exit_all = new JMenuItem(JMeterUtils.getResString("remote_exit_all")); + remote_exit_all.addActionListener(ReportActionRouter.getInstance()); + remote_exit_all.setActionCommand("remote_exit_all"); + runMenu.add(remote_exit_all); + + runMenu.addSeparator(); + runMenu.add(run_clear); + runMenu.add(run_clearAll); + } + + private void makeEditMenu() { + // EDIT MENU + editMenu = new JMenu(JMeterUtils.getResString("edit")); + // From the Java Look and Feel Guidelines: If all items in a menu + // are disabled, then disable the menu. Makes sense. + editMenu.setEnabled(false); + } + + private void makeFileMenu() { + // FILE MENU + fileMenu = new JMenu(JMeterUtils.getResString("file")); + fileMenu.setMnemonic('F'); + JMenuItem file_save = new JMenuItem(JMeterUtils.getResString("save"), 'S'); + file_save.setAccelerator(KeyStrokes.SAVE); + file_save.setActionCommand("save"); + file_save.addActionListener(ReportActionRouter.getInstance()); + file_save.setEnabled(true); + + file_save_as = new JMenuItem(JMeterUtils.getResString("save_all_as"), 'A'); + file_save_as.setAccelerator(KeyStrokes.SAVE_ALL_AS); + file_save_as.setActionCommand("save_all_as"); + file_save_as.addActionListener(ReportActionRouter.getInstance()); + file_save_as.setEnabled(true); + + file_load = new JMenuItem(JMeterUtils.getResString("menu_open"), 'O'); + file_load.setAccelerator(KeyStrokes.OPEN); + file_load.addActionListener(ReportActionRouter.getInstance()); + // Set default SAVE menu item to disabled since the default node that + // is selected is ROOT, which does not allow items to be inserted. + file_load.setEnabled(false); + file_load.setActionCommand("open"); + + file_close = new JMenuItem(JMeterUtils.getResString("menu_close"), 'C'); + file_close.setAccelerator(KeyStrokes.CLOSE); + file_close.setActionCommand("close"); + file_close.addActionListener(ReportActionRouter.getInstance()); + + file_exit = new JMenuItem(JMeterUtils.getResString("exit"), 'X'); + file_exit.setAccelerator(KeyStrokes.EXIT); + file_exit.setActionCommand("exit"); + file_exit.addActionListener(ReportActionRouter.getInstance()); + + file_merge = new JMenuItem(JMeterUtils.getResString("menu_merge"), 'M'); + // file_merge.setAccelerator( + // KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK)); + file_merge.addActionListener(ReportActionRouter.getInstance()); + // Set default SAVE menu item to disabled since the default node that + // is selected is ROOT, which does not allow items to be inserted. + file_merge.setEnabled(false); + file_merge.setActionCommand("merge"); + + fileMenu.add(file_close); + fileMenu.add(file_load); + fileMenu.add(file_merge); + fileMenu.add(file_save); + fileMenu.add(file_save_as); + fileMenu.addSeparator(); + fileMenu.add(file_exit); + } + + public void setRunning(boolean running, String host) { + log.info("setRunning(" + running + "," + host + ")"); + + Iterator iter = remote_engine_start.iterator(); + Iterator iter2 = remote_engine_stop.iterator(); + Iterator iter3 = remote_engine_exit.iterator(); + while (iter.hasNext() && iter2.hasNext() && iter3.hasNext()) { + JMenuItem start = iter.next(); + JMenuItem stop = iter2.next(); + JMenuItem exit = iter3.next(); + if (start.getText().equals(host)) { + log.info("Found start host: " + start.getText()); + start.setEnabled(!running); + } + if (stop.getText().equals(host)) { + log.info("Found stop host: " + stop.getText()); + stop.setEnabled(running); + } + if (exit.getText().equals(host)) { + log.info("Found exit host: " + exit.getText()); + exit.setEnabled(true); + } + } + } + + @Override + public void setEnabled(boolean enable) { + run_start.setEnabled(!enable); + run_stop.setEnabled(enable); + run_shut.setEnabled(enable); + } + + private void getRemoteItems() { + if (remoteHosts.length > 0) { + remote_start = new JMenu(JMeterUtils.getResString("remote_start")); + remote_stop = new JMenu(JMeterUtils.getResString("remote_stop")); + remote_exit = new JMenu(JMeterUtils.getResString("remote_exit")); + + for (int i = 0; i < remoteHosts.length; i++) { + remoteHosts[i] = remoteHosts[i].trim(); + JMenuItem item = new JMenuItem(remoteHosts[i]); + item.setActionCommand("remote_start"); + item.setName(remoteHosts[i]); + item.addActionListener(ReportActionRouter.getInstance()); + remote_engine_start.add(item); + remote_start.add(item); + item = new JMenuItem(remoteHosts[i]); + item.setActionCommand("remote_stop"); + item.setName(remoteHosts[i]); + item.addActionListener(ReportActionRouter.getInstance()); + item.setEnabled(false); + remote_engine_stop.add(item); + remote_stop.add(item); + item = new JMenuItem(remoteHosts[i]); + item.setActionCommand("remote_exit"); + item.setName(remoteHosts[i]); + item.addActionListener(ReportActionRouter.getInstance()); + item.setEnabled(false); + remote_engine_exit.add(item); + remote_exit.add(item); + } + } + } + + /** + * Processes a locale change notification. Changes the texts in all menus to + * the new language. + */ + public void localeChanged(LocaleChangeEvent event) { + updateMenuElement(fileMenu); + updateMenuElement(editMenu); + updateMenuElement(runMenu); + updateMenuElement(optionsMenu); + updateMenuElement(helpMenu); + } + + /** + * Refreshes all texts in the menu and all submenus to a new locale. + */ + private void updateMenuElement(MenuElement menu) { + Component component = menu.getComponent(); + + if (component.getName() != null) { + ((JMenuItem) component).setText(JMeterUtils.getResString(component.getName())); + } + + MenuElement[] subelements = menu.getSubElements(); + + for (int i = 0; i < subelements.length; i++) { + updateMenuElement(subelements[i]); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportMenuFactory.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportMenuFactory.java new file mode 100644 index 0000000..c2f6b07 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/ReportMenuFactory.java @@ -0,0 +1,342 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; +import javax.swing.MenuElement; + +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.KeyStrokes; +import org.apache.jmeter.report.gui.action.ReportActionRouter; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.TestBeanGUI; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Printable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public final class ReportMenuFactory { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String TIMERS = "menu_timer"; + + public static final String CONTROLLERS = "menu_logic_controller"; + + public static final String CONFIG_ELEMENTS = "menu_config_element"; + + public static final String POST_PROCESSORS = "menu_post_processors"; + + public static final String PRE_PROCESSORS = "menu_pre_processors"; + + public static final String NON_TEST_ELEMENTS = "menu_non_test_elements"; + + public static final String LISTENERS = "menu_listener"; + + public static final String REPORT_PAGE = "menu_report_page"; + + public static final String TABLES = "menu_tables"; + + private static final Map> menuMap = new HashMap>(); + + private static final Set elementsToSkip = new HashSet(); + + // MENU_ADD_xxx - controls which items are in the ADD menu + // MENU_PARENT_xxx - controls which items are in the Insert Parent menu + private static final String[] MENU_ADD_CONTROLLER = new String[] { ReportMenuFactory.CONTROLLERS, + ReportMenuFactory.CONFIG_ELEMENTS, ReportMenuFactory.TIMERS, ReportMenuFactory.LISTENERS, + ReportMenuFactory.PRE_PROCESSORS, ReportMenuFactory.POST_PROCESSORS }; + + private static final String[] MENU_PARENT_CONTROLLER = new String[] { ReportMenuFactory.CONTROLLERS }; + + private static List controllers, configElements, listeners, nonTestElements, + postProcessors, preProcessors, reportPage, tables; + + static { + try { + String[] classesToSkip = JOrphanUtils.split(JMeterUtils.getPropDefault("not_in_menu", ""), ","); + for (int i = 0; i < classesToSkip.length; i++) { + elementsToSkip.add(classesToSkip[i].trim()); + } + + initializeMenus(); + } catch (Exception e) { + log.error("", e); + } + } + + /** + * Private constructor to prevent instantiation. + */ + private ReportMenuFactory() { + } + + public static void addEditMenu(JPopupMenu menu, boolean removable) { + addSeparator(menu); + if (removable) { + menu.add(makeMenuItem(JMeterUtils.getResString("remove"), "Remove", "remove", KeyStrokes.REMOVE)); + } + menu.add(makeMenuItem(JMeterUtils.getResString("cut"), "Cut", "Cut", KeyStrokes.CUT)); + menu.add(makeMenuItem(JMeterUtils.getResString("copy"), "Copy", "Copy", KeyStrokes.COPY)); + menu.add(makeMenuItem(JMeterUtils.getResString("paste"), "Paste", "Paste", KeyStrokes.PASTE)); + menu.add(makeMenuItem(JMeterUtils.getResString("paste_insert"), "Paste Insert", "Paste Insert")); + } + + public static void addFileMenu(JPopupMenu menu) { + addSeparator(menu); + menu.add(makeMenuItem(JMeterUtils.getResString("open"), "Open", "open")); + menu.add(makeMenuItem(JMeterUtils.getResString("save_as"), "Save As", "save_as")); + JMenuItem savePicture = makeMenuItem(JMeterUtils.getResString("save_as_image"), "Save Image", "save_graphics", + KeyStrokes.SAVE_GRAPHICS); + menu.add(savePicture); + if (!(ReportGuiPackage.getInstance().getCurrentGui() instanceof Printable)) { + savePicture.setEnabled(false); + } + JMenuItem disabled = makeMenuItem(JMeterUtils.getResString("disable"), "Disable", "disable"); + JMenuItem enabled = makeMenuItem(JMeterUtils.getResString("enable"), "Enable", "enable"); + boolean isEnabled = ReportGuiPackage.getInstance().getTreeListener().getCurrentNode().isEnabled(); + if (isEnabled) { + disabled.setEnabled(true); + enabled.setEnabled(false); + } else { + disabled.setEnabled(false); + enabled.setEnabled(true); + } + menu.add(enabled); + menu.add(disabled); + addSeparator(menu); + menu.add(makeMenuItem(JMeterUtils.getResString("help"), "Help", "help")); + } + + public static JMenu makeMenus(String[] categories, String label, String actionCommand) { + JMenu addMenu = new JMenu(label); + for (int i = 0; i < categories.length; i++) { + addMenu.add(makeMenu(categories[i], actionCommand)); + } + return addMenu; + } + + public static JPopupMenu getDefaultControllerMenu() { + JPopupMenu pop = new JPopupMenu(); + pop.add(MenuFactory.makeMenus(MENU_ADD_CONTROLLER, + JMeterUtils.getResString("add"),// $NON-NLS-1$ + ActionNames.ADD)); + pop.add(makeMenus(MENU_PARENT_CONTROLLER, + JMeterUtils.getResString("insert_parent"),// $NON-NLS-1$ + ActionNames.ADD_PARENT)); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultConfigElementMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultVisualizerMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultTimerMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultAssertionMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JPopupMenu getDefaultExtractorMenu() { + JPopupMenu pop = new JPopupMenu(); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + public static JMenu makeMenu(String category, String actionCommand) { + return makeMenu(menuMap.get(category), actionCommand, JMeterUtils.getResString(category)); + } + + public static JMenu makeMenu(Collection menuInfo, String actionCommand, String menuName) { + Iterator iter = menuInfo.iterator(); + JMenu menu = new JMenu(menuName); + while (iter.hasNext()) { + MenuInfo info = iter.next(); + menu.add(makeMenuItem(info.getLabel(), info.getClassName(), actionCommand)); + } + return menu; + } + + public static void setEnabled(JMenu menu) { + if (menu.getSubElements().length == 0) { + menu.setEnabled(false); + } + } + + public static JMenuItem makeMenuItem(String label, String name, String actionCommand) { + JMenuItem newMenuChoice = new JMenuItem(label); + newMenuChoice.setName(name); + newMenuChoice.addActionListener(ReportActionRouter.getInstance()); + if (actionCommand != null) { + newMenuChoice.setActionCommand(actionCommand); + } + + return newMenuChoice; + } + + public static JMenuItem makeMenuItem(String label, String name, String actionCommand, KeyStroke accel) { + JMenuItem item = makeMenuItem(label, name, actionCommand); + item.setAccelerator(accel); + return item; + } + + private static void initializeMenus() { + try { + List guiClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { + JMeterGUIComponent.class, TestBean.class }); + controllers = new LinkedList(); + configElements = new LinkedList(); + listeners = new LinkedList(); + postProcessors = new LinkedList(); + preProcessors = new LinkedList(); + tables = new LinkedList(); + reportPage = new LinkedList(); + nonTestElements = new LinkedList(); + menuMap.put(CONFIG_ELEMENTS, configElements); + menuMap.put(CONTROLLERS, controllers); + menuMap.put(LISTENERS, listeners); + menuMap.put(NON_TEST_ELEMENTS, nonTestElements); + menuMap.put(POST_PROCESSORS, postProcessors); + menuMap.put(PRE_PROCESSORS, preProcessors); + menuMap.put(REPORT_PAGE, reportPage); + menuMap.put(TABLES, tables); + Collections.sort(guiClasses); + Iterator iter = guiClasses.iterator(); + while (iter.hasNext()) { + String name = iter.next(); + + /* + * JMeterTreeNode and TestBeanGUI are special GUI classes, and + * aren't intended to be added to menus + * + * TODO: find a better way of checking this + */ + if (name.endsWith("JMeterTreeNode") || name.endsWith("TestBeanGUI")) { + continue;// Don't try to instantiate these + } + + JMeterGUIComponent item; + try { + Class c = Class.forName(name); + if (TestBean.class.isAssignableFrom(c)) { + item = new TestBeanGUI(c); + } else { + item = (JMeterGUIComponent) c.newInstance(); + } + } catch (NoClassDefFoundError e) { + log.warn("Missing jar? Could not create " + name + ". " + e); + continue; + } catch (Exception e) { + log.warn("Could not instantiate " + name, e); + continue; + } + if (elementsToSkip.contains(name) || elementsToSkip.contains(item.getStaticLabel())) { + log.info("Skipping " + name); + continue; + } else { + elementsToSkip.add(name); + } + Collection categories = item.getMenuCategories(); + if (categories == null) { + log.debug(name + " participates in no menus."); + continue; + } + + if (categories.contains(POST_PROCESSORS)) { + postProcessors.add(new MenuInfo(item.getStaticLabel(), name)); + } + + if (categories.contains(PRE_PROCESSORS)) { + preProcessors.add(new MenuInfo(item.getStaticLabel(), name)); + } + + if (categories.contains(CONTROLLERS)) { + controllers.add(new MenuInfo(item.getStaticLabel(), name)); + } + + if (categories.contains(NON_TEST_ELEMENTS)) { + nonTestElements.add(new MenuInfo(item.getStaticLabel(), name)); + } + + if (categories.contains(LISTENERS)) { + listeners.add(new MenuInfo(item.getStaticLabel(), name)); + } + + if (categories.contains(CONFIG_ELEMENTS)) { + configElements.add(new MenuInfo(item.getStaticLabel(), name)); + } + + if (categories.contains(TABLES)) { + tables.add(new MenuInfo(item.getStaticLabel(), name)); + } + + if (categories.contains(REPORT_PAGE)) { + reportPage.add(new MenuInfo(item.getStaticLabel(), name)); + } + } + } catch (IOException e) { + log.error("", e); + } + } + + private static void addSeparator(JPopupMenu menu) { + MenuElement[] elements = menu.getSubElements(); + if ((elements.length > 0) && !(elements[elements.length - 1] instanceof JPopupMenu.Separator)) { + menu.addSeparator(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/TextAreaCellRenderer.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/TextAreaCellRenderer.java new file mode 100644 index 0000000..bb6c89c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/TextAreaCellRenderer.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.jmeter.gui.util; + +import java.awt.Component; + +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.table.TableCellRenderer; + +public class TextAreaCellRenderer implements TableCellRenderer { + + private JTextArea rend = new JTextArea(""); + + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, + int row, int column) { + rend = new JTextArea(value.toString()); + // Use two rows, so that we have room for horisontal scrollbar, if the text is one long line. Fix for 40371 + // This is not an optimal solution, but makes it possible to see the line if it is long + rend.setRows(2); + rend.revalidate(); + if (!hasFocus && !isSelected) { + rend.setBackground(JMeterColor.LAVENDER); + } + if (table.getRowHeight(row) < getPreferredHeight()) { + table.setRowHeight(row, getPreferredHeight()); + } + return rend; + } + + public int getPreferredHeight() { + return rend.getPreferredSize().height + 5; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/TextAreaTableCellEditor.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/TextAreaTableCellEditor.java new file mode 100644 index 0000000..acafa3c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/TextAreaTableCellEditor.java @@ -0,0 +1,322 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.gui.util; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.ItemEvent; +import java.awt.event.MouseEvent; +import java.io.Serializable; +import java.util.EventObject; + +import javax.swing.AbstractCellEditor; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.JTree; +import javax.swing.table.TableCellEditor; +import javax.swing.tree.TreeCellEditor; + +public class TextAreaTableCellEditor extends AbstractCellEditor implements TableCellEditor, TreeCellEditor { + private static final long serialVersionUID = 240L; + + // + // Instance Variables + // + + /** The Swing component being edited. */ + protected JTextArea editorComponent; + + /** + * The delegate class which handles all methods sent from the + * CellEditor. + */ + protected EditorDelegate delegate; + + /** + * An integer specifying the number of clicks needed to start editing. Even + * if clickCountToStart is defined as zero, it will not + * initiate until a click occurs. + */ + protected int clickCountToStart = 1; + + // + // Constructors + // + + /** + * Constructs a TableCellEditor that uses a text field. + */ + public TextAreaTableCellEditor() { + editorComponent = new JTextArea(); + editorComponent.setRows(3); + this.clickCountToStart = 2; + delegate = new EditorDelegate() { + private static final long serialVersionUID = 240L; + + @Override + public void setValue(Object value) { + editorComponent.setText((value != null) ? value.toString() : ""); + } + + @Override + public Object getCellEditorValue() { + return editorComponent.getText(); + } + }; + editorComponent.addFocusListener(delegate); + } + + /** + * Returns a reference to the editor component. + * + * @return the editor Component + */ + public Component getComponent() { + return editorComponent; + } + + // + // Modifying + // + + /** + * Specifies the number of clicks needed to start editing. + * + * @param count + * an int specifying the number of clicks needed to start editing + * @see #getClickCountToStart + */ + public void setClickCountToStart(int count) { + clickCountToStart = count; + } + + /** + * Returns the number of clicks needed to start editing. + * + * @return the number of clicks needed to start editing + */ + public int getClickCountToStart() { + return clickCountToStart; + } + + // + // Override the implementations of the superclass, forwarding all methods + // from the CellEditor interface to our delegate. + // + + /** + * Forwards the message from the CellEditor to the + * delegate. + * + * @see EditorDelegate#getCellEditorValue + */ + public Object getCellEditorValue() { + return delegate.getCellEditorValue(); + } + + /** + * Forwards the message from the CellEditor to the + * delegate. + * + * @see EditorDelegate#isCellEditable(EventObject) + */ + @Override + public boolean isCellEditable(EventObject anEvent) { + return delegate.isCellEditable(anEvent); + } + + /** + * Forwards the message from the CellEditor to the + * delegate. + * + * @see EditorDelegate#shouldSelectCell(EventObject) + */ + @Override + public boolean shouldSelectCell(EventObject anEvent) { + return delegate.shouldSelectCell(anEvent); + } + + /** + * Forwards the message from the CellEditor to the + * delegate. + * + * @see EditorDelegate#stopCellEditing + */ + @Override + public boolean stopCellEditing() { + return delegate.stopCellEditing(); + } + + /** + * Forwards the message from the CellEditor to the + * delegate. + * + * @see EditorDelegate#cancelCellEditing + */ + @Override + public void cancelCellEditing() { + delegate.cancelCellEditing(); + } + + // + // Implementing the TreeCellEditor Interface + // + + /** Implements the TreeCellEditor interface. */ + public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, + boolean leaf, int row) { + String stringValue = tree.convertValueToText(value, isSelected, expanded, leaf, row, false); + + delegate.setValue(stringValue); + return new JScrollPane(editorComponent); + } + + // + // Implementing the CellEditor Interface + // + /** Implements the TableCellEditor interface. */ + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + delegate.setValue(value); + return new JScrollPane(editorComponent); + } + + // + // Protected EditorDelegate class + // + + /** + * The protected EditorDelegate class. + */ + protected class EditorDelegate implements FocusListener, Serializable { + private static final long serialVersionUID = 240L; + + /** The value of this cell. */ + protected Object value; + + /** + * Returns the value of this cell. + * + * @return the value of this cell + */ + public Object getCellEditorValue() { + return value; + } + + /** + * Sets the value of this cell. + * + * @param value + * the new value of this cell + */ + public void setValue(Object value) { + this.value = value; + } + + /** + * Returns true if anEvent is not a + * MouseEvent. Otherwise, it returns true if the + * necessary number of clicks have occurred, and returns false + * otherwise. + * + * @param anEvent + * the event + * @return true if cell is ready for editing, false otherwise + * @see #setClickCountToStart(int) + * @see #shouldSelectCell + */ + public boolean isCellEditable(EventObject anEvent) { + if (anEvent instanceof MouseEvent) { + return ((MouseEvent) anEvent).getClickCount() >= clickCountToStart; + } + return true; + } + + /** + * Returns true to indicate that the editing cell may be selected. + * + * @param anEvent + * the event + * @return true + * @see #isCellEditable + */ + public boolean shouldSelectCell(EventObject anEvent) { + return true; + } + + /** + * Returns true to indicate that editing has begun. + * + * @param anEvent + * the event + */ + public boolean startCellEditing(EventObject anEvent) { + return true; + } + + /** + * Stops editing and returns true to indicate that editing has stopped. + * This method calls fireEditingStopped. + * + * @return true + */ + public boolean stopCellEditing() { + fireEditingStopped(); + return true; + } + + /** + * Cancels editing. This method calls fireEditingCanceled. + */ + public void cancelCellEditing() { + fireEditingCanceled(); + } + + /** + * When an action is performed, editing is ended. + * + * @param e + * the action event + * @see #stopCellEditing + */ + public void actionPerformed(ActionEvent e) { + TextAreaTableCellEditor.this.stopCellEditing(); + } + + /** + * When an item's state changes, editing is ended. + * + * @param e + * the action event + * @see #stopCellEditing + */ + public void itemStateChanged(ItemEvent e) { + TextAreaTableCellEditor.this.stopCellEditing(); + } + + public void focusLost(FocusEvent ev) { + TextAreaTableCellEditor.this.stopCellEditing(); + } + + public void focusGained(FocusEvent ev) { + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/TextBoxDialoger.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/TextBoxDialoger.java new file mode 100644 index 0000000..25e0754 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/TextBoxDialoger.java @@ -0,0 +1,233 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.gui.util; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.KeyStroke; +import javax.swing.table.TableModel; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; + +/** + * Dialog text box to display some text in a box + * + */ +public class TextBoxDialoger implements ActionListener { + + private static final String CANCEL_COMMAND = "cancel_dialog"; // $NON-NLS-1$ + + private static final String SAVE_CLOSE_COMMAND = "save_close_dialog"; // $NON-NLS-1$ + + private static final String CLOSE_COMMAND = "close_dialog"; // $NON-NLS-1$ + + private static JDialog dialog; + + private JEditorPane textBox; + + private String originalText; + + private boolean editable = false; + + /** + * Dialog text box + */ + public TextBoxDialoger() { + // Empty box + init(""); //$NON-NLS-1$ + } + + /** + * Dialog text box + * @param text - text to display in a box + */ + public TextBoxDialoger(String text) { + init(text); + } + + /** + * Dialog text box + * @param text - text to display in a box + * @param editable - allow to modify text + */ + public TextBoxDialoger(String text, boolean editable) { + this.editable = editable; + init(text); + } + + private void init(String text) { + createDialogBox(); + setTextBox(text); + dialog.setVisible(true); + } + + private void createDialogBox() { + JFrame mainFrame = GuiPackage.getInstance().getMainFrame(); + String title = editable ? JMeterUtils.getResString("textbox_title_edit") //$NON-NLS-1$ + : JMeterUtils.getResString("textbox_title_view"); //$NON-NLS-1$ + dialog = new JDialog(mainFrame, title, true); // modal dialog box + + // Close action dialog box when tapping Escape key + JPanel content = (JPanel) dialog.getContentPane(); + KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + content.registerKeyboardAction(this, stroke, + JComponent.WHEN_IN_FOCUSED_WINDOW); + + textBox = new JEditorPane(); + textBox.setEditable(editable); + + JScrollPane textBoxScrollPane = GuiUtils.makeScrollPane(textBox); + + JPanel btnBar = new JPanel(); + btnBar.setLayout(new FlowLayout(FlowLayout.RIGHT)); + if (editable) { + JButton cancelBtn = new JButton(JMeterUtils.getResString("textbox_cancel")); //$NON-NLS-1$ + cancelBtn.setActionCommand(CANCEL_COMMAND); + cancelBtn.addActionListener(this); + JButton saveBtn = new JButton(JMeterUtils.getResString("textbox_save_close")); //$NON-NLS-1$ + saveBtn.setActionCommand(SAVE_CLOSE_COMMAND); + saveBtn.addActionListener(this); + + btnBar.add(cancelBtn); + btnBar.add(saveBtn); + } else { + JButton closeBtn = new JButton(JMeterUtils.getResString("textbox_close")); //$NON-NLS-1$ + closeBtn.setActionCommand(CLOSE_COMMAND); + closeBtn.addActionListener(this); + + btnBar.add(closeBtn); + } + + // Prepare dialog box + Container panel = dialog.getContentPane(); + dialog.setMinimumSize(new Dimension(400, 250)); + panel.add(textBoxScrollPane, BorderLayout.CENTER); + panel.add(btnBar, BorderLayout.SOUTH); + + // determine location on screen + Point p = mainFrame.getLocationOnScreen(); + Dimension d1 = mainFrame.getSize(); + Dimension d2 = dialog.getSize(); + dialog.setLocation(p.x + (d1.width - d2.width) / 2, p.y + (d1.height - d2.height) / 2); + dialog.pack(); + } + + private void closeDialog() { + dialog.setVisible(false); + } + + public void actionPerformed(ActionEvent e) { + String command = e.getActionCommand(); + + if (CANCEL_COMMAND.equals(command)) { + closeDialog(); + setTextBox(originalText); + } else { + // must be CLOSE or SAVE_CLOSE COMMANDS + closeDialog(); + } + + } + + public void setTextBox(String text) { + originalText = text; // text backup + textBox.setText(text); + } + + public String getTextBox() { + return textBox.getText(); + } + + /** + * Class to display a dialog box and cell's content + * when double click on a table's cell + * + */ + public static class TextBoxDoubleClick extends MouseAdapter { + + private JTable table = null; + + public TextBoxDoubleClick(JTable table) { + super(); + this.table = table; + } + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { // double click + TableModel tm = table.getModel(); + Object value = tm.getValueAt(table.getSelectedRow(), table.getSelectedColumn()); + new TextBoxDialoger(value.toString(), false); // view only + } + } + } + + /** + * Class to edit in a dialog box the cell's content + * when double (pressed) click on a table's cell which is editable + * + */ + public static class TextBoxDoubleClickPressed extends MouseAdapter { + + private JTable table = null; + + public TextBoxDoubleClickPressed(JTable table) { + super(); + this.table = table; + } + + @Override + public void mousePressed(MouseEvent e) { + if (e.getClickCount() == 2) { // double (pressed) click + TableModel tm = table.getModel(); + Object value = tm.getValueAt(table.getSelectedRow(), table.getSelectedColumn()); + if (value instanceof String) { + if (table.getCellEditor() != null) { + table.getCellEditor().cancelCellEditing(); // in main table (evt mousePressed because cell is editable) + } + TextBoxDialoger tbd = new TextBoxDialoger(value.toString(), true); + tm.setValueAt(tbd.getTextBox(), table.getSelectedRow(), table.getSelectedColumn()); + } // else do nothing (cell isn't a string to edit) + } + } + + } + + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/gui/util/VerticalPanel.java b/ApacheJmeter/src/org/apache/jmeter/gui/util/VerticalPanel.java new file mode 100644 index 0000000..030a34f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/gui/util/VerticalPanel.java @@ -0,0 +1,78 @@ +/* + * 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. + * + */ + +/* + * Created on Apr 25, 2003 + * + * To change the template for this generated file go to + * Window>Preferences>Java>Code Generation>Code and Comments + */ +package org.apache.jmeter.gui.util; + +import java.awt.Color; +import java.awt.BorderLayout; +import java.awt.Component; + +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JPanel; + +public class VerticalPanel extends JPanel { + private static final long serialVersionUID = 240L; + + private final Box subPanel = Box.createVerticalBox(); + + private final float horizontalAlign; + + private final int vgap; + + public VerticalPanel() { + this(5, LEFT_ALIGNMENT); + } + + public VerticalPanel(Color bkg) { + this(); + subPanel.setBackground(bkg); + this.setBackground(bkg); + } + + public VerticalPanel(int vgap, float horizontalAlign) { + super(new BorderLayout()); + add(subPanel, BorderLayout.NORTH); + this.vgap = vgap; + this.horizontalAlign = horizontalAlign; + } + + /** + * {@inheritDoc} + */ + @Override + public Component add(Component c) { + // This won't work right if we remove components. But we don't, so I'm + // not going to worry about it right now. + if (vgap > 0 && subPanel.getComponentCount() > 0) { + subPanel.add(Box.createVerticalStrut(vgap)); + } + + if (c instanceof JComponent) { + ((JComponent) c).setAlignmentX(horizontalAlign); + } + + return subPanel.add(c); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/modifiers/BSFPreProcessor.java b/ApacheJmeter/src/org/apache/jmeter/modifiers/BSFPreProcessor.java new file mode 100644 index 0000000..2967fe9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/modifiers/BSFPreProcessor.java @@ -0,0 +1,49 @@ +/* + * 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.modifiers; + +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class BSFPreProcessor extends BSFTestElement implements Cloneable, PreProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + public void process(){ + BSFManager mgr =null; + try { + mgr = getManager(); + if (mgr == null) { return; } + processFileOrScript(mgr); + } catch (BSFException e) { + log.warn("Problem in BSF script "+e); + } finally { + if (mgr != null) { + mgr.terminate(); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/modifiers/BSFPreProcessorBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/modifiers/BSFPreProcessorBeanInfo.java new file mode 100644 index 0000000..553be38 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/modifiers/BSFPreProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.modifiers; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +public class BSFPreProcessorBeanInfo extends BSFBeanInfoSupport { + + public BSFPreProcessorBeanInfo() { + super(BSFPreProcessor.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/modifiers/BeanShellPreProcessor.java b/ApacheJmeter/src/org/apache/jmeter/modifiers/BeanShellPreProcessor.java new file mode 100644 index 0000000..342a373 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/modifiers/BeanShellPreProcessor.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.modifiers; + +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +public class BeanShellPreProcessor extends BeanShellTestElement + implements Cloneable, PreProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + // can be specified in jmeter.properties + private static final String INIT_FILE = "beanshell.preprocessor.init"; //$NON-NLS-1$ + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + public void process(){ + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + log.error("BeanShell not found"); + return; + } + JMeterContext jmctx = JMeterContextService.getContext(); + Sampler sam = jmctx.getCurrentSampler(); + try { + // Add variables for access to context and variables + bshInterpreter.set("sampler", sam);//$NON-NLS-1$ + processFileOrScript(bshInterpreter); + } catch (JMeterException e) { + log.warn("Problem in BeanShell script "+e); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/modifiers/BeanShellPreProcessorBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/modifiers/BeanShellPreProcessorBeanInfo.java new file mode 100644 index 0000000..6eb1ce6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/modifiers/BeanShellPreProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.modifiers; + +import org.apache.jmeter.util.BeanShellBeanInfoSupport; + +public class BeanShellPreProcessorBeanInfo extends BeanShellBeanInfoSupport { + + public BeanShellPreProcessorBeanInfo() { + super(BeanShellPreProcessor.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/modifiers/CounterConfig.java b/ApacheJmeter/src/org/apache/jmeter/modifiers/CounterConfig.java new file mode 100644 index 0000000..7a4cc02 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/modifiers/CounterConfig.java @@ -0,0 +1,242 @@ +/* + * 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.modifiers; + +import java.io.Serializable; +import java.text.DecimalFormat; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Provides a counter per-thread(user) or per-thread group. + */ +public class CounterConfig extends AbstractTestElement + implements Serializable, LoopIterationListener, NoThreadClone { + + private static final long serialVersionUID = 233L; + + private final static String START = "CounterConfig.start"; // $NON-NLS-1$ + + private final static String END = "CounterConfig.end"; // $NON-NLS-1$ + + private final static String INCREMENT = "CounterConfig.incr"; // $NON-NLS-1$ + + private final static String FORMAT = "CounterConfig.format"; // $NON-NLS-1$ + + private final static String PER_USER = "CounterConfig.per_user"; // $NON-NLS-1$ + + private final static String VAR_NAME = "CounterConfig.name"; // $NON-NLS-1$ + + private final static String RESET_ON_THREAD_GROUP_ITERATION = "CounterConfig.reset_on_tg_iteration"; // $NON-NLS-1$ + + private static final boolean RESET_ON_THREAD_GROUP_ITERATION_DEFAULT = false; + + // This class is not cloned per thread, so this is shared + //@GuardedBy("this") + private long globalCounter = Long.MIN_VALUE; + + // Used for per-thread/user numbers + private transient ThreadLocal perTheadNumber; + + // Used for per-thread/user storage of increment in Thread Group Main loop + private transient ThreadLocal perTheadLastIterationNumber; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private void init() { + perTheadNumber = new ThreadLocal() { + @Override + protected Long initialValue() { + return Long.valueOf(getStart()); + } + }; + perTheadLastIterationNumber = new ThreadLocal() { + @Override + protected Long initialValue() { + return Long.valueOf(1); + } + }; + } + + + public CounterConfig() { + super(); + init(); + } + + private Object readResolve(){ + init(); + return this; + } + /** + * @see LoopIterationListener#iterationStart(LoopIterationEvent) + */ + public void iterationStart(LoopIterationEvent event) { + // Cannot use getThreadContext() as not cloned per thread + JMeterVariables variables = JMeterContextService.getContext().getVariables(); + long start = getStart(); + long end = getEnd(); + long increment = getIncrement(); + if (!isPerUser()) { + synchronized (this) { + if (globalCounter == Long.MIN_VALUE || globalCounter > end) { + globalCounter = start; + } + variables.put(getVarName(), formatNumber(globalCounter)); + globalCounter += increment; + } + } else { + long current = perTheadNumber.get().longValue(); + if(isResetOnThreadGroupIteration()) { + int iteration = variables.getIteration(); + Long lastIterationNumber = perTheadLastIterationNumber.get(); + if(iteration != lastIterationNumber.longValue()) { + // reset + current = getStart(); + } + perTheadLastIterationNumber.set(Long.valueOf(iteration)); + } + variables.put(getVarName(), formatNumber(current)); + current += increment; + if (current > end) { + current = start; + } + perTheadNumber.set(Long.valueOf(current)); + } + } + + // Use format to create number; if it fails, use the default + private String formatNumber(long value){ + String format = getFormat(); + if (format != null && format.length() > 0) { + try { + DecimalFormat myFormatter = new DecimalFormat(format); + return myFormatter.format(value); + } catch (NumberFormatException ignored) { + log.warn("Error formating "+value + " at format:"+format+", using default"); + } catch (IllegalArgumentException ignored) { + log.warn("Error formating "+value + " at format:"+format+", using default"); + } + } + return Long.toString(value); + } + + public void setStart(long start) { + setProperty(new LongProperty(START, start)); + } + + public void setStart(String start) { + setProperty(START, start); + } + + public long getStart() { + return getPropertyAsLong(START); + } + + public String getStartAsString() { + return getPropertyAsString(START); + } + + public void setEnd(long end) { + setProperty(new LongProperty(END, end)); + } + + public void setEnd(String end) { + setProperty(END, end); + } + + /** + * @param value boolean indicating if counter must be reset on Thread Group Iteration + */ + public void setResetOnThreadGroupIteration(boolean value) { + setProperty(RESET_ON_THREAD_GROUP_ITERATION, value, RESET_ON_THREAD_GROUP_ITERATION_DEFAULT); + } + + /** + * @return true if counter must be reset on Thread Group Iteration + */ + public boolean isResetOnThreadGroupIteration() { + return getPropertyAsBoolean(RESET_ON_THREAD_GROUP_ITERATION, RESET_ON_THREAD_GROUP_ITERATION_DEFAULT); + } + + /** + * + * @return counter upper limit (default Long.MAX_VALUE) + */ + public long getEnd() { + long propertyAsLong = getPropertyAsLong(END); + if (propertyAsLong == 0 && "".equals(getProperty(END).getStringValue())) { + propertyAsLong = Long.MAX_VALUE; + } + return propertyAsLong; + } + + public String getEndAsString(){ + return getPropertyAsString(END); + } + + public void setIncrement(long inc) { + setProperty(new LongProperty(INCREMENT, inc)); + } + + public void setIncrement(String incr) { + setProperty(INCREMENT, incr); + } + + public long getIncrement() { + return getPropertyAsLong(INCREMENT); + } + + public String getIncrementAsString() { + return getPropertyAsString(INCREMENT); + } + + public void setIsPerUser(boolean isPer) { + setProperty(new BooleanProperty(PER_USER, isPer)); + } + + public boolean isPerUser() { + return getPropertyAsBoolean(PER_USER); + } + + public void setVarName(String name) { + setProperty(VAR_NAME, name); + } + + public String getVarName() { + return getPropertyAsString(VAR_NAME); + } + + public void setFormat(String format) { + setProperty(FORMAT, format); + } + + public String getFormat() { + return getPropertyAsString(FORMAT); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/modifiers/JSR223PreProcessor.java b/ApacheJmeter/src/org/apache/jmeter/modifiers/JSR223PreProcessor.java new file mode 100644 index 0000000..ac5d0cd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/modifiers/JSR223PreProcessor.java @@ -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. + * + */ + +package org.apache.jmeter.modifiers; + +import java.io.IOException; + +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223PreProcessor extends JSR223TestElement implements Cloneable, PreProcessor, TestBean +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + public void process() { + + try { + ScriptEngineManager sem = getManager(); + if(sem == null) { return; } + processFileOrScript(sem); + } catch (ScriptException e) { + log.warn("Problem in JSR223 script "+e); + } catch (IOException e) { + log.warn("Problem in JSR223 script "+e); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/modifiers/JSR223PreProcessorBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/modifiers/JSR223PreProcessorBeanInfo.java new file mode 100644 index 0000000..27a73d7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/modifiers/JSR223PreProcessorBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.modifiers; + +import org.apache.jmeter.util.JSR223BeanInfoSupport; + +public class JSR223PreProcessorBeanInfo extends JSR223BeanInfoSupport { + + public JSR223PreProcessorBeanInfo() { + super(JSR223PreProcessor.class); + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/modifiers/UserParameters.java b/ApacheJmeter/src/org/apache/jmeter/modifiers/UserParameters.java new file mode 100644 index 0000000..4e5af33 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/modifiers/UserParameters.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.modifiers; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class UserParameters extends AbstractTestElement implements Serializable, PreProcessor, LoopIterationListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + public static final String NAMES = "UserParameters.names";// $NON-NLS-1$ + + public static final String THREAD_VALUES = "UserParameters.thread_values";// $NON-NLS-1$ + + public static final String PER_ITERATION = "UserParameters.per_iteration";// $NON-NLS-1$ + + /* + * Although the lock appears to be an instance lock, in fact the lock is + * shared between all threads in a thread group, but different thread groups + * have different locks - see the clone() method below + * + * The lock ensures that all the variables are processed together, which is + * important for functions such as __CSVRead and _StringFromFile. + */ + private transient Object lock = new Object(); + + private Object readResolve(){ // Lock object must exist + lock = new Object(); + return this; + } + + public CollectionProperty getNames() { + return (CollectionProperty) getProperty(NAMES); + } + + public CollectionProperty getThreadLists() { + return (CollectionProperty) getProperty(THREAD_VALUES); + } + + /** + * The list of names of the variables to hold values. This list must come in + * the same order as the sub lists that are given to + * {@link #setThreadLists(Collection)}. + */ + public void setNames(Collection list) { + setProperty(new CollectionProperty(NAMES, list)); + } + + /** + * The list of names of the variables to hold values. This list must come in + * the same order as the sub lists that are given to + * {@link #setThreadLists(CollectionProperty)}. + */ + public void setNames(CollectionProperty list) { + setProperty(list); + } + + /** + * The thread list is a list of lists. Each list within the parent list is a + * collection of values for a simulated user. As many different sets of + * values can be supplied in this fashion to cause JMeter to set different + * values to variables for different test threads. + */ + public void setThreadLists(Collection threadLists) { + setProperty(new CollectionProperty(THREAD_VALUES, threadLists)); + } + + /** + * The thread list is a list of lists. Each list within the parent list is a + * collection of values for a simulated user. As many different sets of + * values can be supplied in this fashion to cause JMeter to set different + * values to variables for different test threads. + */ + public void setThreadLists(CollectionProperty threadLists) { + setProperty(threadLists); + } + + private CollectionProperty getValues() { + CollectionProperty threadValues = (CollectionProperty) getProperty(THREAD_VALUES); + if (threadValues.size() > 0) { + return (CollectionProperty) threadValues.get(getThreadContext().getThreadNum() % threadValues.size()); + } + return new CollectionProperty("noname", new LinkedList()); + } + + public boolean isPerIteration() { + return getPropertyAsBoolean(PER_ITERATION); + } + + public void setPerIteration(boolean perIter) { + setProperty(new BooleanProperty(PER_ITERATION, perIter)); + } + + public void process() { + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName() + " process " + isPerIteration());//$NON-NLS-1$ + } + if (!isPerIteration()) { + setValues(); + } + } + + private void setValues() { + synchronized (lock) { + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName() + " Running up named: " + getName());//$NON-NLS-1$ + } + PropertyIterator namesIter = getNames().iterator(); + PropertyIterator valueIter = getValues().iterator(); + JMeterVariables jmvars = getThreadContext().getVariables(); + while (namesIter.hasNext() && valueIter.hasNext()) { + String name = namesIter.next().getStringValue(); + String value = valueIter.next().getStringValue(); + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName() + " saving variable: " + name + "=" + value);//$NON-NLS-1$ + } + jmvars.put(name, value); + } + } + } + + /** + * @see LoopIterationListener#iterationStart(LoopIterationEvent) + */ + public void iterationStart(LoopIterationEvent event) { + if (log.isDebugEnabled()) { + log.debug(Thread.currentThread().getName() + " iteration start " + isPerIteration());//$NON-NLS-1$ + } + if (isPerIteration()) { + setValues(); + } + } + + /* + * (non-Javadoc) A new instance is created for each thread group, and the + * clone() method is then called to create copies for each thread in a + * thread group. This means that the lock object is common to a thread + * group; separate thread groups have separate locks. If this is not + * intended, the lock object could be made static. + * + * @see java.lang.Object#clone() + */ + @Override + public Object clone() { + UserParameters up = (UserParameters) super.clone(); + up.lock = lock; // ensure that clones share the same lock object + return up; + } + + /** + * {@inheritDoc} + */ + @Override + protected void mergeIn(TestElement element) { + // super.mergeIn(element); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/modifiers/gui/CounterConfigGui.java b/ApacheJmeter/src/org/apache/jmeter/modifiers/gui/CounterConfigGui.java new file mode 100644 index 0000000..06ca3af --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/modifiers/gui/CounterConfigGui.java @@ -0,0 +1,151 @@ +/* + * 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.modifiers.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBox; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.modifiers.CounterConfig; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class CounterConfigGui extends AbstractConfigGui implements ActionListener { + private static final long serialVersionUID = 240L; + + private JLabeledTextField startField; + private JLabeledTextField incrField; + private JLabeledTextField endField; + private JLabeledTextField varNameField; + private JLabeledTextField formatField; + private JCheckBox resetCounterOnEachThreadGroupIteration; + + private JCheckBox perUserField; + + public CounterConfigGui() { + super(); + init(); + } + + public String getLabelResource() { + return "counter_config_title";//$NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + CounterConfig config = new CounterConfig(); + modifyTestElement(config); + return config; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement c) { + if (c instanceof CounterConfig) { + CounterConfig config = (CounterConfig) c; + config.setStart(startField.getText()); + // Bug 22820 if (endField.getText().length() > 0) + { + config.setEnd(endField.getText()); + } + config.setIncrement(incrField.getText()); + config.setVarName(varNameField.getText()); + config.setFormat(formatField.getText()); + config.setIsPerUser(perUserField.isSelected()); + config.setResetOnThreadGroupIteration(resetCounterOnEachThreadGroupIteration.isEnabled() + && resetCounterOnEachThreadGroupIteration.isSelected()); + } + super.configureTestElement(c); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + startField.setText(""); //$NON-NLS-1$ + incrField.setText(""); //$NON-NLS-1$ + endField.setText(""); //$NON-NLS-1$ + varNameField.setText(""); //$NON-NLS-1$ + formatField.setText(""); //$NON-NLS-1$ + perUserField.setSelected(false); + resetCounterOnEachThreadGroupIteration.setEnabled(false); + } + + @Override + public void configure(TestElement element) { + super.configure(element); + CounterConfig config = (CounterConfig) element; + startField.setText(config.getStartAsString()); + endField.setText(config.getEndAsString()); + incrField.setText(config.getIncrementAsString()); + formatField.setText(config.getFormat()); + varNameField.setText(config.getVarName()); + perUserField.setSelected(config.isPerUser()); + if(config.isPerUser()) { + resetCounterOnEachThreadGroupIteration.setEnabled(true); + resetCounterOnEachThreadGroupIteration.setSelected(config.isResetOnThreadGroupIteration()); + } else { + resetCounterOnEachThreadGroupIteration.setEnabled(false); + } + } + + private void init() { + setBorder(makeBorder()); + setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + + startField = new JLabeledTextField(JMeterUtils.getResString("start"));//$NON-NLS-1$ + incrField = new JLabeledTextField(JMeterUtils.getResString("increment"));//$NON-NLS-1$ + endField = new JLabeledTextField(JMeterUtils.getResString("max"));//$NON-NLS-1$ + varNameField = new JLabeledTextField(JMeterUtils.getResString("var_name"));//$NON-NLS-1$ + formatField = new JLabeledTextField(JMeterUtils.getResString("format"));//$NON-NLS-1$ + perUserField = new JCheckBox(JMeterUtils.getResString("counter_per_user"));//$NON-NLS-1$ + resetCounterOnEachThreadGroupIteration = new JCheckBox(JMeterUtils.getResString("counter_reset_per_tg_iteration"));//$NON-NLS-1$ + add(makeTitlePanel()); + add(startField); + add(incrField); + add(endField); + add(formatField); + add(varNameField); + add(perUserField); + add(resetCounterOnEachThreadGroupIteration); + + perUserField.addActionListener(this); + } + + /** + * Disable/Enable resetCounterOnEachThreadGroupIteration when perUserField is disabled / enabled + */ + public void actionPerformed(ActionEvent e) { + if(e.getSource() == perUserField) { + resetCounterOnEachThreadGroupIteration.setEnabled(perUserField.isSelected()); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/modifiers/gui/UserParametersGui.java b/ApacheJmeter/src/org/apache/jmeter/modifiers/gui/UserParametersGui.java new file mode 100644 index 0000000..51a4614 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/modifiers/gui/UserParametersGui.java @@ -0,0 +1,404 @@ +/* + * 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.modifiers.gui; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import java.awt.FontMetrics; +import java.awt.Component; +import javax.swing.table.DefaultTableCellRenderer; + +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.PowerTableModel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.modifiers.UserParameters; +import org.apache.jmeter.processor.gui.AbstractPreProcessorGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class UserParametersGui extends AbstractPreProcessorGui { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String NAME_COL_RESOURCE = "name"; // $NON-NLS-1$ + private static final String USER_COL_RESOURCE = "user"; // $NON-NLS-1$ + private static final String UNDERSCORE = "_"; // $NON-NLS-1$ + + private JTable paramTable; + + private PowerTableModel tableModel; + + private int numUserColumns = 1; + + private JButton addParameterButton, addUserButton, deleteRowButton, deleteColumnButton; + + private JCheckBox perIterationCheck; + + private JPanel paramPanel; + + public UserParametersGui() { + super(); + init(); + } + + public String getLabelResource() { + return "user_parameters_title"; // $NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + initTableModel(); + paramTable.setModel(tableModel); + UserParameters params = (UserParameters) el; + CollectionProperty names = params.getNames(); + CollectionProperty threadValues = params.getThreadLists(); + tableModel.setColumnData(0, (List) names.getObjectValue()); + PropertyIterator iter = threadValues.iterator(); + if (iter.hasNext()) { + tableModel.setColumnData(1, (List) iter.next().getObjectValue()); + } + int count = 2; + while (iter.hasNext()) { + String colName = getUserColName(count); + tableModel.addNewColumn(colName, String.class); + tableModel.setColumnData(count, (List) iter.next().getObjectValue()); + count++; + } + setColumnWidths(); + perIterationCheck.setSelected(params.isPerIteration()); + super.configure(el); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + UserParameters params = new UserParameters(); + modifyTestElement(params); + return params; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement params) { + if (paramTable.isEditing()) { + paramTable.getCellEditor().stopCellEditing(); + } + UserParameters userParams = ((UserParameters) params); + userParams.setNames(new CollectionProperty(UserParameters.NAMES, tableModel.getColumnData(NAME_COL_RESOURCE))); + CollectionProperty threadLists = new CollectionProperty(UserParameters.THREAD_VALUES, new ArrayList()); + log.debug("making threadlists from gui"); + for (int col = 1; col < tableModel.getColumnCount(); col++) { + threadLists.addItem(tableModel.getColumnData(getUserColName(col))); + if (log.isDebugEnabled()) { + log.debug("Adding column to threadlist: " + tableModel.getColumnData(getUserColName(col))); + log.debug("Threadlists now = " + threadLists); + } + } + if (log.isDebugEnabled()) { + log.debug("In the end, threadlists = " + threadLists); + } + userParams.setThreadLists(threadLists); + userParams.setPerIteration(perIterationCheck.isSelected()); + super.configureTestElement(params); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + initTableModel(); + paramTable.setModel(tableModel); + HeaderAsPropertyRenderer defaultRenderer = new HeaderAsPropertyRenderer(){ + private static final long serialVersionUID = 240L; + + @Override + protected String getText(Object value, int row, int column) { + if (column >= 1){ // Don't process the NAME column + String val = value.toString(); + if (val.startsWith(USER_COL_RESOURCE+UNDERSCORE)){ + return JMeterUtils.getResString(USER_COL_RESOURCE)+val.substring(val.indexOf(UNDERSCORE)); + } + } + return super.getText(value, row, column); + } + }; + paramTable.getTableHeader().setDefaultRenderer(defaultRenderer); + perIterationCheck.setSelected(false); + } + + private String getUserColName(int user){ + return USER_COL_RESOURCE+UNDERSCORE+user; + } + + private void init() { + setBorder(makeBorder()); + setLayout(new BorderLayout()); + JPanel vertPanel = new VerticalPanel(); + vertPanel.add(makeTitlePanel()); + + perIterationCheck = new JCheckBox(JMeterUtils.getResString("update_per_iter"), true); // $NON-NLS-1$ + Box perIterationPanel = Box.createHorizontalBox(); + perIterationPanel.add(perIterationCheck); + perIterationPanel.add(Box.createHorizontalGlue()); + vertPanel.add(perIterationPanel); + add(vertPanel, BorderLayout.NORTH); + + add(makeParameterPanel(), BorderLayout.CENTER); + } + + private JPanel makeParameterPanel() { + JLabel tableLabel = new JLabel(JMeterUtils.getResString("user_parameters_table")); // $NON-NLS-1$ + initTableModel(); + paramTable = new JTable(tableModel); + // paramTable.setRowSelectionAllowed(true); + // paramTable.setColumnSelectionAllowed(true); + paramTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + paramTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + // paramTable.setCellSelectionEnabled(true); + // paramTable.setPreferredScrollableViewportSize(new Dimension(100, + // 70)); + + paramPanel = new JPanel(new BorderLayout()); + paramPanel.add(tableLabel, BorderLayout.NORTH); + JScrollPane scroll = new JScrollPane(paramTable); + scroll.setPreferredSize(scroll.getMinimumSize()); + paramPanel.add(scroll, BorderLayout.CENTER); + paramPanel.add(makeButtonPanel(), BorderLayout.SOUTH); + return paramPanel; + } + + protected void initTableModel() { + tableModel = new PowerTableModel(new String[] { NAME_COL_RESOURCE, // $NON-NLS-1$ + getUserColName(numUserColumns) }, new Class[] { String.class, String.class }); + } + + private JPanel makeButtonPanel() { + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new GridLayout(2, 2)); + addParameterButton = new JButton(JMeterUtils.getResString("add_parameter")); // $NON-NLS-1$ + addUserButton = new JButton(JMeterUtils.getResString("add_user")); // $NON-NLS-1$ + deleteRowButton = new JButton(JMeterUtils.getResString("delete_parameter")); // $NON-NLS-1$ + deleteColumnButton = new JButton(JMeterUtils.getResString("delete_user")); // $NON-NLS-1$ + buttonPanel.add(addParameterButton); + buttonPanel.add(deleteRowButton); + buttonPanel.add(addUserButton); + buttonPanel.add(deleteColumnButton); + addParameterButton.addActionListener(new AddParamAction()); + addUserButton.addActionListener(new AddUserAction()); + deleteRowButton.addActionListener(new DeleteRowAction()); + deleteColumnButton.addActionListener(new DeleteColumnAction()); + return buttonPanel; + } + + /** + * Set Column size + */ + private void setColumnWidths() { + int margin = 10; + int minwidth = 150; + + JTableHeader tableHeader = paramTable.getTableHeader(); + FontMetrics headerFontMetrics = tableHeader.getFontMetrics(tableHeader.getFont()); + + for (int i = 0; i < tableModel.getColumnCount(); i++) { + int headerWidth = headerFontMetrics.stringWidth(paramTable.getColumnName(i)); + int maxWidth = getMaximalRequiredColumnWidth(i, headerWidth); + + paramTable.getColumnModel().getColumn(i).setPreferredWidth(Math.max(maxWidth + margin, minwidth)); + } + } + + /** + * Compute max width between width of the largest column at columnIndex and headerWidth + * @param columnIndex Column index + * @param headerWidth Header width based on Font + */ + private int getMaximalRequiredColumnWidth(int columnIndex, int headerWidth) { + int maxWidth = headerWidth; + + TableColumn column = paramTable.getColumnModel().getColumn(columnIndex); + + TableCellRenderer cellRenderer = column.getCellRenderer(); + + if(cellRenderer == null) { + cellRenderer = new DefaultTableCellRenderer(); + } + + for(int row = 0; row < paramTable.getModel().getRowCount(); row++) { + Component rendererComponent = cellRenderer.getTableCellRendererComponent(paramTable, + paramTable.getModel().getValueAt(row, columnIndex), + false, + false, + row, + columnIndex); + + double valueWidth = rendererComponent.getPreferredSize().getWidth(); + + maxWidth = (int) Math.max(maxWidth, valueWidth); + } + + return maxWidth; + } + + private class AddParamAction implements ActionListener { + public void actionPerformed(ActionEvent e) { + if (paramTable.isEditing()) { + TableCellEditor cellEditor = paramTable.getCellEditor(paramTable.getEditingRow(), paramTable + .getEditingColumn()); + cellEditor.stopCellEditing(); + } + + tableModel.addNewRow(); + tableModel.fireTableDataChanged(); + + // Enable DELETE (which may already be enabled, but it won't hurt) + deleteRowButton.setEnabled(true); + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + paramTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + + private class AddUserAction implements ActionListener { + public void actionPerformed(ActionEvent e) { + + if (paramTable.isEditing()) { + TableCellEditor cellEditor = paramTable.getCellEditor(paramTable.getEditingRow(), paramTable + .getEditingColumn()); + cellEditor.stopCellEditing(); + } + + tableModel.addNewColumn(getUserColName(tableModel.getColumnCount()), String.class); + tableModel.fireTableDataChanged(); + + setColumnWidths(); + // Enable DELETE (which may already be enabled, but it won't hurt) + deleteColumnButton.setEnabled(true); + + // Highlight (select) the appropriate row. + int colToSelect = tableModel.getColumnCount() - 1; + paramTable.setColumnSelectionInterval(colToSelect, colToSelect); + } + } + + private class DeleteRowAction implements ActionListener { + public void actionPerformed(ActionEvent e) { + if (paramTable.isEditing()) { + TableCellEditor cellEditor = paramTable.getCellEditor(paramTable.getEditingRow(), paramTable + .getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = paramTable.getSelectedRow(); + if (rowSelected >= 0) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + deleteRowButton.setEnabled(false); + } + + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + else { + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + paramTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + } + + private class DeleteColumnAction implements ActionListener { + public void actionPerformed(ActionEvent e) { + if (paramTable.isEditing()) { + TableCellEditor cellEditor = paramTable.getCellEditor(paramTable.getEditingRow(), paramTable + .getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int colSelected = paramTable.getSelectedColumn(); + if (colSelected == 0 || colSelected == 1) { + JOptionPane.showMessageDialog(null, + JMeterUtils.getResString("column_delete_disallowed"), // $NON-NLS-1$ + "Error", + JOptionPane.ERROR_MESSAGE); + return; + } + if (colSelected >= 0) { + tableModel.removeColumn(colSelected); + tableModel.fireTableDataChanged(); + + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getColumnCount() == 0) { + deleteColumnButton.setEnabled(false); + } + + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + else { + + if (colSelected >= tableModel.getColumnCount()) { + colSelected = colSelected - 1; + } + + paramTable.setColumnSelectionInterval(colSelected, colSelected); + } + setColumnWidths(); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/Connector.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Connector.java new file mode 100644 index 0000000..751280b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Connector.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.jmeter.monitor.model; + +public interface Connector { + ThreadInfo getThreadInfo(); + + void setThreadInfo(ThreadInfo value); + + RequestInfo getRequestInfo(); + + void setRequestInfo(RequestInfo value); + + Workers getWorkers(); + + void setWorkers(Workers value); + + String getName(); + + void setName(String value); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/ConnectorImpl.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/ConnectorImpl.java new file mode 100644 index 0000000..9d0b58b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/ConnectorImpl.java @@ -0,0 +1,71 @@ +/* + * 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.monitor.model; + +/** + * + * @version $Revision: 674365 $ + */ +public class ConnectorImpl implements Connector { + private ThreadInfo threadinfo = null; + + private RequestInfo requestinfo = null; + + private Workers workers = null; + + private String name = null; + + /** + * + */ + public ConnectorImpl() { + super(); + } + + public ThreadInfo getThreadInfo() { + return this.threadinfo; + } + + public void setThreadInfo(ThreadInfo value) { + this.threadinfo = value; + } + + public RequestInfo getRequestInfo() { + return this.requestinfo; + } + + public void setRequestInfo(RequestInfo value) { + this.requestinfo = value; + } + + public Workers getWorkers() { + return this.workers; + } + + public void setWorkers(Workers value) { + this.workers = value; + } + + public String getName() { + return this.name; + } + + public void setName(String value) { + this.name = value; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/Jvm.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Jvm.java new file mode 100644 index 0000000..2992b4b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Jvm.java @@ -0,0 +1,24 @@ +/* + * 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.monitor.model; + +public interface Jvm { + Memory getMemory(); + + void setMemory(Memory mem); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/JvmImpl.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/JvmImpl.java new file mode 100644 index 0000000..62b8bc4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/JvmImpl.java @@ -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.monitor.model; + +/** + * + * @version $Revision: 810036 $ + */ +public class JvmImpl implements Jvm { + private Memory memory = null; + + /** + * + */ + public JvmImpl() { + super(); + } + + /** {@inheritDoc} */ + public Memory getMemory() { + return this.memory; + } + + /** {@inheritDoc} */ + public void setMemory(Memory mem) { + this.memory = mem; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/Memory.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Memory.java new file mode 100644 index 0000000..05c6c82 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Memory.java @@ -0,0 +1,38 @@ +/* + * 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. + */ +/* + * Created on Mar 12, 2004 + */ +package org.apache.jmeter.monitor.model; + +/** + * @version $Revision: 674365 $ + */ +public interface Memory { + long getMax(); + + void setMax(long value); + + long getFree(); + + void setFree(long value); + + long getTotal(); + + void setTotal(long value); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/MemoryImpl.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/MemoryImpl.java new file mode 100644 index 0000000..bedd235 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/MemoryImpl.java @@ -0,0 +1,61 @@ +/* + * 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.monitor.model; + +/** + * + * @version $Revision: 674365 $ + */ +public class MemoryImpl implements Memory { + private long max = 0; + + private long free = 0; + + private long total = 0; + + /** + * + */ + public MemoryImpl() { + super(); + } + + public long getMax() { + return this.max; + } + + public void setMax(long value) { + this.max = value; + } + + public long getFree() { + return this.free; + } + + public void setFree(long value) { + this.free = value; + } + + public long getTotal() { + return this.total; + } + + public void setTotal(long value) { + this.total = value; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/ObjectFactory.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/ObjectFactory.java new file mode 100644 index 0000000..7ba7ced --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/ObjectFactory.java @@ -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. + */ +package org.apache.jmeter.monitor.model; + +// For unit tests, @see TestObjectFactory + +import org.apache.jmeter.monitor.parser.Parser; +import org.apache.jmeter.monitor.parser.ParserImpl; +import org.apache.jmeter.samplers.SampleResult; + +/** + * ObjectFactory is a simple factory class which creates new instances of + * objects. It also provides convienant method to parse XML status results. + */ +public class ObjectFactory { + + private static class ObjectFactoryHolder { + static final ObjectFactory FACTORY = new ObjectFactory(); + } + + private final Parser PARSER; + + /** + * + */ + protected ObjectFactory() { + super(); + PARSER = new MonitorParser(this); + } + + public static ObjectFactory getInstance() { + return ObjectFactoryHolder.FACTORY; + } + + public Status parseBytes(byte[] bytes) { + return PARSER.parseBytes(bytes); + } + + public Status parseString(String content) { + return PARSER.parseString(content); + } + + public Status parseSampleResult(SampleResult result) { + return PARSER.parseSampleResult(result); + } + + public Status createStatus() { + return new StatusImpl(); + } + + public Connector createConnector() { + return new ConnectorImpl(); + } + + public Jvm createJvm() { + return new JvmImpl(); + } + + public Memory createMemory() { + return new MemoryImpl(); + } + + public RequestInfo createRequestInfo() { + return new RequestInfoImpl(); + } + + public ThreadInfo createThreadInfo() { + return new ThreadInfoImpl(); + } + + public Worker createWorker() { + return new WorkerImpl(); + } + + public Workers createWorkers() { + return new WorkersImpl(); + } + + protected static class MonitorParser extends ParserImpl { + public MonitorParser(ObjectFactory factory) { + super(factory); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/RequestInfo.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/RequestInfo.java new file mode 100644 index 0000000..db8e545 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/RequestInfo.java @@ -0,0 +1,48 @@ +/* + * 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.monitor.model; + +/** + * @version $Revision: 674365 $ + */ +public interface RequestInfo { + long getBytesReceived(); + + void setBytesReceived(long value); + + long getBytesSent(); + + void setBytesSent(long value); + + long getRequestCount(); + + void setRequestCount(long value); + + long getErrorCount(); + + void setErrorCount(long value); + + int getMaxTime(); + + void setMaxTime(int value); + + int getProcessingTime(); + + void setProcessingTime(int value); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/RequestInfoImpl.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/RequestInfoImpl.java new file mode 100644 index 0000000..3c5d66f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/RequestInfoImpl.java @@ -0,0 +1,91 @@ +/* + * 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.monitor.model; + +/** + * + * @version $Revision: 674365 $ + */ +public class RequestInfoImpl implements RequestInfo { + private long bytesReceived = 0; + + private long bytesSent = 0; + + private long requestCount = 0; + + private long errorCount = 0; + + private int maxTime = 0; + + private int processingTime = 0; + + /** + * + */ + public RequestInfoImpl() { + super(); + } + + public long getBytesReceived() { + return this.bytesReceived; + } + + public void setBytesReceived(long value) { + this.bytesReceived = value; + } + + public long getBytesSent() { + return this.bytesSent; + } + + public void setBytesSent(long value) { + this.bytesSent = value; + } + + public long getRequestCount() { + return requestCount; + } + + public void setRequestCount(long value) { + this.requestCount = value; + } + + public long getErrorCount() { + return this.errorCount; + } + + public void setErrorCount(long value) { + this.errorCount = value; + } + + public int getMaxTime() { + return this.maxTime; + } + + public void setMaxTime(int value) { + this.maxTime = value; + } + + public int getProcessingTime() { + return this.processingTime; + } + + public void setProcessingTime(int value) { + this.processingTime = value; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/Status.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Status.java new file mode 100644 index 0000000..3b754d6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Status.java @@ -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.monitor.model; + +public interface Status { + Jvm getJvm(); + + void setJvm(Jvm vm); + + java.util.List getConnector(); + + void addConnector(Connector conn); + + void setConnectorPrefix(String prefix); + + String getConnectorPrefix(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/StatusImpl.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/StatusImpl.java new file mode 100644 index 0000000..b102a68 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/StatusImpl.java @@ -0,0 +1,67 @@ +/* + * 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.monitor.model; + +import java.util.LinkedList; +import java.util.List; + +/** + * + * @version $Revision: 1295112 $ + */ +public class StatusImpl implements Status { + private Jvm jvm = null; + + private String connectorPrefix = null; + + private final List connectors; + + /** + * + */ + public StatusImpl() { + super(); + connectors = new LinkedList(); + } + + /** {@inheritDoc} */ + public Jvm getJvm() { + return jvm; + } + + /** {@inheritDoc} */ + public void setJvm(Jvm vm) { + this.jvm = vm; + } + + /** {@inheritDoc} */ + public List getConnector() { + return this.connectors; + } + + public void addConnector(Connector conn) { + this.connectors.add(conn); + } + + public void setConnectorPrefix(String prefix) { + connectorPrefix = prefix; + } + + public String getConnectorPrefix(){ + return connectorPrefix; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/ThreadInfo.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/ThreadInfo.java new file mode 100644 index 0000000..cec9766 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/ThreadInfo.java @@ -0,0 +1,44 @@ +/* + * 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.monitor.model; + +/** + * @version $Revision: 674365 $ + */ +public interface ThreadInfo { + int getMaxSpareThreads(); + + void setMaxSpareThreads(int value); + + int getMinSpareThreads(); + + void setMinSpareThreads(int value); + + int getMaxThreads(); + + void setMaxThreads(int value); + + int getCurrentThreadsBusy(); + + void setCurrentThreadsBusy(int value); + + int getCurrentThreadCount(); + + void setCurrentThreadCount(int value); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/ThreadInfoImpl.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/ThreadInfoImpl.java new file mode 100644 index 0000000..7543d28 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/ThreadInfoImpl.java @@ -0,0 +1,81 @@ +/* + * 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.monitor.model; + +/** + * + * @version $Revision: 674365 $ + */ +public class ThreadInfoImpl implements ThreadInfo { + private int maxSpareThreads = 0; + + private int minSpareThreads = 0; + + private int maxThreads = 0; + + private int currentThreadCount = 0; + + private int currentThreadsBusy = 0; + + /** + * + */ + public ThreadInfoImpl() { + super(); + } + + public int getMaxSpareThreads() { + return this.maxSpareThreads; + } + + public void setMaxSpareThreads(int value) { + this.maxSpareThreads = value; + } + + public int getMinSpareThreads() { + return this.minSpareThreads; + } + + public void setMinSpareThreads(int value) { + this.minSpareThreads = value; + } + + public int getMaxThreads() { + return this.maxThreads; + } + + public void setMaxThreads(int value) { + this.maxThreads = value; + } + + public int getCurrentThreadsBusy() { + return this.currentThreadsBusy; + } + + public void setCurrentThreadsBusy(int value) { + this.currentThreadsBusy = value; + } + + public int getCurrentThreadCount() { + return this.currentThreadCount; + } + + public void setCurrentThreadCount(int value) { + this.currentThreadCount = value; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/Worker.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Worker.java new file mode 100644 index 0000000..6697993 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Worker.java @@ -0,0 +1,64 @@ +/* + * 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.monitor.model; + +/** + * @version $Revision: 674365 $ + */ +public interface Worker { + int getRequestProcessingTime(); + + void setRequestProcessingTime(int value); + + long getRequestBytesSent(); + + void setRequestBytesSent(long value); + + java.lang.String getCurrentQueryString(); + + void setCurrentQueryString(java.lang.String value); + + java.lang.String getRemoteAddr(); + + void setRemoteAddr(java.lang.String value); + + java.lang.String getCurrentUri(); + + void setCurrentUri(java.lang.String value); + + java.lang.String getStage(); + + void setStage(java.lang.String value); + + java.lang.String getVirtualHost(); + + void setVirtualHost(java.lang.String value); + + java.lang.String getProtocol(); + + void setProtocol(java.lang.String value); + + long getRequestBytesReceived(); + + void setRequestBytesReceived(long value); + + java.lang.String getMethod(); + + void setMethod(java.lang.String value); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/WorkerImpl.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/WorkerImpl.java new file mode 100644 index 0000000..e6756fa --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/WorkerImpl.java @@ -0,0 +1,130 @@ +/* + * 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.monitor.model; + +/** + * + */ +public class WorkerImpl implements Worker { + private int requestProcessingTime = 0; + + private long requestBytesSent = 0; + + private long requestBytesReceived = 0; + + private String currentQueryString = null; + + private String remoteAddr = null; + + private String currentUri = null; + + private String method = null; + + private String protocol = null; + + private String stage = null; + + private String virtualHost = null; + + /** + * + */ + public WorkerImpl() { + super(); + } + + public int getRequestProcessingTime() { + return this.requestProcessingTime; + } + + public void setRequestProcessingTime(int value) { + this.requestProcessingTime = value; + } + + public long getRequestBytesSent() { + return this.requestBytesSent; + } + + public void setRequestBytesSent(long value) { + this.requestBytesSent = value; + } + + public String getCurrentQueryString() { + return this.currentQueryString; + } + + public void setCurrentQueryString(String value) { + this.currentQueryString = value; + } + + public String getRemoteAddr() { + return this.remoteAddr; + } + + public void setRemoteAddr(String value) { + this.remoteAddr = value; + } + + public String getCurrentUri() { + return this.currentUri; + } + + public void setCurrentUri(String value) { + this.currentUri = value; + } + + public String getStage() { + return this.stage; + } + + public void setStage(String value) { + this.stage = value; + } + + public String getVirtualHost() { + return this.virtualHost; + } + + public void setVirtualHost(String value) { + this.virtualHost = value; + } + + public String getProtocol() { + return this.protocol; + } + + public void setProtocol(String value) { + this.protocol = value; + } + + public long getRequestBytesReceived() { + return this.requestBytesReceived; + } + + public void setRequestBytesReceived(long value) { + this.requestBytesReceived = value; + } + + public String getMethod() { + return this.method; + } + + public void setMethod(String value) { + this.method = value; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/Workers.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Workers.java new file mode 100644 index 0000000..616091b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/Workers.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.monitor.model; + +import java.util.List; + +/** + * @version $Revision: 805173 $ + */ +public interface Workers { + + List getWorker(); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/model/WorkersImpl.java b/ApacheJmeter/src/org/apache/jmeter/monitor/model/WorkersImpl.java new file mode 100644 index 0000000..6dfbfa4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/model/WorkersImpl.java @@ -0,0 +1,44 @@ +/* + * 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.monitor.model; + +import java.util.LinkedList; +import java.util.List; + +/** + * + * @version $Revision: 805173 $ + */ +public class WorkersImpl implements Workers { + private final List worker; + + /** + * + */ + public WorkersImpl() { + super(); + worker = new LinkedList(); + } + + public List getWorker() { + return worker; + } + + public void addWorker(Worker value) { + this.worker.add(value); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/parser/Constants.java b/ApacheJmeter/src/org/apache/jmeter/monitor/parser/Constants.java new file mode 100644 index 0000000..09fe311 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/parser/Constants.java @@ -0,0 +1,90 @@ +/* + * 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.monitor.parser; + +/** + * Constants for the custom DocumentHandler. + * + * @version $Revision: 674365 $ + */ +public class Constants { + public static final String STATUS = "status"; + + public static final String JVM = "jvm"; + + public static final String CONNECTOR = "connector"; + + public static final String MEMORY = "memory"; + + public static final String THREADINFO = "threadInfo"; + + public static final String REQUESTINFO = "requestInfo"; + + public static final String WORKER = "worker"; + + public static final String WORKERS = "workers"; + + public static final String MEMORY_FREE = "free"; + + public static final String MEMORY_TOTAL = "total"; + + public static final String MEMORY_MAX = "max"; + + public static final String ATTRIBUTE_NAME = "name"; + + public static final String MAXTHREADS = "maxThreads"; + + public static final String MINSPARETHREADS = "minSpareThreads"; + + public static final String MAXSPARETHREADS = "maxSpareThreads"; + + public static final String CURRENTTHREADCOUNT = "currentThreadCount"; + + public static final String CURRENTBUSYTHREADS = "currentThreadsBusy"; + + public static final String MAXTIME = "maxTime="; + + public static final String PROCESSINGTIME = "processingTime="; + + public static final String REQUESTCOUNT = "requestCount"; + + public static final String ERRORCOUNT = "errorCount="; + + public static final String BYTESRECEIVED = "bytesReceived"; + + public static final String BYTESSENT = "bytesSent"; + + public static final String STAGE = "stage"; + + public static final String REQUESTPROCESSINGTIME = "requestProcessingTime="; + + public static final String REQUESTBYTESSENT = "requestBytesSent"; + + public static final String REQUESTBYTESRECEIVED = "requestBytesReceived"; + + public static final String REMOTEADDR = "remoteAddr"; + + public static final String VIRTUALHOST = "virtualHost"; + + public static final String METHOD = "method"; + + public static final String CURRENTURI = "currentUri"; + + public static final String CURRENTQUERYSTRING = "currentQueryString"; + + public static final String PROTOCOL = "protocol"; +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/parser/MonitorHandler.java b/ApacheJmeter/src/org/apache/jmeter/monitor/parser/MonitorHandler.java new file mode 100644 index 0000000..da11676 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/parser/MonitorHandler.java @@ -0,0 +1,364 @@ +/* + * 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.monitor.parser; + +// import java.util.List; +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.jmeter.monitor.model.ObjectFactory; +import org.apache.jmeter.monitor.model.Connector; +import org.apache.jmeter.monitor.model.Jvm; +import org.apache.jmeter.monitor.model.Memory; +import org.apache.jmeter.monitor.model.RequestInfo; +import org.apache.jmeter.monitor.model.Status; +import org.apache.jmeter.monitor.model.ThreadInfo; +import org.apache.jmeter.monitor.model.Worker; +import org.apache.jmeter.monitor.model.Workers; +import org.apache.jmeter.monitor.model.WorkersImpl; + +public class MonitorHandler extends DefaultHandler { + // private boolean startDoc = false; + // private boolean endDoc = false; + private final ObjectFactory factory; + + private Stack stacktree; + + private Status status; + + private Jvm jvm; + + private Memory memory; + + private Connector connector; + + private ThreadInfo threadinfo; + + private RequestInfo requestinfo; + + private Worker worker; + + private Workers workers; + + // private List workerslist; + + /** + * + */ + public MonitorHandler(ObjectFactory factory) { + super(); + this.factory = factory; + } + + @Override + public void startDocument() throws SAXException { + // this.startDoc = true; + // Reset all work variables so reusing the instance starts afresh. + this.stacktree = new Stack(); + this.status = null; + this.jvm = null; + this.memory = null; + this.connector = null; + this.threadinfo = null; + this.requestinfo = null; + this.worker = null; + this.workers = null; + } + + /** {@inheritDoc} */ + @Override + public void endDocument() throws SAXException { + // this.startDoc = false; + // this.endDoc = true; + } + + /** + * Receive notification of the start of an element. + * + *

+ * By default, do nothing. Application writers may override this method in a + * subclass to take specific actions at the start of each element (such as + * allocating a new tree node or writing output to a file). + *

+ * + * @param uri + * @param localName + * The element type name. + * @param qName + * @param attributes + * The specified or defaulted attributes. + * @exception org.xml.sax.SAXException + * Any SAX exception, possibly wrapping another exception. + * @see org.xml.sax.ContentHandler#startElement + */ + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals(Constants.STATUS)) { + status = factory.createStatus(); + stacktree.push(status); + } else if (qName.equals(Constants.JVM)) { + jvm = factory.createJvm(); + if (stacktree.peek() instanceof Status) { + status.setJvm(jvm); + stacktree.push(jvm); + } + } else if (qName.equals(Constants.MEMORY)) { + memory = factory.createMemory(); + if (stacktree.peek() instanceof Jvm) { + stacktree.push(memory); + if (attributes != null) { + for (int idx = 0; idx < attributes.getLength(); idx++) { + String attr = attributes.getQName(idx); + if (attr.equals(Constants.MEMORY_FREE)) { + memory.setFree(parseLong(attributes.getValue(idx))); + } else if (attr.equals(Constants.MEMORY_TOTAL)) { + memory.setTotal(parseLong(attributes.getValue(idx))); + } else if (attr.equals(Constants.MEMORY_MAX)) { + memory.setMax(parseLong(attributes.getValue(idx))); + } + } + } + jvm.setMemory(memory); + } + } else if (qName.equals(Constants.CONNECTOR)) { + connector = factory.createConnector(); + if (stacktree.peek() instanceof Status || stacktree.peek() instanceof Connector) { + status.addConnector(connector); + stacktree.push(connector); + if (attributes != null) { + for (int idx = 0; idx < attributes.getLength(); idx++) { + String attr = attributes.getQName(idx); + if (attr.equals(Constants.ATTRIBUTE_NAME)) { + connector.setName(attributes.getValue(idx)); + } + } + } + } + } else if (qName.equals(Constants.THREADINFO)) { + threadinfo = factory.createThreadInfo(); + if (stacktree.peek() instanceof Connector) { + stacktree.push(threadinfo); + connector.setThreadInfo(threadinfo); + if (attributes != null) { + for (int idx = 0; idx < attributes.getLength(); idx++) { + String attr = attributes.getQName(idx); + if (attr.equals(Constants.MAXTHREADS)) { + threadinfo.setMaxThreads(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.MINSPARETHREADS)) { + threadinfo.setMinSpareThreads(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.MAXSPARETHREADS)) { + threadinfo.setMaxSpareThreads(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.CURRENTTHREADCOUNT)) { + threadinfo.setCurrentThreadCount(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.CURRENTBUSYTHREADS)) { + threadinfo.setCurrentThreadsBusy(parseInt(attributes.getValue(idx))); + } + } + } + } + } else if (qName.equals(Constants.REQUESTINFO)) { + requestinfo = factory.createRequestInfo(); + if (stacktree.peek() instanceof Connector) { + stacktree.push(requestinfo); + connector.setRequestInfo(requestinfo); + if (attributes != null) { + for (int idx = 0; idx < attributes.getLength(); idx++) { + String attr = attributes.getQName(idx); + if (attr.equals(Constants.MAXTIME)) { + requestinfo.setMaxTime(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.PROCESSINGTIME)) { + requestinfo.setProcessingTime(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.REQUESTCOUNT)) { + requestinfo.setRequestCount(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.ERRORCOUNT)) { + requestinfo.setErrorCount(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.BYTESRECEIVED)) { + requestinfo.setBytesReceived(parseLong(attributes.getValue(idx))); + } else if (attr.equals(Constants.BYTESSENT)) { + requestinfo.setBytesSent(parseLong(attributes.getValue(idx))); + } + } + } + } + } else if (qName.equals(Constants.WORKERS)) { + workers = factory.createWorkers(); + if (stacktree.peek() instanceof Connector) { + connector.setWorkers(workers); + stacktree.push(workers); + } + } else if (qName.equals(Constants.WORKER)) { + worker = factory.createWorker(); + if (stacktree.peek() instanceof Workers || stacktree.peek() instanceof Worker) { + stacktree.push(worker); + ((WorkersImpl) workers).addWorker(worker); + if (attributes != null) { + for (int idx = 0; idx < attributes.getLength(); idx++) { + String attr = attributes.getQName(idx); + if (attr.equals(Constants.STAGE)) { + worker.setStage(attributes.getValue(idx)); + } else if (attr.equals(Constants.REQUESTPROCESSINGTIME)) { + worker.setRequestProcessingTime(parseInt(attributes.getValue(idx))); + } else if (attr.equals(Constants.REQUESTBYTESSENT)) { + worker.setRequestBytesSent(parseLong(attributes.getValue(idx))); + } else if (attr.equals(Constants.REQUESTBYTESRECEIVED)) { + worker.setRequestBytesReceived(parseLong(attributes.getValue(idx))); + } else if (attr.equals(Constants.REMOTEADDR)) { + worker.setRemoteAddr(attributes.getValue(idx)); + } else if (attr.equals(Constants.VIRTUALHOST)) { + worker.setVirtualHost(attributes.getValue(idx)); + } else if (attr.equals(Constants.METHOD)) { + worker.setMethod(attributes.getValue(idx)); + } else if (attr.equals(Constants.CURRENTURI)) { + worker.setCurrentUri(attributes.getValue(idx)); + } else if (attr.equals(Constants.CURRENTQUERYSTRING)) { + worker.setCurrentQueryString(attributes.getValue(idx)); + } else if (attr.equals(Constants.PROTOCOL)) { + worker.setProtocol(attributes.getValue(idx)); + } + } + } + } + } + } + + /** + * Receive notification of the end of an element. + * + *

+ * By default, do nothing. Application writers may override this method in a + * subclass to take specific actions at the end of each element (such as + * finalising a tree node or writing output to a file). + *

+ * + * @param uri + * @param localName + * The element type name. + * @param qName + * The specified or defaulted attributes. + * @exception org.xml.sax.SAXException + * Any SAX exception, possibly wrapping another exception. + * @see org.xml.sax.ContentHandler#endElement + */ + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equals(Constants.STATUS)) { + if (stacktree.peek() instanceof Status) { + stacktree.pop(); + } + } else if (qName.equals(Constants.JVM)) { + if (stacktree.peek() instanceof Jvm) { + stacktree.pop(); + } + } else if (qName.equals(Constants.MEMORY)) { + if (stacktree.peek() instanceof Memory) { + stacktree.pop(); + } + } else if (qName.equals(Constants.CONNECTOR)) { + if (stacktree.peek() instanceof Connector || stacktree.peek() instanceof Connector) { + stacktree.pop(); + } + } else if (qName.equals(Constants.THREADINFO)) { + if (stacktree.peek() instanceof ThreadInfo) { + stacktree.pop(); + } + } else if (qName.equals(Constants.REQUESTINFO)) { + if (stacktree.peek() instanceof RequestInfo) { + stacktree.pop(); + } + } else if (qName.equals(Constants.WORKERS)) { + if (stacktree.peek() instanceof Workers) { + stacktree.pop(); + } + } else if (qName.equals(Constants.WORKER)) { + if (stacktree.peek() instanceof Worker || stacktree.peek() instanceof Worker) { + stacktree.pop(); + } + } + } + + /** + * Receive notification of character data inside an element. + * + *

+ * By default, do nothing. Application writers may override this method to + * take specific actions for each chunk of character data (such as adding + * the data to a node or buffer, or printing it to a file). + *

+ * + * @param ch + * The characters. + * @param start + * The start position in the character array. + * @param length + * The number of characters to use from the character array. + * @exception org.xml.sax.SAXException + * Any SAX exception, possibly wrapping another exception. + * @see org.xml.sax.ContentHandler#characters + */ + @Override + public void characters(char ch[], int start, int length) throws SAXException { + } + + /** + * Convienance method for parsing long. If the string was not a number, the + * method returns zero. + * + * @param data + * @return the value as a long + */ + public long parseLong(String data) { + long val = 0; + if (data.length() > 0) { + try { + val = Long.parseLong(data); + } catch (NumberFormatException e) { + val = 0; + } + } + return val; + } + + /** + * Convienance method for parsing integers. + * + * @param data + * @return the value as an integer + */ + public int parseInt(String data) { + int val = 0; + if (data.length() > 0) { + try { + val = Integer.parseInt(data); + } catch (NumberFormatException e) { + val = 0; + } + } + return val; + } + + /** + * method returns the status object. + * + * @return the status + */ + public Status getContents() { + return this.status; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/parser/Parser.java b/ApacheJmeter/src/org/apache/jmeter/monitor/parser/Parser.java new file mode 100644 index 0000000..a314c73 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/parser/Parser.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.jmeter.monitor.parser; + +import org.apache.jmeter.monitor.model.Status; +import org.apache.jmeter.samplers.SampleResult; + +public interface Parser { + Status parseBytes(byte[] bytes); + + Status parseString(String content); + + Status parseSampleResult(SampleResult result); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/parser/ParserImpl.java b/ApacheJmeter/src/org/apache/jmeter/monitor/parser/ParserImpl.java new file mode 100644 index 0000000..e7929e9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/parser/ParserImpl.java @@ -0,0 +1,119 @@ +/* + * 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.monitor.parser; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringReader; + +import org.xml.sax.SAXException; +import org.xml.sax.InputSource; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.apache.jmeter.monitor.model.ObjectFactory; +import org.apache.jmeter.monitor.model.Status; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public abstract class ParserImpl implements Parser { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final SAXParser PARSER; + + private final MonitorHandler DOCHANDLER; + + private final ObjectFactory FACTORY; + + /** + * + */ + public ParserImpl(ObjectFactory factory) { + super(); + this.FACTORY = factory; + SAXParser parser = null; + MonitorHandler handler = null; + try { + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + parser = parserFactory.newSAXParser(); + handler = new MonitorHandler(this.FACTORY); + } catch (SAXException e) { + log.error("Failed to create the parser",e); + } catch (ParserConfigurationException e) { + log.error("Failed to create the parser",e); + } + PARSER = parser; + DOCHANDLER = handler; + } + + /** + * parse byte array and return Status object + * + * @param bytes + * @return Status + */ + public Status parseBytes(byte[] bytes) { + try { + InputSource is = new InputSource(); + is.setByteStream(new ByteArrayInputStream(bytes)); + PARSER.parse(is, DOCHANDLER); + return DOCHANDLER.getContents(); + } catch (SAXException e) { + log.error("Failed to parse the bytes",e); + // let bad input fail silently + return DOCHANDLER.getContents(); + } catch (IOException e) { // Should never happen + log.error("Failed to read the bytes",e); + // let bad input fail silently + return DOCHANDLER.getContents(); + } + } + + /** + * @param content + * @return Status + */ + public Status parseString(String content) { + try { + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(content)); + PARSER.parse(is, DOCHANDLER); + return DOCHANDLER.getContents(); + } catch (SAXException e) { + log.error("Failed to parse the String",e); + // let bad input fail silently + return DOCHANDLER.getContents(); + } catch (IOException e) { // Should never happen + log.error("Failed to read the String",e); + // let bad input fail silently + return DOCHANDLER.getContents(); + } + } + + /** + * @param result + * @return Status + */ + public Status parseSampleResult(SampleResult result) { + return parseBytes(result.getResponseData()); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/util/MemoryBenchmark.java b/ApacheJmeter/src/org/apache/jmeter/monitor/util/MemoryBenchmark.java new file mode 100644 index 0000000..b298a9b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/util/MemoryBenchmark.java @@ -0,0 +1,130 @@ +/* + * 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.monitor.util; + +import java.util.List; +import java.util.LinkedList; + +import org.apache.jmeter.monitor.model.Connector; +import org.apache.jmeter.monitor.model.Jvm; +import org.apache.jmeter.monitor.model.Memory; +import org.apache.jmeter.monitor.model.ObjectFactory; +import org.apache.jmeter.monitor.model.RequestInfo; +import org.apache.jmeter.monitor.model.Status; +import org.apache.jmeter.monitor.model.ThreadInfo; +import org.apache.jmeter.monitor.model.Worker; +import org.apache.jmeter.monitor.model.Workers; +import org.apache.jmeter.visualizers.MonitorModel; +import org.apache.jmeter.visualizers.MonitorStats; + +/** + * + * @version $Revision: 984221 $ + */ +public class MemoryBenchmark { + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static void main(String[] args) { + if (args.length == 1) { + int objects = 10000; + if (args[0] != null) { + objects = Integer.parseInt(args[0]); + } + List objs = new LinkedList(); + ObjectFactory of = ObjectFactory.getInstance(); + + long bfree = Runtime.getRuntime().freeMemory(); + long btotal = Runtime.getRuntime().totalMemory(); + long bmax = Runtime.getRuntime().maxMemory(); + System.out.println("Before we create objects:"); + System.out.println("------------------------------"); + System.out.println("free: " + bfree); + System.out.println("total: " + btotal); + System.out.println("max: " + bmax); + + for (int idx = 0; idx < objects; idx++) { + Connector cnn = of.createConnector(); + Workers wkrs = of.createWorkers(); + for (int idz = 0; idz < 26; idz++) { + Worker wk0 = of.createWorker(); + wk0.setCurrentQueryString("/manager/status"); + wk0.setCurrentUri("http://localhost/manager/status"); + wk0.setMethod("GET"); + wk0.setProtocol("http"); + wk0.setRemoteAddr("?"); + wk0.setRequestBytesReceived(132); + wk0.setRequestBytesSent(18532); + wk0.setStage("K"); + wk0.setVirtualHost("?"); + wkrs.getWorker().add(wk0); + } + cnn.setWorkers(wkrs); + + RequestInfo rqinfo = of.createRequestInfo(); + rqinfo.setBytesReceived(0); + rqinfo.setBytesSent(434374); + rqinfo.setErrorCount(10); + rqinfo.setMaxTime(850); + rqinfo.setProcessingTime(2634); + rqinfo.setRequestCount(1002); + cnn.setRequestInfo(rqinfo); + + ThreadInfo thinfo = of.createThreadInfo(); + thinfo.setCurrentThreadCount(50); + thinfo.setCurrentThreadsBusy(12); + thinfo.setMaxSpareThreads(50); + thinfo.setMaxThreads(150); + thinfo.setMinSpareThreads(10); + cnn.setThreadInfo(thinfo); + + Jvm vm = of.createJvm(); + Memory mem = of.createMemory(); + mem.setFree(77280); + mem.setTotal(134210000); + mem.setMax(134217728); + vm.setMemory(mem); + + Status st = of.createStatus(); + st.setJvm(vm); + st.getConnector().add(cnn); + + MonitorStats mstats = new MonitorStats(Stats.calculateStatus(st), Stats.calculateLoad(st), 0, Stats + .calculateMemoryLoad(st), Stats.calculateThreadLoad(st), "localhost", "8080", "http", System + .currentTimeMillis()); + MonitorModel monmodel = new MonitorModel(mstats); + objs.add(monmodel); + } + long afree = Runtime.getRuntime().freeMemory(); + long atotal = Runtime.getRuntime().totalMemory(); + long amax = Runtime.getRuntime().maxMemory(); + long delta = ((atotal - afree) - (btotal - bfree)); + System.out.println("After we create objects:"); + System.out.println("------------------------------"); + System.out.println("free: " + afree); + System.out.println("total: " + atotal); + System.out.println("max: " + amax); + System.out.println("------------------------------"); + System.out.println("delta: " + (delta / 1024) + " kilobytes"); + System.out.println("delta: " + (delta / 1024 / 1024) + " megabytes"); + System.out.println("number of objects: " + objects); + System.out.println("potential number of servers: " + (objects / 1000)); + + } else { + System.out.println("Please provide the number of objects"); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/monitor/util/Stats.java b/ApacheJmeter/src/org/apache/jmeter/monitor/util/Stats.java new file mode 100644 index 0000000..a177d5a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/monitor/util/Stats.java @@ -0,0 +1,191 @@ +/* + * 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.monitor.util; + +import org.apache.jmeter.monitor.model.Connector; +import org.apache.jmeter.monitor.model.Status; + +/** + * + * Description: + *

+ * Stats is responsible for calculating the load and health of a given server. + * It uses tomcat's status servlet results. A schema was generated for the XML + * output and JAXB was used to generate classes. + *

+ * The equations are: + *

+ * memory weight = (int)(50 * (free/max))
+ * thread weight = (int)(50 * (current/max)) + *

+ * The load factors are stored in the properties files. Simply change the values + * in the properties to change how load is calculated. The defaults values are + * memory (50) and threads (50). The sum of the factors must equal 100. + */ +public class Stats { + + public static final int DEAD = 0; + + public static final int ACTIVE = 2; + + public static final int WARNING = 1; + + public static final int HEALTHY = 3; + + public static final int DEFAULT_MEMORY_FACTOR = 50; + + public static final int DEFAULT_THREAD_FACTOR = 50; + + public static final double HEALTHY_PER = 0.00; + + public static final double ACTIVE_PER = 0.25; + + public static final double WARNING_PER = 0.67; + + /** + * The method is responsible for taking a status object and calculating an + * int value from 1 to 100. We use a combination of free memory and free + * threads. The current factor is 50/50. + *

+ * + * @param stat + * @return calculated load value + */ + public static int calculateLoad(Status stat) { + if (stat != null) { + // equation for calculating the weight + // w = (int)(33 * (used/max)) + long totMem = stat.getJvm().getMemory().getTotal(); + long freeMem = stat.getJvm().getMemory().getFree(); + long usedMem = totMem - freeMem; + double memdiv = (double) usedMem / (double) totMem; + double memWeight = DEFAULT_MEMORY_FACTOR * memdiv; + + // changed the logic for BEA Weblogic in the case a + // user uses Tomcat's status servlet without any + // modifications. Weblogic will return nothing for + // the connector, therefore we need to check the size + // of the list. Peter 12.22.04 + double threadWeight = 0; + if (stat.getConnector().size() > 0) { + Connector cntr = fetchConnector(stat); + int maxThread = cntr.getThreadInfo().getMaxThreads(); + int curThread = cntr.getThreadInfo().getCurrentThreadsBusy(); + double thdiv = (double) curThread / (double) maxThread; + threadWeight = DEFAULT_THREAD_FACTOR * thdiv; + } + return (int) (memWeight + threadWeight); + } else { + return 0; + } + } + + /** + * Method should calculate if the server is: dead, active, warning or + * healthy. We do this by looking at the current busy threads. + *

    + *
  1. free > spare is healthy + *
  2. free < spare is active + *
  3. busy threads > 75% is warning + *
  4. none of the above is dead + *
+ * + * @param stat + * @return integer representing the status + */ + public static int calculateStatus(Status stat) { + if (stat != null && stat.getConnector().size() > 0) { + Connector cntr = fetchConnector(stat); + int max = cntr.getThreadInfo().getMaxThreads(); + int current = cntr.getThreadInfo().getCurrentThreadsBusy(); + // int spare = cntr.getThreadInfo().getMaxSpareThreads(); + double per = (double) current / (double) max; + if (per > WARNING_PER) { + return WARNING; + } else if (per >= ACTIVE_PER && per <= WARNING_PER) { + return ACTIVE; + } else if (per < ACTIVE_PER && per >= HEALTHY_PER) { + return HEALTHY; + } else { + return DEAD; + } + } else { + return DEAD; + } + } + + /** + * Method will calculate the memory load: used / max = load. The load value + * is an integer between 1 and 100. It is the percent memory used. Changed + * this to be more like other system monitors. Peter Lin 2-11-05 + * + * @param stat + * @return memory load + */ + public static int calculateMemoryLoad(Status stat) { + double load = 0; + if (stat != null) { + double total = stat.getJvm().getMemory().getTotal(); + double free = stat.getJvm().getMemory().getFree(); + double used = total - free; + load = (used / total); + } + return (int) (load * 100); + } + + /** + * Method will calculate the thread load: busy / max = load. The value is an + * integer between 1 and 100. It is the percent busy. + * + * @param stat + * @return thread load + */ + public static int calculateThreadLoad(Status stat) { + int load = 0; + if (stat != null && stat.getConnector().size() > 0) { + Connector cntr = fetchConnector(stat); + double max = cntr.getThreadInfo().getMaxThreads(); + double current = cntr.getThreadInfo().getCurrentThreadsBusy(); + load = (int) ((current / max) * 100); + } + return load; + } + + /** + * Method to get connector to use for calculate server status + * + * @param stat + * @return connector + */ + private static Connector fetchConnector(Status stat) { + Connector cntr = null; + String connectorPrefix = stat.getConnectorPrefix(); + if (connectorPrefix != null && connectorPrefix.length() > 0) { + // loop to fetch desired connector + for (int i = 0; i < stat.getConnector().size(); i++) { + cntr = stat.getConnector().get(i); + if (cntr.getName().startsWith(connectorPrefix)) { + return cntr; + } + } + } + // default : get first connector + cntr = stat.getConnector().get(0); + return cntr; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/plugin/JMeterPlugin.java b/ApacheJmeter/src/org/apache/jmeter/plugin/JMeterPlugin.java new file mode 100644 index 0000000..3f55d1d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/plugin/JMeterPlugin.java @@ -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.plugin; + +public interface JMeterPlugin { + public String[][] getIconMappings(); + + public String[][] getResourceBundles(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/plugin/PluginManager.java b/ApacheJmeter/src/org/apache/jmeter/plugin/PluginManager.java new file mode 100644 index 0000000..672a3e4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/plugin/PluginManager.java @@ -0,0 +1,73 @@ +/* + * 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.plugin; + +import java.net.URL; + +import javax.swing.ImageIcon; + +import org.apache.jmeter.gui.GUIFactory; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public final class PluginManager { + private static final PluginManager instance = new PluginManager(); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private PluginManager() { + } + + /** + * Installs a plugin. + * + * @param plugin + * the plugin to install + * @param useGui + * indication of whether or not the gui will be used + */ + public static void install(JMeterPlugin plugin, boolean useGui) { + if (useGui) { + instance.installPlugin(plugin); + } + } + + private void installPlugin(JMeterPlugin plugin) { + String[][] icons = plugin.getIconMappings(); + ClassLoader classloader = plugin.getClass().getClassLoader(); + + for (int i = 0; i < icons.length; i++) { + URL resource = classloader.getResource(icons[i][1].trim()); + + if (resource == null) { + log.warn("Can't find icon for " + icons[i][0] + " - " + icons[i][1]); + } else { + GUIFactory.registerIcon(icons[i][0], new ImageIcon(resource)); + if (icons[i].length > 2 && icons[i][2] != null) { + URL resource2 = classloader.getResource(icons[i][2].trim()); + if (resource2 == null) { + log.info("Can't find disabled icon for " + icons[i][0] + " - " + icons[i][2]); + } else { + GUIFactory.registerDisabledIcon(icons[i][0], new ImageIcon(resource2)); + } + } + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/processor/PostProcessor.java b/ApacheJmeter/src/org/apache/jmeter/processor/PostProcessor.java new file mode 100644 index 0000000..4ea7da4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/processor/PostProcessor.java @@ -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.processor; + +/** + * The PostProcessor is activated after a sample result has been generated. + * + * @version $Revision: 674365 $ + */ +public interface PostProcessor { + /** + * Provides the PostProcessor with a SampleResult object from which to + * extract values for use in future Queries. + */ + public void process(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/processor/PreProcessor.java b/ApacheJmeter/src/org/apache/jmeter/processor/PreProcessor.java new file mode 100644 index 0000000..bf61ef4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/processor/PreProcessor.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.jmeter.processor; + +/** + * PreProcessors are executed just prior to a sample being run. + * + * @version $Revision: 674365 $ + */ +public interface PreProcessor { + public void process(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/processor/gui/AbstractPostProcessorGui.java b/ApacheJmeter/src/org/apache/jmeter/processor/gui/AbstractPostProcessorGui.java new file mode 100644 index 0000000..597fec4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/processor/gui/AbstractPostProcessorGui.java @@ -0,0 +1,44 @@ +/* + * 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.processor.gui; + +import java.util.Arrays; +import java.util.Collection; + +import org.apache.jmeter.gui.AbstractScopedJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which manage PostProcessors. + * + * PostProcessors which can be applied to different scopes (parent, children or both) + * need to use the createScopePanel() to add the panel to the GUI, and they also + * need to use saveScopeSettings() and showScopeSettings() to keep the test element + * and GUI in synch. + * + */ +public abstract class AbstractPostProcessorGui extends AbstractScopedJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.POST_PROCESSORS }); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/processor/gui/AbstractPreProcessorGui.java b/ApacheJmeter/src/org/apache/jmeter/processor/gui/AbstractPreProcessorGui.java new file mode 100644 index 0000000..531ffca --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/processor/gui/AbstractPreProcessorGui.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.processor.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +public abstract class AbstractPreProcessorGui extends AbstractJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultExtractorMenu(); + } + + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.PRE_PROCESSORS }); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ftp/config/gui/FtpConfigGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ftp/config/gui/FtpConfigGui.java new file mode 100644 index 0000000..ab30885 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ftp/config/gui/FtpConfigGui.java @@ -0,0 +1,241 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ftp.config.gui; + +import java.awt.BorderLayout; + +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.ftp.sampler.FTPSampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class FtpConfigGui extends AbstractConfigGui { + + private static final long serialVersionUID = 240L; + + private JTextField server; + + private JTextField port; + + private JTextField remoteFile; + + private JTextField localFile; + + private JTextArea inputData; + + private JCheckBox binaryMode; + + private JCheckBox saveResponseData; + + private boolean displayName = true; + + private JRadioButton getBox; + + private JRadioButton putBox; + + public FtpConfigGui() { + this(true); + } + + public FtpConfigGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + public String getLabelResource() { + return "ftp_sample_title"; // $NON-NLS-1$ + } + + @Override + public void configure(TestElement element) { + super.configure(element); // TODO - should this be done for embedded usage? + // Note: the element is a ConfigTestElement when used standalone, so we cannot use FTPSampler access methods + server.setText(element.getPropertyAsString(FTPSampler.SERVER)); + port.setText(element.getPropertyAsString(FTPSampler.PORT)); + remoteFile.setText(element.getPropertyAsString(FTPSampler.REMOTE_FILENAME)); + localFile.setText(element.getPropertyAsString(FTPSampler.LOCAL_FILENAME)); + inputData.setText(element.getPropertyAsString(FTPSampler.INPUT_DATA)); + binaryMode.setSelected(element.getPropertyAsBoolean(FTPSampler.BINARY_MODE, false)); + saveResponseData.setSelected(element.getPropertyAsBoolean(FTPSampler.SAVE_RESPONSE, false)); + final boolean uploading = element.getPropertyAsBoolean(FTPSampler.UPLOAD_FILE,false); + if (uploading){ + putBox.setSelected(true); + } else { + getBox.setSelected(true); + } + } + + public TestElement createTestElement() { + ConfigTestElement element = new ConfigTestElement(); + modifyTestElement(element); + return element; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement element) { + configureTestElement(element); + // Note: the element is a ConfigTestElement, so cannot use FTPSampler access methods + element.setProperty(FTPSampler.SERVER,server.getText()); + element.setProperty(FTPSampler.PORT,port.getText()); + element.setProperty(FTPSampler.REMOTE_FILENAME,remoteFile.getText()); + element.setProperty(FTPSampler.LOCAL_FILENAME,localFile.getText()); + element.setProperty(FTPSampler.INPUT_DATA,inputData.getText()); + element.setProperty(FTPSampler.BINARY_MODE,binaryMode.isSelected()); + element.setProperty(FTPSampler.SAVE_RESPONSE, saveResponseData.isSelected()); + element.setProperty(FTPSampler.UPLOAD_FILE,putBox.isSelected()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + server.setText(""); //$NON-NLS-1$ + port.setText(""); //$NON-NLS-1$ + remoteFile.setText(""); //$NON-NLS-1$ + localFile.setText(""); //$NON-NLS-1$ + inputData.setText(""); //$NON-NLS-1$ + binaryMode.setSelected(false); + saveResponseData.setSelected(false); + getBox.setSelected(true); + putBox.setSelected(false); + } + + private JPanel createServerPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("server")); //$NON-NLS-1$ + + server = new JTextField(10); + label.setLabelFor(server); + + JPanel serverPanel = new JPanel(new BorderLayout(5, 0)); + serverPanel.add(label, BorderLayout.WEST); + serverPanel.add(server, BorderLayout.CENTER); + return serverPanel; + } + + private JPanel getPortPanel() { + port = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_port")); // $NON-NLS-1$ + label.setLabelFor(port); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(port, BorderLayout.CENTER); + + return panel; + } + + private JPanel createLocalFilenamePanel() { + JLabel label = new JLabel(JMeterUtils.getResString("ftp_local_file")); //$NON-NLS-1$ + + localFile = new JTextField(10); + label.setLabelFor(localFile); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(localFile, BorderLayout.CENTER); + return filenamePanel; + } + + private JPanel createLocalFileContentsPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("ftp_local_file_contents")); //$NON-NLS-1$ + + inputData = new JTextArea(); + label.setLabelFor(inputData); + + JPanel contentsPanel = new JPanel(new BorderLayout(5, 0)); + contentsPanel.add(label, BorderLayout.WEST); + contentsPanel.add(inputData, BorderLayout.CENTER); + return contentsPanel; + } + + private JPanel createRemoteFilenamePanel() { + JLabel label = new JLabel(JMeterUtils.getResString("ftp_remote_file")); //$NON-NLS-1$ + + remoteFile = new JTextField(10); + label.setLabelFor(remoteFile); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(remoteFile, BorderLayout.CENTER); + return filenamePanel; + } + + private JPanel createOptionsPanel(){ + + ButtonGroup group = new ButtonGroup(); + + getBox = new JRadioButton(JMeterUtils.getResString("ftp_get")); //$NON-NLS-1$ + group.add(getBox); + getBox.setSelected(true); + + putBox = new JRadioButton(JMeterUtils.getResString("ftp_put")); //$NON-NLS-1$ + group.add(putBox); + + binaryMode = new JCheckBox(JMeterUtils.getResString("ftp_binary_mode")); //$NON-NLS-1$ + saveResponseData = new JCheckBox(JMeterUtils.getResString("ftp_save_response_data")); //$NON-NLS-1$ + + + JPanel optionsPanel = new HorizontalPanel(); + optionsPanel.add(getBox); + optionsPanel.add(putBox); + optionsPanel.add(binaryMode); + optionsPanel.add(saveResponseData); + return optionsPanel; + } + private void init() { + setLayout(new BorderLayout(0, 5)); + + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + + // MAIN PANEL + VerticalPanel mainPanel = new VerticalPanel(); + JPanel serverPanel = new HorizontalPanel(); + serverPanel.add(createServerPanel(), BorderLayout.CENTER); + serverPanel.add(getPortPanel(), BorderLayout.EAST); + mainPanel.add(serverPanel); + mainPanel.add(createRemoteFilenamePanel()); + mainPanel.add(createLocalFilenamePanel()); + mainPanel.add(createLocalFileContentsPanel()); + mainPanel.add(createOptionsPanel()); + + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ftp/control/gui/FtpTestSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ftp/control/gui/FtpTestSamplerGui.java new file mode 100644 index 0000000..7431459 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ftp/control/gui/FtpTestSamplerGui.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ftp.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; + +import org.apache.jmeter.config.gui.LoginConfigGui; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.ftp.config.gui.FtpConfigGui; +import org.apache.jmeter.protocol.ftp.sampler.FTPSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class FtpTestSamplerGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + private LoginConfigGui loginPanel; + + private FtpConfigGui ftpDefaultPanel; + + public FtpTestSamplerGui() { + init(); + } + + @Override + public void configure(TestElement element) { + super.configure(element); + loginPanel.configure(element); + ftpDefaultPanel.configure(element); + } + + public TestElement createTestElement() { + FTPSampler sampler = new FTPSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement sampler) { + sampler.clear(); + ftpDefaultPanel.modifyTestElement(sampler); + loginPanel.modifyTestElement(sampler); + this.configureTestElement(sampler); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + ftpDefaultPanel.clearGui(); + loginPanel.clearGui(); + } + + public String getLabelResource() { + return "ftp_testing_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + VerticalPanel mainPanel = new VerticalPanel(); + + ftpDefaultPanel = new FtpConfigGui(false); + mainPanel.add(ftpDefaultPanel); + + loginPanel = new LoginConfigGui(false); + loginPanel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("login_config"))); // $NON-NLS-1$ + mainPanel.add(loginPanel); + + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ftp/sampler/FTPSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ftp/sampler/FTPSampler.java new file mode 100644 index 0000000..0e4023e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ftp/sampler/FTPSampler.java @@ -0,0 +1,317 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ftp.sampler; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.apache.commons.io.output.TeeOutputStream; +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPReply; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A sampler which understands FTP file requests. + * + */ +public class FTPSampler extends AbstractSampler implements Interruptible { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.LoginConfigGui", + "org.apache.jmeter.protocol.ftp.config.gui.FtpConfigGui", + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + public final static String SERVER = "FTPSampler.server"; // $NON-NLS-1$ + + public final static String PORT = "FTPSampler.port"; // $NON-NLS-1$ + + // N.B. Originally there was only one filename, and only get(RETR) was supported + // To maintain backwards compatibility, the property name needs to remain the same + public final static String REMOTE_FILENAME = "FTPSampler.filename"; // $NON-NLS-1$ + + public final static String LOCAL_FILENAME = "FTPSampler.localfilename"; // $NON-NLS-1$ + + public final static String INPUT_DATA = "FTPSampler.inputdata"; // $NON-NLS-1$ + + // Use binary mode file transfer? + public final static String BINARY_MODE = "FTPSampler.binarymode"; // $NON-NLS-1$ + + // Are we uploading? + public final static String UPLOAD_FILE = "FTPSampler.upload"; // $NON-NLS-1$ + + // Should the file data be saved in the response? + public final static String SAVE_RESPONSE = "FTPSampler.saveresponse"; // $NON-NLS-1$ + + private transient volatile FTPClient savedClient; // used for interrupting the sampler + + public FTPSampler() { + } + + public String getUsername() { + return getPropertyAsString(ConfigTestElement.USERNAME); + } + + public String getPassword() { + return getPropertyAsString(ConfigTestElement.PASSWORD); + } + + public void setServer(String newServer) { + this.setProperty(SERVER, newServer); + } + + public String getServer() { + return getPropertyAsString(SERVER); + } + + public void setPort(String newPort) { + this.setProperty(PORT, newPort, ""); // $NON-NLS-1$ + } + + public String getPort() { + return getPropertyAsString(PORT, ""); // $NON-NLS-1$ + } + + public int getPortAsInt() { + return getPropertyAsInt(PORT, 0); + } + + public String getRemoteFilename() { + return getPropertyAsString(REMOTE_FILENAME); + } + + public String getLocalFilename() { + return getPropertyAsString(LOCAL_FILENAME); + } + + private String getLocalFileContents() { + return getPropertyAsString(INPUT_DATA); + } + + public boolean isBinaryMode(){ + return getPropertyAsBoolean(BINARY_MODE,false); + } + + public boolean isSaveResponse(){ + return getPropertyAsBoolean(SAVE_RESPONSE,false); + } + + public boolean isUpload(){ + return getPropertyAsBoolean(UPLOAD_FILE,false); + } + + + /** + * Returns a formatted string label describing this sampler Example output: + * ftp://ftp.nowhere.com/pub/README.txt + * + * @return a formatted string label describing this sampler + */ + public String getLabel() { + StringBuilder sb = new StringBuilder(); + sb.append("ftp://");// $NON-NLS-1$ + sb.append(getServer()); + String port = getPort(); + if (port.length() > 0){ + sb.append(':'); + sb.append(port); + } + sb.append("/");// $NON-NLS-1$ + sb.append(getRemoteFilename()); + sb.append(isBinaryMode() ? " (Binary) " : " (Ascii) ");// $NON-NLS-1$ $NON-NLS-2$ + sb.append(isUpload() ? " <- " : " -> "); // $NON-NLS-1$ $NON-NLS-2$ + sb.append(getLocalFilename()); + return sb.toString(); + } + + public SampleResult sample(Entry e) { + SampleResult res = new SampleResult(); + res.setSuccessful(false); // Assume failure + String remote = getRemoteFilename(); + String local = getLocalFilename(); + boolean binaryTransfer = isBinaryMode(); + res.setSampleLabel(getName()); + final String label = getLabel(); + res.setSamplerData(label); + try { + res.setURL(new URL(label)); + } catch (MalformedURLException e1) { + log.warn("Cannot set URL: "+e1.getLocalizedMessage()); + } + InputStream input = null; + OutputStream output = null; + + res.sampleStart(); + FTPClient ftp = new FTPClient(); + try { + savedClient = ftp; + final int port = getPortAsInt(); + if (port > 0){ + ftp.connect(getServer(),port); + } else { + ftp.connect(getServer()); + } + res.latencyEnd(); + int reply = ftp.getReplyCode(); + if (FTPReply.isPositiveCompletion(reply)) + { + if (ftp.login( getUsername(), getPassword())){ + if (binaryTransfer) { + ftp.setFileType(FTP.BINARY_FILE_TYPE); + } + ftp.enterLocalPassiveMode();// should probably come from the setup dialog + boolean ftpOK=false; + if (isUpload()) { + String contents=getLocalFileContents(); + if (contents.length() > 0){ + byte bytes[] = contents.getBytes(); // TODO - charset? + input = new ByteArrayInputStream(bytes); + res.setBytes(bytes.length); + } else { + File infile = new File(local); + res.setBytes((int)infile.length()); + input = new FileInputStream(infile); + } + ftpOK = ftp.storeFile(remote, input); + } else { + final boolean saveResponse = isSaveResponse(); + ByteArrayOutputStream baos=null; // No need to close this + OutputStream target=null; // No need to close this + if (saveResponse){ + baos = new ByteArrayOutputStream(); + target=baos; + } + if (local.length()>0){ + output=new FileOutputStream(local); + if (target==null) { + target=output; + } else { + target = new TeeOutputStream(output,baos); + } + } + if (target == null){ + target=new NullOutputStream(); + } + input = ftp.retrieveFileStream(remote); + if (input == null){// Could not access file or other error + res.setResponseCode(Integer.toString(ftp.getReplyCode())); + res.setResponseMessage(ftp.getReplyString()); + } else { + long bytes = IOUtils.copy(input,target); + ftpOK = bytes > 0; + if (saveResponse && baos != null){ + res.setResponseData(baos.toByteArray()); + if (!binaryTransfer) { + res.setDataType(SampleResult.TEXT); + } + } else { + res.setBytes((int) bytes); + } + } + } + + if (ftpOK) { + res.setResponseCodeOK(); + res.setResponseMessageOK(); + res.setSuccessful(true); + } else { + res.setResponseCode(Integer.toString(ftp.getReplyCode())); + res.setResponseMessage(ftp.getReplyString()); + } + } else { + res.setResponseCode(Integer.toString(ftp.getReplyCode())); + res.setResponseMessage(ftp.getReplyString()); + } + } else { + res.setResponseCode("501"); // TODO + res.setResponseMessage("Could not connect"); + //res.setResponseCode(Integer.toString(ftp.getReplyCode())); + res.setResponseMessage(ftp.getReplyString()); + } + } catch (IOException ex) { + res.setResponseCode("000"); // TODO + res.setResponseMessage(ex.toString()); + } finally { + savedClient = null; + if (ftp.isConnected()) { + try { + ftp.logout(); + } catch (IOException ignored) { + } + try { + ftp.disconnect(); + } catch (IOException ignored) { + } + } + IOUtils.closeQuietly(input); + IOUtils.closeQuietly(output); + } + + res.sampleEnd(); + return res; + } + + /** {@inheritDoc} */ + public boolean interrupt() { + FTPClient client = savedClient; + if (client != null) { + savedClient = null; + try { + client.abort(); + client.disconnect(); + } catch (IOException ignored) { + } + } + return client != null; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/MultipartUrlConfig.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/MultipartUrlConfig.java new file mode 100644 index 0000000..7cfb75d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/MultipartUrlConfig.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.config; + +import java.io.Serializable; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPFileArgs; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * Configuration element which handles HTTP Parameters and files to be uploaded + */ +public class MultipartUrlConfig implements Serializable { + + private static final long serialVersionUID = 240L; + + private final String boundary; + + private final Arguments args; + + /** + * HTTPFileArgs list to be uploaded with http request. + */ + private final HTTPFileArgs files; + + /** + * @deprecated only for use by unit tests + */ + @Deprecated + public MultipartUrlConfig(){ + this(null); + } + + // called by HttpRequestHdr + public MultipartUrlConfig(String boundary) { + args = new Arguments(); + files = new HTTPFileArgs(); + this.boundary = boundary; + } + + public String getBoundary() { + return boundary; + } + + public Arguments getArguments() { + return args; + } + + public void addArgument(String name, String value) { + Arguments myArgs = this.getArguments(); + myArgs.addArgument(new HTTPArgument(name, value)); + } + + public void addArgument(String name, String value, String metadata) { + Arguments myArgs = this.getArguments(); + myArgs.addArgument(new HTTPArgument(name, value, metadata)); + } + + public HTTPFileArgs getHTTPFileArgs() { + return files; + } + +// NOT USED +// /** +// * @deprecated values in a multipart/form-data are not urlencoded, +// * so it does not make sense to add a value as a encoded value +// */ +// public void addEncodedArgument(String name, String value) { +// Arguments myArgs = getArguments(); +// HTTPArgument arg = new HTTPArgument(name, value, true); +// if (arg.getName().equals(arg.getEncodedName()) && arg.getValue().equals(arg.getEncodedValue())) { +// arg.setAlwaysEncoded(false); +// } +// myArgs.addArgument(arg); +// } + + /** + * Add a value that is not URL encoded, and make sure it + * appears in the GUI that it will not be encoded when + * the request is sent. + * + * @param name + * @param value + */ + private void addNonEncodedArgument(String name, String value) { + Arguments myArgs = getArguments(); + // The value is not encoded + HTTPArgument arg = new HTTPArgument(name, value, false); + // Let the GUI show that it will not be encoded + arg.setAlwaysEncoded(false); + myArgs.addArgument(arg); + } + + /** + * This method allows a proxy server to send over the raw text from a + * browser's output stream to be parsed and stored correctly into the + * UrlConfig object. + */ + public void parseArguments(String queryString) { + String[] parts = JOrphanUtils.split(queryString, "--" + getBoundary()); //$NON-NLS-1$ + for (int i = 0; i < parts.length; i++) { + String contentDisposition = getHeaderValue("Content-disposition", parts[i]); //$NON-NLS-1$ + String contentType = getHeaderValue("Content-type", parts[i]); //$NON-NLS-1$ + // Check if it is form data + if (contentDisposition != null && contentDisposition.indexOf("form-data") > -1) { //$NON-NLS-1$ + // Get the form field name + final String namePrefix = "name=\""; //$NON-NLS-1$ + int index = contentDisposition.indexOf(namePrefix) + namePrefix.length(); + String name = contentDisposition.substring(index, contentDisposition.indexOf("\"", index)); //$NON-NLS-1$ + + // Check if it is a file being uploaded + final String filenamePrefix = "filename=\""; //$NON-NLS-1$ + if (contentDisposition.indexOf(filenamePrefix) > -1) { + // Get the filename + index = contentDisposition.indexOf(filenamePrefix) + filenamePrefix.length(); + String path = contentDisposition.substring(index, contentDisposition.indexOf("\"", index)); //$NON-NLS-1$ + if(path != null && path.length() > 0) { + // Set the values retrieved for the file upload + files.addHTTPFileArg(path, name, contentType); + } + } + else { + // Find the first empty line of the multipart, it signals end of headers for multipart + // Agents are supposed to terminate lines in CRLF: + final String CRLF = "\r\n"; + final String CRLFCRLF = "\r\n\r\n"; + // Code also allows for LF only (not sure why - perhaps because the test code uses it?) + final String LF = "\n"; + final String LFLF = "\n\n"; + int indexEmptyCrLfCrLfLinePos = parts[i].indexOf(CRLFCRLF); //$NON-NLS-1$ + int indexEmptyLfLfLinePos = parts[i].indexOf(LFLF); //$NON-NLS-1$ + String value = null; + if(indexEmptyCrLfCrLfLinePos > -1) {// CRLF blank line found + value = parts[i].substring(indexEmptyCrLfCrLfLinePos+CRLFCRLF.length(),parts[i].lastIndexOf(CRLF)); + } else if(indexEmptyLfLfLinePos > -1) { // LF blank line found + value = parts[i].substring(indexEmptyLfLfLinePos+LFLF.length(),parts[i].lastIndexOf(LF)); + } + this.addNonEncodedArgument(name, value); + } + } + } + } + + private String getHeaderValue(String headerName, String multiPart) { + String regularExpression = headerName + "\\s*:\\s*(.*)$"; //$NON-NLS-1$ + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + Pattern pattern = JMeterUtils.getPattern(regularExpression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.MULTILINE_MASK); + if(localMatcher.contains(multiPart, pattern)) { + return localMatcher.getMatch().group(1).trim(); + } + else { + return null; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java new file mode 100644 index 0000000..297f885 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.config.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.JMeterUtils; + +public class HttpDefaultsGui extends AbstractConfigGui { + + private static final long serialVersionUID = 240L; + + private JCheckBox imageParser; + + private JCheckBox concurrentDwn; + + private JTextField concurrentPool; + + private UrlConfigGui urlConfig; + + public HttpDefaultsGui() { + super(); + init(); + } + + public String getLabelResource() { + return "url_config_title"; // $NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + ConfigTestElement config = new ConfigTestElement(); + modifyTestElement(config); + return config; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement config) { + ConfigTestElement cfg = (ConfigTestElement ) config; + ConfigTestElement el = (ConfigTestElement) urlConfig.createTestElement(); + cfg.clear(); // need to clear because the + cfg.addConfigElement(el); + super.configureTestElement(config); + if (imageParser.isSelected()) { + config.setProperty(new BooleanProperty(HTTPSamplerBase.IMAGE_PARSER, true)); + enableConcurrentDwn(true); + } else { + config.removeProperty(HTTPSamplerBase.IMAGE_PARSER); + enableConcurrentDwn(false); + } + if (concurrentDwn.isSelected()) { + config.setProperty(new BooleanProperty(HTTPSamplerBase.CONCURRENT_DWN, true)); + } else { + // The default is false, so we can remove the property to simplify JMX files + // This also allows HTTPDefaults to work for this checkbox + config.removeProperty(HTTPSamplerBase.CONCURRENT_DWN); + } + if(!StringUtils.isEmpty(concurrentPool.getText())) { + config.setProperty(new StringProperty(HTTPSamplerBase.CONCURRENT_POOL, + concurrentPool.getText())); + } else { + config.setProperty(new StringProperty(HTTPSamplerBase.CONCURRENT_POOL, + String.valueOf(HTTPSamplerBase.CONCURRENT_POOL_SIZE))); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + urlConfig.clear(); + imageParser.setSelected(false); + concurrentDwn.setSelected(false); + concurrentPool.setText(String.valueOf(HTTPSamplerBase.CONCURRENT_POOL_SIZE)); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + urlConfig.configure(el); + imageParser.setSelected(((AbstractTestElement) el).getPropertyAsBoolean(HTTPSamplerBase.IMAGE_PARSER)); + concurrentDwn.setSelected(((AbstractTestElement) el).getPropertyAsBoolean(HTTPSamplerBase.CONCURRENT_DWN)); + concurrentPool.setText(((AbstractTestElement) el).getPropertyAsString(HTTPSamplerBase.CONCURRENT_POOL)); + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + urlConfig = new UrlConfigGui(false, true, false); + add(urlConfig, BorderLayout.CENTER); + + // OPTIONAL TASKS + final JPanel optionalTasksPanel = new HorizontalPanel(); + optionalTasksPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("optional_tasks"))); // $NON-NLS-1$ + + final JPanel checkBoxPanel = new HorizontalPanel(); + imageParser = new JCheckBox(JMeterUtils.getResString("web_testing_retrieve_images")); // $NON-NLS-1$ + checkBoxPanel.add(imageParser); + imageParser.addItemListener(new ItemListener() { + public void itemStateChanged(final ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { enableConcurrentDwn(true); } + else { enableConcurrentDwn(false); } + } + }); + // Concurrent resources download + concurrentDwn = new JCheckBox(JMeterUtils.getResString("web_testing_concurrent_download")); // $NON-NLS-1$ + concurrentDwn.addItemListener(new ItemListener() { + public void itemStateChanged(final ItemEvent e) { + if (imageParser.isSelected() && e.getStateChange() == ItemEvent.SELECTED) { concurrentPool.setEnabled(true); } + else { concurrentPool.setEnabled(false); } + } + }); + concurrentPool = new JTextField(2); // 2 columns size + concurrentPool.setMaximumSize(new Dimension(30,20)); + checkBoxPanel.add(concurrentDwn); + checkBoxPanel.add(concurrentPool); + optionalTasksPanel.add(checkBoxPanel); + add(optionalTasksPanel, BorderLayout.SOUTH); + } + + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + + private void enableConcurrentDwn(final boolean enable) { + if (enable) { + concurrentDwn.setEnabled(true); + if (concurrentDwn.isSelected()) { + concurrentPool.setEnabled(true); + } + } else { + concurrentDwn.setEnabled(false); + concurrentPool.setEnabled(false); + } + } + + public void itemStateChanged(final ItemEvent event) { + if (event.getStateChange() == ItemEvent.SELECTED) { + enableConcurrentDwn(true); + } else { + enableConcurrentDwn(false); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/MultipartUrlConfigGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/MultipartUrlConfigGui.java new file mode 100644 index 0000000..64c78b8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/MultipartUrlConfigGui.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.config.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JPanel; + +import org.apache.jmeter.protocol.http.gui.HTTPFileArgsPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class MultipartUrlConfigGui extends UrlConfigGui { + + private static final long serialVersionUID = 240L; + + /** + * Files panel that holds file informations to be uploaded by + * http request. + */ + private HTTPFileArgsPanel filesPanel; + + // used by HttpTestSampleGui + public MultipartUrlConfigGui() { + super(); + init(); + } + + // not currently used + public MultipartUrlConfigGui(boolean showSamplerFields) { + super(showSamplerFields); + init(); + } + + public MultipartUrlConfigGui(boolean showSamplerFields, boolean showImplementation) { + super(showSamplerFields, showImplementation, true); + init(); + } + + @Override + public void modifyTestElement(TestElement sampler) { + super.modifyTestElement(sampler); + filesPanel.modifyTestElement(sampler); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + filesPanel.configure(el); + } + + private void init() {// called from ctor, so must not be overridable + this.setLayout(new BorderLayout()); + + // WEB REQUEST PANEL + JPanel webRequestPanel = new JPanel(); + webRequestPanel.setLayout(new BorderLayout()); + webRequestPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_request"))); // $NON-NLS-1$ + + JPanel northPanel = new JPanel(); + northPanel.setLayout(new BoxLayout(northPanel, BoxLayout.Y_AXIS)); + northPanel.add(getProtocolAndMethodPanel()); + northPanel.add(getPathPanel()); + + webRequestPanel.add(northPanel, BorderLayout.NORTH); + webRequestPanel.add(getParameterPanel(), BorderLayout.CENTER); + webRequestPanel.add(getHTTPFileArgsPanel(), BorderLayout.SOUTH); + + this.add(getWebServerTimeoutPanel(), BorderLayout.NORTH); + this.add(webRequestPanel, BorderLayout.CENTER); + this.add(getProxyServerPanel(), BorderLayout.SOUTH); + } + + private JPanel getHTTPFileArgsPanel() { + filesPanel = new HTTPFileArgsPanel(JMeterUtils.getResString("send_file")); // $NON-NLS-1$ + return filesPanel; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + super.clear(); + filesPanel.clear(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java new file mode 100644 index 0000000..41a1faf --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java @@ -0,0 +1,744 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.config.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.JLabeledTextArea; + +/** + * Basic URL / HTTP Request configuration: + * - host and port + * - connect and response timeouts + * - path, method, encoding, parameters + * - redirects & keepalive + */ +public class UrlConfigGui extends JPanel implements ChangeListener { + + private static final long serialVersionUID = 240L; + + private static final int TAB_PARAMETERS = 0; + + private static final int TAB_RAW_BODY = 1; + + private HTTPArgumentsPanel argsPanel; + + private JTextField domain; + + private JTextField port; + + private JTextField proxyHost; + + private JTextField proxyPort; + + private JTextField proxyUser; + + private JPasswordField proxyPass; + + private JTextField connectTimeOut; + + private JTextField responseTimeOut; + + private JTextField protocol; + + private JTextField contentEncoding; + + private JTextField path; + + private JCheckBox followRedirects; + + private JCheckBox autoRedirects; + + private JCheckBox useKeepAlive; + + private JCheckBox useMultipartForPost; + + private JCheckBox useBrowserCompatibleMultipartMode; + + private JLabeledChoice method; + + private JLabeledChoice httpImplementation; + + private final boolean notConfigOnly; + // set this false to suppress some items for use in HTTP Request defaults + + private final boolean showImplementation; // Set false for AJP + + // Raw POST Body + private JLabeledTextArea postBodyContent; + + // Tabbed pane that contains parameters and raw body + private ValidationTabbedPane postContentTabbedPane; + + private boolean showRawBodyPane; + + public UrlConfigGui() { + this(true); + } + + /** + * @param showSamplerFields + */ + public UrlConfigGui(boolean showSamplerFields) { + this(showSamplerFields, true, true); + } + + /** + * @param showSamplerFields + * @param showImplementation Show HTTP Implementation + * @param showRawBodyPane + */ + public UrlConfigGui(boolean showSamplerFields, boolean showImplementation, boolean showRawBodyPane) { + notConfigOnly=showSamplerFields; + this.showImplementation = showImplementation; + this.showRawBodyPane = showRawBodyPane; + init(); + } + + public void clear() { + domain.setText(""); // $NON-NLS-1$ + if (notConfigOnly){ + followRedirects.setSelected(true); + autoRedirects.setSelected(false); + method.setText(HTTPSamplerBase.DEFAULT_METHOD); + useKeepAlive.setSelected(true); + useMultipartForPost.setSelected(false); + useBrowserCompatibleMultipartMode.setSelected(HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT); + } + if (showImplementation) { + httpImplementation.setText(""); // $NON-NLS-1$ + } + path.setText(""); // $NON-NLS-1$ + port.setText(""); // $NON-NLS-1$ + proxyHost.setText(""); // $NON-NLS-1$ + proxyPort.setText(""); // $NON-NLS-1$ + proxyUser.setText(""); // $NON-NLS-1$ + proxyPass.setText(""); // $NON-NLS-1$ + connectTimeOut.setText(""); // $NON-NLS-1$ + responseTimeOut.setText(""); // $NON-NLS-1$ + protocol.setText(""); // $NON-NLS-1$ + contentEncoding.setText(""); // $NON-NLS-1$ + argsPanel.clear(); + if(showRawBodyPane) { + postBodyContent.setText("");// $NON-NLS-1$ + } + postContentTabbedPane.setSelectedIndex(TAB_PARAMETERS, false); + } + + public TestElement createTestElement() { + ConfigTestElement element = new ConfigTestElement(); + + element.setName(this.getName()); + element.setProperty(TestElement.GUI_CLASS, this.getClass().getName()); + element.setProperty(TestElement.TEST_CLASS, element.getClass().getName()); + modifyTestElement(element); + return element; + } + + /** + * Save the GUI values in the sampler. + * + * @param element + */ + public void modifyTestElement(TestElement element) { + boolean useRaw = postContentTabbedPane.getSelectedIndex()==TAB_RAW_BODY; + Arguments args; + if(useRaw) { + args = new Arguments(); + String text = postBodyContent.getText(); + /* + * Textfield uses \n (LF) to delimit lines; we need to send CRLF. + * Rather than change the way that arguments are processed by the + * samplers for raw data, it is easier to fix the data. + * On retrival, CRLF is converted back to LF for storage in the text field. + * See + */ + HTTPArgument arg = new HTTPArgument("", text.replaceAll("\n","\r\n"), false); + arg.setAlwaysEncoded(false); + args.addArgument(arg); + } else { + args = (Arguments) argsPanel.createTestElement(); + HTTPArgument.convertArgumentsToHTTP(args); + } + element.setProperty(HTTPSamplerBase.POST_BODY_RAW, useRaw, HTTPSamplerBase.POST_BODY_RAW_DEFAULT); + element.setProperty(new TestElementProperty(HTTPSamplerBase.ARGUMENTS, args)); + element.setProperty(HTTPSamplerBase.DOMAIN, domain.getText()); + element.setProperty(HTTPSamplerBase.PORT, port.getText()); + element.setProperty(HTTPSamplerBase.PROXYHOST, proxyHost.getText(),""); + element.setProperty(HTTPSamplerBase.PROXYPORT, proxyPort.getText(),""); + element.setProperty(HTTPSamplerBase.PROXYUSER, proxyUser.getText(),""); + element.setProperty(HTTPSamplerBase.PROXYPASS, String.valueOf(proxyPass.getPassword()),""); + element.setProperty(HTTPSamplerBase.CONNECT_TIMEOUT, connectTimeOut.getText()); + element.setProperty(HTTPSamplerBase.RESPONSE_TIMEOUT, responseTimeOut.getText()); + element.setProperty(HTTPSamplerBase.PROTOCOL, protocol.getText()); + element.setProperty(HTTPSamplerBase.CONTENT_ENCODING, contentEncoding.getText()); + element.setProperty(HTTPSamplerBase.PATH, path.getText()); + if (notConfigOnly){ + element.setProperty(HTTPSamplerBase.METHOD, method.getText()); + element.setProperty(new BooleanProperty(HTTPSamplerBase.FOLLOW_REDIRECTS, followRedirects.isSelected())); + element.setProperty(new BooleanProperty(HTTPSamplerBase.AUTO_REDIRECTS, autoRedirects.isSelected())); + element.setProperty(new BooleanProperty(HTTPSamplerBase.USE_KEEPALIVE, useKeepAlive.isSelected())); + element.setProperty(new BooleanProperty(HTTPSamplerBase.DO_MULTIPART_POST, useMultipartForPost.isSelected())); + element.setProperty(HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART, useBrowserCompatibleMultipartMode.isSelected(),HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT); + } + if (showImplementation) { + element.setProperty(HTTPSamplerBase.IMPLEMENTATION, httpImplementation.getText(),""); + } + } + + // FIXME FACTOR WITH HTTPHC4Impl, HTTPHC3Impl + // Just append all the parameter values, and use that as the post body + /** + * Compute Post body from arguments + * @param arguments {@link Arguments} + * @return {@link String} + */ + private static final String computePostBody(Arguments arguments) { + return computePostBody(arguments, false); + } + + /** + * Compute Post body from arguments + * @param arguments {@link Arguments} + * @param crlfToLF whether to convert CRLF to LF + * @return {@link String} + */ + private static final String computePostBody(Arguments arguments, boolean crlfToLF) { + StringBuilder postBody = new StringBuilder(); + PropertyIterator args = arguments.iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String value = arg.getValue(); + if (crlfToLF) { + value=value.replaceAll("\r\n", "\n"); // See modifyTestElement + } + postBody.append(value); + } + return postBody.toString(); + } + + /** + * Set the text, etc. in the UI. + * + * @param el + * contains the data to be displayed + */ + public void configure(TestElement el) { + setName(el.getName()); + Arguments arguments = (Arguments) el.getProperty(HTTPSamplerBase.ARGUMENTS).getObjectValue(); + + boolean useRaw = el.getPropertyAsBoolean(HTTPSamplerBase.POST_BODY_RAW, HTTPSamplerBase.POST_BODY_RAW_DEFAULT); + if(useRaw) { + String postBody = computePostBody(arguments, true); // Convert CRLF to CR, see modifyTestElement + postBodyContent.setText(postBody); + postContentTabbedPane.setSelectedIndex(TAB_RAW_BODY, false); + } else { + argsPanel.configure(arguments); + postContentTabbedPane.setSelectedIndex(TAB_PARAMETERS, false); + } + + domain.setText(el.getPropertyAsString(HTTPSamplerBase.DOMAIN)); + + String portString = el.getPropertyAsString(HTTPSamplerBase.PORT); + + // Only display the port number if it is meaningfully specified + if (portString.equals(HTTPSamplerBase.UNSPECIFIED_PORT_AS_STRING)) { + port.setText(""); // $NON-NLS-1$ + } else { + port.setText(portString); + } + proxyHost.setText(el.getPropertyAsString(HTTPSamplerBase.PROXYHOST)); + proxyPort.setText(el.getPropertyAsString(HTTPSamplerBase.PROXYPORT)); + proxyUser.setText(el.getPropertyAsString(HTTPSamplerBase.PROXYUSER)); + proxyPass.setText(el.getPropertyAsString(HTTPSamplerBase.PROXYPASS)); + connectTimeOut.setText(el.getPropertyAsString(HTTPSamplerBase.CONNECT_TIMEOUT)); + responseTimeOut.setText(el.getPropertyAsString(HTTPSamplerBase.RESPONSE_TIMEOUT)); + protocol.setText(el.getPropertyAsString(HTTPSamplerBase.PROTOCOL)); + contentEncoding.setText(el.getPropertyAsString(HTTPSamplerBase.CONTENT_ENCODING)); + path.setText(el.getPropertyAsString(HTTPSamplerBase.PATH)); + if (notConfigOnly){ + method.setText(el.getPropertyAsString(HTTPSamplerBase.METHOD)); + followRedirects.setSelected(el.getPropertyAsBoolean(HTTPSamplerBase.FOLLOW_REDIRECTS)); + autoRedirects.setSelected(el.getPropertyAsBoolean(HTTPSamplerBase.AUTO_REDIRECTS)); + useKeepAlive.setSelected(el.getPropertyAsBoolean(HTTPSamplerBase.USE_KEEPALIVE)); + useMultipartForPost.setSelected(el.getPropertyAsBoolean(HTTPSamplerBase.DO_MULTIPART_POST)); + useBrowserCompatibleMultipartMode.setSelected(el.getPropertyAsBoolean( + HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART, HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT)); + } + if (showImplementation) { + httpImplementation.setText(el.getPropertyAsString(HTTPSamplerBase.IMPLEMENTATION)); + } + } + + private void init() {// called from ctor, so must not be overridable + this.setLayout(new BorderLayout()); + + // WEB REQUEST PANEL + JPanel webRequestPanel = new JPanel(); + webRequestPanel.setLayout(new BorderLayout()); + webRequestPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_request"))); // $NON-NLS-1$ + + JPanel northPanel = new JPanel(); + northPanel.setLayout(new BoxLayout(northPanel, BoxLayout.Y_AXIS)); + northPanel.add(getProtocolAndMethodPanel()); + northPanel.add(getPathPanel()); + + webRequestPanel.add(northPanel, BorderLayout.NORTH); + webRequestPanel.add(getParameterPanel(), BorderLayout.CENTER); + + this.add(getWebServerTimeoutPanel(), BorderLayout.NORTH); + this.add(webRequestPanel, BorderLayout.CENTER); + this.add(getProxyServerPanel(), BorderLayout.SOUTH); + } + + /** + * Create a panel containing the webserver (domain+port) and timeouts (connect+request). + * + * @return the panel + */ + protected final JPanel getWebServerTimeoutPanel() { + // WEB SERVER PANEL + JPanel webServerPanel = new HorizontalPanel(); + webServerPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_server"))); // $NON-NLS-1$ + final JPanel domainPanel = getDomainPanel(); + final JPanel portPanel = getPortPanel(); + webServerPanel.add(domainPanel, BorderLayout.CENTER); + webServerPanel.add(portPanel, BorderLayout.EAST); + + JPanel timeOut = new HorizontalPanel(); + timeOut.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_server_timeout_title"))); // $NON-NLS-1$ + final JPanel connPanel = getConnectTimeOutPanel(); + final JPanel reqPanel = getResponseTimeOutPanel(); + timeOut.add(connPanel); + timeOut.add(reqPanel); + + JPanel webServerTimeoutPanel = new VerticalPanel(); + webServerTimeoutPanel.add(webServerPanel, BorderLayout.CENTER); + webServerTimeoutPanel.add(timeOut, BorderLayout.EAST); + + JPanel bigPanel = new VerticalPanel(); + bigPanel.add(webServerTimeoutPanel); + return bigPanel; + } + + /** + * Create a panel containing the proxy server details + * + * @return the panel + */ + protected final JPanel getProxyServerPanel(){ + JPanel proxyServer = new HorizontalPanel(); + proxyServer.add(getProxyHostPanel(), BorderLayout.CENTER); + proxyServer.add(getProxyPortPanel(), BorderLayout.EAST); + + JPanel proxyLogin = new HorizontalPanel(); + proxyLogin.add(getProxyUserPanel()); + proxyLogin.add(getProxyPassPanel()); + + JPanel proxyServerPanel = new HorizontalPanel(); + proxyServerPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("web_proxy_server_title"))); // $NON-NLS-1$ + proxyServerPanel.add(proxyServer, BorderLayout.CENTER); + proxyServerPanel.add(proxyLogin, BorderLayout.EAST); + + return proxyServerPanel; + } + + private JPanel getPortPanel() { + port = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_port")); // $NON-NLS-1$ + label.setLabelFor(port); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(port, BorderLayout.CENTER); + + return panel; + } + + private JPanel getProxyPortPanel() { + proxyPort = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_port")); // $NON-NLS-1$ + label.setLabelFor(proxyPort); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyPort, BorderLayout.CENTER); + + return panel; + } + + private JPanel getConnectTimeOutPanel() { + connectTimeOut = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_timeout_connect")); // $NON-NLS-1$ + label.setLabelFor(connectTimeOut); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(connectTimeOut, BorderLayout.CENTER); + + return panel; + } + + private JPanel getResponseTimeOutPanel() { + responseTimeOut = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_timeout_response")); // $NON-NLS-1$ + label.setLabelFor(responseTimeOut); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(responseTimeOut, BorderLayout.CENTER); + + return panel; + } + + private JPanel getDomainPanel() { + domain = new JTextField(20); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_domain")); // $NON-NLS-1$ + label.setLabelFor(domain); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(domain, BorderLayout.CENTER); + return panel; + } + + private JPanel getProxyHostPanel() { + proxyHost = new JTextField(20); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_domain")); // $NON-NLS-1$ + label.setLabelFor(proxyHost); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyHost, BorderLayout.CENTER); + return panel; + } + + private JPanel getProxyUserPanel() { + proxyUser = new JTextField(5); + + JLabel label = new JLabel(JMeterUtils.getResString("username")); // $NON-NLS-1$ + label.setLabelFor(proxyUser); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyUser, BorderLayout.CENTER); + return panel; + } + + private JPanel getProxyPassPanel() { + proxyPass = new JPasswordField(5); + + JLabel label = new JLabel(JMeterUtils.getResString("password")); // $NON-NLS-1$ + label.setLabelFor(proxyPass); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyPass, BorderLayout.CENTER); + return panel; + } + + /** + * This method defines the Panel for the HTTP path, 'Follow Redirects' + * 'Use KeepAlive', and 'Use multipart for HTTP POST' elements. + * + * @return JPanel The Panel for the path, 'Follow Redirects' and 'Use + * KeepAlive' elements. + */ + protected Component getPathPanel() { + path = new JTextField(15); + + JLabel label = new JLabel(JMeterUtils.getResString("path")); //$NON-NLS-1$ + label.setLabelFor(path); + + if (notConfigOnly){ + followRedirects = new JCheckBox(JMeterUtils.getResString("follow_redirects")); // $NON-NLS-1$ + followRedirects.setSelected(true); + followRedirects.addChangeListener(this); + + autoRedirects = new JCheckBox(JMeterUtils.getResString("follow_redirects_auto")); //$NON-NLS-1$ + autoRedirects.addChangeListener(this); + autoRedirects.setSelected(false);// Default changed in 2.3 and again in 2.4 + + useKeepAlive = new JCheckBox(JMeterUtils.getResString("use_keepalive")); // $NON-NLS-1$ + useKeepAlive.setSelected(true); + + useMultipartForPost = new JCheckBox(JMeterUtils.getResString("use_multipart_for_http_post")); // $NON-NLS-1$ + useMultipartForPost.setSelected(false); + + useBrowserCompatibleMultipartMode = new JCheckBox(JMeterUtils.getResString("use_multipart_mode_browser")); // $NON-NLS-1$ + useBrowserCompatibleMultipartMode.setSelected(HTTPSamplerBase.BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT); + + } + + JPanel pathPanel = new JPanel(new BorderLayout(5, 0)); + pathPanel.add(label, BorderLayout.WEST); + pathPanel.add(path, BorderLayout.CENTER); + pathPanel.setMinimumSize(pathPanel.getPreferredSize()); + + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + panel.add(pathPanel); + if (notConfigOnly){ + JPanel optionPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + optionPanel.add(autoRedirects); + optionPanel.add(followRedirects); + optionPanel.add(useKeepAlive); + optionPanel.add(useMultipartForPost); + optionPanel.add(useBrowserCompatibleMultipartMode); + optionPanel.setMinimumSize(optionPanel.getPreferredSize()); + panel.add(optionPanel); + } + + return panel; + } + + protected JPanel getProtocolAndMethodPanel() { + + // Implementation + + if (showImplementation) { + httpImplementation = new JLabeledChoice(JMeterUtils.getResString("http_implementation"), // $NON-NLS-1$ + HTTPSamplerFactory.getImplementations()); + httpImplementation.addValue(""); + } + // PROTOCOL + protocol = new JTextField(4); + JLabel protocolLabel = new JLabel(JMeterUtils.getResString("protocol")); // $NON-NLS-1$ + protocolLabel.setLabelFor(protocol); + + // CONTENT_ENCODING + contentEncoding = new JTextField(10); + JLabel contentEncodingLabel = new JLabel(JMeterUtils.getResString("content_encoding")); // $NON-NLS-1$ + contentEncodingLabel.setLabelFor(contentEncoding); + + if (notConfigOnly){ + method = new JLabeledChoice(JMeterUtils.getResString("method"), // $NON-NLS-1$ + HTTPSamplerBase.getValidMethodsAsArray()); + } + + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + + if (showImplementation) { + panel.add(httpImplementation); + } + panel.add(protocolLabel); + panel.add(protocol); + panel.add(Box.createHorizontalStrut(5)); + + if (notConfigOnly){ + panel.add(method); + } + panel.setMinimumSize(panel.getPreferredSize()); + panel.add(Box.createHorizontalStrut(5)); + + panel.add(contentEncodingLabel); + panel.add(contentEncoding); + panel.setMinimumSize(panel.getPreferredSize()); + return panel; + } + + protected JTabbedPane getParameterPanel() { + postContentTabbedPane = new ValidationTabbedPane(); + argsPanel = new HTTPArgumentsPanel(); + postContentTabbedPane.add(JMeterUtils.getResString("post_as_parameters"), argsPanel);// $NON-NLS-1$ + if(showRawBodyPane) { + postBodyContent = new JLabeledTextArea(JMeterUtils.getResString("post_body_raw"));// $NON-NLS-1$ + postContentTabbedPane.add(JMeterUtils.getResString("post_body"), postBodyContent);// $NON-NLS-1$ + } + return postContentTabbedPane; + } + + /** + * + */ + class ValidationTabbedPane extends JTabbedPane{ + + /** + * + */ + private static final long serialVersionUID = 7014311238367882880L; + + /* (non-Javadoc) + * @see javax.swing.JTabbedPane#setSelectedIndex(int) + */ + @Override + public void setSelectedIndex(int index) { + setSelectedIndex(index, true); + } + /** + * Apply some check rules if check is true + */ + public void setSelectedIndex(int index, boolean check) { + int oldSelectedIndex = getSelectedIndex(); + if(!check || oldSelectedIndex==-1) { + super.setSelectedIndex(index); + } + else if(index != this.getSelectedIndex()) + { + if(noData(getSelectedIndex())) { + // If there is no data, then switching between Parameters and Raw should be + // allowed with no further user interaction. + argsPanel.clear(); + postBodyContent.setText(""); + super.setSelectedIndex(index); + } + else { + if(oldSelectedIndex == TAB_RAW_BODY) { + // If RAW data and Parameters match we allow switching + if(postBodyContent.getText().equals(computePostBody((Arguments)argsPanel.createTestElement()).trim())) { + super.setSelectedIndex(index); + } + else { + // If there is data in the Raw panel, then the user should be + // prevented from switching (that would be easy to track). + JOptionPane.showConfirmDialog(this, + JMeterUtils.getResString("web_cannot_switch_tab"), // $NON-NLS-1$ + JMeterUtils.getResString("warning"), // $NON-NLS-1$ + JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE); + return; + } + } + else { + // If the Parameter data can be converted (i.e. no names), we + // warn the user that the Parameter data will be lost. + if(canConvertParameters()) { + Object[] options = { + JMeterUtils.getResString("confirm"), + JMeterUtils.getResString("cancel")}; + int n = JOptionPane.showOptionDialog(this, + JMeterUtils.getResString("web_parameters_lost_message"), + JMeterUtils.getResString("warning"), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[1]); + if(n == JOptionPane.YES_OPTION) { + convertParametersToRaw(); + super.setSelectedIndex(index); + } + else{ + return; + } + } + else { + // If the Parameter data cannot be converted to Raw, then the user should be + // prevented from doing so raise an error dialog + JOptionPane.showConfirmDialog(this, + JMeterUtils.getResString("web_cannot_convert_parameters_to_raw"), // $NON-NLS-1$ + JMeterUtils.getResString("warning"), // $NON-NLS-1$ + JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE); + return; + } + } + } + } + } + } + // autoRedirects and followRedirects cannot both be selected + public void stateChanged(ChangeEvent e) { + if (e.getSource() == autoRedirects){ + if (autoRedirects.isSelected()) { + followRedirects.setSelected(false); + } + } + if (e.getSource() == followRedirects){ + if (followRedirects.isSelected()) { + autoRedirects.setSelected(false); + } + } + } + + + /** + * Convert Parameters to Raw Body + */ + void convertParametersToRaw() { + postBodyContent.setText(computePostBody((Arguments)argsPanel.createTestElement())); + } + + /** + * + * @return true if no argument has a name + */ + boolean canConvertParameters() { + Arguments arguments = (Arguments)argsPanel.createTestElement(); + for (int i = 0; i < arguments.getArgumentCount(); i++) { + if(!StringUtils.isEmpty(arguments.getArgument(i).getName())) { + return false; + } + } + return true; + } + + /** + * @return true if neither Parameters tab nor Raw Body tab contain data + */ + boolean noData(int oldSelectedIndex) { + if(oldSelectedIndex == TAB_RAW_BODY) { + return StringUtils.isEmpty(postBodyContent.getText().trim()); + } + else { + Arguments element = (Arguments)argsPanel.createTestElement(); + return StringUtils.isEmpty(computePostBody(element)); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/AuthManager.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/AuthManager.java new file mode 100644 index 0000000..6977e9f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/AuthManager.java @@ -0,0 +1,294 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +// For Unit tests, @see TestAuthManager + +/** + * This class provides a way to provide Authorization in jmeter requests. The + * format of the authorization file is: URL user pass where URL is an HTTP URL, + * user a username to use and pass the appropriate password. + * + */ +public class AuthManager extends ConfigTestElement implements Serializable { + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final static String AUTH_LIST = "AuthManager.auth_list"; //$NON-NLS-1$ + + private final static String[] COLUMN_RESOURCE_NAMES = { + "auth_base_url", //$NON-NLS-1$ + "username", //$NON-NLS-1$ + "password", //$NON-NLS-1$ + "domain", //$NON-NLS-1$ + "realm", //$NON-NLS-1$ + }; + + // Column numbers - must agree with order above + public final static int COL_URL = 0; + public final static int COL_USERNAME = 1; + public final static int COL_PASSWORD = 2; + public final static int COL_DOMAIN = 3; + public final static int COL_REALM = 4; + + private final static int COLUMN_COUNT = COLUMN_RESOURCE_NAMES.length; + + /** + * Default Constructor. + */ + public AuthManager() { + setProperty(new CollectionProperty(AUTH_LIST, new ArrayList())); + } + + /** {@inheritDoc} */ + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(AUTH_LIST, new ArrayList())); + } + + /** + * Update an authentication record. + */ + public void set(int index, String url, String user, String pass, String domain, String realm) { + Authorization auth = new Authorization(url, user, pass, domain, realm); + if (index >= 0) { + getAuthObjects().set(index, new TestElementProperty(auth.getName(), auth)); + } else { + getAuthObjects().addItem(auth); + } + } + + public CollectionProperty getAuthObjects() { + return (CollectionProperty) getProperty(AUTH_LIST); + } + + public int getColumnCount() { + return COLUMN_COUNT; + } + + public String getColumnName(int column) { + return COLUMN_RESOURCE_NAMES[column]; + } + + public Class getColumnClass(int column) { + return COLUMN_RESOURCE_NAMES[column].getClass(); + } + + public Authorization getAuthObjectAt(int row) { + return (Authorization) getAuthObjects().get(row).getObjectValue(); + } + + public boolean isEditable() { + return true; + } + + /** + * Return the record at index i + */ + public Authorization get(int i) { + return (Authorization) getAuthObjects().get(i).getObjectValue(); + } + + public String getAuthHeaderForURL(URL url) { + Authorization auth = getAuthForURL(url); + if (auth == null) { + return null; + } + return auth.toBasicHeader(); + } + + public Authorization getAuthForURL(URL url) { + if (!isSupportedProtocol(url)) { + return null; + } + + // TODO: replace all this url2 mess with a proper method + // "areEquivalent(url1, url2)" that + // would also ignore case in protocol and host names, etc. -- use that + // method in the CookieManager too. + + URL url2 = null; + + try { + if (url.getPort() == -1) { + // Obtain another URL with an explicit port: + int port = url.getProtocol().equalsIgnoreCase("http") ? 80 : 443; + // only http and https are supported + url2 = new URL(url.getProtocol(), url.getHost(), port, url.getPath()); + } else if ((url.getPort() == 80 && url.getProtocol().equalsIgnoreCase("http")) + || (url.getPort() == 443 && url.getProtocol().equalsIgnoreCase("https"))) { + url2 = new URL(url.getProtocol(), url.getHost(), url.getPath()); + } + } catch (MalformedURLException e) { + log.error("Internal error!", e); // this should never happen + // anyway, we'll continue with url2 set to null. + } + + String s1 = url.toString(); + String s2 = null; + if (url2 != null) { + s2 = url2.toString(); + } + + log.debug("Target URL strings to match against: "+s1+" and "+s2); + // TODO should really return most specific (i.e. longest) match. + for (PropertyIterator iter = getAuthObjects().iterator(); iter.hasNext();) { + Authorization auth = (Authorization) iter.next().getObjectValue(); + + String uRL = auth.getURL(); + log.debug("Checking match against auth'n entry: "+uRL); + if (s1.startsWith(uRL) || s2 != null && s2.startsWith(uRL)) { + log.debug("Matched"); + return auth; + } + log.debug("Did not match"); + } + return null; + } + + /** {@inheritDoc} */ + @Override + public void addConfigElement(ConfigElement config) { + } + + public void addAuth(Authorization auth) { + getAuthObjects().addItem(auth); + } + + public void addAuth() { + getAuthObjects().addItem(new Authorization()); + } + + /** {@inheritDoc} */ + @Override + public boolean expectsModification() { + return false; + } + + /** + * Save the authentication data to a file. + */ + public void save(String authFile) throws IOException { + File file = new File(authFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir"),authFile); + } + PrintWriter writer = new PrintWriter(new FileWriter(file)); + writer.println("# JMeter generated Authorization file"); + for (int i = 0; i < getAuthObjects().size(); i++) { + Authorization auth = (Authorization) getAuthObjects().get(i).getObjectValue(); + writer.println(auth.toString()); + } + writer.flush(); + writer.close(); + } + + /** + * Add authentication data from a file. + */ + public void addFile(String authFile) throws IOException { + File file = new File(authFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir") + File.separator + authFile); + } + if (!file.canRead()) { + throw new IOException("The file you specified cannot be read."); + } + + BufferedReader reader = null; + boolean ok = true; + try { + reader = new BufferedReader(new FileReader(file)); + String line; + while ((line = reader.readLine()) != null) { + try { + if (line.startsWith("#") || line.trim().length() == 0) { //$NON-NLS-1$ + continue; + } + StringTokenizer st = new StringTokenizer(line, "\t"); //$NON-NLS-1$ + String url = st.nextToken(); + String user = st.nextToken(); + String pass = st.nextToken(); + String domain = ""; + String realm = ""; + if (st.hasMoreTokens()){// Allow for old format file without the extra columnns + domain = st.nextToken(); + realm = st.nextToken(); + } + Authorization auth = new Authorization(url, user, pass,domain,realm); + getAuthObjects().addItem(auth); + } catch (NoSuchElementException e) { + log.error("Error parsing auth line: '" + line + "'"); + ok = false; + } + } + } finally { + IOUtils.closeQuietly(reader); + } + if (!ok){ + JMeterUtils.reportErrorToUser("One or more errors found when reading the Auth file - see the log file"); + } + } + + /** + * Remove an authentication record. + */ + public void remove(int index) { + getAuthObjects().remove(index); + } + + /** + * Return the number of records. + */ + public int getAuthCount() { + return getAuthObjects().size(); + } + + // Needs to be package protected for Unit test + static boolean isSupportedProtocol(URL url) { + String protocol = url.getProtocol().toLowerCase(java.util.Locale.ENGLISH); + return protocol.equals(HTTPConstants.PROTOCOL_HTTP) || protocol.equals(HTTPConstants.PROTOCOL_HTTPS); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Authorization.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Authorization.java new file mode 100644 index 0000000..6d8165a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Authorization.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.io.Serializable; + +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.protocol.http.util.Base64Encoder; +import org.apache.jmeter.testelement.AbstractTestElement; + +/** + * This class is an Authorization encapsulator. + * + */ +public class Authorization extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + private static final String URL = "Authorization.url"; // $NON-NLS-1$ + + private static final String USERNAME = "Authorization.username"; // $NON-NLS-1$ + + private static final String PASSWORD = "Authorization.password"; // $NON-NLS-1$ + + private static final String DOMAIN = "Authorization.domain"; // $NON-NLS-1$ + + private static final String REALM = "Authorization.realm"; // $NON-NLS-1$ + + private static final String TAB = "\t"; // $NON-NLS-1$ + + /** + * create the authorization + */ + Authorization(String url, String user, String pass, String domain, String realm) { + setURL(url); + setUser(user); + setPass(pass); + setDomain(domain); + setRealm(realm); + } + + public boolean expectsModification() { + return false; + } + + public Authorization() { + this("","","","",""); + } + + public void addConfigElement(ConfigElement config) { + } + + public synchronized String getURL() { + return getPropertyAsString(URL); + } + + public synchronized void setURL(String url) { + setProperty(URL, url); + } + + public synchronized String getUser() { + return getPropertyAsString(USERNAME); + } + + public synchronized void setUser(String user) { + setProperty(USERNAME, user); + } + + public synchronized String getPass() { + return getPropertyAsString(PASSWORD); + } + + public synchronized void setPass(String pass) { + setProperty(PASSWORD, pass); + } + + public synchronized String getDomain() { + return getPropertyAsString(DOMAIN); + } + + public synchronized void setDomain(String domain) { + setProperty(DOMAIN, domain); + } + + public synchronized String getRealm() { + return getPropertyAsString(REALM); + } + + public synchronized void setRealm(String realm) { + setProperty(REALM, realm); + } + + // Used for saving entries to a file + @Override + public String toString() { + return getURL() + TAB + getUser() + TAB + getPass() + TAB + getDomain() + TAB + getRealm(); + } + + public String toBasicHeader(){ + return "Basic " + Base64Encoder.encode(getUser() + ":" + getPass()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CacheManager.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CacheManager.java new file mode 100644 index 0000000..2855fea --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CacheManager.java @@ -0,0 +1,380 @@ +/* + * 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. + * + */ + +// For unit tests @see TestCookieManager + +package org.apache.jmeter.protocol.http.control; + +import java.io.Serializable; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.Collections; +import java.util.Date; +import java.util.Map; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.URIException; +import org.apache.commons.httpclient.util.DateParseException; +import org.apache.commons.httpclient.util.DateUtil; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.protocol.http.util.HTTPConstantsInterface; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Handles HTTP Caching + */ +public class CacheManager extends ConfigTestElement implements TestListener, Serializable { + + private static final long serialVersionUID = 234L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + //+ JMX attributes, do not change values + public static final String CLEAR = "clearEachIteration"; // $NON-NLS-1$ + public static final String USE_EXPIRES = "useExpires"; // $NON-NLS-1$ + public static final String MAX_SIZE = "maxSize"; // $NON-NLS-1$ + //- + + private transient InheritableThreadLocal> threadCache; + + private transient boolean useExpires; // Cached value + + private static final int DEFAULT_MAX_SIZE = 5000; + + public CacheManager() { + setProperty(new BooleanProperty(CLEAR, false)); + setProperty(new BooleanProperty(USE_EXPIRES, false)); + clearCache(); + useExpires = false; + } + + /* + * Holder for storing cache details. + * Perhaps add original response later? + */ + // package-protected to allow access by unit-test cases + static class CacheEntry{ + private final String lastModified; + private final String etag; + private final Date expires; + public CacheEntry(String lastModified, Date expires, String etag){ + this.lastModified = lastModified; + this.etag = etag; + this.expires = expires; + } + public String getLastModified() { + return lastModified; + } + public String getEtag() { + return etag; + } + @Override + public String toString(){ + return lastModified+" "+etag; + } + public Date getExpires() { + return expires; + } + } + + /** + * Save the Last-Modified, Etag, and Expires headers if the result is cacheable. + * Version for Java implementation. + * @param conn connection + * @param res result + */ + public void saveDetails(URLConnection conn, SampleResult res){ + if (isCacheable(res)){ + String lastModified = conn.getHeaderField(HTTPConstantsInterface.LAST_MODIFIED); + String expires = conn.getHeaderField(HTTPConstantsInterface.EXPIRES); + String etag = conn.getHeaderField(HTTPConstantsInterface.ETAG); + String url = conn.getURL().toString(); + String cacheControl = conn.getHeaderField(HTTPConstantsInterface.CACHE_CONTROL); + setCache(lastModified, cacheControl, expires, etag, url); + } + } + + /** + * Save the Last-Modified, Etag, and Expires headers if the result is cacheable. + * Version for Commons HttpClient implementation. + * @param method + * @param res result + */ + public void saveDetails(HttpMethod method, SampleResult res) throws URIException{ + if (isCacheable(res)){ + String lastModified = getHeader(method ,HTTPConstantsInterface.LAST_MODIFIED); + String expires = getHeader(method ,HTTPConstantsInterface.EXPIRES); + String etag = getHeader(method ,HTTPConstantsInterface.ETAG); + String url = method.getURI().toString(); + String cacheControl = getHeader(method, HTTPConstantsInterface.CACHE_CONTROL); + setCache(lastModified, cacheControl, expires, etag, url); + } + } + + /** + * Save the Last-Modified, Etag, and Expires headers if the result is cacheable. + * Version for Apache HttpClient implementation. + * @param method + * @param res result + */ + public void saveDetails(HttpResponse method, SampleResult res) { + if (isCacheable(res)){ + method.getLastHeader(USE_EXPIRES); + String lastModified = getHeader(method ,HTTPConstantsInterface.LAST_MODIFIED); + String expires = getHeader(method ,HTTPConstantsInterface.EXPIRES); + String etag = getHeader(method ,HTTPConstantsInterface.ETAG); + String cacheControl = getHeader(method, HTTPConstantsInterface.CACHE_CONTROL); + setCache(lastModified, cacheControl, expires, etag, res.getUrlAsString()); // TODO correct URL? + } + } + + // helper method to save the cache entry + private void setCache(String lastModified, String cacheControl, String expires, String etag, String url) { + if (log.isDebugEnabled()){ + log.debug("SET(both) "+url + " " + cacheControl + " " + lastModified + " " + " " + expires + " " + etag); + } + Date expiresDate = null; // i.e. not using Expires + if (useExpires) {// Check that we are processing Expires/CacheControl + final String MAX_AGE = "max-age="; + // TODO - check for other CacheControl attributes? + if (cacheControl != null && cacheControl.contains("public") && cacheControl.contains(MAX_AGE)) { + long maxAgeInSecs = Long.parseLong( + cacheControl.substring(cacheControl.indexOf(MAX_AGE)+MAX_AGE.length()) + .split("[, ]")[0] // Bug 51932 - allow for optional trailing attributes + ); + expiresDate=new Date(System.currentTimeMillis()+maxAgeInSecs*1000); + } else if (expires != null) { + try { + expiresDate = DateUtil.parseDate(expires); + } catch (DateParseException e) { + if (log.isDebugEnabled()){ + log.debug("Unable to parse Expires: '"+expires+"' "+e); + } + expiresDate = new Date(0L); // invalid dates must be treated as expired + } + } + } + getCache().put(url, new CacheEntry(lastModified, expiresDate, etag)); + } + + // Helper method to deal with missing headers - Commons HttpClient + private String getHeader(HttpMethod method, String name){ + org.apache.commons.httpclient.Header hdr = method.getResponseHeader(name); + return hdr != null ? hdr.getValue() : null; + } + + // Apache HttpClient + private String getHeader(HttpResponse method, String name) { + org.apache.http.Header hdr = method.getLastHeader(name); + return hdr != null ? hdr.getValue() : null; + } + + /* + * Is the sample result OK to cache? + * i.e is it in the 2xx range? + */ + private boolean isCacheable(SampleResult res){ + final String responseCode = res.getResponseCode(); + return "200".compareTo(responseCode) <= 0 // $NON-NLS-1$ + && "299".compareTo(responseCode) >= 0; // $NON-NLS-1$ + } + + /** + * Check the cache, and if there is a match, set the headers:
+ * If-Modified-Since
+ * If-None-Match
+ * Commons HttpClient version + * @param url URL to look up in cache + * @param method where to set the headers + */ + public void setHeaders(URL url, HttpMethod method) { + CacheEntry entry = getCache().get(url.toString()); + if (log.isDebugEnabled()){ + log.debug(method.getName()+"(OACH) "+url.toString()+" "+entry); + } + if (entry != null){ + final String lastModified = entry.getLastModified(); + if (lastModified != null){ + method.setRequestHeader(HTTPConstantsInterface.IF_MODIFIED_SINCE, lastModified); + } + final String etag = entry.getEtag(); + if (etag != null){ + method.setRequestHeader(HTTPConstantsInterface.IF_NONE_MATCH, etag); + } + } + } + + /** + * Check the cache, and if there is a match, set the headers:
+ * If-Modified-Since
+ * If-None-Match
+ * Apache HttpClient version. + * @param url URL to look up in cache + * @param request where to set the headers + */ + public void setHeaders(URL url, HttpRequestBase request) { + CacheEntry entry = getCache().get(url.toString()); + if (log.isDebugEnabled()){ + log.debug(request.getMethod()+"(OAH) "+url.toString()+" "+entry); + } + if (entry != null){ + final String lastModified = entry.getLastModified(); + if (lastModified != null){ + request.setHeader(HTTPConstantsInterface.IF_MODIFIED_SINCE, lastModified); + } + final String etag = entry.getEtag(); + if (etag != null){ + request.setHeader(HTTPConstantsInterface.IF_NONE_MATCH, etag); + } + } + } + + /** + * Check the cache, and if there is a match, set the headers:
+ * If-Modified-Since
+ * If-None-Match
+ * @param url URL to look up in cache + * @param conn where to set the headers + */ + public void setHeaders(HttpURLConnection conn, URL url) { + CacheEntry entry = getCache().get(url.toString()); + if (log.isDebugEnabled()){ + log.debug(conn.getRequestMethod()+"(Java) "+url.toString()+" "+entry); + } + if (entry != null){ + final String lastModified = entry.getLastModified(); + if (lastModified != null){ + conn.addRequestProperty(HTTPConstantsInterface.IF_MODIFIED_SINCE, lastModified); + } + final String etag = entry.getEtag(); + if (etag != null){ + conn.addRequestProperty(HTTPConstantsInterface.IF_NONE_MATCH, etag); + } + } + } + + /** + * Check the cache, if the entry has an expires header and the entry has not expired, return true
+ * @param url URL to look up in cache + */ + public boolean inCache(URL url) { + CacheEntry entry = getCache().get(url.toString()); + if (log.isDebugEnabled()){ + log.debug("inCache "+url.toString()+" "+entry); + } + if (entry != null){ + final Date expiresDate = entry.getExpires(); + if (expiresDate != null) { + if (expiresDate.after(new Date())) { + if (log.isDebugEnabled()){ + log.debug("Expires= " + expiresDate + " (Valid)"); + } + return true; + } else { + if (log.isDebugEnabled()){ + log.debug("Expires= " + expiresDate + " (Expired)"); + } + } + } + } + return false; + } + + private Map getCache(){ + return threadCache.get(); + } + + public boolean getClearEachIteration() { + return getPropertyAsBoolean(CLEAR); + } + + public void setClearEachIteration(boolean clear) { + setProperty(new BooleanProperty(CLEAR, clear)); + } + + public boolean getUseExpires() { + return getPropertyAsBoolean(USE_EXPIRES); + } + + public void setUseExpires(boolean expires) { + setProperty(new BooleanProperty(USE_EXPIRES, expires)); + } + + /** + * @return int cache max size + */ + public int getMaxSize() { + return getPropertyAsInt(MAX_SIZE, DEFAULT_MAX_SIZE); + } + + /** + * @param size int cache max size + */ + public void setMaxSize(int size) { + setProperty(MAX_SIZE, size, DEFAULT_MAX_SIZE); + } + + + @Override + public void clear(){ + super.clear(); + clearCache(); + } + + private void clearCache() { + log.debug("Clear cache"); + threadCache = new InheritableThreadLocal>(){ + @Override + protected Map initialValue(){ + // Bug 51942 - this map may be used from multiple threads + @SuppressWarnings("unchecked") // LRUMap is not generic currently + Map map = new LRUMap(getMaxSize()); + return Collections.synchronizedMap(map); + } + }; + } + + public void testStarted() { + } + + public void testEnded() { + } + + public void testStarted(String host) { + } + + public void testEnded(String host) { + } + + public void testIterationStart(LoopIterationEvent event) { + if (getClearEachIteration()) { + clearCache(); + } + useExpires=getUseExpires(); // cache the value + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Cookie.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Cookie.java new file mode 100644 index 0000000..37765b9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Cookie.java @@ -0,0 +1,247 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.io.Serializable; + +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * This class is a Cookie encapsulator. + * + */ +public class Cookie extends AbstractTestElement implements Serializable { + private static final long serialVersionUID = 240L; + + private static final String TAB = "\t"; + + private static final String VALUE = "Cookie.value"; //$NON-NLS-1$ + + private static final String DOMAIN = "Cookie.domain"; //$NON-NLS-1$ + + private static final String EXPIRES = "Cookie.expires"; //$NON-NLS-1$ + + private static final String SECURE = "Cookie.secure"; //$NON-NLS-1$ + + private static final String PATH = "Cookie.path"; //$NON-NLS-1$ + + private static final String PATH_SPECIFIED = "Cookie.path_specified"; //$NON-NLS-1$ + + private static final String DOMAIN_SPECIFIED = "Cookie.domain_specified"; //$NON-NLS-1$ + + private static final String VERSION = "Cookie.version"; //$NON-NLS-1$ + + private static final int DEFAULT_VERSION = 1; + + /** + * create the coookie + */ + public Cookie() { + this("","","","",false,0,false,false); + } + + /** + * create the coookie + * + * @param expires - this is in seconds + * + */ + public Cookie(String name, String value, String domain, String path, boolean secure, long expires) { + this(name,value,domain,path,secure,expires,true,true); + } + + /** + * create the coookie + * + * @param expires - this is in seconds + * @param hasPath - was the path explicitly specified? + * @param hasDomain - was the domain explicitly specified? + * + */ + public Cookie(String name, String value, String domain, String path, + boolean secure, long expires, boolean hasPath, boolean hasDomain) { + this(name, value, domain, path, secure, expires, hasPath, hasDomain, DEFAULT_VERSION); + } + + /** + * Create a JMeter Cookie. + * + * @param name + * @param value + * @param domain + * @param path + * @param secure + * @param expires - this is in seconds + * @param hasPath - was the path explicitly specified? + * @param hasDomain - was the domain explicitly specified? + * @param version - cookie spec. version + */ + public Cookie(String name, String value, String domain, String path, + boolean secure, long expires, boolean hasPath, boolean hasDomain, int version) { + this.setName(name); + this.setValue(value); + this.setDomain(domain); + this.setPath(path); + this.setSecure(secure); + this.setExpires(expires); + this.setPathSpecified(hasPath); + this.setDomainSpecified(hasDomain); + this.setVersion(version); + } + + public void addConfigElement(ConfigElement config) { + } + + /** + * get the value for this object. + */ + public String getValue() { + return getPropertyAsString(VALUE); + } + + /** + * set the value for this object. + */ + public void setValue(String value) { + this.setProperty(VALUE, value); + } + + /** + * get the domain for this object. + */ + public String getDomain() { + return getPropertyAsString(DOMAIN); + } + + /** + * set the domain for this object. + */ + public void setDomain(String domain) { + setProperty(DOMAIN, domain); + } + + /** + * get the expiry time for the cookie + * + * @return Expiry time in seconds since the Java epoch + */ + public long getExpires() { + return getPropertyAsLong(EXPIRES); + } + + /** + * get the expiry time for the cookie + * + * @return Expiry time in milli-seconds since the Java epoch, + * i.e. same as System.currentTimeMillis() + */ + public long getExpiresMillis() { + return getPropertyAsLong(EXPIRES)*1000; + } + + /** + * set the expiry time for the cookie + * @param expires - expiry time in seconds since the Java epoch + */ + public void setExpires(long expires) { + setProperty(new LongProperty(EXPIRES, expires)); + } + + /** + * get the secure for this object. + */ + public boolean getSecure() { + return getPropertyAsBoolean(SECURE); + } + + /** + * set the secure for this object. + */ + public void setSecure(boolean secure) { + setProperty(new BooleanProperty(SECURE, secure)); + } + + /** + * get the path for this object. + */ + public String getPath() { + return getPropertyAsString(PATH); + } + + /** + * set the path for this object. + */ + public void setPath(String path) { + setProperty(PATH, path); + } + + public void setPathSpecified(boolean b) { + setProperty(PATH_SPECIFIED, b); + } + + public boolean isPathSpecified(){ + return getPropertyAsBoolean(PATH_SPECIFIED); + } + + public void setDomainSpecified(boolean b) { + setProperty(DOMAIN_SPECIFIED, b); + } + + public boolean isDomainSpecified(){ + return getPropertyAsBoolean(DOMAIN_SPECIFIED); + } + + /** + * creates a string representation of this cookie + */ + @Override + public String toString() { + StringBuilder sb=new StringBuilder(80); + sb.append(getDomain()); + // flag - if all machines within a given domain can access the variable. + //(from http://www.cookiecentral.com/faq/ 3.5) + sb.append(TAB).append("TRUE"); + sb.append(TAB).append(getPath()); + sb.append(TAB).append(JOrphanUtils.booleanToSTRING(getSecure())); + sb.append(TAB).append(getExpires()); + sb.append(TAB).append(getName()); + sb.append(TAB).append(getValue()); + return sb.toString(); + } + + /** + * @return the version + */ + public int getVersion() { + return getPropertyAsInt(VERSION, DEFAULT_VERSION); + } + + /** + * @param version the version to set + */ + public void setVersion(int version) { + setProperty(VERSION, version, DEFAULT_VERSION); + } + + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CookieHandler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CookieHandler.java new file mode 100644 index 0000000..8e77f6f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CookieHandler.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.jmeter.protocol.http.control; + +import java.net.URL; + +import org.apache.jmeter.testelement.property.CollectionProperty; + +/** + * Interface to be implemented by CookieHandler + */ +public interface CookieHandler { + + /** + * Add cookie to CookieManager from cookieHeader and URL + * @param cookieManager CookieManager on which cookies are added + * @param checkCookies boolean to indicate if cookies must be validated against spec + * @param cookieHeader String cookie Header + * @param url URL + */ + public void addCookieFromHeader(CookieManager cookieManager, boolean checkCookies, + String cookieHeader, URL url); + + /** + * Find cookies applicable to the given URL and build the Cookie header from + * them. + * @param cookiesCP {@link CollectionProperty} of {@link Cookie} + * @param url + * URL of the request to which the returned header will be added. + * @return the value string for the cookie header (goes after "Cookie: "). + */ + public String getCookieHeaderForURL(CollectionProperty cookiesCP, URL url, + boolean allowVariableCookie); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CookieManager.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CookieManager.java new file mode 100644 index 0000000..b67f4df --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/CookieManager.java @@ -0,0 +1,393 @@ +/* + * 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. + * + */ + +// For unit tests @see TestCookieManager + +package org.apache.jmeter.protocol.http.control; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serializable; +import java.net.URL; +import java.util.ArrayList; + +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * This class provides an interface to the netscape cookies file to pass cookies + * along with a request. + * + * Now uses Commons HttpClient parsing and matching code (since 2.1.2) + * + */ +public class CookieManager extends ConfigTestElement implements TestListener, Serializable { + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + //++ JMX tag values + private static final String CLEAR = "CookieManager.clearEachIteration";// $NON-NLS-1$ + + private static final String COOKIES = "CookieManager.cookies";// $NON-NLS-1$ + + private static final String POLICY = "CookieManager.policy"; //$NON-NLS-1$ + //-- JMX tag values + + private static final String TAB = "\t"; //$NON-NLS-1$ + + // See bug 33796 + private static final boolean DELETE_NULL_COOKIES = + JMeterUtils.getPropDefault("CookieManager.delete_null_cookies", true);// $NON-NLS-1$ + + // See bug 28715 + // Package protected for tests + static final boolean ALLOW_VARIABLE_COOKIES + = JMeterUtils.getPropDefault("CookieManager.allow_variable_cookies", true);// $NON-NLS-1$ + + private static final String COOKIE_NAME_PREFIX = + JMeterUtils.getPropDefault("CookieManager.name.prefix", "COOKIE_").trim();// $NON-NLS-1$ $NON-NLS-2$ + + private static final boolean SAVE_COOKIES = + JMeterUtils.getPropDefault("CookieManager.save.cookies", false);// $NON-NLS-1$ + + private static final boolean CHECK_COOKIES = + JMeterUtils.getPropDefault("CookieManager.check.cookies", true);// $NON-NLS-1$ + + static { + log.info("Settings:" + + " Delete null: " + DELETE_NULL_COOKIES + + " Check: " + CHECK_COOKIES + + " Allow variable: " + ALLOW_VARIABLE_COOKIES + + " Save: " + SAVE_COOKIES + + " Prefix: " + COOKIE_NAME_PREFIX + ); + } + private transient CookieHandler cookieHandler; + + private transient CollectionProperty initialCookies; + + public static final String DEFAULT_POLICY = CookiePolicy.BROWSER_COMPATIBILITY; + + public CookieManager() { + clearCookies(); // Ensure that there is always a collection available + } + + // ensure that the initial cookies are copied to the per-thread instances + /** {@inheritDoc} */ + @Override + public Object clone(){ + CookieManager clone = (CookieManager) super.clone(); + clone.initialCookies = initialCookies; + clone.cookieHandler = cookieHandler; + return clone; + } + + public String getPolicy() { + return getPropertyAsString(POLICY, DEFAULT_POLICY); + } + + public void setCookiePolicy(String policy){ + setProperty(POLICY, policy, DEFAULT_POLICY); + } + + public CollectionProperty getCookies() { + return (CollectionProperty) getProperty(COOKIES); + } + + public int getCookieCount() {// Used by GUI + return getCookies().size(); + } + + public boolean getClearEachIteration() { + return getPropertyAsBoolean(CLEAR); + } + + public void setClearEachIteration(boolean clear) { + setProperty(new BooleanProperty(CLEAR, clear)); + } + + /** + * Save the static cookie data to a file. + * Cookies are only taken from the GUI - runtime cookies are not included. + */ + public void save(String authFile) throws IOException { + File file = new File(authFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir") // $NON-NLS-1$ + + File.separator + authFile); + } + PrintWriter writer = new PrintWriter(new FileWriter(file)); // TODO Charset ? + writer.println("# JMeter generated Cookie file");// $NON-NLS-1$ + PropertyIterator cookies = getCookies().iterator(); + long now = System.currentTimeMillis(); + while (cookies.hasNext()) { + Cookie cook = (Cookie) cookies.next().getObjectValue(); + final long expiresMillis = cook.getExpiresMillis(); + if (expiresMillis == 0 || expiresMillis > now) { // only save unexpired cookies + writer.println(cookieToString(cook)); + } + } + writer.flush(); + writer.close(); + } + + /** + * Add cookie data from a file. + */ + public void addFile(String cookieFile) throws IOException { + File file = new File(cookieFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir") // $NON-NLS-1$ + + File.separator + cookieFile); + } + BufferedReader reader = null; + if (file.canRead()) { + reader = new BufferedReader(new FileReader(file)); // TODO Charset ? + } else { + throw new IOException("The file you specified cannot be read."); + } + + // N.B. this must agree with the save() and cookieToString() methods + String line; + try { + final CollectionProperty cookies = getCookies(); + while ((line = reader.readLine()) != null) { + try { + if (line.startsWith("#") || line.trim().length() == 0) {//$NON-NLS-1$ + continue; + } + String[] st = JOrphanUtils.split(line, TAB, false); + + final int _domain = 0; + //final int _ignored = 1; + final int _path = 2; + final int _secure = 3; + final int _expires = 4; + final int _name = 5; + final int _value = 6; + final int _fields = 7; + if (st.length!=_fields) { + throw new IOException("Expected "+_fields+" fields, found "+st.length+" in "+line); + } + + if (st[_path].length()==0) { + st[_path] = "/"; //$NON-NLS-1$ + } + boolean secure = Boolean.valueOf(st[_secure]).booleanValue(); + long expires = Long.valueOf(st[_expires]).longValue(); + if (expires==Long.MAX_VALUE) { + expires=0; + } + //long max was used to represent a non-expiring cookie, but that caused problems + Cookie cookie = new Cookie(st[_name], st[_value], st[_domain], st[_path], secure, expires); + cookies.addItem(cookie); + } catch (NumberFormatException e) { + throw new IOException("Error parsing cookie line\n\t'" + line + "'\n\t" + e); + } + } + } finally { + reader.close(); + } + } + + private String cookieToString(Cookie c){ + StringBuilder sb=new StringBuilder(80); + sb.append(c.getDomain()); + //flag - if all machines within a given domain can access the variable. + //(from http://www.cookiecentral.com/faq/ 3.5) + sb.append(TAB).append("TRUE"); + sb.append(TAB).append(c.getPath()); + sb.append(TAB).append(JOrphanUtils.booleanToSTRING(c.getSecure())); + sb.append(TAB).append(c.getExpires()); + sb.append(TAB).append(c.getName()); + sb.append(TAB).append(c.getValue()); + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void recoverRunningVersion() { + // do nothing, the cookie manager has to accept changes. + } + + /** {@inheritDoc} */ + @Override + public void setRunningVersion(boolean running) { + // do nothing, the cookie manager has to accept changes. + } + + /** + * Add a cookie. + */ + public void add(Cookie c) { + String cv = c.getValue(); + String cn = c.getName(); + removeMatchingCookies(c); // Can't have two matching cookies + + if (DELETE_NULL_COOKIES && (null == cv || cv.length()==0)) { + if (log.isDebugEnabled()) { + log.debug("Dropping cookie with null value " + c.toString()); + } + } else { + if (log.isDebugEnabled()) { + log.debug("Add cookie to store " + c.toString()); + } + getCookies().addItem(c); + if (SAVE_COOKIES) { + JMeterContext context = getThreadContext(); + if (context.isSamplingStarted()) { + context.getVariables().put(COOKIE_NAME_PREFIX+cn, cv); + } + } + } + } + + /** {@inheritDoc} */ + @Override + public void clear(){ + super.clear(); + clearCookies(); // ensure data is set up OK initially + } + + /* + * Remove all the cookies. + */ + private void clearCookies() { + log.debug("Clear all cookies from store"); + setProperty(new CollectionProperty(COOKIES, new ArrayList())); + } + + /** + * Remove a cookie. + */ + public void remove(int index) {// TODO not used by GUI + getCookies().remove(index); + } + + /** + * Return the cookie at index i. + */ + public Cookie get(int i) {// Only used by GUI + return (Cookie) getCookies().get(i).getObjectValue(); + } + + /** + * Find cookies applicable to the given URL and build the Cookie header from + * them. + * + * @param url + * URL of the request to which the returned header will be added. + * @return the value string for the cookie header (goes after "Cookie: "). + */ + public String getCookieHeaderForURL(URL url) { + return cookieHandler.getCookieHeaderForURL(getCookies(), url, ALLOW_VARIABLE_COOKIES); + } + + + public void addCookieFromHeader(String cookieHeader, URL url){ + cookieHandler.addCookieFromHeader(this, CHECK_COOKIES, cookieHeader, url); + } + /** + * Check if cookies match, i.e. name, path and domain are equal. + *
+ * TODO - should we compare secure too? + * @param a + * @param b + * @return true if cookies match + */ + private boolean match(Cookie a, Cookie b){ + return + a.getName().equals(b.getName()) + && + a.getPath().equals(b.getPath()) + && + a.getDomain().equals(b.getDomain()); + } + + void removeMatchingCookies(Cookie newCookie){ + // Scan for any matching cookies + PropertyIterator iter = getCookies().iterator(); + while (iter.hasNext()) { + Cookie cookie = (Cookie) iter.next().getObjectValue(); + if (cookie == null) {// TODO is this possible? + continue; + } + if (match(cookie,newCookie)) { + if (log.isDebugEnabled()) { + log.debug("New Cookie = " + newCookie.toString() + + " removing matching Cookie " + cookie.toString()); + } + iter.remove(); + } + } + } + + /** {@inheritDoc} */ + public void testStarted() { + initialCookies = getCookies(); + cookieHandler = new HC3CookieHandler(getPolicy()); + if (log.isDebugEnabled()){ + log.debug("Policy: "+getPolicy()+" Clear: "+getClearEachIteration()); + } + } + + /** {@inheritDoc} */ + public void testEnded() { + } + + /** {@inheritDoc} */ + public void testStarted(String host) { + testStarted(); + } + + /** {@inheritDoc} */ + public void testEnded(String host) { + } + + /** {@inheritDoc} */ + public void testIterationStart(LoopIterationEvent event) { + if (getClearEachIteration()) { + log.debug("Initialise cookies from pre-defined list"); + // No need to call clear + setProperty(initialCookies.clone()); + } + } + + /** + * Package protected for tests + * @return the cookieHandler + */ + CookieHandler getCookieHandler() { + return cookieHandler; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HC3CookieHandler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HC3CookieHandler.java new file mode 100644 index 0000000..06e9d58 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HC3CookieHandler.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.net.URL; +import java.util.Date; + +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.cookie.CookieSpec; +import org.apache.commons.httpclient.cookie.MalformedCookieException; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * HTTPClient 3.1 implementation + */ +public class HC3CookieHandler implements CookieHandler { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private transient CookieSpec cookieSpec; + + /** + * + */ + public HC3CookieHandler(String policy) { + super(); + this.cookieSpec = CookiePolicy.getCookieSpec(policy); + } + + /** + * Create an HttpClient cookie from a JMeter cookie + */ + private org.apache.commons.httpclient.Cookie makeCookie(Cookie jmc){ + long exp = jmc.getExpiresMillis(); + org.apache.commons.httpclient.Cookie ret= + new org.apache.commons.httpclient.Cookie( + jmc.getDomain(), + jmc.getName(), + jmc.getValue(), + jmc.getPath(), + exp > 0 ? new Date(exp) : null, // use null for no expiry + jmc.getSecure() + ); + ret.setPathAttributeSpecified(jmc.isPathSpecified()); + ret.setDomainAttributeSpecified(jmc.isDomainSpecified()); + ret.setVersion(jmc.getVersion()); + return ret; + } + /** + * Get array of valid HttpClient cookies for the URL + * + * @param url the target URL + * @return array of HttpClient cookies + * + */ + org.apache.commons.httpclient.Cookie[] getCookiesForUrl( + CollectionProperty cookiesCP, + URL url, + boolean allowVariableCookie){ + org.apache.commons.httpclient.Cookie cookies[]= + new org.apache.commons.httpclient.Cookie[cookiesCP.size()]; + int i=0; + for (PropertyIterator iter = cookiesCP.iterator(); iter.hasNext();) { + Cookie jmcookie = (Cookie) iter.next().getObjectValue(); + // Set to running version, to allow function evaluation for the cookie values (bug 28715) + if (allowVariableCookie) { + jmcookie.setRunningVersion(true); + } + cookies[i++] = makeCookie(jmcookie); + if (allowVariableCookie) { + jmcookie.setRunningVersion(false); + } + } + String host = url.getHost(); + String protocol = url.getProtocol(); + int port= HTTPSamplerBase.getDefaultPort(protocol,url.getPort()); + String path = url.getPath(); + boolean secure = HTTPSamplerBase.isSecure(protocol); + return cookieSpec.match(host, port, path, secure, cookies); + } + + /** + * Find cookies applicable to the given URL and build the Cookie header from + * them. + * + * @param url + * URL of the request to which the returned header will be added. + * @return the value string for the cookie header (goes after "Cookie: "). + */ + public String getCookieHeaderForURL( + CollectionProperty cookiesCP, + URL url, + boolean allowVariableCookie) { + org.apache.commons.httpclient.Cookie[] c = + getCookiesForUrl(cookiesCP, url, allowVariableCookie); + int count = c.length; + boolean debugEnabled = log.isDebugEnabled(); + if (debugEnabled){ + log.debug("Found "+count+" cookies for "+url.toExternalForm()); + } + if (count <=0){ + return null; + } + String hdr=cookieSpec.formatCookieHeader(c).getValue(); + if (debugEnabled){ + log.debug("Cookie: "+hdr); + } + return hdr; + } + + /** + * {@inheritDoc} + */ + public void addCookieFromHeader(CookieManager cookieManager, + boolean checkCookies,String cookieHeader, URL url){ + boolean debugEnabled = log.isDebugEnabled(); + if (debugEnabled) { + log.debug("Received Cookie: " + cookieHeader + " From: " + url.toExternalForm()); + } + String protocol = url.getProtocol(); + String host = url.getHost(); + int port= HTTPSamplerBase.getDefaultPort(protocol,url.getPort()); + String path = url.getPath(); + boolean isSecure=HTTPSamplerBase.isSecure(protocol); + org.apache.commons.httpclient.Cookie[] cookies= null; + try { + cookies = cookieSpec.parse(host, port, path, isSecure, cookieHeader); + } catch (MalformedCookieException e) { + log.warn(cookieHeader+e.getLocalizedMessage()); + } catch (IllegalArgumentException e) { + log.warn(cookieHeader+e.getLocalizedMessage()); + } + if (cookies == null) { + return; + } + for(org.apache.commons.httpclient.Cookie cookie : cookies){ + try { + if (checkCookies) { + cookieSpec.validate(host, port, path, isSecure, cookie); + } + Date expiryDate = cookie.getExpiryDate(); + long exp = 0; + if (expiryDate!= null) { + exp=expiryDate.getTime(); + } + Cookie newCookie = new Cookie( + cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getSecure(), + exp / 1000, + cookie.isPathAttributeSpecified(), + cookie.isDomainAttributeSpecified() + ); + + // Store session cookies as well as unexpired ones + if (exp == 0 || exp >= System.currentTimeMillis()) { + newCookie.setVersion(cookie.getVersion()); + cookieManager.add(newCookie); // Has its own debug log; removes matching cookies + } else { + cookieManager.removeMatchingCookies(newCookie); + if (debugEnabled){ + log.debug("Dropping expired Cookie: "+newCookie.toString()); + } + } + } catch (MalformedCookieException e) { // This means the cookie was wrong for the URL + log.warn("Not storing invalid cookie: <"+cookieHeader+"> for URL "+url+" ("+e.getLocalizedMessage()+")"); + } catch (IllegalArgumentException e) { + log.warn(cookieHeader+e.getLocalizedMessage()); + } + } + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Header.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Header.java new file mode 100644 index 0000000..6301d7b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/Header.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.io.Serializable; + +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.testelement.AbstractTestElement; + +/** + * This class is an HTTP Header encapsulator. + * + */ +public class Header extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + private static final String HNAME = "Header.name"; //$NON-NLS-1$ + // See TestElementPropertyConverter + + private static final String VALUE = "Header.value"; //$NON-NLS-1$ + + /** + * Create the header. + */ + public Header() { + this.setName(""); + this.setValue(""); + } + + /** + * Create the coookie. + */ + public Header(String name, String value) { + this.setName(name); + this.setValue(value); + } + + public void addConfigElement(ConfigElement config) { + } + + public boolean expectsModification() { + return false; + } + + /** + * Get the name for this object. + */ + @Override + public String getName() { + return getPropertyAsString(HNAME); + } + + /** + * Set the name for this object. + */ + @Override + public void setName(String name) { + this.setProperty(HNAME, name); + } + + /** + * Get the value for this object. + */ + public String getValue() { + return getPropertyAsString(VALUE); + } + + /** + * Set the value for this object. + */ + public void setValue(String value) { + this.setProperty(VALUE, value); + } + + /** + * Creates a string representation of this header. + */ + @Override + public String toString() { + return getName() + "\t" + getValue(); //$NON-NLS-1$ + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HeaderManager.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HeaderManager.java new file mode 100644 index 0000000..f96a61c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HeaderManager.java @@ -0,0 +1,283 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * This class provides an interface to headers file to pass HTTP headers along + * with a request. + * + * @version $Revision: 1225789 $ + */ +public class HeaderManager extends ConfigTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + public static final String HEADERS = "HeaderManager.headers";// $NON-NLS-1$ + + private final static String[] COLUMN_RESOURCE_NAMES = { + "name", // $NON-NLS-1$ + "value" // $NON-NLS-1$ + }; + + private final static int COLUMN_COUNT = COLUMN_RESOURCE_NAMES.length; + + + /** + * Apache SOAP driver does not provide an easy way to get and set the cookie + * or HTTP header. Therefore it is necessary to store the SOAPHTTPConnection + * object and reuse it. + */ + private Object SOAPHeader = null; + + public HeaderManager() { + setProperty(new CollectionProperty(HEADERS, new ArrayList())); + } + + /** {@inheritDoc} */ + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(HEADERS, new ArrayList())); + } + + /** + * Get the collection of JMeterProperty entries representing the headers. + * + * @return the header collection property + */ + public CollectionProperty getHeaders() { + return (CollectionProperty) getProperty(HEADERS); + } + + public int getColumnCount() { + return COLUMN_COUNT; + } + + public String getColumnName(int column) { + return COLUMN_RESOURCE_NAMES[column]; + } + + public Class getColumnClass(int column) { + return COLUMN_RESOURCE_NAMES[column].getClass(); + } + + public Header getHeader(int row) { + return (Header) getHeaders().get(row).getObjectValue(); + } + + /** + * Save the header data to a file. + */ + public void save(String headFile) throws IOException { + File file = new File(headFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir")// $NON-NLS-1$ + + File.separator + headFile); + } + PrintWriter writer = new PrintWriter(new FileWriter(file)); // TODO Charset ? + writer.println("# JMeter generated Header file");// $NON-NLS-1$ + final CollectionProperty hdrs = getHeaders(); + for (int i = 0; i < hdrs.size(); i++) { + final JMeterProperty hdr = hdrs.get(i); + Header head = (Header) hdr.getObjectValue(); + writer.println(head.toString()); + } + writer.flush(); + writer.close(); + } + + /** + * Add header data from a file. + */ + public void addFile(String headerFile) throws IOException { + File file = new File(headerFile); + if (!file.isAbsolute()) { + file = new File(System.getProperty("user.dir")// $NON-NLS-1$ + + File.separator + headerFile); + } + if (!file.canRead()) { + throw new IOException("The file you specified cannot be read."); + } + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(file)); // TODO Charset ? + String line; + while ((line = reader.readLine()) != null) { + try { + if (line.startsWith("#") || line.trim().length() == 0) {// $NON-NLS-1$ + continue; + } + String[] st = JOrphanUtils.split(line, "\t", " ");// $NON-NLS-1$ $NON-NLS-2$ + int name = 0; + int value = 1; + Header header = new Header(st[name], st[value]); + getHeaders().addItem(header); + } catch (Exception e) { + throw new IOException("Error parsing header line\n\t'" + line + "'\n\t" + e); + } + } + } finally { + IOUtils.closeQuietly(reader); + } + } + + /** + * Add a header. + */ + public void add(Header h) { + getHeaders().addItem(h); + } + + /** + * Add an empty header. + */ + public void add() { + getHeaders().addItem(new Header()); + } + + /** + * Remove a header. + */ + public void remove(int index) { + getHeaders().remove(index); + } + + /** + * Return the number of headers. + */ + public int size() { + return getHeaders().size(); + } + + /** + * Return the header at index i. + */ + public Header get(int i) { + return (Header) getHeaders().get(i).getObjectValue(); + } + + /** + * Remove from Headers the header named name + * @param name header name + */ + public void removeHeaderNamed(String name) { + List removeIndices = new ArrayList(); + for (int i = getHeaders().size() - 1; i >= 0; i--) { + Header header = (Header) getHeaders().get(i).getObjectValue(); + if (header == null) { + continue; + } + if (header.getName().equalsIgnoreCase(name)) { + removeIndices.add(Integer.valueOf(i)); + } + } + for (Integer indice : removeIndices) { + getHeaders().remove(indice.intValue()); + } + } + + /** + * Added support for SOAP related header stuff. 1-29-04 Peter Lin + * + * @return the SOAP header Object + */ + public Object getSOAPHeader() { + return this.SOAPHeader; + } + + /** + * Set the SOAPHeader with the SOAPHTTPConnection object. We may or may not + * want to rename this to setHeaderObject(Object). Concievably, other + * samplers may need this kind of functionality. 1-29-04 Peter Lin + * + * @param header + */ + public void setSOAPHeader(Object header) { + this.SOAPHeader = header; + } + + /** + * Merge the attributes with a another HeaderManager's attributes. + * @param element The object to be merged with + * @param preferLocalValues When both objects have a value for the + * same attribute, this flag determines which value is preferresd. + */ + public HeaderManager merge(TestElement element, boolean preferLocalValues) { + if (!(element instanceof HeaderManager)) { + throw new IllegalArgumentException("Cannot merge type:" + this.getClass().getName() + " with type:" + element.getClass().getName()); + } + + // start off with a merged object as a copy of the local object + HeaderManager merged = (HeaderManager)this.clone(); + + HeaderManager other = (HeaderManager)element; + // iterate thru each of the other headers + for (int i = 0; i < other.getHeaders().size(); i++) { + Header otherHeader = other.get(i); + boolean found = false; + // find the same property in the local headers + for (int j = 0; j < merged.getHeaders().size(); j++) { + Header mergedHeader = merged.get(j); + if (mergedHeader.getName().equalsIgnoreCase(otherHeader.getName())) { + // we have a match + found = true; + if (!preferLocalValues) { + // prefer values from the other object + if ( (otherHeader.getValue() == null) || (otherHeader.getValue().length() == 0) ) { + // the other object has an empty value, so remove this value from the merged object + merged.remove(j); + } else { + // use the other object's value + mergedHeader.setValue(otherHeader.getValue()); + } + } + // break out of the inner loop + break; + } + } + if (!found) { + // the other object has a new value to be added to the merged + merged.add(otherHeader); + } + } + + // finally, merge the names so it's clear they've been merged + merged.setName(merged.getName() + ":" + other.getName()); + + return merged; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorControl.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorControl.java new file mode 100644 index 0000000..1d90b21 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorControl.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.IntegerProperty; + +//For unit tests, @see TestHttpMirrorControl + +/** + * Test element that implements the Workbench HTTP Mirror function + */ +public class HttpMirrorControl extends AbstractTestElement { + + private static final long serialVersionUID = 233L; + + private transient HttpMirrorServer server; + + // Used by HttpMirrorServer + static final int DEFAULT_PORT = 8081; + + // and as a string + public static final String DEFAULT_PORT_S = + Integer.toString(DEFAULT_PORT);// Used by GUI + + public static final String PORT = "HttpMirrorControlGui.port"; // $NON-NLS-1$ + + public static final String MAX_POOL_SIZE = "HttpMirrorControlGui.maxPoolSize"; // $NON-NLS-1$ + + public static final String MAX_QUEUE_SIZE = "HttpMirrorControlGui.maxQueueSize"; // $NON-NLS-1$ + + public static final int DEFAULT_MAX_POOL_SIZE = 0; + + public static final int DEFAULT_MAX_QUEUE_SIZE = 25; + + + public HttpMirrorControl() { + initPort(DEFAULT_PORT); + } + + public HttpMirrorControl(int port) { + initPort(port); + } + + private void initPort(int port){ + setProperty(new IntegerProperty(PORT, port)); + } + + public void setPort(int port) { + initPort(port); + } + + public void setPort(String port) { + setProperty(PORT, port); + } + + public int getPort() { + return getPropertyAsInt(PORT); + } + + public String getPortString() { + return getPropertyAsString(PORT); + } + + /** + * @return Max Thread Pool size + */ + public String getMaxPoolSizeAsString() { + return getPropertyAsString(MAX_POOL_SIZE); + } + + /** + * @return Max Thread Pool size + */ + private int getMaxPoolSize() { + return getPropertyAsInt(MAX_POOL_SIZE, DEFAULT_MAX_POOL_SIZE); + } + + /** + * @param maxPoolSize Max Thread Pool size + */ + public void setMaxPoolSize(String maxPoolSize) { + setProperty(MAX_POOL_SIZE, maxPoolSize); + } + + /** + * @return Max Queue size + */ + public String getMaxQueueSizeAsString() { + return getPropertyAsString(MAX_QUEUE_SIZE); + } + + /** + * @return Max Queue size + */ + private int getMaxQueueSize() { + return getPropertyAsInt(MAX_QUEUE_SIZE, DEFAULT_MAX_QUEUE_SIZE); + } + + /** + * @param maxQueueSize Max Queue size + */ + public void setMaxQueueSize(String maxQueueSize) { + setProperty(MAX_QUEUE_SIZE, maxQueueSize); + } + + public int getDefaultPort() { + return DEFAULT_PORT; + } + + public void startHttpMirror() { + server = new HttpMirrorServer(getPort(), getMaxPoolSize(), getMaxQueueSize()); + server.start(); + GuiPackage instance = GuiPackage.getInstance(); + if (instance != null) { + instance.register(server); + } + } + + public void stopHttpMirror() { + if (server != null) { + server.stopServer(); + GuiPackage instance = GuiPackage.getInstance(); + if (instance != null) { + instance.unregister(server); + } + try { + server.join(1000); // wait for server to stop + } catch (InterruptedException e) { + } + server = null; + } + } + + @Override + public boolean canRemove() { + return null == server; + } + + public boolean isServerAlive(){ + return server != null && server.isAlive(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorServer.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorServer.java new file mode 100644 index 0000000..5f73aa5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorServer.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.io.InterruptedIOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.jmeter.gui.Stoppable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Server daemon thread. + * Creates main socket and listens on it. + * For each client request, creates a thread to handle the request. + * + */ +public class HttpMirrorServer extends Thread implements Stoppable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * The time (in milliseconds) to wait when accepting a client connection. + * The accept will be retried until the Daemon is told to stop. So this + * interval is the longest time that the Daemon will have to wait after + * being told to stop. + */ + private static final int ACCEPT_TIMEOUT = 1000; + + private static final long KEEP_ALIVE_TIME = 10; + + /** The port to listen on. */ + private final int daemonPort; + + /** True if the Daemon is currently running. */ + private volatile boolean running; + + // Saves the error if one occurs + private volatile Exception except; + + /** + * Max Executor Pool size + */ + private int maxThreadPoolSize; + + /** + * Max Queue size + */ + private int maxQueueSize; + + /** + * Create a new Daemon with the specified port and target. + * + * @param port + * the port to listen on. + */ + public HttpMirrorServer(int port) { + this(port, HttpMirrorControl.DEFAULT_MAX_POOL_SIZE, HttpMirrorControl.DEFAULT_MAX_QUEUE_SIZE); + } + + /** + * Create a new Daemon with the specified port and target. + * + * @param port + * the port to listen on. + * @param maxThreadPoolSize Max Thread pool size + * @param maxQueueSize Max Queue size + */ + public HttpMirrorServer(int port, int maxThreadPoolSize, int maxQueueSize) { + super("HttpMirrorServer"); + this.daemonPort = port; + this.maxThreadPoolSize = maxThreadPoolSize; + this.maxQueueSize = maxQueueSize; + } + + /** + * Listen on the daemon port and handle incoming requests. This method will + * not exit until {@link #stopServer()} is called or an error occurs. + */ + @Override + public void run() { + except = null; + running = true; + ServerSocket mainSocket = null; + ThreadPoolExecutor threadPoolExecutor = null; + if(maxThreadPoolSize>0) { + final ArrayBlockingQueue queue = new ArrayBlockingQueue( + maxQueueSize); + threadPoolExecutor = new ThreadPoolExecutor( + maxThreadPoolSize/2, + maxThreadPoolSize, KEEP_ALIVE_TIME, TimeUnit.SECONDS, queue); + threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); + } + try { + log.info("Creating HttpMirror ... on port " + daemonPort); + mainSocket = new ServerSocket(daemonPort); + mainSocket.setSoTimeout(ACCEPT_TIMEOUT); + log.info("HttpMirror up and running!"); + while (running) { + try { + // Listen on main socket + Socket clientSocket = mainSocket.accept(); + if (running) { + // Pass request to new thread + if(threadPoolExecutor != null) { + threadPoolExecutor.execute(new HttpMirrorThread(clientSocket)); + } else { + Thread thd = new Thread(new HttpMirrorThread(clientSocket)); + log.debug("Starting new Mirror thread"); + thd.start(); + } + } else { + log.warn("Server not running"); + JOrphanUtils.closeQuietly(clientSocket); + } + } catch (InterruptedIOException e) { + // Timeout occurred. Ignore, and keep looping until we're + // told to stop running. + } + } + log.info("HttpMirror Server stopped"); + } catch (Exception e) { + except = e; + log.warn("HttpMirror Server stopped", e); + } finally { + if(threadPoolExecutor != null) { + threadPoolExecutor.shutdownNow(); + } + JOrphanUtils.closeQuietly(mainSocket); + } + } + + public void stopServer() { + running = false; + } + + public Exception getException(){ + return except; + } + + public static void main(String args[]){ + int port = HttpMirrorControl.DEFAULT_PORT; + if (args.length > 0){ + port = Integer.parseInt(args[0]); + } + LoggingManager.setPriority("INFO"); // default level + LoggingManager.setLoggingLevels(System.getProperties() ); // allow override by system properties + HttpMirrorServer serv = new HttpMirrorServer(port); + serv.start(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorThread.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorThread.java new file mode 100644 index 0000000..93586c7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/HttpMirrorThread.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.Socket; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * Thread to handle one client request. Gets the request from the client and + * sends the response back to the client. + */ +public class HttpMirrorThread implements Runnable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ISO_8859_1 = "ISO-8859-1"; //$NON-NLS-1$ + private static final byte[] CRLF = { 0x0d, 0x0a }; + + /** Socket to client. */ + private final Socket clientSocket; + + public HttpMirrorThread(Socket _clientSocket) { + this.clientSocket=_clientSocket; + } + + /** + * Main processing method for the HttpMirror object + */ + public void run() { + log.debug("Starting thread"); + BufferedInputStream in = null; + BufferedOutputStream out = null; + + try { + in = new BufferedInputStream(clientSocket.getInputStream()); + + // Read the header part, we will be looking for a content-length + // header, so we know how much we should read. + // We assume headers are in ISO_8859_1 + // If we do not find such a header, we will just have to read until + // we have to block to read more, until we support chunked transfer + int contentLength = -1; + boolean isChunked = false; + byte[] buffer = new byte[1024]; + StringBuilder headers = new StringBuilder(); + int length = 0; + int positionOfBody = 0; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while(positionOfBody <= 0 && ((length = in.read(buffer)) != -1)) { + log.debug("Write body"); + baos.write(buffer, 0, length); // echo back + headers.append(new String(buffer, 0, length, ISO_8859_1)); + // Check if we have read all the headers + positionOfBody = getPositionOfBody(headers.toString()); + } + + baos.close(); + final String headerString = headers.toString(); + + log.debug("Write headers"); + out = new BufferedOutputStream(clientSocket.getOutputStream()); + // The headers are written using ISO_8859_1 encoding + out.write("HTTP/1.0 200 OK".getBytes(ISO_8859_1)); //$NON-NLS-1$ + out.write(CRLF); + out.write("Content-Type: text/plain".getBytes(ISO_8859_1)); //$NON-NLS-1$ + out.write(CRLF); + // Look for special Cookie request + String cookieHeaderValue = getRequestHeaderValue(headerString, "X-SetCookie"); //$NON-NLS-1$ + if (cookieHeaderValue != null) { + out.write("Set-Cookie: ".getBytes(ISO_8859_1)); + out.write(cookieHeaderValue.getBytes(ISO_8859_1)); + out.write(CRLF); + } + out.write(CRLF); + out.flush(); + + out.write(baos.toByteArray()); + + // Check if we have found a content-length header + String contentLengthHeaderValue = getRequestHeaderValue(headerString, "Content-Length"); //$NON-NLS-1$ + if(contentLengthHeaderValue != null) { + contentLength = Integer.valueOf(contentLengthHeaderValue).intValue(); + } + // Look for special Sleep request + String sleepHeaderValue = getRequestHeaderValue(headerString, "X-Sleep"); //$NON-NLS-1$ + if(sleepHeaderValue != null) { + Thread.sleep(Integer.parseInt(sleepHeaderValue)); + } + String transferEncodingHeaderValue = getRequestHeaderValue(headerString, "Transfer-Encoding"); //$NON-NLS-1$ + if(transferEncodingHeaderValue != null) { + isChunked = transferEncodingHeaderValue.equalsIgnoreCase("chunked"); //$NON-NLS-1$ + // We only support chunked transfer encoding + if(!isChunked) { + log.error("Transfer-Encoding header set, the value is not supported : " + transferEncodingHeaderValue); + } + } + + // If we know the content length, we can allow the reading of + // the request to block until more data arrives. + // If it is chunked transfer, we cannot allow the reading to + // block, because we do not know when to stop reading, because + // the chunked transfer is not properly supported yet + length = 0; + if(contentLength > 0) { + // Check how much of the body we have already read as part of reading + // the headers + // We subtract two bytes for the crlf divider between header and body + int totalReadBytes = headerString.length() - positionOfBody - 2; + + // We know when to stop reading, so we can allow the read method to block + log.debug("Reading, "+totalReadBytes+" < " +contentLength); + while((totalReadBytes < contentLength) && ((length = in.read(buffer)) != -1)) { + log.debug("Read bytes: "+length); + out.write(buffer, 0, length); + + totalReadBytes += length; + log.debug("totalReadBytes: "+totalReadBytes); + } + } + else if (isChunked) { + // It is chunked transfer encoding, which we do not really support yet. + // So we just read without blocking, because we do not know when to + // stop reading, so we cannot block + // TODO propery implement support for chunked transfer, i.e. to + // know when we have read the whole request, and therefore allow + // the reading to block + log.debug("Chunked"); + while(in.available() > 0 && ((length = in.read(buffer)) != -1)) { + out.write(buffer, 0, length); + } + } + else { + // The reqest has no body, or it has a transfer encoding we do not support. + // In either case, we read any data available + log.debug("Other"); + while(in.available() > 0 && ((length = in.read(buffer)) != -1)) { + log.debug("Read bytes: "+length); + out.write(buffer, 0, length); + } + } + log.debug("Flush"); + out.flush(); + } catch (IOException e) { + log.error("", e); + } catch (InterruptedException e) { + log.error("", e); + } finally { + JOrphanUtils.closeQuietly(out); + JOrphanUtils.closeQuietly(in); + JOrphanUtils.closeQuietly(clientSocket); + } + log.debug("End of Thread"); + } + + private static String getRequestHeaderValue(String requestHeaders, String headerName) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + // We use multi-line mask so can prefix the line with ^ + String expression = "^" + headerName + ":\\s+([^\\r\\n]+)"; // $NON-NLS-1$ $NON-NLS-2$ + Pattern pattern = JMeterUtils.getPattern(expression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.MULTILINE_MASK); + if(localMatcher.contains(requestHeaders, pattern)) { + // The value is in the first group, group 0 is the whole match +// System.out.println("Found:'"+localMatcher.getMatch().group(1)+"'"); +// System.out.println("in: '"+localMatcher.getMatch().group(0)+"'"); + return localMatcher.getMatch().group(1); + } + else { + return null; + } + } + + private static int getPositionOfBody(String stringToCheck) { + Perl5Matcher localMatcher = JMeterUtils.getMatcher(); + // The headers and body are divided by a blank line (the \r is to allow for the CR before LF) + String regularExpression = "^\\r$"; // $NON-NLS-1$ + Pattern pattern = JMeterUtils.getPattern(regularExpression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.MULTILINE_MASK); + + PatternMatcherInput input = new PatternMatcherInput(stringToCheck); + if(localMatcher.contains(input, pattern)) { + MatchResult match = localMatcher.getMatch(); + return match.beginOffset(0); + } + // No divider was found + return -1; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/RecordingController.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/RecordingController.java new file mode 100644 index 0000000..fd7b53e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/RecordingController.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control; + +import org.apache.jmeter.control.GenericController; + +public class RecordingController extends GenericController { + private static final long serialVersionUID = 240L; + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/AjpSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/AjpSamplerGui.java new file mode 100644 index 0000000..92b3099 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/AjpSamplerGui.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.jmeter.protocol.http.control.gui; + +import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui; +import org.apache.jmeter.protocol.http.sampler.AjpSampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class AjpSamplerGui extends HttpTestSampleGui { + + private static final long serialVersionUID = 240L; + + public AjpSamplerGui() { + super(true); + } + + @Override + public TestElement createTestElement() { + AjpSampler sampler = new AjpSampler(); + modifyTestElement(sampler); + return sampler; + } + + // Use this instead of getLabelResource() otherwise getDocAnchor() below does not work + @Override + public String getStaticLabel() { + return JMeterUtils.getResString("ajp_sampler_title"); // $NON-NLS-1$ + } + + @Override + public String getDocAnchor() {// reuse documentation + return super.getStaticLabel().replace(' ', '_'); //$NON-NLS-1$ //$NON-NLS-2$ + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/HttpMirrorControlGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/HttpMirrorControlGui.java new file mode 100644 index 0000000..7353f87 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/HttpMirrorControlGui.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.gui.LogicControllerGui; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.protocol.http.control.HttpMirrorControl; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class HttpMirrorControlGui extends LogicControllerGui + implements JMeterGUIComponent, ActionListener, UnsharedComponent { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private JTextField portField; + + private JTextField maxPoolSizeField; + + private JTextField maxQueueSizeField; + + private JButton stop, start; + + private static final String ACTION_STOP = "stop"; // $NON-NLS-1$ + + private static final String ACTION_START = "start"; // $NON-NLS-1$ + + private HttpMirrorControl mirrorController; + + + public HttpMirrorControlGui() { + super(); + log.debug("Creating HttpMirrorControlGui"); + init(); + } + + @Override + public TestElement createTestElement() { + mirrorController = new HttpMirrorControl(); + log.debug("creating/configuring model = " + mirrorController); + modifyTestElement(mirrorController); + return mirrorController; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement el) { + configureTestElement(el); + if (el instanceof HttpMirrorControl) { + mirrorController = (HttpMirrorControl) el; + mirrorController.setPort(portField.getText()); + mirrorController.setMaxPoolSize(maxPoolSizeField.getText()); + mirrorController.setMaxQueueSize(maxQueueSizeField.getText()); + } + } + + @Override + public String getLabelResource() { + return "httpmirror_title"; // $NON-NLS-1$ + } + + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.NON_TEST_ELEMENTS }); + } + + @Override + public void configure(TestElement element) { + log.debug("Configuring gui with " + element); + super.configure(element); + mirrorController = (HttpMirrorControl) element; + portField.setText(mirrorController.getPortString()); + maxPoolSizeField.setText(mirrorController.getMaxPoolSizeAsString()); + maxQueueSizeField.setText(mirrorController.getMaxQueueSizeAsString()); + repaint(); + } + + + public void actionPerformed(ActionEvent action) { + String command = action.getActionCommand(); + + if (command.equals(ACTION_STOP)) { + mirrorController.stopHttpMirror(); + stop.setEnabled(false); + start.setEnabled(true); + } else if (command.equals(ACTION_START)) { + modifyTestElement(mirrorController); + mirrorController.startHttpMirror(); + start.setEnabled(false); + stop.setEnabled(true); + } + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + Box myBox = Box.createVerticalBox(); + myBox.add(createPortPanel()); + mainPanel.add(myBox, BorderLayout.NORTH); + + mainPanel.add(createControls(), BorderLayout.CENTER); + + add(mainPanel, BorderLayout.CENTER); + } + + private JPanel createControls() { + start = new JButton(JMeterUtils.getResString("start")); // $NON-NLS-1$ + start.addActionListener(this); + start.setActionCommand(ACTION_START); + start.setEnabled(true); + + stop = new JButton(JMeterUtils.getResString("stop")); // $NON-NLS-1$ + stop.addActionListener(this); + stop.setActionCommand(ACTION_STOP); + stop.setEnabled(false); + + JPanel panel = new JPanel(); + panel.add(start); + panel.add(stop); + return panel; + } + + private JPanel createPortPanel() { + portField = new JTextField(HttpMirrorControl.DEFAULT_PORT_S, 8); + portField.setName(HttpMirrorControl.PORT); + + JLabel label = new JLabel(JMeterUtils.getResString("port")); // $NON-NLS-1$ + label.setLabelFor(portField); + + maxPoolSizeField = new JTextField(Integer.toString(HttpMirrorControl.DEFAULT_MAX_POOL_SIZE), 8); + maxPoolSizeField.setName(HttpMirrorControl.MAX_POOL_SIZE); + + JLabel mpsLabel = new JLabel(JMeterUtils.getResString("httpmirror_max_pool_size")); // $NON-NLS-1$ + mpsLabel.setLabelFor(maxPoolSizeField); + + maxQueueSizeField = new JTextField(Integer.toString(HttpMirrorControl.DEFAULT_MAX_QUEUE_SIZE), 8); + maxQueueSizeField.setName(HttpMirrorControl.MAX_QUEUE_SIZE); + + JLabel mqsLabel = new JLabel(JMeterUtils.getResString("httpmirror_max_queue_size")); // $NON-NLS-1$ + mqsLabel.setLabelFor(maxQueueSizeField); + + HorizontalPanel panel = new HorizontalPanel(); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("httpmirror_settings"))); // $NON-NLS-1$ + + panel.add(label); + panel.add(portField); + + panel.add(mpsLabel); + panel.add(maxPoolSizeField); + + panel.add(mqsLabel); + panel.add(maxQueueSizeField); + + panel.add(Box.createHorizontalStrut(10)); + + return panel; + } + + @Override + public void clearGui(){ + super.clearGui(); + portField.setText(HttpMirrorControl.DEFAULT_PORT_S); + maxPoolSizeField.setText(Integer.toString(HttpMirrorControl.DEFAULT_MAX_POOL_SIZE)); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java new file mode 100644 index 0000000..eb4b036 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java @@ -0,0 +1,249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.http.config.gui.MultipartUrlConfigGui; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; + +//For unit tests, @see TestHttpTestSampleGui + +/** + * HTTP Sampler GUI + * + */ +public class HttpTestSampleGui extends AbstractSamplerGui + implements ItemListener { + private static final long serialVersionUID = 240L; + + private MultipartUrlConfigGui urlConfigGui; + + private JCheckBox getImages; + + private JCheckBox concurrentDwn; + + private JTextField concurrentPool; + + private JCheckBox isMon; + + private JCheckBox useMD5; + + private JLabeledTextField embeddedRE; // regular expression used to match against embedded resource URLs + + private JLabeledTextField sourceIpAddr; // does not apply to Java implementation + + private final boolean isAJP; + + public HttpTestSampleGui() { + isAJP = false; + init(); + } + + // For use by AJP + protected HttpTestSampleGui(boolean ajp) { + isAJP = ajp; + init(); + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement element) { + super.configure(element); + final HTTPSamplerBase samplerBase = (HTTPSamplerBase) element; + urlConfigGui.configure(element); + getImages.setSelected(samplerBase.isImageParser()); + concurrentDwn.setSelected(samplerBase.isConcurrentDwn()); + concurrentPool.setText(samplerBase.getConcurrentPool()); + isMon.setSelected(samplerBase.isMonitor()); + useMD5.setSelected(samplerBase.useMD5()); + embeddedRE.setText(samplerBase.getEmbeddedUrlRE()); + if (!isAJP) { + sourceIpAddr.setText(samplerBase.getIpSource()); + } + } + + /** + * {@inheritDoc} + */ + public TestElement createTestElement() { + HTTPSamplerBase sampler = new HTTPSamplerProxy(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + *

+ * {@inheritDoc} + */ + public void modifyTestElement(TestElement sampler) { + sampler.clear(); + urlConfigGui.modifyTestElement(sampler); + final HTTPSamplerBase samplerBase = (HTTPSamplerBase) sampler; + samplerBase.setImageParser(getImages.isSelected()); + enableConcurrentDwn(getImages.isSelected()); + samplerBase.setConcurrentDwn(concurrentDwn.isSelected()); + samplerBase.setConcurrentPool(concurrentPool.getText()); + samplerBase.setMonitor(isMon.isSelected()); + samplerBase.setMD5(useMD5.isSelected()); + samplerBase.setEmbeddedUrlRE(embeddedRE.getText()); + if (!isAJP) { + samplerBase.setIpSource(sourceIpAddr.getText()); + } + this.configureTestElement(sampler); + } + + /** + * {@inheritDoc} + */ + public String getLabelResource() { + return "web_testing_title"; // $NON-NLS-1$ + } + + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + // URL CONFIG + urlConfigGui = new MultipartUrlConfigGui(true, !isAJP); + add(urlConfigGui, BorderLayout.CENTER); + + // OPTIONAL TASKS + add(createOptionalTasksPanel(), BorderLayout.SOUTH); + } + + protected JPanel createOptionalTasksPanel() { + // OPTIONAL TASKS + final JPanel optionalTasksPanel = new VerticalPanel(); + optionalTasksPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("optional_tasks"))); // $NON-NLS-1$ + + final JPanel checkBoxPanel = new HorizontalPanel(); + // RETRIEVE IMAGES + getImages = new JCheckBox(JMeterUtils.getResString("web_testing_retrieve_images")); // $NON-NLS-1$ + // add a listener to activate or not concurrent dwn. + getImages.addItemListener(new ItemListener() { + public void itemStateChanged(final ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { enableConcurrentDwn(true); } + else { enableConcurrentDwn(false); } + } + }); + // Download concurrent resources + concurrentDwn = new JCheckBox(JMeterUtils.getResString("web_testing_concurrent_download")); // $NON-NLS-1$ + concurrentDwn.addItemListener(new ItemListener() { + public void itemStateChanged(final ItemEvent e) { + if (getImages.isSelected() && e.getStateChange() == ItemEvent.SELECTED) { concurrentPool.setEnabled(true); } + else { concurrentPool.setEnabled(false); } + } + }); + concurrentPool = new JTextField(2); // 2 column size + concurrentPool.setMaximumSize(new Dimension(30,20)); + // Is monitor + isMon = new JCheckBox(JMeterUtils.getResString("monitor_is_title")); // $NON-NLS-1$ + // Use MD5 + useMD5 = new JCheckBox(JMeterUtils.getResString("response_save_as_md5")); // $NON-NLS-1$ + + checkBoxPanel.add(getImages); + checkBoxPanel.add(concurrentDwn); + checkBoxPanel.add(concurrentPool); + checkBoxPanel.add(isMon); + checkBoxPanel.add(useMD5); + optionalTasksPanel.add(checkBoxPanel); + + // Embedded URL match regex + embeddedRE = new JLabeledTextField(JMeterUtils.getResString("web_testing_embedded_url_pattern"),30); // $NON-NLS-1$ + optionalTasksPanel.add(embeddedRE, BorderLayout.CENTER); + + if (!isAJP) { + // Add a new field source ip address (for HC implementations only) + sourceIpAddr = new JLabeledTextField(JMeterUtils.getResString("web_testing2_source_ip")); // $NON-NLS-1$ + optionalTasksPanel.add(sourceIpAddr, BorderLayout.EAST); + } + + return optionalTasksPanel; + } + + /** + * {@inheritDoc} + */ + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + getImages.setSelected(false); + concurrentDwn.setSelected(false); + concurrentPool.setText(String.valueOf(HTTPSamplerBase.CONCURRENT_POOL_SIZE)); + enableConcurrentDwn(false); + isMon.setSelected(false); + useMD5.setSelected(false); + urlConfigGui.clear(); + embeddedRE.setText(""); // $NON-NLS-1$ + if (!isAJP) { + sourceIpAddr.setText(""); // $NON-NLS-1$ + } + } + + private void enableConcurrentDwn(boolean enable) { + if (enable) { + concurrentDwn.setEnabled(true); + if (concurrentDwn.isSelected()) { + concurrentPool.setEnabled(true); + } + } else { + concurrentDwn.setEnabled(false); + concurrentPool.setEnabled(false); + } + } + + public void itemStateChanged(ItemEvent event) { + if (event.getStateChange() == ItemEvent.SELECTED) { + enableConcurrentDwn(true); + } else { + enableConcurrentDwn(false); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/RecordController.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/RecordController.java new file mode 100644 index 0000000..aee8ba5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/RecordController.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.jmeter.protocol.http.control.gui; + +import org.apache.jmeter.control.gui.LogicControllerGui; +import org.apache.jmeter.protocol.http.control.RecordingController; +import org.apache.jmeter.testelement.TestElement; + +public class RecordController extends LogicControllerGui { + private static final long serialVersionUID = 240L; + + @Override + public String getLabelResource() { + return "record_controller_title"; // $NON-NLS-1$ + } + + @Override + public TestElement createTestElement() { + RecordingController con = new RecordingController(); + this.configureTestElement(con); + return con; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/SoapSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/SoapSamplerGui.java new file mode 100644 index 0000000..e354f4c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/SoapSamplerGui.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridBagLayout; +import java.awt.GridBagConstraints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBox; +import javax.swing.JPanel; + +import org.apache.jmeter.protocol.http.sampler.SoapSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jorphan.gui.JLabeledTextArea; +import org.apache.jorphan.gui.JLabeledTextField; + +public class SoapSamplerGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + private JLabeledTextField urlField; + private JLabeledTextField soapAction; + private JCheckBox sendSoapAction; + private JCheckBox useKeepAlive; + private JLabeledTextArea soapXml; + + private FilePanel soapXmlFile = new FilePanel(); + + public SoapSamplerGui() { + init(); + } + + public String getLabelResource() { + return "soap_sampler_title"; //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + public TestElement createTestElement() { + SoapSampler sampler = new SoapSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement s) { + this.configureTestElement(s); + if (s instanceof SoapSampler) { + SoapSampler sampler = (SoapSampler) s; + sampler.setURLData(urlField.getText()); + sampler.setXmlData(soapXml.getText()); + sampler.setXmlFile(soapXmlFile.getFilename()); + sampler.setSOAPAction(soapAction.getText()); + sampler.setSendSOAPAction(sendSoapAction.isSelected()); + sampler.setUseKeepAlive(useKeepAlive.isSelected()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + urlField.setText(""); //$NON-NLS-1$ + soapAction.setText(""); //$NON-NLS-1$ + soapXml.setText(""); //$NON-NLS-1$ + sendSoapAction.setSelected(true); + soapXmlFile.setFilename(""); //$NON-NLS-1$ + useKeepAlive.setSelected(false); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + urlField = new JLabeledTextField(JMeterUtils.getResString("url"), 10); //$NON-NLS-1$ + soapXml = new JLabeledTextArea(JMeterUtils.getResString("soap_data_title")); //$NON-NLS-1$ + soapAction = new JLabeledTextField("", 10); //$NON-NLS-1$ + sendSoapAction = new JCheckBox(JMeterUtils.getResString("soap_send_action"), true); //$NON-NLS-1$ + useKeepAlive = new JCheckBox(JMeterUtils.getResString("use_keepalive")); // $NON-NLS-1$ + + JPanel mainPanel = new JPanel(new BorderLayout()); + JPanel soapActionPanel = new JPanel(); + soapActionPanel.setLayout(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridwidth = 2; + c.gridx = 0; + c.gridy = 0; + c.weightx = 1; + soapActionPanel.add(urlField, c); + c.fill = GridBagConstraints.NONE; + c.gridwidth = 1; + c.gridy = 1; + c.weightx = 0; + soapActionPanel.add(sendSoapAction, c); + c.gridx = 1; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + soapActionPanel.add(soapAction, c); + + c.fill = GridBagConstraints.HORIZONTAL; + c.gridwidth = 2; + c.gridy = 2; + c.gridx = 0; + soapActionPanel.add(useKeepAlive, c); + + mainPanel.add(soapActionPanel, BorderLayout.NORTH); + mainPanel.add(soapXml, BorderLayout.CENTER); + mainPanel.add(soapXmlFile, BorderLayout.SOUTH); + + sendSoapAction.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + soapAction.setEnabled(sendSoapAction.isSelected()); + } + }); + + add(mainPanel, BorderLayout.CENTER); + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement el) { + super.configure(el); + SoapSampler sampler = (SoapSampler) el; + urlField.setText(sampler.getURLData()); + sendSoapAction.setSelected(sampler.getSendSOAPAction()); + soapAction.setText(sampler.getSOAPAction()); + soapXml.setText(sampler.getXmlData()); + soapXmlFile.setFilename(sampler.getXmlFile()); + useKeepAlive.setSelected(sampler.getUseKeepAlive()); + } + + /** + * {@inheritDoc} + */ + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/WebServiceSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/WebServiceSamplerGui.java new file mode 100644 index 0000000..5d058bf --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/control/gui/WebServiceSamplerGui.java @@ -0,0 +1,525 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.control.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.WebServiceSampler; +import org.apache.jmeter.protocol.http.util.WSDLHelper; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * This is the GUI for the webservice samplers. It extends AbstractSamplerGui + * and is modeled after the SOAP sampler GUI. I've added instructional notes to + * the GUI for instructional purposes. XML parsing is pretty heavy weight, + * therefore the notes address those situations.
+ * Created on: Jun 26, 2003 + * + */ +public class WebServiceSamplerGui extends AbstractSamplerGui implements java.awt.event.ActionListener { + + private static final long serialVersionUID = 240L; + + private final JLabeledTextField domain = new JLabeledTextField(JMeterUtils.getResString("web_server_domain")); // $NON-NLS-1$ + + private final JLabeledTextField protocol = new JLabeledTextField(JMeterUtils.getResString("protocol"), 4); // $NON-NLS-1$ + + private final JLabeledTextField port = new JLabeledTextField(JMeterUtils.getResString("web_server_port"), 4); // $NON-NLS-1$ + + private final JLabeledTextField path = new JLabeledTextField(JMeterUtils.getResString("path")); // $NON-NLS-1$ + + private final JLabeledTextField soapAction = new JLabeledTextField(JMeterUtils.getResString("webservice_soap_action")); // $NON-NLS-1$ + + /** + * checkbox for Session maintenance. + */ + private JCheckBox maintainSession = new JCheckBox(JMeterUtils.getResString("webservice_maintain_session"), true); // $NON-NLS-1$ + + + private JTextArea soapXml; + + private final JLabeledTextField wsdlField = new JLabeledTextField(JMeterUtils.getResString("wsdl_url")); // $NON-NLS-1$ + + private final JButton wsdlButton = new JButton(JMeterUtils.getResString("load_wsdl")); // $NON-NLS-1$ + + private final JButton selectButton = new JButton(JMeterUtils.getResString("configure_wsdl")); // $NON-NLS-1$ + + private JLabeledChoice wsdlMethods = null; + + private transient WSDLHelper HELPER = null; + + private final FilePanel soapXmlFile = new FilePanel(JMeterUtils.getResString("get_xml_from_file"), ".xml"); // $NON-NLS-1$ + + private final JLabeledTextField randomXmlFile = new JLabeledTextField(JMeterUtils.getResString("get_xml_from_random")); // $NON-NLS-1$ + + private final JLabeledTextField connectTimeout = new JLabeledTextField(JMeterUtils.getResString("webservice_timeout"), 4); // $NON-NLS-1$ + + /** + * checkbox for memory cache. + */ + private JCheckBox memCache = new JCheckBox(JMeterUtils.getResString("memory_cache"), true); // $NON-NLS-1$ + + /** + * checkbox for reading the response + */ + private JCheckBox readResponse = new JCheckBox(JMeterUtils.getResString("read_soap_response")); // $NON-NLS-1$ + + /** + * checkbox for use proxy + */ + private JCheckBox useProxy = new JCheckBox(JMeterUtils.getResString("webservice_use_proxy")); // $NON-NLS-1$ + + /** + * text field for the proxy host + */ + private JTextField proxyHost; + + /** + * text field for the proxy port + */ + private JTextField proxyPort; + + /** + * Text note about read response and its usage. + */ + private String readToolTip = JMeterUtils.getResString("read_response_note") // $NON-NLS-1$ + + " " // $NON-NLS-1$ + + JMeterUtils.getResString("read_response_note2") // $NON-NLS-1$ + + " " // $NON-NLS-1$ + + JMeterUtils.getResString("read_response_note3"); // $NON-NLS-1$ + + /** + * Text note for proxy + */ + private String proxyToolTip = JMeterUtils.getResString("webservice_proxy_note") // $NON-NLS-1$ + + " " // $NON-NLS-1$ + + JMeterUtils.getResString("webservice_proxy_note2") // $NON-NLS-1$ + + " " // $NON-NLS-1$ + + JMeterUtils.getResString("webservice_proxy_note3"); // $NON-NLS-1$ + public WebServiceSamplerGui() { + init(); + } + + public String getLabelResource() { + return "webservice_sampler_title"; // $NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + WebServiceSampler sampler = new WebServiceSampler(); + this.configureTestElement(sampler); + this.modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement s) { + WebServiceSampler sampler = (WebServiceSampler) s; + this.configureTestElement(sampler); + sampler.setDomain(domain.getText()); + sampler.setProperty(HTTPSamplerBase.PORT,port.getText()); + sampler.setProtocol(protocol.getText()); + sampler.setPath(path.getText()); + sampler.setWsdlURL(wsdlField.getText()); + sampler.setMethod(HTTPSamplerBase.POST); + sampler.setSoapAction(soapAction.getText()); + sampler.setMaintainSession(maintainSession.isSelected()); + sampler.setXmlData(soapXml.getText()); + sampler.setXmlFile(soapXmlFile.getFilename()); + sampler.setXmlPathLoc(randomXmlFile.getText()); + sampler.setTimeout(connectTimeout.getText()); + sampler.setMemoryCache(memCache.isSelected()); + sampler.setReadResponse(readResponse.isSelected()); + sampler.setUseProxy(useProxy.isSelected()); + sampler.setProxyHost(proxyHost.getText()); + sampler.setProxyPort(proxyPort.getText()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + wsdlMethods.setValues(new String[0]); + domain.setText(""); //$NON-NLS-1$ + protocol.setText(""); //$NON-NLS-1$ + port.setText(""); //$NON-NLS-1$ + path.setText(""); //$NON-NLS-1$ + soapAction.setText(""); //$NON-NLS-1$ + maintainSession.setSelected(WebServiceSampler.MAINTAIN_SESSION_DEFAULT); + soapXml.setText(""); //$NON-NLS-1$ + wsdlField.setText(""); //$NON-NLS-1$ + randomXmlFile.setText(""); //$NON-NLS-1$ + connectTimeout.setText(""); //$NON-NLS-1$ + proxyHost.setText(""); //$NON-NLS-1$ + proxyPort.setText(""); //$NON-NLS-1$ + memCache.setSelected(true); + readResponse.setSelected(false); + useProxy.setSelected(false); + soapXmlFile.setFilename(""); //$NON-NLS-1$ + } + + /** + * init() adds soapAction to the mainPanel. The class reuses logic from + * SOAPSampler, since it is common. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + + mainPanel.add(createTopPanel(), BorderLayout.NORTH); + mainPanel.add(createMessagePanel(), BorderLayout.CENTER); + mainPanel.add(createBottomPanel(), BorderLayout.SOUTH); + this.add(mainPanel); + } + + private final JPanel createTopPanel() { + JPanel topPanel = new JPanel(); + topPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + + JPanel wsdlHelper = new JPanel(); + wsdlHelper.setLayout(new BoxLayout(wsdlHelper, BoxLayout.Y_AXIS)); + wsdlHelper.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("webservice_configuration_wizard"))); // $NON-NLS-1$ + + // Button for browsing webservice wsdl + JPanel wsdlEntry = new JPanel(); + wsdlEntry.setLayout(new BoxLayout(wsdlEntry, BoxLayout.X_AXIS)); + Border margin = new EmptyBorder(0, 5, 0, 5); + wsdlEntry.setBorder(margin); + wsdlHelper.add(wsdlEntry); + wsdlEntry.add(wsdlField); + wsdlEntry.add(wsdlButton); + wsdlButton.addActionListener(this); + + // Web Methods + JPanel listPanel = new JPanel(); + listPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + JLabel selectLabel = new JLabel(JMeterUtils.getResString("webservice_methods")); // $NON-NLS-1$ + wsdlMethods = new JLabeledChoice(); + wsdlHelper.add(listPanel); + listPanel.add(selectLabel); + listPanel.add(wsdlMethods); + listPanel.add(selectButton); + selectButton.addActionListener(this); + + topPanel.add(wsdlHelper); + + JPanel urlPane = new JPanel(); + urlPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + urlPane.add(protocol); + urlPane.add(Box.createRigidArea(new Dimension(5,0))); + urlPane.add(domain); + urlPane.add(Box.createRigidArea(new Dimension(5,0))); + urlPane.add(port); + urlPane.add(Box.createRigidArea(new Dimension(5,0))); + urlPane.add(connectTimeout); + topPanel.add(urlPane); + + topPanel.add(createParametersPanel()); + + return topPanel; + } + + private final JPanel createParametersPanel() { + JPanel paramsPanel = new JPanel(); + paramsPanel.setLayout(new BoxLayout(paramsPanel, BoxLayout.X_AXIS)); + paramsPanel.add(path); + paramsPanel.add(Box.createHorizontalGlue()); + paramsPanel.add(soapAction); + paramsPanel.add(Box.createHorizontalGlue()); + paramsPanel.add(maintainSession); + return paramsPanel; + } + + private final JPanel createMessagePanel() { + JPanel msgPanel = new JPanel(); + msgPanel.setLayout(new BorderLayout(5, 0)); + msgPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("webservice_message_soap"))); // $NON-NLS-1$ + + JPanel soapXmlPane = new JPanel(); + soapXmlPane.setLayout(new BorderLayout(5, 0)); + soapXmlPane.setBorder(BorderFactory.createTitledBorder( + JMeterUtils.getResString("soap_data_title"))); // $NON-NLS-1$ + soapXmlPane.setPreferredSize(new Dimension(4, 4)); // Permit dynamic resize of TextArea + soapXml = new JTextArea(); + soapXml.setLineWrap(true); + soapXml.setWrapStyleWord(true); + soapXml.setTabSize(4); // improve xml display + soapXmlPane.add(new JScrollPane(soapXml), BorderLayout.CENTER); + msgPanel.add(soapXmlPane, BorderLayout.CENTER); + + JPanel southPane = new JPanel(); + southPane.setLayout(new BoxLayout(southPane, BoxLayout.Y_AXIS)); + southPane.add(soapXmlFile); + JPanel randomXmlPane = new JPanel(); + randomXmlPane.setLayout(new BorderLayout(5, 0)); + randomXmlPane.setBorder(BorderFactory.createTitledBorder( + JMeterUtils.getResString("webservice_get_xml_from_random_title"))); // $NON-NLS-1$ + randomXmlPane.add(randomXmlFile, BorderLayout.CENTER); + southPane.add(randomXmlPane); + msgPanel.add(southPane, BorderLayout.SOUTH); + return msgPanel; + } + + private final JPanel createBottomPanel() { + JPanel optionPane = new JPanel(); + optionPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("option"))); // $NON-NLS-1$ + optionPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + JPanel ckboxPane = new HorizontalPanel(); + ckboxPane.add(memCache, BorderLayout.WEST); + ckboxPane.add(readResponse, BorderLayout.CENTER); + readResponse.setToolTipText(readToolTip); + optionPane.add(ckboxPane); + + // add the proxy elements + optionPane.add(getProxyServerPanel()); + return optionPane; + + } + /** + * Create a panel containing the proxy server details + * + * @return the panel + */ + private final JPanel getProxyServerPanel(){ + JPanel proxyServer = new JPanel(); + proxyServer.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + proxyServer.add(useProxy); + useProxy.addActionListener(this); + useProxy.setToolTipText(proxyToolTip); + proxyServer.add(Box.createRigidArea(new Dimension(5,0))); + proxyServer.add(getProxyHostPanel()); + proxyServer.add(Box.createRigidArea(new Dimension(5,0))); + proxyServer.add(getProxyPortPanel()); + return proxyServer; + } + + private JPanel getProxyHostPanel() { + proxyHost = new JTextField(12); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_domain")); // $NON-NLS-1$ + label.setLabelFor(proxyHost); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyHost, BorderLayout.CENTER); + return panel; + } + + private JPanel getProxyPortPanel() { + proxyPort = new JTextField(4); + + JLabel label = new JLabel(JMeterUtils.getResString("web_server_port")); // $NON-NLS-1$ + label.setLabelFor(proxyPort); + + JPanel panel = new JPanel(new BorderLayout(5, 0)); + panel.add(label, BorderLayout.WEST); + panel.add(proxyPort, BorderLayout.CENTER); + + return panel; + } + + /** + * the implementation loads the URL and the soap action for the request. + */ + @Override + public void configure(TestElement el) { + super.configure(el); + WebServiceSampler sampler = (WebServiceSampler) el; + wsdlField.setText(sampler.getWsdlURL()); + final String wsdlText = wsdlField.getText(); + if (wsdlText != null && wsdlText.length() > 0) { + fillWsdlMethods(wsdlField.getText(), true, sampler.getSoapAction()); + } + protocol.setText(sampler.getProtocol()); + domain.setText(sampler.getDomain()); + port.setText(sampler.getPropertyAsString(HTTPSamplerBase.PORT)); + path.setText(sampler.getPath()); + soapAction.setText(sampler.getSoapAction()); + maintainSession.setSelected(sampler.getMaintainSession()); + soapXml.setText(sampler.getXmlData()); + soapXml.setCaretPosition(0); // go to 1st line + soapXmlFile.setFilename(sampler.getXmlFile()); + randomXmlFile.setText(sampler.getXmlPathLoc()); + connectTimeout.setText(sampler.getTimeout()); + memCache.setSelected(sampler.getMemoryCache()); + readResponse.setSelected(sampler.getReadResponse()); + useProxy.setSelected(sampler.getUseProxy()); + if (sampler.getProxyHost().length() == 0) { + proxyHost.setEnabled(false); + } else { + proxyHost.setText(sampler.getProxyHost()); + } + if (sampler.getProxyPort() == 0) { + proxyPort.setEnabled(false); + } else { + proxyPort.setText(String.valueOf(sampler.getProxyPort())); + } + } + + /** + * configure the sampler from the WSDL. If the WSDL did not include service + * node, it will use the original URL minus the querystring. That may not be + * correct, so we should probably add a note. For Microsoft webservices it + * will work, since that's how IIS works. + */ + public void configureFromWSDL() { + if (HELPER != null) { + if(HELPER.getBinding() != null) { + this.protocol.setText(HELPER.getProtocol()); + this.domain.setText(HELPER.getBindingHost()); + if (HELPER.getBindingPort() > 0) { + this.port.setText(String.valueOf(HELPER.getBindingPort())); + } else { + this.port.setText("80"); // $NON-NLS-1$ + } + this.path.setText(HELPER.getBindingPath()); + } + this.soapAction.setText(HELPER.getSoapAction(this.wsdlMethods.getText())); + } + } + + /** + * The method uses WSDLHelper to get the information from the WSDL. Since + * the logic for getting the description is isolated to this method, we can + * easily replace it with a different WSDL driver later on. + * + * @param url + * @param silent + * @return array of web methods + */ + public String[] browseWSDL(String url, boolean silent) { + try { + // We get the AuthManager and pass it to the WSDLHelper + // once the sampler is updated to Axis, all of this stuff + // should not be necessary. Now I just need to find the + // time and motivation to do it. + WebServiceSampler sampler = (WebServiceSampler) this.createTestElement(); + AuthManager manager = sampler.getAuthManager(); + HELPER = new WSDLHelper(url, manager); + HELPER.parse(); + return HELPER.getWebMethods(); + } catch (Exception exception) { + if (!silent) { + JOptionPane.showConfirmDialog(this, + JMeterUtils.getResString("wsdl_helper_error") // $NON-NLS-1$ + +"\n"+exception, // $NON-NLS-1$ + JMeterUtils.getResString("warning"), // $NON-NLS-1$ + JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE); + } + return ArrayUtils.EMPTY_STRING_ARRAY; + } + } + + /** + * method from ActionListener + * + * @param event + * that occurred + */ + public void actionPerformed(ActionEvent event) { + final Object eventSource = event.getSource(); + if (eventSource == selectButton) { + this.configureFromWSDL(); + } else if (eventSource == useProxy) { + // if use proxy is checked, we enable + // the text fields for the host and port + boolean use = useProxy.isSelected(); + if (use) { + proxyHost.setEnabled(true); + proxyPort.setEnabled(true); + } else { + proxyHost.setEnabled(false); + proxyPort.setEnabled(false); + } + } else if (eventSource == wsdlButton){ + final String wsdlText = wsdlField.getText(); + if (wsdlText != null && wsdlText.length() > 0) { + fillWsdlMethods(wsdlText, false, null); + } else { + JOptionPane.showConfirmDialog(this, + JMeterUtils.getResString("wsdl_url_error"), // $NON-NLS-1$ + JMeterUtils.getResString("warning"), // $NON-NLS-1$ + JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE); + } + } + } + + /** + * @param wsdlText + * @param silent + * @param soapAction + */ + private void fillWsdlMethods(final String wsdlText, boolean silent, String soapAction) { + String[] wsdlData = browseWSDL(wsdlText, silent); + if (wsdlData != null) { + wsdlMethods.setValues(wsdlData); + if (HELPER != null && soapAction != null) { + String selected = HELPER.getSoapActionName(soapAction); + if (selected != null) { + wsdlMethods.setText(selected); + } + } + wsdlMethods.repaint(); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/AuthPanel.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/AuthPanel.java new file mode 100644 index 0000000..6e4a322 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/AuthPanel.java @@ -0,0 +1,414 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.BorderFactory; +import javax.swing.DefaultCellEditor; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.Authorization; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Handles input for determining if authentication services are required for a + * Sampler. It also understands how to get AuthManagers for the files that the + * user selects. + */ +public class AuthPanel extends AbstractConfigGui implements ActionListener { + private static final long serialVersionUID = -9214884465261470761L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ADD_COMMAND = "Add"; //$NON-NLS-1$ + + private static final String DELETE_COMMAND = "Delete"; //$NON-NLS-1$ + + private static final String LOAD_COMMAND = "Load"; //$NON-NLS-1$ + + private static final String SAVE_COMMAND = "Save"; //$NON-NLS-1$ + + private InnerTableModel tableModel; + + /** + * A table to show the authentication information. + */ + private JTable authTable; + + private JButton addButton; + + private JButton deleteButton; + + private JButton loadButton; + + private JButton saveButton; + + /** + * Default Constructor. + */ + public AuthPanel() { + tableModel = new InnerTableModel(); + init(); + } + + public TestElement createTestElement() { + AuthManager authMan = tableModel.manager; + configureTestElement(authMan); + return (TestElement) authMan.clone(); + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement el) { + if (authTable.isEditing()) { + authTable.getCellEditor().stopCellEditing(); + } + el.clear(); + el.addTestElement((TestElement) tableModel.manager.clone()); + configureTestElement(el); + } + + /** + * Implements JMeterGUIComponent.clear + */ + @Override + public void clearGui() { + super.clearGui(); + + tableModel.clearData(); + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + tableModel.manager.clear(); + tableModel.manager.addTestElement((AuthManager) el.clone()); + if (tableModel.getRowCount() != 0) { + deleteButton.setEnabled(true); + saveButton.setEnabled(true); + } + } + + public String getLabelResource() { + return "auth_manager_title"; //$NON-NLS-1$ + } + + /** + * Shows the main authentication panel for this object. + */ + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + add(createAuthTablePanel(), BorderLayout.CENTER); + } + + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + + if (action.equals(DELETE_COMMAND)) { + if (tableModel.getRowCount() > 0) { + // If a table cell is being edited, we must cancel the editing + // before deleting the row. + if (authTable.isEditing()) { + TableCellEditor cellEditor = authTable.getCellEditor(authTable.getEditingRow(), authTable + .getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = authTable.getSelectedRow(); + + if (rowSelected != -1) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable the DELETE and SAVE buttons if no rows remaining + // after delete. + if (tableModel.getRowCount() == 0) { + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + } + + // Table still contains one or more rows, so highlight + // (select) the appropriate one. + else { + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + authTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + } else if (action.equals(ADD_COMMAND)) { + // If a table cell is being edited, we should accept the current + // value and stop the editing before adding a new row. + if (authTable.isEditing()) { + TableCellEditor cellEditor = authTable.getCellEditor(authTable.getEditingRow(), authTable + .getEditingColumn()); + cellEditor.stopCellEditing(); + } + + tableModel.addNewRow(); + tableModel.fireTableDataChanged(); + + // Enable the DELETE and SAVE buttons if they are currently + // disabled. + if (!deleteButton.isEnabled()) { + deleteButton.setEnabled(true); + } + if (!saveButton.isEnabled()) { + saveButton.setEnabled(true); + } + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + authTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } else if (action.equals(LOAD_COMMAND)) { + try { + final String [] _txt={".txt"}; //$NON-NLS-1$ + final JFileChooser dialog = FileDialoger.promptToOpenFile(_txt); + if (dialog != null) { + tableModel.manager.addFile(dialog.getSelectedFile().getAbsolutePath()); + tableModel.fireTableDataChanged(); + + if (tableModel.getRowCount() > 0) { + deleteButton.setEnabled(true); + saveButton.setEnabled(true); + } + } + } catch (IOException ex) { + log.error("", ex); + } + } else if (action.equals(SAVE_COMMAND)) { + try { + final JFileChooser chooser = FileDialoger.promptToSaveFile("auth.txt"); //$NON-NLS-1$ + if (chooser != null) { + tableModel.manager.save(chooser.getSelectedFile().getAbsolutePath()); + } + } catch (IOException ex) { + log.error("", ex); + } + } + } + + public JPanel createAuthTablePanel() { + // create the JTable that holds auth per row + authTable = new JTable(tableModel); + authTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + authTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + authTable.setPreferredScrollableViewportSize(new Dimension(100, 70)); + + TableColumn passwordColumn = authTable.getColumnModel().getColumn(AuthManager.COL_PASSWORD); + passwordColumn.setCellEditor(new DefaultCellEditor(new JPasswordField())); + passwordColumn.setCellRenderer(new PasswordCellRenderer()); + + JPanel panel = new JPanel(new BorderLayout(0, 5)); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("auths_stored"))); //$NON-NLS-1$ + panel.add(new JScrollPane(authTable)); + panel.add(createButtonPanel(), BorderLayout.SOUTH); + return panel; + } + + private JButton createButton(String resName, char mnemonic, String command, boolean enabled) { + JButton button = new JButton(JMeterUtils.getResString(resName)); + button.setMnemonic(mnemonic); + button.setActionCommand(command); + button.setEnabled(enabled); + button.addActionListener(this); + return button; + } + + private JPanel createButtonPanel() { + boolean tableEmpty = (tableModel.getRowCount() == 0); + + addButton = createButton("add", 'A', ADD_COMMAND, true); //$NON-NLS-1$ + deleteButton = createButton("delete", 'D', DELETE_COMMAND, !tableEmpty); //$NON-NLS-1$ + loadButton = createButton("load", 'L', LOAD_COMMAND, true); //$NON-NLS-1$ + saveButton = createButton("save", 'S', SAVE_COMMAND, !tableEmpty); //$NON-NLS-1$ + + // Button Panel + JPanel buttonPanel = new JPanel(); + buttonPanel.add(addButton); + buttonPanel.add(deleteButton); + buttonPanel.add(loadButton); + buttonPanel.add(saveButton); + return buttonPanel; + } + + private static class InnerTableModel extends AbstractTableModel { + private static final long serialVersionUID = 4638155137475747946L; + final AuthManager manager; + + public InnerTableModel() { + manager = new AuthManager(); + } + + public void clearData() { + manager.clear(); + fireTableDataChanged(); + } + + public void removeRow(int row) { + manager.remove(row); + } + + public void addNewRow() { + manager.addAuth(); + } + + @Override + public boolean isCellEditable(int row, int column) { + // all table cells are editable + return true; + } + + @Override + public Class getColumnClass(int column) { + return getValueAt(0, column).getClass(); + } + + /** + * Required by table model interface. + */ + public int getRowCount() { + return manager.getAuthObjects().size(); + } + + /** + * Required by table model interface. + */ + public int getColumnCount() { + return manager.getColumnCount(); + } + + /** + * Required by table model interface. + */ + @Override + public String getColumnName(int column) { + return manager.getColumnName(column); + } + + /** + * Required by table model interface. + */ + public Object getValueAt(int row, int column) { + Authorization auth = manager.getAuthObjectAt(row); + + switch (column){ + case AuthManager.COL_URL: + return auth.getURL(); + case AuthManager.COL_USERNAME: + return auth.getUser(); + case AuthManager.COL_PASSWORD: + return auth.getPass(); + case AuthManager.COL_DOMAIN: + return auth.getDomain(); + case AuthManager.COL_REALM: + return auth.getRealm(); + default: + return null; + } + } + + @Override + public void setValueAt(Object value, int row, int column) { + Authorization auth = manager.getAuthObjectAt(row); + log.debug("Setting auth value: " + value); + switch (column){ + case AuthManager.COL_URL: + auth.setURL((String) value); + break; + case AuthManager.COL_USERNAME: + auth.setUser((String) value); + break; + case AuthManager.COL_PASSWORD: + auth.setPass((String) value); + break; + case AuthManager.COL_DOMAIN: + auth.setDomain((String) value); + break; + case AuthManager.COL_REALM: + auth.setRealm((String) value); + break; + default: + break; + } + } + } + + private static class PasswordCellRenderer extends JPasswordField implements TableCellRenderer { + private static final long serialVersionUID = 5169856333827579927L; + private Border myBorder; + + public PasswordCellRenderer() { + super(); + myBorder = new EmptyBorder(1, 2, 1, 2); + setOpaque(true); + setBorder(myBorder); + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, + boolean hasFocus, int row, int column) { + setText((String) value); + + setBackground(isSelected && !hasFocus ? table.getSelectionBackground() : table.getBackground()); + setForeground(isSelected && !hasFocus ? table.getSelectionForeground() : table.getForeground()); + + setFont(table.getFont()); + + return this; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/CacheManagerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/CacheManagerGui.java new file mode 100644 index 0000000..ef75634 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/CacheManagerGui.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.gui; + +import java.awt.BorderLayout; + +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * The GUI for the HTTP Cache Manager + * + */ +public class CacheManagerGui extends AbstractConfigGui { + + private static final long serialVersionUID = 240L; + + private JCheckBox clearEachIteration; + + private JCheckBox useExpires; + + private JTextField maxCacheSize; + + /** + * Create a new LoginConfigGui as a standalone component. + */ + public CacheManagerGui() { + init(); + } + + public String getLabelResource() { + return "cache_manager_title"; // $NON-NLS-1$ + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + final CacheManager cacheManager = (CacheManager)element; + clearEachIteration.setSelected(cacheManager.getClearEachIteration()); + useExpires.setSelected(cacheManager.getUseExpires()); + maxCacheSize.setText(Integer.toString(cacheManager.getMaxSize())); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + CacheManager element = new CacheManager(); + modifyTestElement(element); + return element; + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement element) { + configureTestElement(element); + final CacheManager cacheManager = (CacheManager)element; + cacheManager.setClearEachIteration(clearEachIteration.isSelected()); + cacheManager.setUseExpires(useExpires.isSelected()); + try { + cacheManager.setMaxSize(Integer.parseInt(maxCacheSize.getText())); + } catch (NumberFormatException e) { + // NOOP + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + clearEachIteration.setSelected(false); + useExpires.setSelected(false); + maxCacheSize.setText(""); //$NON-NLS-1$ + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + clearEachIteration = new JCheckBox(JMeterUtils.getResString("clear_cache_per_iter"), false); + useExpires = new JCheckBox(JMeterUtils.getResString("use_expires"), false); + + JPanel northPanel = new JPanel(); + northPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + northPanel.add(makeTitlePanel()); + northPanel.add(clearEachIteration); + northPanel.add(useExpires); + + JLabel label = new JLabel(JMeterUtils.getResString("cache_manager_size")); //$NON-NLS-1$ + + maxCacheSize = new JTextField(20); + maxCacheSize.setName(CacheManager.MAX_SIZE); + label.setLabelFor(maxCacheSize); + JPanel maxCacheSizePanel = new JPanel(new BorderLayout(5, 0)); + maxCacheSizePanel.add(label, BorderLayout.WEST); + maxCacheSizePanel.add(maxCacheSize, BorderLayout.CENTER); + northPanel.add(maxCacheSizePanel); + add(northPanel, BorderLayout.NORTH); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/CookiePanel.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/CookiePanel.java new file mode 100644 index 0000000..651a03b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/CookiePanel.java @@ -0,0 +1,366 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.PowerTableModel; +import org.apache.jmeter.protocol.http.control.Cookie; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.layout.VerticalLayout; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This is the GUI for Cookie Manager + * + * Allows the user to specify if she needs cookie services, and give parameters + * for this service. + * + */ +public class CookiePanel extends AbstractConfigGui implements ActionListener { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + //++ Action command names + private static final String ADD_COMMAND = "Add"; //$NON-NLS-1$ + + private static final String DELETE_COMMAND = "Delete"; //$NON-NLS-1$ + + private static final String LOAD_COMMAND = "Load"; //$NON-NLS-1$ + + private static final String SAVE_COMMAND = "Save"; //$NON-NLS-1$ + //-- + + private JTable cookieTable; + + private PowerTableModel tableModel; + + private JCheckBox clearEachIteration; + + private static final String[] COLUMN_RESOURCE_NAMES = { + ("name"), //$NON-NLS-1$ + ("value"), //$NON-NLS-1$ + ("domain"), //$NON-NLS-1$ + ("path"), //$NON-NLS-1$ + ("secure"), //$NON-NLS-1$ + // removed expiration because it's just an annoyance for static cookies + }; + + private static final Class[] columnClasses = { + String.class, + String.class, + String.class, + String.class, + Boolean.class, }; + + private JButton addButton; + + private JButton deleteButton; + + private JButton loadButton; + + private JButton saveButton; + + /** + * List of cookie policies. + * + * These are used both for the display, and for setting the policy + */ + private final String[] policies = new String[] { + "default", + "compatibility", + "rfc2109", + "rfc2965", + "ignorecookies", + "netscape" + }; + + private JLabeledChoice policy; + + /** + * Default constructor. + */ + public CookiePanel() { + init(); + } + + public String getLabelResource() { + return "cookie_manager_title"; //$NON-NLS-1$ + } + + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + + if (action.equals(DELETE_COMMAND)) { + if (tableModel.getRowCount() > 0) { + // If a table cell is being edited, we must cancel the editing + // before deleting the row. + if (cookieTable.isEditing()) { + TableCellEditor cellEditor = cookieTable.getCellEditor(cookieTable.getEditingRow(), + cookieTable.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = cookieTable.getSelectedRow(); + + if (rowSelected != -1) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable the DELETE and SAVE buttons if no rows remaining + // after delete. + if (tableModel.getRowCount() == 0) { + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + } + + // Table still contains one or more rows, so highlight + // (select) the appropriate one. + else { + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + cookieTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + } else if (action.equals(ADD_COMMAND)) { + // If a table cell is being edited, we should accept the current + // value and stop the editing before adding a new row. + if (cookieTable.isEditing()) { + TableCellEditor cellEditor = cookieTable.getCellEditor(cookieTable.getEditingRow(), + cookieTable.getEditingColumn()); + cellEditor.stopCellEditing(); + } + + tableModel.addNewRow(); + tableModel.fireTableDataChanged(); + + // Enable the DELETE and SAVE buttons if they are currently + // disabled. + if (!deleteButton.isEnabled()) { + deleteButton.setEnabled(true); + } + if (!saveButton.isEnabled()) { + saveButton.setEnabled(true); + } + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + cookieTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } else if (action.equals(LOAD_COMMAND)) { + try { + final String [] _txt={".txt"}; //$NON-NLS-1$ + final JFileChooser chooser = FileDialoger.promptToOpenFile(_txt); + if (chooser != null) { + CookieManager manager = new CookieManager(); + manager.addFile(chooser.getSelectedFile().getAbsolutePath()); + for (int i = 0; i < manager.getCookieCount() ; i++){ + addCookieToTable(manager.get(i)); + } + tableModel.fireTableDataChanged(); + + if (tableModel.getRowCount() > 0) { + deleteButton.setEnabled(true); + saveButton.setEnabled(true); + } + } + } catch (IOException ex) { + log.error("", ex); + } + } else if (action.equals(SAVE_COMMAND)) { + try { + final JFileChooser chooser = FileDialoger.promptToSaveFile("cookies.txt"); //$NON-NLS-1$ + if (chooser != null) { + ((CookieManager) createTestElement()).save(chooser.getSelectedFile().getAbsolutePath()); + } + } catch (IOException ex) { + log.error("", ex); + } + } + } + + private void addCookieToTable(Cookie cookie) { + tableModel.addRow(new Object[] { cookie.getName(), cookie.getValue(), cookie.getDomain(), cookie.getPath(), + Boolean.valueOf(cookie.getSecure()) }); + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement cm) { + if (cookieTable.isEditing()) { + cookieTable.getCellEditor().stopCellEditing(); + } + cm.clear(); + configureTestElement(cm); + if (cm instanceof CookieManager) { + CookieManager cookieManager = (CookieManager) cm; + for (int i = 0; i < tableModel.getRowCount(); i++) { + Cookie cookie = createCookie(tableModel.getRowData(i)); + cookieManager.add(cookie); + } + cookieManager.setClearEachIteration(clearEachIteration.isSelected()); + cookieManager.setCookiePolicy(policy.getText()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + tableModel.clearData(); + clearEachIteration.setSelected(false); + policy.setText(CookieManager.DEFAULT_POLICY); + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + } + + private Cookie createCookie(Object[] rowData) { + Cookie cookie = new Cookie( + (String) rowData[0], + (String) rowData[1], + (String) rowData[2], + (String) rowData[3], + ((Boolean) rowData[4]).booleanValue(), + 0); // Non-expiring + return cookie; + } + + private void populateTable(CookieManager manager) { + tableModel.clearData(); + PropertyIterator iter = manager.getCookies().iterator(); + while (iter.hasNext()) { + addCookieToTable((Cookie) iter.next().getObjectValue()); + } + } + + public TestElement createTestElement() { + CookieManager cookieManager = new CookieManager(); + modifyTestElement(cookieManager); + return cookieManager; + } + + @Override + public void configure(TestElement el) { + super.configure(el); + + CookieManager cookieManager = (CookieManager) el; + populateTable(cookieManager); + clearEachIteration.setSelected((cookieManager).getClearEachIteration()); + policy.setText(cookieManager.getPolicy()); + } + + /** + * Shows the main cookie configuration panel. + */ + private void init() { + tableModel = new PowerTableModel(COLUMN_RESOURCE_NAMES, columnClasses); + clearEachIteration = + new JCheckBox(JMeterUtils.getResString("clear_cookies_per_iter"), false); //$NON-NLS-1$ + policy = new JLabeledChoice( + JMeterUtils.getResString("cookie_manager_policy"), //$NON-NLS-1$ + policies); + policy.setText(CookieManager.DEFAULT_POLICY); + setLayout(new BorderLayout()); + setBorder(makeBorder()); + JPanel northPanel = new JPanel(); + northPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + northPanel.add(makeTitlePanel()); + northPanel.add(clearEachIteration); + northPanel.add(policy); + add(northPanel, BorderLayout.NORTH); + add(createCookieTablePanel(), BorderLayout.CENTER); + } + + public JPanel createCookieTablePanel() { + // create the JTable that holds one cookie per row + cookieTable = new JTable(tableModel); + cookieTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + cookieTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + cookieTable.setPreferredScrollableViewportSize(new Dimension(100, 70)); + + JPanel buttonPanel = createButtonPanel(); + + JPanel panel = new JPanel(new BorderLayout(0, 5)); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("cookies_stored"))); //$NON-NLS-1$ + + panel.add(new JScrollPane(cookieTable), BorderLayout.CENTER); + panel.add(buttonPanel, BorderLayout.SOUTH); + return panel; + } + + private JButton createButton(String resName, char mnemonic, String command, boolean enabled) { + JButton button = new JButton(JMeterUtils.getResString(resName)); + button.setMnemonic(mnemonic); + button.setActionCommand(command); + button.setEnabled(enabled); + button.addActionListener(this); + return button; + } + + private JPanel createButtonPanel() { + boolean tableEmpty = (tableModel.getRowCount() == 0); + + addButton = createButton("add", 'A', ADD_COMMAND, true); //$NON-NLS-1$ + deleteButton = createButton("delete", 'D', DELETE_COMMAND, !tableEmpty); //$NON-NLS-1$ + loadButton = createButton("load", 'L', LOAD_COMMAND, true); //$NON-NLS-1$ + saveButton = createButton("save", 'S', SAVE_COMMAND, !tableEmpty); //$NON-NLS-1$ + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(addButton); + buttonPanel.add(deleteButton); + buttonPanel.add(loadButton); + buttonPanel.add(saveButton); + return buttonPanel; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HTTPArgumentsPanel.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HTTPArgumentsPanel.java new file mode 100644 index 0000000..b598d78 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HTTPArgumentsPanel.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.gui; + +import java.util.Iterator; + +import javax.swing.JTable; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +/** + * A GUI panel allowing the user to enter HTTP Parameters. + * These have names and values, as well as check-boxes to determine whether or not to + * include the "=" sign in the output and whether or not to encode the output. + */ +public class HTTPArgumentsPanel extends ArgumentsPanel { + + private static final long serialVersionUID = 240L; + + private static final String ENCODE_OR_NOT = "encode?"; //$NON-NLS-1$ + + private static final String INCLUDE_EQUALS = "include_equals"; //$NON-NLS-1$ + + @Override + protected void initializeTableModel() { + tableModel = new ObjectTableModel(new String[] { + ArgumentsPanel.COLUMN_RESOURCE_NAMES_0, ArgumentsPanel.COLUMN_RESOURCE_NAMES_1, ENCODE_OR_NOT, INCLUDE_EQUALS }, + HTTPArgument.class, + new Functor[] { + new Functor("getName"), //$NON-NLS-1$ + new Functor("getValue"), //$NON-NLS-1$ + new Functor("isAlwaysEncoded"), //$NON-NLS-1$ + new Functor("isUseEquals") }, //$NON-NLS-1$ + new Functor[] { + new Functor("setName"), //$NON-NLS-1$ + new Functor("setValue"), //$NON-NLS-1$ + new Functor("setAlwaysEncoded"), //$NON-NLS-1$ + new Functor("setUseEquals") }, //$NON-NLS-1$ + new Class[] {String.class, String.class, Boolean.class, Boolean.class }); + } + + public static boolean testFunctors(){ + HTTPArgumentsPanel instance = new HTTPArgumentsPanel(); + instance.initializeTableModel(); + return instance.tableModel.checkFunctors(null,instance.getClass()); + } + + @Override + protected void sizeColumns(JTable table) { + GuiUtils.fixSize(table.getColumn(INCLUDE_EQUALS), table); + GuiUtils.fixSize(table.getColumn(ENCODE_OR_NOT), table); + } + + @Override + protected HTTPArgument makeNewArgument() { + HTTPArgument arg = new HTTPArgument("", ""); + arg.setAlwaysEncoded(false); + arg.setUseEquals(true); + return arg; + } + + public HTTPArgumentsPanel() { + super(JMeterUtils.getResString("paramtable")); //$NON-NLS-1$ + } + + @Override + public TestElement createTestElement() { + Arguments args = getUnclonedParameters(); + this.configureTestElement(args); + return (TestElement) args.clone(); + } + + /** + * Convert the argument panel contents to an {@link Arguments} collection. + * + * @return a collection of {@link HTTPArgument} entries + */ + public Arguments getParameters() { + Arguments args = getUnclonedParameters(); + return (Arguments) args.clone(); + } + + private Arguments getUnclonedParameters() { + stopTableEditing(); + @SuppressWarnings("unchecked") // only contains Argument (or HTTPArgument) + Iterator modelData = (Iterator) tableModel.iterator(); + Arguments args = new Arguments(); + while (modelData.hasNext()) { + HTTPArgument arg = modelData.next(); + args.addArgument(arg); + } + return args; + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof Arguments) { + tableModel.clearData(); + HTTPArgument.convertArgumentsToHTTP((Arguments) el); + PropertyIterator iter = ((Arguments) el).getArguments().iterator(); + while (iter.hasNext()) { + HTTPArgument arg = (HTTPArgument) iter.next().getObjectValue(); + tableModel.addRow(arg); + } + } + checkDeleteStatus(); + } + + protected boolean isMetaDataNormal(HTTPArgument arg) { + return arg.getMetaData() == null || arg.getMetaData().equals("=") + || (arg.getValue() != null && arg.getValue().length() > 0); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HTTPFileArgsPanel.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HTTPFileArgsPanel.java new file mode 100644 index 0000000..c5660bb --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HTTPFileArgsPanel.java @@ -0,0 +1,406 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.Iterator; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; + +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +/* + * Note: this class is currently only suitable for use with HTTSamplerBase. + * If it is required for other classes, then the appropriate configure() and modifyTestElement() + * method code needs to be written. + */ +/** + * A GUI panel allowing the user to enter file information for http upload. + * Used by MultipartUrlConfigGui for use in HTTP Samplers. + */ +public class HTTPFileArgsPanel extends JPanel implements ActionListener { + + private static final long serialVersionUID = 240L; + + /** The title label for this component. */ + private JLabel tableLabel; + + /** The table containing the list of files. */ + private transient JTable table; + + /** The model for the files table. */ + private transient ObjectTableModel tableModel; // only contains HTTPFileArg elements + + /** A button for adding new files to the table. */ + private JButton add; + + /** A button for browsing file system to set path of selected row in table. */ + private JButton browse; + + /** A button for removing files from the table. */ + private JButton delete; + + /** Command for adding a row to the table. */ + private static final String ADD = "add"; // $NON-NLS-1$ + + /** Command for browsing filesystem to set path of selected row in table. */ + private static final String BROWSE = "browse"; // $NON-NLS-1$ + + /** Command for removing a row from the table. */ + private static final String DELETE = "delete"; // $NON-NLS-1$ + + private static final String FILEPATH = "send_file_filename_label"; // $NON-NLS-1$ + + /** The parameter name column title of file table. */ + private static final String PARAMNAME = "send_file_param_name_label"; //$NON-NLS-1$ + + /** The mime type column title of file table. */ + private static final String MIMETYPE = "send_file_mime_label"; //$NON-NLS-1$ + + public HTTPFileArgsPanel() { + this(""); // required for unit tests + } + + /** + * Create a new HTTPFileArgsPanel as an embedded component, using the + * specified title. + * + * @param label + * the title for the component. + */ + public HTTPFileArgsPanel(String label) { + tableLabel = new JLabel(label); + init(); + } + + /** + * Initialize the table model used for the http files table. + */ + private void initializeTableModel() { + tableModel = new ObjectTableModel(new String[] { + FILEPATH, PARAMNAME, MIMETYPE}, + HTTPFileArg.class, + new Functor[] { + new Functor("getPath"), //$NON-NLS-1$ + new Functor("getParamName"), //$NON-NLS-1$ + new Functor("getMimeType")}, //$NON-NLS-1$ + new Functor[] { + new Functor("setPath"), //$NON-NLS-1$ + new Functor("setParamName"), //$NON-NLS-1$ + new Functor("setMimeType")}, //$NON-NLS-1$ + new Class[] {String.class, String.class, String.class}); + } + + public static boolean testFunctors(){ + HTTPFileArgsPanel instance = new HTTPFileArgsPanel(""); //$NON-NLS-1$ + instance.initializeTableModel(); + return instance.tableModel.checkFunctors(null,instance.getClass()); + } + + /** + * Resize the table columns to appropriate widths. + * + * @param table + * the table to resize columns for + */ + private void sizeColumns(JTable table) { + GuiUtils.fixSize(table.getColumn(PARAMNAME), table); + GuiUtils.fixSize(table.getColumn(MIMETYPE), table); + } + + /** + * Save the GUI data in the HTTPSamplerBase element. + * + * @param testElement + */ + public void modifyTestElement(TestElement testElement) { + stopTableEditing(); + if (testElement instanceof HTTPSamplerBase) { + HTTPSamplerBase base = (HTTPSamplerBase) testElement; + int rows = tableModel.getRowCount(); + @SuppressWarnings("unchecked") // we only put HTTPFileArgs in it + Iterator modelData = (Iterator) tableModel.iterator(); + HTTPFileArg[] files = new HTTPFileArg[rows]; + int row=0; + while (modelData.hasNext()) { + HTTPFileArg file = modelData.next(); + files[row++]=file; + } + base.setHTTPFiles(files); + } + } + + /** + * A newly created component can be initialized with the contents of a + * HTTPSamplerBase object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param testElement the HTTPSamplerBase to be used to configure the GUI + */ + public void configure(TestElement testElement) { + if (testElement instanceof HTTPSamplerBase) { + HTTPSamplerBase base = (HTTPSamplerBase) testElement; + tableModel.clearData(); + for(HTTPFileArg file : base.getHTTPFiles()){ + tableModel.addRow(file); + } + checkDeleteAndBrowseStatus(); + } + } + + + /** + * Enable or disable the delete button depending on whether or not there is + * a row to be deleted. + */ + private void checkDeleteAndBrowseStatus() { + // Disable DELETE and BROWSE buttons if there are no rows in + // the table to delete. + if (tableModel.getRowCount() == 0) { + browse.setEnabled(false); + delete.setEnabled(false); + } else { + browse.setEnabled(true); + delete.setEnabled(true); + } + } + + /** + * Clear all rows from the table. + */ + public void clear() { + stopTableEditing(); + tableModel.clearData(); + } + + /** + * Invoked when an action occurs. This implementation supports the add and + * delete buttons. + * + * @param e + * the event that has occurred + */ + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(ADD)) { + addFile(""); //$NON-NLS-1$ + } + runCommandOnSelectedFile(action); + } + + /** + * runs specified command on currently selected file. + * + * @param command specifies which process will be done on selected + * file. it's coming from action command currently catched by + * action listener. + * + * @see runCommandOnRow + */ + private void runCommandOnSelectedFile(String command) { + // If a table cell is being edited, we must cancel the editing before + // deleting the row + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + int rowSelected = table.getSelectedRow(); + if (rowSelected >= 0) { + runCommandOnRow(command, rowSelected); + tableModel.fireTableDataChanged(); + // Disable DELETE and BROWSE if there are no rows in the table to delete. + checkDeleteAndBrowseStatus(); + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + if (tableModel.getRowCount() != 0) { + int rowToSelect = rowSelected; + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + + /** + * runs specified command on currently selected table row. + * + * @param command specifies which process will be done on selected + * file. it's coming from action command currently catched by + * action listener. + * + * @param rowSelected index of selected row. + */ + private void runCommandOnRow(String command, int rowSelected) { + if (DELETE.equals(command)) { + tableModel.removeRow(rowSelected); + } else if (BROWSE.equals(command)) { + String path = browseAndGetFilePath(); + tableModel.setValueAt(path, rowSelected, 0); + } + } + + /** + * Add a new file row to the table. + */ + private void addFile(String path) { + // If a table cell is being edited, we should accept the current value + // and stop the editing before adding a new row. + stopTableEditing(); + + tableModel.addRow(new HTTPFileArg(path)); + + // Enable DELETE (which may already be enabled, but it won't hurt) + delete.setEnabled(true); + browse.setEnabled(true); + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + + /** + * opens a dialog box to choose a file and returns selected file's + * path. + * + * @return a new File object + */ + private String browseAndGetFilePath() { + String path = ""; //$NON-NLS-1$ + JFileChooser chooser = FileDialoger.promptToOpenFile(); + if (chooser != null) { + File file = chooser.getSelectedFile(); + if (file != null) { + path = file.getPath(); + } + } + return path; + } + + /** + * Stop any editing that is currently being done on the table. This will + * save any changes that have already been made. + */ + protected void stopTableEditing() { + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.stopCellEditing(); + } + } + + /** + * Create the main GUI panel which contains the file table. + * + * @return the main GUI panel + */ + private Component makeMainPanel() { + initializeTableModel(); + table = new JTable(tableModel); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + return makeScrollPane(table); + } + + /** + * Create a panel containing the title label for the table. + * + * @return a panel containing the title label + */ + private Component makeLabelPanel() { + JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + labelPanel.add(tableLabel); + return labelPanel; + } + + /** + * Create a panel containing the add and delete buttons. + * + * @return a GUI panel containing the buttons + */ + private JPanel makeButtonPanel() { + add = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ + add.setActionCommand(ADD); + add.setEnabled(true); + + browse = new JButton(JMeterUtils.getResString("browse")); // $NON-NLS-1$ + browse.setActionCommand(BROWSE); + + delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + delete.setActionCommand(DELETE); + + checkDeleteAndBrowseStatus(); + + JPanel buttonPanel = new JPanel(); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); + add.addActionListener(this); + browse.addActionListener(this); + delete.addActionListener(this); + buttonPanel.add(add); + buttonPanel.add(browse); + buttonPanel.add(delete); + return buttonPanel; + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + JPanel p = this; + + p.setLayout(new BorderLayout()); + + p.add(makeLabelPanel(), BorderLayout.NORTH); + p.add(makeMainPanel(), BorderLayout.CENTER); + // Force a minimum table height of 70 pixels + p.add(Box.createVerticalStrut(70), BorderLayout.WEST); + p.add(makeButtonPanel(), BorderLayout.SOUTH); + + table.revalidate(); + sizeColumns(table); + } + + private JScrollPane makeScrollPane(Component comp) { + JScrollPane pane = new JScrollPane(comp); + pane.setPreferredSize(pane.getMinimumSize()); + return pane; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HeaderPanel.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HeaderPanel.java new file mode 100644 index 0000000..d7dd6f6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/gui/HeaderPanel.java @@ -0,0 +1,350 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellEditor; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Allows the user to specify if she needs HTTP header services, and give + * parameters for this service. + * + */ +public class HeaderPanel extends AbstractConfigGui implements ActionListener +{ + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ADD_COMMAND = "Add"; // $NON-NLS-1$ + + private static final String DELETE_COMMAND = "Delete"; // $NON-NLS-1$ + + private static final String LOAD_COMMAND = "Load"; // $NON-NLS-1$ + + private static final String SAVE_COMMAND = "Save"; // $NON-NLS-1$ + + private InnerTableModel tableModel; + + private HeaderManager headerManager; + + private JTable headerTable; + + private JButton addButton; + + private JButton deleteButton; + + private JButton loadButton; + + private JButton saveButton; + + public HeaderPanel() { + headerManager = new HeaderManager(); + tableModel = new InnerTableModel(headerManager); + init(); + } + + public TestElement createTestElement() { + configureTestElement(headerManager); + return (TestElement) headerManager.clone(); + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement el) { + if (headerTable.isEditing()) {// Bug 41905 + headerTable.getCellEditor().stopCellEditing(); + } + el.clear(); + el.addTestElement(headerManager); + configureTestElement(el); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + tableModel.clearData(); + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + } + + @Override + public void configure(TestElement el) { + headerManager.clear(); + super.configure(el); + headerManager.addTestElement(el); + boolean hasRows = tableModel.getRowCount() > 0; + deleteButton.setEnabled(hasRows); + saveButton.setEnabled(hasRows); + + } + + public String getLabelResource() { + return "header_manager_title"; // $NON-NLS-1$ + } + + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + add(createHeaderTablePanel(), BorderLayout.CENTER); + } + + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + + if (action.equals(DELETE_COMMAND)) { + if (tableModel.getRowCount() > 0) { + // If a table cell is being edited, we must cancel the editing + // before deleting the row. + if (headerTable.isEditing()) { + TableCellEditor cellEditor = headerTable.getCellEditor(headerTable.getEditingRow(), + headerTable.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = headerTable.getSelectedRow(); + + if (rowSelected != -1) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable the DELETE and SAVE buttons if no rows remaining + // after delete + if (tableModel.getRowCount() == 0) { + deleteButton.setEnabled(false); + saveButton.setEnabled(false); + } + + // Table still contains one or more rows, so highlight + // (select) the appropriate one. + else { + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + headerTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + } else if (action.equals(ADD_COMMAND)) { + // If a table cell is being edited, we should accept the current + // value and stop the editing before adding a new row. + if (headerTable.isEditing()) { + TableCellEditor cellEditor = headerTable.getCellEditor(headerTable.getEditingRow(), + headerTable.getEditingColumn()); + cellEditor.stopCellEditing(); + } + + tableModel.addNewRow(); + tableModel.fireTableDataChanged(); + + // Enable the DELETE and SAVE buttons if they are currently + // disabled. + if (!deleteButton.isEnabled()) { + deleteButton.setEnabled(true); + } + if (!saveButton.isEnabled()) { + saveButton.setEnabled(true); + } + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + headerTable.setRowSelectionInterval(rowToSelect, rowToSelect); + } else if (action.equals(LOAD_COMMAND)) { + try { + final JFileChooser chooser = FileDialoger.promptToOpenFile(); + if (chooser != null) { + headerManager.addFile(chooser.getSelectedFile().getAbsolutePath()); + tableModel.fireTableDataChanged(); + + if (tableModel.getRowCount() > 0) { + deleteButton.setEnabled(true); + saveButton.setEnabled(true); + } + } + } catch (IOException ex) { + log.error("Could not load headers", ex); + } + } else if (action.equals(SAVE_COMMAND)) { + try { + final JFileChooser chooser = FileDialoger.promptToSaveFile(null); + if (chooser != null) { + headerManager.save(chooser.getSelectedFile().getAbsolutePath()); + } + } catch (IOException ex) { + log.error("Could not save headers", ex); + } + } + } + + public JPanel createHeaderTablePanel() { + // create the JTable that holds header per row + headerTable = new JTable(tableModel); + headerTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + headerTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + headerTable.setPreferredScrollableViewportSize(new Dimension(100, 70)); + + JPanel panel = new JPanel(new BorderLayout(0, 5)); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("headers_stored"))); // $NON-NLS-1$ + panel.add(new JScrollPane(headerTable), BorderLayout.CENTER); + panel.add(createButtonPanel(), BorderLayout.SOUTH); + return panel; + } + + private JButton createButton(String resName, char mnemonic, String command, boolean enabled) { + JButton button = new JButton(JMeterUtils.getResString(resName)); + button.setMnemonic(mnemonic); + button.setActionCommand(command); + button.setEnabled(enabled); + button.addActionListener(this); + return button; + } + + private JPanel createButtonPanel() { + boolean tableEmpty = (tableModel.getRowCount() == 0); + + addButton = createButton("add", 'A', ADD_COMMAND, true); // $NON-NLS-1$ + deleteButton = createButton("delete", 'D', DELETE_COMMAND, !tableEmpty); // $NON-NLS-1$ + loadButton = createButton("load", 'L', LOAD_COMMAND, true); // $NON-NLS-1$ + saveButton = createButton("save", 'S', SAVE_COMMAND, !tableEmpty); // $NON-NLS-1$ + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(addButton); + buttonPanel.add(deleteButton); + buttonPanel.add(loadButton); + buttonPanel.add(saveButton); + return buttonPanel; + } + + private static class InnerTableModel extends AbstractTableModel { + private static final long serialVersionUID = 240L; + + private HeaderManager manager; + + public InnerTableModel(HeaderManager man) { + manager = man; + } + + public void clearData() { + manager.clear(); + fireTableDataChanged(); + } + + public void removeRow(int row) { + manager.remove(row); + } + + public void addNewRow() { + manager.add(); + } + + @Override + public boolean isCellEditable(int row, int column) { + // all table cells are editable + return true; + } + + @Override + public Class getColumnClass(int column) { + return getValueAt(0, column).getClass(); + } + + public int getRowCount() { + return manager.getHeaders().size(); + } + + /** + * Required by table model interface. + */ + public int getColumnCount() { + return manager.getColumnCount(); + } + + /** + * Required by table model interface. + */ + @Override + public String getColumnName(int column) { + return manager.getColumnName(column); + } + + /** + * Required by table model interface. + */ + public Object getValueAt(int row, int column) { + Header head = manager.getHeader(row); + if (column == 0) { + return head.getName(); + } else if (column == 1) { + return head.getValue(); + } + return null; + } + + /** + * Required by table model interface. + */ + @Override + public void setValueAt(Object value, int row, int column) { + Header header = manager.getHeader(row); + + if (column == 0) { + header.setName((String) value); + } else if (column == 1) { + header.setValue((String) value); + } + } + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/AnchorModifier.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/AnchorModifier.java new file mode 100644 index 0000000..57ee1b5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/AnchorModifier.java @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier; + +import java.io.Serializable; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.protocol.http.parser.HtmlParsingUtils; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +// For Unit tests, @see TestAnchorModifier + +public class AnchorModifier extends AbstractTestElement implements PreProcessor, Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Random rand = new Random(); + + public AnchorModifier() { + } + + /** + * Modifies an Entry object based on HTML response text. + */ + public void process() { + JMeterContext context = getThreadContext(); + Sampler sam = context.getCurrentSampler(); + SampleResult res = context.getPreviousResult(); + HTTPSamplerBase sampler = null; + HTTPSampleResult result = null; + if (res == null || !(sam instanceof HTTPSamplerBase) || !(res instanceof HTTPSampleResult)) { + log.info("Can't apply HTML Link Parser when the previous" + " sampler run is not an HTTP Request."); + return; + } else { + sampler = (HTTPSamplerBase) sam; + result = (HTTPSampleResult) res; + } + List potentialLinks = new ArrayList(); + String responseText = ""; // $NON-NLS-1$ + responseText = result.getResponseDataAsString(); + Document html; + int index = responseText.indexOf("<"); // $NON-NLS-1$ + if (index == -1) { + index = 0; + } + if (log.isDebugEnabled()) { + log.debug("Check for matches against: "+sampler.toString()); + } + html = (Document) HtmlParsingUtils.getDOM(responseText.substring(index)); + addAnchorUrls(html, result, sampler, potentialLinks); + addFormUrls(html, result, sampler, potentialLinks); + addFramesetUrls(html, result, sampler, potentialLinks); + if (potentialLinks.size() > 0) { + HTTPSamplerBase url = potentialLinks.get(rand.nextInt(potentialLinks.size())); + if (log.isDebugEnabled()) { + log.debug("Selected: "+url.toString()); + } + sampler.setDomain(url.getDomain()); + sampler.setPath(url.getPath()); + if (url.getMethod().equals(HTTPConstants.POST)) { + PropertyIterator iter = sampler.getArguments().iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + modifyArgument(arg, url.getArguments()); + } + } else { + sampler.setArguments(url.getArguments()); + // config.parseArguments(url.getQueryString()); + } + sampler.setProtocol(url.getProtocol()); + return; + } else { + log.debug("No matches found"); + } + return; + } + + private void modifyArgument(Argument arg, Arguments args) { + if (log.isDebugEnabled()) { + log.debug("Modifying argument: " + arg); + } + List possibleReplacements = new ArrayList(); + PropertyIterator iter = args.iterator(); + Argument replacementArg; + while (iter.hasNext()) { + replacementArg = (Argument) iter.next().getObjectValue(); + try { + if (HtmlParsingUtils.isArgumentMatched(replacementArg, arg)) { + possibleReplacements.add(replacementArg); + } + } catch (Exception ex) { + log.error("Problem adding Argument", ex); + } + } + + if (possibleReplacements.size() > 0) { + replacementArg = possibleReplacements.get(rand.nextInt(possibleReplacements.size())); + arg.setName(replacementArg.getName()); + arg.setValue(replacementArg.getValue()); + if (log.isDebugEnabled()) { + log.debug("Just set argument to values: " + arg.getName() + " = " + arg.getValue()); + } + args.removeArgument(replacementArg); + } + } + + public void addConfigElement(ConfigElement config) { + } + + private void addFormUrls(Document html, HTTPSampleResult result, HTTPSamplerBase config, + List potentialLinks) { + NodeList rootList = html.getChildNodes(); + List urls = new LinkedList(); + for (int x = 0; x < rootList.getLength(); x++) { + urls.addAll(HtmlParsingUtils.createURLFromForm(rootList.item(x), result.getURL())); + } + for (HTTPSamplerBase newUrl : urls) { + newUrl.setMethod(HTTPConstants.POST); + if (log.isDebugEnabled()) { + log.debug("Potential Form match: " + newUrl.toString()); + } + if (HtmlParsingUtils.isAnchorMatched(newUrl, config)) { + log.debug("Matched!"); + potentialLinks.add(newUrl); + } + } + } + + private void addAnchorUrls(Document html, HTTPSampleResult result, HTTPSamplerBase config, + List potentialLinks) { + String base = ""; + NodeList baseList = html.getElementsByTagName("base"); // $NON-NLS-1$ + if (baseList.getLength() > 0) { + base = baseList.item(0).getAttributes().getNamedItem("href").getNodeValue(); // $NON-NLS-1$ + } + NodeList nodeList = html.getElementsByTagName("a"); // $NON-NLS-1$ + for (int i = 0; i < nodeList.getLength(); i++) { + Node tempNode = nodeList.item(i); + NamedNodeMap nnm = tempNode.getAttributes(); + Node namedItem = nnm.getNamedItem("href"); // $NON-NLS-1$ + if (namedItem == null) { + continue; + } + String hrefStr = namedItem.getNodeValue(); + if (hrefStr.startsWith("javascript:")) { // $NON-NLS-1$ + continue; // No point trying these + } + try { + HTTPSamplerBase newUrl = HtmlParsingUtils.createUrlFromAnchor(hrefStr, ConversionUtils.makeRelativeURL(result.getURL(), base)); + newUrl.setMethod(HTTPConstants.GET); + if (log.isDebugEnabled()) { + log.debug("Potential match: " + newUrl); + } + if (HtmlParsingUtils.isAnchorMatched(newUrl, config)) { + log.debug("Matched!"); + potentialLinks.add(newUrl); + } + } catch (MalformedURLException e) { + log.warn("Bad URL "+e); + } + } + } + + private void addFramesetUrls(Document html, HTTPSampleResult result, + HTTPSamplerBase config, List potentialLinks) { + String base = ""; + NodeList baseList = html.getElementsByTagName("base"); // $NON-NLS-1$ + if (baseList.getLength() > 0) { + base = baseList.item(0).getAttributes().getNamedItem("href") // $NON-NLS-1$ + .getNodeValue(); + } + NodeList nodeList = html.getElementsByTagName("frame"); // $NON-NLS-1$ + for (int i = 0; i < nodeList.getLength(); i++) { + Node tempNode = nodeList.item(i); + NamedNodeMap nnm = tempNode.getAttributes(); + Node namedItem = nnm.getNamedItem("src"); // $NON-NLS-1$ + if (namedItem == null) { + continue; + } + String hrefStr = namedItem.getNodeValue(); + try { + HTTPSamplerBase newUrl = HtmlParsingUtils.createUrlFromAnchor( + hrefStr, ConversionUtils.makeRelativeURL(result.getURL(), base)); + newUrl.setMethod(HTTPConstants.GET); + if (log.isDebugEnabled()) { + log.debug("Potential match: " + newUrl); + } + if (HtmlParsingUtils.isAnchorMatched(newUrl, config)) { + log.debug("Matched!"); + potentialLinks.add(newUrl); + } + } catch (MalformedURLException e) { + log.warn("Bad URL "+e); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/ParamMask.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/ParamMask.java new file mode 100644 index 0000000..6df16b6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/ParamMask.java @@ -0,0 +1,248 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.LongProperty; + +/** + * This object defines with what a parameter has its value replaced, and the + * policies for how that value changes. Used in {@link ParamModifier}. + * + * @version $Revision: 905028 $ + */ +public class ParamMask extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + private String PREFIX = "ParamModifier.prefix"; + + private String FIELD_NAME = "ParamModifier.field_name"; + + private String UPPER_BOUND = "ParamModifier.upper_bound"; + + private String LOWER_BOUND = "ParamModifier.lower_bound"; + + private String INCREMENT = "ParamModifier.increment"; + + private String SUFFIX = "ParamModifier.suffix"; + + private long _value = 0; + + /** + * Default constructor. + */ + public ParamMask() { + setFieldName(""); + setPrefix(""); + setLowerBound(0); + setUpperBound(0); + setIncrement(0); + setSuffix(""); + } + + /** + * Sets the prefix for the long value. The prefix, the value + * and the suffix are concatenated to give the parameter value. This allows + * a wider range of posibilities for the parameter values. + * + * @param prefix + * a string to prefix to the parameter value + */ + public void setPrefix(String prefix) { + setProperty(PREFIX, prefix); + } + + /** + * Set the current value of the long portion of the parameter + * value to replace. This is usually not used, as the method + * {@link #resetValue} is used to define a policy for the starting value. + * + * @param val the new parameter value + */ + public void setValue(long val) { + _value = val; + } + + public void setFieldName(String fieldName) { + setProperty(FIELD_NAME, fieldName); + } + + /** + * Sets the lowest possible value that the long portion of + * the parameter value may be. + * + * @param val + * the new lowest possible parameter value + */ + public void setLowerBound(long val) { + setProperty(new LongProperty(LOWER_BOUND, val)); + } + + /** + * Sets the highest possible value that the long portion of + * the parameter value may be. + * + * @param val + * the new highest possible parameter value + */ + public void setUpperBound(long val) { + setProperty(new LongProperty(UPPER_BOUND, val)); + } + + /** + * Sets the number by which the parameter value is incremented between + * loops. + * + * @param incr + * the new increment for the parameter value + */ + public void setIncrement(long incr) { + setProperty(new LongProperty(INCREMENT, incr)); + } + + /** + * Sets the suffix for the long value. The prefix, the value + * and the suffix are concatenated to give the parameter value. This allows + * a wider range of posibilities for the parameter values. + * + * @param suffix + * a string to suffix to the parameter value + */ + public void setSuffix(String suffix) { + setProperty(SUFFIX, suffix); + } + + /** + * Accessor method to return the String that will be prefixed + * to the long value. + * + * @return the parameter value prefix + */ + public String getPrefix() { + return getPropertyAsString(PREFIX); + } + + /** + * Accessor method, returns the lowest possible value that the + * long portion of the parameter value may be. + * + * @return the lower bound of the possible values + */ + public long getLowerBound() { + return getPropertyAsLong(LOWER_BOUND); + } + + /** + * Accessor method, returns the highest possible value that the + * long portion of the parameter value may be. + * + * @return the higher bound of the possible values + */ + public long getUpperBound() { + return getPropertyAsLong(UPPER_BOUND); + } + + /** + * Accessor method, returns the number by which the parameter value is + * incremented between loops. + * + * @return the increment + */ + public long getIncrement() { + return getPropertyAsLong(INCREMENT); + } + + /** + * Accessor method to return the String that will be suffixed + * to the long value. + * + * @return the parameter value suffix + */ + public String getSuffix() { + return getPropertyAsString(SUFFIX); + } + + /* + * ----------------------------------------------------------------------- + * Methods + * ----------------------------------------------------------------------- + */ + + /** + * Returns the current value, prefixed and suffixed, as a string, then + * increments it. If the incremented value is above the upper bound, the + * value is reset to the lower bound.
+ *

+ * This method determines the policy of what happens when an upper bound is + * reached/surpassed. + * + * @return a String representing the current + * long value + */ + public String getNextValue() { + // return the current value (don't forget the prefix!) + String retval = getPrefix() + Long.toString(_value) + getSuffix(); + + // increment the value + _value += getIncrement(); + if (_value > getUpperBound()) { + _value = getLowerBound(); + } + + return retval; + } + + /** + * This method determines the policy of what value to start (and re-start) + * at. + */ + public void resetValue() { + _value = getLowerBound(); + } + + public String getFieldName() { + return getPropertyAsString(FIELD_NAME); + } + + /** + * For debugging purposes. + * + * @return a String representing the object + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("-------------------------------\n"); + sb.append("Dumping ParamMask Object\n"); + sb.append("-------------------------------\n"); + sb.append("Name = " + getFieldName() + "\n"); + sb.append("Prefix = " + getPrefix() + "\n"); + sb.append("Current Value = " + _value + "\n"); + sb.append("Lower Bound = " + getLowerBound() + "\n"); + sb.append("Upper Bound = " + getUpperBound() + "\n"); + sb.append("Increment = " + getIncrement() + "\n"); + sb.append("Suffix = " + getSuffix() + "\n"); + sb.append("-------------------------------\n"); + + return sb.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/ParamModifier.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/ParamModifier.java new file mode 100644 index 0000000..32f6bf7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/ParamModifier.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier; + +import java.io.Serializable; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; + +/** + * This modifier will replace any single http sampler's url parameter value with + * a value from a given range - thereby "masking" the value set in the http + * sampler. The parameter names must match exactly, and the parameter value must + * be preset to "*" to diferentiate between duplicate parameter names. + *

+ * For example, if you set up the modifier with a lower bound of 1, an upper + * bound of 10, and an increment of 2, and run the loop 12 times, the parameter + * will have the following values (one per loop): 1, 3, 5, 7, 9, 1, 3, 5, 7, 9, + * 1, 3 + *

+ * The {@link ParamMask} object contains most of the logic for stepping through + * this loop. You can make large modifications to this modifier's behaviour by + * changing one or two method implementations there. + * + * @see ParamMask + * @version $Revision: 905028 $ + */ +public class ParamModifier extends AbstractTestElement implements TestListener, PreProcessor, Serializable { + + private static final long serialVersionUID = 240L; + + /* + * ------------------------------------------------------------------------ + * Fields + * ------------------------------------------------------------------------ + */ + + /** + * The key used to find the ParamMask object in the HashMap. + */ + private final static String MASK = "ParamModifier.mask"; + + /* + * ------------------------------------------------------------------------ + * Constructors + * ------------------------------------------------------------------------ + */ + + /** + * Default constructor. + */ + public ParamModifier() { + setProperty(new TestElementProperty(MASK, new ParamMask())); + } + + public ParamMask getMask() { + return (ParamMask) getProperty(MASK).getObjectValue(); + } + + public void testStarted() { + getMask().resetValue(); + } + + public void testStarted(String host) { + getMask().resetValue(); + } + + public void testEnded() { + } + + public void testEnded(String host) { + } + + /* + * ------------------------------------------------------------------------ + * Methods implemented from interface org.apache.jmeter.config.Modifier + * ------------------------------------------------------------------------ + */ + + /** + * Modifies an entry object to replace the value of any url parameter that + * matches a defined mask. + * + */ + public void process() { + Sampler sam = getThreadContext().getCurrentSampler(); + HTTPSamplerBase sampler = null; + if (!(sam instanceof HTTPSamplerBase)) { + return; + } else { + sampler = (HTTPSamplerBase) sam; + } + boolean modified = false; + PropertyIterator iter = sampler.getArguments().iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + modified = modifyArgument(arg); + if (modified) { + break; + } + } + } + + /* + * ------------------------------------------------------------------------ + * Methods + * ------------------------------------------------------------------------ + */ + + /** + * Helper method for {@link #modifyEntry} Replaces a parameter's value if + * the parameter name matches the mask name and the value is a '*'. + * + * @param arg + * an {@link Argument} representing a http parameter + * @return trueif the value was replaced + */ + private boolean modifyArgument(Argument arg) { + // if a mask for this argument exists + if (arg.getName().equals(getMask().getFieldName())) { + // values to be masked must be set in the WebApp to "*" + if ("*".equals(arg.getValue())) { + arg.setValue(getMask().getNextValue()); + return true; + } + } + return false; + } + + /** + * @see TestListener#testIterationStart(LoopIterationEvent) + */ + public void testIterationStart(LoopIterationEvent event) { + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/URLRewritingModifier.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/URLRewritingModifier.java new file mode 100644 index 0000000..286ffa5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/URLRewritingModifier.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier; + +import java.io.Serializable; + +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +//For unit tests, @see TestURLRewritingModifier + +public class URLRewritingModifier extends AbstractTestElement implements Serializable, PreProcessor { + + private static final long serialVersionUID = 233L; + + private static final String SEMI_COLON = ";"; // $NON-NLS-1$ + + private transient Pattern pathExtensionEqualsQuestionmarkRegexp; + + private transient Pattern pathExtensionEqualsNoQuestionmarkRegexp; + + private transient Pattern parameterRegexp; + + private transient Pattern pathExtensionNoEqualsQuestionmarkRegexp; + + private transient Pattern pathExtensionNoEqualsNoQuestionmarkRegexp; + + // transient Perl5Compiler compiler = new Perl5Compiler(); + private final static String ARGUMENT_NAME = "argument_name"; // $NON-NLS-1$ + + private final static String PATH_EXTENSION = "path_extension"; // $NON-NLS-1$ + + private final static String PATH_EXTENSION_NO_EQUALS = "path_extension_no_equals"; // $NON-NLS-1$ + + private final static String PATH_EXTENSION_NO_QUESTIONMARK = "path_extension_no_questionmark"; // $NON-NLS-1$ + + private final static String SHOULD_CACHE = "cache_value"; // $NON-NLS-1$ + + // PreProcessors are cloned per-thread, so this will be saved per-thread + private transient String savedValue = ""; // $NON-NLS-1$ + + public void process() { + JMeterContext ctx = getThreadContext(); + Sampler sampler = ctx.getCurrentSampler(); + if (!(sampler instanceof HTTPSamplerBase)) {// Ignore non-HTTP samplers + return; + } + SampleResult responseText = ctx.getPreviousResult(); + if (responseText == null) { + return; + } + initRegex(getArgumentName()); + String text = responseText.getResponseDataAsString(); + Perl5Matcher matcher = JMeterUtils.getMatcher(); + String value = ""; + if (isPathExtension() && isPathExtensionNoEquals() && isPathExtensionNoQuestionmark()) { + if (matcher.contains(text, pathExtensionNoEqualsNoQuestionmarkRegexp)) { + MatchResult result = matcher.getMatch(); + value = result.group(1); + } + } else if (isPathExtension() && isPathExtensionNoEquals()) // && !isPathExtensionNoQuestionmark() + { + if (matcher.contains(text, pathExtensionNoEqualsQuestionmarkRegexp)) { + MatchResult result = matcher.getMatch(); + value = result.group(1); + } + } else if (isPathExtension() && isPathExtensionNoQuestionmark()) // && !isPathExtensionNoEquals() + { + if (matcher.contains(text, pathExtensionEqualsNoQuestionmarkRegexp)) { + MatchResult result = matcher.getMatch(); + value = result.group(1); + } + } else if (isPathExtension()) // && !isPathExtensionNoEquals() && !isPathExtensionNoQuestionmark() + { + if (matcher.contains(text, pathExtensionEqualsQuestionmarkRegexp)) { + MatchResult result = matcher.getMatch(); + value = result.group(1); + } + } else // if ! isPathExtension() + { + if (matcher.contains(text, parameterRegexp)) { + MatchResult result = matcher.getMatch(); + for (int i = 1; i < result.groups(); i++) { + value = result.group(i); + if (value != null) { + break; + } + } + } + } + + // Bug 15025 - save session value across samplers + if (shouldCache()){ + if (value == null || value.length() == 0) { + value = savedValue; + } else { + savedValue = value; + } + } + modify((HTTPSamplerBase) sampler, value); + } + + private void modify(HTTPSamplerBase sampler, String value) { + if (isPathExtension()) { + if (isPathExtensionNoEquals()) { + sampler.setPath(sampler.getPath() + SEMI_COLON + getArgumentName() + value); // $NON-NLS-1$ + } else { + sampler.setPath(sampler.getPath() + SEMI_COLON + getArgumentName() + "=" + value); // $NON-NLS-1$ // $NON-NLS-2$ + } + } else { + sampler.getArguments().removeArgument(getArgumentName()); + sampler.getArguments().addArgument(new HTTPArgument(getArgumentName(), value, true)); + } + } + + public void setArgumentName(String argName) { + setProperty(ARGUMENT_NAME, argName); + } + + private void initRegex(String argName) { + String quotedArg = Perl5Compiler.quotemeta(argName);// Don't get tripped up by RE chars in the arg name + pathExtensionEqualsQuestionmarkRegexp = JMeterUtils.getPatternCache().getPattern( + SEMI_COLON + quotedArg + "=([^\"'<>&\\s;]*)", // $NON-NLS-1$ + Perl5Compiler.MULTILINE_MASK | Perl5Compiler.READ_ONLY_MASK); + + pathExtensionEqualsNoQuestionmarkRegexp = JMeterUtils.getPatternCache().getPattern( + SEMI_COLON + quotedArg + "=([^\"'<>&\\s;?]*)", // $NON-NLS-1$ + Perl5Compiler.MULTILINE_MASK | Perl5Compiler.READ_ONLY_MASK); + + pathExtensionNoEqualsQuestionmarkRegexp = JMeterUtils.getPatternCache().getPattern( + SEMI_COLON + quotedArg + "([^\"'<>&\\s;]*)", // $NON-NLS-1$ + Perl5Compiler.MULTILINE_MASK | Perl5Compiler.READ_ONLY_MASK); + + pathExtensionNoEqualsNoQuestionmarkRegexp = JMeterUtils.getPatternCache().getPattern( + SEMI_COLON + quotedArg + "([^\"'<>&\\s;?]*)", // $NON-NLS-1$ + Perl5Compiler.MULTILINE_MASK | Perl5Compiler.READ_ONLY_MASK); + + parameterRegexp = JMeterUtils.getPatternCache().getPattern( + // ;sessionid=value + "[;\\?&]" + quotedArg + "=([^\"'<>&\\s;\\\\]*)" + // $NON-NLS-1$ + + // name="sessionid" value="value" + "|\\s[Nn][Aa][Mm][Ee]\\s*=\\s*[\"']" + quotedArg + + "[\"']" + "[^>]*" // $NON-NLS-1$ + + "\\s[vV][Aa][Ll][Uu][Ee]\\s*=\\s*[\"']" // $NON-NLS-1$ + + "([^\"']*)" + "[\"']" // $NON-NLS-1$ + + // value="value" name="sessionid" + + "|\\s[vV][Aa][Ll][Uu][Ee]\\s*=\\s*[\"']" // $NON-NLS-1$ + + "([^\"']*)" + "[\"']" + "[^>]*" // $NON-NLS-1$ // $NON-NLS-2$ // $NON-NLS-3$ + + "\\s[Nn][Aa][Mm][Ee]\\s*=\\s*[\"']" // $NON-NLS-1$ + + quotedArg + "[\"']", // $NON-NLS-1$ + Perl5Compiler.MULTILINE_MASK | Perl5Compiler.READ_ONLY_MASK); + // NOTE: the handling of simple- vs. double-quotes could be formally + // more accurate, but I can't imagine a session id containing + // either, so we should be OK. The whole set of expressions is a + // quick hack anyway, so who cares. + } + + public String getArgumentName() { + return getPropertyAsString(ARGUMENT_NAME); + } + + public void setPathExtension(boolean pathExt) { + setProperty(new BooleanProperty(PATH_EXTENSION, pathExt)); + } + + public void setPathExtensionNoEquals(boolean pathExtNoEquals) { + setProperty(new BooleanProperty(PATH_EXTENSION_NO_EQUALS, pathExtNoEquals)); + } + + public void setPathExtensionNoQuestionmark(boolean pathExtNoQuestionmark) { + setProperty(new BooleanProperty(PATH_EXTENSION_NO_QUESTIONMARK, pathExtNoQuestionmark)); + } + + public void setShouldCache(boolean b) { + setProperty(new BooleanProperty(SHOULD_CACHE, b)); + } + + public boolean isPathExtension() { + return getPropertyAsBoolean(PATH_EXTENSION); + } + + public boolean isPathExtensionNoEquals() { + return getPropertyAsBoolean(PATH_EXTENSION_NO_EQUALS); + } + + public boolean isPathExtensionNoQuestionmark() { + return getPropertyAsBoolean(PATH_EXTENSION_NO_QUESTIONMARK); + } + + public boolean shouldCache() { + return getPropertyAsBoolean(SHOULD_CACHE,true); + } + + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLContentHandler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLContentHandler.java new file mode 100644 index 0000000..3e52c50 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLContentHandler.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier; + +import java.io.CharArrayWriter; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; + +/** + * The handler used to read in XML parameter data. + * + * @version $Revision: 810036 $ + */ +public class UserParameterXMLContentHandler implements ContentHandler { + // ------------------------------------------- + // Constants and Data Members + // ------------------------------------------- + + // Note UserParameterXML accesses this variable + // to obtain the Set data via method getParsedParameters() + private List> userThreads = new LinkedList>(); + + private String paramname = ""; + + private String paramvalue = ""; + + private Map nameValuePair = new HashMap(); + + /** Buffer for collecting data from the "characters" SAX event. */ + private CharArrayWriter contents = new CharArrayWriter(); + + // ------------------------------------------- + // Methods + // ------------------------------------------- + + /*------------------------------------------------------------------------- + * Methods implemented from org.xml.sax.ContentHandler + *----------------------------------------------------------------------- */ + public void setDocumentLocator(Locator locator) { + } + + public void startDocument() throws SAXException { + } + + public void endDocument() throws SAXException { + } + + public void startPrefixMapping(String prefix, String uri) throws SAXException { + } + + public void endPrefixMapping(String prefix) throws SAXException { + } + + public void startElement(String namespaceURL, String localName, String qName, Attributes atts) throws SAXException { + + contents.reset(); + + // haven't got to reset paramname & paramvalue + // but did it to keep the code looking correct + if (qName.equals("parameter")) { + paramname = ""; + paramvalue = ""; + } + + // must create a new object, + // or else end up with a set full of the same Map object + if (qName.equals("thread")) { + nameValuePair = new HashMap(); + } + + } + + public void endElement(String namespaceURI, String localName, String qName) throws SAXException { + if (qName.equals("paramname")) { + paramname = contents.toString(); + } + if (qName.equals("paramvalue")) { + paramvalue = contents.toString(); + } + if (qName.equals("parameter")) { + nameValuePair.put(paramname, paramvalue); + } + if (qName.equals("thread")) { + userThreads.add(nameValuePair); + } + } + + public void characters(char ch[], int start, int length) throws SAXException { + contents.write(ch, start, length); + } + + public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { + } + + public void processingInstruction(String target, String date) throws SAXException { + } + + public void skippedEntity(String name) throws SAXException { + } + + /*------------------------------------------------------------------------- + * Methods (used by UserParameterXML to get XML parameters from XML file) + *----------------------------------------------------------------------- */ + + /** + * results of parsing all user parameter data defined in XML file. + * + * @return all users name value pairs obtained from XML file + */ + public List> getParsedParameters() { + return userThreads; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLErrorHandler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLErrorHandler.java new file mode 100644 index 0000000..6b2a51a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLErrorHandler.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.jmeter.protocol.http.modifier; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * XML Parseing errors for XML parameters file are handled here. + * + */ +public class UserParameterXMLErrorHandler implements ErrorHandler { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public void warning(SAXParseException exception) throws SAXException { + log.warn("**Parsing Warning**\n" + " line: " + exception.getLineNumber() + "\n" + " URI: :" + + exception.getSystemId() + "\n" + " Message: " + exception.getMessage()); + throw new SAXException("Warning encountered"); + } + + public void error(SAXParseException exception) throws SAXException { + log.error("**Parsing Warning**\n" + " line: " + exception.getLineNumber() + "\n" + " URI: :" + + exception.getSystemId() + "\n" + " Message: " + exception.getMessage()); + throw new SAXException("Error encountered"); + } + + public void fatalError(SAXParseException exception) throws SAXException { + log.error("**Parsing Warning**\n" + " line: " + exception.getLineNumber() + "\n" + " URI: :" + + exception.getSystemId() + "\n" + " Message: " + exception.getMessage()); + throw new SAXException("Fatal Error encountered"); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLParser.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLParser.java new file mode 100644 index 0000000..c9156cc --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserParameterXMLParser.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.util.JMeterUtils; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +/** + * Parse an XML file to obtain parameter name and value information for all + * users defined in the XML file. + * + * This test element is deprecated. Test plans should use User Parameters instead. + * @deprecated + */ +@Deprecated +public class UserParameterXMLParser { + + /** + * Parse all user parameter data defined in XML file. + * + * @param xmlURI + * name of the XML to load users parameter data + * @return all users name value pairs obtained from XML file + */ + public List> getXMLParameters(String xmlURI) throws SAXException, IOException { + // create instances needed for parsing + XMLReader reader = JMeterUtils.getXMLParser(); + // XMLReaderFactory.createXMLReader(vendorParseClass); + UserParameterXMLContentHandler threadParametersContentHandler = new UserParameterXMLContentHandler(); + UserParameterXMLErrorHandler parameterErrorHandler = new UserParameterXMLErrorHandler(); + + // register content handler + reader.setContentHandler(threadParametersContentHandler); + + // register error handler + reader.setErrorHandler(parameterErrorHandler); + + // Request validation + reader.setFeature("http://xml.org/sax/features/validation", true); // $NON-NLS-1$ + + // parse + InputSource inputSource = new InputSource(xmlURI); + reader.parse(inputSource); + + return threadParametersContentHandler.getParsedParameters(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserSequence.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserSequence.java new file mode 100644 index 0000000..745fdf7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/UserSequence.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This module controls the Sequence in which user details are returned. This + * module uses round robin allocation of users. + * + * @version $Revision: 810036 $ + */ +public class UserSequence implements Serializable { + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // ------------------------------------------- + // Constants and Data Members + // ------------------------------------------- + private List> allUsers; + + private transient Iterator> indexOfUsers; + + // ------------------------------------------- + // Constructors + // ------------------------------------------- + + public UserSequence() { + } + + /** + * Load all user and parameter data into the sequence module. + *

+ * ie a Set of Mapped "parameter names and parameter values" for each user + * to be loaded into the sequencer. + */ + public UserSequence(List> allUsers) { + this.allUsers = allUsers; + + // initalise pointer to first user + indexOfUsers = allUsers.iterator(); + } + + // ------------------------------------------- + // Methods + // ------------------------------------------- + + /** + * Returns the parameter data for the next user in the sequence + * + * @return a Map object of parameter names and matching parameter values for + * the next user + */ + public synchronized Map getNextUserMods() { + // Use round robin allocation of user details + if (!indexOfUsers.hasNext()) { + indexOfUsers = allUsers.iterator(); + } + + Map user; + if (indexOfUsers.hasNext()) { + user = indexOfUsers.next(); + log.debug("UserSequence.getNextuserMods(): current parameters will be " + "changed to: " + user); + } else { + // no entries in all users, therefore create an empty Map object + user = new HashMap(); + } + + return user; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/AnchorModifierGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/AnchorModifierGui.java new file mode 100644 index 0000000..a356935 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/AnchorModifierGui.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier.gui; + +import java.awt.BorderLayout; + +import org.apache.jmeter.processor.gui.AbstractPreProcessorGui; +import org.apache.jmeter.protocol.http.modifier.AnchorModifier; +import org.apache.jmeter.testelement.TestElement; + +public class AnchorModifierGui extends AbstractPreProcessorGui { + private static final long serialVersionUID = 240L; + + public AnchorModifierGui() { + init(); + } + + public String getLabelResource() { + return "anchor_modifier_title"; //$NON-NLS-1$ + } + + public TestElement createTestElement() { + AnchorModifier modifier = new AnchorModifier(); + modifyTestElement(modifier); + return modifier; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement modifier) { + configureTestElement(modifier); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/ParamModifierGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/ParamModifierGui.java new file mode 100644 index 0000000..8e800a8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/ParamModifierGui.java @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.processor.gui.AbstractPreProcessorGui; +import org.apache.jmeter.protocol.http.modifier.ParamMask; +import org.apache.jmeter.protocol.http.modifier.ParamModifier; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * A swing panel to allow UI with the ParamModifier class. + * + * Created Jan 18, 2002 + * + */ +public class ParamModifierGui extends AbstractPreProcessorGui implements FocusListener { + + private static final long serialVersionUID = 240L; + + /* + * These are used as GUI item names; + * LOWERBOUND, UPPERBOUND and INCREMENT are used in the focusLost() method + */ + + private static final String NAME = "name"; + + private static final String PREFIX = "prefix"; + + private static final String LOWERBOUND = "lowerBound"; + + private static final String UPPERBOUND = "upperBound"; + + private static final String INCREMENT = "increment"; + + private static final String SUFFIX = "suffix"; + + private static final String ZERO = "0"; //$NON-NLS-1$ + + private JTextField _fieldName; + + private JTextField _prefix; + + private JTextField _lowerBound; + + private JTextField _upperBound; + + private JTextField _increment; + + private JTextField _suffix; + + public ParamModifierGui() { + init(); + } + + public String getLabelResource() { + return "html_parameter_mask"; //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + ParamModifier model = (ParamModifier) el; + updateGui(model); + } + + public TestElement createTestElement() { + ParamModifier modifier = new ParamModifier(); + modifyTestElement(modifier); + return modifier; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement m) { + configureTestElement(m); + if (m instanceof ParamModifier) { + ParamModifier modifier = (ParamModifier) m; + ParamMask mask = modifier.getMask(); + mask.setFieldName(_fieldName.getText()); + mask.setPrefix(_prefix.getText()); + mask.setLowerBound(Long.parseLong(_lowerBound.getText())); + mask.setIncrement(Long.parseLong(_increment.getText())); + mask.setUpperBound(Long.parseLong(_upperBound.getText())); + mask.setSuffix(_suffix.getText()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + _fieldName.setText(""); //$NON-NLS-1$ + _prefix.setText(""); //$NON-NLS-1$ + _lowerBound.setText(ZERO); + _upperBound.setText("10"); //$NON-NLS-1$ + _increment.setText("1"); //$NON-NLS-1$ + _suffix.setText(""); //$NON-NLS-1$ + } + + public void focusGained(FocusEvent evt) { + } + + public void focusLost(FocusEvent evt) { + String name = ((Component) evt.getSource()).getName(); + if (evt.isTemporary()) { + return; + } else if (name.equals(LOWERBOUND)) { + checkTextField(evt, ZERO); + } else if (name.equals(UPPERBOUND)) { + checkTextField(evt, ZERO); + } else if (name.equals(INCREMENT)) { + checkTextField(evt, ZERO); + } + } + + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + add(getParameterMaskPanel(), BorderLayout.CENTER); + // this.updateUI(); + } + + private void updateGui(ParamModifier model) { + _fieldName.setText(model.getMask().getFieldName()); + _prefix.setText(model.getMask().getPrefix()); + _lowerBound.setText(Long.toString(model.getMask().getLowerBound())); + _upperBound.setText(Long.toString(model.getMask().getUpperBound())); + _increment.setText(Long.toString(model.getMask().getIncrement())); + _suffix.setText(model.getMask().getSuffix()); + } + + private JPanel createLabeledField(String labelResName, JTextField field) { + JLabel label = new JLabel(JMeterUtils.getResString(labelResName)); + label.setLabelFor(field); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(label, BorderLayout.NORTH); + panel.add(field, BorderLayout.CENTER); + return panel; + } + + private JPanel getParameterMaskPanel() { + HorizontalPanel panel = new HorizontalPanel(10, HorizontalPanel.TOP_ALIGNMENT); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("html_parameter_mask"))); //$NON-NLS-1$ + + _fieldName = new JTextField(10); + _fieldName.setName(NAME); + panel.add(createLabeledField("name", _fieldName)); //$NON-NLS-1$ resource name + + _prefix = new JTextField(5); + _prefix.setName(PREFIX); + panel.add(createLabeledField("id_prefix", _prefix)); //$NON-NLS-1$ resource name + + _lowerBound = new JTextField(ZERO, 5); + _lowerBound.addFocusListener(this); + _lowerBound.setName(LOWERBOUND); + panel.add(createLabeledField("lower_bound", _lowerBound)); //$NON-NLS-1$ resource name + + _upperBound = new JTextField("10", 5); + _upperBound.addFocusListener(this); + _upperBound.setName(UPPERBOUND); + panel.add(createLabeledField("upper_bound", _upperBound)); //$NON-NLS-1$ resource name + + _increment = new JTextField("1", 3); + _increment.addFocusListener(this); + _increment.setName(INCREMENT); + panel.add(createLabeledField("increment", _increment)); //$NON-NLS-1$ resource name + + _suffix = new JTextField(5); + _suffix.setName(SUFFIX); + panel.add(createLabeledField("id_suffix", _suffix)); //$NON-NLS-1$ resource name + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(panel, BorderLayout.NORTH); + return mainPanel; + } + + /** + * Used to validate a text field that requires a long input. + * Returns the long if valid, else creates a pop-up error + * message and throws a NumberFormatException. + * + * @return the number entered in the text field + */ + private long checkTextField(FocusEvent evt, String defaultValue) { + JTextField temp = (JTextField) evt.getSource(); + // boolean pass = true; + long longVal = 0; + + try { + longVal = Long.parseLong(temp.getText()); + } catch (NumberFormatException err) { + JOptionPane.showMessageDialog(this, "This field must have a long value!", "Value Required", + JOptionPane.ERROR_MESSAGE); + temp.setText(defaultValue); + temp.requestFocus(); + } + return longVal; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/URLRewritingModifierGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/URLRewritingModifierGui.java new file mode 100644 index 0000000..145d8d9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/modifier/gui/URLRewritingModifierGui.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.modifier.gui; + +import java.awt.BorderLayout; + +import javax.swing.JCheckBox; + +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.processor.gui.AbstractPreProcessorGui; +import org.apache.jmeter.protocol.http.modifier.URLRewritingModifier; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; + +public class URLRewritingModifierGui extends AbstractPreProcessorGui { + private static final long serialVersionUID = 240L; + + private JLabeledTextField argumentName; + + private JCheckBox pathExt; + + private JCheckBox pathExtNoEquals; + + private JCheckBox pathExtNoQuestionmark; + + private JCheckBox shouldCache; + + public String getLabelResource() { + return "http_url_rewriting_modifier_title"; // $NON-NLS-1$ + } + + public URLRewritingModifierGui() { + init(); + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + VerticalPanel mainPanel = new VerticalPanel(); + + argumentName = new JLabeledTextField(JMeterUtils.getResString("session_argument_name"), 10); // $NON-NLS-1$ + mainPanel.add(argumentName); + + pathExt = new JCheckBox(JMeterUtils.getResString("path_extension_choice")); // $NON-NLS-1$ + mainPanel.add(pathExt); + + pathExtNoEquals = new JCheckBox(JMeterUtils.getResString("path_extension_dont_use_equals")); // $NON-NLS-1$ + mainPanel.add(pathExtNoEquals); + + pathExtNoQuestionmark = new JCheckBox(JMeterUtils.getResString("path_extension_dont_use_questionmark")); // $NON-NLS-1$ + mainPanel.add(pathExtNoQuestionmark); + + shouldCache = new JCheckBox(JMeterUtils.getResString("cache_session_id")); // $NON-NLS-1$ + shouldCache.setSelected(true); + mainPanel.add(shouldCache); + + add(mainPanel, BorderLayout.CENTER); + } + + /** + * {@inheritDoc} + */ + public TestElement createTestElement() { + URLRewritingModifier modifier = new URLRewritingModifier(); + modifyTestElement(modifier); + return modifier; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + *

+ * {@inheritDoc} + */ + public void modifyTestElement(TestElement modifier) { + this.configureTestElement(modifier); + URLRewritingModifier rewritingModifier = ((URLRewritingModifier) modifier); + rewritingModifier.setArgumentName(argumentName.getText()); + rewritingModifier.setPathExtension(pathExt.isSelected()); + rewritingModifier.setPathExtensionNoEquals(pathExtNoEquals.isSelected()); + rewritingModifier.setPathExtensionNoQuestionmark(pathExtNoQuestionmark.isSelected()); + rewritingModifier.setShouldCache((shouldCache.isSelected())); + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + + argumentName.setText(""); //$NON-NLS-1$ + pathExt.setSelected(false); + pathExtNoEquals.setSelected(false); + pathExtNoQuestionmark.setSelected(false); + shouldCache.setSelected(false); + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement el) { + URLRewritingModifier rewritingModifier = ((URLRewritingModifier) el); + argumentName.setText(rewritingModifier.getArgumentName()); + pathExt.setSelected(rewritingModifier.isPathExtension()); + pathExtNoEquals.setSelected(rewritingModifier.isPathExtensionNoEquals()); + pathExtNoQuestionmark.setSelected(rewritingModifier.isPathExtensionNoQuestionmark()); + shouldCache.setSelected(rewritingModifier.shouldCache()); + + super.configure(el); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParseError.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParseError.java new file mode 100644 index 0000000..0454b02 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParseError.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.protocol.http.parser; + +/** + * Error class for use with HTMLParser classes. + * The main rationale for the class + * was to support chained Errors in JDK 1.3, + * however it is now used in its own right. + * + * @version $Revision: 905028 $ + */ +public class HTMLParseError extends Error { + private static final long serialVersionUID = 240L; + + public HTMLParseError() { + super(); + } + + public HTMLParseError(String message) { + super(message); + } + + public HTMLParseError(Throwable cause) { + super(cause); + } + + public HTMLParseError(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParseException.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParseException.java new file mode 100644 index 0000000..abec20d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParseException.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.protocol.http.parser; + +/** + * Exception class for use with HTMLParser classes. + * The main rationale for the class + * was to support chained Exceptions in JDK 1.3, + * however it is now used in its own right. + * + * @version $Revision: 905028 $ + */ +public class HTMLParseException extends Exception { + private static final long serialVersionUID = 240L; + + public HTMLParseException() { + super(); + } + + public HTMLParseException(String message) { + super(message); + } + + public HTMLParseException(Throwable cause) { + super(cause); + } + + public HTMLParseException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParser.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParser.java new file mode 100644 index 0000000..5d9d482 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HTMLParser.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.parser; + +import java.net.URL; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * HtmlParsers can parse HTML content to obtain URLs. + * + */ +public abstract class HTMLParser { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected static final String ATT_BACKGROUND = "background";// $NON-NLS-1$ + protected static final String ATT_HREF = "href";// $NON-NLS-1$ + protected static final String ATT_REL = "rel";// $NON-NLS-1$ + protected static final String ATT_SRC = "src";// $NON-NLS-1$ + protected static final String ATT_STYLE = "style";// $NON-NLS-1$ + protected static final String ATT_TYPE = "type";// $NON-NLS-1$ + protected static final String ATT_IS_IMAGE = "image";// $NON-NLS-1$ + protected static final String TAG_APPLET = "applet";// $NON-NLS-1$ + protected static final String TAG_BASE = "base";// $NON-NLS-1$ + protected static final String TAG_BGSOUND = "bgsound";// $NON-NLS-1$ + protected static final String TAG_EMBED = "embed";// $NON-NLS-1$ + protected static final String TAG_FRAME = "frame";// $NON-NLS-1$ + protected static final String TAG_IFRAME = "iframe";// $NON-NLS-1$ + protected static final String TAG_IMAGE = "img";// $NON-NLS-1$ + protected static final String TAG_INPUT = "input";// $NON-NLS-1$ + protected static final String TAG_LINK = "link";// $NON-NLS-1$ + protected static final String TAG_SCRIPT = "script";// $NON-NLS-1$ + protected static final String STYLESHEET = "stylesheet";// $NON-NLS-1$ + + // Cache of parsers - parsers must be re-usable + private static final Map parsers = new ConcurrentHashMap(3); + + public static final String PARSER_CLASSNAME = "htmlParser.className"; // $NON-NLS-1$ + + public static final String DEFAULT_PARSER = + "org.apache.jmeter.protocol.http.parser.HtmlParserHTMLParser"; // $NON-NLS-1$ + + /** + * Protected constructor to prevent instantiation except from within + * subclasses. + */ + protected HTMLParser() { + } + + public static final HTMLParser getParser() { + return getParser(JMeterUtils.getPropDefault(PARSER_CLASSNAME, DEFAULT_PARSER)); + } + + public static final HTMLParser getParser(String htmlParserClassName) { + + // Is there a cached parser? + HTMLParser pars = parsers.get(htmlParserClassName); + if (pars != null) { + log.debug("Fetched " + htmlParserClassName); + return pars; + } + + try { + Object clazz = Class.forName(htmlParserClassName).newInstance(); + if (clazz instanceof HTMLParser) { + pars = (HTMLParser) clazz; + } else { + throw new HTMLParseError(new ClassCastException(htmlParserClassName)); + } + } catch (InstantiationException e) { + throw new HTMLParseError(e); + } catch (IllegalAccessException e) { + throw new HTMLParseError(e); + } catch (ClassNotFoundException e) { + throw new HTMLParseError(e); + } + log.info("Created " + htmlParserClassName); + if (pars.isReusable()) { + parsers.put(htmlParserClassName, pars);// cache the parser + } + + return pars; + } + + /** + * Get the URLs for all the resources that a browser would automatically + * download following the download of the HTML content, that is: images, + * stylesheets, javascript files, applets, etc... + *

+ * URLs should not appear twice in the returned iterator. + *

+ * Malformed URLs can be reported to the caller by having the Iterator + * return the corresponding RL String. Overall problems parsing the html + * should be reported by throwing an HTMLParseException. + * + * @param html + * HTML code + * @param baseUrl + * Base URL from which the HTML code was obtained + * @param encoding Charset + * @return an Iterator for the resource URLs + */ + public Iterator getEmbeddedResourceURLs(byte[] html, URL baseUrl, String encoding) throws HTMLParseException { + // The Set is used to ignore duplicated binary files. + // Using a LinkedHashSet to avoid unnecessary overhead in iterating + // the elements in the set later on. As a side-effect, this will keep + // them roughly in order, which should be a better model of browser + // behaviour. + + Collection col = new LinkedHashSet(); + return getEmbeddedResourceURLs(html, baseUrl, new URLCollection(col),encoding); + + // An additional note on using HashSets to store URLs: I just + // discovered that obtaining the hashCode of a java.net.URL implies + // a domain-name resolution process. This means significant delays + // can occur, even more so if the domain name is not resolvable. + // Whether this can be a problem in practical situations I can't tell, + // but + // thought I'd keep a note just in case... + // BTW, note that using a List and removing duplicates via scan + // would not help, since URL.equals requires name resolution too. + // The above problem has now been addressed with the URLString and + // URLCollection classes. + + } + + /** + * Get the URLs for all the resources that a browser would automatically + * download following the download of the HTML content, that is: images, + * stylesheets, javascript files, applets, etc... + *

+ * All URLs should be added to the Collection. + *

+ * Malformed URLs can be reported to the caller by having the Iterator + * return the corresponding RL String. Overall problems parsing the html + * should be reported by throwing an HTMLParseException. + * + * N.B. The Iterator returns URLs, but the Collection will contain objects + * of class URLString. + * + * @param html + * HTML code + * @param baseUrl + * Base URL from which the HTML code was obtained + * @param coll + * URLCollection + * @param encoding Charset + * @return an Iterator for the resource URLs + */ + public abstract Iterator getEmbeddedResourceURLs(byte[] html, URL baseUrl, URLCollection coll, String encoding) + throws HTMLParseException; + + /** + * Get the URLs for all the resources that a browser would automatically + * download following the download of the HTML content, that is: images, + * stylesheets, javascript files, applets, etc... + * + * N.B. The Iterator returns URLs, but the Collection will contain objects + * of class URLString. + * + * @param html + * HTML code + * @param baseUrl + * Base URL from which the HTML code was obtained + * @param coll + * Collection - will contain URLString objects, not URLs + * @param encoding Charset + * @return an Iterator for the resource URLs + */ + public Iterator getEmbeddedResourceURLs(byte[] html, URL baseUrl, Collection coll, String encoding) throws HTMLParseException { + return getEmbeddedResourceURLs(html, baseUrl, new URLCollection(coll), encoding); + } + + /** + * Parsers should over-ride this method if the parser class is re-usable, in + * which case the class will be cached for the next getParser() call. + * + * @return true if the Parser is reusable + */ + protected boolean isReusable() { + return false; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HtmlParserHTMLParser.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HtmlParserHTMLParser.java new file mode 100644 index 0000000..c809f05 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HtmlParserHTMLParser.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.parser; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; + +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.htmlparser.Node; +import org.htmlparser.Parser; +import org.htmlparser.Tag; +import org.htmlparser.tags.AppletTag; +import org.htmlparser.tags.BaseHrefTag; +import org.htmlparser.tags.BodyTag; +import org.htmlparser.tags.CompositeTag; +import org.htmlparser.tags.FrameTag; +import org.htmlparser.tags.ImageTag; +import org.htmlparser.tags.InputTag; +import org.htmlparser.tags.ScriptTag; +import org.htmlparser.util.NodeIterator; +import org.htmlparser.util.ParserException; + +/** + * HtmlParser implementation using SourceForge's HtmlParser. + * + */ +class HtmlParserHTMLParser extends HTMLParser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + static{ + org.htmlparser.scanners.ScriptScanner.STRICT = false; // Try to ensure that more javascript code is processed OK ... + } + + protected HtmlParserHTMLParser() { + super(); + log.info("Using htmlparser version: "+Parser.getVersion()); + } + + @Override + protected boolean isReusable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator getEmbeddedResourceURLs(byte[] html, URL baseUrl, URLCollection urls, String encoding) throws HTMLParseException { + + if (log.isDebugEnabled()) { + log.debug("Parsing html of: " + baseUrl); + } + + Parser htmlParser = null; + try { + String contents = new String(html,encoding); + htmlParser = new Parser(); + htmlParser.setInputHTML(contents); + } catch (Exception e) { + throw new HTMLParseException(e); + } + + // Now parse the DOM tree + try { + // we start to iterate through the elements + parseNodes(htmlParser.elements(), new URLPointer(baseUrl), urls); + log.debug("End : parseNodes"); + } catch (ParserException e) { + throw new HTMLParseException(e); + } + + return urls.iterator(); + } + + /* + * A dummy class to pass the pointer of URL. + */ + private static class URLPointer { + private URLPointer(URL newUrl) { + url = newUrl; + } + private URL url; + } + + /** + * Recursively parse all nodes to pick up all URL s. + * @see e the nodes to be parsed + * @see baseUrl Base URL from which the HTML code was obtained + * @see urls URLCollection + */ + private void parseNodes(final NodeIterator e, + final URLPointer baseUrl, final URLCollection urls) + throws HTMLParseException, ParserException { + while(e.hasMoreNodes()) { + Node node = e.nextNode(); + // a url is always in a Tag. + if (!(node instanceof Tag)) { + continue; + } + Tag tag = (Tag) node; + String tagname=tag.getTagName(); + String binUrlStr = null; + + // first we check to see if body tag has a + // background set + if (tag instanceof BodyTag) { + binUrlStr = tag.getAttribute(ATT_BACKGROUND); + } else if (tag instanceof BaseHrefTag) { + BaseHrefTag baseHref = (BaseHrefTag) tag; + String baseref = baseHref.getBaseUrl(); + try { + if (!baseref.equals(""))// Bugzilla 30713 + { + baseUrl.url = ConversionUtils.makeRelativeURL(baseUrl.url, baseHref.getBaseUrl()); + } + } catch (MalformedURLException e1) { + throw new HTMLParseException(e1); + } + } else if (tag instanceof ImageTag) { + ImageTag image = (ImageTag) tag; + binUrlStr = image.getImageURL(); + } else if (tag instanceof AppletTag) { + // look for applets + + // This will only work with an Applet .class file. + // Ideally, this should be upgraded to work with Objects (IE) + // and archives (.jar and .zip) files as well. + AppletTag applet = (AppletTag) tag; + binUrlStr = applet.getAppletClass(); + } else if (tag instanceof InputTag) { + // we check the input tag type for image + if (ATT_IS_IMAGE.equalsIgnoreCase(tag.getAttribute(ATT_TYPE))) { + // then we need to download the binary + binUrlStr = tag.getAttribute(ATT_SRC); + } + } else if (tag instanceof ScriptTag) { + binUrlStr = tag.getAttribute(ATT_SRC); + // Bug 51750 + } else if (tag instanceof FrameTag || tagname.equalsIgnoreCase(TAG_IFRAME)) { + binUrlStr = tag.getAttribute(ATT_SRC); + } else if (tagname.equalsIgnoreCase(TAG_EMBED) + || tagname.equalsIgnoreCase(TAG_BGSOUND)){ + binUrlStr = tag.getAttribute(ATT_SRC); + } else if (tagname.equalsIgnoreCase(TAG_LINK)) { + // Putting the string first means it works even if the attribute is null + if (STYLESHEET.equalsIgnoreCase(tag.getAttribute(ATT_REL))) { + binUrlStr = tag.getAttribute(ATT_HREF); + } + } else { + binUrlStr = tag.getAttribute(ATT_BACKGROUND); + } + + if (binUrlStr != null) { + urls.addURL(binUrlStr, baseUrl.url); + } + + // Now look for URLs in the STYLE attribute + String styleTagStr = tag.getAttribute(ATT_STYLE); + if(styleTagStr != null) { + HtmlParsingUtils.extractStyleURLs(baseUrl.url, urls, styleTagStr); + } + + // second, if the tag was a composite tag, + // recursively parse its children. + if (tag instanceof CompositeTag) { + CompositeTag composite = (CompositeTag) tag; + parseNodes(composite.elements(), baseUrl, urls); + } + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HtmlParsingUtils.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HtmlParsingUtils.java new file mode 100644 index 0000000..92b9cb7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/HtmlParsingUtils.java @@ -0,0 +1,393 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.parser; + +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.PatternCacheLRU; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.tidy.Tidy; + +// For Junit tests @see TestHtmlParsingUtils + +public final class HtmlParsingUtils { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Private constructor to prevent instantiation. + */ + private HtmlParsingUtils() { + } + + /** + * Check if anchor matches by checking against: + * - protocol + * - domain + * - path + * - parameter names + * + * @param newLink target to match + * @param config pattern to match against + * + * @return true if target URL matches pattern URL + */ + public static boolean isAnchorMatched(HTTPSamplerBase newLink, HTTPSamplerBase config) + { + String query = null; + try { + query = URLDecoder.decode(newLink.getQueryString(), "UTF-8"); // $NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + // UTF-8 unsupported? You must be joking! + log.error("UTF-8 encoding not supported!"); + throw new Error("Should not happen: " + e.toString()); + } + + final Arguments arguments = config.getArguments(); + + final Perl5Matcher matcher = JMeterUtils.getMatcher(); + final PatternCacheLRU patternCache = JMeterUtils.getPatternCache(); + + if (!isEqualOrMatches(newLink.getProtocol(), config.getProtocol(), matcher, patternCache)){ + return false; + } + + final String domain = config.getDomain(); + if (domain != null && domain.length() > 0) { + if (!isEqualOrMatches(newLink.getDomain(), domain, matcher, patternCache)){ + return false; + } + } + + final String path = config.getPath(); + if (!newLink.getPath().equals(path) + && !matcher.matches(newLink.getPath(), patternCache.getPattern("[/]*" + path, // $NON-NLS-1$ + Perl5Compiler.READ_ONLY_MASK))) { + return false; + } + + PropertyIterator iter = arguments.iterator(); + while (iter.hasNext()) { + Argument item = (Argument) iter.next().getObjectValue(); + final String name = item.getName(); + if (query.indexOf(name + "=") == -1) { // $NON-NLS-1$ + if (!(matcher.contains(query, patternCache.getPattern(name, Perl5Compiler.READ_ONLY_MASK)))) { + return false; + } + } + } + + return true; + } + + /** + * Arguments match if the input name matches the corresponding pattern name + * and the input value matches the pattern value, where the matching is done + * first using String equals, and then Regular Expression matching if the equals test fails. + * + * @param arg - input Argument + * @param patternArg - pattern to match against + * @return true if both name and value match + */ + public static boolean isArgumentMatched(Argument arg, Argument patternArg) { + final Perl5Matcher matcher = JMeterUtils.getMatcher(); + final PatternCacheLRU patternCache = JMeterUtils.getPatternCache(); + return + isEqualOrMatches(arg.getName(), patternArg.getName(), matcher, patternCache) + && + isEqualOrMatches(arg.getValue(), patternArg.getValue(), matcher, patternCache); + } + + /** + * Match the input argument against the pattern using String.equals() or pattern matching if that fails. + * + * @param arg input string + * @param pat pattern string + * @param matcher Perl5Matcher + * @param cache PatternCache + * + * @return true if input matches the pattern + */ + public static boolean isEqualOrMatches(String arg, String pat, Perl5Matcher matcher, PatternCacheLRU cache){ + return + arg.equals(pat) + || + matcher.matches(arg,cache.getPattern(pat,Perl5Compiler.READ_ONLY_MASK)); + } + + /** + * Match the input argument against the pattern using String.equals() or pattern matching if that fails + * using case-insenssitive matching. + * + * @param arg input string + * @param pat pattern string + * @param matcher Perl5Matcher + * @param cache PatternCache + * + * @return true if input matches the pattern + */ + public static boolean isEqualOrMatchesCaseBlind(String arg, String pat, Perl5Matcher matcher, PatternCacheLRU cache){ + return + arg.equalsIgnoreCase(pat) + || + matcher.matches(arg,cache.getPattern(pat,Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK)); + } + + /** + * Match the input argument against the pattern using String.equals() or pattern matching if that fails + * using case-insensitive matching. + * + * @param arg input string + * @param pat pattern string + * + * @return true if input matches the pattern + */ + public static boolean isEqualOrMatches(String arg, String pat){ + return isEqualOrMatches(arg, pat, JMeterUtils.getMatcher(), JMeterUtils.getPatternCache()); + } + + /** + * Match the input argument against the pattern using String.equals() or pattern matching if that fails + * using case-insensitive matching. + * + * @param arg input string + * @param pat pattern string + * + * @return true if input matches the pattern + */ + public static boolean isEqualOrMatchesCaseBlind(String arg, String pat){ + return isEqualOrMatchesCaseBlind(arg, pat, JMeterUtils.getMatcher(), JMeterUtils.getPatternCache()); + } + + /** + * Returns tidy as HTML parser. + * + * @return a tidy HTML parser + */ + public static Tidy getParser() { + log.debug("Start : getParser1"); + Tidy tidy = new Tidy(); + tidy.setInputEncoding("UTF8"); + tidy.setOutputEncoding("UTF8"); + tidy.setQuiet(true); + tidy.setShowWarnings(false); + + if (log.isDebugEnabled()) { + log.debug("getParser1 : tidy parser created - " + tidy); + } + + log.debug("End : getParser1"); + + return tidy; + } + + /** + * Returns a node representing a whole xml given an xml document. + * + * @param text + * an xml document + * @return a node representing a whole xml + */ + public static Node getDOM(String text) { + log.debug("Start : getDOM1"); + + try { + Node node = getParser().parseDOM(new ByteArrayInputStream(text.getBytes("UTF-8")), null);// $NON-NLS-1$ + + if (log.isDebugEnabled()) { + log.debug("node : " + node); + } + + log.debug("End : getDOM1"); + + return node; + } catch (UnsupportedEncodingException e) { + log.error("getDOM1 : Unsupported encoding exception - " + e); + log.debug("End : getDOM1"); + throw new RuntimeException("UTF-8 encoding failed"); + } + } + + public static Document createEmptyDoc() { + return Tidy.createEmptyDocument(); + } + + /** + * Create a new Sampler based on an HREF string plus a contextual URL + * object. Given that an HREF string might be of three possible forms, some + * processing is required. + */ + public static HTTPSamplerBase createUrlFromAnchor(String parsedUrlString, URL context) throws MalformedURLException { + if (log.isDebugEnabled()) { + log.debug("Creating URL from Anchor: " + parsedUrlString + ", base: " + context); + } + URL url = ConversionUtils.makeRelativeURL(context, parsedUrlString); + HTTPSamplerBase sampler =HTTPSamplerFactory.newInstance(); + sampler.setDomain(url.getHost()); + sampler.setProtocol(url.getProtocol()); + sampler.setPort(url.getPort()); + sampler.setPath(url.getPath()); + sampler.parseArguments(url.getQuery()); + + return sampler; + } + + public static List createURLFromForm(Node doc, URL context) { + String selectName = null; + LinkedList urlConfigs = new LinkedList(); + recurseForm(doc, urlConfigs, context, selectName, false); + /* + * NamedNodeMap atts = formNode.getAttributes(); + * if(atts.getNamedItem("action") == null) { throw new + * MalformedURLException(); } String action = + * atts.getNamedItem("action").getNodeValue(); UrlConfig url = + * createUrlFromAnchor(action, context); recurseForm(doc, url, + * selectName,true,formStart); + */ + return urlConfigs; + } + + // N.B. Since the tags are extracted from an HTML Form, any values must already have been encoded + private static boolean recurseForm(Node tempNode, LinkedList urlConfigs, URL context, String selectName, + boolean inForm) { + NamedNodeMap nodeAtts = tempNode.getAttributes(); + String tag = tempNode.getNodeName(); + try { + if (inForm) { + HTTPSamplerBase url = urlConfigs.getLast(); + if (tag.equalsIgnoreCase("form")) { // $NON-NLS-1$ + try { + urlConfigs.add(createFormUrlConfig(tempNode, context)); + } catch (MalformedURLException e) { + inForm = false; + } + } else if (tag.equalsIgnoreCase("input")) { // $NON-NLS-1$ + url.addEncodedArgument(getAttributeValue(nodeAtts, "name"), // $NON-NLS-1$ + getAttributeValue(nodeAtts, "value")); // $NON-NLS-1$ + } else if (tag.equalsIgnoreCase("textarea")) { // $NON-NLS-1$ + try { + url.addEncodedArgument(getAttributeValue(nodeAtts, "name"), // $NON-NLS-1$ + tempNode.getFirstChild().getNodeValue()); + } catch (NullPointerException e) { + url.addArgument(getAttributeValue(nodeAtts, "name"), ""); // $NON-NLS-1$ + } + } else if (tag.equalsIgnoreCase("select")) { // $NON-NLS-1$ + selectName = getAttributeValue(nodeAtts, "name"); // $NON-NLS-1$ + } else if (tag.equalsIgnoreCase("option")) { // $NON-NLS-1$ + String value = getAttributeValue(nodeAtts, "value"); // $NON-NLS-1$ + if (value == null) { + try { + value = tempNode.getFirstChild().getNodeValue(); + } catch (NullPointerException e) { + value = ""; // $NON-NLS-1$ + } + } + url.addEncodedArgument(selectName, value); + } + } else if (tag.equalsIgnoreCase("form")) { // $NON-NLS-1$ + try { + urlConfigs.add(createFormUrlConfig(tempNode, context)); + inForm = true; + } catch (MalformedURLException e) { + inForm = false; + } + } + } catch (Exception ex) { + log.warn("Some bad HTML " + printNode(tempNode), ex); + } + NodeList childNodes = tempNode.getChildNodes(); + for (int x = 0; x < childNodes.getLength(); x++) { + inForm = recurseForm(childNodes.item(x), urlConfigs, context, selectName, inForm); + } + return inForm; + } + + private static String getAttributeValue(NamedNodeMap att, String attName) { + try { + return att.getNamedItem(attName).getNodeValue(); + } catch (Exception ex) { + return ""; // $NON-NLS-1$ + } + } + + private static String printNode(Node node) { + StringBuilder buf = new StringBuilder(); + buf.append("<"); // $NON-NLS-1$ + buf.append(node.getNodeName()); + NamedNodeMap atts = node.getAttributes(); + for (int x = 0; x < atts.getLength(); x++) { + buf.append(" "); // $NON-NLS-1$ + buf.append(atts.item(x).getNodeName()); + buf.append("=\""); // $NON-NLS-1$ + buf.append(atts.item(x).getNodeValue()); + buf.append("\""); // $NON-NLS-1$ + } + + buf.append(">"); // $NON-NLS-1$ + + return buf.toString(); + } + + private static HTTPSamplerBase createFormUrlConfig(Node tempNode, URL context) throws MalformedURLException { + NamedNodeMap atts = tempNode.getAttributes(); + if (atts.getNamedItem("action") == null) { // $NON-NLS-1$ + throw new MalformedURLException(); + } + String action = atts.getNamedItem("action").getNodeValue(); // $NON-NLS-1$ + HTTPSamplerBase url = createUrlFromAnchor(action, context); + return url; + } + + public static void extractStyleURLs(final URL baseUrl, final URLCollection urls, String styleTagStr) { + Perl5Matcher matcher = JMeterUtils.getMatcher(); + Pattern pattern = JMeterUtils.getPatternCache().getPattern( + "URL\\(\\s*('|\")(.*)('|\")\\s*\\)", // $NON-NLS-1$ + Perl5Compiler.CASE_INSENSITIVE_MASK | Perl5Compiler.SINGLELINE_MASK | Perl5Compiler.READ_ONLY_MASK); + PatternMatcherInput input = null; + input = new PatternMatcherInput(styleTagStr); + while (matcher.contains(input, pattern)) { + MatchResult match = matcher.getMatch(); + // The value is in the second group + String styleUrl = match.group(2); + urls.addURL(styleUrl, baseUrl); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/JTidyHTMLParser.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/JTidyHTMLParser.java new file mode 100644 index 0000000..7195d55 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/JTidyHTMLParser.java @@ -0,0 +1,233 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.parser; + +import java.io.ByteArrayInputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; + +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.tidy.Tidy; +import org.xml.sax.SAXException; + +/** + * HtmlParser implementation using JTidy. + * + */ +class JTidyHTMLParser extends HTMLParser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected JTidyHTMLParser() { + super(); + } + + @Override + protected boolean isReusable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator getEmbeddedResourceURLs(byte[] html, URL baseUrl, URLCollection urls, String encoding) throws HTMLParseException { + Document dom = null; + try { + dom = (Document) getDOM(html, encoding); + } catch (SAXException se) { + throw new HTMLParseException(se); + } + + // Now parse the DOM tree + + scanNodes(dom, urls, baseUrl); + + return urls.iterator(); + } + + /** + * Scan nodes recursively, looking for embedded resources + * + * @param node - + * initial node + * @param urls - + * container for URLs + * @param baseUrl - + * used to create absolute URLs + * + * @return new base URL + */ + private URL scanNodes(Node node, URLCollection urls, URL baseUrl) throws HTMLParseException { + if (node == null) { + return baseUrl; + } + + String name = node.getNodeName(); + + int type = node.getNodeType(); + + switch (type) { + + case Node.DOCUMENT_NODE: + scanNodes(((Document) node).getDocumentElement(), urls, baseUrl); + break; + + case Node.ELEMENT_NODE: + + NamedNodeMap attrs = node.getAttributes(); + if (name.equalsIgnoreCase(TAG_BASE)) { + String tmp = getValue(attrs, ATT_HREF); + if (tmp != null) { + try { + baseUrl = ConversionUtils.makeRelativeURL(baseUrl, tmp); + } catch (MalformedURLException e) { + throw new HTMLParseException(e); + } + } + break; + } + + if (name.equalsIgnoreCase(TAG_IMAGE) || name.equalsIgnoreCase(TAG_EMBED)) { + urls.addURL(getValue(attrs, ATT_SRC), baseUrl); + break; + } + + if (name.equalsIgnoreCase(TAG_APPLET)) { + urls.addURL(getValue(attrs, "code"), baseUrl); + break; + } + if (name.equalsIgnoreCase(TAG_INPUT)) { + String src = getValue(attrs, ATT_SRC); + String typ = getValue(attrs, ATT_TYPE); + if ((src != null) && (typ.equalsIgnoreCase(ATT_IS_IMAGE))) { + urls.addURL(src, baseUrl); + } + break; + } + if (name.equalsIgnoreCase(TAG_LINK) && getValue(attrs, ATT_REL).equalsIgnoreCase(STYLESHEET)) { + urls.addURL(getValue(attrs, ATT_HREF), baseUrl); + break; + } + if (name.equalsIgnoreCase(TAG_SCRIPT)) { + urls.addURL(getValue(attrs, ATT_SRC), baseUrl); + break; + } + if (name.equalsIgnoreCase(TAG_FRAME)) { + urls.addURL(getValue(attrs, ATT_SRC), baseUrl); + break; + } + if (name.equalsIgnoreCase(TAG_IFRAME)) { + urls.addURL(getValue(attrs, ATT_SRC), baseUrl); + break; + } + String back = getValue(attrs, ATT_BACKGROUND); + if (back != null) { + urls.addURL(back, baseUrl); + } + if (name.equalsIgnoreCase(TAG_BGSOUND)) { + urls.addURL(getValue(attrs, ATT_SRC), baseUrl); + break; + } + + String style = getValue(attrs, ATT_STYLE); + if (style != null) { + HtmlParsingUtils.extractStyleURLs(baseUrl, urls, style); + } + + NodeList children = node.getChildNodes(); + if (children != null) { + int len = children.getLength(); + for (int i = 0; i < len; i++) { + baseUrl = scanNodes(children.item(i), urls, baseUrl); + } + } + + break; + + // case Node.TEXT_NODE: + // break; + + default: + // ignored + break; + } + + return baseUrl; + + } + + /* + * Helper method to get an attribute value, if it exists @param attrs list + * of attributs @param attname attribute name @return + */ + private String getValue(NamedNodeMap attrs, String attname) { + String v = null; + Node n = attrs.getNamedItem(attname); + if (n != null) { + v = n.getNodeValue(); + } + return v; + } + + /** + * Returns tidy as HTML parser. + * + * @return a tidy HTML parser + */ + private static Tidy getTidyParser(String encoding) { + log.debug("Start : getParser"); + Tidy tidy = new Tidy(); + tidy.setInputEncoding(encoding); + tidy.setOutputEncoding("UTF8"); + tidy.setQuiet(true); + tidy.setShowWarnings(false); + if (log.isDebugEnabled()) { + log.debug("getParser : tidy parser created - " + tidy); + } + log.debug("End : getParser"); + return tidy; + } + + /** + * Returns a node representing a whole xml given an xml document. + * + * @param text + * an xml document (as a byte array) + * @return a node representing a whole xml + * + * @throws SAXException + * indicates an error parsing the xml document + */ + private static Node getDOM(byte[] text, String encoding) throws SAXException { + log.debug("Start : getDOM"); + Node node = getTidyParser(encoding).parseDOM(new ByteArrayInputStream(text), null); + if (log.isDebugEnabled()) { + log.debug("node : " + node); + } + log.debug("End : getDOM"); + return node; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/RegexpHTMLParser.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/RegexpHTMLParser.java new file mode 100644 index 0000000..47f805b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/RegexpHTMLParser.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.parser; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; + +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +// NOTE: Also looked at using Java 1.4 regexp instead of ORO. The change was +// trivial. Performance did not improve -- at least not significantly. +// Finally decided for ORO following advise from Stefan Bodewig (message +// to jmeter-dev dated 25 Nov 2003 8:52 CET) [Jordi] +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * HtmlParser implementation using regular expressions. + *

+ * This class will find RLs specified in the following ways (where url + * represents the RL being found: + *

+ * + *

+ * This class will take into account the following construct: + *

    + *
  • <base href=url> + *
+ * + *

+ * But not the following: + *

    + *
  • < ... codebase=url ... > + *
+ * + */ +class RegexpHTMLParser extends HTMLParser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Regexp fragment matching a tag attribute's value (including the equals + * sign and any spaces before it). Note it matches unquoted values, which to + * my understanding, are not conformant to any of the HTML specifications, + * but are still quite common in the web and all browsers seem to understand + * them. + */ + private static final String VALUE = "\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\"'\\s>\\\\][^\\s>]*)(?=[\\s>]))"; + + // Note there's 3 capturing groups per value + + /** + * Regexp fragment matching the separation between two tag attributes. + */ + private static final String SEP = "\\s(?:[^>]*\\s)?"; + + /** + * Regular expression used against the HTML code to find the URIs of images, + * etc.: + */ + private static final String REGEXP = + "<(?:" + "!--.*?-->" + + "|BASE" + SEP + "HREF" + VALUE + + "|(?:IMG|SCRIPT|FRAME|IFRAME|BGSOUND)" + SEP + "SRC" + VALUE + + "|APPLET" + SEP + "CODE(?:BASE)?" + VALUE + + "|(?:EMBED|OBJECT)" + SEP + "(?:SRC|CODEBASE)" + VALUE + + "|(?:BODY|TABLE|TR|TD)" + SEP + "BACKGROUND" + VALUE + + "|[^<]+?STYLE\\s*=['\"].*?URL\\(\\s*['\"](.+?)['\"]\\s*\\)" + + "|INPUT(?:" + SEP + "(?:SRC" + VALUE + + "|TYPE\\s*=\\s*(?:\"image\"|'image'|image(?=[\\s>])))){2,}" + + "|LINK(?:" + SEP + "(?:HREF" + VALUE + + "|REL\\s*=\\s*(?:\"stylesheet\"|'stylesheet'|stylesheet(?=[\\s>])))){2,}" + ")"; + + // Number of capturing groups possibly containing Base HREFs: + private static final int NUM_BASE_GROUPS = 3; + + /** + * Thread-local input: + */ + private static final ThreadLocal localInput = + new ThreadLocal() { + @Override + protected PatternMatcherInput initialValue() { + return new PatternMatcherInput(new char[0]); + } + }; + + /** + * {@inheritDoc} + */ + @Override + protected boolean isReusable() { + return true; + } + + /** + * Make sure to compile the regular expression upon instantiation: + */ + protected RegexpHTMLParser() { + super(); + } + + /** + * {@inheritDoc} + * @throws HTMLParseException + */ + @Override + public Iterator getEmbeddedResourceURLs(byte[] html, URL baseUrl, URLCollection urls, String encoding) throws HTMLParseException { + + try { + Perl5Matcher matcher = JMeterUtils.getMatcher(); + PatternMatcherInput input = localInput.get(); + // TODO: find a way to avoid the cost of creating a String here -- + // probably a new PatternMatcherInput working on a byte[] would do + // better. + input.setInput(new String(html, encoding)); + Pattern pattern=JMeterUtils.getPatternCache().getPattern( + REGEXP, + Perl5Compiler.CASE_INSENSITIVE_MASK + | Perl5Compiler.SINGLELINE_MASK + | Perl5Compiler.READ_ONLY_MASK); + + while (matcher.contains(input, pattern)) { + MatchResult match = matcher.getMatch(); + String s; + if (log.isDebugEnabled()) { + log.debug("match groups " + match.groups() + " " + match.toString()); + } + // Check for a BASE HREF: + for (int g = 1; g <= NUM_BASE_GROUPS && g <= match.groups(); g++) { + s = match.group(g); + if (s != null) { + if (log.isDebugEnabled()) { + log.debug("new baseUrl: " + s + " - " + baseUrl.toString()); + } + try { + baseUrl = ConversionUtils.makeRelativeURL(baseUrl, s); + } catch (MalformedURLException e) { + // Doesn't even look like a URL? + // Maybe it isn't: Ignore the exception. + if (log.isDebugEnabled()) { + log.debug("Can't build base URL from RL " + s + " in page " + baseUrl, e); + } + } + } + } + for (int g = NUM_BASE_GROUPS + 1; g <= match.groups(); g++) { + s = match.group(g); + if (s != null) { + if (log.isDebugEnabled()) { + log.debug("group " + g + " - " + match.group(g)); + } + urls.addURL(s, baseUrl); + } + } + } + return urls.iterator(); + } catch (UnsupportedEncodingException e) { + throw new HTMLParseException(e.getMessage(), e); + } catch (MalformedCachePatternException e) { + throw new HTMLParseException(e.getMessage(), e); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/URLCollection.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/URLCollection.java new file mode 100644 index 0000000..187fb55 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/URLCollection.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.parser; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collection; +import java.util.Iterator; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.jmeter.protocol.http.util.ConversionUtils; + +/** + * Collection class designed for handling URLs + * + * Before a URL is added to the collection, it is wrapped in a URLString class. + * The iterator unwraps the URL before return. + * + * N.B. Designed for use by HTMLParser, so is not a full implementation - e.g. + * does not support remove() + * + */ +public class URLCollection { + private final Collection coll; + + /** + * Creates a new URLCollection from an existing Collection + * + */ + public URLCollection(Collection c) { + coll = c; + } + + /** + * Adds the URL to the Collection, first wrapping it in the URLString class + * + * @param u + * URL to add + * @return boolean condition returned by the add() method of the underlying + * collection + */ + public boolean add(URL u) { + return coll.add(new URLString(u)); + } + + /* + * Adds the string to the Collection, first wrapping it in the URLString + * class + * + * @param s string to add @return boolean condition returned by the add() + * method of the underlying collection + */ + private boolean add(String s) { + return coll.add(new URLString(s)); + } + + /** + * Convenience method for adding URLs to the collection If the url parameter + * is null or empty, nothing is done + * + * @param url + * String, may be null or empty + * @param baseUrl + * @return boolean condition returned by the add() method of the underlying + * collection + */ + public boolean addURL(String url, URL baseUrl) { + if (url == null || url.length() == 0) { + return false; + } + //url.replace('+',' '); + url=StringEscapeUtils.unescapeXml(url); + boolean b = false; + try { + b = this.add(ConversionUtils.makeRelativeURL(baseUrl, url)); + } catch (MalformedURLException mfue) { + // TODO log a warning message? + b = this.add(url);// Add the string if cannot create the URL + } + return b; + } + + public Iterator iterator() { + return new UrlIterator(coll.iterator()); + } + + /* + * Private iterator used to unwrap the URL from the URLString class + * + */ + private static class UrlIterator implements Iterator { + private final Iterator iter; + + UrlIterator(Iterator i) { + iter = i; + } + + public boolean hasNext() { + return iter.hasNext(); + } + + /* + * Unwraps the URLString class to return the URL + */ + public URL next() { + return iter.next().getURL(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/URLString.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/URLString.java new file mode 100644 index 0000000..d5d431b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/parser/URLString.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.parser; + +import java.net.URL; + +/** + * Helper class to allow URLs to be stored in Collections without incurring the + * cost of the hostname lookup performed by the URL methods equals() and + * hashCode() URL is a final class, so cannot be extended ... + * + * @version $Revision: 804543 $ + */ +public class URLString implements Comparable { + + private final URL url; + + private final String urlAsString; + + private final int hashCode; + + public URLString(URL u) { + url = u; + urlAsString = u.toExternalForm(); + /* + * TODO improve string version to better match browser behaviour? e.g. + * do browsers regard http://host/ and http://Host:80/ as the same? If + * so, it would be better to reflect this in the string + */ + + hashCode = urlAsString.hashCode(); + } + + /* + * Parsers can return the URL as a string if it does not parse properly + */ + public URLString(String s) { + url = null; + urlAsString = s; + hashCode = urlAsString.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return urlAsString; + } + + public URL getURL() { + return url; + } + + /** {@inheritDoc} */ + public int compareTo(URLString o) { + return urlAsString.compareTo(o.toString()); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + return (o instanceof URLString && urlAsString.equals(o.toString())); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return hashCode; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/AbstractSamplerCreator.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/AbstractSamplerCreator.java new file mode 100644 index 0000000..f4296a0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/AbstractSamplerCreator.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy; + +import java.util.HashSet; +import java.util.Set; +import java.util.StringTokenizer; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Base class for SamplerCreator + */ +public abstract class AbstractSamplerCreator implements SamplerCreator { + + protected static final String HTTP = "http"; // $NON-NLS-1$ + protected static final String HTTPS = "https"; // $NON-NLS-1$ + + /** Filetype to be used for the temporary binary files*/ + private static final String binaryFileSuffix = + JMeterUtils.getPropDefault("proxy.binary.filesuffix",// $NON-NLS-1$ + ".binary"); // $NON-NLS-1$ + + /** Which content-types will be treated as binary (exact match) */ + private static final Set binaryContentTypes = new HashSet(); + + /** Where to store the temporary binary files */ + private static final String binaryDirectory = + JMeterUtils.getPropDefault("proxy.binary.directory",// $NON-NLS-1$ + System.getProperty("user.dir")); // $NON-NLS-1$ proxy.binary.filetype=binary + + static { + String binaries = JMeterUtils.getPropDefault("proxy.binary.types", // $NON-NLS-1$ + "application/x-amf,application/x-java-serialized-object"); // $NON-NLS-1$ + if (binaries.length() > 0){ + StringTokenizer s = new StringTokenizer(binaries,"|, ");// $NON-NLS-1$ + while (s.hasMoreTokens()){ + binaryContentTypes.add(s.nextToken()); + } + } + } + + /* + * Optionally number the requests + */ + private static final boolean numberRequests = + JMeterUtils.getPropDefault("proxy.number.requests", false); // $NON-NLS-1$ + + private static volatile int requestNumber = 0;// running number + + + /** + * + */ + /** + * + */ + public AbstractSamplerCreator() { + super(); + } + + /** + * @return int request number + */ + protected static int getRequestNumber() { + return requestNumber; + } + + /** + * Increment request number + */ + protected static void incrementRequestNumber() { + requestNumber++; + } + + /** + * @return boolean is numbering requests is required + */ + protected static boolean isNumberRequests() { + return numberRequests; + } + + /** + * @param contentType String content type + * @return true if contentType is part of binary declared types + */ + protected boolean isBinaryContent(String contentType) { + if (contentType == null) { + return false; + } + return binaryContentTypes.contains(contentType); + } + + /** + * @return String binary file suffix + */ + protected String getBinaryFileSuffix() { + return binaryFileSuffix; + } + + /** + * @return String binary directory + */ + protected String getBinaryDirectory() { + return binaryDirectory; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/Daemon.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/Daemon.java new file mode 100644 index 0000000..0c2373e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/Daemon.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.gui.Stoppable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Web daemon thread. Creates main socket on port 8080 and listens on it + * forever. For each client request, creates a proxy thread to handle the + * request. + * + */ +public class Daemon extends Thread implements Stoppable { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * The time (in milliseconds) to wait when accepting a client connection. + * The accept will be retried until the Daemon is told to stop. So this + * interval is the longest time that the Daemon will have to wait after + * being told to stop. + */ + private static final int ACCEPT_TIMEOUT = 1000; + + /** The port to listen on. */ + private final int daemonPort; + + private final ServerSocket mainSocket; + + /** True if the Daemon is currently running. */ + private volatile boolean running; + + /** The target which will receive the generated JMeter test components. */ + private final ProxyControl target; + + /** + * The proxy class which will be used to handle individual requests. This + * class must be the {@link Proxy} class or a subclass. + */ + private final Class proxyClass; + + /** + * Create a new Daemon with the specified port and target. + * + * @param port + * the port to listen on. + * @param target + * the target which will receive the generated JMeter test + * components. + * @throws IOException + */ + public Daemon(int port, ProxyControl target) throws IOException { + this(port, target, Proxy.class); + } + + /** + * Create a new Daemon with the specified port and target, using the + * specified class to handle individual requests. + * + * @param port + * the port to listen on. + * @param target + * the target which will receive the generated JMeter test + * components. + * @param proxyClass + * the proxy class to use to handle individual requests. This + * class must be the {@link Proxy} class or a subclass. + * @throws IOException + */ + public Daemon(int port, ProxyControl target, Class proxyClass) throws IOException { + super("HTTP Proxy Daemon"); + this.target = target; + this.daemonPort = port; + this.proxyClass = proxyClass; + log.info("Creating Daemon Socket on port: " + daemonPort); + mainSocket = new ServerSocket(daemonPort); + mainSocket.setSoTimeout(ACCEPT_TIMEOUT); + } + + /** + * Listen on the daemon port and handle incoming requests. This method will + * not exit until {@link #stopServer()} is called or an error occurs. + */ + @Override + public void run() { + running = true; + log.info("Proxy up and running!"); + + // Maps to contain page and form encodings + // TODO - do these really need to be shared between all Proxy instances? + Map pageEncodings = Collections.synchronizedMap(new HashMap()); + Map formEncodings = Collections.synchronizedMap(new HashMap()); + + try { + while (running) { + try { + // Listen on main socket + Socket clientSocket = mainSocket.accept(); + if (running) { + // Pass request to new proxy thread + Proxy thd = proxyClass.newInstance(); + thd.configure(clientSocket, target, pageEncodings, formEncodings); + thd.start(); + } + } catch (InterruptedIOException e) { + continue; + // Timeout occurred. Ignore, and keep looping until we're + // told to stop running. + } + } + log.info("Proxy Server stopped"); + } catch (Exception e) { + log.warn("Proxy Server stopped", e); + } finally { + JOrphanUtils.closeQuietly(mainSocket); + } + + // Clear maps + pageEncodings = null; + formEncodings = null; + } + + /** + * Stop the proxy daemon. The daemon may not stop immediately. + * + * see #ACCEPT_TIMEOUT + */ + public void stopServer() { + running = false; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java new file mode 100644 index 0000000..477b0ad --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/DefaultSamplerCreator.java @@ -0,0 +1,348 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.protocol.http.config.MultipartUrlConfig; +import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; +import org.apache.jmeter.protocol.http.sampler.PostWriter; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Default implementation that handles classical HTTP textual + Multipart requests + */ +public class DefaultSamplerCreator extends AbstractSamplerCreator { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * + */ + public DefaultSamplerCreator() { + } + + /** + * @see org.apache.jmeter.protocol.http.proxy.SamplerCreator#getManagedContentTypes() + */ + public String[] getManagedContentTypes() { + return new String[0]; + } + + /** + * + * @see org.apache.jmeter.protocol.http.proxy.SamplerCreator#createSampler(org.apache.jmeter.protocol.http.proxy.HttpRequestHdr, java.util.Map, java.util.Map) + */ + public HTTPSamplerBase createSampler(HttpRequestHdr request, + Map pageEncodings, Map formEncodings) { + // Instantiate the sampler + HTTPSamplerBase sampler = HTTPSamplerFactory.newInstance(request.getHttpSamplerName()); + + sampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName()); + + // Defaults + sampler.setFollowRedirects(false); + sampler.setUseKeepAlive(true); + + if (log.isDebugEnabled()) { + log.debug("getSampler: sampler path = " + sampler.getPath()); + } + return sampler; + } + + /** + * @see org.apache.jmeter.protocol.http.proxy.SamplerCreator#populateSampler(org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase, org.apache.jmeter.protocol.http.proxy.HttpRequestHdr, java.util.Map, java.util.Map) + */ + public final void populateSampler(HTTPSamplerBase sampler, + HttpRequestHdr request, Map pageEncodings, + Map formEncodings) throws Exception{ + computeFromHeader(sampler, request, pageEncodings, formEncodings); + + computeFromPostBody(sampler, request); + if (log.isDebugEnabled()) { + log.debug("sampler path = " + sampler.getPath()); + } + } + + /** + * Compute sampler informations from Request Header + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + * @param pageEncodings Map + * @param formEncodings Map + * @throws Exception + */ + protected void computeFromHeader(HTTPSamplerBase sampler, + HttpRequestHdr request, Map pageEncodings, + Map formEncodings) throws Exception { + computeDomain(sampler, request); + + computeMethod(sampler, request); + + computePort(sampler, request); + + computeProtocol(sampler, request); + + computeContentEncoding(sampler, request, + pageEncodings, formEncodings); + + computePath(sampler, request); + + computeSamplerName(sampler, request); + } + + /** + * Compute sampler informations from Request Header + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + * @throws Exception + */ + protected void computeFromPostBody(HTTPSamplerBase sampler, + HttpRequestHdr request) throws Exception { + // If it was a HTTP GET request, then all parameters in the URL + // has been handled by the sampler.setPath above, so we just need + // to do parse the rest of the request if it is not a GET request + if((!HTTPConstants.CONNECT.equals(request.getMethod())) && (!HTTPConstants.GET.equals(request.getMethod()))) { + // Check if it was a multipart http post request + final String contentType = request.getContentType(); + MultipartUrlConfig urlConfig = request.getMultipartConfig(contentType); + String contentEncoding = sampler.getContentEncoding(); + // Get the post data using the content encoding of the request + String postData = null; + if (log.isDebugEnabled()) { + if(!StringUtils.isEmpty(contentEncoding)) { + log.debug("Using encoding " + contentEncoding + " for request body"); + } + else { + log.debug("No encoding found, using JRE default encoding for request body"); + } + } + + + if (!StringUtils.isEmpty(contentEncoding)) { + postData = new String(request.getRawPostData(), contentEncoding); + } else { + // Use default encoding + postData = new String(request.getRawPostData(), PostWriter.ENCODING); + } + + if (urlConfig != null) { + urlConfig.parseArguments(postData); + // Tell the sampler to do a multipart post + sampler.setDoMultipartPost(true); + // Remove the header for content-type and content-length, since + // those values will most likely be incorrect when the sampler + // performs the multipart request, because the boundary string + // will change + request.getHeaderManager().removeHeaderNamed(HttpRequestHdr.CONTENT_TYPE); + request.getHeaderManager().removeHeaderNamed(HttpRequestHdr.CONTENT_LENGTH); + + // Set the form data + sampler.setArguments(urlConfig.getArguments()); + // Set the file uploads + sampler.setHTTPFiles(urlConfig.getHTTPFileArgs().asArray()); + // used when postData is pure xml (eg. an xml-rpc call) or for PUT + } else if (postData.trim().startsWith(" 0) { + if (isBinaryContent(contentType)) { + try { + File tempDir = new File(getBinaryDirectory()); + File out = File.createTempFile(request.getMethod(), getBinaryFileSuffix(), tempDir); + FileUtils.writeByteArrayToFile(out,request.getRawPostData()); + HTTPFileArg [] files = {new HTTPFileArg(out.getPath(),"",contentType)}; + sampler.setHTTPFiles(files); + } catch (IOException e) { + log.warn("Could not create binary file: "+e); + } + } else { + // Just put the whole postbody as the value of a parameter + sampler.addNonEncodedArgument("", postData, ""); //used when postData is pure xml (ex. an xml-rpc call) + } + } + } + } + + /** + * Compute sampler name + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computeSamplerName(HTTPSamplerBase sampler, + HttpRequestHdr request) { + if (!HTTPConstants.CONNECT.equals(request.getMethod()) && isNumberRequests()) { + incrementRequestNumber(); + sampler.setName(getRequestNumber() + " " + sampler.getPath()); + } else { + sampler.setName(sampler.getPath()); + } + } + + /** + * Set path on sampler + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computePath(HTTPSamplerBase sampler, HttpRequestHdr request) { + if(sampler.getContentEncoding() != null) { + sampler.setPath(request.getPath(), sampler.getContentEncoding()); + } + else { + // Although the spec says UTF-8 should be used for encoding URL parameters, + // most browser use ISO-8859-1 for default if encoding is not known. + // We use null for contentEncoding, then the url parameters will be added + // with the value in the URL, and the "encode?" flag set to false + sampler.setPath(request.getPath(), null); + } + if (log.isDebugEnabled()) { + log.debug("Proxy: setting path: " + sampler.getPath()); + } + } + + /** + * Compute content encoding + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + * @param pageEncodings Map + * @param formEncodings Map + * @throws MalformedURLException + */ + protected void computeContentEncoding(HTTPSamplerBase sampler, + HttpRequestHdr request, Map pageEncodings, + Map formEncodings) throws MalformedURLException { + URL pageUrl = null; + if(sampler.isProtocolDefaultPort()) { + pageUrl = new URL(sampler.getProtocol(), sampler.getDomain(), request.getPath()); + } + else { + pageUrl = new URL(sampler.getProtocol(), sampler.getDomain(), + sampler.getPort(), request.getPath()); + } + String urlWithoutQuery = request.getUrlWithoutQuery(pageUrl); + + + String contentEncoding = computeContentEncoding(request, pageEncodings, + formEncodings, urlWithoutQuery); + + // Set the content encoding + if(!StringUtils.isEmpty(contentEncoding)) { + sampler.setContentEncoding(contentEncoding); + } + } + + /** + * Computes content encoding from request and if not found uses pageEncoding + * and formEncoding to see if URL was previously computed with a content type + * @param request {@link HttpRequestHdr} + * @param pageEncodings Map + * @param formEncodings Map + * @return String content encoding + */ + protected String computeContentEncoding(HttpRequestHdr request, + Map pageEncodings, + Map formEncodings, String urlWithoutQuery) { + // Check if the request itself tells us what the encoding is + String contentEncoding = null; + String requestContentEncoding = ConversionUtils.getEncodingFromContentType( + request.getContentType()); + if(requestContentEncoding != null) { + contentEncoding = requestContentEncoding; + } + else { + // Check if we know the encoding of the page + if (pageEncodings != null) { + synchronized (pageEncodings) { + contentEncoding = pageEncodings.get(urlWithoutQuery); + } + } + // Check if we know the encoding of the form + if (formEncodings != null) { + synchronized (formEncodings) { + String formEncoding = formEncodings.get(urlWithoutQuery); + // Form encoding has priority over page encoding + if (formEncoding != null) { + contentEncoding = formEncoding; + } + } + } + } + return contentEncoding; + } + + /** + * Set protocol on sampler + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computeProtocol(HTTPSamplerBase sampler, + HttpRequestHdr request) { + sampler.setProtocol(request.getProtocol(sampler)); + } + + /** + * Set Port on sampler + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computePort(HTTPSamplerBase sampler, HttpRequestHdr request) { + sampler.setPort(request.serverPort()); + if (log.isDebugEnabled()) { + log.debug("Proxy: setting port: " + sampler.getPort()); + } + } + + /** + * Set method on sampler + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computeMethod(HTTPSamplerBase sampler, HttpRequestHdr request) { + sampler.setMethod(request.getMethod()); + log.debug("Proxy: setting method: " + sampler.getMethod()); + } + + /** + * Set domain on sampler + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + */ + protected void computeDomain(HTTPSamplerBase sampler, HttpRequestHdr request) { + sampler.setDomain(request.serverName()); + if (log.isDebugEnabled()) { + log.debug("Proxy: setting server: " + sampler.getDomain()); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/FormCharSetFinder.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/FormCharSetFinder.java new file mode 100644 index 0000000..cda0613 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/FormCharSetFinder.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy; + +import java.util.Map; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.apache.jmeter.protocol.http.parser.HTMLParseException; +import org.htmlparser.Node; +import org.htmlparser.Parser; +import org.htmlparser.Tag; +import org.htmlparser.tags.CompositeTag; +import org.htmlparser.tags.FormTag; +import org.htmlparser.util.NodeIterator; +import org.htmlparser.util.ParserException; + +/** + * A parser for html, to find the form tags, and their accept-charset value + */ +// made public see Bug 49976 +public class FormCharSetFinder { + private static final Logger log = LoggingManager.getLoggerForClass(); + + static { + log.info("Using htmlparser version: "+Parser.getVersion()); + } + + public FormCharSetFinder() { + super(); + } + + /** + * Add form action urls and their corresponding encodings for all forms on the page + * + * @param html the html to parse for form encodings + * @param formEncodings the Map where form encodings should be added + * @param pageEncoding the encoding used for the whole page + * @throws HTMLParseException + */ + public void addFormActionsAndCharSet(String html, Map formEncodings, String pageEncoding) + throws HTMLParseException { + if (log.isDebugEnabled()) { + log.debug("Parsing html of: " + html); + } + + Parser htmlParser = null; + try { + htmlParser = new Parser(); + htmlParser.setInputHTML(html); + } catch (Exception e) { + throw new HTMLParseException(e); + } + + // Now parse the DOM tree + try { + // we start to iterate through the elements + parseNodes(htmlParser.elements(), formEncodings, pageEncoding); + log.debug("End : parseNodes"); + } catch (ParserException e) { + throw new HTMLParseException(e); + } + } + + /** + * Recursively parse all nodes to pick up all form encodings + * + * @param e the nodes to be parsed + * @param formEncodings the Map where we should add form encodings found + * @param pageEncoding the encoding used for the page where the nodes are present + */ + private void parseNodes(final NodeIterator e, Map formEncodings, String pageEncoding) + throws HTMLParseException, ParserException { + while(e.hasMoreNodes()) { + Node node = e.nextNode(); + // a url is always in a Tag. + if (!(node instanceof Tag)) { + continue; + } + Tag tag = (Tag) node; + + // Only check form tags + if (tag instanceof FormTag) { + // Find the action / form url + String action = tag.getAttribute("action"); + String acceptCharSet = tag.getAttribute("accept-charset"); + if(action != null && action.length() > 0) { + // We use the page encoding where the form resides, as the + // default encoding for the form + String formCharSet = pageEncoding; + // Check if we found an accept-charset attribute on the form + if(acceptCharSet != null) { + String[] charSets = JOrphanUtils.split(acceptCharSet, ","); + // Just use the first one of the possible many charsets + if(charSets.length > 0) { + formCharSet = charSets[0].trim(); + if(formCharSet.length() == 0) { + formCharSet = null; + } + } + } + if(formCharSet != null) { + synchronized (formEncodings) { + formEncodings.put(action, formCharSet); + } + } + } + } + + // second, if the tag was a composite tag, + // recursively parse its children. + if (tag instanceof CompositeTag) { + CompositeTag composite = (CompositeTag) tag; + parseNodes(composite.elements(), formEncodings, pageEncoding); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/HttpReplyHdr.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/HttpReplyHdr.java new file mode 100644 index 0000000..e2ada04 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/HttpReplyHdr.java @@ -0,0 +1,292 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy; + +/** + * Utility class to generate HTTP responses of various types. + * + * @version $Revision: 915405 $ + */ +public final class HttpReplyHdr { + /** String representing a carriage-return/line-feed pair. */ + private static final String CR = "\r\n"; + + /** A HTTP protocol version string. */ + private static final String HTTP_PROTOCOL = "HTTP/1.0"; + + /** The HTTP server name. */ + private static final String HTTP_SERVER = "Java Proxy Server"; + + /** + * Don't allow instantiation of this utility class. + */ + private HttpReplyHdr() { + } + + /** + * Forms a http ok reply header + * + * @param contentType + * the mime-type of the content + * @param contentLength + * the length of the content + * @return a string with the header in it + */ + public static String formOk(String contentType, long contentLength) { + StringBuilder out = new StringBuilder(); + + out.append(HTTP_PROTOCOL).append(" 200 Ok").append(CR); + out.append("Server: ").append(HTTP_SERVER).append(CR); + out.append("MIME-version: 1.0").append(CR); + + if (0 < contentType.length()) { + out.append("Content-Type: ").append(contentType).append(CR); + } else { + out.append("Content-Type: text/html").append(CR); + } + + if (0 != contentLength) { + out.append("Content-Length: ").append(contentLength).append(CR); + } + + out.append(CR); + + return out.toString(); + } + + /** + * private! builds an http document describing a headers reason. + * + * @param error + * Error name. + * @param description + * Errors description. + * @return A string with the HTML description body + */ + private static String formErrorBody(String error, String description) { + StringBuilder out = new StringBuilder(); + // Generate Error Body + out.append(""); + out.append(error); + out.append(""); + out.append("

").append(error).append("

\n"); + out.append("

"); + out.append(description); + out.append(""); + return out.toString(); + } + + /** + * builds an http document describing an error. + * + * @param error + * Error name. + * @param description + * Errors description. + * @return A string with the HTML description body + */ + private static String formError(String error, String description) { + /* + * A HTTP RESPONSE HEADER LOOKS ALOT LIKE: + * + * HTTP/1.0 200 OK Date: Wednesday, 02-Feb-94 23:04:12 GMT Server: + * NCSA/1.1 MIME-version: 1.0 Last-modified: Monday, 15-Nov-93 23:33:16 + * GMT Content-Type: text/html Content-Length: 2345 \r\n + */ + + String body = formErrorBody(error, description); + StringBuilder header = new StringBuilder(); + + header.append(HTTP_PROTOCOL).append(" ").append(error).append(CR); + header.append("Server: ").append(HTTP_SERVER).append(CR); + header.append("MIME-version: 1.0").append(CR); + header.append("Content-Type: text/html").append(CR); + + header.append("Content-Length: ").append(body.length()).append(CR); + + header.append(CR); + header.append(body); + + return header.toString(); + } + + /** + * Indicates a new file was created. + * + * @return The header in a string; + */ + public static String formCreated() { + return formError("201 Created", "Object was created"); + } + + /** + * Indicates the document was accepted. + * + * @return The header in a string; + */ + public static String formAccepted() { + return formError("202 Accepted", "Object checked in"); + } + + /** + * Indicates only a partial responce was sent. + * + * @return The header in a string; + */ + public static String formPartial() { + return formError("203 Partial", "Only partail document available"); + } + + /** + * Indicates a requested URL has moved to a new address or name. + * + * @return The header in a string; + */ + public static String formMoved() { + // 300 codes tell client to do actions + return formError("301 Moved", "File has moved"); + } + + /** + * Never seen this used. + * + * @return The header in a string; + */ + public static String formFound() { + return formError("302 Found", "Object was found"); + } + + /** + * The requested method is not implemented by the server. + * + * @return The header in a string; + */ + public static String formMethod() { + return formError("303 Method unseported", "Method unseported"); + } + + /** + * Indicates remote copy of the requested object is current. + * + * @return The header in a string; + */ + public static String formNotModified() { + return formError("304 Not modified", "Use local copy"); + } + + /** + * Client not otherized for the request. + * + * @return The header in a string; + */ + public static String formUnautorized() { + return formError("401 Unathorized", "Unathorized use of this service"); + } + + /** + * Payment is required for service. + * + * @return The header in a string; + */ + public static String formPaymentNeeded() { + return formError("402 Payment required", "Payment is required"); + } + + /** + * Client if forbidden to get the request service. + * + * @return The header in a string; + */ + public static String formForbidden() { + return formError("403 Forbidden", "You need permission for this service"); + } + + /** + * The requested object was not found. + * + * @return The header in a string; + */ + public static String formNotFound() { + return formError("404 Not_found", "Requested object was not found"); + } + + /** + * The server had a problem and could not fulfill the request. + * + * @return The header in a string; + */ + public static String formInternalError() { + return formError("500 Internal server error", "Server broke"); + } + + /** + * Server does not do the requested feature. + * + * @return The header in a string; + */ + public static String formNotImplemented() { + return formError("501 Method not implemented", "Service not implemented"); + } + + /** + * Server does not do the requested feature. + * + * @param reason detailed information for causing the failure + * @return The header in a string; + */ + public static String formNotImplemented(String reason) { + return formError("501 Method not implemented", "Service not implemented. " + reason); + } + + /** + * Server is overloaded, client should try again latter. + * + * @return The header in a string; + */ + public static String formOverloaded() { + return formError("502 Server overloaded", "Try again latter"); + } + + /** + * Indicates the request took to long. + * + * @return The header in a string; + */ + public static String formTimeout() { + return formError("503 Gateway timeout", "The connection timed out"); + } + + /** + * Indicates the client's proxies could not locate a server. + * + * @return The header in a string; + */ + public static String formServerNotFound() { + return formError("503 Gateway timeout", "The requested server was not found"); + } + + /** + * Indicates the client is not allowed to access the object. + * + * @return The header in a string; + */ + public static String formNotAllowed() { + return formError("403 Access Denied", "Access is not allowed"); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/HttpRequestHdr.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/HttpRequestHdr.java new file mode 100644 index 0000000..cf9ecc1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/HttpRequestHdr.java @@ -0,0 +1,414 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.commons.lang.CharUtils; +import org.apache.jmeter.protocol.http.config.MultipartUrlConfig; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.gui.HeaderPanel; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +//For unit tests, @see TestHttpRequestHdr + +/** + * The headers of the client HTTP request. + * + */ +public class HttpRequestHdr { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String HTTP = "http"; // $NON-NLS-1$ + private static final String HTTPS = "https"; // $NON-NLS-1$ + private static final String PROXY_CONNECTION = "proxy-connection"; // $NON-NLS-1$ + public static final String CONTENT_TYPE = "content-type"; // $NON-NLS-1$ + public static final String CONTENT_LENGTH = "content-length"; // $NON-NLS-1$ + + + /** + * Http Request method, uppercased, e.g. GET or POST. + */ + private String method = ""; // $NON-NLS-1$ + + /** CONNECT url. */ + private String paramHttps = ""; // $NON-NLS-1$ + + /** + * The requested url. The universal resource locator that hopefully uniquely + * describes the object or service the client is requesting. + */ + private String url = ""; // $NON-NLS-1$ + + /** + * Version of http being used. Such as HTTP/1.0. + */ + private String version = ""; // NOTREAD // $NON-NLS-1$ + + private byte[] rawPostData; + + private final Map headers = new HashMap(); + + private final String httpSamplerName; + + private HeaderManager headerManager; + + public HttpRequestHdr() { + this.httpSamplerName = ""; // $NON-NLS-1$ + } + + /** + * @param httpSamplerName the http sampler name + */ + public HttpRequestHdr(String httpSamplerName) { + this.httpSamplerName = httpSamplerName; + } + + /** + * Parses a http header from a stream. + * + * @param in + * the stream to parse. + * @return array of bytes from client. + */ + public byte[] parse(InputStream in) throws IOException { + boolean inHeaders = true; + int readLength = 0; + int dataLength = 0; + boolean firstLine = true; + ByteArrayOutputStream clientRequest = new ByteArrayOutputStream(); + ByteArrayOutputStream line = new ByteArrayOutputStream(); + int x; + while ((inHeaders || readLength < dataLength) && ((x = in.read()) != -1)) { + line.write(x); + clientRequest.write(x); + if (firstLine && !CharUtils.isAscii((char) x)){// includes \n + throw new IllegalArgumentException("Only ASCII supported in headers (perhaps SSL was used?)"); + } + if (inHeaders && (byte) x == (byte) '\n') { // $NON-NLS-1$ + if (line.size() < 3) { + inHeaders = false; + firstLine = false; // cannot be first line either + } + if (firstLine) { + parseFirstLine(line.toString()); + firstLine = false; + } else { + // parse other header lines, looking for Content-Length + final int contentLen = parseLine(line.toString()); + if (contentLen > 0) { + dataLength = contentLen; // Save the last valid content length one + } + } + if (log.isDebugEnabled()){ + log.debug("Client Request Line: " + line.toString()); + } + line.reset(); + } else if (!inHeaders) { + readLength++; + } + } + // Keep the raw post data + rawPostData = line.toByteArray(); + + if (log.isDebugEnabled()){ + log.debug("rawPostData in default JRE encoding: " + new String(rawPostData)); // TODO - charset? + log.debug("Request: " + clientRequest.toString()); + } + return clientRequest.toByteArray(); + } + + private void parseFirstLine(String firstLine) { + if (log.isDebugEnabled()) { + log.debug("browser request: " + firstLine); + } + StringTokenizer tz = new StringTokenizer(firstLine); + method = getToken(tz).toUpperCase(java.util.Locale.ENGLISH); + url = getToken(tz); + version = getToken(tz); + if (log.isDebugEnabled()) { + log.debug("parser input: " + firstLine); + log.debug("parsed method: " + method); + log.debug("parsed url: " + url); + log.debug("parsed version:" + version); + } + // SSL connection + if (getMethod().startsWith(HTTPConstants.CONNECT)) { + paramHttps = url; + } + if (url.startsWith("/")) { + url = HTTPS + "://" + paramHttps + url; // $NON-NLS-1$ + } + log.debug("First Line: " + url); + } + + /* + * Split line into name/value pairs and store in headers if relevant + * If name = "content-length", then return value as int, else return 0 + */ + private int parseLine(String nextLine) { + int colon = nextLine.indexOf(':'); + if (colon <= 0){ + return 0; // Nothing to do + } + String name = nextLine.substring(0, colon).trim(); + String value = nextLine.substring(colon+1).trim(); + headers.put(name.toLowerCase(java.util.Locale.ENGLISH), new Header(name, value)); + if (name.equalsIgnoreCase(CONTENT_LENGTH)) { + return Integer.parseInt(value); + } + return 0; + } + + private HeaderManager createHeaderManager() { + HeaderManager manager = new HeaderManager(); + for (String key : headers.keySet()) { + if (!key.equals(PROXY_CONNECTION) + && !key.equals(CONTENT_LENGTH) + && !key.equalsIgnoreCase(HTTPConstants.HEADER_CONNECTION)) { + manager.add(headers.get(key)); + } + } + manager.setName(JMeterUtils.getResString("header_manager_title")); // $NON-NLS-1$ + manager.setProperty(TestElement.TEST_CLASS, HeaderManager.class.getName()); + manager.setProperty(TestElement.GUI_CLASS, HeaderPanel.class.getName()); + return manager; + } + + public HeaderManager getHeaderManager() { + if(headerManager == null) { + headerManager = createHeaderManager(); + } + return headerManager; + } + + public String getContentType() { + Header contentTypeHeader = headers.get(CONTENT_TYPE); + if (contentTypeHeader != null) { + return contentTypeHeader.getValue(); + } + return null; + } + + private boolean isMultipart(String contentType) { + if (contentType != null && contentType.startsWith(HTTPConstants.MULTIPART_FORM_DATA)) { + return true; + } + return false; + } + + public MultipartUrlConfig getMultipartConfig(String contentType) { + if(isMultipart(contentType)) { + // Get the boundary string for the multiparts from the content type + String boundaryString = contentType.substring(contentType.toLowerCase(java.util.Locale.ENGLISH).indexOf("boundary=") + "boundary=".length()); + return new MultipartUrlConfig(boundaryString); + } + return null; + } + + // + // Parsing Methods + // + + /** + * Find the //server.name from an url. + * + * @return server's internet name + */ + public String serverName() { + // chop to "server.name:x/thing" + String str = url; + int i = str.indexOf("//"); // $NON-NLS-1$ + if (i > 0) { + str = str.substring(i + 2); + } + // chop to server.name:xx + i = str.indexOf("/"); // $NON-NLS-1$ + if (0 < i) { + str = str.substring(0, i); + } + // chop to server.name + i = str.lastIndexOf(":"); // $NON-NLS-1$ + if (0 < i) { + str = str.substring(0, i); + } + // Handle IPv6 urls + if(str.startsWith("[")&& str.endsWith("]")) { + return str.substring(1, str.length()-1); + } + return str; + } + + // TODO replace repeated substr() above and below with more efficient method. + + /** + * Find the :PORT from http://server.ect:PORT/some/file.xxx + * + * @return server's port (or UNSPECIFIED if not found) + */ + public int serverPort() { + String str = url; + // chop to "server.name:x/thing" + int i = str.indexOf("//"); + if (i > 0) { + str = str.substring(i + 2); + } + // chop to server.name:xx + i = str.indexOf("/"); + if (0 < i) { + str = str.substring(0, i); + } + // chop to server.name + i = str.lastIndexOf(":"); + if (0 < i) { + return Integer.parseInt(str.substring(i + 1).trim()); + } + return HTTPSamplerBase.UNSPECIFIED_PORT; + } + + /** + * Find the /some/file.xxxx from http://server.ect:PORT/some/file.xxx + * + * @return the path + */ + public String getPath() { + String str = url; + int i = str.indexOf("//"); + if (i > 0) { + str = str.substring(i + 2); + } + i = str.indexOf("/"); + if (i < 0) { + return ""; + } + return str.substring(i); + } + + /** + * Returns the url string extracted from the first line of the client request. + * + * @return the url + */ + public String getUrl(){ + return url; + } + + /** + * Returns the method string extracted from the first line of the client request. + * + * @return the method (will always be upper case) + */ + public String getMethod(){ + return method; + } + + /** + * Returns the next token in a string. + * + * @param tk + * String that is partially tokenized. + * @return The remainder + */ + private String getToken(StringTokenizer tk) { + if (tk.hasMoreTokens()) { + return tk.nextToken(); + } + return "";// $NON-NLS-1$ + } + +// /** +// * Returns the remainder of a tokenized string. +// * +// * @param tk +// * String that is partially tokenized. +// * @return The remainder +// */ +// private String getRemainder(StringTokenizer tk) { +// StringBuilder strBuff = new StringBuilder(); +// if (tk.hasMoreTokens()) { +// strBuff.append(tk.nextToken()); +// } +// while (tk.hasMoreTokens()) { +// strBuff.append(" "); // $NON-NLS-1$ +// strBuff.append(tk.nextToken()); +// } +// return strBuff.toString(); +// } + + public String getUrlWithoutQuery(URL _url) { + String fullUrl = _url.toString(); + String urlWithoutQuery = fullUrl; + String query = _url.getQuery(); + if(query != null) { + // Get rid of the query and the ? + urlWithoutQuery = urlWithoutQuery.substring(0, urlWithoutQuery.length() - query.length() - 1); + } + return urlWithoutQuery; + } + + /** + * @return the httpSamplerName + */ + public String getHttpSamplerName() { + return httpSamplerName; + } + + /** + * @return byte[] Raw post data + */ + public byte[] getRawPostData() { + return rawPostData; + } + + /** + * @param sampler {@link HTTPSamplerBase} + * @return String Protocol (http or https) + */ + public String getProtocol(HTTPSamplerBase sampler) { + if (url.indexOf("//") > -1) { + String protocol = url.substring(0, url.indexOf(":")); + if (log.isDebugEnabled()) { + log.debug("Proxy: setting protocol to : " + protocol); + } + return protocol; + } else if (sampler.getPort() == HTTPConstants.DEFAULT_HTTPS_PORT) { + if (log.isDebugEnabled()) { + log.debug("Proxy: setting protocol to https"); + } + return HTTPS; + } else { + if (log.isDebugEnabled()) { + log.debug("Proxy setting default protocol to: http"); + } + return HTTP; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/Proxy.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/Proxy.java new file mode 100644 index 0000000..547f948 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/Proxy.java @@ -0,0 +1,585 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.URL; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.parser.HTMLParseException; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.util.ConversionUtils; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Thread to handle one client request. Gets the request from the client and + * passes it on to the server, then sends the response back to the client. + * Information about the request and response is stored so it can be used in a + * JMeter test plan. + * + */ +public class Proxy extends Thread { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final byte[] CRLF_BYTES = { 0x0d, 0x0a }; + private static final String CRLF_STRING = "\r\n"; + + private static final String NEW_LINE = "\n"; // $NON-NLS-1$ + + private static final String[] headersToRemove; + + // Allow list of headers to be overridden + private static final String PROXY_HEADERS_REMOVE = "proxy.headers.remove"; // $NON-NLS-1$ + + private static final String PROXY_HEADERS_REMOVE_DEFAULT = "If-Modified-Since,If-None-Match,Host"; // $NON-NLS-1$ + + private static final String PROXY_HEADERS_REMOVE_SEPARATOR = ","; // $NON-NLS-1$ + + // for ssl connection + private static final String KEYSTORE_TYPE = + JMeterUtils.getPropDefault("proxy.cert.type", "JKS"); // $NON-NLS-1$ $NON-NLS-2$ + + private static final String KEYMANAGERFACTORY = + JMeterUtils.getPropDefault("proxy.cert.factory", "SunX509"); // $NON-NLS-1$ $NON-NLS-2$ + + private static final String SSLCONTEXT_PROTOCOL = + JMeterUtils.getPropDefault("proxy.ssl.protocol", "SSLv3"); // $NON-NLS-1$ $NON-NLS-2$ + + // HashMap to save ssl connection between Jmeter proxy and browser + private static final HashMap hashHost = new HashMap(); + + // Proxy configuration SSL + private static final String CERT_DIRECTORY = + JMeterUtils.getPropDefault("proxy.cert.directory", JMeterUtils.getJMeterBinDir()); // $NON-NLS-1$ + + private static final String CERT_FILE_DEFAULT = "proxyserver.jks";// $NON-NLS-1$ + + private static final String CERT_FILE = + JMeterUtils.getPropDefault("proxy.cert.file", CERT_FILE_DEFAULT); // $NON-NLS-1$ + + private static final char[] KEYSTORE_PASSWORD = + JMeterUtils.getPropDefault("proxy.cert.keystorepass", "password").toCharArray(); // $NON-NLS-1$ $NON-NLS-2$ + + private static final char[] KEY_PASSWORD = + JMeterUtils.getPropDefault("proxy.cert.keypassword","password").toCharArray(); // $NON-NLS-1$ $NON-NLS-2$ + + private static final SamplerCreatorFactory factory = new SamplerCreatorFactory(); + + private static final Pattern COOKIE_SECURE_PATTERN = Pattern.compile("\\bsecure\\b", Pattern.CASE_INSENSITIVE); + + // Use with SSL connection + private OutputStream outStreamClient = null; + + static { + String removeList = JMeterUtils.getPropDefault(PROXY_HEADERS_REMOVE,PROXY_HEADERS_REMOVE_DEFAULT); + headersToRemove = JOrphanUtils.split(removeList,PROXY_HEADERS_REMOVE_SEPARATOR); + log.info("Proxy will remove the headers: "+removeList); + } + + /** Socket to client. */ + private Socket clientSocket = null; + + /** Target to receive the generated sampler. */ + private ProxyControl target; + + /** Whether or not to capture the HTTP headers. */ + private boolean captureHttpHeaders; + + /** Whether to try to spoof as https **/ + private boolean httpsSpoof; + + private String httpsSpoofMatch; // if non-empty, then URLs must match in order to be spoofed + + /** Reference to Deamon's Map of url string to page character encoding of that page */ + private Map pageEncodings; + /** Reference to Deamon's Map of url string to character encoding for the form */ + private Map formEncodings; + + /** + * Default constructor - used by newInstance call in Daemon + */ + public Proxy() { + } + + /** + * Configure the Proxy. + * Intended to be called directly after construction. + * Should not be called after it has been passed to a new thread, + * otherwise the variables may not be published correctly. + * + * @param _clientSocket + * the socket connection to the client + * @param _target + * the ProxyControl which will receive the generated sampler + * @param _pageEncodings + * reference to the Map of Deamon, with mappings from page urls to encoding used + * @param formEncodingsEncodings + * reference to the Map of Deamon, with mappings from form action urls to encoding used + */ + void configure(Socket _clientSocket, ProxyControl _target, Map _pageEncodings, Map _formEncodings) { + this.target = _target; + this.clientSocket = _clientSocket; + this.captureHttpHeaders = _target.getCaptureHttpHeaders(); + this.httpsSpoof = _target.getHttpsSpoof(); + this.httpsSpoofMatch = _target.getHttpsSpoofMatch(); + this.pageEncodings = _pageEncodings; + this.formEncodings = _formEncodings; + } + + /** + * Main processing method for the Proxy object + */ + @Override + public void run() { + // Check which HTTPSampler class we should use + String httpSamplerName = target.getSamplerTypeName(); + + HttpRequestHdr request = new HttpRequestHdr(httpSamplerName); + SampleResult result = null; + HeaderManager headers = null; + HTTPSamplerBase sampler = null; + try { + // Now, parse only first line + request.parse(new BufferedInputStream(clientSocket.getInputStream())); + outStreamClient = clientSocket.getOutputStream(); + + if ((request.getMethod().startsWith(HTTPConstants.CONNECT)) && (outStreamClient != null)) { + log.debug("Method CONNECT => SSL"); + // write a OK reponse to browser, to engage SSL exchange + outStreamClient.write(("HTTP/1.0 200 OK\r\n\r\n").getBytes(SampleResult.DEFAULT_HTTP_ENCODING)); // $NON-NLS-1$ + outStreamClient.flush(); + // With ssl request, url is host:port (without https:// or path) + String[] param = request.getUrl().split(":"); // $NON-NLS-1$ + if (param.length == 2) { + log.debug("Start to negotiate SSL connection, host: " + param[0]); + clientSocket = startSSL(clientSocket, param[0]); + } else { + log.warn("In SSL request, unable to find host and port in CONNECT request"); + } + // Re-parse (now it's the http request over SSL) + request.parse(new BufferedInputStream(clientSocket.getInputStream())); + } + + SamplerCreator samplerCreator = factory.getSamplerCreator(request, pageEncodings, formEncodings); + sampler = samplerCreator.createSampler(request, pageEncodings, formEncodings); + samplerCreator.populateSampler(sampler, request, pageEncodings, formEncodings); + + /* + * Create a Header Manager to ensure that the browsers headers are + * captured and sent to the server + */ + headers = request.getHeaderManager(); + sampler.setHeaderManager(headers); + + /* + * If we are trying to spoof https, change the protocol + */ + boolean forcedHTTPS = false; // so we know when to revert + if (httpsSpoof) { + if (httpsSpoofMatch.length() > 0){ + String url = request.getUrl(); + if (url.matches(httpsSpoofMatch)){ + sampler.setProtocol(HTTPConstants.PROTOCOL_HTTPS); + forcedHTTPS = true; + } + } else { + sampler.setProtocol(HTTPConstants.PROTOCOL_HTTPS); + forcedHTTPS = true; + } + } + sampler.threadStarted(); // Needed for HTTPSampler2 + result = sampler.sample(); + + /* + * If we're dealing with text data, and if we're spoofing https, + * replace all occurences of "https://" with "http://" for the client. + * TODO - also check the match string to restrict the changes further? + */ + if (httpsSpoof && SampleResult.TEXT.equals(result.getDataType())) + { + final String enc = result.getDataEncodingWithDefault(); + String noHttpsResult = new String(result.getResponseData(),enc); + final String HTTPS_HOST = // match https://host[:port]/ and drop default port if present + "https://([^:/]+)(:"+HTTPConstants.DEFAULT_HTTPS_PORT_STRING+")?"; // $NON-NLS-1$ $NON-NLS-2$ + noHttpsResult = noHttpsResult.replaceAll(HTTPS_HOST, "http://$1"); // $NON-NLS-1$ + result.setResponseData(noHttpsResult.getBytes(enc)); + } + + // Find the page encoding and possibly encodings for forms in the page + // in the response from the web server + String pageEncoding = addPageEncoding(result); + addFormEncodings(result, pageEncoding); + + writeToClient(result, new BufferedOutputStream(clientSocket.getOutputStream()), forcedHTTPS); + } catch (UnknownHostException uhe) { + log.warn("Server Not Found.", uhe); + writeErrorToClient(HttpReplyHdr.formServerNotFound()); + result = generateErrorResult(result, uhe); // Generate result (if nec.) and populate it + } catch (IllegalArgumentException e) { + log.error("Not implemented (probably used https)", e); + writeErrorToClient(HttpReplyHdr.formNotImplemented("Probably used https instead of http. " + + "To record https requests, see " + + "
HTTP Proxy Server documentation")); + result = generateErrorResult(result, e); // Generate result (if nec.) and populate it + } catch (IOException ioe) { + log.error("Problem with SSL certificate? Ensure browser is set to accept the JMeter proxy cert: "+ioe.getLocalizedMessage()); + // won't work: writeErrorToClient(HttpReplyHdr.formInternalError()); + if (result == null) { + result = new SampleResult(); + result.setSampleLabel("Sample failed"); + } + result.setResponseMessage(ioe.getMessage()+ "\n**ensure browser is set to accept the JMeter proxy certificate**"); + } catch (Exception e) { + log.error("Exception when processing sample", e); + writeErrorToClient(HttpReplyHdr.formInternalError()); + result = generateErrorResult(result, e); // Generate result (if nec.) and populate it + } finally { + boolean samplerAvailable = sampler != null; + if (log.isDebugEnabled()) { + if(samplerAvailable) { + log.debug("Will deliver sample " + sampler.getName()); + } + } + /* + * We don't want to store any cookies in the generated test plan + */ + if (headers != null) { + headers.removeHeaderNamed(HTTPConstants.HEADER_COOKIE);// Always remove cookies + headers.removeHeaderNamed(HTTPConstants.HEADER_AUTHORIZATION);// Always remove authorization + // Remove additional headers + for(String hdr : headersToRemove){ + headers.removeHeaderNamed(hdr); + } + } + if(samplerAvailable) { + target.deliverSampler(sampler, new TestElement[] { captureHttpHeaders ? headers : null }, result); + } + try { + clientSocket.close(); + } catch (Exception e) { + log.error("", e); + } + if(samplerAvailable) { + sampler.threadFinished(); // Needed for HTTPSampler2 + } + } + } + + /** + * Get SSL connection from hashmap, creating it if necessary. + * + * @param host + * @return a ssl socket factory + * @throws IOException + */ + private SSLSocketFactory getSSLSocketFactory(String host) throws IOException { + synchronized (hashHost) { + if (hashHost.containsKey(host)) { + log.debug("Good, already in map, host=" + host); + return hashHost.get(host); + } + InputStream in = getCertificate(); + Exception except = null; + if (in != null) { + KeyStore ks = null; + KeyManagerFactory kmf = null; + SSLContext sslcontext = null; + try { + ks = KeyStore.getInstance(KEYSTORE_TYPE); + ks.load(in, KEYSTORE_PASSWORD); + kmf = KeyManagerFactory.getInstance(KEYMANAGERFACTORY); + kmf.init(ks, KEY_PASSWORD); + sslcontext = SSLContext.getInstance(SSLCONTEXT_PROTOCOL); + sslcontext.init(kmf.getKeyManagers(), null, null); + SSLSocketFactory sslFactory = sslcontext.getSocketFactory(); + hashHost.put(host, sslFactory); + log.info("KeyStore for SSL loaded OK and put host in map ("+host+")"); + return sslFactory; + } catch (NoSuchAlgorithmException e) { + except=e; + } catch (KeyManagementException e) { + except=e; + } catch (KeyStoreException e) { + except=e; + } catch (UnrecoverableKeyException e) { + except=e; + } catch (CertificateException e) { + except=e; + } finally { + if (except != null){ + log.error("Problem with SSL certificate",except); + } + IOUtils.closeQuietly(in); + } + } else { + throw new IOException("Unable to read keystore"); + } + return null; + } + } + + /** + * Negotiate a SSL connection. + * + * @param sock socket in + * @param host + * @return a new client socket over ssl + * @throws Exception if negotiation failed + */ + private Socket startSSL(Socket sock, String host) throws IOException { + SSLSocketFactory sslFactory = getSSLSocketFactory(host); + SSLSocket secureSocket; + if (sslFactory != null) { + try { + secureSocket = (SSLSocket) sslFactory.createSocket(sock, + sock.getInetAddress().getHostName(), sock.getPort(), true); + secureSocket.setUseClientMode(false); + if (log.isDebugEnabled()){ + log.debug("SSL transaction ok with cipher: " + secureSocket.getSession().getCipherSuite()); + } + return secureSocket; + } catch (IOException e) { + log.error("Error in SSL socket negotiation: ", e); + throw e; + } + } else { + log.warn("Unable to negotiate SSL transaction, no keystore?"); + throw new IOException("Unable to negotiate SSL transaction, no keystore?"); + } + } + + /** + * Open the local certificate file. + * + * @return stream to key cert; null if there was a problem opening it + */ + private InputStream getCertificate() { + File certFile = new File(CERT_DIRECTORY, CERT_FILE); + InputStream in = null; + final String certPath = certFile.getAbsolutePath(); + if (certFile.exists() && certFile.canRead()) { + try { + in = new FileInputStream(certFile); + log.info("Opened Keystore file: "+certPath); + } catch (FileNotFoundException e) { + log.error("No server cert file found: "+certPath, e); + } + } else { + log.error("No server cert file found: "+certPath); + } + return in; + } + + private static SampleResult generateErrorResult(SampleResult result, Exception e) { + if (result == null) { + result = new SampleResult(); + result.setSampleLabel("Sample failed"); + } + result.setResponseMessage(e.getMessage()); + return result; + } + + /** + * Write output to the output stream, then flush and close the stream. + * + * @param inBytes + * the bytes to write + * @param out + * the output stream to write to + * @param forcedHTTPS if we changed the protocol to https + * @throws IOException + * if an IOException occurs while writing + */ + private void writeToClient(SampleResult res, OutputStream out, boolean forcedHTTPS) throws IOException { + try { + String responseHeaders = messageResponseHeaders(res, forcedHTTPS); + out.write(responseHeaders.getBytes(SampleResult.DEFAULT_HTTP_ENCODING)); + out.write(CRLF_BYTES); + out.write(res.getResponseData()); + out.flush(); + log.debug("Done writing to client"); + } catch (IOException e) { + log.error("", e); + throw e; + } finally { + try { + out.close(); + } catch (Exception ex) { + log.warn("Error while closing socket", ex); + } + } + } + + /** + * In the event the content was gzipped and unpacked, the content-encoding + * header must be removed and the content-length header should be corrected. + * + * The Transfer-Encoding header is also removed. + * If the protocol was changed to HTTPS then change any Location header back to http + * @param res - response + * @param forcedHTTPS if we changed the protocol to https + * + * @return updated headers to be sent to client + */ + private String messageResponseHeaders(SampleResult res, boolean forcedHTTPS) { + String headers = res.getResponseHeaders(); + String [] headerLines=headers.split(NEW_LINE, 0); // drop empty trailing content + int contentLengthIndex=-1; + boolean fixContentLength = forcedHTTPS; + for (int i=0;i=0){// Fix the content length + headerLines[contentLengthIndex]=HTTPConstants.HEADER_CONTENT_LENGTH+": "+res.getResponseData().length; + } + StringBuilder sb = new StringBuilder(headers.length()); + for (int i=0;i + * This property is not persistent. + */ + private JMeterTreeNode target; + + public ProxyControl() { + setPort(DEFAULT_PORT); + setExcludeList(new HashSet()); + setIncludeList(new HashSet()); + setCaptureHttpHeaders(true); // maintain original behaviour + } + + public void setPort(int port) { + this.setProperty(new IntegerProperty(PORT, port)); + } + + public void setPort(String port) { + setProperty(PORT, port); + } + + public void setCaptureHttpHeaders(boolean capture) { + setProperty(new BooleanProperty(CAPTURE_HTTP_HEADERS, capture)); + } + + public void setGroupingMode(int grouping) { + this.groupingMode.set(grouping); + setProperty(new IntegerProperty(GROUPING_MODE, grouping)); + } + + public void setAssertions(boolean b) { + addAssertions.set(b); + setProperty(new BooleanProperty(ADD_ASSERTIONS, b)); + } + + public void setSamplerTypeName(int samplerTypeName) { + setProperty(new IntegerProperty(SAMPLER_TYPE_NAME, samplerTypeName)); + } + + public void setSamplerRedirectAutomatically(boolean b) { + samplerRedirectAutomatically.set(b); + setProperty(new BooleanProperty(SAMPLER_REDIRECT_AUTOMATICALLY, b)); + } + + public void setSamplerFollowRedirects(boolean b) { + samplerFollowRedirects.set(b); + setProperty(new BooleanProperty(SAMPLER_FOLLOW_REDIRECTS, b)); + } + + /** + * @param b + */ + public void setUseKeepAlive(boolean b) { + useKeepAlive.set(b); + setProperty(new BooleanProperty(USE_KEEPALIVE, b)); + } + + public void setSamplerDownloadImages(boolean b) { + samplerDownloadImages.set(b); + setProperty(new BooleanProperty(SAMPLER_DOWNLOAD_IMAGES, b)); + } + + public void setIncludeList(Collection list) { + setProperty(new CollectionProperty(INCLUDE_LIST, new HashSet(list))); + } + + public void setExcludeList(Collection list) { + setProperty(new CollectionProperty(EXCLUDE_LIST, new HashSet(list))); + } + + /** + * @param b + */ + public void setRegexMatch(boolean b) { + regexMatch.set(b); + setProperty(new BooleanProperty(REGEX_MATCH, b)); + } + + public void setHttpsSpoof(boolean b) { + setProperty(new BooleanProperty(HTTPS_SPOOF, b)); + } + + public void setHttpsSpoofMatch(String s) { + setProperty(new StringProperty(HTTPS_SPOOF_MATCH, s)); + } + + public void setContentTypeExclude(String contentTypeExclude) { + setProperty(new StringProperty(CONTENT_TYPE_EXCLUDE, contentTypeExclude)); + } + + public void setContentTypeInclude(String contentTypeInclude) { + setProperty(new StringProperty(CONTENT_TYPE_INCLUDE, contentTypeInclude)); + } + + public boolean getAssertions() { + return getPropertyAsBoolean(ADD_ASSERTIONS); + } + + public int getGroupingMode() { + return getPropertyAsInt(GROUPING_MODE); + } + + public int getPort() { + return getPropertyAsInt(PORT); + } + + public String getPortString() { + return getPropertyAsString(PORT); + } + + public int getDefaultPort() { + return DEFAULT_PORT; + } + + public boolean getCaptureHttpHeaders() { + return getPropertyAsBoolean(CAPTURE_HTTP_HEADERS); + } + + public String getSamplerTypeName() { + // Convert the old numeric types - just in case someone wants to reload the workbench + String type = getPropertyAsString(SAMPLER_TYPE_NAME); + if (SAMPLER_TYPE_HTTP_SAMPLER_JAVA.equals(type)){ + type = HTTPSamplerFactory.IMPL_JAVA; + } else if (SAMPLER_TYPE_HTTP_SAMPLER_HC3_1.equals(type)){ + type = HTTPSamplerFactory.IMPL_HTTP_CLIENT3_1; + } else if (SAMPLER_TYPE_HTTP_SAMPLER_HC4.equals(type)){ + type = HTTPSamplerFactory.IMPL_HTTP_CLIENT4; + } + return type; + } + + public boolean getSamplerRedirectAutomatically() { + return getPropertyAsBoolean(SAMPLER_REDIRECT_AUTOMATICALLY, false); + } + + public boolean getSamplerFollowRedirects() { + return getPropertyAsBoolean(SAMPLER_FOLLOW_REDIRECTS, true); + } + + public boolean getUseKeepalive() { + return getPropertyAsBoolean(USE_KEEPALIVE, true); + } + + public boolean getSamplerDownloadImages() { + return getPropertyAsBoolean(SAMPLER_DOWNLOAD_IMAGES, false); + } + + public boolean getRegexMatch() { + return getPropertyAsBoolean(REGEX_MATCH, false); + } + + public boolean getHttpsSpoof() { + return getPropertyAsBoolean(HTTPS_SPOOF, false); + } + + public String getHttpsSpoofMatch() { + return getPropertyAsString(HTTPS_SPOOF_MATCH, ""); + } + + public String getContentTypeExclude() { + return getPropertyAsString(CONTENT_TYPE_EXCLUDE); + } + + public String getContentTypeInclude() { + return getPropertyAsString(CONTENT_TYPE_INCLUDE); + } + + public void addConfigElement(ConfigElement config) { + // NOOP + } + + public void startProxy() throws IOException { + notifyTestListenersOfStart(); + try { + server = new Daemon(getPort(), this); + server.start(); + GuiPackage.getInstance().register(server); + } catch (IOException e) { + log.error("Could not create Proxy daemon", e); + throw e; + } + } + + public void addExcludedPattern(String pattern) { + getExcludePatterns().addItem(pattern); + } + + public CollectionProperty getExcludePatterns() { + return (CollectionProperty) getProperty(EXCLUDE_LIST); + } + + public void addIncludedPattern(String pattern) { + getIncludePatterns().addItem(pattern); + } + + public CollectionProperty getIncludePatterns() { + return (CollectionProperty) getProperty(INCLUDE_LIST); + } + + public void clearExcludedPatterns() { + getExcludePatterns().clear(); + } + + public void clearIncludedPatterns() { + getIncludePatterns().clear(); + } + + /** + * @return the target controller node + */ + public JMeterTreeNode getTarget() { + return target; + } + + /** + * Sets the target node where the samples generated by the proxy have to be + * stored. + */ + public void setTarget(JMeterTreeNode target) { + this.target = target; + } + + /** + * Receives the recorded sampler from the proxy server for placing in the + * test tree. param serverResponse to be added to allow saving of the + * server's response while recording. A future consideration. + */ + public synchronized void deliverSampler(final HTTPSamplerBase sampler, final TestElement[] subConfigs, final SampleResult result) { + if (filterContentType(result) && filterUrl(sampler)) { + JMeterTreeNode myTarget = findTargetControllerNode(); + @SuppressWarnings("unchecked") // OK, because find only returns correct element types + Collection defaultConfigurations = (Collection) findApplicableElements(myTarget, ConfigTestElement.class, false); + @SuppressWarnings("unchecked") // OK, because find only returns correct element types + Collection userDefinedVariables = (Collection) findApplicableElements(myTarget, Arguments.class, true); + + removeValuesFromSampler(sampler, defaultConfigurations); + replaceValues(sampler, subConfigs, userDefinedVariables); + sampler.setAutoRedirects(samplerRedirectAutomatically.get()); + sampler.setFollowRedirects(samplerFollowRedirects.get()); + sampler.setUseKeepAlive(useKeepAlive.get()); + sampler.setImageParser(samplerDownloadImages.get()); + + placeSampler(sampler, subConfigs, myTarget); + } + else { + if(log.isDebugEnabled()) { + log.debug("Sample excluded based on url or content-type: " + result.getUrlAsString() + " - " + result.getContentType()); + } + result.setSampleLabel("["+result.getSampleLabel()+"]"); + } + // SampleEvent is not passed JMeterVariables, because they don't make sense for Proxy Recording + notifySampleListeners(new SampleEvent(result, "WorkBench")); // TODO - is this the correct threadgroup name? + } + + public void stopProxy() { + if (server != null) { + server.stopServer(); + GuiPackage.getInstance().unregister(server); + try { + server.join(1000); // wait for server to stop + } catch (InterruptedException e) { + //NOOP + } + notifyTestListenersOfEnd(); + server = null; + } + } + + // Package protected to allow test case access + boolean filterUrl(HTTPSamplerBase sampler) { + String domain = sampler.getDomain(); + if (domain == null || domain.length() == 0) { + return false; + } + + String url = generateMatchUrl(sampler); + CollectionProperty includePatterns = getIncludePatterns(); + if (includePatterns.size() > 0) { + if (!matchesPatterns(url, includePatterns)) { + return false; + } + } + + CollectionProperty excludePatterns = getExcludePatterns(); + if (excludePatterns.size() > 0) { + if (matchesPatterns(url, excludePatterns)) { + return false; + } + } + + return true; + } + + // Package protected to allow test case access + /** + * Filter the response based on the content type. + * If no include nor exclude filter is specified, the result will be included + * + * @param result the sample result to check, true means result will be kept + */ + boolean filterContentType(SampleResult result) { + String includeExp = getContentTypeInclude(); + String excludeExp = getContentTypeExclude(); + // If no expressions are specified, we let the sample pass + if((includeExp == null || includeExp.length() == 0) && + (excludeExp == null || excludeExp.length() == 0) + ) + { + return true; + } + + // Check that we have a content type + String sampleContentType = result.getContentType(); + if(sampleContentType == null || sampleContentType.length() == 0) { + if(log.isDebugEnabled()) { + log.debug("No Content-type found for : " + result.getUrlAsString()); + } + + return true; + } + + if(log.isDebugEnabled()) { + log.debug("Content-type to filter : " + sampleContentType); + } + + // Check if the include pattern is matched + boolean matched = testPattern(includeExp, sampleContentType, true); + if(!matched) { + return false; + } + + // Check if the exclude pattern is matched + matched = testPattern(excludeExp, sampleContentType, false); + if(!matched) { + return false; + } + + return true; + } + + /** + * Returns true if matching pattern was different from expectedToMatch + * @param expression Expression to match + * @param sampleContentType + * @return boolean true if Matching expression + */ + private final boolean testPattern(String expression, String sampleContentType, boolean expectedToMatch) { + if(expression != null && expression.length() > 0) { + if(log.isDebugEnabled()) { + log.debug("Testing Expression : " + expression + " on sampleContentType:"+sampleContentType+", expected to match:"+expectedToMatch); + } + + Pattern pattern = null; + try { + pattern = JMeterUtils.getPatternCache().getPattern(expression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.SINGLELINE_MASK); + if(JMeterUtils.getMatcher().contains(sampleContentType, pattern) != expectedToMatch) { + return false; + } + } catch (MalformedCachePatternException e) { + log.warn("Skipped invalid content pattern: " + expression, e); + } + } + return true; + } + /** + * Helper method to add a Response Assertion + * Called from AWT Event thread + */ + private void addAssertion(JMeterTreeModel model, JMeterTreeNode node) throws IllegalUserActionException { + ResponseAssertion ra = new ResponseAssertion(); + ra.setProperty(TestElement.GUI_CLASS, ASSERTION_GUI); + ra.setName(JMeterUtils.getResString("assertion_title")); // $NON-NLS-1$ + ra.setTestFieldResponseData(); + model.addComponent(ra, node); + } + + /** + * Helper method to add a Divider + * Called from Application Thread that needs to update GUI (JMeterTreeModel) + */ + private void addDivider(final JMeterTreeModel model, final JMeterTreeNode node) { + final GenericController sc = new GenericController(); + sc.setProperty(TestElement.GUI_CLASS, LOGIC_CONTROLLER_GUI); + sc.setName("-------------------"); // $NON-NLS-1$ + JMeterUtils.runSafe(new Runnable() { + public void run() { + try { + model.addComponent(sc, node); + } catch (IllegalUserActionException e) { + log.error("Program error", e); + throw new Error(e); + } + } + }); + } + + /** + * Helper method to add a Simple Controller to contain the samplers. + * Called from Application Thread that needs to update GUI (JMeterTreeModel) + * @param model + * Test component tree model + * @param node + * Node in the tree where we will add the Controller + * @param name + * A name for the Controller + * @throws InvocationTargetException + * @throws InterruptedException + */ + private void addSimpleController(final JMeterTreeModel model, final JMeterTreeNode node, String name) + throws InterruptedException, InvocationTargetException { + final GenericController sc = new GenericController(); + sc.setProperty(TestElement.GUI_CLASS, LOGIC_CONTROLLER_GUI); + sc.setName(name); + JMeterUtils.runSafe(new Runnable() { + public void run() { + try { + model.addComponent(sc, node); + } catch (IllegalUserActionException e) { + log.error("Program error", e); + throw new Error(e); + } + } + }); + } + + /** + * Helper method to add a Transaction Controller to contain the samplers. + * Called from Application Thread that needs to update GUI (JMeterTreeModel) + * @param model + * Test component tree model + * @param node + * Node in the tree where we will add the Controller + * @param name + * A name for the Controller + * @throws InvocationTargetException + * @throws InterruptedException + */ + private void addTransactionController(final JMeterTreeModel model, final JMeterTreeNode node, String name) + throws InterruptedException, InvocationTargetException { + final TransactionController sc = new TransactionController(); + sc.setProperty(TestElement.GUI_CLASS, TRANSACTION_CONTROLLER_GUI); + sc.setName(name); + JMeterUtils.runSafe(new Runnable() { + public void run() { + try { + model.addComponent(sc, node); + } catch (IllegalUserActionException e) { + log.error("Program error", e); + throw new Error(e); + } + } + }); + } + /** + * Helpler method to replicate any timers found within the Proxy Controller + * into the provided sampler, while replacing any occurences of string _T_ + * in the timer's configuration with the provided deltaT. + * Called from AWT Event thread + * @param model + * Test component tree model + * @param node + * Sampler node in where we will add the timers + * @param deltaT + * Time interval from the previous request + */ + private void addTimers(JMeterTreeModel model, JMeterTreeNode node, long deltaT) { + TestPlan variables = new TestPlan(); + variables.addParameter("T", Long.toString(deltaT)); // $NON-NLS-1$ + ValueReplacer replacer = new ValueReplacer(variables); + JMeterTreeNode mySelf = model.getNodeOf(this); + Enumeration children = mySelf.children(); + while (children.hasMoreElements()) { + JMeterTreeNode templateNode = children.nextElement(); + if (templateNode.isEnabled()) { + TestElement template = templateNode.getTestElement(); + if (template instanceof Timer) { + TestElement timer = (TestElement) template.clone(); + try { + replacer.undoReverseReplace(timer); + model.addComponent(timer, node); + } catch (InvalidVariableException e) { + // Not 100% sure, but I believe this can't happen, so + // I'll log and throw an error: + log.error("Program error", e); + throw new Error(e); + } catch (IllegalUserActionException e) { + // Not 100% sure, but I believe this can't happen, so + // I'll log and throw an error: + log.error("Program error", e); + throw new Error(e); + } + } + } + } + } + + /** + * Finds the first enabled node of a given type in the tree. + * + * @param type + * class of the node to be found + * + * @return the first node of the given type in the test component tree, or + * null if none was found. + */ + private JMeterTreeNode findFirstNodeOfType(Class type) { + JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + List nodes = treeModel.getNodesOfType(type); + for (JMeterTreeNode node : nodes) { + if (node.isEnabled()) { + return node; + } + } + return null; + } + + /** + * Finds the controller where samplers have to be stored, that is: + *
    + *
  • The controller specified by the target property. + *
  • If none was specified, the first RecordingController in the tree. + *
  • If none is found, the first AbstractThreadGroup in the tree. + *
  • If none is found, the Workspace. + *
+ * + * @return the tree node for the controller where the proxy must store the + * generated samplers. + */ + private JMeterTreeNode findTargetControllerNode() { + JMeterTreeNode myTarget = getTarget(); + if (myTarget != null) { + return myTarget; + } + myTarget = findFirstNodeOfType(RecordingController.class); + if (myTarget != null) { + return myTarget; + } + myTarget = findFirstNodeOfType(AbstractThreadGroup.class); + if (myTarget != null) { + return myTarget; + } + myTarget = findFirstNodeOfType(WorkBench.class); + if (myTarget != null) { + return myTarget; + } + log.error("Program error: proxy recording target not found."); + return null; + } + + /** + * Finds all configuration objects of the given class applicable to the + * recorded samplers, that is: + *
    + *
  • All such elements directly within the HTTP Proxy Server (these have + * the highest priority). + *
  • All such elements directly within the target controller (higher + * priority) or directly within any containing controller (lower priority), + * including the Test Plan itself (lowest priority). + *
+ * + * @param myTarget + * tree node for the recording target controller. + * @param myClass + * Class of the elements to be found. + * @param ascending + * true if returned elements should be ordered in ascending + * priority, false if they should be in descending priority. + * + * @return a collection of applicable objects of the given class. + */ + // TODO - could be converted to generic class? + private Collection findApplicableElements(JMeterTreeNode myTarget, Class myClass, boolean ascending) { + JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + LinkedList elements = new LinkedList(); + + // Look for elements directly within the HTTP proxy: + Enumeration kids = treeModel.getNodeOf(this).children(); + while (kids.hasMoreElements()) { + JMeterTreeNode subNode = (JMeterTreeNode) kids.nextElement(); + if (subNode.isEnabled()) { + TestElement element = (TestElement) subNode.getUserObject(); + if (myClass.isInstance(element)) { + if (ascending) { + elements.addFirst(element); + } else { + elements.add(element); + } + } + } + } + + // Look for arguments elements in the target controller or higher up: + for (JMeterTreeNode controller = myTarget; controller != null; controller = (JMeterTreeNode) controller + .getParent()) { + kids = controller.children(); + while (kids.hasMoreElements()) { + JMeterTreeNode subNode = (JMeterTreeNode) kids.nextElement(); + if (subNode.isEnabled()) { + TestElement element = (TestElement) subNode.getUserObject(); + if (myClass.isInstance(element)) { + log.debug("Applicable: " + element.getName()); + if (ascending) { + elements.addFirst(element); + } else { + elements.add(element); + } + } + + // Special case for the TestPlan's Arguments sub-element: + if (element instanceof TestPlan) { + TestPlan tp = (TestPlan) element; + Arguments args = tp.getArguments(); + if (myClass.isInstance(args)) { + if (ascending) { + elements.addFirst(args); + } else { + elements.add(args); + } + } + } + } + } + } + + return elements; + } + + private void placeSampler(final HTTPSamplerBase sampler, final TestElement[] subConfigs, + JMeterTreeNode myTarget) { + try { + final JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + + boolean firstInBatch = false; + long now = System.currentTimeMillis(); + long deltaT = now - lastTime; + int cachedGroupingMode = groupingMode.get(); + if (deltaT > sampleGap) { + if (!myTarget.isLeaf() && cachedGroupingMode == GROUPING_ADD_SEPARATORS) { + addDivider(treeModel, myTarget); + } + if (cachedGroupingMode == GROUPING_IN_SIMPLE_CONTROLLERS) { + addSimpleController(treeModel, myTarget, sampler.getName()); + } + if (cachedGroupingMode == GROUPING_IN_TRANSACTION_CONTROLLERS) { + addTransactionController(treeModel, myTarget, sampler.getName()); + } + firstInBatch = true;// Remember this was first in its batch + } + if (lastTime == 0) { + deltaT = 0; // Decent value for timers + } + lastTime = now; + + if (cachedGroupingMode == GROUPING_STORE_FIRST_ONLY) { + if (!firstInBatch) { + return; // Huh! don't store this one! + } + + // If we're not storing subsequent samplers, we'll need the + // first sampler to do all the work...: + sampler.setFollowRedirects(true); + sampler.setImageParser(true); + } + + if (cachedGroupingMode == GROUPING_IN_SIMPLE_CONTROLLERS || + cachedGroupingMode == GROUPING_IN_TRANSACTION_CONTROLLERS) { + // Find the last controller in the target to store the + // sampler there: + for (int i = myTarget.getChildCount() - 1; i >= 0; i--) { + JMeterTreeNode c = (JMeterTreeNode) myTarget.getChildAt(i); + if (c.getTestElement() instanceof GenericController) { + myTarget = c; + break; + } + } + } + final long deltaTFinal = deltaT; + final boolean firstInBatchFinal = firstInBatch; + final JMeterTreeNode myTargetFinal = myTarget; + JMeterUtils.runSafe(new Runnable() { + public void run() { + try { + final JMeterTreeNode newNode = treeModel.addComponent(sampler, myTargetFinal); + if (firstInBatchFinal) { + if (addAssertions.get()) { + addAssertion(treeModel, newNode); + } + addTimers(treeModel, newNode, deltaTFinal); + } + + for (int i = 0; subConfigs != null && i < subConfigs.length; i++) { + if (subConfigs[i] instanceof HeaderManager) { + final TestElement headerManager = subConfigs[i]; + headerManager.setProperty(TestElement.GUI_CLASS, HEADER_PANEL); + treeModel.addComponent(headerManager, newNode); + } + } + } catch (IllegalUserActionException e) { + JMeterUtils.reportErrorToUser(e.getMessage()); + } + } + }); + } catch (Exception e) { + JMeterUtils.reportErrorToUser(e.getMessage()); + } + } + + /** + * Remove from the sampler all values which match the one provided by the + * first configuration in the given collection which provides a value for + * that property. + * + * @param sampler + * Sampler to remove values from. + * @param configurations + * ConfigTestElements in descending priority. + */ + private void removeValuesFromSampler(HTTPSamplerBase sampler, Collection configurations) { + for (PropertyIterator props = sampler.propertyIterator(); props.hasNext();) { + JMeterProperty prop = props.next(); + String name = prop.getName(); + String value = prop.getStringValue(); + + // There's a few properties which are excluded from this processing: + if (name.equals(TestElement.ENABLED) || name.equals(TestElement.GUI_CLASS) || name.equals(TestElement.NAME) + || name.equals(TestElement.TEST_CLASS)) { + continue; // go on with next property. + } + + for (Iterator configs = configurations.iterator(); configs.hasNext();) { + ConfigTestElement config = configs.next(); + + String configValue = config.getPropertyAsString(name); + + if (configValue != null && configValue.length() > 0) { + if (configValue.equals(value)) { + sampler.setProperty(name, ""); // $NON-NLS-1$ + } + // Property was found in a config element. Whether or not + // it matched the value in the sampler, we're done with + // this property -- don't look at lower-priority configs: + break; + } + } + } + } + + private String generateMatchUrl(HTTPSamplerBase sampler) { + StringBuilder buf = new StringBuilder(sampler.getDomain()); + buf.append(':'); // $NON-NLS-1$ + buf.append(sampler.getPort()); + buf.append(sampler.getPath()); + if (sampler.getQueryString().length() > 0) { + buf.append('?'); // $NON-NLS-1$ + buf.append(sampler.getQueryString()); + } + return buf.toString(); + } + + private boolean matchesPatterns(String url, CollectionProperty patterns) { + PropertyIterator iter = patterns.iterator(); + while (iter.hasNext()) { + String item = iter.next().getStringValue(); + Pattern pattern = null; + try { + pattern = JMeterUtils.getPatternCache().getPattern(item, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.SINGLELINE_MASK); + if (JMeterUtils.getMatcher().matches(url, pattern)) { + return true; + } + } catch (MalformedCachePatternException e) { + log.warn("Skipped invalid pattern: " + item, e); + } + } + return false; + } + + /** + * Scan all test elements passed in for values matching the value of any of + * the variables in any of the variable-holding elements in the collection. + * + * @param sampler + * A TestElement to replace values on + * @param configs + * More TestElements to replace values on + * @param variables + * Collection of Arguments to use to do the replacement, ordered + * by ascending priority. + */ + private void replaceValues(TestElement sampler, TestElement[] configs, Collection variables) { + // Build the replacer from all the variables in the collection: + ValueReplacer replacer = new ValueReplacer(); + for (Iterator vars = variables.iterator(); vars.hasNext();) { + final Map map = vars.next().getArgumentsAsMap(); + for (Iterator vals = map.values().iterator(); vals.hasNext();){ + final Object next = vals.next(); + if ("".equals(next)) {// Drop any empty values (Bug 45199) + vals.remove(); + } + } + replacer.addVariables(map); + } + + try { + boolean cachedRegexpMatch = regexMatch.get(); + replacer.reverseReplace(sampler, cachedRegexpMatch); + for (int i = 0; i < configs.length; i++) { + if (configs[i] != null) { + replacer.reverseReplace(configs[i], cachedRegexpMatch); + } + } + } catch (InvalidVariableException e) { + log.warn("Invalid variables included for replacement into recorded " + "sample", e); + } + } + + /** + * This will notify sample listeners directly within the Proxy of the + * sampling that just occured -- so that we have a means to record the + * server's responses as we go. + * + * @param event + * sampling event to be delivered + */ + private void notifySampleListeners(SampleEvent event) { + JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + JMeterTreeNode myNode = treeModel.getNodeOf(this); + Enumeration kids = myNode.children(); + while (kids.hasMoreElements()) { + JMeterTreeNode subNode = kids.nextElement(); + if (subNode.isEnabled()) { + TestElement testElement = subNode.getTestElement(); + if (testElement instanceof SampleListener) { + ((SampleListener) testElement).sampleOccurred(event); + } + } + } + } + + /** + * This will notify test listeners directly within the Proxy that the 'test' + * (here meaning the proxy recording) has started. + */ + private void notifyTestListenersOfStart() { + JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + JMeterTreeNode myNode = treeModel.getNodeOf(this); + Enumeration kids = myNode.children(); + while (kids.hasMoreElements()) { + JMeterTreeNode subNode = kids.nextElement(); + if (subNode.isEnabled()) { + TestElement testElement = subNode.getTestElement(); + if (testElement instanceof TestListener) { + ((TestListener) testElement).testStarted(); + } + } + } + } + + /** + * This will notify test listeners directly within the Proxy that the 'test' + * (here meaning the proxy recording) has ended. + */ + private void notifyTestListenersOfEnd() { + JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); + JMeterTreeNode myNode = treeModel.getNodeOf(this); + Enumeration kids = myNode.children(); + while (kids.hasMoreElements()) { + JMeterTreeNode subNode = kids.nextElement(); + if (subNode.isEnabled()) { + TestElement testElement = subNode.getTestElement(); + if (testElement instanceof TestListener) { + ((TestListener) testElement).testEnded(); + } + } + } + } + + @Override + public boolean canRemove() { + return null == server; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java new file mode 100644 index 0000000..019589d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/SamplerCreator.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy; + +import java.util.Map; + +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; + +/** + * Factory of sampler + */ +public interface SamplerCreator { + + /** + * @return String[] array of Content types managed by Factory + */ + public String[] getManagedContentTypes(); + + /** + * Create HTTPSamplerBase + * @param request {@link HttpRequestHdr} + * @param pageEncodings Map + * @param formEncodings Map + * @return {@link HTTPSamplerBase} + */ + public HTTPSamplerBase createSampler(HttpRequestHdr request, + Map pageEncodings, Map formEncodings); + + /** + * Populate sampler from request + * @param sampler {@link HTTPSamplerBase} + * @param request {@link HttpRequestHdr} + * @param pageEncodings Map + * @param formEncodings Map + * @throws Exception + */ + public void populateSampler(HTTPSamplerBase sampler, + HttpRequestHdr request, Map pageEncodings, + Map formEncodings) + throws Exception; +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java new file mode 100644 index 0000000..ae73b3c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/SamplerCreatorFactory.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy; + +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; + +/** + * {@link SamplerCreator} factory + */ +public class SamplerCreatorFactory { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final SamplerCreator DEFAULT_SAMPLER_CREATOR = new DefaultSamplerCreator(); + + private Map samplerCreatorMap = new HashMap(); + + /** + * + */ + public SamplerCreatorFactory() { + init(); + } + + /** + * Initialize factory from classpath + */ + private void init() { + try { + List listClasses = ClassFinder.findClassesThatExtend( + JMeterUtils.getSearchPaths(), + new Class[] {SamplerCreator.class }); + for (String strClassName : listClasses) { + try { + if(log.isDebugEnabled()) { + log.debug("Loading class: "+ strClassName); + } + Class commandClass = Class.forName(strClassName); + if (!Modifier.isAbstract(commandClass.getModifiers())) { + if(log.isDebugEnabled()) { + log.debug("Instantiating: "+ commandClass.getName()); + } + SamplerCreator creator = (SamplerCreator) commandClass.newInstance(); + String[] contentTypes = creator.getManagedContentTypes(); + for (String contentType : contentTypes) { + if(log.isDebugEnabled()) { + log.debug("Registering samplerCreator "+commandClass.getName()+" for content type:"+contentType); + } + samplerCreatorMap.put(contentType, creator); + } + } + } catch (Exception e) { + log.error("Exception registering "+SamplerCreator.class.getName() + " with implementation:"+strClassName, e); + } + } + } catch (Exception e) { + log.error("Exception finding implementations of "+SamplerCreator.class, e); + } + } + + /** + * Gets {@link SamplerCreator} for content type, if none is found returns {@link DefaultSamplerCreator} + * @param request {@link HttpRequestHdr} + * @param pageEncodings Map pageEncodings + * @param formEncodings Map formEncodings + * @return SamplerCreator + */ + public SamplerCreator getSamplerCreator(HttpRequestHdr request, + Map pageEncodings, Map formEncodings) { + SamplerCreator creator = samplerCreatorMap.get(request.getContentType()); + if(creator == null) { + return DEFAULT_SAMPLER_CREATOR; + } + return creator; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/gui/ProxyControlGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/gui/ProxyControlGui.java new file mode 100644 index 0000000..e0fbb45 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/proxy/gui/ProxyControlGui.java @@ -0,0 +1,825 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.proxy.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.io.IOException; +import java.net.BindException; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.gui.LogicControllerGui; +import org.apache.jmeter.engine.util.ValueReplacer; +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.tree.JMeterTreeNode; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.gui.util.PowerTableModel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.http.proxy.ProxyControl; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class ProxyControlGui extends LogicControllerGui implements JMeterGUIComponent, ActionListener, ItemListener, + KeyListener, UnsharedComponent { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + private JTextField portField; + + /** + * Used to indicate that HTTP request headers should be captured. The + * default is to capture the HTTP request headers, which are specific to + * particular browser settings. + */ + private JCheckBox httpHeaders; + + /** + * Whether to group requests together based on inactivity separation periods -- + * and how to handle such grouping afterwards. + */ + private JComboBox groupingMode; + + /** + * Add an Assertion to the first sample of each set + */ + private JCheckBox addAssertions; + + /** + * Set/clear the Use Keep-Alive box on the samplers (default is true) + */ + private JCheckBox useKeepAlive; + + /* + * Use regexes to match the source data + */ + private JCheckBox regexMatch; + + /** + * The list of sampler type names to choose from + */ + private JComboBox samplerTypeName; + + /** + * Set/clear the Redirect automatically box on the samplers (default is false) + */ + private JCheckBox samplerRedirectAutomatically; + + /** + * Set/clear the Follow-redirects box on the samplers (default is true) + */ + private JCheckBox samplerFollowRedirects; + + /** + * Set/clear the Download images box on the samplers (default is false) + */ + private JCheckBox samplerDownloadImages; + + /* + * Spoof the client into thinking that it is communicating with http + * even if it is really https. + */ + private JCheckBox httpsSpoof; + + /* + * Only spoof the URLs that match (optional) + */ + private JTextField httpsMatch; + + /** + * Regular expression to include results based on content type + */ + private JTextField contentTypeInclude; + + /** + * Regular expression to exclude results based on content type + */ + private JTextField contentTypeExclude; + + /** + * List of available target controllers + */ + private JComboBox targetNodes; + + private DefaultComboBoxModel targetNodesModel; + + private ProxyControl model; + + private JTable excludeTable; + + private PowerTableModel excludeModel; + + private JTable includeTable; + + private PowerTableModel includeModel; + + private static final String CHANGE_TARGET = "change_target"; // $NON-NLS-1$ + + private JButton stop, start, restart; + + //+ action names + private static final String STOP = "stop"; // $NON-NLS-1$ + + private static final String START = "start"; // $NON-NLS-1$ + + private static final String RESTART = "restart"; // $NON-NLS-1$ + + // This is applied to fields that should cause a restart when changed + private static final String ENABLE_RESTART = "enable_restart"; // $NON-NLS-1$ + + private static final String ADD_INCLUDE = "add_include"; // $NON-NLS-1$ + + private static final String ADD_EXCLUDE = "add_exclude"; // $NON-NLS-1$ + + private static final String DELETE_INCLUDE = "delete_include"; // $NON-NLS-1$ + + private static final String DELETE_EXCLUDE = "delete_exclude"; // $NON-NLS-1$ + //- action names + + // Resource names for column headers + private static final String INCLUDE_COL = "patterns_to_include"; // $NON-NLS-1$ + + private static final String EXCLUDE_COL = "patterns_to_exclude"; // $NON-NLS-1$ + + // Used by itemListener + private static final String PORTFIELD = "portField"; // $NON-NLS-1$ + + public ProxyControlGui() { + super(); + log.debug("Creating ProxyControlGui"); + init(); + } + + /** {@inheritDoc} */ + @Override + public TestElement createTestElement() { + model = makeProxyControl(); + log.debug("creating/configuring model = " + model); + modifyTestElement(model); + return model; + } + + protected ProxyControl makeProxyControl() { + ProxyControl local = new ProxyControl(); + return local; + } + + /** {@inheritDoc} */ + @Override + public void modifyTestElement(TestElement el) { + if (excludeTable.isEditing()) {// Bug 42948 + excludeTable.getCellEditor().stopCellEditing(); + } + if (includeTable.isEditing()) {// Bug 42948 + includeTable.getCellEditor().stopCellEditing(); + } + configureTestElement(el); + if (el instanceof ProxyControl) { + model = (ProxyControl) el; + model.setPort(portField.getText()); + setIncludeListInProxyControl(model); + setExcludeListInProxyControl(model); + model.setCaptureHttpHeaders(httpHeaders.isSelected()); + model.setGroupingMode(groupingMode.getSelectedIndex()); + model.setAssertions(addAssertions.isSelected()); + model.setSamplerTypeName(samplerTypeName.getSelectedIndex()); + model.setSamplerRedirectAutomatically(samplerRedirectAutomatically.isSelected()); + model.setSamplerFollowRedirects(samplerFollowRedirects.isSelected()); + model.setUseKeepAlive(useKeepAlive.isSelected()); + model.setSamplerDownloadImages(samplerDownloadImages.isSelected()); + model.setRegexMatch(regexMatch.isSelected()); + model.setHttpsSpoof(httpsSpoof.isSelected()); + model.setHttpsSpoofMatch(httpsMatch.getText()); + model.setContentTypeInclude(contentTypeInclude.getText()); + model.setContentTypeExclude(contentTypeExclude.getText()); + TreeNodeWrapper nw = (TreeNodeWrapper) targetNodes.getSelectedItem(); + if (nw == null) { + model.setTarget(null); + } else { + model.setTarget(nw.getTreeNode()); + } + } + } + + protected void setIncludeListInProxyControl(ProxyControl element) { + List includeList = getDataList(includeModel, INCLUDE_COL); + element.setIncludeList(includeList); + } + + protected void setExcludeListInProxyControl(ProxyControl element) { + List excludeList = getDataList(excludeModel, EXCLUDE_COL); + element.setExcludeList(excludeList); + } + + private List getDataList(PowerTableModel p_model, String colName) { + String[] dataArray = p_model.getData().getColumn(colName); + List list = new LinkedList(); + for (int i = 0; i < dataArray.length; i++) { + list.add(dataArray[i]); + } + return list; + } + + /** {@inheritDoc} */ + @Override + public String getLabelResource() { + return "proxy_title"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.NON_TEST_ELEMENTS }); + } + + /** {@inheritDoc} */ + @Override + public void configure(TestElement element) { + log.debug("Configuring gui with " + element); + super.configure(element); + model = (ProxyControl) element; + portField.setText(model.getPortString()); + httpHeaders.setSelected(model.getCaptureHttpHeaders()); + groupingMode.setSelectedIndex(model.getGroupingMode()); + addAssertions.setSelected(model.getAssertions()); + samplerTypeName.setSelectedItem(model.getSamplerTypeName()); + samplerRedirectAutomatically.setSelected(model.getSamplerRedirectAutomatically()); + samplerFollowRedirects.setSelected(model.getSamplerFollowRedirects()); + useKeepAlive.setSelected(model.getUseKeepalive()); + samplerDownloadImages.setSelected(model.getSamplerDownloadImages()); + regexMatch.setSelected(model.getRegexMatch()); + httpsSpoof.setSelected(model.getHttpsSpoof()); + httpsMatch.setText(model.getHttpsSpoofMatch()); + httpsMatch.setEnabled(httpsSpoof.isSelected()); // Only valid if Spoof is selected + contentTypeInclude.setText(model.getContentTypeInclude()); + contentTypeExclude.setText(model.getContentTypeExclude()); + + reinitializeTargetCombo();// Set up list of potential targets and + // enable listener + + populateTable(includeModel, model.getIncludePatterns().iterator()); + populateTable(excludeModel, model.getExcludePatterns().iterator()); + repaint(); + } + + private void populateTable(PowerTableModel p_model, PropertyIterator iter) { + p_model.clearData(); + while (iter.hasNext()) { + p_model.addRow(new Object[] { iter.next().getStringValue() }); + } + p_model.fireTableDataChanged(); + } + + /* + * Handles groupingMode. actionPerfomed is not suitable, as that seems to be + * activated whenever the Proxy is selected in the Test Plan + * Also handles samplerTypeName + */ + /** {@inheritDoc} */ + public void itemStateChanged(ItemEvent e) { + // System.err.println(e.paramString()); + enableRestart(); + } + + /** {@inheritDoc} */ + public void actionPerformed(ActionEvent action) { + String command = action.getActionCommand(); + + // System.err.println(action.paramString()+" "+command+ " + // "+action.getModifiers()); + + if (command.equals(STOP)) { + model.stopProxy(); + stop.setEnabled(false); + start.setEnabled(true); + restart.setEnabled(false); + } else if (command.equals(START)) { + startProxy(); + } else if (command.equals(RESTART)) { + model.stopProxy(); + startProxy(); + } else if (command.equals(ENABLE_RESTART)){ + enableRestart(); + httpsMatch.setEnabled(httpsSpoof.isSelected()); // Only valid if Spoof is selected + } else if (command.equals(ADD_EXCLUDE)) { + excludeModel.addNewRow(); + excludeModel.fireTableDataChanged(); + enableRestart(); + } else if (command.equals(ADD_INCLUDE)) { + includeModel.addNewRow(); + includeModel.fireTableDataChanged(); + enableRestart(); + } else if (command.equals(DELETE_EXCLUDE)) { + excludeModel.removeRow(excludeTable.getSelectedRow()); + excludeModel.fireTableDataChanged(); + enableRestart(); + } else if (command.equals(DELETE_INCLUDE)) { + includeModel.removeRow(includeTable.getSelectedRow()); + includeModel.fireTableDataChanged(); + enableRestart(); + } else if (command.equals(CHANGE_TARGET)) { + log.debug("Change target " + targetNodes.getSelectedItem()); + log.debug("In model " + model); + TreeNodeWrapper nw = (TreeNodeWrapper) targetNodes.getSelectedItem(); + model.setTarget(nw.getTreeNode()); + enableRestart(); + } + } + + private void startProxy() { + ValueReplacer replacer = GuiPackage.getInstance().getReplacer(); + modifyTestElement(model); + try { + replacer.replaceValues(model); + model.startProxy(); + start.setEnabled(false); + stop.setEnabled(true); + restart.setEnabled(false); + } catch (InvalidVariableException e) { + JOptionPane.showMessageDialog(this, + JMeterUtils.getResString("invalid_variables"), // $NON-NLS-1$ + "Error", + JOptionPane.ERROR_MESSAGE); + } catch (BindException e) { + JOptionPane.showMessageDialog(this, + JMeterUtils.getResString("proxy_daemon_bind_error"), // $NON-NLS-1$ + "Error", + JOptionPane.ERROR_MESSAGE); + } catch (IOException e) { + JOptionPane.showMessageDialog(this, + JMeterUtils.getResString("proxy_daemon_error"), // $NON-NLS-1$ + "Error", + JOptionPane.ERROR_MESSAGE); + } + } + + private void enableRestart() { + if (stop.isEnabled()) { + // System.err.println("Enable Restart"); + restart.setEnabled(true); + } + } + + /** {@inheritDoc} */ + public void keyPressed(KeyEvent e) { + } + + /** {@inheritDoc} */ + public void keyTyped(KeyEvent e) { + } + + /** {@inheritDoc} */ + public void keyReleased(KeyEvent e) { + String fieldName = e.getComponent().getName(); + + if (fieldName.equals(PORTFIELD)) { + try { + Integer.parseInt(portField.getText()); + } catch (NumberFormatException nfe) { + int length = portField.getText().length(); + if (length > 0) { + JOptionPane.showMessageDialog(this, "Only digits allowed", "Invalid data", + JOptionPane.WARNING_MESSAGE); + // Drop the last character: + portField.setText(portField.getText().substring(0, length-1)); + } + } + enableRestart(); + } else if (fieldName.equals(ENABLE_RESTART)){ + enableRestart(); + } + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + Box myBox = Box.createVerticalBox(); + myBox.add(createPortPanel()); + myBox.add(Box.createVerticalStrut(5)); + myBox.add(createTestPlanContentPanel()); + myBox.add(Box.createVerticalStrut(5)); + myBox.add(createHTTPSamplerPanel()); + myBox.add(Box.createVerticalStrut(5)); + myBox.add(createContentTypePanel()); + myBox.add(Box.createVerticalStrut(5)); + mainPanel.add(myBox, BorderLayout.NORTH); + + Box includeExcludePanel = Box.createVerticalBox(); + includeExcludePanel.add(createIncludePanel()); + includeExcludePanel.add(createExcludePanel()); + mainPanel.add(includeExcludePanel, BorderLayout.CENTER); + + mainPanel.add(createControls(), BorderLayout.SOUTH); + + add(mainPanel, BorderLayout.CENTER); + } + + private JPanel createControls() { + start = new JButton(JMeterUtils.getResString("start")); // $NON-NLS-1$ + start.addActionListener(this); + start.setActionCommand(START); + start.setEnabled(true); + + stop = new JButton(JMeterUtils.getResString("stop")); // $NON-NLS-1$ + stop.addActionListener(this); + stop.setActionCommand(STOP); + stop.setEnabled(false); + + restart = new JButton(JMeterUtils.getResString("restart")); // $NON-NLS-1$ + restart.addActionListener(this); + restart.setActionCommand(RESTART); + restart.setEnabled(false); + + JPanel panel = new JPanel(); + panel.add(start); + panel.add(stop); + panel.add(restart); + return panel; + } + + private JPanel createPortPanel() { + portField = new JTextField(ProxyControl.DEFAULT_PORT_S, 5); + portField.setName(PORTFIELD); + portField.addKeyListener(this); + + JLabel label = new JLabel(JMeterUtils.getResString("port")); // $NON-NLS-1$ + label.setLabelFor(portField); + + httpsSpoof = new JCheckBox(JMeterUtils.getResString("proxy_httpsspoofing")); // $NON-NLS-1$ + httpsSpoof.setSelected(false); + httpsSpoof.addActionListener(this); + httpsSpoof.setActionCommand(ENABLE_RESTART); + + httpsMatch = new JTextField(40); + httpsMatch.addKeyListener(this); + httpsMatch.setName(ENABLE_RESTART); + httpsMatch.setEnabled(false); // Only valid if Spoof is selected + + JLabel matchlabel = new JLabel(JMeterUtils.getResString("proxy_httpsspoofing_match")); // $NON-NLS-1$ + matchlabel.setLabelFor(httpsMatch); + + + HorizontalPanel panel = new HorizontalPanel(); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("proxy_general_settings"))); // $NON-NLS-1$ + + panel.add(label); + panel.add(portField); + + panel.add(Box.createHorizontalStrut(10)); + panel.add(httpsSpoof); + + panel.add(matchlabel); + panel.add(httpsMatch); + + return panel; + } + + private JPanel createTestPlanContentPanel() { + httpHeaders = new JCheckBox(JMeterUtils.getResString("proxy_headers")); // $NON-NLS-1$ + httpHeaders.setSelected(true); // maintain original default + httpHeaders.addActionListener(this); + httpHeaders.setActionCommand(ENABLE_RESTART); + + addAssertions = new JCheckBox(JMeterUtils.getResString("proxy_assertions")); // $NON-NLS-1$ + addAssertions.setSelected(false); + addAssertions.addActionListener(this); + addAssertions.setActionCommand(ENABLE_RESTART); + + regexMatch = new JCheckBox(JMeterUtils.getResString("proxy_regex")); // $NON-NLS-1$ + regexMatch.setSelected(false); + regexMatch.addActionListener(this); + regexMatch.setActionCommand(ENABLE_RESTART); + + VerticalPanel mainPanel = new VerticalPanel(); + mainPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("proxy_test_plan_content"))); // $NON-NLS-1$ + + HorizontalPanel nodeCreationPanel = new HorizontalPanel(); + nodeCreationPanel.add(httpHeaders); + nodeCreationPanel.add(addAssertions); + nodeCreationPanel.add(regexMatch); + + HorizontalPanel targetPanel = new HorizontalPanel(); + targetPanel.add(createTargetPanel()); + targetPanel.add(createGroupingPanel()); + mainPanel.add(targetPanel); + mainPanel.add(nodeCreationPanel); + + return mainPanel; + } + + private JPanel createHTTPSamplerPanel() { + DefaultComboBoxModel m = new DefaultComboBoxModel(); + for (String s : HTTPSamplerFactory.getImplementations()){ + m.addElement(s); + } + samplerTypeName = new JComboBox(m); + samplerTypeName.setSelectedIndex(0); + samplerTypeName.addItemListener(this); + JLabel label2 = new JLabel(JMeterUtils.getResString("proxy_sampler_type")); // $NON-NLS-1$ + label2.setLabelFor(samplerTypeName); + + samplerRedirectAutomatically = new JCheckBox(JMeterUtils.getResString("follow_redirects_auto")); // $NON-NLS-1$ + samplerRedirectAutomatically.setSelected(false); + samplerRedirectAutomatically.addActionListener(this); + samplerRedirectAutomatically.setActionCommand(ENABLE_RESTART); + + samplerFollowRedirects = new JCheckBox(JMeterUtils.getResString("follow_redirects")); // $NON-NLS-1$ + samplerFollowRedirects.setSelected(true); + samplerFollowRedirects.addActionListener(this); + samplerFollowRedirects.setActionCommand(ENABLE_RESTART); + + useKeepAlive = new JCheckBox(JMeterUtils.getResString("use_keepalive")); // $NON-NLS-1$ + useKeepAlive.setSelected(true); + useKeepAlive.addActionListener(this); + useKeepAlive.setActionCommand(ENABLE_RESTART); + + samplerDownloadImages = new JCheckBox(JMeterUtils.getResString("web_testing_retrieve_images")); // $NON-NLS-1$ + samplerDownloadImages.setSelected(false); + samplerDownloadImages.addActionListener(this); + samplerDownloadImages.setActionCommand(ENABLE_RESTART); + + HorizontalPanel panel = new HorizontalPanel(); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("proxy_sampler_settings"))); // $NON-NLS-1$ + panel.add(label2); + panel.add(samplerTypeName); + panel.add(samplerRedirectAutomatically); + panel.add(samplerFollowRedirects); + panel.add(useKeepAlive); + panel.add(samplerDownloadImages); + + return panel; + } + + private JPanel createTargetPanel() { + targetNodesModel = new DefaultComboBoxModel(); + targetNodes = new JComboBox(targetNodesModel); + targetNodes.setActionCommand(CHANGE_TARGET); + // Action listener will be added later + + JLabel label = new JLabel(JMeterUtils.getResString("proxy_target")); // $NON-NLS-1$ + label.setLabelFor(targetNodes); + + HorizontalPanel panel = new HorizontalPanel(); + panel.add(label); + panel.add(targetNodes); + + return panel; + } + + private JPanel createGroupingPanel() { + DefaultComboBoxModel m = new DefaultComboBoxModel(); + // Note: position of these elements in the menu *must* match the + // corresponding ProxyControl.GROUPING_* values. + m.addElement(JMeterUtils.getResString("grouping_no_groups")); // $NON-NLS-1$ + m.addElement(JMeterUtils.getResString("grouping_add_separators")); // $NON-NLS-1$ + m.addElement(JMeterUtils.getResString("grouping_in_controllers")); // $NON-NLS-1$ + m.addElement(JMeterUtils.getResString("grouping_store_first_only")); // $NON-NLS-1$ + m.addElement(JMeterUtils.getResString("grouping_in_transaction_controllers")); // $NON-NLS-1$ + groupingMode = new JComboBox(m); + groupingMode.setSelectedIndex(0); + groupingMode.addItemListener(this); + + JLabel label2 = new JLabel(JMeterUtils.getResString("grouping_mode")); // $NON-NLS-1$ + label2.setLabelFor(groupingMode); + + HorizontalPanel panel = new HorizontalPanel(); + panel.add(label2); + panel.add(groupingMode); + + return panel; + } + + private JPanel createContentTypePanel() { + contentTypeInclude = new JTextField(35); + contentTypeInclude.addKeyListener(this); + contentTypeInclude.setName(ENABLE_RESTART); + JLabel labelInclude = new JLabel(JMeterUtils.getResString("proxy_content_type_include")); // $NON-NLS-1$ + labelInclude.setLabelFor(contentTypeInclude); + // Default value + contentTypeInclude.setText(JMeterUtils.getProperty("proxy.content_type_include")); // $NON-NLS-1$ + + contentTypeExclude = new JTextField(35); + contentTypeExclude.addKeyListener(this); + contentTypeExclude.setName(ENABLE_RESTART); + JLabel labelExclude = new JLabel(JMeterUtils.getResString("proxy_content_type_exclude")); // $NON-NLS-1$ + labelExclude.setLabelFor(contentTypeExclude); + // Default value + contentTypeExclude.setText(JMeterUtils.getProperty("proxy.content_type_exclude")); // $NON-NLS-1$ + + HorizontalPanel panel = new HorizontalPanel(); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("proxy_content_type_filter"))); // $NON-NLS-1$ + panel.add(labelInclude); + panel.add(contentTypeInclude); + panel.add(labelExclude); + panel.add(contentTypeExclude); + + return panel; + } + + private JPanel createIncludePanel() { + includeModel = new PowerTableModel(new String[] { INCLUDE_COL }, new Class[] { String.class }); + includeTable = new JTable(includeModel); + includeTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + includeTable.setPreferredScrollableViewportSize(new Dimension(100, 30)); + + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("patterns_to_include"))); // $NON-NLS-1$ + + panel.add(new JScrollPane(includeTable), BorderLayout.CENTER); + panel.add(createTableButtonPanel(ADD_INCLUDE, DELETE_INCLUDE), BorderLayout.SOUTH); + + return panel; + } + + private JPanel createExcludePanel() { + excludeModel = new PowerTableModel(new String[] { EXCLUDE_COL }, new Class[] { String.class }); + excludeTable = new JTable(excludeModel); + excludeTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + excludeTable.setPreferredScrollableViewportSize(new Dimension(100, 30)); + + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils + .getResString("patterns_to_exclude"))); // $NON-NLS-1$ + + panel.add(new JScrollPane(excludeTable), BorderLayout.CENTER); + panel.add(createTableButtonPanel(ADD_EXCLUDE, DELETE_EXCLUDE), BorderLayout.SOUTH); + + return panel; + } + + private JPanel createTableButtonPanel(String addCommand, String deleteCommand) { + JPanel buttonPanel = new JPanel(); + + JButton addButton = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ + addButton.setActionCommand(addCommand); + addButton.addActionListener(this); + buttonPanel.add(addButton); + + JButton deleteButton = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + deleteButton.setActionCommand(deleteCommand); + deleteButton.addActionListener(this); + buttonPanel.add(deleteButton); + + return buttonPanel; + } + + private void reinitializeTargetCombo() { + log.debug("Reinitializing target combo"); + + // Stop action notifications while we shuffle this around: + targetNodes.removeActionListener(this); + + targetNodesModel.removeAllElements(); + GuiPackage gp = GuiPackage.getInstance(); + JMeterTreeNode root; + if (gp != null) { + root = (JMeterTreeNode) GuiPackage.getInstance().getTreeModel().getRoot(); + targetNodesModel + .addElement(new TreeNodeWrapper(null, JMeterUtils.getResString("use_recording_controller"))); // $NON-NLS-1$ + buildNodesModel(root, "", 0); + } + TreeNodeWrapper choice = null; + for (int i = 0; i < targetNodesModel.getSize(); i++) { + choice = (TreeNodeWrapper) targetNodesModel.getElementAt(i); + log.debug("Selecting item " + choice + " for model " + model + " in " + this); + if (choice.getTreeNode() == model.getTarget()) // .equals caused + // NPE + { + break; + } + } + // Reinstate action notifications: + targetNodes.addActionListener(this); + // Set the current value: + targetNodesModel.setSelectedItem(choice); + + log.debug("Reinitialization complete"); + } + + private void buildNodesModel(JMeterTreeNode node, String parent_name, int level) { + String seperator = " > "; + if (node != null) { + for (int i = 0; i < node.getChildCount(); i++) { + StringBuilder name = new StringBuilder(); + JMeterTreeNode cur = (JMeterTreeNode) node.getChildAt(i); + TestElement te = cur.getTestElement(); + /* + * Will never be true. Probably intended to use + * org.apache.jmeter.threads.ThreadGroup rather than + * java.lang.ThreadGroup However, that does not work correctly; + * whereas treating it as a Controller does. if (te instanceof + * ThreadGroup) { name.append(parent_name); + * name.append(cur.getName()); name.append(seperator); + * buildNodesModel(cur, name.toString(), level); } else + */ + if (te instanceof Controller) { + name.append(spaces(level)); + name.append(parent_name); + name.append(cur.getName()); + TreeNodeWrapper tnw = new TreeNodeWrapper(cur, name.toString()); + targetNodesModel.addElement(tnw); + name = new StringBuilder(); + name.append(cur.getName()); + name.append(seperator); + buildNodesModel(cur, name.toString(), level + 1); + } else if (te instanceof TestPlan || te instanceof WorkBench) { + name.append(cur.getName()); + name.append(seperator); + buildNodesModel(cur, name.toString(), 0); + } + // Ignore everything else + } + } + } + + private String spaces(int level) { + int multi = 4; + StringBuilder spaces = new StringBuilder(level * multi); + for (int i = 0; i < level * multi; i++) { + spaces.append(" "); // $NON-NLS-1$ + } + return spaces.toString(); + } + +} + +class TreeNodeWrapper { + private final JMeterTreeNode tn; + + private final String label; + + public TreeNodeWrapper(JMeterTreeNode tn, String label) { + this.tn = tn; + this.label = label; + } + + public JMeterTreeNode getTreeNode() { + return tn; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return label; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AccessLogSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AccessLogSampler.java new file mode 100644 index 0000000..44c9658 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AccessLogSampler.java @@ -0,0 +1,357 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.util.accesslog.Filter; +import org.apache.jmeter.protocol.http.util.accesslog.LogParser; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestCloneable; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +/** + * Description:
+ *
+ * AccessLogSampler is responsible for a couple of things: + *

+ *

    + *
  • creating instances of Generator + *
  • creating instances of Parser + *
  • triggering popup windows + *
  • calling Generator.generateRequest() + *
  • checking to make sure the classes are valid + *
  • making sure a class can be instantiated + *
+ * The intent of this sampler is it uses the generator and parser to create a + * HTTPSampler when it is needed. It does not contain logic about how to parse + * the logs. It also doesn't care how Generator is implemented, as long as it + * implements the interface. This means a person could simply implement a dummy + * parser to generate random parameters and the generator consumes the results. + * This wasn't the original intent of the sampler. I originaly wanted to write + * this sampler, so that I can take production logs to simulate production + * traffic in a test environment. Doing so is desirable to study odd or unusual + * behavior. It's also good to compare a new system against an existing system + * to get near apples- to-apples comparison. I've been asked if benchmarks are + * really fair comparisons just about every single time, so this helps me + * accomplish that task. + *

+ * Some bugs only appear under production traffic, so it is useful to generate + * traffic using production logs. This way, JMeter can record when problems + * occur and provide a way to match the server logs. + *

+ * Created on: Jun 26, 2003 + * + */ +public class AccessLogSampler extends HTTPSampler implements TestBean,ThreadListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; // Remember to change this when the class changes ... + + public static final String DEFAULT_CLASS = "org.apache.jmeter.protocol.http.util.accesslog.TCLogParser"; // $NON-NLS-1$ + + /** private members used by class * */ + private transient LogParser PARSER = null; + + // NOTUSED private Class PARSERCLASS = null; + private String logFile, parserClassName, filterClassName; + + private transient Filter filter; + + private int count = 0; + + private boolean started = false; + + /** + * Set the path where XML messages are stored for random selection. + */ + public void setLogFile(String path) { + logFile = path; + } + + /** + * Get the path where XML messages are stored. this is the directory where + * JMeter will randomly select a file. + */ + public String getLogFile() { + return logFile; + } + + /** + * it's kinda obvious, but we state it anyways. Set the xml file with a + * string path. + * + * @param classname - + * parser class name + */ + public void setParserClassName(String classname) { + parserClassName = classname; + } + + /** + * Get the file location of the xml file. + * + * @return String file path. + */ + public String getParserClassName() { + return parserClassName; + } + + /** + * sample gets a new HTTPSampler from the generator and calls it's sample() + * method. + */ + public SampleResult sampleWithParser() { + initFilter(); + instantiateParser(); + SampleResult res = null; + try { + + if (PARSER == null) { + throw new JMeterException("No Parser available"); + } + /* + * samp.setDomain(this.getDomain()); samp.setPort(this.getPort()); + */ + // we call parse with 1 to get only one. + // this also means if we change the implementation + // to use 2, it would use every other entry and + // so on. Not that it is really useful, but a + // person could use it that way if they have a + // huge gigabyte log file and they only want to + // use a quarter of the entries. + int thisCount = PARSER.parseAndConfigure(1, this); + if (thisCount < 0) // Was there an error? + { + return errorResult(new Error("Problem parsing the log file"), new HTTPSampleResult()); + } + if (thisCount == 0) { + if (count == 0 || filter == null) { + log.info("Stopping current thread"); + JMeterContextService.getContext().getThread().stop(); + } + if (filter != null) { + filter.reset(); + } + CookieManager cm = getCookieManager(); + if (cm != null) { + cm.clear(); + } + count = 0; + return errorResult(new Error("No entries found"), new HTTPSampleResult()); + } + count = thisCount; + res = sample(); + res.setSampleLabel(toString()); + } catch (Exception e) { + log.warn("Sampling failure", e); + return errorResult(e, new HTTPSampleResult()); + } + return res; + } + + /** + * sample(Entry e) simply calls sample(). + * + * @param e - + * ignored + * @return the new sample + */ + @Override + public SampleResult sample(Entry e) { + return sampleWithParser(); + } + + /** + * Method will instantiate the log parser based on the class in the text + * field. This was done to make it easier for people to plugin their own log + * parser and use different log parser. + */ + public void instantiateParser() { + if (PARSER == null) { + try { + if (this.getParserClassName() != null && this.getParserClassName().length() > 0) { + if (this.getLogFile() != null && this.getLogFile().length() > 0) { + PARSER = (LogParser) Class.forName(getParserClassName()).newInstance(); + PARSER.setSourceFile(this.getLogFile()); + PARSER.setFilter(filter); + } else { + log.error("No log file specified"); + } + } + } catch (InstantiationException e) { + log.error("", e); + } catch (IllegalAccessException e) { + log.error("", e); + } catch (ClassNotFoundException e) { + log.error("", e); + } + } + } + + /** + * @return Returns the filterClassName. + */ + public String getFilterClassName() { + return filterClassName; + } + + /** + * @param filterClassName + * The filterClassName to set. + */ + public void setFilterClassName(String filterClassName) { + this.filterClassName = filterClassName; + } + + /** + * @return Returns the domain. + */ + @Override + public String getDomain() { // N.B. Must be in this class for the TestBean code to work + return super.getDomain(); + } + + /** + * @param domain + * The domain to set. + */ + @Override + public void setDomain(String domain) { // N.B. Must be in this class for the TestBean code to work + super.setDomain(domain); + } + + /** + * @return Returns the imageParsing. + */ + public boolean isImageParsing() { + return super.isImageParser(); + } + + /** + * @param imageParsing + * The imageParsing to set. + */ + public void setImageParsing(boolean imageParsing) { + super.setImageParser(imageParsing); + } + + /** + * @return Returns the port. + */ + public String getPortString() { + return super.getPropertyAsString(HTTPSamplerBase.PORT); + } + + /** + * @param port + * The port to set. + */ + public void setPortString(String port) { + super.setProperty(HTTPSamplerBase.PORT, port); + } + + /** + * + */ + public AccessLogSampler() { + super(); + } + + protected void initFilter() { + if (filter == null && filterClassName != null && filterClassName.length() > 0) { + try { + filter = (Filter) Class.forName(filterClassName).newInstance(); + } catch (Exception e) { + log.warn("Couldn't instantiate filter '" + filterClassName + "'", e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object clone() { + AccessLogSampler s = (AccessLogSampler) super.clone(); + if (started) { + if (filterClassName != null && filterClassName.length() > 0) { + + try { + if (TestCloneable.class.isAssignableFrom(Class.forName(filterClassName))) { + initFilter(); + s.filter = (Filter) ((TestCloneable) filter).clone(); + } + if(TestCloneable.class.isAssignableFrom(Class.forName(parserClassName))) + { + instantiateParser(); + s.PARSER = (LogParser)((TestCloneable)PARSER).clone(); + if(filter != null) + { + s.PARSER.setFilter(s.filter); + } + } + } catch (Exception e) { + log.warn("Could not clone cloneable filter", e); + } + } + } + return s; + } + + /** + * {@inheritDoc} + */ + @Override + public void testEnded() { + if (PARSER != null) { + PARSER.close(); + } + filter = null; + started = false; + super.testEnded(); + } + + /** + * {@inheritDoc} + */ + @Override + public void testStarted() { + started = true; + super.testStarted(); + } + + /** + * {@inheritDoc} + */ + @Override + public void threadFinished() { + if(PARSER instanceof ThreadListener) { + ((ThreadListener)PARSER).threadFinished(); + } + if(filter instanceof ThreadListener) { + ((ThreadListener)filter).threadFinished(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java new file mode 100644 index 0000000..cb8c5a0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerBeanInfo.java @@ -0,0 +1,99 @@ +/* + * 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. + * + */ + +/* + * Created on May 24, 2004 + * + */ +package org.apache.jmeter.protocol.http.sampler; + +import java.beans.PropertyDescriptor; +import java.io.IOException; +import java.util.List; + +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.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; + +public class AccessLogSamplerBeanInfo extends BeanInfoSupport { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public AccessLogSamplerBeanInfo() { + super(AccessLogSampler.class); + log.debug("Entered access log sampler bean info"); + try { + createPropertyGroup("defaults", // $NON-NLS-1$ + new String[] { "domain", "portString", "imageParsing" });// $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ + + 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); + final List logParserClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { LogParser.class }); + if (log.isDebugEnabled()) { + log.debug("found parsers: " + logParserClasses); + } + p.setValue(TAGS, logParserClasses.toArray(new String[logParserClasses.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("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"); + } + log.debug("Got to end of access log samper bean info init"); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AjpSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AjpSampler.java new file mode 100644 index 0000000..f044183 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/AjpSampler.java @@ -0,0 +1,520 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jmeter.protocol.http.sampler; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.URL; + +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.Cookie; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Selector for the AJP/1.3 protocol + * (i.e. what Tomcat uses with mod_jk) + * It allows you to test Tomcat in AJP mode without + * actually having Apache installed and configured + * + */ +public class AjpSampler extends HTTPSamplerBase implements Interruptible { + + private static final long serialVersionUID = 233L; + + private static final Logger log= LoggingManager.getLoggerForClass(); + + private static final char NEWLINE = '\n'; + private static final String COLON_SPACE = ": ";//$NON-NLS-1$ + + /** + * Translates integer codes to request header names + */ + private static final String []headerTransArray = { + "accept", //$NON-NLS-1$ + "accept-charset", //$NON-NLS-1$ + "accept-encoding", //$NON-NLS-1$ + "accept-language", //$NON-NLS-1$ + "authorization", //$NON-NLS-1$ + "connection", //$NON-NLS-1$ + "content-type", //$NON-NLS-1$ + "content-length", //$NON-NLS-1$ + "cookie", //$NON-NLS-1$ + "cookie2", //$NON-NLS-1$ + "host", //$NON-NLS-1$ + "pragma", //$NON-NLS-1$ + "referer", //$NON-NLS-1$ + "user-agent" //$NON-NLS-1$ + }; + + /** + * Base value for translated headers + */ + static final int AJP_HEADER_BASE = 0xA000; + + static final int MAX_SEND_SIZE = 8*1024 - 4 - 4; + + private transient Socket channel = null; + private transient Socket activeChannel = null; + private int lastPort = -1; + private String lastHost = null; + private String localName = null; + private String localAddress = null; + private byte [] inbuf = new byte[8*1024]; + private byte [] outbuf = new byte[8*1024]; + private transient ByteArrayOutputStream responseData = new ByteArrayOutputStream(); + private int inpos = 0; + private int outpos = 0; + private transient String stringBody = null; + private transient InputStream body = null; + + public AjpSampler() { + } + + @Override + protected HTTPSampleResult sample(URL url, + String method, + boolean frd, + int fd) { + HTTPSampleResult res = new HTTPSampleResult(); + res.setMonitor(false); + res.setSampleLabel(url.toExternalForm()); + res.sampleStart(); + try { + setupConnection(url, method, res); + activeChannel = channel; + execute(method, res); + res.sampleEnd(); + res.setResponseData(responseData.toByteArray()); + return res; + } catch(IOException iex) { + res.sampleEnd(); + lastPort = -1; // force reopen on next sample + channel = null; + return errorResult(iex, res); + } finally { + activeChannel = null; + } + } + + @Override + public void threadFinished() { + if(channel != null) { + try { + channel.close(); + } catch(IOException iex) { + log.debug("Error closing channel",iex); + } + } + channel = null; + body = null; + stringBody = null; + } + + private void setupConnection(URL url, + String method, + HTTPSampleResult res) throws IOException { + + String host = url.getHost(); + int port = url.getPort(); + if(port <= 0 || port == url.getDefaultPort()) { + port = 8009; + } + String scheme = url.getProtocol(); + if(channel == null || !host.equals(lastHost) || port != lastPort) { + if(channel != null) { + channel.close(); + } + channel = new Socket(host, port); + int timeout = JMeterUtils.getPropDefault("httpclient.timeout",0);//$NON-NLS-1$ + if(timeout > 0) { + channel.setSoTimeout(timeout); + } + localAddress = channel.getLocalAddress().getHostAddress(); + localName = channel.getLocalAddress().getHostName(); + lastHost = host; + lastPort = port; + } + res.setURL(url); + res.setHTTPMethod(method); + outpos = 4; + setByte((byte)2); + if(method.equals(POST)) { + setByte((byte)4); + } else { + setByte((byte)2); + } + if(JMeterUtils.getPropDefault("httpclient.version","1.1").equals("1.0")) {//$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + setString("HTTP/1.0");//$NON-NLS-1$ + } else { + setString(HTTP_1_1); + } + setString(url.getPath()); + setString(localAddress); + setString(localName); + setString(host); + setInt(url.getDefaultPort()); + setByte(PROTOCOL_HTTPS.equalsIgnoreCase(scheme) ? (byte)1 : (byte)0); + setInt(getHeaderSize(method, url)); + String hdr = setConnectionHeaders(url, host, method); + res.setRequestHeaders(hdr); + res.setCookies(setConnectionCookies(url, getCookieManager())); + String query = url.getQuery(); + if (query != null) { + setByte((byte)0x05); // Marker for query string attribute + setString(query); + } + setByte((byte)0xff); // More general attributes not supported + } + + private int getHeaderSize(String method, URL url) { + HeaderManager headers = getHeaderManager(); + CookieManager cookies = getCookieManager(); + AuthManager auth = getAuthManager(); + int hsz = 1; // Host always + if(method.equals(POST)) { + HTTPFileArg[] hfa = getHTTPFiles(); + if(hfa.length > 0) { + hsz += 3; + } else { + hsz += 2; + } + } + if(headers != null) { + hsz += headers.size(); + } + if(cookies != null) { + hsz += cookies.getCookieCount(); + } + if(auth != null) { + String authHeader = auth.getAuthHeaderForURL(url); + if(authHeader != null) { + ++hsz; + } + } + return hsz; + } + + + private String setConnectionHeaders(URL url, String host, String method) + throws IOException { + HeaderManager headers = getHeaderManager(); + AuthManager auth = getAuthManager(); + StringBuilder hbuf = new StringBuilder(); + // Allow Headers to override Host setting + hbuf.append("Host").append(COLON_SPACE).append(host).append(NEWLINE);//$NON-NLS-1$ + setInt(0xA00b); //Host + setString(host); + if(headers != null) { + CollectionProperty coll = headers.getHeaders(); + PropertyIterator i = coll.iterator(); + while(i.hasNext()) { + Header header = (Header)i.next().getObjectValue(); + String n = header.getName(); + String v = header.getValue(); + hbuf.append(n).append(COLON_SPACE).append(v).append(NEWLINE); + int hc = translateHeader(n); + if(hc > 0) { + setInt(hc+AJP_HEADER_BASE); + } else { + setString(n); + } + setString(v); + } + } + if(method.equals(POST)) { + int cl = -1; + HTTPFileArg[] hfa = getHTTPFiles(); + if(hfa.length > 0) { + HTTPFileArg fa = hfa[0]; + String fn = fa.getName(); + File input = new File(fn); + cl = (int)input.length(); + body = new FileInputStream(input); + setString(HEADER_CONTENT_DISPOSITION); + setString("form-data; name=\""+encode(fa.getParamName())+ + "\"; filename=\"" + encode(fn) +"\""); //$NON-NLS-1$ //$NON-NLS-2$ + String mt = fa.getMimeType(); + hbuf.append(HEADER_CONTENT_TYPE).append(COLON_SPACE).append(mt).append(NEWLINE); + setInt(0xA007); // content-type + setString(mt); + } else { + hbuf.append(HEADER_CONTENT_TYPE).append(COLON_SPACE).append(APPLICATION_X_WWW_FORM_URLENCODED).append(NEWLINE); + setInt(0xA007); // content-type + setString(APPLICATION_X_WWW_FORM_URLENCODED); + StringBuilder sb = new StringBuilder(); + boolean first = true; + PropertyIterator args = getArguments().iterator(); + while(args.hasNext()) { + JMeterProperty arg = args.next(); + if(first) { + first = false; + } else { + sb.append('&'); + } + sb.append(arg.getStringValue()); + } + stringBody = sb.toString(); + byte [] sbody = stringBody.getBytes(); // TODO - charset? + cl = sbody.length; + body = new ByteArrayInputStream(sbody); + } + hbuf.append(HEADER_CONTENT_LENGTH).append(COLON_SPACE).append(String.valueOf(cl)).append(NEWLINE); + setInt(0xA008); // Content-length + setString(String.valueOf(cl)); + } + if(auth != null) { + String authHeader = auth.getAuthHeaderForURL(url); + if(authHeader != null) { + setInt(0xA005); // Authorization + setString(authHeader); + hbuf.append(HEADER_AUTHORIZATION).append(COLON_SPACE).append(authHeader).append(NEWLINE); + } + } + return hbuf.toString(); + } + + private String encode(String value) { + StringBuilder newValue = new StringBuilder(); + char[] chars = value.toCharArray(); + for (int i = 0; i < chars.length; i++) + { + if (chars[i] == '\\')//$NON-NLS-1$ + { + newValue.append("\\\\");//$NON-NLS-1$ + } + else + { + newValue.append(chars[i]); + } + } + return newValue.toString(); + } + + private String setConnectionCookies(URL url, CookieManager cookies) { + String cookieHeader = null; + if(cookies != null) { + cookieHeader = cookies.getCookieHeaderForURL(url); + CollectionProperty coll = cookies.getCookies(); + PropertyIterator i = coll.iterator(); + while(i.hasNext()) { + Cookie cookie = (Cookie)(i.next().getObjectValue()); + setInt(0xA009); // Cookie + setString(cookie.getName()+"="+cookie.getValue());//$NON-NLS-1$ + } + } + return cookieHeader; + } + + private int translateHeader(String n) { + for(int i=0; i < headerTransArray.length; i++) { + if(headerTransArray[i].equalsIgnoreCase(n)) { + return i+1; + } + } + return -1; + } + + private void setByte(byte b) { + outbuf[outpos++] = b; + } + + private void setInt(int n) { + outbuf[outpos++] = (byte)((n >> 8)&0xff); + outbuf[outpos++] = (byte) (n&0xff); + } + + private void setString(String s) { + if( s == null ) { + setInt(0xFFFF); + } else { + int len = s.length(); + setInt(len); + for(int i=0; i < len; i++) { + setByte((byte)s.charAt(i)); + } + setByte((byte)0); + } + } + + private void send() throws IOException { + OutputStream os = channel.getOutputStream(); + int len = outpos; + outpos = 0; + setInt(0x1234); + setInt(len-4); + os.write(outbuf, 0, len); + } + + private void execute(String method, HTTPSampleResult res) + throws IOException { + send(); + if(method.equals(POST)) { + res.setQueryString(stringBody); + sendPostBody(); + } + handshake(res); + } + + private void handshake(HTTPSampleResult res) throws IOException { + responseData.reset(); + int msg = getMessage(); + while(msg != 5) { + if(msg == 3) { + int len = getInt(); + responseData.write(inbuf, inpos, len); + } else if(msg == 4) { + parseHeaders(res); + } else if(msg == 6) { + setNextBodyChunk(); + send(); + } + msg = getMessage(); + } + } + + + private void sendPostBody() throws IOException { + setNextBodyChunk(); + send(); + } + + private void setNextBodyChunk() throws IOException { + int len = body.available(); + if(len < 0) { + len = 0; + } else if(len > MAX_SEND_SIZE) { + len = MAX_SEND_SIZE; + } + outpos = 4; + int nr = 0; + if(len > 0) { + nr = body.read(outbuf, outpos+2, len); + } + setInt(nr); + outpos += nr; + } + + + private void parseHeaders(HTTPSampleResult res) + throws IOException { + int status = getInt(); + res.setResponseCode(Integer.toString(status)); + res.setSuccessful(200 <= status && status <= 399); + String msg = getString(); + res.setResponseMessage(msg); + int nh = getInt(); + StringBuilder sb = new StringBuilder(); + sb.append(HTTP_1_1 ).append(status).append(" ").append(msg).append(NEWLINE);//$NON-NLS-1$//$NON-NLS-2$ + for(int i=0; i < nh; i++) { + String name; + int thn = peekInt(); + if((thn & 0xff00) == AJP_HEADER_BASE) { + name = headerTransArray[(thn&0xff)-1]; + getInt(); // we need to use up the int now + } else { + name = getString(); + } + String value = getString(); + if(HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) { + res.setContentType(value); + res.setEncodingAndType(value); + } else if(HEADER_SET_COOKIE.equalsIgnoreCase(name)) { + CookieManager cookies = getCookieManager(); + if(cookies != null) { + cookies.addCookieFromHeader(value, res.getURL()); + } + } + sb.append(name).append(COLON_SPACE).append(value).append(NEWLINE); + } + res.setResponseHeaders(sb.toString()); + } + + + private int getMessage() throws IOException { + InputStream is = channel.getInputStream(); + inpos = 0; + int nr = is.read(inbuf, inpos, 4); + if(nr != 4) { + channel.close(); + channel = null; + throw new IOException("Connection Closed: "+nr); + } + //int mark = + getInt(); + int len = getInt(); + int toRead = len; + int cpos = inpos; + while(toRead > 0) { + nr = is.read(inbuf, cpos, toRead); + cpos += nr; + toRead -= nr; + } + return getByte(); + } + + private byte getByte() { + return inbuf[inpos++]; + } + + private int getInt() { + int res = (inbuf[inpos++]<<8)&0xff00; + res += inbuf[inpos++]&0xff; + return res; + } + + private int peekInt() { + int res = (inbuf[inpos]<<8)&0xff00; + res += inbuf[inpos+1]&0xff; + return res; + } + + private String getString() throws IOException { + int len = getInt(); + String s = new String(inbuf, inpos, len, "iso-8859-1");//$NON-NLS-1$ + inpos+= len+1; + return s; + } + + public boolean interrupt() { + Socket chan = activeChannel; + if (chan != null) { + activeChannel = null; + try { + chan.close(); + } catch (Exception e) { + // Ignored + } + } + return chan != null; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPAbstractImpl.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPAbstractImpl.java new file mode 100644 index 0000000..a6e66d3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPAbstractImpl.java @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.HTTPConstantsInterface; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; + +/** + * Base class for HTTP implementations used by the HTTPSamplerProxy sampler. + */ +public abstract class HTTPAbstractImpl implements Interruptible, HTTPConstantsInterface { + + protected final HTTPSamplerBase testElement; + + protected HTTPAbstractImpl(HTTPSamplerBase testElement){ + this.testElement = testElement; + } + + protected abstract HTTPSampleResult sample(URL url, String method, boolean areFollowingRedirect, int frameDepth); + + // Allows HTTPSamplerProxy to call threadFinished; subclasses can override if necessary + protected void threadFinished() { + } + + // Provide access to HTTPSamplerBase methods + + /** + * Invokes {@link HTTPSamplerBase#errorResult(Throwable, HTTPSampleResult)} + */ + protected HTTPSampleResult errorResult(Throwable t, HTTPSampleResult res) { + return testElement.errorResult(t, res); + } + + /** + * Invokes {@link HTTPSamplerBase#getArguments()} + */ + protected Arguments getArguments() { + return testElement.getArguments(); + } + + /** + * Invokes {@link HTTPSamplerBase#getAuthManager()} + */ + protected AuthManager getAuthManager() { + return testElement.getAuthManager(); + } + + /** + * Invokes {@link HTTPSamplerBase#getAutoRedirects()} + */ + protected boolean getAutoRedirects() { + return testElement.getAutoRedirects(); + } + + /** + * Invokes {@link HTTPSamplerBase#getCacheManager()} + */ + protected CacheManager getCacheManager() { + return testElement.getCacheManager(); + } + + /** + * Invokes {@link HTTPSamplerBase#getConnectTimeout()} + */ + protected int getConnectTimeout() { + return testElement.getConnectTimeout(); + } + + /** + * Invokes {@link HTTPSamplerBase#getContentEncoding()} + */ + protected String getContentEncoding() { + return testElement.getContentEncoding(); + } + + /** + * Invokes {@link HTTPSamplerBase#getCookieManager()} + */ + protected CookieManager getCookieManager() { + return testElement.getCookieManager(); + } + + /** + * Invokes {@link HTTPSamplerBase#getHeaderManager()} + */ + protected HeaderManager getHeaderManager() { + return testElement.getHeaderManager(); + } + + /** + * Invokes {@link HTTPSamplerBase#getHTTPFiles()} + */ + protected HTTPFileArg[] getHTTPFiles() { + return testElement.getHTTPFiles(); + } + + /** + * Invokes {@link HTTPSamplerBase#getIpSource()} + */ + protected String getIpSource() { + return testElement.getIpSource(); + } + + /** + * Invokes {@link HTTPSamplerBase#getProxyHost()} + */ + protected String getProxyHost() { + return testElement.getProxyHost(); + } + + /** + * Invokes {@link HTTPSamplerBase#getProxyPass()} + */ + protected String getProxyPass() { + return testElement.getProxyPass(); + } + + /** + * Invokes {@link HTTPSamplerBase#getProxyPortInt()} + */ + protected int getProxyPortInt() { + return testElement.getProxyPortInt(); + } + + /** + * Invokes {@link HTTPSamplerBase#getProxyUser()} + */ + protected String getProxyUser() { + return testElement.getProxyUser(); + } + + /** + * Invokes {@link HTTPSamplerBase#getResponseTimeout()} + */ + protected int getResponseTimeout() { + return testElement.getResponseTimeout(); + } + + /** + * Invokes {@link HTTPSamplerBase#getSendFileAsPostBody()} + */ + protected boolean getSendFileAsPostBody() { + return testElement.getSendFileAsPostBody(); + } + + /** + * Invokes {@link HTTPSamplerBase#getSendParameterValuesAsPostBody()} + */ + protected boolean getSendParameterValuesAsPostBody() { + return testElement.getSendParameterValuesAsPostBody(); + } + + /** + * Invokes {@link HTTPSamplerBase#getUseKeepAlive()} + */ + protected boolean getUseKeepAlive() { + return testElement.getUseKeepAlive(); + } + + /** + * Invokes {@link HTTPSamplerBase#getUseMultipartForPost()} + */ + protected boolean getUseMultipartForPost() { + return testElement.getUseMultipartForPost(); + } + + /** + * Invokes {@link HTTPSamplerBase#getDoBrowserCompatibleMultipart()} + */ + protected boolean getDoBrowserCompatibleMultipart() { + return testElement.getDoBrowserCompatibleMultipart(); + } + + /** + * Invokes {@link HTTPSamplerBase#hasArguments()} + */ + protected boolean hasArguments() { + return testElement.hasArguments(); + } + + /** + * Invokes {@link HTTPSamplerBase#isMonitor()} + */ + protected boolean isMonitor() { + return testElement.isMonitor(); + } + + /** + * Invokes {@link HTTPSamplerBase#isSuccessCode(int)} + */ + protected boolean isSuccessCode(int errorLevel) { + return testElement.isSuccessCode(errorLevel); + } + + /** + * Invokes {@link HTTPSamplerBase#readResponse(SampleResult, InputStream, int)} + */ + protected byte[] readResponse(SampleResult res, InputStream instream, + int responseContentLength) throws IOException { + return testElement.readResponse(res, instream, responseContentLength); + } + + /** + * Invokes {@link HTTPSamplerBase#readResponse(SampleResult, InputStream, int)} + */ + protected byte[] readResponse(SampleResult res, BufferedInputStream in, + int contentLength) throws IOException { + return testElement.readResponse(res, in, contentLength); + } + + /** + * Invokes {@link HTTPSamplerBase#resultProcessing(boolean, int, HTTPSampleResult)} + */ + protected HTTPSampleResult resultProcessing(boolean areFollowingRedirect, + int frameDepth, HTTPSampleResult res) { + return testElement.resultProcessing(areFollowingRedirect, frameDepth, res); + } + + /** + * Invokes {@link HTTPSamplerBase#setUseKeepAlive(boolean)} + */ + protected void setUseKeepAlive(boolean b) { + testElement.setUseKeepAlive(b); + } + + /** + * Called by testIterationStart if the SSL Context was reset. + * + * This implementation does nothing. + */ + protected void notifySSLContextWasReset() { + // NOOP + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPFileImpl.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPFileImpl.java new file mode 100644 index 0000000..fc747e6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPFileImpl.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +import org.apache.commons.io.IOUtils; + +/** + * HTTP Sampler which can read from file: URLs + */ +public class HTTPFileImpl extends HTTPAbstractImpl { + + protected HTTPFileImpl(HTTPSamplerBase base) { + super(base); + } + + public boolean interrupt() { + return false; + } + + @Override + protected HTTPSampleResult sample(URL url, String method, + boolean areFollowingRedirect, int frameDepth) { + + HTTPSampleResult res = new HTTPSampleResult(); + res.setHTTPMethod(GET); // Dummy + res.setURL(url); + res.setSampleLabel(url.toString()); + InputStream is = null; + res.sampleStart(); + try { + byte[] responseData; + URLConnection conn = url.openConnection(); + is = conn.getInputStream(); + responseData = IOUtils.toByteArray(is); + res.sampleEnd(); + res.setResponseData(responseData); + res.setResponseCodeOK(); + res.setResponseMessageOK(); + res.setSuccessful(true); + StringBuilder ctb=new StringBuilder("text/html"); // $NON-NLS-1$ + // TODO can this be obtained from the file somehow? + String contentEncoding = getContentEncoding(); + if (contentEncoding.length() > 0) { + ctb.append("; charset="); // $NON-NLS-1$ + ctb.append(contentEncoding); + } + String ct = ctb.toString(); + res.setContentType(ct); + res.setEncodingAndType(ct); + + res = resultProcessing(areFollowingRedirect, frameDepth, res); + + return res; + } catch (FileNotFoundException e) { + return errorResult(e, res); + } catch (IOException e) { + return errorResult(e, res); + } finally { + IOUtils.closeQuietly(is); + } + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHC3Impl.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHC3Impl.java new file mode 100644 index 0000000..c066c4a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHC3Impl.java @@ -0,0 +1,1126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jmeter.protocol.http.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HostConfiguration; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpConnectionManager; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.HttpState; +import org.apache.commons.httpclient.HttpVersion; +import org.apache.commons.httpclient.NTCredentials; +import org.apache.commons.httpclient.ProtocolException; +import org.apache.commons.httpclient.SimpleHttpConnectionManager; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.methods.DeleteMethod; +import org.apache.commons.httpclient.methods.FileRequestEntity; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.HeadMethod; +import org.apache.commons.httpclient.methods.OptionsMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.httpclient.methods.TraceMethod; +import org.apache.commons.httpclient.methods.multipart.FilePart; +import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; +import org.apache.commons.httpclient.methods.multipart.Part; +import org.apache.commons.httpclient.methods.multipart.PartBase; +import org.apache.commons.httpclient.methods.multipart.StringPart; +import org.apache.commons.httpclient.params.DefaultHttpParams; +import org.apache.commons.httpclient.params.HttpClientParams; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.apache.commons.httpclient.params.HttpParams; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.io.input.CountingInputStream; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.Authorization; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.EncoderCache; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.protocol.http.util.LoopbackHttpClientSocketFactory; +import org.apache.jmeter.protocol.http.util.SlowHttpClientSocketFactory; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.SSLManager; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * HTTP sampler using Apache (Jakarta) Commons HttpClient 3.1. + */ +public class HTTPHC3Impl extends HTTPHCAbstractImpl { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** retry count to be used (default 1); 0 = disable retries */ + private static final int RETRY_COUNT = JMeterUtils.getPropDefault("httpclient3.retrycount", 1); + + private static final String HTTP_AUTHENTICATION_PREEMPTIVE = "http.authentication.preemptive"; // $NON-NLS-1$ + + private static final boolean canSetPreEmptive; // OK to set pre-emptive auth? + + private static final ThreadLocal> httpClients = + new ThreadLocal>(){ + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + // Needs to be accessible by HTTPSampler2 + volatile HttpClient savedClient; + + static { + log.info("HTTP request retry count = "+RETRY_COUNT); + if (CPS_HTTP > 0) { + log.info("Setting up HTTP SlowProtocol, cps="+CPS_HTTP); + Protocol.registerProtocol(PROTOCOL_HTTP, + new Protocol(PROTOCOL_HTTP,new SlowHttpClientSocketFactory(CPS_HTTP),DEFAULT_HTTP_PORT)); + } + + // Now done in JsseSSLManager (which needs to register the protocol) +// cps = +// JMeterUtils.getPropDefault("httpclient.socket.https.cps", 0); // $NON-NLS-1$ +// +// if (cps > 0) { +// log.info("Setting up HTTPS SlowProtocol, cps="+cps); +// Protocol.registerProtocol(PROTOCOL_HTTPS, +// new Protocol(PROTOCOL_HTTPS,new SlowHttpClientSocketFactory(cps),DEFAULT_HTTPS_PORT)); +// } + + // Set default parameters as needed + HttpParams params = DefaultHttpParams.getDefaultParams(); + + // Process Commons HttpClient parameters file + String file=JMeterUtils.getProperty("httpclient.parameters.file"); // $NON-NLS-1$ + if (file != null) { + HttpClientDefaultParameters.load(file, params); + } + + // If the pre-emptive parameter is undefined, then we can set it as needed + // otherwise we should do what the user requested. + canSetPreEmptive = params.getParameter(HTTP_AUTHENTICATION_PREEMPTIVE) == null; + + // Handle old-style JMeter properties + try { + params.setParameter(HttpMethodParams.PROTOCOL_VERSION, HttpVersion.parse("HTTP/"+HTTP_VERSION)); + } catch (ProtocolException e) { + log.warn("Problem setting protocol version "+e.getLocalizedMessage()); + } + + if (SO_TIMEOUT >= 0){ + params.setIntParameter(HttpMethodParams.SO_TIMEOUT, SO_TIMEOUT); + } + + // This must be done last, as must not be overridden + params.setParameter(HttpMethodParams.COOKIE_POLICY,CookiePolicy.IGNORE_COOKIES); + // We do our own cookie handling + + if (USE_LOOPBACK){ + LoopbackHttpClientSocketFactory.setup(); + } + } + + protected HTTPHC3Impl(HTTPSamplerBase base) { + super(base); + } + + + /** + * Samples the URL passed in and stores the result in + * HTTPSampleResult, following redirects and downloading + * page resources as appropriate. + *

+ * When getting a redirect target, redirects are not followed and resources + * are not downloaded. The caller will take care of this. + * + * @param url + * URL to sample + * @param method + * HTTP method: GET, POST,... + * @param areFollowingRedirect + * whether we're getting a redirect target + * @param frameDepth + * Depth of this target in the frame structure. Used only to + * prevent infinite recursion. + * @return results of the sampling + */ + @Override + protected HTTPSampleResult sample(URL url, String method, boolean areFollowingRedirect, int frameDepth) { + + String urlStr = url.toString(); + + log.debug("Start : sample " + urlStr); + log.debug("method " + method); + + HttpMethodBase httpMethod = null; + + HTTPSampleResult res = new HTTPSampleResult(); + res.setMonitor(isMonitor()); + + res.setSampleLabel(urlStr); // May be replaced later + res.setHTTPMethod(method); + res.setURL(url); + + res.sampleStart(); // Count the retries as well in the time + try { + // May generate IllegalArgumentException + if (method.equals(POST)) { + httpMethod = new PostMethod(urlStr); + } else if (method.equals(PUT)){ + httpMethod = new PutMethod(urlStr); + } else if (method.equals(HEAD)){ + httpMethod = new HeadMethod(urlStr); + } else if (method.equals(TRACE)){ + httpMethod = new TraceMethod(urlStr); + } else if (method.equals(OPTIONS)){ + httpMethod = new OptionsMethod(urlStr); + } else if (method.equals(DELETE)){ + httpMethod = new DeleteMethod(urlStr); + } else if (method.equals(GET)){ + httpMethod = new GetMethod(urlStr); + } else { + throw new IllegalArgumentException("Unexpected method: "+method); + } + + final CacheManager cacheManager = getCacheManager(); + if (cacheManager != null && GET.equalsIgnoreCase(method)) { + if (cacheManager.inCache(url)) { + res.sampleEnd(); + res.setResponseNoContent(); + res.setSuccessful(true); + return res; + } + } + + // Set any default request headers + setDefaultRequestHeaders(httpMethod); + + // Setup connection + HttpClient client = setupConnection(url, httpMethod, res); + savedClient = client; + + // Handle the various methods + if (method.equals(POST)) { + String postBody = sendPostData((PostMethod)httpMethod); + res.setQueryString(postBody); + } else if (method.equals(PUT)) { + String putBody = sendPutData((PutMethod)httpMethod); + res.setQueryString(putBody); + } + + int statusCode = client.executeMethod(httpMethod); + + // Needs to be done after execute to pick up all the headers + res.setRequestHeaders(getConnectionHeaders(httpMethod)); + + // Request sent. Now get the response: + InputStream instream = httpMethod.getResponseBodyAsStream(); + + if (instream != null) {// will be null for HEAD + instream = new CountingInputStream(instream); + try { + Header responseHeader = httpMethod.getResponseHeader(HEADER_CONTENT_ENCODING); + if (responseHeader!= null && ENCODING_GZIP.equals(responseHeader.getValue())) { + InputStream tmpInput = new GZIPInputStream(instream); // tmp inputstream needs to have a good counting + res.setResponseData(readResponse(res, tmpInput, (int) httpMethod.getResponseContentLength())); + } else { + res.setResponseData(readResponse(res, instream, (int) httpMethod.getResponseContentLength())); + } + } finally { + JOrphanUtils.closeQuietly(instream); + } + } + + res.sampleEnd(); + // Done with the sampling proper. + + // Now collect the results into the HTTPSampleResult: + + res.setSampleLabel(httpMethod.getURI().toString()); + // Pick up Actual path (after redirects) + + res.setResponseCode(Integer.toString(statusCode)); + res.setSuccessful(isSuccessCode(statusCode)); + + res.setResponseMessage(httpMethod.getStatusText()); + + String ct = null; + Header h = httpMethod.getResponseHeader(HEADER_CONTENT_TYPE); + if (h != null)// Can be missing, e.g. on redirect + { + ct = h.getValue(); + res.setContentType(ct);// e.g. text/html; charset=ISO-8859-1 + res.setEncodingAndType(ct); + } + + res.setResponseHeaders(getResponseHeaders(httpMethod)); + if (res.isRedirect()) { + final Header headerLocation = httpMethod.getResponseHeader(HEADER_LOCATION); + if (headerLocation == null) { // HTTP protocol violation, but avoids NPE + throw new IllegalArgumentException("Missing location header"); + } + res.setRedirectLocation(headerLocation.getValue()); + } + + // record some sizes to allow HTTPSampleResult.getBytes() with different options + if (instream != null) { + res.setBodySize(((CountingInputStream) instream).getCount()); + } + res.setHeadersSize(calculateHeadersSize(httpMethod)); + if (log.isDebugEnabled()) { + log.debug("Response headersSize=" + res.getHeadersSize() + " bodySize=" + res.getBodySize() + + " Total=" + (res.getHeadersSize() + res.getBodySize())); + } + + // If we redirected automatically, the URL may have changed + if (getAutoRedirects()){ + res.setURL(new URL(httpMethod.getURI().toString())); + } + + // Store any cookies received in the cookie manager: + saveConnectionCookies(httpMethod, res.getURL(), getCookieManager()); + + // Save cache information + if (cacheManager != null){ + cacheManager.saveDetails(httpMethod, res); + } + + // Follow redirects and download page resources if appropriate: + res = resultProcessing(areFollowingRedirect, frameDepth, res); + + log.debug("End : sample"); + return res; + } catch (IllegalArgumentException e) { // e.g. some kinds of invalid URL + res.sampleEnd(); + errorResult(e, res); + return res; + } catch (IOException e) { + res.sampleEnd(); + errorResult(e, res); + return res; + } finally { + savedClient = null; + if (httpMethod != null) { + httpMethod.releaseConnection(); + } + } + } + + /** + * Calculate response headers size + * + * @return the size response headers (in bytes) + */ + private static int calculateHeadersSize(HttpMethodBase httpMethod) { + int headerSize = httpMethod.getStatusLine().toString().length()+2; // add a \r\n + Header[] rh = httpMethod.getResponseHeaders(); + for (int i = 0; i < rh.length; i++) { + headerSize += (rh[i]).toString().length(); // already include the \r\n + } + headerSize += 2; // last \r\n before response data + return headerSize; + } + + /** + * Returns an HttpConnection fully ready to attempt + * connection. This means it sets the request method (GET or POST), headers, + * cookies, and authorization for the URL request. + *

+ * The request infos are saved into the sample result if one is provided. + * + * @param u + * URL of the URL request + * @param httpMethod + * GET/PUT/HEAD etc + * @param res + * sample result to save request infos to + * @return HttpConnection ready for .connect + * @exception IOException + * if an I/O Exception occurs + */ + protected HttpClient setupConnection(URL u, HttpMethodBase httpMethod, HTTPSampleResult res) throws IOException { + + String urlStr = u.toString(); + + org.apache.commons.httpclient.URI uri = new org.apache.commons.httpclient.URI(urlStr,false); + + String schema = uri.getScheme(); + if ((schema == null) || (schema.length()==0)) { + schema = PROTOCOL_HTTP; + } + + if (PROTOCOL_HTTPS.equalsIgnoreCase(schema)){ + SSLManager.getInstance(); // ensure the manager is initialised + // we don't currently need to do anything further, as this sets the default https protocol + } + + Protocol protocol = Protocol.getProtocol(schema); + + String host = uri.getHost(); + int port = uri.getPort(); + + /* + * We use the HostConfiguration as the key to retrieve the HttpClient, + * so need to ensure that any items used in its equals/hashcode methods are + * not changed after use, i.e.: + * host, port, protocol, localAddress, proxy + * + */ + HostConfiguration hc = new HostConfiguration(); + hc.setHost(host, port, protocol); // All needed to ensure re-usablility + + // Set up the local address if one exists + if (localAddress != null){ + hc.setLocalAddress(localAddress); + } else { + final String ipSource = getIpSource(); + if (ipSource.length() > 0) {// Use special field ip source address (for pseudo 'ip spoofing') + InetAddress inetAddr = InetAddress.getByName(ipSource); + hc.setLocalAddress(inetAddr); + } + } + + final String proxyHost = getProxyHost(); + final int proxyPort = getProxyPortInt(); + + boolean useStaticProxy = isStaticProxy(host); + boolean useDynamicProxy = isDynamicProxy(proxyHost, proxyPort); + + if (useDynamicProxy){ + hc.setProxy(proxyHost, proxyPort); + useStaticProxy = false; // Dynamic proxy overrules static proxy + } else if (useStaticProxy) { + if (log.isDebugEnabled()){ + log.debug("Setting proxy: "+PROXY_HOST+":"+PROXY_PORT); + } + hc.setProxy(PROXY_HOST, PROXY_PORT); + } + + Map map = httpClients.get(); + // N.B. HostConfiguration.equals() includes proxy settings in the compare. + HttpClient httpClient = map.get(hc); + + if ( httpClient == null ) + { + httpClient = new HttpClient(new SimpleHttpConnectionManager()); + httpClient.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, + new DefaultHttpMethodRetryHandler(RETRY_COUNT, false)); + if (log.isDebugEnabled()) { + log.debug("Created new HttpClient: @"+System.identityHashCode(httpClient)); + } + httpClient.setHostConfiguration(hc); + map.put(hc, httpClient); + } else { + if (log.isDebugEnabled()) { + log.debug("Reusing the HttpClient: @"+System.identityHashCode(httpClient)); + } + } + + // Set up any required Proxy credentials + if (useDynamicProxy){ + String user = getProxyUser(); + if (user.length() > 0){ + httpClient.getState().setProxyCredentials( + new AuthScope(proxyHost,proxyPort,null,AuthScope.ANY_SCHEME), + new NTCredentials(user,getProxyPass(),localHost,PROXY_DOMAIN) + ); + } else { + httpClient.getState().clearProxyCredentials(); + } + } else { + if (useStaticProxy) { + if (PROXY_USER.length() > 0){ + httpClient.getState().setProxyCredentials( + new AuthScope(PROXY_HOST,PROXY_PORT,null,AuthScope.ANY_SCHEME), + new NTCredentials(PROXY_USER,PROXY_PASS,localHost,PROXY_DOMAIN) + ); + } + } else { + httpClient.getState().clearProxyCredentials(); + } + } + + int rto = getResponseTimeout(); + if (rto > 0){ + httpMethod.getParams().setSoTimeout(rto); + } + + int cto = getConnectTimeout(); + if (cto > 0){ + httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(cto); + } + + + // Allow HttpClient to handle the redirects: + httpMethod.setFollowRedirects(getAutoRedirects()); + + // a well-behaved browser is supposed to send 'Connection: close' + // with the last request to an HTTP server. Instead, most browsers + // leave it to the server to close the connection after their + // timeout period. Leave it to the JMeter user to decide. + if (getUseKeepAlive()) { + httpMethod.setRequestHeader(HEADER_CONNECTION, KEEP_ALIVE); + } else { + httpMethod.setRequestHeader(HEADER_CONNECTION, CONNECTION_CLOSE); + } + + setConnectionHeaders(httpMethod, u, getHeaderManager(), getCacheManager()); + String cookies = setConnectionCookie(httpMethod, u, getCookieManager()); + + setConnectionAuthorization(httpClient, u, getAuthManager()); + + if (res != null) { + res.setCookies(cookies); + } + + return httpClient; + } + + /** + * Set any default request headers to include + * + * @param httpMethod the HttpMethod used for the request + */ + protected void setDefaultRequestHeaders(HttpMethod httpMethod) { + // Method left empty here, but allows subclasses to override + } + + /** + * Gets the ResponseHeaders + * + * @param method the method used to perform the request + * @return string containing the headers, one per line + */ + protected String getResponseHeaders(HttpMethod method) { + StringBuilder headerBuf = new StringBuilder(); + org.apache.commons.httpclient.Header rh[] = method.getResponseHeaders(); + headerBuf.append(method.getStatusLine());// header[0] is not the status line... + headerBuf.append("\n"); // $NON-NLS-1$ + + for (int i = 0; i < rh.length; i++) { + String key = rh[i].getName(); + headerBuf.append(key); + headerBuf.append(": "); // $NON-NLS-1$ + headerBuf.append(rh[i].getValue()); + headerBuf.append("\n"); // $NON-NLS-1$ + } + return headerBuf.toString(); + } + + /** + * Extracts all the required cookies for that particular URL request and + * sets them in the HttpMethod passed in. + * + * @param method HttpMethod for the request + * @param u URL of the request + * @param cookieManager the CookieManager containing all the cookies + * @return a String containing the cookie details (for the response) + * May be null + */ + private String setConnectionCookie(HttpMethod method, URL u, CookieManager cookieManager) { + String cookieHeader = null; + if (cookieManager != null) { + cookieHeader = cookieManager.getCookieHeaderForURL(u); + if (cookieHeader != null) { + method.setRequestHeader(HEADER_COOKIE, cookieHeader); + } + } + return cookieHeader; + } + + /** + * Extracts all the required non-cookie headers for that particular URL request and + * sets them in the HttpMethod passed in + * + * @param method + * HttpMethod which represents the request + * @param u + * URL of the URL request + * @param headerManager + * the HeaderManager containing all the cookies + * for this UrlConfig + * @param cacheManager the CacheManager (may be null) + */ + private void setConnectionHeaders(HttpMethod method, URL u, HeaderManager headerManager, CacheManager cacheManager) { + // Set all the headers from the HeaderManager + if (headerManager != null) { + CollectionProperty headers = headerManager.getHeaders(); + if (headers != null) { + PropertyIterator i = headers.iterator(); + while (i.hasNext()) { + org.apache.jmeter.protocol.http.control.Header header + = (org.apache.jmeter.protocol.http.control.Header) + i.next().getObjectValue(); + String n = header.getName(); + // Don't allow override of Content-Length + // This helps with SoapSampler hack too + // TODO - what other headers are not allowed? + if (! HEADER_CONTENT_LENGTH.equalsIgnoreCase(n)){ + String v = header.getValue(); + if (HEADER_HOST.equalsIgnoreCase(n)) { + v = v.replaceFirst(":\\d+$",""); // remove any port specification // $NON-NLS-1$ $NON-NLS-2$ + method.getParams().setVirtualHost(v); + } else { + method.addRequestHeader(n, v); + } + } + } + } + } + if (cacheManager != null){ + cacheManager.setHeaders(u, method); + } + } + + /** + * Get all the request headers for the HttpMethod + * + * @param method + * HttpMethod which represents the request + * @return the headers as a string + */ + protected String getConnectionHeaders(HttpMethod method) { + // Get all the request headers + StringBuilder hdrs = new StringBuilder(100); + Header[] requestHeaders = method.getRequestHeaders(); + for(int i = 0; i < requestHeaders.length; i++) { + // Exclude the COOKIE header, since cookie is reported separately in the sample + if(!HEADER_COOKIE.equalsIgnoreCase(requestHeaders[i].getName())) { + hdrs.append(requestHeaders[i].getName()); + hdrs.append(": "); // $NON-NLS-1$ + hdrs.append(requestHeaders[i].getValue()); + hdrs.append("\n"); // $NON-NLS-1$ + } + } + + return hdrs.toString(); + } + + + /** + * Extracts all the required authorization for that particular URL request + * and sets it in the HttpMethod passed in. + * + * @param client the HttpClient object + * + * @param u + * URL of the URL request + * @param authManager + * the AuthManager containing all the authorisations for + * this UrlConfig + */ + private void setConnectionAuthorization(HttpClient client, URL u, AuthManager authManager) { + HttpState state = client.getState(); + if (authManager != null) { + HttpClientParams params = client.getParams(); + Authorization auth = authManager.getAuthForURL(u); + if (auth != null) { + String username = auth.getUser(); + String realm = auth.getRealm(); + String domain = auth.getDomain(); + if (log.isDebugEnabled()){ + log.debug(username + " > D="+ username + " D="+domain+" R="+realm); + } + state.setCredentials( + new AuthScope(u.getHost(),u.getPort(), + realm.length()==0 ? null : realm //"" is not the same as no realm + ,AuthScope.ANY_SCHEME), + // NT Includes other types of Credentials + new NTCredentials( + username, + auth.getPass(), + localHost, + domain + )); + // We have credentials - should we set pre-emptive authentication? + if (canSetPreEmptive){ + log.debug("Setting Pre-emptive authentication"); + params.setAuthenticationPreemptive(true); + } + } else { + state.clearCredentials(); + if (canSetPreEmptive){ + params.setAuthenticationPreemptive(false); + } + } + } else { + state.clearCredentials(); + } + } + + + /* + * Send POST data from Entry to the open connection. + * + * @param connection + * URLConnection where POST data should be sent + * @return a String show what was posted. Will not contain actual file upload content + * @exception IOException + * if an I/O exception occurs + */ + private String sendPostData(PostMethod post) throws IOException { + // Buffer to hold the post body, except file content + StringBuilder postedBody = new StringBuilder(1000); + HTTPFileArg files[] = getHTTPFiles(); + // Check if we should do a multipart/form-data or an + // application/x-www-form-urlencoded post request + if(getUseMultipartForPost()) { + // If a content encoding is specified, we use that as the + // encoding of any parameter values + String contentEncoding = getContentEncoding(); + if(isNullOrEmptyTrimmed(contentEncoding)) { + contentEncoding = null; + } + + final boolean browserCompatible = getDoBrowserCompatibleMultipart(); + // We don't know how many entries will be skipped + ArrayList partlist = new ArrayList(); + // Create the parts + // Add any parameters + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String parameterName = arg.getName(); + if (arg.isSkippable(parameterName)){ + continue; + } + StringPart part = new StringPart(arg.getName(), arg.getValue(), contentEncoding); + if (browserCompatible) { + part.setTransferEncoding(null); + part.setContentType(null); + } + partlist.add(part); + } + + // Add any files + for (int i=0; i < files.length; i++) { + HTTPFileArg file = files[i]; + File inputFile = new File(file.getPath()); + // We do not know the char set of the file to be uploaded, so we set it to null + ViewableFilePart filePart = new ViewableFilePart(file.getParamName(), inputFile, file.getMimeType(), null); + filePart.setCharSet(null); // We do not know what the char set of the file is + partlist.add(filePart); + } + + // Set the multipart for the post + int partNo = partlist.size(); + Part[] parts = partlist.toArray(new Part[partNo]); + MultipartRequestEntity multiPart = new MultipartRequestEntity(parts, post.getParams()); + post.setRequestEntity(multiPart); + + // Set the content type + String multiPartContentType = multiPart.getContentType(); + post.setRequestHeader(HEADER_CONTENT_TYPE, multiPartContentType); + + // If the Multipart is repeatable, we can send it first to + // our own stream, without the actual file content, so we can return it + if(multiPart.isRepeatable()) { + // For all the file multiparts, we must tell it to not include + // the actual file content + for(int i = 0; i < partNo; i++) { + if(parts[i] instanceof ViewableFilePart) { + ((ViewableFilePart) parts[i]).setHideFileData(true); // .sendMultipartWithoutFileContent(bos); + } + } + // Write the request to our own stream + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + multiPart.writeRequest(bos); + bos.flush(); + // We get the posted bytes using the encoding used to create it + postedBody.append(new String(bos.toByteArray(), + contentEncoding == null ? "US-ASCII" // $NON-NLS-1$ this is the default used by HttpClient + : contentEncoding)); + bos.close(); + + // For all the file multiparts, we must revert the hiding of + // the actual file content + for(int i = 0; i < partNo; i++) { + if(parts[i] instanceof ViewableFilePart) { + ((ViewableFilePart) parts[i]).setHideFileData(false); + } + } + } + else { + postedBody.append(""); // $NON-NLS-1$ + } + } + else { + // Check if the header manager had a content type header + // This allows the user to specify his own content-type for a POST request + Header contentTypeHeader = post.getRequestHeader(HEADER_CONTENT_TYPE); + boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.getValue() != null && contentTypeHeader.getValue().length() > 0; + // If there are no arguments, we can send a file as the body of the request + // TODO: needs a multiple file upload scenerio + if(!hasArguments() && getSendFileAsPostBody()) { + // If getSendFileAsPostBody returned true, it's sure that file is not null + HTTPFileArg file = files[0]; + if(!hasContentTypeHeader) { + // Allow the mimetype of the file to control the content type + if(file.getMimeType() != null && file.getMimeType().length() > 0) { + post.setRequestHeader(HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + post.setRequestHeader(HEADER_CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED); + } + } + + FileRequestEntity fileRequestEntity = new FileRequestEntity(new File(file.getPath()),null); + post.setRequestEntity(fileRequestEntity); + + // We just add placeholder text for file content + postedBody.append(""); + } + else { + // In a post request which is not multipart, we only support + // parameters, no file upload is allowed + + // If a content encoding is specified, we set it as http parameter, so that + // the post body will be encoded in the specified content encoding + String contentEncoding = getContentEncoding(); + boolean haveContentEncoding = false; + if(isNullOrEmptyTrimmed(contentEncoding)) { + contentEncoding=null; + } else { + post.getParams().setContentCharset(contentEncoding); + haveContentEncoding = true; + } + + // If none of the arguments have a name specified, we + // just send all the values as the post body + if(getSendParameterValuesAsPostBody()) { + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + // TODO: needs a multiple file upload scenerio + if(!hasContentTypeHeader) { + HTTPFileArg file = files.length > 0? files[0] : null; + if(file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { + post.setRequestHeader(HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + // TODO - is this the correct default? + post.setRequestHeader(HEADER_CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED); + } + } + + // Just append all the parameter values, and use that as the post body + StringBuilder postBody = new StringBuilder(); + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String value; + if (haveContentEncoding){ + value = arg.getEncodedValue(contentEncoding); + } else { + value = arg.getEncodedValue(); + } + postBody.append(value); + } + StringRequestEntity requestEntity = new StringRequestEntity(postBody.toString(), post.getRequestHeader(HEADER_CONTENT_TYPE).getValue(), contentEncoding); + post.setRequestEntity(requestEntity); + } + else { + // It is a normal post request, with parameter names and values + + // Set the content type + if(!hasContentTypeHeader) { + post.setRequestHeader(HEADER_CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED); + } + // Add the parameters + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + // The HTTPClient always urlencodes both name and value, + // so if the argument is already encoded, we have to decode + // it before adding it to the post request + String parameterName = arg.getName(); + if (arg.isSkippable(parameterName)){ + continue; + } + String parameterValue = arg.getValue(); + if(!arg.isAlwaysEncoded()) { + // The value is already encoded by the user + // Must decode the value now, so that when the + // httpclient encodes it, we end up with the same value + // as the user had entered. + String urlContentEncoding = contentEncoding; + if(urlContentEncoding == null || urlContentEncoding.length() == 0) { + // Use the default encoding for urls + urlContentEncoding = EncoderCache.URL_ARGUMENT_ENCODING; + } + parameterName = URLDecoder.decode(parameterName, urlContentEncoding); + parameterValue = URLDecoder.decode(parameterValue, urlContentEncoding); + } + // Add the parameter, httpclient will urlencode it + post.addParameter(parameterName, parameterValue); + } + +/* +// // Alternative implementation, to make sure that HTTPSampler and HTTPSampler2 +// // sends the same post body. +// +// // Only include the content char set in the content-type header if it is not +// // an APPLICATION_X_WWW_FORM_URLENCODED content type +// String contentCharSet = null; +// if(!post.getRequestHeader(HEADER_CONTENT_TYPE).getValue().equals(APPLICATION_X_WWW_FORM_URLENCODED)) { +// contentCharSet = post.getRequestCharSet(); +// } +// StringRequestEntity requestEntity = new StringRequestEntity(getQueryString(contentEncoding), post.getRequestHeader(HEADER_CONTENT_TYPE).getValue(), contentCharSet); +// post.setRequestEntity(requestEntity); +*/ + } + + // If the request entity is repeatable, we can send it first to + // our own stream, so we can return it + if(post.getRequestEntity().isRepeatable()) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + post.getRequestEntity().writeRequest(bos); + bos.flush(); + // We get the posted bytes using the encoding used to create it + postedBody.append(new String(bos.toByteArray(),post.getRequestCharSet())); + bos.close(); + } + else { + postedBody.append(""); + } + } + } + // Set the content length + post.setRequestHeader(HEADER_CONTENT_LENGTH, Long.toString(post.getRequestEntity().getContentLength())); + + return postedBody.toString(); + } + + /** + * Set up the PUT data + */ + private String sendPutData(PutMethod put) throws IOException { + // Buffer to hold the put body, except file content + StringBuilder putBody = new StringBuilder(1000); + boolean hasPutBody = false; + + // Check if the header manager had a content type header + // This allows the user to specify his own content-type for a POST request + Header contentTypeHeader = put.getRequestHeader(HEADER_CONTENT_TYPE); + boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.getValue() != null && contentTypeHeader.getValue().length() > 0; + HTTPFileArg files[] = getHTTPFiles(); + + // If there are no arguments, we can send a file as the body of the request + + if(!hasArguments() && getSendFileAsPostBody()) { + hasPutBody = true; + + // If getSendFileAsPostBody returned true, it's sure that file is not null + FileRequestEntity fileRequestEntity = new FileRequestEntity(new File(files[0].getPath()),null); + put.setRequestEntity(fileRequestEntity); + + // We just add placeholder text for file content + putBody.append(""); + } + // If none of the arguments have a name specified, we + // just send all the values as the put body + else if(getSendParameterValuesAsPostBody()) { + hasPutBody = true; + + // If a content encoding is specified, we set it as http parameter, so that + // the post body will be encoded in the specified content encoding + String contentEncoding = getContentEncoding(); + boolean haveContentEncoding = false; + if(isNullOrEmptyTrimmed(contentEncoding)) { + contentEncoding = null; + } else { + put.getParams().setContentCharset(contentEncoding); + haveContentEncoding = true; + } + + // Just append all the parameter values, and use that as the post body + StringBuilder putBodyContent = new StringBuilder(); + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String value = null; + if (haveContentEncoding){ + value = arg.getEncodedValue(contentEncoding); + } else { + value = arg.getEncodedValue(); + } + putBodyContent.append(value); + } + String contentTypeValue = null; + if(hasContentTypeHeader) { + contentTypeValue = put.getRequestHeader(HEADER_CONTENT_TYPE).getValue(); + } + StringRequestEntity requestEntity = new StringRequestEntity(putBodyContent.toString(), contentTypeValue, put.getRequestCharSet()); + put.setRequestEntity(requestEntity); + } + // Check if we have any content to send for body + if(hasPutBody) { + // If the request entity is repeatable, we can send it first to + // our own stream, so we can return it + if(put.getRequestEntity().isRepeatable()) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + put.getRequestEntity().writeRequest(bos); + bos.flush(); + // We get the posted bytes using the charset that was used to create them + putBody.append(new String(bos.toByteArray(),put.getRequestCharSet())); + bos.close(); + } + else { + putBody.append(""); + } + if(!hasContentTypeHeader) { + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + // TODO: needs a multiple file upload scenerio + HTTPFileArg file = files.length > 0? files[0] : null; + if(file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { + put.setRequestHeader(HEADER_CONTENT_TYPE, file.getMimeType()); + } + } + // Set the content length + put.setRequestHeader(HEADER_CONTENT_LENGTH, Long.toString(put.getRequestEntity().getContentLength())); + return putBody.toString(); + } + return null; + } + + /** + * Class extending FilePart, so that we can send placeholder text + * instead of the actual file content + */ + private static class ViewableFilePart extends FilePart { + private boolean hideFileData; + + public ViewableFilePart(String name, File file, String contentType, String charset) throws FileNotFoundException { + super(name, file, contentType, charset); + this.hideFileData = false; + } + + public void setHideFileData(boolean hideFileData) { + this.hideFileData = hideFileData; + } + + @Override + protected void sendData(OutputStream out) throws IOException { + // Check if we should send only placeholder text for the + // file content, or the real file content + if(hideFileData) { + out.write("".getBytes());// encoding does not really matter here + } + else { + super.sendData(out); + } + } + } + + /** + * From the HttpMethod, store all the "set-cookie" key-pair + * values in the cookieManager of the UrlConfig. + * + * @param method + * HttpMethod which represents the request + * @param u + * URL of the URL request + * @param cookieManager + * the CookieManager containing all the cookies + */ + protected void saveConnectionCookies(HttpMethod method, URL u, CookieManager cookieManager) { + if (cookieManager != null) { + Header hdr[] = method.getResponseHeaders(HEADER_SET_COOKIE); + for (int i = 0; i < hdr.length; i++) { + cookieManager.addCookieFromHeader(hdr[i].getValue(),u); + } + } + } + + + @Override + public void threadFinished() { + log.debug("Thread Finished"); + + closeThreadLocalConnections(); + } + + + /** + * + */ + private void closeThreadLocalConnections() { + // Does not need to be synchronised, as all access is from same thread + Map map = httpClients.get(); + + if ( map != null ) { + for (HttpClient cl : map.values()) + { + // Can cause NPE in HttpClient 3.1 + //((SimpleHttpConnectionManager)cl.getHttpConnectionManager()).shutdown();// Closes the connection + // Revert to original method: + cl.getHttpConnectionManager().closeIdleConnections(-1000);// Closes the connection + } + map.clear(); + } + } + + /** {@inheritDoc} */ + public boolean interrupt() { + HttpClient client = savedClient; + if (client != null) { + savedClient = null; + // TODO - not sure this is the best method + final HttpConnectionManager httpConnectionManager = client.getHttpConnectionManager(); + if (httpConnectionManager instanceof SimpleHttpConnectionManager) {// Should be true + ((SimpleHttpConnectionManager)httpConnectionManager).shutdown(); + } + } + return client != null; + } + + /** + * {@inheritDoc} + * This implementation closes all local connections. + */ + @Override + protected void notifySSLContextWasReset() { + log.debug("freeThreadConnections called"); + closeThreadLocalConnections(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java new file mode 100644 index 0000000..a1ad803 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java @@ -0,0 +1,1158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.http.Header; +import org.apache.http.HttpConnection; +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.NameValuePair; +import org.apache.http.StatusLine; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.NTCredentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpOptions; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.methods.HttpTrace; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.params.CookiePolicy; +import org.apache.http.client.protocol.ResponseContentEncoding; +import org.apache.http.conn.params.ConnRoutePNames; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.entity.FileEntity; +import org.apache.http.entity.StringEntity; +import org.apache.http.entity.mime.FormBodyPart; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntity; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.entity.mime.content.StringBody; +import org.apache.http.impl.client.AbstractHttpClient; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; +import org.apache.http.impl.client.RequestWrapper; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.CoreProtocolPNames; +import org.apache.http.params.DefaultedHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.ExecutionContext; +import org.apache.http.protocol.HttpContext; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.Authorization; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.EncoderCache; +import org.apache.jmeter.protocol.http.util.HC4TrustAllSSLSocketFactory; +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.protocol.http.util.SlowHC4SSLSocketFactory; +import org.apache.jmeter.protocol.http.util.SlowHC4SocketFactory; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * HTTP Sampler using Apache HttpClient 4.x. + * + */ +public class HTTPHC4Impl extends HTTPHCAbstractImpl { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** retry count to be used (default 1); 0 = disable retries */ + private static final int RETRY_COUNT = JMeterUtils.getPropDefault("httpclient4.retrycount", 1); + + private static final String CONTEXT_METRICS = "jmeter_metrics"; // TODO hack, to be removed later + + private static final HttpResponseInterceptor METRICS_SAVER = new HttpResponseInterceptor(){ + public void process(HttpResponse response, HttpContext context) + throws HttpException, IOException { + HttpConnection conn = (HttpConnection) context.getAttribute(ExecutionContext.HTTP_CONNECTION); + HttpConnectionMetrics metrics = conn.getMetrics(); + context.setAttribute(CONTEXT_METRICS, metrics); + } + }; + private static final HttpRequestInterceptor METRICS_RESETTER = new HttpRequestInterceptor() { + public void process(HttpRequest request, HttpContext context) + throws HttpException, IOException { + HttpConnection conn = (HttpConnection) context.getAttribute(ExecutionContext.HTTP_CONNECTION); + HttpConnectionMetrics metrics = conn.getMetrics(); + metrics.reset(); + } + }; + + private static final ThreadLocal> HTTPCLIENTS = + new ThreadLocal>(){ + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + // Scheme used for slow HTTP sockets. Cannot be set as a default, because must be set on an HttpClient instance. + private static final Scheme SLOW_HTTP; + + // We always want to override the HTTPS scheme, because we want to trust all certificates and hosts + private static final Scheme HTTPS_SCHEME; + + /* + * Create a set of default parameters from the ones initially created. + * This allows the defaults to be overridden if necessary from the properties file. + */ + private static final HttpParams DEFAULT_HTTP_PARAMS; + + static { + log.info("HTTP request retry count = "+RETRY_COUNT); + + // TODO use new setDefaultHttpParams(HttpParams params) static method when 4.1 is available + final DefaultHttpClient dhc = new DefaultHttpClient(); + DEFAULT_HTTP_PARAMS = dhc.getParams(); // Get the default params + dhc.getConnectionManager().shutdown(); // Tidy up + + // Process Apache HttpClient parameters file + String file=JMeterUtils.getProperty("hc.parameters.file"); // $NON-NLS-1$ + if (file != null) { + HttpClientDefaultParameters.load(file, DEFAULT_HTTP_PARAMS); + } + + // Set up HTTP scheme override if necessary + if (CPS_HTTP > 0) { + log.info("Setting up HTTP SlowProtocol, cps="+CPS_HTTP); + SLOW_HTTP = null;//tms new Scheme(PROTOCOL_HTTP, DEFAULT_HTTP_PORT, new SlowHC4SocketFactory(CPS_HTTP)); + } else { + SLOW_HTTP = null; + } + + // We always want to override the HTTPS scheme + Scheme https = null; + if (CPS_HTTPS > 0) { + log.info("Setting up HTTPS SlowProtocol, cps="+CPS_HTTPS); + try { + https = null;//tms new Scheme(PROTOCOL_HTTPS, DEFAULT_HTTPS_PORT, new SlowHC4SSLSocketFactory(CPS_HTTPS)); + } catch (/*tms GeneralSecurity*/Exception e) { + log.warn("Failed to initialise SLOW_HTTPS scheme, cps="+CPS_HTTPS, e); + } + } else { + log.info("Setting up HTTPS TrustAll scheme"); + try { + https = null; //tms new Scheme(PROTOCOL_HTTPS, DEFAULT_HTTPS_PORT, new HC4TrustAllSSLSocketFactory()); + } catch (/*tms GeneralSecurity*/Exception e) { + log.warn("Failed to initialise HTTPS TrustAll scheme", e); + } + } + HTTPS_SCHEME = https; + if (localAddress != null){ + DEFAULT_HTTP_PARAMS.setParameter(ConnRoutePNames.LOCAL_ADDRESS, localAddress); + } + + } + + private volatile HttpUriRequest currentRequest; // Accessed from multiple threads + + protected HTTPHC4Impl(HTTPSamplerBase testElement) { + super(testElement); + } + + @Override + protected HTTPSampleResult sample(URL url, String method, + boolean areFollowingRedirect, int frameDepth) { + + HTTPSampleResult res = new HTTPSampleResult(); + res.setMonitor(isMonitor()); + + res.setSampleLabel(url.toString()); // May be replaced later + res.setHTTPMethod(method); + res.setURL(url); + + HttpClient httpClient = setupClient(url); + + HttpRequestBase httpRequest = null; + try { + URI uri = url.toURI(); + if (method.equals(POST)) { + httpRequest = new HttpPost(uri); + } else if (method.equals(PUT)) { + httpRequest = new HttpPut(uri); + } else if (method.equals(HEAD)) { + httpRequest = new HttpHead(uri); + } else if (method.equals(TRACE)) { + httpRequest = new HttpTrace(uri); + } else if (method.equals(OPTIONS)) { + httpRequest = new HttpOptions(uri); + } else if (method.equals(DELETE)) { + httpRequest = new HttpDelete(uri); + } else if (method.equals(GET)) { + httpRequest = new HttpGet(uri); + } else { + throw new IllegalArgumentException("Unexpected method: "+method); + } + setupRequest(url, httpRequest, res); // can throw IOException + } catch (Exception e) { + res.sampleStart(); + res.sampleEnd(); + errorResult(e, res); + return res; + } + + HttpContext localContext = null; //tms new BasicHttpContext(); + + res.sampleStart(); + + final CacheManager cacheManager = getCacheManager(); + if (cacheManager != null && GET.equalsIgnoreCase(method)) { + if (cacheManager.inCache(url)) { + res.sampleEnd(); + res.setResponseNoContent(); + res.setSuccessful(true); + return res; + } + } + + try { + currentRequest = httpRequest; + // Handle the various methods + if (method.equals(POST)) { + String postBody = sendPostData((HttpPost)httpRequest); + res.setQueryString(postBody); + } else if (method.equals(PUT)) { + String putBody = sendPutData((HttpPut)httpRequest); + res.setQueryString(putBody); + } + HttpResponse httpResponse = httpClient.execute(httpRequest, localContext); // perform the sample + + // Needs to be done after execute to pick up all the headers + res.setRequestHeaders(getConnectionHeaders((HttpRequest) localContext.getAttribute(ExecutionContext.HTTP_REQUEST))); + + Header contentType = httpResponse.getLastHeader(HEADER_CONTENT_TYPE); + if (contentType != null){ + String ct = contentType.getValue(); + res.setContentType(ct); + res.setEncodingAndType(ct); + } + HttpEntity entity = httpResponse.getEntity(); + if (entity != null) { + InputStream instream = entity.getContent(); + res.setResponseData(readResponse(res, instream, (int) entity.getContentLength())); + } + + res.sampleEnd(); // Done with the sampling proper. + currentRequest = null; + + // Now collect the results into the HTTPSampleResult: + StatusLine statusLine = httpResponse.getStatusLine(); + int statusCode = statusLine.getStatusCode(); + res.setResponseCode(Integer.toString(statusCode)); + res.setResponseMessage(statusLine.getReasonPhrase()); + res.setSuccessful(isSuccessCode(statusCode)); + + res.setResponseHeaders(getResponseHeaders(httpResponse)); + if (res.isRedirect()) { + final Header headerLocation = httpResponse.getLastHeader(HEADER_LOCATION); + if (headerLocation == null) { // HTTP protocol violation, but avoids NPE + throw new IllegalArgumentException("Missing location header"); + } + res.setRedirectLocation(headerLocation.getValue()); + } + + // record some sizes to allow HTTPSampleResult.getBytes() with different options + HttpConnectionMetrics metrics = (HttpConnectionMetrics) localContext.getAttribute(CONTEXT_METRICS); + long headerBytes = + res.getResponseHeaders().length() // condensed length (without \r) + + httpResponse.getAllHeaders().length // Add \r for each header + + 1 // Add \r for initial header + + 2; // final \r\n before data + long totalBytes = metrics.getReceivedBytesCount(); + res.setHeadersSize((int) headerBytes); + res.setBodySize((int)(totalBytes - headerBytes)); + if (log.isDebugEnabled()) { + log.debug("ResponseHeadersSize=" + res.getHeadersSize() + " Content-Length=" + res.getBodySize() + + " Total=" + (res.getHeadersSize() + res.getBodySize())); + } + + // If we redirected automatically, the URL may have changed + if (getAutoRedirects()){ + HttpUriRequest req = (HttpUriRequest) localContext.getAttribute(ExecutionContext.HTTP_REQUEST); + HttpHost target = (HttpHost) localContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST); + URI redirectURI = req.getURI(); + if (redirectURI.isAbsolute()){ + res.setURL(redirectURI.toURL()); + } else { + res.setURL(new URL(new URL(target.toURI()),redirectURI.toString())); + } + } + + // Store any cookies received in the cookie manager: + saveConnectionCookies(httpResponse, res.getURL(), getCookieManager()); + + // Save cache information + if (cacheManager != null){ + cacheManager.saveDetails(httpResponse, res); + } + + // Follow redirects and download page resources if appropriate: + res = resultProcessing(areFollowingRedirect, frameDepth, res); + + } catch (IOException e) { + res.sampleEnd(); + errorResult(e, res); + return res; + } catch (RuntimeException e) { + res.sampleEnd(); + errorResult(e, res); + return res; + } finally { + currentRequest = null; + } + return res; + } + + /** + * Holder class for all fields that define an HttpClient instance; + * used as the key to the ThreadLocal map of HttpClient instances. + */ + private static final class HttpClientKey { + + private final String target; // protocol://[user:pass@]host:[port] + private final boolean hasProxy; + private final String proxyHost; + private final int proxyPort; + private final String proxyUser; + private final String proxyPass; + + private final int hashCode; // Always create hash because we will always need it + + public HttpClientKey(URL url, boolean b, String proxyHost, + int proxyPort, String proxyUser, String proxyPass) { + // N.B. need to separate protocol from authority otherwise http://server would match https://erver + // could use separate fields, but simpler to combine them + this.target = url.getProtocol()+"://"+url.getAuthority(); + this.hasProxy = b; + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.proxyUser = proxyUser; + this.proxyPass = proxyPass; + this.hashCode = getHash(); + } + + private int getHash() { + int hash = 17; + hash = hash*31 + (hasProxy ? 1 : 0); + if (hasProxy) { + hash = hash*31 + getHash(proxyHost); + hash = hash*31 + proxyPort; + hash = hash*31 + getHash(proxyUser); + hash = hash*31 + getHash(proxyPass); + } + hash = hash*31 + target.hashCode(); + return hash; + } + + // Allow for null strings + private int getHash(String s) { + return s == null ? 0 : s.hashCode(); + } + + @Override + public boolean equals (Object obj){ + if (this == obj) { + return true; + } + if (!(obj instanceof HttpClientKey)) { + return false; + } + HttpClientKey other = (HttpClientKey) obj; + if (this.hasProxy) { // otherwise proxy String fields may be null + return + this.hasProxy == other.hasProxy && + this.proxyPort == other.proxyPort && + this.proxyHost.equals(other.proxyHost) && + this.proxyUser.equals(other.proxyUser) && + this.proxyPass.equals(other.proxyPass) && + this.target.equals(other.target); + } + // No proxy, so don't check proxy fields + return + this.hasProxy == other.hasProxy && + this.target.equals(other.target); + } + + @Override + public int hashCode(){ + return hashCode; + } + } + + private HttpClient setupClient(URL url) { + + Map map = HTTPCLIENTS.get(); + + final String host = url.getHost(); + final String proxyHost = getProxyHost(); + final int proxyPort = getProxyPortInt(); + + boolean useStaticProxy = isStaticProxy(host); + boolean useDynamicProxy = isDynamicProxy(proxyHost, proxyPort); + + // Lookup key - must agree with all the values used to create the HttpClient. + HttpClientKey key = new HttpClientKey(url, (useStaticProxy || useDynamicProxy), + useDynamicProxy ? proxyHost : PROXY_HOST, + useDynamicProxy ? proxyPort : PROXY_PORT, + useDynamicProxy ? getProxyUser() : PROXY_USER, + useDynamicProxy ? getProxyPass() : PROXY_PASS); + + HttpClient httpClient = map.get(key); + + if (httpClient == null){ // One-time init for this client + + HttpParams clientParams = new DefaultedHttpParams(new BasicHttpParams(), DEFAULT_HTTP_PARAMS); + + httpClient = new DefaultHttpClient(clientParams){ + @Override + protected HttpRequestRetryHandler createHttpRequestRetryHandler() { + return new DefaultHttpRequestRetryHandler(RETRY_COUNT, false) { + // TODO HACK to fix https://issues.apache.org/jira/browse/HTTPCLIENT-1120 + // can hopefully be removed when 4.1.3 or 4.2 are released + @Override + public boolean retryRequest(IOException ex, int count, HttpContext ctx) { + Object request = ctx.getAttribute(ExecutionContext.HTTP_REQUEST); + if(request instanceof HttpUriRequest){ + if (request instanceof RequestWrapper) { + request = ((RequestWrapper) request).getOriginal(); + } + if(((HttpUriRequest)request).isAborted()){ + log.warn("Workround for HTTPCLIENT-1120 request retry: "+ex); + return false; + } + } + /* + * When connect fails due to abort, the request is not in the context. + * Tried adding the request - with a new key - to the local context in the sample() method, + * but the request was not flagged as aborted, so that did not help. + * So we check for any specific exception that is triggered. + */ + if ( + (ex instanceof java.net.BindException && + ex.getMessage().contains("Address already in use: connect")) + || + ex.getMessage().contains("Request aborted") // plain IOException + ) { + /* + * The above messages may be generated by aborted connects. + * If either occurs in other situations, retrying is unlikely to help, + * so preventing retry should not cause a problem. + */ + log.warn("Workround for HTTPCLIENT-1120 connect retry: "+ex); + return false; + } + return super.retryRequest(ex, count, ctx); + } // end of hack + }; // set retry count + } + }; + ((AbstractHttpClient) httpClient).addResponseInterceptor(new ResponseContentEncoding()); + ((AbstractHttpClient) httpClient).addResponseInterceptor(METRICS_SAVER); // HACK + ((AbstractHttpClient) httpClient).addRequestInterceptor(METRICS_RESETTER); + + // Override the defualt schemes as necessary + SchemeRegistry schemeRegistry = httpClient.getConnectionManager().getSchemeRegistry(); + + if (SLOW_HTTP != null){ + schemeRegistry.register(SLOW_HTTP); + } + + if (HTTPS_SCHEME != null){ + schemeRegistry.register(HTTPS_SCHEME); + } + + // Set up proxy details + if (useDynamicProxy){ + HttpHost proxy = new HttpHost(proxyHost, proxyPort); + clientParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); + String proxyUser = getProxyUser(); + if (proxyUser.length() > 0) { + ((AbstractHttpClient) httpClient).getCredentialsProvider().setCredentials( + new AuthScope(proxyHost, proxyPort), + new UsernamePasswordCredentials(proxyUser, getProxyPass())); + } + } else if (useStaticProxy) { + HttpHost proxy = new HttpHost(PROXY_HOST, PROXY_PORT); + clientParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); + if (PROXY_USER.length() > 0) + ((AbstractHttpClient) httpClient).getCredentialsProvider().setCredentials( + new AuthScope(PROXY_HOST, PROXY_PORT), + new UsernamePasswordCredentials(PROXY_USER, PROXY_PASS)); + } + + // Bug 52126 - we do our own cookie handling + //tms clientParams.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES); + + if (log.isDebugEnabled()) { + log.debug("Created new HttpClient: @"+System.identityHashCode(httpClient)); + } + + map.put(key, httpClient); // save the agent for next time round + } else { + if (log.isDebugEnabled()) { + log.debug("Reusing the HttpClient: @"+System.identityHashCode(httpClient)); + } + } + + // TODO - should this be done when the client is created? + // If so, then the details need to be added as part of HttpClientKey + setConnectionAuthorization(httpClient, url, getAuthManager()); + + return httpClient; + } + + private void setupRequest(URL url, HttpRequestBase httpRequest, HTTPSampleResult res) + throws IOException { + + HttpParams requestParams = httpRequest.getParams(); + + // Set up the local address if one exists + final String ipSource = getIpSource(); + if (ipSource.length() > 0) {// Use special field ip source address (for pseudo 'ip spoofing') + InetAddress inetAddr = InetAddress.getByName(ipSource); + requestParams.setParameter(ConnRoutePNames.LOCAL_ADDRESS, inetAddr); + } else if (localAddress != null){ + requestParams.setParameter(ConnRoutePNames.LOCAL_ADDRESS, localAddress); + } else { // reset in case was set previously + //tms requestParams.removeParameter(ConnRoutePNames.LOCAL_ADDRESS); + } + + int rto = getResponseTimeout(); + if (rto > 0){ + requestParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, rto); + } + + int cto = getConnectTimeout(); + if (cto > 0){ + requestParams.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, cto); + } + + requestParams.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, getAutoRedirects()); + + // a well-behaved browser is supposed to send 'Connection: close' + // with the last request to an HTTP server. Instead, most browsers + // leave it to the server to close the connection after their + // timeout period. Leave it to the JMeter user to decide. + if (getUseKeepAlive()) { + httpRequest.setHeader(HEADER_CONNECTION, KEEP_ALIVE); + } else { + httpRequest.setHeader(HEADER_CONNECTION, CONNECTION_CLOSE); + } + + setConnectionHeaders(httpRequest, url, getHeaderManager(), getCacheManager()); + + String cookies = setConnectionCookie(httpRequest, url, getCookieManager()); + + if (res != null) { + res.setCookies(cookies); + } + +} + + + /** + * Set any default request headers to include + * + * @param request the HttpRequest to be used + */ + protected void setDefaultRequestHeaders(HttpRequest request) { + // Method left empty here, but allows subclasses to override + } + + /** + * Gets the ResponseHeaders + * + * @param response + * containing the headers + * @return string containing the headers, one per line + */ + private String getResponseHeaders(HttpResponse response) { + StringBuilder headerBuf = new StringBuilder(); + Header[] rh = response.getAllHeaders(); + headerBuf.append(response.getStatusLine());// header[0] is not the status line... + headerBuf.append("\n"); // $NON-NLS-1$ + + for (int i = 0; i < rh.length; i++) { + headerBuf.append(rh[i].getName()); + headerBuf.append(": "); // $NON-NLS-1$ + headerBuf.append(rh[i].getValue()); + headerBuf.append("\n"); // $NON-NLS-1$ + } + return headerBuf.toString(); + } + + /** + * Extracts all the required cookies for that particular URL request and + * sets them in the HttpMethod passed in. + * + * @param request HttpRequest for the request + * @param url URL of the request + * @param cookieManager the CookieManager containing all the cookies + * @return a String containing the cookie details (for the response) + * May be null + */ + private String setConnectionCookie(HttpRequest request, URL url, CookieManager cookieManager) { + String cookieHeader = null; + if (cookieManager != null) { + cookieHeader = cookieManager.getCookieHeaderForURL(url); + if (cookieHeader != null) { + request.setHeader(HEADER_COOKIE, cookieHeader); + } + } + return cookieHeader; + } + + /** + * Extracts all the required non-cookie headers for that particular URL request and + * sets them in the HttpMethod passed in + * + * @param request + * HttpRequest which represents the request + * @param url + * URL of the URL request + * @param headerManager + * the HeaderManager containing all the cookies + * for this UrlConfig + * @param cacheManager the CacheManager (may be null) + */ + private void setConnectionHeaders(HttpRequestBase request, URL url, HeaderManager headerManager, CacheManager cacheManager) { + if (headerManager != null) { + CollectionProperty headers = headerManager.getHeaders(); + if (headers != null) { + PropertyIterator i = headers.iterator(); + while (i.hasNext()) { + org.apache.jmeter.protocol.http.control.Header header + = (org.apache.jmeter.protocol.http.control.Header) + i.next().getObjectValue(); + String n = header.getName(); + // Don't allow override of Content-Length + // TODO - what other headers are not allowed? + if (! HEADER_CONTENT_LENGTH.equalsIgnoreCase(n)){ + String v = header.getValue(); + if (HEADER_HOST.equalsIgnoreCase(n)) { + int port = url.getPort(); + v = v.replaceFirst(":\\d+$",""); // remove any port specification // $NON-NLS-1$ $NON-NLS-2$ + if (port != -1) { + if (port == url.getDefaultPort()) { + port = -1; // no need to specify the port if it is the default + } + } + request.getParams().setParameter(ClientPNames.VIRTUAL_HOST, new HttpHost(v, port)); + } else { + request.addHeader(n, v); + } + } + } + } + } + if (cacheManager != null){ + cacheManager.setHeaders(url, request); + } + } + + /** + * Get all the request headers for the HttpMethod + * + * @param method + * HttpMethod which represents the request + * @return the headers as a string + */ + private String getConnectionHeaders(HttpRequest method) { + // Get all the request headers + StringBuilder hdrs = new StringBuilder(100); + Header[] requestHeaders = method.getAllHeaders(); + for(int i = 0; i < requestHeaders.length; i++) { + // Exclude the COOKIE header, since cookie is reported separately in the sample + if(!HEADER_COOKIE.equalsIgnoreCase(requestHeaders[i].getName())) { + hdrs.append(requestHeaders[i].getName()); + hdrs.append(": "); // $NON-NLS-1$ + hdrs.append(requestHeaders[i].getValue()); + hdrs.append("\n"); // $NON-NLS-1$ + } + } + + return hdrs.toString(); + } + + private void setConnectionAuthorization(HttpClient client, URL url, AuthManager authManager) { + CredentialsProvider credentialsProvider = + ((AbstractHttpClient) client).getCredentialsProvider(); + if (authManager != null) { + Authorization auth = authManager.getAuthForURL(url); + if (auth != null) { + String username = auth.getUser(); + String realm = auth.getRealm(); + String domain = auth.getDomain(); + if (log.isDebugEnabled()){ + log.debug(username + " > D="+domain+" R="+realm); + } + credentialsProvider.setCredentials( + new AuthScope(url.getHost(), url.getPort(), realm.length()==0 ? null : realm), + new NTCredentials(username, auth.getPass(), localHost, domain)); + } else { + credentialsProvider.clear(); + } + } else { + credentialsProvider.clear(); + } + } + + // Helper class so we can generate request data without dumping entire file contents + private static class ViewableFileBody extends FileBody { + private boolean hideFileData; + + public ViewableFileBody(File file, String mimeType) { + super(file);//tms , mimeType); + hideFileData = false; + } + + @Override + public void writeTo(final OutputStream out) throws IOException { + if (hideFileData) { + out.write("".getBytes());// encoding does not really matter here + } else { + super.writeTo(out); + } + } + } + + // TODO needs cleaning up + private String sendPostData(HttpPost post) throws IOException { + // Buffer to hold the post body, except file content + StringBuilder postedBody = new StringBuilder(1000); + HTTPFileArg files[] = getHTTPFiles(); + // Check if we should do a multipart/form-data or an + // application/x-www-form-urlencoded post request + if(getUseMultipartForPost()) { + // If a content encoding is specified, we use that as the + // encoding of any parameter values + String contentEncoding = getContentEncoding(); + if(isNullOrEmptyTrimmed(contentEncoding)) { + contentEncoding = null; + } + Charset charset = null; + if(contentEncoding != null) { + charset = Charset.forName(contentEncoding); + } + + // Write the request to our own stream + MultipartEntity multiPart = new MultipartEntity( + getDoBrowserCompatibleMultipart() ? HttpMultipartMode.BROWSER_COMPATIBLE : HttpMultipartMode.STRICT, + null, charset); + // Create the parts + // Add any parameters + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String parameterName = arg.getName(); + if (arg.isSkippable(parameterName)){ + continue; + } + FormBodyPart formPart; + StringBody stringBody = new StringBody(arg.getValue(), + Charset.forName(contentEncoding == null ? "US-ASCII" : contentEncoding)); + formPart = new FormBodyPart(arg.getName(), stringBody); + //tms multiPart.addPart(formPart); + } + + // Add any files + // Cannot retrieve parts once added to the MultiPartEntity, so have to save them here. + ViewableFileBody[] fileBodies = new ViewableFileBody[files.length]; + for (int i=0; i < files.length; i++) { + HTTPFileArg file = files[i]; + fileBodies[i] = new ViewableFileBody(new File(file.getPath()), file.getMimeType()); + multiPart.addPart(file.getParamName(),fileBodies[i]); + } + + post.setEntity(multiPart); + + if (multiPart.isRepeatable()){ + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + for(ViewableFileBody fileBody : fileBodies){ + fileBody.hideFileData = true; + } + multiPart.writeTo(bos); + for(ViewableFileBody fileBody : fileBodies){ + fileBody.hideFileData = false; + } + bos.flush(); + // We get the posted bytes using the encoding used to create it + postedBody.append(new String(bos.toByteArray(), + contentEncoding == null ? "US-ASCII" // $NON-NLS-1$ this is the default used by HttpClient + : contentEncoding)); + bos.close(); + } else { + postedBody.append(""); // $NON-NLS-1$ + } + +// // Set the content type TODO - needed? +// String multiPartContentType = multiPart.getContentType().getValue(); +// post.setHeader(HEADER_CONTENT_TYPE, multiPartContentType); + + } else { // not multipart + // Check if the header manager had a content type header + // This allows the user to specify his own content-type for a POST request + Header contentTypeHeader = post.getFirstHeader(HEADER_CONTENT_TYPE); + boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.getValue() != null && contentTypeHeader.getValue().length() > 0; + // If there are no arguments, we can send a file as the body of the request + // TODO: needs a multiple file upload scenerio + if(!hasArguments() && getSendFileAsPostBody()) { + // If getSendFileAsPostBody returned true, it's sure that file is not null + HTTPFileArg file = files[0]; + if(!hasContentTypeHeader) { + // Allow the mimetype of the file to control the content type + if(file.getMimeType() != null && file.getMimeType().length() > 0) { + post.setHeader(HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + post.setHeader(HEADER_CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED); + } + } + + FileEntity fileRequestEntity = new FileEntity(new File(file.getPath()),(String) null);// TODO is null correct? + post.setEntity(fileRequestEntity); + + // We just add placeholder text for file content + postedBody.append(""); + } else { + // In a post request which is not multipart, we only support + // parameters, no file upload is allowed + + // If a content encoding is specified, we set it as http parameter, so that + // the post body will be encoded in the specified content encoding + String contentEncoding = getContentEncoding(); + boolean haveContentEncoding = false; + if(isNullOrEmptyTrimmed(contentEncoding)) { + contentEncoding=null; + } else { + post.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, contentEncoding); + haveContentEncoding = true; + } + + // If none of the arguments have a name specified, we + // just send all the values as the post body + if(getSendParameterValuesAsPostBody()) { + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + // TODO: needs a multiple file upload scenerio + if(!hasContentTypeHeader) { + HTTPFileArg file = files.length > 0? files[0] : null; + if(file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { + post.setHeader(HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + // TODO - is this the correct default? + post.setHeader(HEADER_CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED); + } + } + + // Just append all the parameter values, and use that as the post body + StringBuilder postBody = new StringBuilder(); + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String value; + if (haveContentEncoding){ + value = arg.getEncodedValue(contentEncoding); + } else { + value = arg.getEncodedValue(); + } + postBody.append(value); + } + /*tms StringEntity requestEntity = new StringEntity (postBody.toString(), post.getFirstHeader(HEADER_CONTENT_TYPE).getValue(), contentEncoding); + post.setEntity(requestEntity);*/ + postedBody.append(postBody.toString()); // TODO OK? + } else { + // It is a normal post request, with parameter names and values + + // Set the content type + if(!hasContentTypeHeader) { + post.setHeader(HEADER_CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED); + } + // Add the parameters + PropertyIterator args = getArguments().iterator(); + List nvps = new ArrayList (); + String urlContentEncoding = contentEncoding; + if(urlContentEncoding == null || urlContentEncoding.length() == 0) { + // Use the default encoding for urls + urlContentEncoding = EncoderCache.URL_ARGUMENT_ENCODING; + } + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + // The HTTPClient always urlencodes both name and value, + // so if the argument is already encoded, we have to decode + // it before adding it to the post request + String parameterName = arg.getName(); + if (arg.isSkippable(parameterName)){ + continue; + } + String parameterValue = arg.getValue(); + if(!arg.isAlwaysEncoded()) { + // The value is already encoded by the user + // Must decode the value now, so that when the + // httpclient encodes it, we end up with the same value + // as the user had entered. + parameterName = URLDecoder.decode(parameterName, urlContentEncoding); + parameterValue = URLDecoder.decode(parameterValue, urlContentEncoding); + } + // Add the parameter, httpclient will urlencode it + nvps.add(new BasicNameValuePair(parameterName, parameterValue)); + } + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(nvps, urlContentEncoding); + post.setEntity(entity); + if (entity.isRepeatable()){ + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + post.getEntity().writeTo(bos); + bos.flush(); + // We get the posted bytes using the encoding used to create it + if (contentEncoding != null) { + postedBody.append(new String(bos.toByteArray(), contentEncoding)); + } else { + postedBody.append(new String(bos.toByteArray(), SampleResult.DEFAULT_HTTP_ENCODING)); + } + bos.close(); + } else { + postedBody.append(""); + } + } + +// // If the request entity is repeatable, we can send it first to +// // our own stream, so we can return it +// if(post.getEntity().isRepeatable()) { +// ByteArrayOutputStream bos = new ByteArrayOutputStream(); +// post.getEntity().writeTo(bos); +// bos.flush(); +// // We get the posted bytes using the encoding used to create it +// if (contentEncoding != null) { +// postedBody.append(new String(bos.toByteArray(), contentEncoding)); +// } else { +// postedBody.append(new String(bos.toByteArray())); +// } +// bos.close(); +// } +// else { +// postedBody.append(""); +// } + } + } + return postedBody.toString(); + } + + // TODO - implementation not fully tested + private String sendPutData(HttpPut put) throws IOException { + // Buffer to hold the put body, except file content + StringBuilder putBody = new StringBuilder(1000); + boolean hasPutBody = false; + + // Check if the header manager had a content type header + // This allows the user to specify his own content-type + Header contentTypeHeader = put.getFirstHeader(HEADER_CONTENT_TYPE); + boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.getValue() != null && contentTypeHeader.getValue().length() > 0; + + // Check for local contentEncoding override + final String contentEncoding = getContentEncoding(); + boolean haveContentEncoding = !isNullOrEmptyTrimmed(contentEncoding); + + HttpParams putParams = put.getParams(); + HTTPFileArg files[] = getHTTPFiles(); + + // If there are no arguments, we can send a file as the body of the request + + if(!hasArguments() && getSendFileAsPostBody()) { + hasPutBody = true; + + // If getSendFileAsPostBody returned true, it's sure that file is not null + FileEntity fileRequestEntity = new FileEntity(new File(files[0].getPath()), (String) null); // TODO is null correct? + put.setEntity(fileRequestEntity); + + // We just add placeholder text for file content + putBody.append(""); + } + // If none of the arguments have a name specified, we + // just send all the values as the put body + else if(getSendParameterValuesAsPostBody()) { + hasPutBody = true; + + // If a content encoding is specified, we set it as http parameter, so that + // the post body will be encoded in the specified content encoding + if(haveContentEncoding) { + putParams.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET,contentEncoding); + } + String charset = getCharsetWithDefault(putParams); + // Just append all the parameter values, and use that as the post body + StringBuilder putBodyContent = new StringBuilder(); + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String value = null; + if (haveContentEncoding){ + value = arg.getEncodedValue(contentEncoding); + } else { + value = arg.getEncodedValue(); + } + putBodyContent.append(value); + } + String contentTypeValue = null; + if(hasContentTypeHeader) { + contentTypeValue = put.getFirstHeader(HEADER_CONTENT_TYPE).getValue(); + } + /*StringEntity requestEntity = new StringEntity(putBodyContent.toString(), contentTypeValue, charset); + put.setEntity(requestEntity);*/ + } + // Check if we have any content to send for body + if(hasPutBody) { + // If the request entity is repeatable, we can send it first to + // our own stream, so we can return it + if(put.getEntity().isRepeatable()) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + put.getEntity().writeTo(bos); + bos.flush(); + String charset = getCharsetWithDefault(putParams); + + // We get the posted bytes using the charset that was used to create them + // if none was set, platform encoding will be used + putBody.append(new String(bos.toByteArray(), charset)); + bos.close(); + } + else { + putBody.append(""); + } + if(!hasContentTypeHeader) { + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + // TODO: needs a multiple file upload scenerio + HTTPFileArg file = files.length > 0? files[0] : null; + if(file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { + put.setHeader(HEADER_CONTENT_TYPE, file.getMimeType()); + } + } + return putBody.toString(); + } + return null; + } + + /** + * If contentEncoding is not set by user, then Platform encoding will be used to convert to String + * @param putParams {@link HttpParams} + * @return String charset + */ + protected String getCharsetWithDefault(HttpParams putParams) { + String charset =(String) putParams.getParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET); + if(StringUtils.isEmpty(charset)) { + charset = Charset.defaultCharset().name(); + } + return charset; + } + + private void saveConnectionCookies(HttpResponse method, URL u, CookieManager cookieManager) { + if (cookieManager != null) { + Header[] hdrs = method.getHeaders(HEADER_SET_COOKIE); + for (Header hdr : hdrs) { + cookieManager.addCookieFromHeader(hdr.getValue(),u); + } + } + } + + @Override + public void threadFinished() { + log.debug("Thread Finished"); + closeThreadLocalConnections(); + } + + /** + * + */ + private void closeThreadLocalConnections() { + // Does not need to be synchronised, as all access is from same thread + Map map = HTTPCLIENTS.get(); + if ( map != null ) { + for ( HttpClient cl : map.values() ) { + ((AbstractHttpClient) cl).clearRequestInterceptors(); + ((AbstractHttpClient) cl).clearResponseInterceptors(); + cl.getConnectionManager().shutdown(); + } + map.clear(); + } + } + + public boolean interrupt() { + HttpUriRequest request = currentRequest; + if (request != null) { + currentRequest = null; // don't try twice + try { + request.abort(); + } catch (UnsupportedOperationException e) { + log.warn("Could not abort pending request", e); + } + } + return request != null; + } + + /** {@inheritDoc} */ + @Override + protected void notifySSLContextWasReset() { + log.debug("closeThreadLocalConnections called"); + closeThreadLocalConnections(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHCAbstractImpl.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHCAbstractImpl.java new file mode 100644 index 0000000..f38fefd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPHCAbstractImpl.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; + +import org.apache.jmeter.JMeter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Common parent class for HttpClient implementations. + * + * Includes system property settings that are handled internally by the Java HTTP implementation, + * but which need to be explicitly configured in HttpClient implementations. + */ +public abstract class HTTPHCAbstractImpl extends HTTPAbstractImpl { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected static final String PROXY_HOST = System.getProperty("http.proxyHost",""); + + protected static final String NONPROXY_HOSTS = System.getProperty("http.nonProxyHosts",""); + + protected static final int PROXY_PORT = Integer.parseInt(System.getProperty("http.proxyPort","0")); + + protected static final boolean PROXY_DEFINED = PROXY_HOST.length() > 0 && PROXY_PORT > 0; + + protected static final String PROXY_USER = JMeterUtils.getPropDefault(JMeter.HTTP_PROXY_USER,""); + + protected static final String PROXY_PASS = JMeterUtils.getPropDefault(JMeter.HTTP_PROXY_PASS,""); + + protected static final String PROXY_DOMAIN = JMeterUtils.getPropDefault("http.proxyDomain",""); + + protected static final InetAddress localAddress; + + protected static final String localHost; + + protected static final Set nonProxyHostFull = new HashSet(); + + protected static final List nonProxyHostSuffix = new ArrayList(); + + protected static final int nonProxyHostSuffixSize; + + protected static final int CPS_HTTP = JMeterUtils.getPropDefault("httpclient.socket.http.cps", 0); + + protected static final int CPS_HTTPS = JMeterUtils.getPropDefault("httpclient.socket.https.cps", 0); + + protected static final boolean USE_LOOPBACK = JMeterUtils.getPropDefault("httpclient.loopback", false); + + protected static final String HTTP_VERSION = JMeterUtils.getPropDefault("httpclient.version", "1.1"); + + // -1 means not defined + protected static final int SO_TIMEOUT = JMeterUtils.getPropDefault("httpclient.timeout", -1); + + static { + if (NONPROXY_HOSTS.length() > 0){ + StringTokenizer s = new StringTokenizer(NONPROXY_HOSTS,"|");// $NON-NLS-1$ + while (s.hasMoreTokens()){ + String t = s.nextToken(); + if (t.indexOf("*") ==0){// e.g. *.apache.org // $NON-NLS-1$ + nonProxyHostSuffix.add(t.substring(1)); + } else { + nonProxyHostFull.add(t);// e.g. www.apache.org + } + } + } + nonProxyHostSuffixSize=nonProxyHostSuffix.size(); + + InetAddress inet=null; + String localHostOrIP = + JMeterUtils.getPropDefault("httpclient.localaddress",""); // $NON-NLS-1$ + if (localHostOrIP.length() > 0){ + try { + inet = InetAddress.getByName(localHostOrIP); + log.info("Using localAddress "+inet.getHostAddress()); + } catch (UnknownHostException e) { + log.warn(e.getLocalizedMessage()); + } + } else { + try { + InetAddress addr = InetAddress.getLocalHost(); + // Get hostname + localHostOrIP = addr.getHostName(); + } catch (UnknownHostException e) { + log.warn("Cannot determine localhost name, and httpclient.localaddress was not specified"); + } + } + localAddress = inet; + localHost = localHostOrIP; + log.info("Local host = "+localHost); + + } + + protected HTTPHCAbstractImpl(HTTPSamplerBase testElement) { + super(testElement); + } + + protected static boolean isNonProxy(String host){ + return nonProxyHostFull.contains(host) || isPartialMatch(host); + } + + protected static boolean isPartialMatch(String host) { + for (int i=0;i 0 && proxyPort > 0); + } + + /** + * Is a static proxy defined? + * + * @param host to check against non-proxy hosts + * @return {@code true} iff a static proxy has been defined. + */ + protected static boolean isStaticProxy(String host){ + return PROXY_DEFINED && !isNonProxy(host); + } + + /** + * @param value String value to test + * @return true if value is null or empty trimmed + */ + protected static boolean isNullOrEmptyTrimmed(String value) { + return value == null || value.trim().length() == 0; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPJavaImpl.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPJavaImpl.java new file mode 100644 index 0000000..b110edc --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPJavaImpl.java @@ -0,0 +1,657 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jmeter.protocol.http.sampler; + +import java.io.BufferedInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import java.net.BindException; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; + +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +import org.apache.commons.io.input.CountingInputStream; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.Authorization; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.HTTPConstantsInterface; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.SSLManager; + +import org.apache.jorphan.logging.LoggingManager; + +import org.apache.log.Logger; + +/** + * A sampler which understands all the parts necessary to read statistics about + * HTTP requests, including cookies and authentication. + * + */ +public class HTTPJavaImpl extends HTTPAbstractImpl { + private static final boolean OBEY_CONTENT_LENGTH = + JMeterUtils.getPropDefault("httpsampler.obey_contentlength", false); // $NON-NLS-1$ + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int MAX_CONN_RETRIES = + JMeterUtils.getPropDefault("http.java.sampler.retries" // $NON-NLS-1$ + ,10); // Maximum connection retries + + static { + log.info("Maximum connection retries = "+MAX_CONN_RETRIES); // $NON-NLS-1$ + // Temporary copies, so can set the final ones + } + + private static final byte[] NULL_BA = new byte[0];// can share these + + /** Handles writing of a post or put request */ + private transient PostWriter postOrPutWriter; + + private volatile HttpURLConnection savedConn; + + protected HTTPJavaImpl(HTTPSamplerBase base) { + super(base); + } + + /** + * Set request headers in preparation to opening a connection. + * + * @param conn + * URLConnection to set headers on + * @exception IOException + * if an I/O exception occurs + */ + protected void setPostHeaders(URLConnection conn) throws IOException { + postOrPutWriter = new PostWriter(); + postOrPutWriter.setHeaders(conn, testElement); + } + + private void setPutHeaders(URLConnection conn) throws IOException { + postOrPutWriter = new PutWriter(); + postOrPutWriter.setHeaders(conn, testElement); + } + + /** + * Send POST data from Entry to the open connection. + * This also handles sending data for PUT requests + * + * @param connection + * URLConnection where POST data should be sent + * @return a String show what was posted. Will not contain actual file upload content + * @exception IOException + * if an I/O exception occurs + */ + protected String sendPostData(URLConnection connection) throws IOException { + return postOrPutWriter.sendPostData(connection, testElement); + } + + private String sendPutData(URLConnection connection) throws IOException { + return postOrPutWriter.sendPostData(connection, testElement); + } + + /** + * Returns an HttpURLConnection fully ready to attempt + * connection. This means it sets the request method (GET or POST), headers, + * cookies, and authorization for the URL request. + *

+ * The request infos are saved into the sample result if one is provided. + * + * @param u + * URL of the URL request + * @param method + * GET, POST etc + * @param res + * sample result to save request infos to + * @return HttpURLConnection ready for .connect + * @exception IOException + * if an I/O Exception occurs + */ + protected HttpURLConnection setupConnection(URL u, String method, HTTPSampleResult res) throws IOException { + SSLManager sslmgr = null; + if (PROTOCOL_HTTPS.equalsIgnoreCase(u.getProtocol())) { + try { + sslmgr=SSLManager.getInstance(); // N.B. this needs to be done before opening the connection + } catch (Exception e) { + log.warn("Problem creating the SSLManager: ", e); + } + } + + final HttpURLConnection conn; + final String proxyHost = getProxyHost(); + final int proxyPort = getProxyPortInt(); + if (proxyHost.length() > 0 && proxyPort > 0){ + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); + //TODO - how to define proxy authentication for a single connection? + // It's not clear if this is possible +// String user = getProxyUser(); +// if (user.length() > 0){ +// Authenticator auth = new ProxyAuthenticator(user, getProxyPass()); +// } + conn = (HttpURLConnection) u.openConnection(proxy); + } else { + conn = (HttpURLConnection) u.openConnection(); + } + + // Update follow redirects setting just for this connection + conn.setInstanceFollowRedirects(getAutoRedirects()); + + int cto = getConnectTimeout(); + if (cto > 0){ + conn.setConnectTimeout(cto); + } + + int rto = getResponseTimeout(); + if (rto > 0){ + conn.setReadTimeout(rto); + } + + if (HTTPConstantsInterface.PROTOCOL_HTTPS.equalsIgnoreCase(u.getProtocol())) { + try { + if (null != sslmgr){ + sslmgr.setContext(conn); // N.B. must be done after opening connection + } + } catch (Exception e) { + log.warn("Problem setting the SSLManager for the connection: ", e); + } + } + + // a well-bahaved browser is supposed to send 'Connection: close' + // with the last request to an HTTP server. Instead, most browsers + // leave it to the server to close the connection after their + // timeout period. Leave it to the JMeter user to decide. + if (getUseKeepAlive()) { + conn.setRequestProperty(HEADER_CONNECTION, KEEP_ALIVE); + } else { + conn.setRequestProperty(HEADER_CONNECTION, CONNECTION_CLOSE); + } + + conn.setRequestMethod(method); + setConnectionHeaders(conn, u, getHeaderManager(), getCacheManager()); + String cookies = setConnectionCookie(conn, u, getCookieManager()); + + setConnectionAuthorization(conn, u, getAuthManager()); + + if (method.equals(POST)) { + setPostHeaders(conn); + } else if (method.equals(PUT)) { + setPutHeaders(conn); + } + + if (res != null) { + res.setRequestHeaders(getConnectionHeaders(conn)); + res.setCookies(cookies); + } + + return conn; + } + + /** + * Reads the response from the URL connection. + * + * @param conn + * URL from which to read response + * @return response content + * @exception IOException + * if an I/O exception occurs + */ + protected byte[] readResponse(HttpURLConnection conn, SampleResult res) throws IOException { + BufferedInputStream in; + + final int contentLength = conn.getContentLength(); + if ((contentLength == 0) + && OBEY_CONTENT_LENGTH) { + log.info("Content-Length: 0, not reading http-body"); + res.setResponseHeaders(getResponseHeaders(conn)); + res.latencyEnd(); + return NULL_BA; + } + + // works OK even if ContentEncoding is null + boolean gzipped = ENCODING_GZIP.equals(conn.getContentEncoding()); + InputStream instream = null; + try { + instream = new CountingInputStream(conn.getInputStream()); + if (gzipped) { + in = new BufferedInputStream(new GZIPInputStream(instream)); + } else { + in = new BufferedInputStream(instream); + } + } catch (IOException e) { + if (! (e.getCause() instanceof FileNotFoundException)) + { + log.error("readResponse: "+e.toString()); + Throwable cause = e.getCause(); + if (cause != null){ + log.error("Cause: "+cause); + if(cause instanceof Error) { + throw (Error)cause; + } + } + } + // Normal InputStream is not available + InputStream errorStream = conn.getErrorStream(); + if (errorStream == null) { + log.info("Error Response Code: "+conn.getResponseCode()+", Server sent no Errorpage"); + res.setResponseHeaders(getResponseHeaders(conn)); + res.latencyEnd(); + return NULL_BA; + } + + log.info("Error Response Code: "+conn.getResponseCode()); + + if (gzipped) { + in = new BufferedInputStream(new GZIPInputStream(errorStream)); + } else { + in = new BufferedInputStream(errorStream); + } + } catch (Exception e) { + log.error("readResponse: "+e.toString()); + Throwable cause = e.getCause(); + if (cause != null){ + log.error("Cause: "+cause); + if(cause instanceof Error) { + throw (Error)cause; + } + } + in = new BufferedInputStream(conn.getErrorStream()); + } + byte[] responseData = readResponse(res, in, contentLength); + if (instream != null) { + res.setBodySize(((CountingInputStream) instream).getCount()); + } + return responseData; + } + + /** + * Gets the ResponseHeaders from the URLConnection + * + * @param conn + * connection from which the headers are read + * @return string containing the headers, one per line + */ + protected String getResponseHeaders(HttpURLConnection conn) { + StringBuilder headerBuf = new StringBuilder(); + headerBuf.append(conn.getHeaderField(0));// Leave header as is + // headerBuf.append(conn.getHeaderField(0).substring(0, 8)); + // headerBuf.append(" "); + // headerBuf.append(conn.getResponseCode()); + // headerBuf.append(" "); + // headerBuf.append(conn.getResponseMessage()); + headerBuf.append("\n"); //$NON-NLS-1$ + + String hfk; + for (int i = 1; (hfk=conn.getHeaderFieldKey(i)) != null; i++) { + headerBuf.append(hfk); + headerBuf.append(": "); // $NON-NLS-1$ + headerBuf.append(conn.getHeaderField(i)); + headerBuf.append("\n"); // $NON-NLS-1$ + } + return headerBuf.toString(); + } + + /** + * Extracts all the required cookies for that particular URL request and + * sets them in the HttpURLConnection passed in. + * + * @param conn + * HttpUrlConnection which represents the URL + * request + * @param u + * URL of the URL request + * @param cookieManager + * the CookieManager containing all the cookies + * for this UrlConfig + */ + private String setConnectionCookie(HttpURLConnection conn, URL u, CookieManager cookieManager) { + String cookieHeader = null; + if (cookieManager != null) { + cookieHeader = cookieManager.getCookieHeaderForURL(u); + if (cookieHeader != null) { + conn.setRequestProperty(HEADER_COOKIE, cookieHeader); + } + } + return cookieHeader; + } + + /** + * Extracts all the required headers for that particular URL request and + * sets them in the HttpURLConnection passed in + * + * @param conn + * HttpUrlConnection which represents the URL + * request + * @param u + * URL of the URL request + * @param headerManager + * the HeaderManager containing all the cookies + * for this UrlConfig + * @param cacheManager the CacheManager (may be null) + */ + private void setConnectionHeaders(HttpURLConnection conn, URL u, HeaderManager headerManager, CacheManager cacheManager) { + // Add all the headers from the HeaderManager + if (headerManager != null) { + CollectionProperty headers = headerManager.getHeaders(); + if (headers != null) { + PropertyIterator i = headers.iterator(); + while (i.hasNext()) { + Header header = (Header) i.next().getObjectValue(); + String n = header.getName(); + String v = header.getValue(); + conn.addRequestProperty(n, v); + } + } + } + if (cacheManager != null){ + cacheManager.setHeaders(conn, u); + } + } + + /** + * Get all the headers for the HttpURLConnection passed in + * + * @param conn + * HttpUrlConnection which represents the URL + * request + * @return the headers as a string + */ + private String getConnectionHeaders(HttpURLConnection conn) { + // Get all the request properties, which are the headers set on the connection + StringBuilder hdrs = new StringBuilder(100); + Map> requestHeaders = conn.getRequestProperties(); + for(Map.Entry> entry : requestHeaders.entrySet()) { + String headerKey=entry.getKey(); + // Exclude the COOKIE header, since cookie is reported separately in the sample + if(!HEADER_COOKIE.equalsIgnoreCase(headerKey)) { + // value is a List of Strings + for (String value : entry.getValue()){ + hdrs.append(headerKey); + hdrs.append(": "); // $NON-NLS-1$ + hdrs.append(value); + hdrs.append("\n"); // $NON-NLS-1$ + } + } + } + return hdrs.toString(); + } + + /** + * Extracts all the required authorization for that particular URL request + * and sets it in the HttpURLConnection passed in. + * + * @param conn + * HttpUrlConnection which represents the URL + * request + * @param u + * URL of the URL request + * @param authManager + * the AuthManager containing all the cookies for + * this UrlConfig + */ + private void setConnectionAuthorization(HttpURLConnection conn, URL u, AuthManager authManager) { + if (authManager != null) { + Authorization auth = authManager.getAuthForURL(u); + if (auth != null) { + conn.setRequestProperty(HEADER_AUTHORIZATION, auth.toBasicHeader()); + } + } + } + + /** + * Samples the URL passed in and stores the result in + * HTTPSampleResult, following redirects and downloading + * page resources as appropriate. + *

+ * When getting a redirect target, redirects are not followed and resources + * are not downloaded. The caller will take care of this. + * + * @param url + * URL to sample + * @param method + * HTTP method: GET, POST,... + * @param areFollowingRedirect + * whether we're getting a redirect target + * @param frameDepth + * Depth of this target in the frame structure. Used only to + * prevent infinite recursion. + * @return results of the sampling + */ + @Override + protected HTTPSampleResult sample(URL url, String method, boolean areFollowingRedirect, int frameDepth) { + HttpURLConnection conn = null; + + String urlStr = url.toString(); + log.debug("Start : sample " + urlStr); + + HTTPSampleResult res = new HTTPSampleResult(); + res.setMonitor(isMonitor()); + + res.setSampleLabel(urlStr); + res.setURL(url); + res.setHTTPMethod(method); + + res.sampleStart(); // Count the retries as well in the time + + // Check cache for an entry with an Expires header in the future + final CacheManager cacheManager = getCacheManager(); + if (cacheManager != null && GET.equalsIgnoreCase(method)) { + if (cacheManager.inCache(url)) { + res.sampleEnd(); + res.setResponseNoContent(); + res.setSuccessful(true); + return res; + } + } + + try { + // Sampling proper - establish the connection and read the response: + // Repeatedly try to connect: + int retry; + // Start with 0 so tries at least once, and retries at most MAX_CONN_RETRIES times + for (retry = 0; retry <= MAX_CONN_RETRIES; retry++) { + try { + conn = setupConnection(url, method, res); + // Attempt the connection: + savedConn = conn; + conn.connect(); + break; + } catch (BindException e) { + if (retry >= MAX_CONN_RETRIES) { + log.error("Can't connect after "+retry+" retries, "+e); + throw e; + } + log.debug("Bind exception, try again"); + if (conn!=null) { + savedConn = null; // we don't want interrupt to try disconnection again + conn.disconnect(); + } + setUseKeepAlive(false); + continue; // try again + } catch (IOException e) { + log.debug("Connection failed, giving up"); + throw e; + } + } + if (retry > MAX_CONN_RETRIES) { + // This should never happen, but... + throw new BindException(); + } + // Nice, we've got a connection. Finish sending the request: + if (method.equals(POST)) { + String postBody = sendPostData(conn); + res.setQueryString(postBody); + } + else if (method.equals(PUT)) { + String putBody = sendPutData(conn); + res.setQueryString(putBody); + } + // Request sent. Now get the response: + byte[] responseData = readResponse(conn, res); + + res.sampleEnd(); + // Done with the sampling proper. + + // Now collect the results into the HTTPSampleResult: + + res.setResponseData(responseData); + + @SuppressWarnings("null") // Cannot be null here + int errorLevel = conn.getResponseCode(); + String respMsg = conn.getResponseMessage(); + String hdr=conn.getHeaderField(0); + if (hdr == null) { + hdr="(null)"; // $NON-NLS-1$ + } + if (errorLevel == -1){// Bug 38902 - sometimes -1 seems to be returned unnecessarily + if (respMsg != null) {// Bug 41902 - NPE + try { + errorLevel = Integer.parseInt(respMsg.substring(0, 3)); + log.warn("ResponseCode==-1; parsed "+respMsg+ " as "+errorLevel); + } catch (NumberFormatException e) { + log.warn("ResponseCode==-1; could not parse "+respMsg+" hdr: "+hdr); + } + } else { + respMsg=hdr; // for result + log.warn("ResponseCode==-1 & null ResponseMessage. Header(0)= "+hdr); + } + } + if (errorLevel == -1) { + res.setResponseCode("(null)"); // $NON-NLS-1$ + } else { + res.setResponseCode(Integer.toString(errorLevel)); + } + res.setSuccessful(isSuccessCode(errorLevel)); + + if (respMsg == null) {// has been seen in a redirect + respMsg=hdr; // use header (if possible) if no message found + } + res.setResponseMessage(respMsg); + + String ct = conn.getContentType(); + if (ct != null){ + res.setContentType(ct);// e.g. text/html; charset=ISO-8859-1 + res.setEncodingAndType(ct); + } + + String responseHeaders = getResponseHeaders(conn); + res.setResponseHeaders(responseHeaders); + if (res.isRedirect()) { + res.setRedirectLocation(conn.getHeaderField(HEADER_LOCATION)); + } + + // record headers size to allow HTTPSampleResult.getBytes() with different options + res.setHeadersSize(responseHeaders.replaceAll("\n", "\r\n") // $NON-NLS-1$ $NON-NLS-2$ + .length() + 2); // add 2 for a '\r\n' at end of headers (before data) + if (log.isDebugEnabled()) { + log.debug("Response headersSize=" + res.getHeadersSize() + " bodySize=" + res.getBodySize() + + " Total=" + (res.getHeadersSize() + res.getBodySize())); + } + + // If we redirected automatically, the URL may have changed + if (getAutoRedirects()){ + res.setURL(conn.getURL()); + } + + // Store any cookies received in the cookie manager: + saveConnectionCookies(conn, url, getCookieManager()); + + // Save cache information + if (cacheManager != null){ + cacheManager.saveDetails(conn, res); + } + + res = resultProcessing(areFollowingRedirect, frameDepth, res); + + log.debug("End : sample"); + return res; + } catch (IOException e) { + res.sampleEnd(); + savedConn = null; // we don't want interrupt to try disconnection again + // We don't want to continue using this connection, even if KeepAlive is set + if (conn != null) { // May not exist + conn.disconnect(); + } + conn=null; // Don't process again + return errorResult(e, res); + } finally { + // calling disconnect doesn't close the connection immediately, + // but indicates we're through with it. The JVM should close + // it when necessary. + savedConn = null; // we don't want interrupt to try disconnection again + disconnect(conn); // Disconnect unless using KeepAlive + } + } + + protected void disconnect(HttpURLConnection conn) { + if (conn != null) { + String connection = conn.getHeaderField(HEADER_CONNECTION); + String protocol = conn.getHeaderField(0); + if ((connection == null && (protocol == null || !protocol.startsWith(HTTP_1_1))) + || (connection != null && connection.equalsIgnoreCase(CONNECTION_CLOSE))) { + conn.disconnect(); + } // TODO ? perhaps note connection so it can be disconnected at end of test? + } + } + + /** + * From the HttpURLConnection, store all the "set-cookie" + * key-pair values in the cookieManager of the UrlConfig. + * + * @param conn + * HttpUrlConnection which represents the URL + * request + * @param u + * URL of the URL request + * @param cookieManager + * the CookieManager containing all the cookies + * for this UrlConfig + */ + private void saveConnectionCookies(HttpURLConnection conn, URL u, CookieManager cookieManager) { + if (cookieManager != null) { + for (int i = 1; conn.getHeaderFieldKey(i) != null; i++) { + if (conn.getHeaderFieldKey(i).equalsIgnoreCase(HEADER_SET_COOKIE)) { + cookieManager.addCookieFromHeader(conn.getHeaderField(i), u); + } + } + } + } + + /** {@inheritDoc} */ + public boolean interrupt() { + HttpURLConnection conn = savedConn; + if (conn != null) { + savedConn = null; + conn.disconnect(); + } + return conn != null; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSampleResult.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSampleResult.java new file mode 100644 index 0000000..4e8c13f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSampleResult.java @@ -0,0 +1,219 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.net.HttpURLConnection; +import java.net.URL; + +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.samplers.SampleResult; + +/** + * This is a specialisation of the SampleResult class for the HTTP protocol. + * + */ +public class HTTPSampleResult extends SampleResult { + + private static final long serialVersionUID = 240L; + + private String cookies = ""; // never null + + private String method; + + /** + * The raw value of the Location: header; may be null. + * This is supposed to be an absolute URL: + * RFC2616 sec14.30 + * but is often relative. + */ + private String redirectLocation; + + private String queryString = ""; // never null + + private static final String HTTP_NO_CONTENT_CODE = Integer.toString(HttpURLConnection.HTTP_NO_CONTENT); + private static final String HTTP_NO_CONTENT_MSG = "No Content"; // $NON-NLS-1$ + + public HTTPSampleResult() { + super(); + } + + public HTTPSampleResult(long elapsed) { + super(elapsed, true); + } + + /** + * Construct a 'parent' result for an already-existing result, essentially + * cloning it + * + * @param res + * existing sample result + */ + public HTTPSampleResult(HTTPSampleResult res) { + super(res); + method=res.method; + cookies=res.cookies; + queryString=res.queryString; + redirectLocation=res.redirectLocation; + } + + public void setHTTPMethod(String method) { + this.method = method; + } + + public String getHTTPMethod() { + return method; + } + + public void setRedirectLocation(String redirectLocation) { + this.redirectLocation = redirectLocation; + } + + public String getRedirectLocation() { + return redirectLocation; + } + + /** + * Determine whether this result is a redirect. + * + * @return true iif res is an HTTP redirect response + */ + public boolean isRedirect() { + final String[] REDIRECT_CODES = { "301", "302", "303" }; // NOT 304! + String code = getResponseCode(); + for (int i = 0; i < REDIRECT_CODES.length; i++) { + if (REDIRECT_CODES[i].equals(code)) { + return true; + } + } + return false; + } + + /** + * Overrides version in Sampler data to provide more details + *

+ * {@inheritDoc} + */ + @Override + public String getSamplerData() { + StringBuilder sb = new StringBuilder(); + sb.append(method); + URL u = super.getURL(); + if (u != null) { + sb.append(' '); + sb.append(u.toString()); + sb.append("\n"); + // Include request body if it is a post or put + if (HTTPConstants.POST.equals(method) || HTTPConstants.PUT.equals(method)) { + sb.append("\n"+method+" data:\n"); + sb.append(queryString); + sb.append("\n"); + } + if (cookies.length()>0){ + sb.append("\nCookie Data:\n"); + sb.append(cookies); + } else { + sb.append("\n[no cookies]"); + } + sb.append("\n"); + } + final String sampData = super.getSamplerData(); + if (sampData != null){ + sb.append(sampData); + } + return sb.toString(); + } + + /** + * @return cookies as a string + */ + public String getCookies() { + return cookies; + } + + /** + * @param string + * representing the cookies + */ + public void setCookies(String string) { + if (string == null) { + cookies="";// $NON-NLS-1$ + } else { + cookies = string; + } + } + + /** + * Fetch the query string + * + * @return the query string + */ + public String getQueryString() { + return queryString; + } + + /** + * Save the query string + * + * @param string + * the query string + */ + public void setQueryString(String string) { + if (string == null ) { + queryString="";// $NON-NLS-1$ + } else { + queryString = string; + } + } + /** + * Overrides the method from SampleResult - so the encoding can be extracted from + * the Meta content-type if necessary. + * + * Updates the dataEncoding field if the content-type is found. + * + * @return the dataEncoding value as a String + */ + @Override + public String getDataEncodingWithDefault() { + if (getDataEncodingNoDefault() == null && getContentType().startsWith("text/html")){ // $NON-NLS-1$ + byte[] bytes=getResponseData(); + // get the start of the file + // TODO - charset? + String prefix = new String(bytes,0,Math.min(bytes.length, 2000)).toLowerCase(java.util.Locale.ENGLISH); + // Extract the content-type if present + final String METATAG = " APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.LoginConfigGui", + "org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui", + "org.apache.jmeter.config.gui.SimpleConfigGui", + "org.apache.jmeter.protocol.http.gui.HeaderPanel", + "org.apache.jmeter.protocol.http.gui.AuthPanel", + "org.apache.jmeter.protocol.http.gui.CacheManagerGui", + "org.apache.jmeter.protocol.http.gui.CookiePanel"})); + + //+ JMX names - do not change + public static final String ARGUMENTS = "HTTPsampler.Arguments"; // $NON-NLS-1$ + + public static final String AUTH_MANAGER = "HTTPSampler.auth_manager"; // $NON-NLS-1$ + + public static final String COOKIE_MANAGER = "HTTPSampler.cookie_manager"; // $NON-NLS-1$ + + public static final String CACHE_MANAGER = "HTTPSampler.cache_manager"; // $NON-NLS-1$ + + public static final String HEADER_MANAGER = "HTTPSampler.header_manager"; // $NON-NLS-1$ + + public static final String DOMAIN = "HTTPSampler.domain"; // $NON-NLS-1$ + + public static final String PORT = "HTTPSampler.port"; // $NON-NLS-1$ + + public static final String PROXYHOST = "HTTPSampler.proxyHost"; // $NON-NLS-1$ + + public static final String PROXYPORT = "HTTPSampler.proxyPort"; // $NON-NLS-1$ + + public static final String PROXYUSER = "HTTPSampler.proxyUser"; // $NON-NLS-1$ + + public static final String PROXYPASS = "HTTPSampler.proxyPass"; // $NON-NLS-1$ + + public static final String CONNECT_TIMEOUT = "HTTPSampler.connect_timeout"; // $NON-NLS-1$ + + public static final String RESPONSE_TIMEOUT = "HTTPSampler.response_timeout"; // $NON-NLS-1$ + + public static final String METHOD = "HTTPSampler.method"; // $NON-NLS-1$ + + public static final String CONTENT_ENCODING = "HTTPSampler.contentEncoding"; // $NON-NLS-1$ + + public static final String IMPLEMENTATION = "HTTPSampler.implementation"; // $NON-NLS-1$ + + public static final String PATH = "HTTPSampler.path"; // $NON-NLS-1$ + + public static final String FOLLOW_REDIRECTS = "HTTPSampler.follow_redirects"; // $NON-NLS-1$ + + public static final String AUTO_REDIRECTS = "HTTPSampler.auto_redirects"; // $NON-NLS-1$ + + public static final String PROTOCOL = "HTTPSampler.protocol"; // $NON-NLS-1$ + + static final String PROTOCOL_FILE = "file"; // $NON-NLS-1$ + + private static final String DEFAULT_PROTOCOL = PROTOCOL_HTTP; + + public static final String URL = "HTTPSampler.URL"; // $NON-NLS-1$ + + /** + * IP source to use - does not apply to Java HTTP implementation currently + */ + public static final String IP_SOURCE = "HTTPSampler.ipSource"; // $NON-NLS-1$ + + public static final String USE_KEEPALIVE = "HTTPSampler.use_keepalive"; // $NON-NLS-1$ + + public static final String DO_MULTIPART_POST = "HTTPSampler.DO_MULTIPART_POST"; // $NON-NLS-1$ + + public static final String BROWSER_COMPATIBLE_MULTIPART = "HTTPSampler.BROWSER_COMPATIBLE_MULTIPART"; // $NON-NLS-1$ + + public static final String CONCURRENT_DWN = "HTTPSampler.concurrentDwn"; // $NON-NLS-1$ + + public static final String CONCURRENT_POOL = "HTTPSampler.concurrentPool"; // $NON-NLS-1$ + + private static final String CONCURRENT_POOL_DEFAULT = "4"; // default for concurrent pool (do not change) + + //- JMX names + + public static final boolean BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT = false; // The default setting to be used (i.e. historic) + + private static final long KEEPALIVETIME = 0; // for Thread Pool for resources but no need to use a special value? + + private static final long AWAIT_TERMINATION_TIMEOUT = + JMeterUtils.getPropDefault("httpsampler.await_termination_timeout", 60); // $NON-NLS-1$ // default value: 60 secs + + private static final boolean IGNORE_FAILED_EMBEDDED_RESOURCES = + JMeterUtils.getPropDefault("httpsampler.ignore_failed_embedded_resources", false); // $NON-NLS-1$ // default value: false + + public static final int CONCURRENT_POOL_SIZE = 4; // Default concurrent pool size for download embedded resources + + + public static final String DEFAULT_METHOD = GET; // $NON-NLS-1$ + // Supported methods: + private static final String [] METHODS = { + DEFAULT_METHOD, // i.e. GET + POST, + HEAD, + PUT, + OPTIONS, + TRACE, + DELETE, + }; + + private static final List METHODLIST = Collections.unmodifiableList(Arrays.asList(METHODS)); + + // @see mergeFileProperties + // Must be private, as the file list needs special handling + private final static String FILE_ARGS = "HTTPsampler.Files"; // $NON-NLS-1$ + // MIMETYPE is kept for backward compatibility with old test plans + private static final String MIMETYPE = "HTTPSampler.mimetype"; // $NON-NLS-1$ + // FILE_NAME is kept for backward compatibility with old test plans + private static final String FILE_NAME = "HTTPSampler.FILE_NAME"; // $NON-NLS-1$ + /* Shown as Parameter Name on the GUI */ + // FILE_FIELD is kept for backward compatibility with old test plans + private static final String FILE_FIELD = "HTTPSampler.FILE_FIELD"; // $NON-NLS-1$ + + public static final String CONTENT_TYPE = "HTTPSampler.CONTENT_TYPE"; // $NON-NLS-1$ + + // IMAGE_PARSER now really means EMBEDDED_PARSER + public static final String IMAGE_PARSER = "HTTPSampler.image_parser"; // $NON-NLS-1$ + + // Embedded URLs must match this RE (if provided) + public static final String EMBEDDED_URL_RE = "HTTPSampler.embedded_url_re"; // $NON-NLS-1$ + + public static final String MONITOR = "HTTPSampler.monitor"; // $NON-NLS-1$ + + // Store MD5 hash instead of storing response + private static final String MD5 = "HTTPSampler.md5"; // $NON-NLS-1$ + + /** A number to indicate that the port has not been set. */ + public static final int UNSPECIFIED_PORT = 0; + public static final String UNSPECIFIED_PORT_AS_STRING = "0"; // $NON-NLS-1$ + // TODO - change to use URL version? Will this affect test plans? + + /** If the port is not present in a URL, getPort() returns -1 */ + public static final int URL_UNSPECIFIED_PORT = -1; + public static final String URL_UNSPECIFIED_PORT_AS_STRING = "-1"; // $NON-NLS-1$ + + protected static final String NON_HTTP_RESPONSE_CODE = "Non HTTP response code"; + + protected static final String NON_HTTP_RESPONSE_MESSAGE = "Non HTTP response message"; + + public static final String POST_BODY_RAW = "HTTPSampler.postBodyRaw"; // TODO - belongs elsewhere + + public static final boolean POST_BODY_RAW_DEFAULT = false; + + private static final String ARG_VAL_SEP = "="; // $NON-NLS-1$ + + private static final String QRY_SEP = "&"; // $NON-NLS-1$ + + private static final String QRY_PFX = "?"; // $NON-NLS-1$ + + protected static final int MAX_REDIRECTS = JMeterUtils.getPropDefault("httpsampler.max_redirects", 5); // $NON-NLS-1$ + + protected static final int MAX_FRAME_DEPTH = JMeterUtils.getPropDefault("httpsampler.max_frame_depth", 5); // $NON-NLS-1$ + + + // Derive the mapping of content types to parsers + private static final Map parsersForType = new HashMap(); + // Not synch, but it is not modified after creation + + private static final String RESPONSE_PARSERS= // list of parsers + JMeterUtils.getProperty("HTTPResponse.parsers");//$NON-NLS-1$ + + // Control reuse of cached SSL Context in subsequent iterations + private static final boolean USE_CACHED_SSL_CONTEXT = + JMeterUtils.getPropDefault("https.use.cached.ssl.context", true);//$NON-NLS-1$ + + static{ + String []parsers = JOrphanUtils.split(RESPONSE_PARSERS, " " , true);// returns empty array for null + for (int i=0;i 0) + && (files[0].getParamName().length() == 0); + } + + /** + * Determine if none of the parameters have a name, and if that + * is the case, it means that the parameter values should be sent + * as the post body + * + * @return true if none of the parameters have a name specified + */ + public boolean getSendParameterValuesAsPostBody() { + if(getPostBodyRaw()) { + return true; + } else { + boolean noArgumentsHasName = true; + PropertyIterator args = getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + if(arg.getName() != null && arg.getName().length() > 0) { + noArgumentsHasName = false; + break; + } + } + return noArgumentsHasName; + } + } + + /** + * Determine if we should use multipart/form-data or + * application/x-www-form-urlencoded for the post + * + * @return true if multipart/form-data should be used and method is POST + */ + public boolean getUseMultipartForPost(){ + // We use multipart if we have been told so, or files are present + // and the files should not be send as the post body + HTTPFileArg[] files = getHTTPFiles(); + if(POST.equals(getMethod()) && (getDoMultipartPost() || (files.length > 0 && !getSendFileAsPostBody()))) { + return true; + } + return false; + } + + public void setProtocol(String value) { + setProperty(PROTOCOL, value.toLowerCase(java.util.Locale.ENGLISH)); + } + + /** + * Gets the protocol, with default. + * + * @return the protocol + */ + public String getProtocol() { + String protocol = getPropertyAsString(PROTOCOL); + if (protocol == null || protocol.length() == 0 ) { + return DEFAULT_PROTOCOL; + } + return protocol; + } + + /** + * Sets the Path attribute of the UrlConfig object Also calls parseArguments + * to extract and store any query arguments + * + * @param path + * The new Path value + */ + public void setPath(String path) { + // We know that URL arguments should always be encoded in UTF-8 according to spec + setPath(path, EncoderCache.URL_ARGUMENT_ENCODING); + } + + /** + * Sets the PATH property; also calls {@link #parseArguments(String, String)} + * to extract and store any query arguments if the request is a GET or DELETE. + * + * @param path + * The new Path value + * @param contentEncoding + * The encoding used for the querystring parameter values + */ + public void setPath(String path, String contentEncoding) { + if (GET.equals(getMethod()) || DELETE.equals(getMethod())) { + int index = path.indexOf(QRY_PFX); + if (index > -1) { + setProperty(PATH, path.substring(0, index)); + // Parse the arguments in querystring, assuming specified encoding for values + parseArguments(path.substring(index + 1), contentEncoding); + } else { + setProperty(PATH, path); + } + } else { + setProperty(PATH, path); + } + } + + public String getPath() { + String p = getPropertyAsString(PATH); + return encodeSpaces(p); + } + + public void setFollowRedirects(boolean value) { + setProperty(new BooleanProperty(FOLLOW_REDIRECTS, value)); + } + + public boolean getFollowRedirects() { + return getPropertyAsBoolean(FOLLOW_REDIRECTS); + } + + public void setAutoRedirects(boolean value) { + setProperty(new BooleanProperty(AUTO_REDIRECTS, value)); + } + + public boolean getAutoRedirects() { + return getPropertyAsBoolean(AUTO_REDIRECTS); + } + + public void setMethod(String value) { + setProperty(METHOD, value); + } + + public String getMethod() { + return getPropertyAsString(METHOD); + } + + public void setContentEncoding(String value) { + setProperty(CONTENT_ENCODING, value); + } + + public String getContentEncoding() { + return getPropertyAsString(CONTENT_ENCODING); + } + + public void setUseKeepAlive(boolean value) { + setProperty(new BooleanProperty(USE_KEEPALIVE, value)); + } + + public boolean getUseKeepAlive() { + return getPropertyAsBoolean(USE_KEEPALIVE); + } + + public void setDoMultipartPost(boolean value) { + setProperty(new BooleanProperty(DO_MULTIPART_POST, value)); + } + + public boolean getDoMultipartPost() { + return getPropertyAsBoolean(DO_MULTIPART_POST, false); + } + + public void setDoBrowserCompatibleMultipart(boolean value) { + setProperty(BROWSER_COMPATIBLE_MULTIPART, value, BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT); + } + + public boolean getDoBrowserCompatibleMultipart() { + return getPropertyAsBoolean(BROWSER_COMPATIBLE_MULTIPART, BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT); + } + + public void setMonitor(String value) { + this.setProperty(MONITOR, value); + } + + public void setMonitor(boolean truth) { + this.setProperty(MONITOR, truth); + } + + public String getMonitor() { + return this.getPropertyAsString(MONITOR); + } + + public boolean isMonitor() { + return this.getPropertyAsBoolean(MONITOR); + } + + public void setImplementation(String value) { + this.setProperty(IMPLEMENTATION, value); + } + + public String getImplementation() { + return this.getPropertyAsString(IMPLEMENTATION); + } + + public boolean useMD5() { + return this.getPropertyAsBoolean(MD5, false); + } + + public void setMD5(boolean truth) { + this.setProperty(MD5, truth, false); + } + + /** + * Add an argument which has already been encoded + */ + public void addEncodedArgument(String name, String value) { + this.addEncodedArgument(name, value, ARG_VAL_SEP); + } + + /** + * Creates an HTTPArgument and adds it to the current set {@link #getArguments()} of arguments. + * + * @param name - the parameter name + * @param value - the parameter value + * @param metaData - normally just '=' + * @param contentEncoding - the encoding, may be null + */ + public void addEncodedArgument(String name, String value, String metaData, String contentEncoding) { + if (log.isDebugEnabled()){ + log.debug("adding argument: name: " + name + " value: " + value + " metaData: " + metaData + " contentEncoding: " + contentEncoding); + } + + HTTPArgument arg = null; + final boolean nonEmptyEncoding = !StringUtils.isEmpty(contentEncoding); + if(nonEmptyEncoding) { + arg = new HTTPArgument(name, value, metaData, true, contentEncoding); + } + else { + arg = new HTTPArgument(name, value, metaData, true); + } + + // Check if there are any difference between name and value and their encoded name and value + String valueEncoded = null; + if(nonEmptyEncoding) { + try { + valueEncoded = arg.getEncodedValue(contentEncoding); + } + catch (UnsupportedEncodingException e) { + log.warn("Unable to get encoded value using encoding " + contentEncoding); + valueEncoded = arg.getEncodedValue(); + } + } + else { + valueEncoded = arg.getEncodedValue(); + } + // If there is no difference, we mark it as not needing encoding + if (arg.getName().equals(arg.getEncodedName()) && arg.getValue().equals(valueEncoded)) { + arg.setAlwaysEncoded(false); + } + this.getArguments().addArgument(arg); + } + + public void addEncodedArgument(String name, String value, String metaData) { + this.addEncodedArgument(name, value, metaData, null); + } + + public void addNonEncodedArgument(String name, String value, String metadata) { + HTTPArgument arg = new HTTPArgument(name, value, metadata, false); + arg.setAlwaysEncoded(false); + this.getArguments().addArgument(arg); + } + + public void addArgument(String name, String value) { + this.getArguments().addArgument(new HTTPArgument(name, value)); + } + + public void addArgument(String name, String value, String metadata) { + this.getArguments().addArgument(new HTTPArgument(name, value, metadata)); + } + + public boolean hasArguments() { + return getArguments().getArgumentCount() > 0; + } + + @Override + public void addTestElement(TestElement el) { + if (el instanceof CookieManager) { + setCookieManager((CookieManager) el); + } else if (el instanceof CacheManager) { + setCacheManager((CacheManager) el); + } else if (el instanceof HeaderManager) { + setHeaderManager((HeaderManager) el); + } else if (el instanceof AuthManager) { + setAuthManager((AuthManager) el); + } else { + super.addTestElement(el); + } + } + + /** + * {@inheritDoc} + *

+ * Clears the Header Manager property so subsequent loops don't keep merging more elements + */ + @Override + public void clearTestElementChildren(){ + removeProperty(HEADER_MANAGER); + } + + public void setPort(int value) { + setProperty(new IntegerProperty(PORT, value)); + } + + /** + * Get the port number for a URL, applying defaults if necessary. + * (Called by CookieManager.) + * @param protocol from {@link URL#getProtocol()} + * @param port number from {@link URL#getPort()} + * @return the default port for the protocol + */ + public static int getDefaultPort(String protocol,int port){ + if (port==URL_UNSPECIFIED_PORT){ + return + protocol.equalsIgnoreCase(PROTOCOL_HTTP) ? DEFAULT_HTTP_PORT : + protocol.equalsIgnoreCase(PROTOCOL_HTTPS) ? DEFAULT_HTTPS_PORT : + port; + } + return port; + } + + /** + * Get the port number from the port string, allowing for trailing blanks. + * + * @return port number or UNSPECIFIED_PORT (== 0) + */ + public int getPortIfSpecified() { + String port_s = getPropertyAsString(PORT, UNSPECIFIED_PORT_AS_STRING); + try { + return Integer.parseInt(port_s.trim()); + } catch (NumberFormatException e) { + return UNSPECIFIED_PORT; + } + } + + /** + * Tell whether the default port for the specified protocol is used + * + * @return true if the default port number for the protocol is used, false otherwise + */ + public boolean isProtocolDefaultPort() { + final int port = getPortIfSpecified(); + final String protocol = getProtocol(); + if (port == UNSPECIFIED_PORT || + (PROTOCOL_HTTP.equalsIgnoreCase(protocol) && port == DEFAULT_HTTP_PORT) || + (PROTOCOL_HTTPS.equalsIgnoreCase(protocol) && port == DEFAULT_HTTPS_PORT)) { + return true; + } + return false; + } + + /** + * Get the port; apply the default for the protocol if necessary. + * + * @return the port number, with default applied if required. + */ + public int getPort() { + final int port = getPortIfSpecified(); + if (port == UNSPECIFIED_PORT) { + String prot = getProtocol(); + if (PROTOCOL_HTTPS.equalsIgnoreCase(prot)) { + return DEFAULT_HTTPS_PORT; + } + if (!PROTOCOL_HTTP.equalsIgnoreCase(prot)) { + log.warn("Unexpected protocol: "+prot); + // TODO - should this return something else? + } + return DEFAULT_HTTP_PORT; + } + return port; + } + + public void setDomain(String value) { + setProperty(DOMAIN, value); + } + + public String getDomain() { + return getPropertyAsString(DOMAIN); + } + + public void setConnectTimeout(String value) { + setProperty(CONNECT_TIMEOUT, value, ""); + } + + public int getConnectTimeout() { + return getPropertyAsInt(CONNECT_TIMEOUT, 0); + } + + public void setResponseTimeout(String value) { + setProperty(RESPONSE_TIMEOUT, value, ""); + } + + public int getResponseTimeout() { + return getPropertyAsInt(RESPONSE_TIMEOUT, 0); + } + + public String getProxyHost() { + return getPropertyAsString(PROXYHOST); + } + + public int getProxyPortInt() { + return getPropertyAsInt(PROXYPORT, 0); + } + + public String getProxyUser() { + return getPropertyAsString(PROXYUSER); + } + + public String getProxyPass() { + return getPropertyAsString(PROXYPASS); + } + + public void setArguments(Arguments value) { + setProperty(new TestElementProperty(ARGUMENTS, value)); + } + + public Arguments getArguments() { + return (Arguments) getProperty(ARGUMENTS).getObjectValue(); + } + + /** + * @param value Boolean that indicates body will be sent as is + */ + public void setPostBodyRaw(boolean value) { + setProperty(POST_BODY_RAW, value, POST_BODY_RAW_DEFAULT); + } + + /** + * @return boolean that indicates body will be sent as is + */ + public boolean getPostBodyRaw() { + return getPropertyAsBoolean(POST_BODY_RAW, POST_BODY_RAW_DEFAULT); + } + + public void setAuthManager(AuthManager value) { + AuthManager mgr = getAuthManager(); + if (mgr != null) { + log.warn("Existing AuthManager " + mgr.getName() + " superseded by " + value.getName()); + } + setProperty(new TestElementProperty(AUTH_MANAGER, value)); + } + + public AuthManager getAuthManager() { + return (AuthManager) getProperty(AUTH_MANAGER).getObjectValue(); + } + + public void setHeaderManager(HeaderManager value) { + HeaderManager mgr = getHeaderManager(); + if (mgr != null) { + value = mgr.merge(value, true); + if (log.isDebugEnabled()) { + log.debug("Existing HeaderManager '" + mgr.getName() + "' merged with '" + value.getName() + "'"); + for (int i=0; i < value.getHeaders().size(); i++) { + log.debug(" " + value.getHeader(i).getName() + "=" + value.getHeader(i).getValue()); + } + } + } + setProperty(new TestElementProperty(HEADER_MANAGER, value)); + } + + public HeaderManager getHeaderManager() { + return (HeaderManager) getProperty(HEADER_MANAGER).getObjectValue(); + } + + public void setCookieManager(CookieManager value) { + CookieManager mgr = getCookieManager(); + if (mgr != null) { + log.warn("Existing CookieManager " + mgr.getName() + " superseded by " + value.getName()); + } + setProperty(new TestElementProperty(COOKIE_MANAGER, value)); + } + + public CookieManager getCookieManager() { + return (CookieManager) getProperty(COOKIE_MANAGER).getObjectValue(); + } + + public void setCacheManager(CacheManager value) { + CacheManager mgr = getCacheManager(); + if (mgr != null) { + log.warn("Existing CacheManager " + mgr.getName() + " superseded by " + value.getName()); + } + setProperty(new TestElementProperty(CACHE_MANAGER, value)); + } + + public CacheManager getCacheManager() { + return (CacheManager) getProperty(CACHE_MANAGER).getObjectValue(); + } + + public boolean isImageParser() { + return getPropertyAsBoolean(IMAGE_PARSER, false); + } + + public void setImageParser(boolean parseImages) { + setProperty(IMAGE_PARSER, parseImages, false); + } + + /** + * Get the regular expression URLs must match. + * + * @return regular expression (or empty) string + */ + public String getEmbeddedUrlRE() { + return getPropertyAsString(EMBEDDED_URL_RE,""); + } + + public void setEmbeddedUrlRE(String regex) { + setProperty(new StringProperty(EMBEDDED_URL_RE, regex)); + } + + /** + * Populates the provided HTTPSampleResult with details from the Exception. + * Does not create a new instance, so should not be used directly to add a subsample. + * + * @param e + * Exception representing the error. + * @param res + * SampleResult to be modified + * @return the modified sampling result containing details of the Exception. + */ + protected HTTPSampleResult errorResult(Throwable e, HTTPSampleResult res) { + res.setSampleLabel("Error: " + res.getSampleLabel()); + res.setDataType(SampleResult.TEXT); + ByteArrayOutputStream text = new ByteArrayOutputStream(200); + e.printStackTrace(new PrintStream(text)); + res.setResponseData(text.toByteArray()); + res.setResponseCode(NON_HTTP_RESPONSE_CODE+": "+e.getClass().getName()); + res.setResponseMessage(NON_HTTP_RESPONSE_MESSAGE+": "+e.getMessage()); + res.setSuccessful(false); + res.setMonitor(this.isMonitor()); + return res; + } + + private static final String HTTP_PREFIX = PROTOCOL_HTTP+"://"; // $NON-NLS-1$ + private static final String HTTPS_PREFIX = PROTOCOL_HTTPS+"://"; // $NON-NLS-1$ + + // Bug 51939 + private static final boolean SEPARATE_CONTAINER = + JMeterUtils.getPropDefault("httpsampler.separate.container", true); // $NON-NLS-1$ + + /** + * Get the URL, built from its component parts. + * + *

+ * As a special case, if the path starts with "http[s]://", + * then the path is assumed to be the entire URL. + *

+ * + * @return The URL to be requested by this sampler. + * @throws MalformedURLException + */ + public URL getUrl() throws MalformedURLException { + StringBuilder pathAndQuery = new StringBuilder(100); + String path = this.getPath(); + // Hack to allow entire URL to be provided in host field + if (path.startsWith(HTTP_PREFIX) + || path.startsWith(HTTPS_PREFIX)){ + return new URL(path); + } + String domain = getDomain(); + String protocol = getProtocol(); + if (PROTOCOL_FILE.equalsIgnoreCase(protocol)) { + domain=null; // allow use of relative file URLs + } else { + // HTTP URLs must be absolute, allow file to be relative + if (!path.startsWith("/")){ // $NON-NLS-1$ + pathAndQuery.append("/"); // $NON-NLS-1$ + } + } + pathAndQuery.append(path); + + // Add the query string if it is a HTTP GET or DELETE request + if(GET.equals(getMethod()) || DELETE.equals(getMethod())) { + // Get the query string encoded in specified encoding + // If no encoding is specified by user, we will get it + // encoded in UTF-8, which is what the HTTP spec says + String queryString = getQueryString(getContentEncoding()); + if(queryString.length() > 0) { + if (path.indexOf(QRY_PFX) > -1) {// Already contains a prefix + pathAndQuery.append(QRY_SEP); + } else { + pathAndQuery.append(QRY_PFX); + } + pathAndQuery.append(queryString); + } + } + // If default port for protocol is used, we do not include port in URL + if(isProtocolDefaultPort()) { + return new URL(protocol, domain, pathAndQuery.toString()); + } + return new URL(protocol, domain, getPort(), pathAndQuery.toString()); + } + + /** + * Gets the QueryString attribute of the UrlConfig object, using + * UTF-8 to encode the URL + * + * @return the QueryString value + */ + public String getQueryString() { + // We use the encoding which should be used according to the HTTP spec, which is UTF-8 + return getQueryString(EncoderCache.URL_ARGUMENT_ENCODING); + } + + /** + * Gets the QueryString attribute of the UrlConfig object, using the + * specified encoding to encode the parameter values put into the URL + * + * @param contentEncoding the encoding to use for encoding parameter values + * @return the QueryString value + */ + public String getQueryString(String contentEncoding) { + // Check if the sampler has a specified content encoding + if(contentEncoding == null || contentEncoding.trim().length() == 0) { + // We use the encoding which should be used according to the HTTP spec, which is UTF-8 + contentEncoding = EncoderCache.URL_ARGUMENT_ENCODING; + } + StringBuilder buf = new StringBuilder(); + PropertyIterator iter = getArguments().iterator(); + boolean first = true; + while (iter.hasNext()) { + HTTPArgument item = null; + /* + * N.B. Revision 323346 introduced the ClassCast check, but then used iter.next() + * to fetch the item to be cast, thus skipping the element that did not cast. + * Reverted to work more like the original code, but with the check in place. + * Added a warning message so can track whether it is necessary + */ + Object objectValue = iter.next().getObjectValue(); + try { + item = (HTTPArgument) objectValue; + } catch (ClassCastException e) { + log.warn("Unexpected argument type: "+objectValue.getClass().getName()); + item = new HTTPArgument((Argument) objectValue); + } + final String encodedName = item.getEncodedName(); + if (encodedName.length() == 0) { + continue; // Skip parameters with a blank name (allows use of optional variables in parameter lists) + } + if (!first) { + buf.append(QRY_SEP); + } else { + first = false; + } + buf.append(encodedName); + if (item.getMetaData() == null) { + buf.append(ARG_VAL_SEP); + } else { + buf.append(item.getMetaData()); + } + + // Encode the parameter value in the specified content encoding + try { + buf.append(item.getEncodedValue(contentEncoding)); + } + catch(UnsupportedEncodingException e) { + log.warn("Unable to encode parameter in encoding " + contentEncoding + ", parameter value not included in query string"); + } + } + return buf.toString(); + } + + // Mark Walsh 2002-08-03, modified to also parse a parameter name value + // string, where string contains only the parameter name and no equal sign. + /** + * This method allows a proxy server to send over the raw text from a + * browser's output stream to be parsed and stored correctly into the + * UrlConfig object. + * + * For each name found, addArgument() is called + * + * @param queryString - + * the query string, might be the post body of a http post request. + * @param contentEncoding - + * the content encoding of the query string; + * if non-null then it is used to decode the + */ + public void parseArguments(String queryString, String contentEncoding) { + String[] args = JOrphanUtils.split(queryString, QRY_SEP); + for (int i = 0; i < args.length; i++) { + // need to handle four cases: + // - string contains name=value + // - string contains name= + // - string contains name + // - empty string + + String metaData; // records the existance of an equal sign + String name; + String value; + int length = args[i].length(); + int endOfNameIndex = args[i].indexOf(ARG_VAL_SEP); + if (endOfNameIndex != -1) {// is there a separator? + // case of name=value, name= + metaData = ARG_VAL_SEP; + name = args[i].substring(0, endOfNameIndex); + value = args[i].substring(endOfNameIndex + 1, length); + } else { + metaData = ""; + name=args[i]; + value=""; + } + if (name.length() > 0) { + // If we know the encoding, we can decode the argument value, + // to make it easier to read for the user + if(!StringUtils.isEmpty(contentEncoding)) { + addEncodedArgument(name, value, metaData, contentEncoding); + } + else { + // If we do not know the encoding, we just use the encoded value + // The browser has already done the encoding, so save the values as is + addNonEncodedArgument(name, value, metaData); + } + } + } + } + + public void parseArguments(String queryString) { + // We do not know the content encoding of the query string + parseArguments(queryString, null); + } + + @Override + public String toString() { + try { + StringBuilder stringBuffer = new StringBuilder(); + stringBuffer.append(this.getUrl().toString()); + // Append body if it is a post or put + if(POST.equals(getMethod()) || PUT.equals(getMethod())) { + stringBuffer.append("\nQuery Data: "); + stringBuffer.append(getQueryString()); + } + return stringBuffer.toString(); + } catch (MalformedURLException e) { + return ""; + } + } + + /** + * Do a sampling and return its results. + * + * @param e + * Entry to be sampled + * @return results of the sampling + */ + public SampleResult sample(Entry e) { + return sample(); + } + + /** + * Perform a sample, and return the results + * + * @return results of the sampling + */ + public SampleResult sample() { + SampleResult res = null; + try { + res = sample(getUrl(), getMethod(), false, 0); + res.setSampleLabel(getName()); + return res; + } catch (Exception e) { + return errorResult(e, new HTTPSampleResult()); + } + } + + /** + * Samples the URL passed in and stores the result in + * HTTPSampleResult, following redirects and downloading + * page resources as appropriate. + *

+ * When getting a redirect target, redirects are not followed and resources + * are not downloaded. The caller will take care of this. + * + * @param u + * URL to sample + * @param method + * HTTP method: GET, POST,... + * @param areFollowingRedirect + * whether we're getting a redirect target + * @param depth + * Depth of this target in the frame structure. Used only to + * prevent infinite recursion. + * @return results of the sampling + */ + protected abstract HTTPSampleResult sample(URL u, + String method, boolean areFollowingRedirect, int depth); + + /** + * Download the resources of an HTML page. + * + * @param res + * result of the initial request - must contain an HTML response + * @param container + * for storing the results, if any + * @param frameDepth + * Depth of this target in the frame structure. Used only to + * prevent infinite recursion. + * @return res if no resources exist, otherwise the "Container" result with one subsample per request issued + */ + protected HTTPSampleResult downloadPageResources(HTTPSampleResult res, HTTPSampleResult container, int frameDepth) { + Iterator urls = null; + try { + final byte[] responseData = res.getResponseData(); + if (responseData.length > 0){ // Bug 39205 + String parserName = getParserClass(res); + if(parserName != null) + { + final HTMLParser parser = + parserName.length() > 0 ? // we have a name + HTMLParser.getParser(parserName) + : + HTMLParser.getParser(); // we don't; use the default parser + urls = parser.getEmbeddedResourceURLs(responseData, res.getURL(), res.getDataEncodingWithDefault()); + } + } + } catch (HTMLParseException e) { + // Don't break the world just because this failed: + res.addSubResult(errorResult(e, new HTTPSampleResult(res))); + setParentSampleSuccess(res, false); + } + + // Iterate through the URLs and download each image: + if (urls != null && urls.hasNext()) { + if (container == null) { + // TODO needed here because currently done on sample completion in JMeterThread, + // but that only catches top-level samples. + res.setThreadName(Thread.currentThread().getName()); + container = new HTTPSampleResult(res); + container.addRawSubResult(res); + } + res = container; + + // Get the URL matcher + String re=getEmbeddedUrlRE(); + Perl5Matcher localMatcher = null; + Pattern pattern = null; + if (re.length()>0){ + try { + pattern = JMeterUtils.getPattern(re); + localMatcher = JMeterUtils.getMatcher();// don't fetch unless pattern compiles + } catch (MalformedCachePatternException e) { + log.warn("Ignoring embedded URL match string: "+e.getMessage()); + } + } + + // For concurrent get resources + final List> liste = new ArrayList>(); + + while (urls.hasNext()) { + Object binURL = urls.next(); // See catch clause below + try { + URL url = (URL) binURL; + if (url == null) { + log.warn("Null URL detected (should not happen)"); + } else { + String urlstr = url.toString(); + String urlStrEnc=encodeSpaces(urlstr); + if (!urlstr.equals(urlStrEnc)){// There were some spaces in the URL + try { + url = new URL(urlStrEnc); + } catch (MalformedURLException e) { + res.addSubResult(errorResult(new Exception(urlStrEnc + " is not a correct URI"), new HTTPSampleResult(res))); + setParentSampleSuccess(res, false); + continue; + } + } + // I don't think localMatcher can be null here, but check just in case + if (pattern != null && localMatcher != null && !localMatcher.matches(urlStrEnc, pattern)) { + continue; // we have a pattern and the URL does not match, so skip it + } + + if (isConcurrentDwn()) { + // if concurrent download emb. resources, add to a list for async gets later + liste.add(new ASyncSample(url, GET, false, frameDepth + 1, getCookieManager(), this)); + } else { + // default: serial download embedded resources + HTTPSampleResult binRes = sample(url, GET, false, frameDepth + 1); + res.addSubResult(binRes); + setParentSampleSuccess(res, res.isSuccessful() && binRes.isSuccessful()); + } + + } + } catch (ClassCastException e) { // TODO can this happen? + res.addSubResult(errorResult(new Exception(binURL + " is not a correct URI"), new HTTPSampleResult(res))); + setParentSampleSuccess(res, false); + continue; + } + } + // IF for download concurrent embedded resources + if (isConcurrentDwn()) { + int poolSize = CONCURRENT_POOL_SIZE; // init with default value + try { + poolSize = Integer.parseInt(getConcurrentPool()); + } catch (NumberFormatException nfe) { + log.warn("Concurrent download resources selected, "// $NON-NLS-1$ + + "but pool size value is bad. Use default value");// $NON-NLS-1$ + } + // Thread pool Executor to get resources + // use a LinkedBlockingQueue, note: max pool size doesn't effect + final ThreadPoolExecutor exec = new ThreadPoolExecutor( + poolSize, poolSize, KEEPALIVETIME, TimeUnit.SECONDS, + new LinkedBlockingQueue(), + new ThreadFactory() { + public Thread newThread(final Runnable r) { + Thread t = new CleanerThread(new Runnable() { + public void run() { + try { + r.run(); + } finally { + ((CleanerThread)Thread.currentThread()).notifyThreadEnd(); + } + } + }); + return t; + } + }); + + boolean tasksCompleted = false; + try { + // sample all resources with threadpool + final List> retExec = exec.invokeAll(liste); + // call normal shutdown (wait ending all tasks) + exec.shutdown(); + // put a timeout if tasks couldn't terminate + exec.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.SECONDS); + CookieManager cookieManager = getCookieManager(); + // add result to main sampleResult + for (Future future : retExec) { + AsynSamplerResultHolder binRes; + try { + binRes = future.get(1, TimeUnit.MILLISECONDS); + if(cookieManager != null) { + CollectionProperty cookies = binRes.getCookies(); + PropertyIterator iter = cookies.iterator(); + while (iter.hasNext()) { + Cookie cookie = (Cookie) iter.next().getObjectValue(); + cookieManager.add(cookie) ; + } + } + res.addSubResult(binRes.getResult()); + setParentSampleSuccess(res, res.isSuccessful() && binRes.getResult().isSuccessful()); + } catch (TimeoutException e) { + errorResult(e, res); + } + } + tasksCompleted = exec.awaitTermination(1, TimeUnit.MILLISECONDS); // did all the tasks finish? + } catch (InterruptedException ie) { + log.warn("Interruped fetching embedded resources", ie); // $NON-NLS-1$ + } catch (ExecutionException ee) { + log.warn("Execution issue when fetching embedded resources", ee); // $NON-NLS-1$ + } finally { + if (!tasksCompleted) { + exec.shutdownNow(); // kill any remaining tasks + } + } + } + } + return res; + } + + /** + * Set parent successful attribute based on IGNORE_FAILED_EMBEDDED_RESOURCES parameter + * @param res {@link HTTPSampleResult} + * @param initialValue boolean + */ + private void setParentSampleSuccess(HTTPSampleResult res, boolean initialValue) { + if(!IGNORE_FAILED_EMBEDDED_RESOURCES) { + res.setSuccessful(initialValue); + } + } + + /* + * @param res HTTPSampleResult to check + * @return parser class name (may be "") or null if entry does not exist + */ + private String getParserClass(HTTPSampleResult res) { + final String ct = res.getMediaType(); + return parsersForType.get(ct); + } + + // TODO: make static? + protected String encodeSpaces(String path) { + return JOrphanUtils.replaceAllChars(path, ' ', "%20"); // $NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + public void testEnded() { + } + + /** + * {@inheritDoc} + */ + public void testEnded(String host) { + testEnded(); + } + + /** + * {@inheritDoc} + */ + public void testIterationStart(LoopIterationEvent event) { + if (!USE_CACHED_SSL_CONTEXT) { + JsseSSLManager sslMgr = (JsseSSLManager) SSLManager.getInstance(); + sslMgr.resetContext(); + notifySSLContextWasReset(); + } + } + + /** + * Called by testIterationStart if the SSL Context was reset. + * + * This implementation does nothing. + */ + protected void notifySSLContextWasReset() { + // NOOP + } + + /** + * {@inheritDoc} + */ + public void testStarted() { + } + + /** + * {@inheritDoc} + */ + public void testStarted(String host) { + testStarted(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object clone() { + HTTPSamplerBase base = (HTTPSamplerBase) super.clone(); + return base; + } + + /** + * Iteratively download the redirect targets of a redirect response. + *

+ * The returned result will contain one subsample for each request issued, + * including the original one that was passed in. It will be an + * HTTPSampleResult that should mostly look as if the final destination of + * the redirect chain had been obtained in a single shot. + * + * @param res + * result of the initial request - must be a redirect response + * @param frameDepth + * Depth of this target in the frame structure. Used only to + * prevent infinite recursion. + * @return "Container" result with one subsample per request issued + */ + protected HTTPSampleResult followRedirects(HTTPSampleResult res, int frameDepth) { + HTTPSampleResult totalRes = new HTTPSampleResult(res); + totalRes.addRawSubResult(res); + HTTPSampleResult lastRes = res; + + int redirect; + for (redirect = 0; redirect < MAX_REDIRECTS; redirect++) { + boolean invalidRedirectUrl = false; + // Browsers seem to tolerate Location headers with spaces, + // replacing them automatically with %20. We want to emulate + // this behaviour. + String location = lastRes.getRedirectLocation(); + if (REMOVESLASHDOTDOT) { + location = ConversionUtils.removeSlashDotDot(location); + } + location = encodeSpaces(location); + try { + lastRes = sample(ConversionUtils.makeRelativeURL(lastRes.getURL(), location), GET, true, frameDepth); + } catch (MalformedURLException e) { + errorResult(e, lastRes); + // The redirect URL we got was not a valid URL + invalidRedirectUrl = true; + } + if (lastRes.getSubResults() != null && lastRes.getSubResults().length > 0) { + SampleResult[] subs = lastRes.getSubResults(); + for (int i = 0; i < subs.length; i++) { + totalRes.addSubResult(subs[i]); + } + } else { + // Only add sample if it is a sample of valid url redirect, i.e. that + // we have actually sampled the URL + if(!invalidRedirectUrl) { + totalRes.addSubResult(lastRes); + } + } + + if (!lastRes.isRedirect()) { + break; + } + } + if (redirect >= MAX_REDIRECTS) { + lastRes = errorResult(new IOException("Exceeeded maximum number of redirects: " + MAX_REDIRECTS), new HTTPSampleResult(lastRes)); + totalRes.addSubResult(lastRes); + } + + // Now populate the any totalRes fields that need to + // come from lastRes: + totalRes.setSampleLabel(totalRes.getSampleLabel() + "->" + lastRes.getSampleLabel()); + // The following three can be discussed: should they be from the + // first request or from the final one? I chose to do it this way + // because that's what browsers do: they show the final URL of the + // redirect chain in the location field. + totalRes.setURL(lastRes.getURL()); + totalRes.setHTTPMethod(lastRes.getHTTPMethod()); + totalRes.setQueryString(lastRes.getQueryString()); + totalRes.setRequestHeaders(lastRes.getRequestHeaders()); + + totalRes.setResponseData(lastRes.getResponseData()); + totalRes.setResponseCode(lastRes.getResponseCode()); + totalRes.setSuccessful(lastRes.isSuccessful()); + totalRes.setResponseMessage(lastRes.getResponseMessage()); + totalRes.setDataType(lastRes.getDataType()); + totalRes.setResponseHeaders(lastRes.getResponseHeaders()); + totalRes.setContentType(lastRes.getContentType()); + totalRes.setDataEncoding(lastRes.getDataEncodingNoDefault()); + return totalRes; + } + + /** + * Follow redirects and download page resources if appropriate. this works, + * but the container stuff here is what's doing it. followRedirects() is + * actually doing the work to make sure we have only one container to make + * this work more naturally, I think this method - sample() - needs to take + * an HTTPSamplerResult container parameter instead of a + * boolean:areFollowingRedirect. + * + * @param areFollowingRedirect + * @param frameDepth + * @param res + * @return the sample result + */ + protected HTTPSampleResult resultProcessing(boolean areFollowingRedirect, int frameDepth, HTTPSampleResult res) { + boolean wasRedirected = false; + if (!areFollowingRedirect) { + if (res.isRedirect()) { + log.debug("Location set to - " + res.getRedirectLocation()); + + if (getFollowRedirects()) { + res = followRedirects(res, frameDepth); + areFollowingRedirect = true; + wasRedirected = true; + } + } + } + if (isImageParser() && (HTTPSampleResult.TEXT).equals(res.getDataType()) && res.isSuccessful()) { + if (frameDepth > MAX_FRAME_DEPTH) { + res.addSubResult(errorResult(new Exception("Maximum frame/iframe nesting depth exceeded."), new HTTPSampleResult(res))); + } else { + // Only download page resources if we were not redirected. + // If we were redirected, the page resources have already been + // downloaded for the sample made for the redirected url + // otherwise, use null so the container is created if necessary unless + // the flag is false, in which case revert to broken 2.1 behaviour + // Bug 51939 - https://issues.apache.org/bugzilla/show_bug.cgi?id=51939 + if(!wasRedirected) { + HTTPSampleResult container = (HTTPSampleResult) ( + areFollowingRedirect ? res.getParent() : SEPARATE_CONTAINER ? null : res); + res = downloadPageResources(res, container, frameDepth); + } + } + } + return res; + } + + /** + * Determine if the HTTP status code is successful or not + * i.e. in range 200 to 399 inclusive + * + * @return whether in range 200-399 or not + */ + protected boolean isSuccessCode(int code){ + return (code >= 200 && code <= 399); + } + + protected static String encodeBackSlashes(String value) { + StringBuilder newValue = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char charAt = value.charAt(i); + if (charAt == '\\') { // $NON-NLS-1$ + newValue.append("\\\\"); // $NON-NLS-1$ + } else { + newValue.append(charAt); + } + } + return newValue.toString(); + } + + /* + * Method to set files list to be uploaded. + * + * @param value + * HTTPFileArgs object that stores file list to be uploaded. + */ + private void setHTTPFileArgs(HTTPFileArgs value) { + if (value.getHTTPFileArgCount() > 0){ + setProperty(new TestElementProperty(FILE_ARGS, value)); + } else { + removeProperty(FILE_ARGS); // no point saving an empty list + } + } + + /* + * Method to get files list to be uploaded. + */ + private HTTPFileArgs getHTTPFileArgs() { + return (HTTPFileArgs) getProperty(FILE_ARGS).getObjectValue(); + } + + /** + * Get the collection of files as a list. + * The list is built up from the filename/filefield/mimetype properties, + * plus any additional entries saved in the FILE_ARGS property. + * + * If there are no valid file entries, then an empty list is returned. + * + * @return an array of file arguments (never null) + */ + public HTTPFileArg[] getHTTPFiles() { + final HTTPFileArgs fileArgs = getHTTPFileArgs(); + return fileArgs == null ? new HTTPFileArg[] {} : fileArgs.asArray(); + } + + public int getHTTPFileCount(){ + return getHTTPFiles().length; + } + /** + * Saves the list of files. + * The first file is saved in the Filename/field/mimetype properties. + * Any additional files are saved in the FILE_ARGS array. + * + * @param files list of files to save + */ + public void setHTTPFiles(HTTPFileArg[] files) { + HTTPFileArgs fileArgs = new HTTPFileArgs(); + // Weed out the empty files + if (files.length > 0) { + for(int i=0; i < files.length; i++){ + HTTPFileArg file = files[i]; + if (file.isNotEmpty()){ + fileArgs.addHTTPFileArg(file); + } + } + } + setHTTPFileArgs(fileArgs); + } + + public static String[] getValidMethodsAsArray(){ + return METHODLIST.toArray(new String[METHODLIST.size()]); + } + + public static boolean isSecure(String protocol){ + return PROTOCOL_HTTPS.equalsIgnoreCase(protocol); + } + + public static boolean isSecure(URL url){ + return isSecure(url.getProtocol()); + } + + // Implement these here, to avoid re-implementing for sub-classes + // (previously these were implemented in all TestElements) + public void threadStarted(){ + } + + public void threadFinished(){ + } + + /** + * Read response from the input stream, converting to MD5 digest if the useMD5 property is set. + * + * For the MD5 case, the result byte count is set to the size of the original response. + * + * Closes the inputStream (unless there was an error) + * + * @param sampleResult + * @param in input stream + * @param length expected input length or zero + * @return the response or the MD5 of the response + * @throws IOException + */ + public byte[] readResponse(SampleResult sampleResult, InputStream in, int length) throws IOException { + + byte[] readBuffer = new byte[8192]; // 8kB is the (max) size to have the latency ('the first packet') + int bufferSize=32;// Enough for MD5 + + MessageDigest md=null; + boolean asMD5 = useMD5(); + if (asMD5) { + try { + md = MessageDigest.getInstance("MD5"); //$NON-NLS-1$ + } catch (NoSuchAlgorithmException e) { + log.error("Should not happen - could not find MD5 digest", e); + asMD5=false; + } + } else { + if (length <= 0) {// may also happen if long value > int.max + bufferSize = 4 * 1024; + } else { + bufferSize = length; + } + } + ByteArrayOutputStream w = new ByteArrayOutputStream(bufferSize); + int bytesRead = 0; + int totalBytes = 0; + boolean first = true; + while ((bytesRead = in.read(readBuffer)) > -1) { + if (first) { + sampleResult.latencyEnd(); + first = false; + } + if (asMD5 && md != null) { + md.update(readBuffer, 0 , bytesRead); + totalBytes += bytesRead; + } else { + w.write(readBuffer, 0, bytesRead); + } + } + if (first){ // Bug 46838 - if there was no data, still need to set latency + sampleResult.latencyEnd(); + } + in.close(); + w.flush(); + if (asMD5 && md != null) { + byte[] md5Result = md.digest(); + w.write(JOrphanUtils.baToHexBytes(md5Result)); + sampleResult.setBytes(totalBytes); + } + w.close(); + return w.toByteArray(); + } + + /** + * JMeter 2.3.1 and earlier only had fields for one file on the GUI: + * - FILE_NAME + * - FILE_FIELD + * - MIMETYPE + * These were stored in their own individual properties. + * + * Version 2.3.3 introduced a list of files, each with their own path, name and mimetype. + * + * In order to maintain backwards compatibility of test plans, the 3 original properties + * were retained; additional file entries are stored in an HTTPFileArgs class. + * The HTTPFileArgs class was only present if there is more than 1 file; this means that + * such test plans are backward compatible. + * + * Versions after 2.3.4 dispense with the original set of 3 properties. + * Test plans that use them are converted to use a single HTTPFileArgs list. + * + * @see HTTPSamplerBaseConverter + */ + void mergeFileProperties() { + JMeterProperty fileName = getProperty(FILE_NAME); + JMeterProperty paramName = getProperty(FILE_FIELD); + JMeterProperty mimeType = getProperty(MIMETYPE); + HTTPFileArg oldStyleFile = new HTTPFileArg(fileName, paramName, mimeType); + + HTTPFileArgs fileArgs = getHTTPFileArgs(); + + HTTPFileArgs allFileArgs = new HTTPFileArgs(); + if(oldStyleFile.isNotEmpty()) { // OK, we have an old-style file definition + allFileArgs.addHTTPFileArg(oldStyleFile); // save it + // Now deal with any additional file arguments + if(fileArgs != null) { + HTTPFileArg[] infiles = fileArgs.asArray(); + for (int i = 0; i < infiles.length; i++){ + allFileArgs.addHTTPFileArg(infiles[i]); + } + } + } else { + if(fileArgs != null) { // for new test plans that don't have FILE/PARAM/MIME properties + allFileArgs = fileArgs; + } + } + // Updated the property lists + setHTTPFileArgs(allFileArgs); + removeProperty(FILE_FIELD); + removeProperty(FILE_NAME); + removeProperty(MIMETYPE); + } + + /** + * set IP source to use - does not apply to Java HTTP implementation currently + */ + public void setIpSource(String value) { + setProperty(IP_SOURCE, value, ""); + } + + /** + * get IP source to use - does not apply to Java HTTP implementation currently + */ + public String getIpSource() { + return getPropertyAsString(IP_SOURCE,""); + } + + /** + * Return if used a concurrent thread pool to get embedded resources. + * + * @return true if used + */ + public boolean isConcurrentDwn() { + return getPropertyAsBoolean(CONCURRENT_DWN, false); + } + + public void setConcurrentDwn(boolean concurrentDwn) { + setProperty(CONCURRENT_DWN, concurrentDwn, false); + } + + /** + * Get the pool size for concurrent thread pool to get embedded resources. + * + * @return the pool size + */ + public String getConcurrentPool() { + return getPropertyAsString(CONCURRENT_POOL,CONCURRENT_POOL_DEFAULT); + } + + public void setConcurrentPool(String poolSize) { + setProperty(CONCURRENT_POOL, poolSize, CONCURRENT_POOL_DEFAULT); + } + + + /** + * Callable class to sample asynchronously resources embedded + * + */ + private static class ASyncSample implements Callable { + final private URL url; + final private String method; + final private boolean areFollowingRedirect; + final private int depth; + private final HTTPSamplerBase sampler; + private final JMeterContext jmeterContextOfParentThread; + + ASyncSample(URL url, String method, + boolean areFollowingRedirect, int depth, CookieManager cookieManager, HTTPSamplerBase base){ + this.url = url; + this.method = method; + this.areFollowingRedirect = areFollowingRedirect; + this.depth = depth; + this.sampler = (HTTPSamplerBase) base.clone(); + // We don't want to use CacheManager clone but the parent one, and CacheManager is Thread Safe + CacheManager cacheManager = base.getCacheManager(); + if (cacheManager != null) { + this.sampler.setCacheManager(cacheManager); + } + + if(cookieManager != null) { + CookieManager clonedCookieManager = (CookieManager) cookieManager.clone(); + this.sampler.setCookieManager(clonedCookieManager); + } + this.jmeterContextOfParentThread = JMeterContextService.getContext(); + } + + public AsynSamplerResultHolder call() { + JMeterContextService.replaceContext(jmeterContextOfParentThread); + ((CleanerThread) Thread.currentThread()).registerSamplerForEndNotification(sampler); + HTTPSampleResult httpSampleResult = sampler.sample(url, method, areFollowingRedirect, depth); + if(sampler.getCookieManager() != null) { + CollectionProperty cookies = sampler.getCookieManager().getCookies(); + return new AsynSamplerResultHolder(httpSampleResult, cookies); + } else { + return new AsynSamplerResultHolder(httpSampleResult, new CollectionProperty()); + } + } + } + + /** + * Custom thread implementation that + * + */ + private static class CleanerThread extends Thread { + private List samplersToNotify = new ArrayList(); + /** + * @param runnable Runnable + */ + public CleanerThread(Runnable runnable) { + super(runnable); + } + + /** + * Notify of thread end + */ + public void notifyThreadEnd() { + for (HTTPSamplerBase samplerBase : samplersToNotify) { + samplerBase.threadFinished(); + } + samplersToNotify.clear(); + } + + /** + * Register sampler to be notify at end of thread + * @param sampler {@link HTTPSamplerBase} + */ + public void registerSamplerForEndNotification(HTTPSamplerBase sampler) { + this.samplersToNotify.add(sampler); + } + } + + /** + * Holder of AsynSampler result + */ + private static class AsynSamplerResultHolder { + private HTTPSampleResult result; + private CollectionProperty cookies; + /** + * @param result + * @param cookies + */ + public AsynSamplerResultHolder(HTTPSampleResult result, CollectionProperty cookies) { + super(); + this.result = result; + this.cookies = cookies; + } + /** + * @return the result + */ + public HTTPSampleResult getResult() { + return result; + } + /** + * @return the cookies + */ + public CollectionProperty getCookies() { + return cookies; + } + } + + + /** + * We search in URL and arguments + * {@inheritDoc}} + */ + @Override + public List getSearchableTokens() throws Exception { + List result = super.getSearchableTokens(); + result.add(getUrl().toExternalForm()); + Arguments arguments = getArguments(); + if(arguments != null) { + for (int i = 0; i < arguments.getArgumentCount(); i++) { + Argument argument = arguments.getArgument(i); + result.add(argument.getName()); + result.add(argument.getValue()); + } + } + return result; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseBeanInfo.java new file mode 100644 index 0000000..8c1cc49 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseBeanInfo.java @@ -0,0 +1,34 @@ +/* + * 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. + * + */ + +/* + * Created on May 24, 2004 + */ +package org.apache.jmeter.protocol.http.sampler; + +import org.apache.jmeter.testelement.AbstractTestElementBeanInfo; + +/** + * This class does not appear to be used. + * However, without it, there are test errors for the AccessLog Sampler: + * unable to find property autoRedirects.displayName. + * + */ +public class HTTPSamplerBaseBeanInfo extends AbstractTestElementBeanInfo { + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java new file mode 100644 index 0000000..1f2bc44 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java @@ -0,0 +1,75 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 14, 2004 + * + */ +package org.apache.jmeter.protocol.http.sampler; + +import org.apache.jmeter.save.converters.TestElementConverter; + +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.mapper.Mapper; + +/** + * Class for XStream conversion of HTTPResult + * + */ +public class HTTPSamplerBaseConverter extends TestElementConverter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 1040356 $"; //$NON-NLS-1$ + } + + public HTTPSamplerBaseConverter(Mapper arg0) { + super(arg0); + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not support types + return HTTPSamplerBase.class.isAssignableFrom(arg0); + } + + /** + * Override TestElementConverter; convert HTTPSamplerBase to merge + * the two means of providing file names into a single list. + * + * {@inheritDoc} + */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final HTTPSamplerBase httpSampler = (HTTPSamplerBase) super.unmarshal(reader, context); + // Help convert existing JMX files which use HTTPSampler[2] nodes + String nodeName = reader.getNodeName(); + if (nodeName.equals(HTTPSamplerFactory.HTTP_SAMPLER_JAVA)){ + httpSampler.setImplementation(HTTPSamplerFactory.IMPL_JAVA); + } + if (nodeName.equals(HTTPSamplerFactory.HTTP_SAMPLER_APACHE)){ + httpSampler.setImplementation(HTTPSamplerFactory.IMPL_HTTP_CLIENT3_1); + } + httpSampler.mergeFileProperties(); + return httpSampler; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerFactory.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerFactory.java new file mode 100644 index 0000000..afa1842 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerFactory.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Factory to return the appropriate HTTPSampler for use with classes that need + * an HTTPSampler; also creates the implementations for use with HTTPSamplerProxy. + * + */ +public class HTTPSamplerFactory { + + // N.B. These values are used in jmeter.properties (jmeter.httpsampler) - do not change + // They can alse be used as the implementation name + /** Use the the default Java HTTP implementation */ + public static final String HTTP_SAMPLER_JAVA = "HTTPSampler"; //$NON-NLS-1$ + + /** Use Apache HTTPClient HTTP implementation */ + public static final String HTTP_SAMPLER_APACHE = "HTTPSampler2"; //$NON-NLS-1$ + + //+ JMX implementation attribute values (also displayed in GUI) - do not change + public static final String IMPL_HTTP_CLIENT4 = "HttpClient4"; // $NON-NLS-1$ + + public static final String IMPL_HTTP_CLIENT3_1 = "HttpClient3.1"; // $NON-NLS-1$ + + public static final String IMPL_JAVA = "Java"; // $NON-NLS-1$ + //- JMX + + public static final String DEFAULT_CLASSNAME = + JMeterUtils.getPropDefault("jmeter.httpsampler", HTTP_SAMPLER_JAVA); //$NON-NLS-1$ + + private HTTPSamplerFactory() { + // Not intended to be instantiated + } + + /** + * Create a new instance of the default sampler + * + * @return instance of default sampler + */ + public static HTTPSamplerBase newInstance() { + return newInstance(DEFAULT_CLASSNAME); + } + + /** + * Create a new instance of the required sampler type + * + * @param alias HTTP_SAMPLER or HTTP_SAMPLER_APACHE or IMPL_HTTP_CLIENT3_1 or IMPL_HTTP_CLIENT4 + * @return the appropriate sampler + * @throws UnsupportedOperationException if alias is not recognised + */ + public static HTTPSamplerBase newInstance(String alias) { + if (alias ==null || alias.length() == 0) { + alias = DEFAULT_CLASSNAME; + } + if (alias.equals(HTTP_SAMPLER_JAVA) || alias.equals(IMPL_JAVA)) { + return new HTTPSamplerProxy(IMPL_JAVA); + } + if (alias.equals(HTTP_SAMPLER_APACHE) || alias.equals(IMPL_HTTP_CLIENT3_1)) { + return new HTTPSamplerProxy(IMPL_HTTP_CLIENT3_1); + } + if (alias.equals(IMPL_HTTP_CLIENT4)) { + return new HTTPSamplerProxy(IMPL_HTTP_CLIENT4); + } + throw new IllegalArgumentException("Unknown sampler type: '" + alias+"'"); + } + + public static String[] getImplementations(){ + return new String[]{IMPL_JAVA, IMPL_HTTP_CLIENT3_1, IMPL_HTTP_CLIENT4}; + } + + public static HTTPAbstractImpl getImplementation(String impl, HTTPSamplerBase base){ + if (HTTPSamplerBase.PROTOCOL_FILE.equals(base.getProtocol())) { + return new HTTPFileImpl(base); + } + if (impl.trim().length() == 0){ + impl = DEFAULT_CLASSNAME; + } + if (IMPL_JAVA.equals(impl) || HTTP_SAMPLER_JAVA.equals(impl)) { + return new HTTPJavaImpl(base); + } else if (IMPL_HTTP_CLIENT3_1.equals(impl) || HTTP_SAMPLER_APACHE.equals(impl)) { + return new HTTPHC3Impl(base); + } else if (IMPL_HTTP_CLIENT4.equals(impl)) { + return new HTTPHC4Impl(base); + } else { + throw new IllegalArgumentException("Unknown implementation type: '"+impl+"'"); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java new file mode 100644 index 0000000..a55bb71 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.net.URL; + +import org.apache.jmeter.samplers.Interruptible; + +/** + * Proxy class that dispatches to the appropriate HTTP sampler. + *

+ * This class is stored in the test plan, and holds all the configuration settings. + * The actual implementation is created at run-time, and is passed a reference to this class + * so it can get access to all the settings stored by HTTPSamplerProxy. + */ +public final class HTTPSamplerProxy extends HTTPSamplerBase implements Interruptible { + + private static final long serialVersionUID = 1L; + + private transient HTTPAbstractImpl impl; + + public HTTPSamplerProxy(){ + super(); + } + + /** + * Convenience method used to initialise the implementation. + * + * @param impl the implementation to use. + */ + public HTTPSamplerProxy(String impl){ + super(); + setImplementation(impl); + } + + /** {@inheritDoc} */ + @Override + protected HTTPSampleResult sample(URL u, String method, boolean areFollowingRedirect, int depth) { + if (impl == null) { // Not called from multiple threads, so this is OK + try { + impl = HTTPSamplerFactory.getImplementation(getImplementation(), this); + } catch (Exception ex) { + return errorResult(ex, new HTTPSampleResult()); + } + } + return impl.sample(u, method, areFollowingRedirect, depth); + } + + // N.B. It's not possible to forward threadStarted() to the implementation class. + // This is because Config items are not processed until later, and HTTPDefaults may define the implementation + + @Override + public void threadFinished(){ + if (impl != null){ + impl.threadFinished(); // Forward to sampler + } + } + + public boolean interrupt() { + if (impl != null) { + return impl.interrupt(); // Forward to sampler + } + return false; + } + + /** + * {@inheritDoc} + * This implementation forwards to the implementation class. + */ + @Override + protected void notifySSLContextWasReset() { + if (impl != null) { + impl.notifySSLContextWasReset(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HttpClientDefaultParameters.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HttpClientDefaultParameters.java new file mode 100644 index 0000000..f9093ac --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/HttpClientDefaultParameters.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/* + * Utility class to set up default HttpClient parameters from a file. + * + * Supports both Commons HttpClient and Apache HttpClient. + * + */ +public class HttpClientDefaultParameters { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Non-instantiable + private HttpClientDefaultParameters(){ + } + + // Helper class (callback) for applying parameter definitions + private static abstract class GenericHttpParams { + public abstract void setParameter(String name, Object value); + public abstract void setVersion(String name, String value) throws Exception; + } + + /** + * Loads a property file and converts parameters as necessary. + * + * @param file the file to load + * @param params Commons HttpClient parameter instance + */ + public static void load(String file, + final org.apache.commons.httpclient.params.HttpParams params){ + load(file, + new GenericHttpParams (){ + @Override + public void setParameter(String name, Object value) { + params.setParameter(name, value); + } + @Override + public void setVersion(String name, String value) throws Exception { + params.setParameter(name, + org.apache.commons.httpclient.HttpVersion.parse("HTTP/"+value)); + } + } + ); + } + + /** + * Loads a property file and converts parameters as necessary. + * + * @param file the file to load + * @param params Apache HttpClient parameter instance + */ + public static void load(String file, + final org.apache.http.params.HttpParams params){ + load(file, + new GenericHttpParams (){ + @Override + public void setParameter(String name, Object value) { + params.setParameter(name, value); + } + + @Override + public void setVersion(String name, String value) { + String parts[] = value.split("\\."); + if (parts.length != 2){ + throw new IllegalArgumentException("Version must have form m.n"); + } + params.setParameter(name, + new org.apache.http.HttpVersion( + Integer.parseInt(parts[0]), Integer.parseInt(parts[1]))); + } + } + ); + } + + private static void load(String file, GenericHttpParams params){ + log.info("Reading httpclient parameters from "+file); + File f = new File(file); + InputStream is = null; + Properties props = new Properties(); + try { + is = new FileInputStream(f); + props.load(is); + for (Map.Entry me : props.entrySet()){ + String key = (String) me.getKey(); + String value = (String)me.getValue(); + int typeSep = key.indexOf("$"); // $NON-NLS-1$ + try { + if (typeSep > 0){ + String type = key.substring(typeSep+1);// get past separator + String name=key.substring(0,typeSep); + log.info("Defining "+name+ " as "+value+" ("+type+")"); + if (type.equals("Integer")){ + params.setParameter(name, Integer.valueOf(value)); + } else if (type.equals("Long")){ + params.setParameter(name, Long.valueOf(value)); + } else if (type.equals("Boolean")){ + params.setParameter(name, Boolean.valueOf(value)); + } else if (type.equals("HttpVersion")){ // Commons HttpClient only + params.setVersion(name, value); + } else { + log.warn("Unexpected type: "+type+" for name "+name); + } + } else { + log.info("Defining "+key+ " as "+value); + params.setParameter(key, value); + } + } catch (Exception e) { + log.error("Error in property: "+key+"="+value+" "+e.toString()); + } + } + } catch (FileNotFoundException e) { + log.error("Problem loading properties "+e.toString()); + } catch (IOException e) { + log.error("Problem loading properties "+e.toString()); + } finally { + JOrphanUtils.closeQuietly(is); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/PostWriter.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/PostWriter.java new file mode 100644 index 0000000..68f5a1d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/PostWriter.java @@ -0,0 +1,458 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLConnection; + +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Class for setting the necessary headers for a POST request, and sending the + * body of the POST. + */ +public class PostWriter { + + private static final String DASH_DASH = "--"; // $NON-NLS-1$ + private static final byte[] DASH_DASH_BYTES = {'-', '-'}; + + /** The bounday string between multiparts */ + protected final static String BOUNDARY = "---------------------------7d159c1302d0y0"; // $NON-NLS-1$ + + private final static byte[] CRLF = { 0x0d, 0x0A }; + + public static final String ENCODING = "ISO-8859-1"; // $NON-NLS-1$ + + /** The form data that is going to be sent as url encoded */ + protected byte[] formDataUrlEncoded; + /** The form data that is going to be sent in post body */ + protected byte[] formDataPostBody; + /** The boundary string for multipart */ + private final String boundary; + + /** + * Constructor for PostWriter. + * Uses the PostWriter.BOUNDARY as the boundary string + * + */ + public PostWriter() { + this(BOUNDARY); + } + + /** + * Constructor for PostWriter + * + * @param boundary the boundary string to use as marker between multipart parts + */ + public PostWriter(String boundary) { + this.boundary = boundary; + } + + /** + * Send POST data from Entry to the open connection. + * + * @return the post body sent. Actual file content is not returned, it + * is just shown as a placeholder text "actual file content" + */ + public String sendPostData(URLConnection connection, HTTPSamplerBase sampler) throws IOException { + // Buffer to hold the post body, except file content + StringBuilder postedBody = new StringBuilder(1000); + + HTTPFileArg files[] = sampler.getHTTPFiles(); + + String contentEncoding = sampler.getContentEncoding(); + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = ENCODING; + } + + // Check if we should do a multipart/form-data or an + // application/x-www-form-urlencoded post request + if(sampler.getUseMultipartForPost()) { + OutputStream out = connection.getOutputStream(); + + // Write the form data post body, which we have constructed + // in the setHeaders. This contains the multipart start divider + // and any form data, i.e. arguments + out.write(formDataPostBody); + // Retrieve the formatted data using the same encoding used to create it + postedBody.append(new String(formDataPostBody, contentEncoding)); + + // Add any files + for (int i=0; i < files.length; i++) { + HTTPFileArg file = files[i]; + // First write the start multipart file + byte[] header = file.getHeader().getBytes(); // TODO - charset? + out.write(header); + // Retrieve the formatted data using the same encoding used to create it + postedBody.append(new String(header)); // TODO - charset? + // Write the actual file content + writeFileToStream(file.getPath(), out); + // We just add placeholder text for file content + postedBody.append(""); // $NON-NLS-1$ + // Write the end of multipart file + byte[] fileMultipartEndDivider = getFileMultipartEndDivider(); + out.write(fileMultipartEndDivider); + // Retrieve the formatted data using the same encoding used to create it + postedBody.append(new String(fileMultipartEndDivider, ENCODING)); + if(i + 1 < files.length) { + out.write(CRLF); + postedBody.append(new String(CRLF, SampleResult.DEFAULT_HTTP_ENCODING)); + } + } + // Write end of multipart + byte[] multipartEndDivider = getMultipartEndDivider(); + out.write(multipartEndDivider); + postedBody.append(new String(multipartEndDivider, ENCODING)); + + out.flush(); + out.close(); + } + else { + // If there are no arguments, we can send a file as the body of the request + if(sampler.getArguments() != null && !sampler.hasArguments() && sampler.getSendFileAsPostBody()) { + OutputStream out = connection.getOutputStream(); + // we're sure that there is at least one file because of + // getSendFileAsPostBody method's return value. + HTTPFileArg file = files[0]; + writeFileToStream(file.getPath(), out); + out.flush(); + out.close(); + + // We just add placeholder text for file content + postedBody.append(""); // $NON-NLS-1$ + } + else if (formDataUrlEncoded != null){ // may be null for PUT + // In an application/x-www-form-urlencoded request, we only support + // parameters, no file upload is allowed + OutputStream out = connection.getOutputStream(); + out.write(formDataUrlEncoded); + out.flush(); + out.close(); + + postedBody.append(new String(formDataUrlEncoded, contentEncoding)); + } + } + return postedBody.toString(); + } + + public void setHeaders(URLConnection connection, HTTPSamplerBase sampler) throws IOException { + // Get the encoding to use for the request + String contentEncoding = sampler.getContentEncoding(); + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = ENCODING; + } + long contentLength = 0L; + HTTPFileArg files[] = sampler.getHTTPFiles(); + + // Check if we should do a multipart/form-data or an + // application/x-www-form-urlencoded post request + if(sampler.getUseMultipartForPost()) { + // Set the content type + connection.setRequestProperty( + HTTPConstants.HEADER_CONTENT_TYPE, + HTTPConstants.MULTIPART_FORM_DATA + "; boundary=" + getBoundary()); // $NON-NLS-1$ + + // Write the form section + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + // First the multipart start divider + bos.write(getMultipartDivider()); + // Add any parameters + PropertyIterator args = sampler.getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + String parameterName = arg.getName(); + if (arg.isSkippable(parameterName)){ + continue; + } + // End the previous multipart + bos.write(CRLF); + // Write multipart for parameter + writeFormMultipart(bos, parameterName, arg.getValue(), contentEncoding, sampler.getDoBrowserCompatibleMultipart()); + } + // If there are any files, we need to end the previous multipart + if(files.length > 0) { + // End the previous multipart + bos.write(CRLF); + } + bos.flush(); + // Keep the content, will be sent later + formDataPostBody = bos.toByteArray(); + bos.close(); + contentLength = formDataPostBody.length; + + // Now we just construct any multipart for the files + // We only construct the file multipart start, we do not write + // the actual file content + for (int i=0; i < files.length; i++) { + HTTPFileArg file = files[i]; + // Write multipart for file + bos = new ByteArrayOutputStream(); + writeStartFileMultipart(bos, file.getPath(), file.getParamName(), file.getMimeType()); + bos.flush(); + String header = bos.toString(contentEncoding);// TODO is this correct? + // If this is not the first file we can't write its header now + // for simplicity we always save it, even if there is only one file + file.setHeader(header); + bos.close(); + contentLength += header.length(); + // Add also the length of the file content + File uploadFile = new File(file.getPath()); + contentLength += uploadFile.length(); + // And the end of the file multipart + contentLength += getFileMultipartEndDivider().length; + if(i+1 < files.length) { + contentLength += CRLF.length; + } + } + + // Add the end of multipart + contentLength += getMultipartEndDivider().length; + + // Set the content length + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_LENGTH, Long.toString(contentLength)); + + // Make the connection ready for sending post data + connection.setDoOutput(true); + connection.setDoInput(true); + } + else { + // Check if the header manager had a content type header + // This allows the user to specify his own content-type for a POST request + String contentTypeHeader = connection.getRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE); + boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.length() > 0; + + // If there are no arguments, we can send a file as the body of the request + if(sampler.getArguments() != null && sampler.getArguments().getArgumentCount() == 0 && sampler.getSendFileAsPostBody()) { + // we're sure that there is one file because of + // getSendFileAsPostBody method's return value. + HTTPFileArg file = files[0]; + if(!hasContentTypeHeader) { + // Allow the mimetype of the file to control the content type + if(file.getMimeType() != null && file.getMimeType().length() > 0) { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + } + // Create the content length we are going to write + File inputFile = new File(file.getPath()); + contentLength = inputFile.length(); + } + else { + // We create the post body content now, so we know the size + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + // If none of the arguments have a name specified, we + // just send all the values as the post body + String postBody = null; + if(!sampler.getSendParameterValuesAsPostBody()) { + // Set the content type + if(!hasContentTypeHeader) { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + + // It is a normal post request, with parameter names and values + postBody = sampler.getQueryString(contentEncoding); + } + else { + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + // TODO: needs a multiple file upload scenerio + if(!hasContentTypeHeader) { + HTTPFileArg file = files.length > 0? files[0] : null; + if(file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); + } + else { + // TODO: is this the correct default? + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); + } + } + + // Just append all the parameter values, and use that as the post body + StringBuilder postBodyBuffer = new StringBuilder(); + PropertyIterator args = sampler.getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + postBodyBuffer.append(arg.getEncodedValue(contentEncoding)); + } + postBody = postBodyBuffer.toString(); + } + + bos.write(postBody.getBytes(contentEncoding)); + bos.flush(); + bos.close(); + + // Keep the content, will be sent later + formDataUrlEncoded = bos.toByteArray(); + contentLength = bos.toByteArray().length; + } + + // Set the content length + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_LENGTH, Long.toString(contentLength)); + + // Make the connection ready for sending post data + connection.setDoOutput(true); + } + } + + /** + * Get the boundary string, used to separate multiparts + * + * @return the boundary string + */ + protected String getBoundary() { + return boundary; + } + + /** + * Get the bytes used to separate multiparts + * Encoded using ENCODING + * + * @return the bytes used to separate multiparts + * @throws IOException + */ + private byte[] getMultipartDivider() throws IOException { + return (DASH_DASH + getBoundary()).getBytes(ENCODING); + } + + /** + * Get the bytes used to end a file multipart + * Encoded using ENCODING + * + * @return the bytes used to end a file multipart + * @throws IOException + */ + private byte[] getFileMultipartEndDivider() throws IOException{ + byte[] ending = getMultipartDivider(); + byte[] completeEnding = new byte[ending.length + CRLF.length]; + System.arraycopy(CRLF, 0, completeEnding, 0, CRLF.length); + System.arraycopy(ending, 0, completeEnding, CRLF.length, ending.length); + return completeEnding; + } + + /** + * Get the bytes used to end the multipart request + * + * @return the bytes used to end the multipart request + */ + private byte[] getMultipartEndDivider(){ + byte[] ending = DASH_DASH_BYTES; + byte[] completeEnding = new byte[ending.length + CRLF.length]; + System.arraycopy(ending, 0, completeEnding, 0, ending.length); + System.arraycopy(CRLF, 0, completeEnding, ending.length, CRLF.length); + return completeEnding; + } + + /** + * Write the start of a file multipart, up to the point where the + * actual file content should be written + */ + private void writeStartFileMultipart(OutputStream out, String filename, + String nameField, String mimetype) + throws IOException { + write(out, "Content-Disposition: form-data; name=\""); // $NON-NLS-1$ + write(out, nameField); + write(out, "\"; filename=\"");// $NON-NLS-1$ + write(out, (new File(filename).getName())); + writeln(out, "\""); // $NON-NLS-1$ + writeln(out, "Content-Type: " + mimetype); // $NON-NLS-1$ + writeln(out, "Content-Transfer-Encoding: binary"); // $NON-NLS-1$ + out.write(CRLF); + } + + /** + * Write the content of a file to the output stream + * + * @param filename the filename of the file to write to the stream + * @param out the stream to write to + * @throws IOException + */ + private static void writeFileToStream(String filename, OutputStream out) throws IOException { + byte[] buf = new byte[1024]; + // 1k - the previous 100k made no sense (there's tons of buffers + // elsewhere in the chain) and it caused OOM when many concurrent + // uploads were being done. Could be fixed by increasing the evacuation + // ratio in bin/jmeter[.bat], but this is better. + InputStream in = new BufferedInputStream(new FileInputStream(filename)); + int read; + boolean noException = false; + try { + while ((read = in.read(buf)) > 0) { + out.write(buf, 0, read); + } + noException = true; + } + finally { + if(!noException) { + // Exception in progress + JOrphanUtils.closeQuietly(in); + } else { + in.close(); + } + } + } + + /** + * Writes form data in multipart format. + */ + private void writeFormMultipart(OutputStream out, String name, String value, String charSet, + boolean browserCompatibleMultipart) + throws IOException { + writeln(out, "Content-Disposition: form-data; name=\"" + name + "\""); // $NON-NLS-1$ // $NON-NLS-2$ + if (!browserCompatibleMultipart){ + writeln(out, "Content-Type: text/plain; charset=" + charSet); // $NON-NLS-1$ + writeln(out, "Content-Transfer-Encoding: 8bit"); // $NON-NLS-1$ + } + out.write(CRLF); + out.write(value.getBytes(charSet)); + out.write(CRLF); + // Write boundary end marker + out.write(getMultipartDivider()); + } + + private void write(OutputStream out, String value) + throws UnsupportedEncodingException, IOException + { + out.write(value.getBytes(ENCODING)); + } + + + private void writeln(OutputStream out, String value) + throws UnsupportedEncodingException, IOException + { + out.write(value.getBytes(ENCODING)); + out.write(CRLF); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/PutWriter.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/PutWriter.java new file mode 100644 index 0000000..d1f8fc9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/PutWriter.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.URLConnection; + +import org.apache.jmeter.protocol.http.util.HTTPArgument; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.testelement.property.PropertyIterator; + +/** + * Class for setting the necessary headers for a PUT request, and sending the + * body of the PUT. + */ +public class PutWriter extends PostWriter { + /** + * Constructor for PutWriter. + */ + public PutWriter() { + // Put request does not use multipart, so no need for boundary + super(null); + } + + @Override + public void setHeaders(URLConnection connection, HTTPSamplerBase sampler) throws IOException { + // Get the encoding to use for the request + String contentEncoding = sampler.getContentEncoding(); + if(contentEncoding == null || contentEncoding.length() == 0) { + contentEncoding = ENCODING; + } + long contentLength = 0L; + boolean hasPutBody = false; + + // Check if the header manager had a content type header + // This allows the user to specify his own content-type for a PUT request + String contentTypeHeader = connection.getRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE); + boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.length() > 0; + + HTTPFileArg files[] = sampler.getHTTPFiles(); + + // If there are no arguments, we can send a file as the body of the request + if(sampler.getArguments() != null && sampler.getArguments().getArgumentCount() == 0 && sampler.getSendFileAsPostBody()) { + // If getSendFileAsPostBody returned true, it's sure that file is not null + HTTPFileArg file = files[0]; + hasPutBody = true; + if(!hasContentTypeHeader) { + // Allow the mimetype of the file to control the content type + if(file.getMimeType().length() > 0) { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); + } + } + + // Create the content length we are going to write + File inputFile = new File(file.getPath()); + contentLength = inputFile.length(); + } + else if(sampler.getSendParameterValuesAsPostBody()) { + hasPutBody = true; + // Allow the mimetype of the file to control the content type + // This is not obvious in GUI if you are not uploading any files, + // but just sending the content of nameless parameters + if(!hasContentTypeHeader && files.length == 1 && files[0].getMimeType().length() > 0) { + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_TYPE, files[0].getMimeType()); + } + + // We create the post body content now, so we know the size + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + // Just append all the parameter values, and use that as the put body + StringBuilder putBodyBuffer = new StringBuilder(); + PropertyIterator args = sampler.getArguments().iterator(); + while (args.hasNext()) { + HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); + putBodyBuffer.append(arg.getEncodedValue(contentEncoding)); + } + + bos.write(putBodyBuffer.toString().getBytes(contentEncoding)); + bos.flush(); + bos.close(); + + // Keep the content, will be sent later + formDataUrlEncoded = bos.toByteArray(); + contentLength = bos.toByteArray().length; + } + if(hasPutBody) { + // Set the content length + connection.setRequestProperty(HTTPConstants.HEADER_CONTENT_LENGTH, Long.toString(contentLength)); + + // Make the connection ready for sending post data + connection.setDoOutput(true); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/SoapSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/SoapSampler.java new file mode 100644 index 0000000..ac9eed2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/SoapSampler.java @@ -0,0 +1,367 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.RequestEntity; +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.protocol.http.control.CacheManager; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.util.HTTPConstants; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.zip.GZIPInputStream; + +/** + * Commons HTTPClient based soap sampler + */ +public class SoapSampler extends HTTPSampler2 implements Interruptible { // Implemented by parent class + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + public static final String XML_DATA = "HTTPSamper.xml_data"; //$NON-NLS-1$ + + public static final String URL_DATA = "SoapSampler.URL_DATA"; //$NON-NLS-1$ + + public static final String SOAP_ACTION = "SoapSampler.SOAP_ACTION"; //$NON-NLS-1$ + + public static final String SEND_SOAP_ACTION = "SoapSampler.SEND_SOAP_ACTION"; //$NON-NLS-1$ + + public static final String XML_DATA_FILE = "SoapSampler.xml_data_file"; //$NON-NLS-1$ + + private static final String DOUBLE_QUOTE = "\""; //$NON-NLS-1$ + + private static final String SOAPACTION = "SOAPAction"; //$NON-NLS-1$ + + private static final String ENCODING = "utf-8"; //$NON-NLS-1$ TODO should this be variable? + + private static final String DEFAULT_CONTENT_TYPE = "text/xml"; //$NON-NLS-1$ + + public void setXmlData(String data) { + setProperty(XML_DATA, data); + } + + public String getXmlData() { + return getPropertyAsString(XML_DATA); + } + + /** + * it's kinda obvious, but we state it anyways. Set the xml file with a + * string path. + * + * @param filename + */ + public void setXmlFile(String filename) { + setProperty(XML_DATA_FILE, filename); + } + + /** + * Get the file location of the xml file. + * + * @return String file path. + */ + public String getXmlFile() { + return getPropertyAsString(XML_DATA_FILE); + } + + public String getURLData() { + return getPropertyAsString(URL_DATA); + } + + public void setURLData(String url) { + setProperty(URL_DATA, url); + } + + public String getSOAPAction() { + return getPropertyAsString(SOAP_ACTION); + } + + public String getSOAPActionQuoted() { + String action = getSOAPAction(); + StringBuilder sb = new StringBuilder(action.length()+2); + sb.append(DOUBLE_QUOTE); + sb.append(action); + sb.append(DOUBLE_QUOTE); + return sb.toString(); + } + + public void setSOAPAction(String action) { + setProperty(SOAP_ACTION, action); + } + + public boolean getSendSOAPAction() { + return getPropertyAsBoolean(SEND_SOAP_ACTION); + } + + public void setSendSOAPAction(boolean action) { + setProperty(SEND_SOAP_ACTION, String.valueOf(action)); + } + + protected int setPostHeaders(PostMethod post) { + int length=0;// Take length from file + if (getHeaderManager() != null) { + // headerManager was set, so let's set the connection + // to use it. + HeaderManager mngr = getHeaderManager(); + int headerSize = mngr.size(); + for (int idx = 0; idx < headerSize; idx++) { + Header hd = mngr.getHeader(idx); + if (HEADER_CONTENT_LENGTH.equalsIgnoreCase(hd.getName())) {// Use this to override file length + length = Integer.parseInt(hd.getValue()); + } + // All the other headers are set up by HTTPSampler2.setupConnection() + } + } else { + // otherwise we use "text/xml" as the default + post.setRequestHeader(HEADER_CONTENT_TYPE, DEFAULT_CONTENT_TYPE); //$NON-NLS-1$ + } + if (getSendSOAPAction()) { + post.setRequestHeader(SOAPACTION, getSOAPActionQuoted()); + } + return length; + } + + /** + * Send POST data from Entry to the open connection. + * + * @param post + * @throws IOException if an I/O exception occurs + */ + private String sendPostData(PostMethod post, final int length) { + // Buffer to hold the post body, except file content + StringBuilder postedBody = new StringBuilder(1000); + final String xmlFile = getXmlFile(); + if (xmlFile != null && xmlFile.length() > 0) { + File xmlFileAsFile = new File(xmlFile); + if(!(xmlFileAsFile.exists() && xmlFileAsFile.canRead())) { + throw new IllegalArgumentException(JMeterUtils.getResString("soap_sampler_file_invalid") // $NON-NLS-1$ + + xmlFileAsFile.getAbsolutePath()); + } + // We just add placeholder text for file content + postedBody.append("Filename: ").append(xmlFile).append("\n"); + postedBody.append(""); + post.setRequestEntity(new RequestEntity() { + public boolean isRepeatable() { + return true; + } + + public void writeRequest(OutputStream out) throws IOException { + InputStream in = null; + try{ + in = new FileInputStream(xmlFile); + IOUtils.copy(in, out); + out.flush(); + } finally { + IOUtils.closeQuietly(in); + } + } + + public long getContentLength() { + switch(length){ + case -1: + return -1; + case 0: // No header provided + return (new File(xmlFile)).length(); + default: + return length; + } + } + + public String getContentType() { + // TODO do we need to add a charset for the file contents? + return DEFAULT_CONTENT_TYPE; // $NON-NLS-1$ + } + }); + } else { + postedBody.append(getXmlData()); + post.setRequestEntity(new RequestEntity() { + public boolean isRepeatable() { + return true; + } + + public void writeRequest(OutputStream out) throws IOException { + // charset must agree with content-type below + IOUtils.write(getXmlData(), out, ENCODING); // $NON-NLS-1$ + out.flush(); + } + + public long getContentLength() { + try { + return getXmlData().getBytes(ENCODING).length; // so we don't generate chunked encoding + } catch (UnsupportedEncodingException e) { + log.warn(e.getLocalizedMessage()); + return -1; // will use chunked encoding + } + } + + public String getContentType() { + return DEFAULT_CONTENT_TYPE+"; charset="+ENCODING; // $NON-NLS-1$ + } + }); + } + return postedBody.toString(); + } + + @Override + protected HTTPSampleResult sample(URL url, String method, boolean areFollowingRedirect, int frameDepth) { + + String urlStr = url.toString(); + + log.debug("Start : sample " + urlStr); + + PostMethod httpMethod; + httpMethod = new PostMethod(urlStr); + + HTTPSampleResult res = new HTTPSampleResult(); + res.setMonitor(false); + + res.setSampleLabel(urlStr); // May be replaced later + res.setHTTPMethod(HTTPConstants.POST); + res.setURL(url); + res.sampleStart(); // Count the retries as well in the time + HttpClient client = null; + InputStream instream = null; + try { + int content_len = setPostHeaders(httpMethod); + client = setupConnection(url, httpMethod, res); + setSavedClient(client); + + res.setQueryString(sendPostData(httpMethod,content_len)); + int statusCode = client.executeMethod(httpMethod); + // Some headers are set by executeMethod() + res.setRequestHeaders(getConnectionHeaders(httpMethod)); + + // Request sent. Now get the response: + instream = httpMethod.getResponseBodyAsStream(); + + if (instream != null) {// will be null for HEAD + + org.apache.commons.httpclient.Header responseHeader = httpMethod.getResponseHeader(HEADER_CONTENT_ENCODING); + if (responseHeader != null && ENCODING_GZIP.equals(responseHeader.getValue())) { + instream = new GZIPInputStream(instream); + } + + //int contentLength = httpMethod.getResponseContentLength();Not visible ... + //TODO size ouststream according to actual content length + ByteArrayOutputStream outstream = new ByteArrayOutputStream(4 * 1024); + //contentLength > 0 ? contentLength : DEFAULT_INITIAL_BUFFER_SIZE); + byte[] buffer = new byte[4096]; + int len; + boolean first = true;// first response + while ((len = instream.read(buffer)) > 0) { + if (first) { // save the latency + res.latencyEnd(); + first = false; + } + outstream.write(buffer, 0, len); + } + + res.setResponseData(outstream.toByteArray()); + outstream.close(); + + } + + res.sampleEnd(); + // Done with the sampling proper. + + // Now collect the results into the HTTPSampleResult: + + res.setSampleLabel(httpMethod.getURI().toString()); + // Pick up Actual path (after redirects) + + res.setResponseCode(Integer.toString(statusCode)); + res.setSuccessful(isSuccessCode(statusCode)); + + res.setResponseMessage(httpMethod.getStatusText()); + + // Set up the defaults (may be overridden below) + res.setDataEncoding(ENCODING); + res.setContentType(DEFAULT_CONTENT_TYPE); + String ct = null; + org.apache.commons.httpclient.Header h + = httpMethod.getResponseHeader(HEADER_CONTENT_TYPE); + if (h != null)// Can be missing, e.g. on redirect + { + ct = h.getValue(); + res.setContentType(ct);// e.g. text/html; charset=ISO-8859-1 + res.setEncodingAndType(ct); + } + + res.setResponseHeaders(getResponseHeaders(httpMethod)); + if (res.isRedirect()) { + res.setRedirectLocation(httpMethod.getResponseHeader(HEADER_LOCATION).getValue()); + } + + // If we redirected automatically, the URL may have changed + if (getAutoRedirects()) { + res.setURL(new URL(httpMethod.getURI().toString())); + } + + // Store any cookies received in the cookie manager: + saveConnectionCookies(httpMethod, res.getURL(), getCookieManager()); + + // Save cache information + final CacheManager cacheManager = getCacheManager(); + if (cacheManager != null){ + cacheManager.saveDetails(httpMethod, res); + } + + // Follow redirects and download page resources if appropriate: + res = resultProcessing(areFollowingRedirect, frameDepth, res); + + log.debug("End : sample"); + httpMethod.releaseConnection(); + return res; + } catch (IllegalArgumentException e)// e.g. some kinds of invalid URL + { + res.sampleEnd(); + errorResult(e, res); + return res; + } catch (IOException e) { + res.sampleEnd(); + errorResult(e, res); + return res; + } finally { + JOrphanUtils.closeQuietly(instream); + setSavedClient(null); + httpMethod.releaseConnection(); + } + } + + @Override + public URL getUrl() throws MalformedURLException { + return new URL(getURLData()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/WebServiceSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/WebServiceSampler.java new file mode 100644 index 0000000..8eb8729 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/sampler/WebServiceSampler.java @@ -0,0 +1,693 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.sampler; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Random; +//tms this idea didn't work import javax.mail.*; + +import javax.xml.parsers.DocumentBuilder; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.JMeter; +import org.apache.jmeter.gui.JMeterFileFilter; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jmeter.protocol.http.control.Authorization; +import org.apache.jmeter.protocol.http.util.DOMPool; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.io.TextFile; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.soap.Envelope; +import org.apache.soap.SOAPException; +import org.apache.soap.messaging.Message; +import org.apache.soap.rpc.SOAPContext; +import org.apache.soap.transport.http.SOAPHTTPConnection; +import org.apache.soap.util.xml.XMLParserUtils; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Sampler to handle Web Service requests. It uses Apache SOAP drivers to + * perform the XML generation, connection, SOAP encoding and other SOAP + * functions. + *

+ * Created on: Jun 26, 2003 + * + */ +public class WebServiceSampler extends HTTPSamplerBase { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + //+ JMX file attribut names - do not change! + private static final String XML_DATA = "HTTPSamper.xml_data"; //$NON-NLS-1$ + + private static final String SOAP_ACTION = "Soap.Action"; //$NON-NLS-1$ + + private static final String XML_DATA_FILE = "WebServiceSampler.xml_data_file"; //$NON-NLS-1$ + + private static final String XML_PATH_LOC = "WebServiceSampler.xml_path_loc"; //$NON-NLS-1$ + + private static final String MEMORY_CACHE = "WebServiceSampler.memory_cache"; //$NON-NLS-1$ + + private static final String MAINTAIN_SESSION = "WebServiceSampler.maintain_session"; //$NON-NLS-1$ + + private static final String READ_RESPONSE = "WebServiceSampler.read_response"; //$NON-NLS-1$ + + private static final String USE_PROXY = "WebServiceSampler.use_proxy"; //$NON-NLS-1$ + + private static final String PROXY_HOST = "WebServiceSampler.proxy_host"; //$NON-NLS-1$ + + private static final String PROXY_PORT = "WebServiceSampler.proxy_port"; //$NON-NLS-1$ + + private static final String WSDL_URL = "WebserviceSampler.wsdl_url"; //$NON-NLS-1$ + + private static final String TIMEOUT = "WebserviceSampler.timeout"; //$NON-NLS-1$ + //- JMX file attribut names - do not change! + + private static final String PROXY_USER = + JMeterUtils.getPropDefault(JMeter.HTTP_PROXY_USER,""); // $NON-NLS-1$ + + private static final String PROXY_PASS = + JMeterUtils.getPropDefault(JMeter.HTTP_PROXY_PASS,""); // $NON-NLS-1$ + + private static final String ENCODING = "UTF-8"; // $NON-NLS-1$ TODO should this be a variable? + + public static final boolean MAINTAIN_SESSION_DEFAULT = true; + + /* + * Random class for generating random numbers. + */ + private final Random RANDOM = new Random(); + + private String fileContents = null; + + /** + * Set the path where XML messages are stored for random selection. + */ + public void setXmlPathLoc(String path) { + setProperty(XML_PATH_LOC, path); + } + + /** + * Get the path where XML messages are stored. this is the directory where + * JMeter will randomly select a file. + */ + public String getXmlPathLoc() { + return getPropertyAsString(XML_PATH_LOC); + } + + /** + * it's kinda obvious, but we state it anyways. Set the xml file with a + * string path. + * + * @param filename + */ + public void setXmlFile(String filename) { + setProperty(XML_DATA_FILE, filename); + } + + /** + * Get the file location of the xml file. + * + * @return String file path. + */ + public String getXmlFile() { + return getPropertyAsString(XML_DATA_FILE); + } + + /** + * Method is used internally to check if a random file should be used for + * the message. Messages must be valid. This is one way to load test with + * different messages. The limitation of this approach is parsing XML takes + * CPU resources, so it could affect JMeter GUI responsiveness. + * + * @return String filename + */ + protected String getRandomFileName() { + if (this.getXmlPathLoc() != null) { + File src = new File(this.getXmlPathLoc()); + if (src.isDirectory() && src.list() != null) { + File [] fileList = src.listFiles(new JMeterFileFilter(new String[] { ".xml" }, false)); + File one = fileList[RANDOM.nextInt(fileList.length)]; + // return the absolutePath of the file + return one.getAbsolutePath(); + } + return getXmlFile(); + } + return getXmlFile(); + } + + /** + * Set the XML data. + * + * @param data + */ + public void setXmlData(String data) { + setProperty(XML_DATA, data); + } + + /** + * Get the XML data as a string. + * + * @return String data + */ + public String getXmlData() { + return getPropertyAsString(XML_DATA); + } + + /** + * Set the soap action which should be in the form of an URN. + * + * @param data + */ + public void setSoapAction(String data) { + setProperty(SOAP_ACTION, data); + } + + /** + * Return the soap action string. + * + * @return String soap action + */ + public String getSoapAction() { + return getPropertyAsString(SOAP_ACTION); + } + + /** + * Set the maintain session option. + * + * @param maintainSession + */ + public void setMaintainSession(boolean maintainSession) { + setProperty(MAINTAIN_SESSION, maintainSession, MAINTAIN_SESSION_DEFAULT); + } + + /** + * Get the maintain session option. + * + * @return boolean cache + */ + public boolean getMaintainSession() { + return getPropertyAsBoolean(MAINTAIN_SESSION, MAINTAIN_SESSION_DEFAULT); + } + + /** + * Set the memory cache. + * + * @param cache + */ + public void setMemoryCache(boolean cache) { + setProperty(MEMORY_CACHE, String.valueOf(cache)); + } + + /** + * Get the memory cache. + * + * @return boolean cache + */ + public boolean getMemoryCache() { + return getPropertyAsBoolean(MEMORY_CACHE); + } + + /** + * Set whether the sampler should read the response or not. + * + * @param read + */ + public void setReadResponse(boolean read) { + setProperty(READ_RESPONSE, String.valueOf(read)); + } + + /** + * Return whether or not to read the response. + * + * @return boolean + */ + public boolean getReadResponse() { + return this.getPropertyAsBoolean(READ_RESPONSE); + } + + /** + * Set whether or not to use a proxy + * + * @param proxy + */ + public void setUseProxy(boolean proxy) { + setProperty(USE_PROXY, String.valueOf(proxy)); + } + + /** + * Return whether or not to use proxy + * + * @return true if should use proxy + */ + public boolean getUseProxy() { + return this.getPropertyAsBoolean(USE_PROXY); + } + + /** + * Set the proxy hostname + * + * @param host + */ + public void setProxyHost(String host) { + setProperty(PROXY_HOST, host); + } + + /** + * Return the proxy hostname + * + * @return the proxy hostname + */ + @Override + public String getProxyHost() { + this.checkProxy(); + return this.getPropertyAsString(PROXY_HOST); + } + + /** + * Set the proxy port + * + * @param port + */ + public void setProxyPort(String port) { + setProperty(PROXY_PORT, port); + } + + /** + * Return the proxy port + * + * @return the proxy port + */ + public int getProxyPort() { + this.checkProxy(); + return this.getPropertyAsInt(PROXY_PORT); + } + + /** + * + * @param url + */ + public void setWsdlURL(String url) { + this.setProperty(WSDL_URL, url); + } + + /** + * method returns the WSDL URL + * + * @return the WSDL URL + */ + public String getWsdlURL() { + return getPropertyAsString(WSDL_URL); + } + + /* + * The method will check to see if JMeter was started in NonGui mode. If it + * was, it will try to pick up the proxy host and port values if they were + * passed to JMeter.java. + */ + private void checkProxy() { + if (JMeter.isNonGUI()) { + this.setUseProxy(true); + // we check to see if the proxy host and port are set + String port = this.getPropertyAsString(PROXY_PORT); + String host = this.getPropertyAsString(PROXY_HOST); + if (host == null || host.length() == 0) { + // it's not set, lets check if the user passed + // proxy host and port from command line + host = System.getProperty("http.proxyHost"); + if (host != null) { + this.setProxyHost(host); + } + } + if (port == null || port.length() == 0) { + // it's not set, lets check if the user passed + // proxy host and port from command line + port = System.getProperty("http.proxyPort"); + if (port != null) { + this.setProxyPort(port); + } + } + } + } + + /* + * This method uses Apache soap util to create the proper DOM elements. + * + * @return Element + */ + private org.w3c.dom.Element createDocument() throws SAXException, IOException { + Document doc = null; + String next = this.getRandomFileName();//get filename or "" + + /* Note that the filename is also used as a key to the pool (if used) + ** Documents provided in the testplan are not currently pooled, as they may change + * between samples. + */ + + if (next.length() > 0 && getMemoryCache()) { + doc = DOMPool.getDocument(next); + if (doc == null){ + doc = openDocument(next); + if (doc != null) {// we created the document + DOMPool.putDocument(next, doc); + } + } + } else { // Must be local content - or not using pool + doc = openDocument(next); + } + + if (doc == null) { + return null; + } + return doc.getDocumentElement(); + } + + /** + * Open the file and create a Document. + * + * @param file - input filename or empty if using data from tesplan + * @return Document + * @throws IOException + * @throws SAXException + */ + private Document openDocument(String file) throws SAXException, IOException { + /* + * Consider using Apache commons pool to create a pool of document + * builders or make sure XMLParserUtils creates builders efficiently. + */ + DocumentBuilder XDB = XMLParserUtils.getXMLDocBuilder(); + XDB.setErrorHandler(null);//Suppress messages to stdout + + Document doc = null; + // if either a file or path location is given, + // get the file object. + if (file.length() > 0) {// we have a file + if (this.getReadResponse()) { + TextFile tfile = new TextFile(file); + fileContents = tfile.getText(); + } + FileInputStream fileInputStream = null; + try { + fileInputStream = new FileInputStream(file); + doc = XDB.parse(fileInputStream); + } finally { + IOUtils.closeQuietly(fileInputStream); + } + } else {// must be a "here" document + fileContents = getXmlData(); + if (fileContents != null && fileContents.length() > 0) { + doc = XDB.parse(new InputSource(new StringReader(fileContents))); + } else { + log.warn("No post data provided!"); + } + } + return doc; + } + + /* + * Required to satisfy HTTPSamplerBase Should not be called, as we override + * sample() + */ + + @Override + protected HTTPSampleResult sample(URL u, String s, boolean b, int i) { + throw new RuntimeException("Not implemented - should not be called"); + } + + /** + * Sample the URL using Apache SOAP driver. Implementation note for myself + * and those that are curious. Current logic marks the end after the + * response has been read. If read response is set to false, the buffered + * reader will read, but do nothing with it. Essentially, the stream from + * the server goes into the ether. + */ + @Override + public SampleResult sample() { + SampleResult result = new SampleResult(); + result.setSuccessful(false); // Assume it will fail + result.setResponseCode("000"); // ditto $NON-NLS-1$ + result.setSampleLabel(getName()); + try { + result.setURL(this.getUrl()); + org.w3c.dom.Element rdoc = createDocument(); + if (rdoc == null) { + throw new SOAPException("Could not create document", null); + } + // set the response defaults + result.setDataEncoding(ENCODING); + result.setContentType("text/xml"); // $NON-NLS-1$ + result.setDataType(SampleResult.TEXT); + result.setSamplerData(fileContents);// WARNING - could be large + + Envelope msgEnv = Envelope.unmarshall(rdoc); + // create a new message + Message msg = new Message(); + result.sampleStart(); + SOAPHTTPConnection spconn = null; + // if a blank HeaderManager exists, try to + // get the SOAPHTTPConnection. After the first + // request, there should be a connection object + // stored with the cookie header info. + if (this.getHeaderManager() != null && this.getHeaderManager().getSOAPHeader() != null) { + spconn = (SOAPHTTPConnection) this.getHeaderManager().getSOAPHeader(); + } else { + spconn = new SOAPHTTPConnection(); + } + + spconn.setTimeout(getTimeoutAsInt()); + + // set the auth. thanks to KiYun Roe for contributing the patch + // I cleaned up the patch slightly. 5-26-05 + if (getAuthManager() != null) { + if (getAuthManager().getAuthForURL(getUrl()) != null) { + AuthManager authmanager = getAuthManager(); + Authorization auth = authmanager.getAuthForURL(getUrl()); + spconn.setUserName(auth.getUser()); + spconn.setPassword(auth.getPass()); + } else { + log.warn("the URL for the auth was null." + " Username and password not set"); + } + } + // check the proxy + String phost = ""; + int pport = 0; + // if use proxy is set, we try to pick up the + // proxy host and port from either the text + // fields or from JMeterUtil if they were passed + // from command line + if (this.getUseProxy()) { + if (this.getProxyHost().length() > 0 && this.getProxyPort() > 0) { + phost = this.getProxyHost(); + pport = this.getProxyPort(); + } else { + if (System.getProperty("http.proxyHost") != null || System.getProperty("http.proxyPort") != null) { + phost = System.getProperty("http.proxyHost"); + pport = Integer.parseInt(System.getProperty("http.proxyPort")); + } + } + // if for some reason the host is blank and the port is + // zero, the sampler will fail silently + if (phost.length() > 0 && pport > 0) { + spconn.setProxyHost(phost); + spconn.setProxyPort(pport); + if (PROXY_USER.length()>0 && PROXY_PASS.length()>0){ + spconn.setProxyUserName(PROXY_USER); + spconn.setProxyPassword(PROXY_PASS); + } + } + } + + spconn.setMaintainSession(getMaintainSession()); + msg.setSOAPTransport(spconn); + msg.send(this.getUrl(), this.getSoapAction(), msgEnv); + @SuppressWarnings("unchecked") // API uses raw types + final Map headers = spconn.getHeaders(); + result.setResponseHeaders(convertSoapHeaders(headers)); + + if (this.getHeaderManager() != null) { + this.getHeaderManager().setSOAPHeader(spconn); + } + + BufferedReader br = null; + if (spconn.receive() != null) { + br = spconn.receive(); + SOAPContext sc = spconn.getResponseSOAPContext(); + // Set details from the actual response + // Needs to be done before response can be stored + final String contentType = sc.getContentType(); + result.setContentType(contentType); + result.setEncodingAndType(contentType); + int length=0; + if (getReadResponse()) { + StringWriter sw = new StringWriter(); + length=IOUtils.copy(br, sw); + result.sampleEnd(); + result.setResponseData(sw.toString().getBytes(result.getDataEncodingWithDefault())); + } else { + // by not reading the response + // for real, it improves the + // performance on slow clients + length=br.read(); + result.sampleEnd(); + result.setResponseData(JMeterUtils.getResString("read_response_message"), null); //$NON-NLS-1$ + } + // It is not possible to access the actual HTTP response code, so we assume no data means failure + if (length > 0){ + result.setSuccessful(true); + result.setResponseCodeOK(); + result.setResponseMessageOK(); + } else { + result.setSuccessful(false); + result.setResponseCode("999"); + result.setResponseMessage("Empty response"); + } + } else { + result.sampleEnd(); + result.setSuccessful(false); + final String contentType = spconn.getResponseSOAPContext().getContentType(); + result.setContentType(contentType); + result.setEncodingAndType(contentType); + result.setResponseData(spconn.getResponseSOAPContext().toString().getBytes(result.getDataEncodingWithDefault())); + } + if (br != null) { + br.close(); + } + // reponse code doesn't really apply, since + // the soap driver doesn't provide a + // response code + } catch (IllegalArgumentException exception){ + String message = exception.getMessage(); + log.warn(message); + result.setResponseMessage(message); + } catch (SAXException exception) { + log.warn(exception.toString()); + result.setResponseMessage(exception.getMessage()); + } catch (SOAPException exception) { + log.warn(exception.toString()); + result.setResponseMessage(exception.getMessage()); + } catch (MalformedURLException exception) { + String message = exception.getMessage(); + log.warn(message); + result.setResponseMessage(message); + } catch (IOException exception) { + String message = exception.getMessage(); + log.warn(message); + result.setResponseMessage(message); + } catch (NoClassDefFoundError error){ + log.error("Missing class: ",error); + result.setResponseMessage(error.toString()); + } catch (Exception exception) { + if ("javax.mail.MessagingException".equals(exception.getClass().getName())){ + log.warn(exception.toString()); + result.setResponseMessage(exception.getMessage()); + } else { + log.error("Problem processing the SOAP request", exception); + result.setResponseMessage(exception.toString()); + } + } finally { + // Make sure the sample start time and sample end time are recorded + // in order not to confuse the statistics calculation methods: if + // an error occurs and an exception is thrown it is possible that + // the result.sampleStart() or result.sampleEnd() won't be called + if (result.getStartTime() == 0) + { + result.sampleStart(); + } + if (result.getEndTime() == 0) + { + result.sampleEnd(); + } + } + return result; + } + + /** + * We override this to prevent the wrong encoding and provide no + * implementation. We want to reuse the other parts of HTTPSampler, but not + * the connection. The connection is handled by the Apache SOAP driver. + */ + @Override + public void addEncodedArgument(String name, String value, String metaData) { + } + + public String convertSoapHeaders(Map ht) { + StringBuilder buf = new StringBuilder(); + for (Entry entry : ht.entrySet()) { + buf.append(entry.getKey()).append("=").append(entry.getValue()).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ + } + return buf.toString(); + } + +// /** +// * Process headerLines +// * @param en enumeration of Strings +// * @return String containing the lines +// */ +// private String convertSoapHeaders(Enumeration en) { +// StringBuilder buf = new StringBuilder(100); +// while (en.hasMoreElements()) { +// buf.append(en.nextElement()).append("\n"); //$NON-NLS-1$ +// } +// return buf.toString(); +// } + + public String getTimeout() { + return getPropertyAsString(TIMEOUT); + } + + public int getTimeoutAsInt() { + return getPropertyAsInt(TIMEOUT); + } + + public void setTimeout(String text) { + setProperty(TIMEOUT, text); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#testEnded() + */ + @Override + public void testEnded() { + DOMPool.clear(); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#testEnded(java.lang.String) + */ + @Override + public void testEnded(String host) { + testEnded(); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/Base64Encoder.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/Base64Encoder.java new file mode 100644 index 0000000..1bcdbe8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/Base64Encoder.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +/** + * This class provides an implementation of Base64 encoding without relying on + * the the sun.* packages. + * + * @version $Revision: 937662 $ + */ +public final class Base64Encoder { + private final static char[] pem_array = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47 }; + + private final static char eq = 61; + + /** + * Private constructor to prevent instantiation. + */ + private Base64Encoder() { + } + + public final static String encode(String s) { + return encode(s.getBytes()); // TODO - charset? + } + + public final static String encode(byte[] bs) { + StringBuilder out = new StringBuilder(); + int bl = bs.length; + for (int i = 0; i < bl; i += 3) { + out.append(encodeAtom(bs, i, (bl - i))); + } + return out.toString(); + } + + public final static String encodeAtom(byte[] b, int strt, int left) { + StringBuilder out = new StringBuilder(); + if (left == 1) { + byte b1 = b[strt]; + int k = 0; + out.append(String.valueOf(pem_array[b1 >>> 2 & 63])); + out.append(String.valueOf(pem_array[(b1 << 4 & 48) + (k >>> 4 & 15)])); + out.append(String.valueOf(eq)); + out.append(String.valueOf(eq)); + return out.toString(); + } + if (left == 2) { + byte b2 = b[strt]; + byte b4 = b[strt + 1]; + int l = 0; + out.append(String.valueOf(pem_array[b2 >>> 2 & 63])); + out.append(String.valueOf(pem_array[(b2 << 4 & 48) + (b4 >>> 4 & 15)])); + out.append(String.valueOf(pem_array[(b4 << 2 & 60) + (l >>> 6 & 3)])); + out.append(String.valueOf(eq)); + return out.toString(); + } + byte b3 = b[strt]; + byte b5 = b[strt + 1]; + byte b6 = b[strt + 2]; + out.append(String.valueOf(pem_array[b3 >>> 2 & 63])); + out.append(String.valueOf(pem_array[(b3 << 4 & 48) + (b5 >>> 4 & 15)])); + out.append(String.valueOf(pem_array[(b5 << 2 & 60) + (b6 >>> 6 & 3)])); + out.append(String.valueOf(pem_array[b6 & 63])); + return out.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/ConversionUtils.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/ConversionUtils.java new file mode 100644 index 0000000..736f4ac --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/ConversionUtils.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.jorphan.util.JOrphanUtils; + +// @see TestHTTPUtils for unit tests + +/** + * General purpose conversion utilities related to HTTP/HTML + */ +public class ConversionUtils { + + private static final String CHARSET_EQ = "charset="; // $NON-NLS-1$ + private static final int CHARSET_EQ_LEN = CHARSET_EQ.length(); + + private static final String SLASHDOTDOT = "/.."; + private static final String DOTDOT = ".."; + private static final String SLASH = "/"; + private static final String COLONSLASHSLASH = "://"; + + /** + * Extract the encoding (charset) from the Content-Type, + * e.g. "text/html; charset=utf-8". + * + * @param contentType + * @return the charset encoding - or null, if none was found or the charset is not supported + */ + public static String getEncodingFromContentType(String contentType){ + String charSet = null; + if (contentType != null) { + int charSetStartPos = contentType.toLowerCase(java.util.Locale.ENGLISH).indexOf(CHARSET_EQ); + if (charSetStartPos >= 0) { + charSet = contentType.substring(charSetStartPos + CHARSET_EQ_LEN); + if (charSet != null) { + // Remove quotes from charset name + charSet = JOrphanUtils.replaceAllChars(charSet, '"', ""); + charSet = charSet.trim(); + if (charSet.length() > 0) { + // See Bug 44784 + int semi = charSet.indexOf(";"); + if (semi == 0){ + return null; + } + if (semi != -1) { + charSet = charSet.substring(0,semi); + } + if (!Charset.isSupported(charSet)){ + return null; + } + return charSet; + } + return null; + } + } + } + return charSet; + } + + /** + * Generate a relative URL, allowing for extraneous leading "../" segments. + * The Java {@link URL#URL(URL, String)} constructor does not remove these. + * + * @param baseURL + * @param location relative location, possibly with extraneous leading "../" + * @return URL with extraneous ../ removed + * @throws MalformedURLException + */ + public static URL makeRelativeURL(URL baseURL, String location) throws MalformedURLException{ + URL initial = new URL(baseURL,location); + + // skip expensive processing if it cannot apply + if (!location.startsWith("../")){// $NON-NLS-1$ + return initial; + } + String path = initial.getPath(); + // Match /../[../] etc. + Pattern p = Pattern.compile("^/((?:\\.\\./)+)"); // $NON-NLS-1$ + Matcher m = p.matcher(path); + if (m.lookingAt()){ + String prefix = m.group(1); // get ../ or ../../ etc. + if (location.startsWith(prefix)){ + return new URL(baseURL, location.substring(prefix.length())); + } + } + return initial; + } + + /** + * collapses absolute or relative URLs containing '/..' converting + * http://host/path1/../path2 to http://host/path2 or /one/two/../three to + * /one/three + * + * @param url + * @return collapsed URL + */ + public static String removeSlashDotDot(String url) + { + if (url == null || (url = url.trim()).length() < 4 || !url.contains(SLASHDOTDOT)) + { + return url; + } + + /** + * http://auth@host:port/path1/path2/path3/?query#anchor + */ + + // get to 'path' part of the URL, preserving schema, auth, host if + // present + + // find index of path start + + int dotSlashSlashIndex = url.indexOf(COLONSLASHSLASH); + final int pathStartIndex; + if (dotSlashSlashIndex >= 0) + { + // absolute URL + pathStartIndex = url.indexOf(SLASH, dotSlashSlashIndex + COLONSLASHSLASH.length()); + } else + { + // document or context-relative URL like: + // '/path/to' + // OR '../path/to' + // OR '/path/to/../path/' + pathStartIndex = 0; + } + + // find path endIndex + int pathEndIndex = url.length(); + + int questionMarkIdx = url.indexOf('?'); + if (questionMarkIdx > 0) + { + pathEndIndex = questionMarkIdx; + } else { + int anchorIdx = url.indexOf('#'); + if (anchorIdx > 0) + { + pathEndIndex = anchorIdx; + } + } + + // path is between idx='pathStartIndex' (inclusive) and + // idx='pathEndIndex' (exclusive) + String currentPath = url.substring(pathStartIndex, pathEndIndex); + + final boolean startsWithSlash = currentPath.startsWith(SLASH); + final boolean endsWithSlash = currentPath.endsWith(SLASH); + + StringTokenizer st = new StringTokenizer(currentPath, SLASH); + List tokens = new ArrayList(); + while (st.hasMoreTokens()) + { + tokens.add(st.nextToken()); + } + + for (int i = 0; i < tokens.size(); i++) + { + if (i < tokens.size() - 1) + { + final String thisToken = tokens.get(i); + + // Verify for a ".." component at next iteration + if (thisToken.length() > 0 && !thisToken.equals(DOTDOT) && tokens.get(i + 1).equals(DOTDOT)) + { + tokens.remove(i); + tokens.remove(i); + i = i - 2; + if (i < -1) + { + i = -1; + } + } + } + + } + + StringBuilder newPath = new StringBuilder(); + if (startsWithSlash) { + newPath.append(SLASH); + } + for (int i = 0; i < tokens.size(); i++) + { + newPath.append(tokens.get(i)); + + // append '/' if this isn't the last token or it is but the original + // path terminated w/ a '/' + boolean appendSlash = i < (tokens.size() - 1) ? true : endsWithSlash; + if (appendSlash) + { + newPath.append(SLASH); + } + } + + // install new path + StringBuilder s = new StringBuilder(url); + s.replace(pathStartIndex, pathEndIndex, newPath.toString()); + return s.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/DOMPool.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/DOMPool.java new file mode 100644 index 0000000..a47ee35 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/DOMPool.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.util.Collections; +import java.util.Map; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.jmeter.util.JMeterUtils; +import org.w3c.dom.Document; + +/** + * The purpose of this class is to cache the DOM Documents in memory and by-pass + * parsing. For old systems or laptops, it's not practical to parse the XML + * documents every time. Therefore using a memory cache can reduce the CPU + * usage. + *

+ * For now this is a simple version to test the feasibility of caching. If it + * works, this class will be replaced with an Apache commons or something + * equivalent. If I was familiar with Apache Commons Pool, I would probably use + * it, but since I don't know the API, it is quicker for Proof of Concept to + * just write a dumb one. If the number documents in the pool exceed several + * hundred, it will take a long time for the lookup. + *

+ * Created on: Jun 17, 2003
+ * + */ +public final class DOMPool { + /** + * The cache is created with an initial size of 50. Running a webservice + * test on an old system will likely run into memory or CPU problems long + * before the HashMap is an issue. + */ + @SuppressWarnings("unchecked") // LRUMap does not support generics currently + private static final Map MEMCACHE = Collections.synchronizedMap( + new LRUMap(JMeterUtils.getPropDefault("soap.document_cache", 50))); + + /** + * Return a document. + * + * @param key + * @return Document + */ + public static Document getDocument(Object key) { + return MEMCACHE.get(key); + } + + /** + * Add an object to the cache. + * + * @param key + * @param data + */ + public static void putDocument(Object key, Document data) { + MEMCACHE.put(key, data); + } + + /** + * Private constructor to prevent instantiation. + */ + private DOMPool() { + } + + /** + * Clear cache + */ + public static void clear() { + MEMCACHE.clear(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/EncoderCache.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/EncoderCache.java new file mode 100644 index 0000000..f95e5c5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/EncoderCache.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +import org.apache.oro.util.Cache; +import org.apache.oro.util.CacheLRU; + +public class EncoderCache { + + /** The encoding which should be usd for URLs, according to HTTP specification */ + public static final String URL_ARGUMENT_ENCODING = "UTF-8"; + + private Cache cache; + + public EncoderCache(int cacheSize) { + cache = new CacheLRU(cacheSize); + } + + + /** + * Get the specified value URL encoded using UTF-8 encoding + * + * @param k the value to encode + * @return the value URL encoded using UTF-8 + */ + public String getEncoded(String k) { + try { + return getEncoded(k, URL_ARGUMENT_ENCODING); + } catch (UnsupportedEncodingException e) { + // This can't happen (how should utf8 not be supported!?!), + // so just throw an Error: + throw new Error("Should not happen: " + e.toString()); + } + } + + /** + * Get the specified value URL encoded using the specified encoding + * + * @param k the value to encode + * @param contentEncoding the encoding to use when URL encoding + * @return the value URL encoded using the specified encoding + * @throws UnsupportedEncodingException if the specified encoding is not supported + */ + public String getEncoded(String k, String contentEncoding) throws UnsupportedEncodingException { + String cacheKey = k + contentEncoding; + // Check if we have it in the cache + Object encodedValue = cache.getElement(cacheKey); + if (encodedValue != null) { + return (String) encodedValue; + } + // Perform the encoding + encodedValue = URLEncoder.encode(k, contentEncoding); + // Add to cache + cache.addElement(cacheKey, encodedValue); + return (String) encodedValue; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HC4TrustAllSSLSocketFactory.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HC4TrustAllSSLSocketFactory.java new file mode 100644 index 0000000..637f701 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HC4TrustAllSSLSocketFactory.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLSocket; + +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.params.HttpParams; +import org.apache.jmeter.util.HttpSSLProtocolSocketFactory; +import org.apache.jmeter.util.JsseSSLManager; + +/** + * Apache HttpClient protocol factory to generate SSL sockets + */ + +public class HC4TrustAllSSLSocketFactory {//tms extends SSLSocketFactory { + + private static final TrustStrategy TRUSTALL = new TrustStrategy(){ + public boolean isTrusted(X509Certificate[] chain, String authType) { + return true; + } + }; + private javax.net.ssl.SSLSocketFactory factory; + + /** + * Create an SSL factory which trusts all certificates and hosts. + * {@link SSLSocketFactory#SSLSocketFactory(TrustStrategy, org.apache.http.conn.ssl.X509HostnameVerifier)} + * @throws GeneralSecurityException if there's a problem setting up the security + */ + public HC4TrustAllSSLSocketFactory() throws GeneralSecurityException { + this(new HttpSSLProtocolSocketFactory((JsseSSLManager)JsseSSLManager.getInstance())); + } + + /** + * Create an SSL factory which trusts all certificates and hosts. + * {@link SSLSocketFactory#SSLSocketFactory(TrustStrategy, org.apache.http.conn.ssl.X509HostnameVerifier)} + * @param factory javax.net.ssl.SSLSocketFactory + * @throws GeneralSecurityException if there's a problem setting up the security + */ + protected HC4TrustAllSSLSocketFactory(javax.net.ssl.SSLSocketFactory factory) throws GeneralSecurityException { + //tms super(TRUSTALL, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + this.factory = new HttpSSLProtocolSocketFactory((JsseSSLManager)JsseSSLManager.getInstance()); + } + + /* (non-Javadoc) + * @see org.apache.http.conn.ssl.SSLSocketFactory#createSocket(org.apache.http.params.HttpParams) + */ + //tms removed to compile @Override + public Socket createSocket(HttpParams params) throws IOException { + return factory.createSocket(); + } + + /* (non-Javadoc) + * @see org.apache.http.conn.ssl.SSLSocketFactory#createSocket() + */ + //tms @Override + public Socket createSocket() throws IOException { + return factory.createSocket(); + } + + /* (non-Javadoc) + * @see org.apache.http.conn.ssl.SSLSocketFactory#createLayeredSocket(java.net.Socket, java.lang.String, int, boolean) + */ + //tms removed to compile @Override + public Socket createLayeredSocket(Socket socket, String host, int port, + boolean autoClose) throws IOException, UnknownHostException { + SSLSocket sslSocket = (SSLSocket) this.factory.createSocket( + socket, + host, + port, + autoClose + ); + //tms ALLOW_ALL_HOSTNAME_VERIFIER.verify(host, sslSocket); + return sslSocket; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPArgument.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPArgument.java new file mode 100644 index 0000000..d063755 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPArgument.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +//For unit tests, @see TestHTTPArgument + +/* + * + * Represents an Argument for HTTP requests. + */ +public class HTTPArgument extends Argument implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final String ALWAYS_ENCODE = "HTTPArgument.always_encode"; + + private static final String USE_EQUALS = "HTTPArgument.use_equals"; + + private static final EncoderCache cache = new EncoderCache(1000); + + /** + * Constructor for the Argument object. + */ + public HTTPArgument(String name, String value, String metadata) { + this(name, value, false); + this.setMetaData(metadata); + } + + public void setUseEquals(boolean ue) { + if (ue) { + setMetaData("="); + } else { + setMetaData(""); + } + setProperty(new BooleanProperty(USE_EQUALS, ue)); + } + + public boolean isUseEquals() { + boolean eq = getPropertyAsBoolean(USE_EQUALS); + if (getMetaData().equals("=") || (getValue() != null && getValue().length() > 0)) { + setUseEquals(true); + return true; + } + return eq; + + } + + public void setAlwaysEncoded(boolean ae) { + setProperty(new BooleanProperty(ALWAYS_ENCODE, ae)); + } + + public boolean isAlwaysEncoded() { + return getPropertyAsBoolean(ALWAYS_ENCODE); + } + + /** + * Constructor for the Argument object. + */ + public HTTPArgument(String name, String value) { + this(name, value, false); + } + + public HTTPArgument(String name, String value, boolean alreadyEncoded) { + // We assume the argument value is encoded according to the HTTP spec, i.e. UTF-8 + this(name, value, alreadyEncoded, EncoderCache.URL_ARGUMENT_ENCODING); + } + + /** + * Construct a new HTTPArgument instance; alwaysEncoded is set to true. + * + * @param name the name of the parameter + * @param value the value of the parameter + * @param alreadyEncoded true if the name and value is already encoded, in which case they are decoded before storage. + * @param contentEncoding the encoding used for the parameter value + */ + public HTTPArgument(String name, String value, boolean alreadyEncoded, String contentEncoding) { + setAlwaysEncoded(true); + if (alreadyEncoded) { + try { + // We assume the name is always encoded according to spec + name = URLDecoder.decode(name, EncoderCache.URL_ARGUMENT_ENCODING); + // The value is encoded in the specified encoding + value = URLDecoder.decode(value, contentEncoding); + } catch (UnsupportedEncodingException e) { + log.error(contentEncoding + " encoding not supported!"); + throw new Error(e.toString()); + } + } + setName(name); + setValue(value); + setMetaData("="); + } + + public HTTPArgument(String name, String value, String metaData, boolean alreadyEncoded) { + // We assume the argument value is encoded according to the HTTP spec, i.e. UTF-8 + this(name, value, metaData, alreadyEncoded, EncoderCache.URL_ARGUMENT_ENCODING); + } + + /** + * Construct a new HTTPArgument instance + * + * @param name the name of the parameter + * @param value the value of the parameter + * @param metaData the separator to use between name and value + * @param alreadyEncoded true if the name and value is already encoded + * @param contentEncoding the encoding used for the parameter value + */ + public HTTPArgument(String name, String value, String metaData, boolean alreadyEncoded, String contentEncoding) { + this(name, value, alreadyEncoded, contentEncoding); + setMetaData(metaData); + } + + public HTTPArgument(Argument arg) { + this(arg.getName(), arg.getValue(), arg.getMetaData()); + } + + /** + * Constructor for the Argument object + */ + public HTTPArgument() { + } + + /** + * Sets the Name attribute of the Argument object. + * + * @param newName + * the new Name value + */ + @Override + public void setName(String newName) { + if (newName == null || !newName.equals(getName())) { + super.setName(newName); + } + } + + /** + * Get the argument value encoded using UTF-8 + * + * @return the argument value encoded in UTF-8 + */ + public String getEncodedValue() { + // Encode according to the HTTP spec, i.e. UTF-8 + try { + return getEncodedValue(EncoderCache.URL_ARGUMENT_ENCODING); + } catch (UnsupportedEncodingException e) { + // This can't happen (how should utf8 not be supported!?!), + // so just throw an Error: + throw new Error("Should not happen: " + e.toString()); + } + } + + /** + * Get the argument value encoded in the specified encoding + * + * @param contentEncoding the encoding to use when encoding the argument value + * @return the argument value encoded in the specified encoding + * @throws UnsupportedEncodingException + */ + public String getEncodedValue(String contentEncoding) throws UnsupportedEncodingException { + if (isAlwaysEncoded()) { + return cache.getEncoded(getValue(), contentEncoding); + } else { + return getValue(); + } + } + + public String getEncodedName() { + if (isAlwaysEncoded()) { + return cache.getEncoded(getName()); + } else { + return getName(); + } + + } + + /** + * Converts all {@link Argument} entries in the collection to {@link HTTPArgument} entries. + * + * @param args collection of {@link Argument} and/or {@link HTTPArgument} entries + */ + public static void convertArgumentsToHTTP(Arguments args) { + List newArguments = new LinkedList(); + PropertyIterator iter = args.getArguments().iterator(); + while (iter.hasNext()) { + Argument arg = (Argument) iter.next().getObjectValue(); + if (!(arg instanceof HTTPArgument)) { + newArguments.add(new HTTPArgument(arg)); + } else { + newArguments.add(arg); + } + } + args.removeAllArguments(); + args.setArguments(newArguments); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPConstants.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPConstants.java new file mode 100644 index 0000000..5be8c3b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPConstants.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +/** + * Constants used in HTTP, mainly header names. + * This is a class suitable for extending + * or for accessing individual constants without needing to import them all. + */ + +public class HTTPConstants implements HTTPConstantsInterface { + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPConstantsInterface.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPConstantsInterface.java new file mode 100644 index 0000000..caaf9b5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPConstantsInterface.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + + +/** + * Constants used in HTTP, mainly header names. + */ + +public interface HTTPConstantsInterface { + + public static final int DEFAULT_HTTPS_PORT = 443; + public static final String DEFAULT_HTTPS_PORT_STRING = "443"; // $NON-NLS-1$ + public static final int DEFAULT_HTTP_PORT = 80; + public static final String DEFAULT_HTTP_PORT_STRING = "80"; // $NON-NLS-1$ + public static final String PROTOCOL_HTTP = "http"; // $NON-NLS-1$ + public static final String PROTOCOL_HTTPS = "https"; // $NON-NLS-1$ + public static final String HEAD = "HEAD"; // $NON-NLS-1$ + public static final String POST = "POST"; // $NON-NLS-1$ + public static final String PUT = "PUT"; // $NON-NLS-1$ + public static final String GET = "GET"; // $NON-NLS-1$ + public static final String OPTIONS = "OPTIONS"; // $NON-NLS-1$ + public static final String TRACE = "TRACE"; // $NON-NLS-1$ + public static final String DELETE = "DELETE"; // $NON-NLS-1$ + public static final String CONNECT = "CONNECT"; // $NON-NLS-1$ + public static final String HEADER_AUTHORIZATION = "Authorization"; // $NON-NLS-1$ + public static final String HEADER_COOKIE = "Cookie"; // $NON-NLS-1$ + public static final String HEADER_CONNECTION = "Connection"; // $NON-NLS-1$ + public static final String CONNECTION_CLOSE = "close"; // $NON-NLS-1$ + public static final String KEEP_ALIVE = "keep-alive"; // $NON-NLS-1$ + // e.g. "Transfer-Encoding: chunked", which is processed automatically by the underlying protocol + public static final String TRANSFER_ENCODING = "transfer-encoding"; // $NON-NLS-1$ + public static final String HEADER_CONTENT_ENCODING = "content-encoding"; // $NON-NLS-1$ + public static final String HTTP_1_1 = "HTTP/1.1"; // $NON-NLS-1$ + public static final String HEADER_SET_COOKIE = "set-cookie"; // $NON-NLS-1$ + public static final String ENCODING_GZIP = "gzip"; // $NON-NLS-1$ + public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition"; // $NON-NLS-1$ + public static final String HEADER_CONTENT_TYPE = "Content-Type"; // $NON-NLS-1$ + public static final String HEADER_CONTENT_LENGTH = "Content-Length"; // $NON-NLS-1$ + public static final String HEADER_HOST = "Host"; // $NON-NLS-1$ + public static final String HEADER_LOCATION = "Location"; // $NON-NLS-1$ + public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; // $NON-NLS-1$ + public static final String MULTIPART_FORM_DATA = "multipart/form-data"; // $NON-NLS-1$ + // For handling caching + public static final String IF_NONE_MATCH = "If-None-Match"; // $NON-NLS-1$ + public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; // $NON-NLS-1$ + public static final String ETAG = "Etag"; // $NON-NLS-1$ + public static final String LAST_MODIFIED = "Last-Modified"; // $NON-NLS-1$ + public static final String EXPIRES = "Expires"; // $NON-NLS-1$ + public static final String CACHE_CONTROL = "Cache-Control"; //e.g. public, max-age=259200 + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPFileArg.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPFileArg.java new file mode 100644 index 0000000..582f239 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPFileArg.java @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; + +/** + * Class representing a file parameter for http upload. + * Consists of a http parameter name/file path pair with (optional) mimetype. + * + * Also provides temporary storage for the headers which are sent with files. + * + */ +public class HTTPFileArg extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + /** Name used to store the file's path. */ + private static final String FILEPATH = "File.path"; + + /** Name used to store the file's paramname. */ + private static final String PARAMNAME = "File.paramname"; + + /** Name used to store the file's mimetype. */ + private static final String MIMETYPE = "File.mimetype"; + + /** temporary storage area for the body header. */ + private String header; + + /** + * Constructor for an empty HTTPFileArg object + */ + public HTTPFileArg() { + } + + /** + * Constructor for the HTTPFileArg object with given path. + */ + public HTTPFileArg(String path) { + this(path, "", ""); + } + + /** + * Constructor for the HTTPFileArg object with full information. + */ + public HTTPFileArg(String path, String paramname, String mimetype) { + if (path == null || paramname == null || mimetype == null){ + throw new IllegalArgumentException("Parameters must not be null"); + } + setPath(path); + setParamName(paramname); + setMimeType(mimetype); + } + + /** + * Constructor for the HTTPFileArg object with full information, + * using existing properties + */ + public HTTPFileArg(JMeterProperty path, JMeterProperty paramname, JMeterProperty mimetype) { + if (path == null || paramname == null || mimetype == null){ + throw new IllegalArgumentException("Parameters must not be null"); + } + setProperty(FILEPATH, path); + setProperty(MIMETYPE, mimetype); + setProperty(PARAMNAME, paramname); + } + + private void setProperty(String name, JMeterProperty prop) { + JMeterProperty jmp = prop.clone(); + jmp.setName(name); + setProperty(jmp); + } + + /** + * Copy Constructor. + */ + public HTTPFileArg(HTTPFileArg file) { + this(file.getPath(), file.getParamName(), file.getMimeType()); + } + + /** + * Set the http parameter name of the File. + * + * @param newParamName + * the new http parameter name + */ + public void setParamName(String newParamName) { + setProperty(new StringProperty(PARAMNAME, newParamName)); + } + + /** + * Get the http parameter name of the File. + * + * @return the http parameter name + */ + public String getParamName() { + return getPropertyAsString(PARAMNAME); + } + + /** + * Set the mimetype of the File. + * + * @param newMimeType + * the new mimetype + */ + public void setMimeType(String newMimeType) { + setProperty(new StringProperty(MIMETYPE, newMimeType)); + } + + /** + * Get the mimetype of the File. + * + * @return the http parameter mimetype + */ + public String getMimeType() { + return getPropertyAsString(MIMETYPE); + } + + /** + * Set the path of the File. + * + * @param newPath + * the new path + */ + public void setPath(String newPath) { + setProperty(new StringProperty(FILEPATH, newPath)); + } + + /** + * Get the path of the File. + * + * @return the file's path + */ + public String getPath() { + return getPropertyAsString(FILEPATH); + } + + /** + * Sets the body header for the HTTPFileArg object. Header + * contains path, parameter name and mime type information. + * This is only intended for use by methods which need to store information + * temporarily whilst creating the HTTP body. + * + * @param newHeader + * the new Header value + */ + public void setHeader(String newHeader) { + header = newHeader; + } + + /** + * Gets the saved body header for the HTTPFileArg object. + */ + public String getHeader() { + return header; + } + + /** + * returns path, param name, mime type information of + * HTTPFileArg object. + * + * @return the string demonstration of HTTPFileArg object in this + * format: + * "path:''|param:''|mimetype:''" + */ + @Override + public String toString() { + return "path:'" + getPath() + + "'|param:'" + getParamName() + + "'|mimetype:'" + getMimeType() + "'"; + } + + /** + * Check if the entry is not empty. + * @return true if Path, name or mimetype fields are not the empty string + */ + public boolean isNotEmpty() { + return getPath().length() > 0 + || getParamName().length() > 0 + || getMimeType().length() > 0; // TODO should we allow mimetype only? + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPFileArgs.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPFileArgs.java new file mode 100644 index 0000000..229fc91 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/HTTPFileArgs.java @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; + +/** + * A set of HTTPFileArg objects. + * + */ +public class HTTPFileArgs extends ConfigTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + /** The name of the property used to store the files. */ + private static final String HTTP_FILE_ARGS = "HTTPFileArgs.files"; //$NON-NLS-1$ + + /** + * Create a new HTTPFileArgs object with no files. + */ + public HTTPFileArgs() { + setProperty(new CollectionProperty(HTTP_FILE_ARGS, new ArrayList())); + } + + /** + * Get the files. + * + * @return the files + */ + public CollectionProperty getHTTPFileArgsCollection() { + return (CollectionProperty) getProperty(HTTP_FILE_ARGS); + } + + /** + * Clear the files. + */ + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(HTTP_FILE_ARGS, new ArrayList())); + } + + /** + * Set the list of files. Any existing files will be lost. + * + * @param files the new files + */ + public void setHTTPFileArgs(List files) { + setProperty(new CollectionProperty(HTTP_FILE_ARGS, files)); + } + + /** + * Add a new file with the given path. + * + * @param path + * the path of the file + */ + public void addHTTPFileArg(String path) { + addHTTPFileArg(new HTTPFileArg(path)); + } + + /** + * Add a new file. + * + * @param file + * the new file + */ + public void addHTTPFileArg(HTTPFileArg file) { + TestElementProperty newHTTPFileArg = new TestElementProperty(file.getPath(), file); + if (isRunningVersion()) { + this.setTemporary(newHTTPFileArg); + } + getHTTPFileArgsCollection().addItem(newHTTPFileArg); + } + + /** + * adds a new File to the HTTPFileArgs list to be uploaded with http + * request. + * + * @param path file full path. + * @param param http parameter name. + * @param mime mime type of file. + */ + public void addHTTPFileArg(String path, String param, String mime) { + addHTTPFileArg(new HTTPFileArg(path, param, mime)); + } + + /** + * Get a PropertyIterator of the files. + * + * @return an iteration of the files + */ + public PropertyIterator iterator() { + return getHTTPFileArgsCollection().iterator(); + } + + /** + * Get the current arguments as an array. + * + * @return an array of file arguments + */ + public HTTPFileArg[] asArray(){ + CollectionProperty props = getHTTPFileArgsCollection(); + final int size = props.size(); + HTTPFileArg[] args = new HTTPFileArg[size]; + for(int i=0; i0 + && res.getResponseData().length == 0) { + readFile(resultFileName,res); + } + return res; + } + + private void retrieveHTTPItem(HierarchicalStreamReader reader, UnmarshallingContext context, + HTTPSampleResult res, Object subItem) { + if (subItem instanceof URL) { + res.setURL((URL) subItem); + } else if (reader.getNodeName().equals(TAG_COOKIES)) { + res.setCookies((String) subItem); + } else if (reader.getNodeName().equals(TAG_METHOD)) { + res.setHTTPMethod((String) subItem); + } else if (reader.getNodeName().equals(TAG_QUERY_STRING)) { + res.setQueryString((String) subItem); + } else if (reader.getNodeName().equals(TAG_REDIRECT_LOCATION)) { + res.setRedirectLocation((String) subItem); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/LoopbackHTTPSocket.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/LoopbackHTTPSocket.java new file mode 100644 index 0000000..2f9b55f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/LoopbackHTTPSocket.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.apache.jmeter.samplers.SampleResult; + +/* + * Socket that reads back from the output + */ +public class LoopbackHTTPSocket extends Socket { + + // get access to buffer + static class LoopbackOutputStream extends ByteArrayOutputStream{ + byte [] getBuffer() { + return buf; + } + } + + // wrap read() methods to track output buffer + static class LoopBackInputStream extends ByteArrayInputStream{ + private LoopbackOutputStream os; + @Override + public synchronized int read() { + buf=os.getBuffer(); // make sure buffer details + count=buf.length; // track the output + return super.read(); + } + @Override + public synchronized int read(byte[] b, int off, int len) { + buf=os.getBuffer(); + count=buf.length; + return super.read(b, off, len); + } + + public LoopBackInputStream(LoopbackOutputStream _os) { + super(_os.getBuffer()); + os=_os; + } + } + + private final LoopbackOutputStream os; + + private LoopbackHTTPSocket() throws IOException{ + os=new LoopbackOutputStream(); + // Preload the output so that can be read back as HTTP + os.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n".getBytes(SampleResult.DEFAULT_HTTP_ENCODING)); + } + + public LoopbackHTTPSocket(String host, int port, InetAddress localAddress, int localPort, int timeout) throws IOException { + this(); + } + + public LoopbackHTTPSocket(String host, int port, InetAddress localAddr, int localPort) throws IOException { + this(); + } + + public LoopbackHTTPSocket(String host, int port) throws UnknownHostException, IOException { + this(); + } + + // Override so we can intercept the stream + @Override + public OutputStream getOutputStream() throws IOException { + return os; + } + + // Override so we can intercept the stream + @Override + public InputStream getInputStream() throws IOException { + return new LoopBackInputStream(os); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/LoopbackHttpClientSocketFactory.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/LoopbackHttpClientSocketFactory.java new file mode 100644 index 0000000..7f9f58d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/LoopbackHttpClientSocketFactory.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import java.net.UnknownHostException; + +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.params.HttpConnectionParams; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; + +/** + * Commons HttpClient protocol factory to generate Loopback HTTP sockets + */ + +public class LoopbackHttpClientSocketFactory implements ProtocolSocketFactory { + + public LoopbackHttpClientSocketFactory() { + super(); + } + + public Socket createSocket(String host, int port, InetAddress clientHost, + int clientPort) throws IOException, UnknownHostException { + return new LoopbackHTTPSocket(host,port,clientHost,clientPort); + } + + public Socket createSocket(String host, int port) throws IOException, + UnknownHostException { + return new LoopbackHTTPSocket(host,port); + } + + public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, + HttpConnectionParams params) + throws IOException, UnknownHostException, ConnectTimeoutException { + int timeout = params.getConnectionTimeout(); + if (timeout == 0) { + return new LoopbackHTTPSocket(host,port,localAddress,localPort); + } else { + return new LoopbackHTTPSocket(host,port,localAddress,localPort, timeout); + } + } + + /** + * Convenience method to set up the necessary HttpClient protocol and URL handler. + * + * Only works for HttpClient, because it's not possible (or at least very difficult) + * to provide a different socket factory for the HttpURLConnection class. + */ + public static void setup(){ + final String LOOPBACK = "loopback"; // $NON-NLS-1$ + + // This ensures tha HttpClient knows about the protocol + Protocol.registerProtocol(LOOPBACK, new Protocol(LOOPBACK,new LoopbackHttpClientSocketFactory(),1)); + + // Now allow the URL handling to work. + URLStreamHandlerFactory ushf = new URLStreamHandlerFactory(){ + public URLStreamHandler createURLStreamHandler(String protocol) { + if (protocol.equalsIgnoreCase(LOOPBACK)){ + return new URLStreamHandler(){ + @Override + protected URLConnection openConnection(URL u) throws IOException { + return null;// not needed for HttpClient + } + }; + } + return null; + } + }; + + java.net.URL.setURLStreamHandlerFactory(ushf); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHC4SSLSocketFactory.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHC4SSLSocketFactory.java new file mode 100644 index 0000000..2c9394a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHC4SSLSocketFactory.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.security.GeneralSecurityException; + +import org.apache.jmeter.util.HttpSSLProtocolSocketFactory; +import org.apache.jmeter.util.JsseSSLManager; + +/** + * Apache HttpClient protocol factory to generate "slow" SSL sockets for emulating dial-up modems + */ + +public class SlowHC4SSLSocketFactory extends HC4TrustAllSSLSocketFactory { + + /** + * Create a factory + * @param cps - characters per second, must be > 0 + * @throws GeneralSecurityException if there's a problem setting up the security + * @throws IllegalArgumentException if cps ≤ 0 + */ + public SlowHC4SSLSocketFactory(final int cps) throws GeneralSecurityException { + super(new HttpSSLProtocolSocketFactory((JsseSSLManager)JsseSSLManager.getInstance(), cps)); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHC4SocketFactory.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHC4SocketFactory.java new file mode 100644 index 0000000..4accde5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHC4SocketFactory.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.net.Socket; + +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.params.HttpParams; +import org.apache.jmeter.util.SlowSocket; + +/** + * Apache HttpClient protocol factory to generate "slow" sockets for emulating dial-up modems + */ + +public class SlowHC4SocketFactory {//tms edited to compile extends PlainSocketFactory { + + private final int CPS; // Characters per second to emulate + + /** + * Create a factory + * @param cps - characters per second + */ + public SlowHC4SocketFactory(final int cps) { + super(); + CPS = cps; + } + + // Override all the super-class Socket methods. + + //tms removed to compile @Override + public Socket createSocket(final HttpParams params) { + return new SlowSocket(CPS); + } + + //tms removed to compile @Override + public Socket createSocket() { + return new SlowSocket(CPS); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHttpClientSocketFactory.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHttpClientSocketFactory.java new file mode 100644 index 0000000..fe9b0de --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/SlowHttpClientSocketFactory.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.params.HttpConnectionParams; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.jmeter.util.SlowSocket; + +/** + * Commons HttpClient protocol factory to generate "slow" sockets for emulating dial-up modems + */ + +public class SlowHttpClientSocketFactory implements ProtocolSocketFactory { + + private final int CPS; // Characters per second to emulate + + /** + * + * @param cps - characters per second + */ + public SlowHttpClientSocketFactory(final int cps) { + super(); + CPS = cps; + } + + public Socket createSocket(String host, int port, InetAddress clientHost, + int clientPort) throws IOException, UnknownHostException { + return new SlowSocket(CPS,host,port,clientHost,clientPort); + } + + public Socket createSocket(String host, int port) throws IOException, + UnknownHostException { + return new SlowSocket(CPS,host,port); + } + + public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, + HttpConnectionParams params) + throws IOException, UnknownHostException, ConnectTimeoutException { + int timeout = params.getConnectionTimeout(); + if (timeout == 0) { + return new SlowSocket(CPS,host,port,localAddress,localPort); + } else { + return new SlowSocket(CPS,host,port,localAddress,localPort, timeout); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/WSDLException.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/WSDLException.java new file mode 100644 index 0000000..7c2c9bb --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/WSDLException.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +/** + * Created on: Jun 3, 2003
+ * + */ +public class WSDLException extends Exception { + + private static final long serialVersionUID = 240L; + + public WSDLException() { + super(); + } + + /** + * @param message + */ + public WSDLException(String message) { + super(message); + } + + /** + * @param exception + */ + public WSDLException(Exception exception) { + super(exception.getMessage()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/WSDLHelper.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/WSDLHelper.java new file mode 100644 index 0000000..b808b04 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/WSDLHelper.java @@ -0,0 +1,427 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; +import org.apache.jmeter.protocol.http.control.AuthManager; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * For now I use DOM for WSDLHelper, but it would be more efficient to use JAXB + * to generate an object model for WSDL and use it to perform serialization and + * deserialization. It also makes it easier to traverse the WSDL to get + * necessary information. + *

+ * Created on: Jun 3, 2003
+ * + */ +public class WSDLHelper { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static int GET_WDSL_TIMEOUT = 5000; // timeout to retrieve wsdl when server not response + + /** + * -------------------------------------------- The members used by the + * class to do its work -------------------------------------------- + */ + + private URL WSDLURL = null; + + private URLConnection CONN = null; + + private Document WSDLDOC = null; + + private String SOAPBINDING = null; + + private URL bindingURL = null; + + private Object[] SOAPOPS = null; + + private final Map ACTIONS = new HashMap(); + + private final AuthManager AUTH; + + /** + * Default constructor takes a string URL + */ + public WSDLHelper(String url) throws MalformedURLException { + this(url, null); + } + + public WSDLHelper(String url, AuthManager auth) throws MalformedURLException { + WSDLURL = new URL(url); + this.AUTH = auth; + } + + /** + * Returns the URL + * + * @return the URL + */ + public URL getURL() { + return this.WSDLURL; + } + + /** + * Return the protocol from the URL. this is needed, so that HTTPS works + * as expected. + */ + public String getProtocol() { + return this.bindingURL.getProtocol(); + } + + /** + * Return the host in the WSDL binding address + */ + public String getBindingHost() { + return this.bindingURL.getHost(); + } + + /** + * Return the path in the WSDL for the binding address + */ + public String getBindingPath() { + return this.bindingURL.getPath(); + } + + /** + * Return the port for the binding address + */ + public int getBindingPort() { + return this.bindingURL.getPort(); + } + + /** + * Returns the binding point for the webservice. Right now it naively + * assumes there's only one binding point with numerous soap operations. + * + * @return String + */ + public String getBinding() { + try { + NodeList services = this.WSDLDOC.getElementsByTagName("service"); + if (services.getLength() == 0) { + services = this.WSDLDOC.getElementsByTagName("wsdl:service"); + } + // the document should only have one service node + // if it doesn't it may not work! + Element node = (Element) services.item(0); + NodeList ports = node.getElementsByTagName("port"); + if (ports.getLength() == 0) { + ports = node.getElementsByTagName("wsdl:port"); + } + + if(ports.getLength()>0) { + Element pnode = (Element) ports.item(0); + // NOTUSED String portname = pnode.getAttribute("name"); + // used to check binding, but now it doesn't. it was + // failing when wsdl did not using binding as expected + NodeList servlist = pnode.getElementsByTagName("soap:address"); + // check wsdlsoap + if (servlist.getLength() == 0) { + servlist = pnode.getElementsByTagName("wsdlsoap:address"); + } + if (servlist.getLength() == 0) { + servlist = pnode.getElementsByTagName("SOAP:address"); + } + Element addr = (Element) servlist.item(0); + this.SOAPBINDING = addr.getAttribute("location"); + this.bindingURL = new URL(this.SOAPBINDING); + return this.SOAPBINDING; + } else { + return null; + } + } catch (Exception exception) { + log.warn("Exception calling getBinding:"+exception.getMessage(),exception); + return null; + } + } + + /** + * Method is used internally to connect to the URL. It's protected; + * therefore external classes should use parse to get the resource at the + * given location. + * + * @throws IOException + */ + protected void connect() throws IOException { + try { + CONN = WSDLURL.openConnection(); + CONN.setConnectTimeout(GET_WDSL_TIMEOUT); + CONN.setReadTimeout(GET_WDSL_TIMEOUT); + // in the rare case the WSDL is protected and requires + // authentication, use the AuthManager to set the + // authorization. Basic and Digest authorization are + // pretty weak and don't provide real security. + if (CONN instanceof HttpURLConnection && this.AUTH != null && this.AUTH.getAuthHeaderForURL(this.WSDLURL) != null) { + CONN.setRequestProperty("Authorization", this.AUTH.getAuthHeaderForURL(this.WSDLURL)); + } + } catch (IOException exception) { + throw exception; + } + } + + /** + * We try to close the connection to make sure it doesn't hang around. + */ + protected void close() { + try { + if (CONN != null) { + CONN.getInputStream().close(); + } + } catch (IOException ignored) { + // do nothing + } + } + + /** + * Method is used internally to parse the InputStream and build the document + * using javax.xml.parser API. + */ + protected void buildDocument() throws ParserConfigurationException, IOException, SAXException { + try { + DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docbuild = dbfactory.newDocumentBuilder(); + WSDLDOC = docbuild.parse(CONN.getInputStream()); + } catch (ParserConfigurationException exception) { + throw exception; + } catch (IOException exception) { + throw exception; + } catch (SAXException exception) { + throw exception; + } + } + + /** + * Call this method to retrieve the WSDL. This method must be called, + * otherwise a connection to the URL won't be made and the stream won't be + * parsed. + */ + public void parse() throws WSDLException { + try { + this.connect(); + this.buildDocument(); + SOAPOPS = this.getOperations(); + } catch (IOException exception) { + throw (new WSDLException(exception)); + } catch (SAXException exception) { + throw (new WSDLException(exception)); + } catch (ParserConfigurationException exception) { + throw (new WSDLException(exception)); + } finally { + this.close(); + } + } + + /** + * Get a list of the web methods as a string array. + */ + public String[] getWebMethods() { + for (int idx = 0; idx < SOAPOPS.length; idx++) { + // get the node + Node act = (Node) SOAPOPS[idx]; + // get the soap:operation + NodeList opers = ((Element) act).getElementsByTagName("soap:operation"); + if (opers.getLength() == 0) { + opers = ((Element) act).getElementsByTagName("wsdlsoap:operation"); + } + if (opers.getLength() == 0) { + opers = ((Element) act).getElementsByTagName("wsdl:operation"); + } + if (opers.getLength() == 0) { + opers = ((Element) act).getElementsByTagName("operation"); + } + + // there should only be one soap:operation node per operation + Element op = (Element) opers.item(0); + String value; + if (op != null) { + value = op.getAttribute("soapAction"); + } else { + value = ""; + } + String key = ((Element) act).getAttribute("name"); + this.ACTIONS.put(key, value); + } + Set keys = this.ACTIONS.keySet(); + String[] stringmeth = new String[keys.size()]; + Object[] stringKeys = keys.toArray(); + System.arraycopy(stringKeys, 0, stringmeth, 0, keys.size()); + return stringmeth; + } + + /** + * Return the soap action matching the operation name. + */ + public String getSoapAction(String key) { + return this.ACTIONS.get(key); + } + + /** + * Get the wsdl document. + */ + public Document getWSDLDocument() { + return WSDLDOC; + } + + /** + * Method will look at the binding nodes and see if the first child is a + * soap:binding. If it is, it adds it to an array. + * + * @return Node[] + */ + public Object[] getSOAPBindings() { + ArrayList list = new ArrayList(); + NodeList bindings = WSDLDOC.getElementsByTagName("binding"); + String soapBind = "soap:binding"; + if (bindings.getLength() == 0) { + bindings = WSDLDOC.getElementsByTagName("wsdl:binding"); + } + if (WSDLDOC.getElementsByTagName(soapBind).getLength() == 0) { + soapBind = "wsdlsoap:binding"; + } + if (WSDLDOC.getElementsByTagName(soapBind).getLength() == 0) { + soapBind = "SOAP:binding"; + } + + for (int idx = 0; idx < bindings.getLength(); idx++) { + Element nd = (Element) bindings.item(idx); + NodeList slist = nd.getElementsByTagName(soapBind); + if (slist.getLength() > 0) { + nd.getAttribute("name"); + list.add(nd); + } + } + if (list.size() > 0) { + return list.toArray(); + } + return new Object[0]; + } + + /** + * Look at the bindings with soap operations and get the soap operations. + * Since WSDL may describe multiple bindings and each binding may have + * multiple soap operations, we iterate through the binding nodes with a + * first child that is a soap binding. If a WSDL doesn't use the same + * formatting convention, it is possible we may not get a list of all the + * soap operations. If that is the case, getSOAPBindings() will need to be + * changed. I should double check the WSDL spec to see what the official + * requirement is. Another option is to get all operation nodes and check to + * see if the first child is a soap:operation. The benefit of not getting + * all operation nodes is WSDL could contain duplicate operations that are + * not SOAP methods. If there are a large number of methods and half of them + * are HTTP operations, getting all operations could slow things down. + * + * @return Node[] + */ + public Object[] getOperations() { + Object[] res = this.getSOAPBindings(); + ArrayList ops = new ArrayList(); + // first we iterate through the bindings + for (int idx = 0; idx < res.length; idx++) { + Element one = (Element) res[idx]; + NodeList opnodes = one.getElementsByTagName("operation"); + String soapOp = "soap:operation"; + if (opnodes.getLength() == 0) { + opnodes = one.getElementsByTagName("wsdl:operation"); + } + if (one.getElementsByTagName(soapOp).getLength() == 0) { + soapOp = "wsdlsoap:operation"; + } + // now we iterate through the operations + for (int idz = 0; idz < opnodes.getLength(); idz++) { + // if the first child is soap:operation + // we add it to the array + Element child = (Element) opnodes.item(idz); + + // TODO - the following code looks wrong - it does the same in both cases + NodeList soapnode = child.getElementsByTagName(soapOp); + if (soapnode.getLength() > 0) { + ops.add(child); + } else { + ops.add(child); + } + } + } + return ops.toArray(); + } + + /** + * return the "wsdl method name" from a soap action + * @param soapAction the soap action + * @return the associated "wsdl method name" or null if not found + */ + public String getSoapActionName(String soapAction) { + for (Map.Entry entry : ACTIONS.entrySet()) { + if (entry.getValue().equals(soapAction)) { + return entry.getKey(); + } + } + return null; + } + + /** + * Simple test for the class uses bidbuy.wsdl from Apache's soap driver + * examples. + * + * @param args + */ + public static void main(String[] args) { + try { + WSDLHelper help = + // new WSDLHelper("http://localhost/WSTest/WSTest.asmx?WSDL"); + // new WSDLHelper("http://localhost/AxisWSDL.xml"); + new WSDLHelper("http://localhost:8080/ServiceGateway.wsdl"); + // new WSDLHelper("http://services.bio.ifi.lmu.de:1046/prothesaurus/services/BiologicalNameService?wsdl"); + long start = System.currentTimeMillis(); + help.parse(); + String[] methods = help.getWebMethods(); + System.out.println("el: " + (System.currentTimeMillis() - start)); + for (int idx = 0; idx < methods.length; idx++) { + System.out.println("method name: " + methods[idx]); + } + System.out.println("service url: " + help.getBinding()); + System.out.println("protocol: " + help.getProtocol()); + System.out.println("port=" + help.getURL().getPort()); + } catch (Exception exception) { + System.out.println("main method catch:"); + exception.printStackTrace(); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/Filter.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/Filter.java new file mode 100644 index 0000000..f5d94ae --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/Filter.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util.accesslog; + +import org.apache.jmeter.testelement.TestElement; + +/** + * Description:
+ *
+ * Filter interface is designed to make it easier to use Access Logs for JMeter + * test plans. Normally, a person would have to clean a log file manually and + * create the JMeter requests. The access log parse utility uses the filter to + * include/exclude files by either file name or regular expression pattern. + *

+ * It will also be used by HttpSamplers that use access logs. Using access logs + * is intended as a way to simulate production traffic. For functional testing, + * it is better to use the standard functional testing tools in JMeter. Using + * access logs can also reduce the amount of memory needed to run large test + * plans.
+ * + * @version $Revision: 674365 $ + */ + +public interface Filter { + + /** + * @param oldextension + * @param newextension + */ + public void setReplaceExtension(String oldextension, String newextension); + + /** + * Include all files in the array. + * + * @param filenames + */ + public void includeFiles(String[] filenames); + + /** + * Exclude all files in the array + * + * @param filenames + */ + public void excludeFiles(String[] filenames); + + /** + * Include any log entry that contains the following regular expression + * pattern. + * + * @param regexp + */ + public void includePattern(String[] regexp); + + /** + * Exclude any log entry that contains the following regular expression + * pattern. + * + * @param regexp + */ + public void excludePattern(String[] regexp); + + /** + * Log parser will call this method to see if a particular entry should be + * filtered or not. + * + * @param path + * @return boolean + */ + public boolean isFiltered(String path,TestElement sampler); + + /** + * In case the user wants to replace the file extension, log parsers should + * call this method. This is useful for regression test plans. If a website + * is migrating from one platform to another and the file extension changes, + * the filter provides an easy way to do it without spending a lot of time. + * + * @param text + * @return String + */ + public String filter(String text); + + /** + * Tell the filter when the parsing has reached the end of the log file and + * is about to begin again. Gives the filter a chance to adjust it's values, + * if needed. + * + */ + public void reset(); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/Generator.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/Generator.java new file mode 100644 index 0000000..baa01f8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/Generator.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util.accesslog; + +/** + * Description:
+ *
+ * Generator is a base interface that defines the minimum methods needed to + * implement a concrete generator. The reason for creating this interface is + * eventually JMeter could use the logs directly rather than pre- process the + * logs into a JMeter .jmx file. In situations where a test plan simulates load + * from production logs, it is more efficient for JMeter to use the logs + * directly. + *

+ * From first hand experience, loading a test plan with 10K or more Requests + * requires a lot of memory. It's important to keep in mind this type of testing + * is closer to functional and regression testing than the typical stress tests. + * Typically, this kind of testing is most useful for search sites that get a + * large number of requests per day, but the request parameters vary + * dramatically. E-commerce sites typically have limited inventory, therefore it + * is better to design test plans that use data from the database. + *

+ * + * @version $Revision: 674365 $ + */ + +public interface Generator { + + /** + * close the generator + */ + public void close(); + + /** + * The host is the name of the server. + * + * @param host + */ + public void setHost(String host); + + /** + * This is the label for the request, which is used in the logs and results. + * + * @param label + */ + public void setLabel(String label); + + /** + * The method is the HTTP request method. It's normally POST or GET. + * + * @param post_get + */ + public void setMethod(String post_get); + + /** + * Set the request parameters + * + * @param params + */ + public void setParams(NVPair[] params); + + /** + * The path is the web page you want to test. + * + * @param path + */ + public void setPath(String path); + + /** + * The default port for HTTP is 80, but not all servers run on that port. + * + * @param port - + * port number + */ + public void setPort(int port); + + /** + * Set the querystring for the request if the method is GET. + * + * @param querystring + */ + public void setQueryString(String querystring); + + /** + * The source logs is the location where the access log resides. + * + * @param sourcefile + */ + public void setSourceLogs(String sourcefile); + + /** + * The target can be either a java.io.File or a Sampler. We make it generic, + * so that later on we can use these classes directly from a HTTPSampler. + * + * @param target + */ + public void setTarget(Object target); + + /** + * The method is responsible for calling the necessary methods to generate a + * valid request. If the generator is used to pre-process access logs, the + * method wouldn't return anything. If the generator is used by a control + * element, it should return the correct Sampler class with the required + * fields set. + */ + public Object generateRequest(); + + /** + * If the generator is converting the logs to a .jmx file, save should be + * called. + */ + public void save(); + + /** + * The purpose of the reset is so Samplers can explicitly call reset to + * create a new instance of HTTPSampler. + * + */ + public void reset(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java new file mode 100644 index 0000000..461bb0a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/LogFilter.java @@ -0,0 +1,426 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util.accesslog; + +import java.io.Serializable; +import java.util.ArrayList; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; + +// For JUnit tests, @see TestLogFilter + +/** + * Description:
+ *
+ * LogFilter is a basic implementation of Filter interface. This implementation + * will keep a record of the filtered strings to avoid repeating the process + * unnecessarily. + *

+ * The current implementation supports replacing the file extension. The reason + * for supporting this is from first hand experience porting an existing website + * to Tomcat + JSP. Later on we may want to provide the ability to replace the + * whole filename. If the need materializes, we can add it later. + *

+ * Example of how to use it is provided in the main method. An example is + * provided below. + *

+ * + *

+ * testf = new LogFilter();
+ * String[] incl = { "hello.html", "index.html", "/index.jsp" };
+ * String[] thefiles = { "/test/hello.jsp", "/test/one/hello.html", "hello.jsp", "hello.htm", "/test/open.jsp",
+ *      "/test/open.html", "/index.jsp", "/index.jhtml", "newindex.jsp", "oldindex.jsp", "oldindex1.jsp",
+ *      "oldindex2.jsp", "oldindex3.jsp", "oldindex4.jsp", "oldindex5.jsp", "oldindex6.jsp", "/test/index.htm" };
+ * testf.excludeFiles(incl);
+ * System.out.println(" ------------ exclude test -------------");
+ * for (int idx = 0; idx < thefiles.length; idx++) {
+ *  boolean fl = testf.isFiltered(thefiles[idx]);
+ *  String line = testf.filter(thefiles[idx]);
+ *  if (line != null) {
+ *     System.out.println("the file: " + line);
+ *  }
+ * }
+ * 
+ * + * As a general note. Both isFiltered and filter() have to be called. Calling + * either one will not produce the desired result. isFiltered(string) will tell + * you if a string should be filtered. The second step is to filter the string, + * which will return null if it is filtered and replace any part of the string + * that should be replaced. + *

+ * + */ + +public class LogFilter implements Filter, Serializable { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** protected members used by class to filter * */ + protected boolean CHANGEEXT = false; + + protected String OLDEXT = null; + + protected String NEWEXT = null; + + protected String[] INCFILE = null; + + protected String[] EXCFILE = null; + + protected boolean FILEFILTER = false; + + protected boolean USEFILE = true; + + protected String[] INCPTRN = null; + + protected String[] EXCPTRN = null; + + protected boolean PTRNFILTER = false; + + protected ArrayList EXCPATTERNS = new ArrayList(); + + protected ArrayList INCPATTERNS = new ArrayList(); + + protected String NEWFILE = null; + + /** + * The default constructor is empty + */ + public LogFilter() { + super(); + } + + /** + * The method will replace the file extension with the new one. You can + * either provide the extension without the period ".", or with. The method + * will check for period and add it if it isn't present. + * + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#setReplaceExtension(java.lang.String, + * java.lang.String) + */ + public void setReplaceExtension(String oldext, String newext) { + if (oldext != null && newext != null) { + this.CHANGEEXT = true; + if (oldext.indexOf(".") < 0 && newext.indexOf(".") < 0) { + this.OLDEXT = "." + oldext; + this.NEWEXT = "." + newext; + } else { + this.OLDEXT = oldext; + this.NEWEXT = newext; + } + } + } + + /** + * Give the filter a list of files to include + * + * @param filenames + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#includeFiles(java.lang.String[]) + */ + public void includeFiles(String[] filenames) { + if (filenames != null && filenames.length > 0) { + INCFILE = filenames; + this.FILEFILTER = true; + } + } + + /** + * Give the filter a list of files to exclude + * + * @param filenames + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#excludeFiles(java.lang.String[]) + */ + public void excludeFiles(String[] filenames) { + if (filenames != null && filenames.length > 0) { + EXCFILE = filenames; + this.FILEFILTER = true; + } + } + + /** + * Give the filter a set of regular expressions to filter with for + * inclusion. This method hasn't been fully implemented and test yet. The + * implementation is not complete. + * + * @param regexp + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#includePattern(String[]) + */ + public void includePattern(String[] regexp) { + if (regexp != null && regexp.length > 0) { + INCPTRN = regexp; + this.PTRNFILTER = true; + // now we create the compiled pattern and + // add it to the arraylist + for (int idx = 0; idx < INCPTRN.length; idx++) { + this.INCPATTERNS.add(this.createPattern(INCPTRN[idx])); + } + } + } + + /** + * Give the filter a set of regular expressions to filter with for + * exclusion. This method hasn't been fully implemented and test yet. The + * implementation is not complete. + * + * @param regexp + * + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#excludePattern(String[]) + */ + public void excludePattern(String[] regexp) { + if (regexp != null && regexp.length > 0) { + EXCPTRN = regexp; + this.PTRNFILTER = true; + // now we create the compiled pattern and + // add it to the arraylist + for (int idx = 0; idx < EXCPTRN.length; idx++) { + this.EXCPATTERNS.add(this.createPattern(EXCPTRN[idx])); + } + } + } + + /** + * In the case of log filtering the important thing is whether the log entry + * should be used. Therefore, the method will only return true if the entry + * should be used. Since the interface defines both inclusion and exclusion, + * that means by default inclusion filtering assumes all entries are + * excluded unless it matches. In the case of exlusion filtering, it assumes + * all entries are included unless it matches, which means it should be + * excluded. + * + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#isFiltered(String, TestElement) + * @param path + * @return boolean + */ + public boolean isFiltered(String path,TestElement el) { + // we do a quick check to see if any + // filters are set. If not we just + // return false to be efficient. + if (this.FILEFILTER || this.PTRNFILTER || this.CHANGEEXT) { + if (this.FILEFILTER) { + return filterFile(path); + } else if (this.PTRNFILTER) { + return filterPattern(path); + } else { + return false; + } + } else { + return false; + } + } + + /** + * Filter the file. The implementation performs the exclusion first before + * the inclusion. This means if a file name is in both string arrays, the + * exclusion will take priority. Depending on how users expect this to work, + * we may want to change the priority so that inclusion is performed first + * and exclusion second. Another possible alternative is to perform both + * inclusion and exclusion. Doing so would make the most sense if the method + * throws an exception and tells the user the same filename is in both the + * include and exclude array. + * + * @param file + * @return boolean + */ + protected boolean filterFile(String file) { + // double check this logic make sure it + // makes sense + if (this.EXCFILE != null) { + return excFile(file); + } else if (this.INCFILE != null) { + return !incFile(file); + } + return false; + } + + /** + * Method implements the logic for filtering file name inclusion. The method + * iterates through the array and uses indexOf. Once it finds a match, it + * won't bother with the rest of the filenames in the array. + * + * @param text + * @return boolean include + */ + public boolean incFile(String text) { + // inclusion filter assumes most of + // the files are not wanted, therefore + // usefile is set to false unless it + // matches. + this.USEFILE = false; + for (int idx = 0; idx < this.INCFILE.length; idx++) { + if (text.indexOf(this.INCFILE[idx]) > -1) { + this.USEFILE = true; + break; + } + } + return this.USEFILE; + } + + /** + * Method implements the logic for filtering file name exclusion. The method + * iterates through the array and uses indexOf. Once it finds a match, it + * won't bother with the rest of the filenames in the array. + * + * @param text + * @return boolean exclude + */ + public boolean excFile(String text) { + // exclusion filter assumes most of + // the files are used, therefore + // usefile is set to true, unless + // it matches. + this.USEFILE = true; + boolean exc = false; + for (int idx = 0; idx < this.EXCFILE.length; idx++) { + if (text.indexOf(this.EXCFILE[idx]) > -1) { + exc = true; + this.USEFILE = false; + break; + } + } + return exc; + } + + /** + * The current implemenation assumes the user has checked the regular + * expressions so that they don't cancel each other. The basic assumption is + * the method will return true if the text should be filtered. If not, it + * will return false, which means it should not be filtered. + * + * @param text + * @return boolean + */ + protected boolean filterPattern(String text) { + if (this.INCPTRN != null) { + return !incPattern(text); + } else if (this.EXCPTRN != null) { + return excPattern(text); + } + return false; + } + + /** + * By default, the method assumes the entry is not included, unless it + * matches. In that case, it will return true. + * + * @param text + * @return true if text is included + */ + protected boolean incPattern(String text) { + this.USEFILE = false; + for (int idx = 0; idx < this.INCPATTERNS.size(); idx++) { + if (JMeterUtils.getMatcher().contains(text, this.INCPATTERNS.get(idx))) { + this.USEFILE = true; + break; + } + } + return this.USEFILE; + } + + /** + * The method assumes by default the text is not excluded. If the text + * matches the pattern, it will then return true. + * + * @param text + * @return true if text is excluded + */ + protected boolean excPattern(String text) { + this.USEFILE = true; + boolean exc = false; + for (int idx = 0; idx < this.EXCPATTERNS.size(); idx++) { + if (JMeterUtils.getMatcher().contains(text, this.EXCPATTERNS.get(idx))) { + exc = true; + this.USEFILE = false; + break; + } + } + return exc; + } + + /** + * Method uses indexOf to replace the old extension with the new extesion. + * It might be good to use regular expression, but for now this is a simple + * method. The method isn't designed to replace multiple instances of the + * text, since that isn't how file extensions work. If the string contains + * more than one instance of the old extension, only the first instance will + * be replaced. + * + * @param text + * @return boolean + */ + public boolean replaceExtension(String text) { + int pt = text.indexOf(this.OLDEXT); + if (pt > -1) { + int extsize = this.OLDEXT.length(); + this.NEWFILE = text.substring(0, pt) + this.NEWEXT + text.substring(pt + extsize); + return true; + } else { + return false; + } + } + + /** + * The current implementation checks the boolean if the text should be used + * or not. isFilter( string) has to be called first. + * + * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#filter(java.lang.String) + */ + public String filter(String text) { + if (this.CHANGEEXT) { + if (replaceExtension(text)) { + return this.NEWFILE; + } else { + return text; + } + } else if (this.USEFILE) { + return text; + } else { + return null; + } + } + + /** + * create a new pattern object from the string. + * + * @param pattern + * @return Pattern + */ + public Pattern createPattern(String pattern) { + try { + return JMeterUtils.getPatternCache().getPattern(pattern, + Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.SINGLELINE_MASK); + } catch (MalformedCachePatternException exception) { + log.error("Problem with pattern: "+pattern,exception); + return null; + } + } + + /** + * {@inheritDoc} + */ + public void reset() { + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java new file mode 100644 index 0000000..e9678c9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/LogParser.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util.accesslog; + +import org.apache.jmeter.testelement.TestElement; + +/** + * Description:
+ *
+ * LogParser is the base interface for classes implementing concrete parse + * logic. For an example of how to use the interface, look at the Tomcat access + * log parser. + *

+ * The original log parser was written in 2 hours to parse access logs. Since + * then, the design and implementation has been rewritten from scratch several + * times to make it more generic and extensible. The first version was hard + * coded and written over the weekend. + *

+ * + * @version $Revision: 674365 $ + */ + +public interface LogParser { + + /** + * close the any streams or readers. + */ + public void close(); + + /** + * the method will parse the given number of lines. Pass "-1" to parse the + * entire file. If the end of the file is reached without parsing a line, a + * 0 is returned. If the method is subsequently called again, it will + * restart parsing at the beginning. + * + * @param count + * @return int + */ + public int parseAndConfigure(int count, TestElement el); + + /** + * We allow for filters, so that users can simply point to an Access log + * without having to clean it up. This makes it significantly easier and + * reduces the amount of work. Plus I'm lazy, so going through a log file to + * clean it up is a bit tedious. One example of this is using the filter to + * exclude any log entry that has a 505 response code. + * + * @param filter + */ + public void setFilter(Filter filter); + + /** + * The method is provided to make it easy to dynamically create new classes + * using Class.newInstance(). Then the access log file is set using this + * method. + * + * @param source + */ + public void setSourceFile(String source); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/NVPair.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/NVPair.java new file mode 100644 index 0000000..73f9301 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/NVPair.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util.accesslog; + +/** + * Description:
+ *
+ * + * @version $Revision: 674365 $ + */ + +public class NVPair { + + protected String NAME = ""; + + protected String VALUE = ""; + + public NVPair() { + } + + /** + * The constructor takes a name and value which represent HTTP request + * parameters. + * + * @param name + * @param value + */ + public NVPair(String name, String value) { + this.NAME = name; + this.VALUE = value; + } + + /** + * Set the name + * + * @param name + */ + public void setName(String name) { + this.NAME = name; + } + + /** + * Set the value + * + * @param value + */ + public void setValue(String value) { + this.VALUE = value; + } + + /** + * Return the name + * + * @return name + */ + public String getName() { + return this.NAME; + } + + /** + * Return the value + * + * @return value + */ + public String getValue() { + return this.VALUE; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java new file mode 100644 index 0000000..c1514ee --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/OrderPreservingLogParser.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util.accesslog; + +import org.apache.jmeter.testelement.TestElement; + +public class OrderPreservingLogParser extends SharedTCLogParser { + + public OrderPreservingLogParser() { + super(); + } + + public OrderPreservingLogParser(String source) { + super(source); + } + + /** + * parse a set number of lines from the access log. Keep in mind the number + * of lines parsed will depend the filter and number of lines in the log. + * The method returns the actual lines parsed. + * + * @param count + * @return lines parsed + */ + @Override + public synchronized int parseAndConfigure(int count, TestElement el) { + return this.parse(el, count); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java new file mode 100644 index 0000000..df6e262 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/SessionFilter.java @@ -0,0 +1,215 @@ +/* + * 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. + * + */ + +/* + * Created on May 21, 2004 + */ +package org.apache.jmeter.protocol.http.util.accesslog; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.sampler.HTTPSampler; +import org.apache.jmeter.testelement.TestCloneable; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * Provides Session Filtering for the AccessLog Sampler. + * + */ +public class SessionFilter implements Filter, Serializable, TestCloneable,ThreadListener { + private static final long serialVersionUID = 232L; + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * These objects are static across multiple threads in a test, via clone() + * method. + */ + protected Map cookieManagers; + protected Set managersInUse; + + protected CookieManager lastUsed; + + /* + * (non-Javadoc) + * + * @see org.apache.jmeter.protocol.http.util.accesslog.LogFilter#excPattern(java.lang.String) + */ + protected boolean hasExcPattern(String text) { + return false; + } + + protected String getIpAddress(String logLine) { + Pattern incIp = JMeterUtils.getPatternCache().getPattern("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}", + Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.SINGLELINE_MASK); + Perl5Matcher matcher = JMeterUtils.getMatcher(); + matcher.contains(logLine, incIp); + return matcher.getMatch().group(0); + } + + /** + * {@inheritDoc} + */ + public void reset() { + cookieManagers.clear(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object clone() { + if(cookieManagers == null) + { + cookieManagers = new ConcurrentHashMap(); + } + if(managersInUse == null) + { + managersInUse = Collections.synchronizedSet(new HashSet()); + } + SessionFilter f = new SessionFilter(); + f.cookieManagers = cookieManagers; + f.managersInUse = managersInUse; + return f; + } + + /** + * + */ + public SessionFilter() { + } + + /** + * {@inheritDoc} + */ + public void excludeFiles(String[] filenames) { + } + + /** + * {@inheritDoc} + */ + public void excludePattern(String[] regexp) { + } + + /** + * {@inheritDoc} + */ + public String filter(String text) { + return text; + } + + /** + * {@inheritDoc} + */ + public void includeFiles(String[] filenames) { + } + + /** + * {@inheritDoc} + */ + public void includePattern(String[] regexp) { + } + + /** + * {@inheritDoc} + */ + public boolean isFiltered(String path,TestElement sampler) { + String ipAddr = getIpAddress(path); + CookieManager cm = getCookieManager(ipAddr); + ((HTTPSampler)sampler).setCookieManager(cm); + return false; + } + + protected CookieManager getCookieManager(String ipAddr) + { + CookieManager cm = null; + // First have to release the cookie we were using so other + // threads stuck in wait can move on + synchronized(managersInUse) + { + if(lastUsed != null) + { + managersInUse.remove(lastUsed); + managersInUse.notify(); + } + } + // let notified threads move on and get lock on managersInUse + if(lastUsed != null) + { + Thread.yield(); + } + // here is the core routine to find appropriate cookie manager and + // check it's not being used. If used, wait until whoever's using it gives + // it up + synchronized(managersInUse) + { + cm = cookieManagers.get(ipAddr); + if(cm == null) + { + cm = new CookieManager(); + cookieManagers.put(ipAddr,cm); + } + while(managersInUse.contains(cm)) + { + try { + managersInUse.wait(); + } catch (InterruptedException e) { + log.info("SessionFilter wait interrupted"); + } + } + managersInUse.add(cm); + lastUsed = cm; + } + return cm; + } + + /** + * {@inheritDoc} + */ + public void setReplaceExtension(String oldextension, String newextension) { + } + + /** + * {@inheritDoc} + */ + public void threadFinished() { + synchronized(managersInUse) + { + managersInUse.remove(lastUsed); + managersInUse.notify(); + } + } + + /** + * {@inheritDoc} + */ + public void threadStarted() { + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java new file mode 100644 index 0000000..2278bfe --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/SharedTCLogParser.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util.accesslog; + +import java.io.IOException; + +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestCloneable; +import org.apache.jmeter.testelement.TestElement; + +public class SharedTCLogParser extends TCLogParser implements TestCloneable { + + public SharedTCLogParser() { + super(); + } + + public SharedTCLogParser(String source) { + super(source); + } + + /** + * {@inheritDoc} + */ + @Override + public Object clone() { + SharedTCLogParser parser = new SharedTCLogParser(); + parser.FILENAME = FILENAME; + parser.FILTER = FILTER; + return parser; + } + + /** + * {@inheritDoc} + */ + @Override + public int parse(TestElement el, int parseCount) { + FileServer fileServer = FileServer.getFileServer(); + fileServer.reserveFile(FILENAME); + try { + return parse(fileServer, el, parseCount); + } catch (Exception exception) { + log.error("Problem creating samples", exception); + } + return -1;// indicate that an error occured + } + + /** + * The method is responsible for reading each line, and breaking out of the + * while loop if a set number of lines is given. + * + * @param breader + */ + protected int parse(FileServer breader, TestElement el, int parseCount) { + int actualCount = 0; + String line = null; + try { + // read one line at a time using + // BufferedReader + line = breader.readLine(FILENAME); + while (line != null) { + if (line.length() > 0) { + actualCount += this.parseLine(line, el); + } + // we check the count to see if we have exceeded + // the number of lines to parse. There's no way + // to know where to stop in the file. Therefore + // we use break to escape the while loop when + // we've reached the count. + if (parseCount != -1 && actualCount >= parseCount) { + break; + } + line = breader.readLine(FILENAME); + } + if (line == null) { + breader.closeFile(FILENAME); + // this.READER = new BufferedReader(new + // FileReader(this.SOURCE)); + // parse(this.READER,el); + } + } catch (IOException ioe) { + log.error("Error reading log file", ioe); + } + return actualCount; + } + + /** + * {@inheritDoc} + */ + @Override + public void close() { + try { + FileServer.getFileServer().closeFile(FILENAME); + } catch (IOException e) { + // do nothing + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/StandardGenerator.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/StandardGenerator.java new file mode 100644 index 0000000..2eb7550 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/StandardGenerator.java @@ -0,0 +1,219 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util.accesslog; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; + +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Description:
+ *
+ * StandardGenerator will be the default generator used to pre-process logs. It + * uses JMeter classes to generate the .jmx file. The first version of the + * utility only generated the HTTP requests as XML, but it required users to + * copy and paste it into a blank jmx file. Doing that way isn't flexible and + * would require changes to keep the format in sync. + *

+ * This version is a completely new class with a totally different + * implementation, since generating the XML is no longer handled by the + * generator. The generator is only responsible for handling the parsed results + * and passing it to the appropriate JMeter class. + *

+ * Notes:
+ * the class needs to first create a thread group and add it to the HashTree. + * Then the samplers should be added to the thread group. Listeners shouldn't be + * added and should be left up to the user. One option is to provide parameters, + * so the user can pass the desired listener to the tool. + *

+ * + */ + +public class StandardGenerator implements Generator, Serializable { + + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected HTTPSamplerBase SAMPLE = null; + + protected transient FileWriter WRITER = null; + + protected transient OutputStream OUTPUT = null; + + protected String FILENAME = null; + + protected File FILE = null; + + // NOT USED transient protected ThreadGroup THREADGROUP = null; + // Anyway, was this supposed to be the class from java.lang, or + // jmeter.threads? + + /** + * The constructor is used by GUI and samplers to generate request objects. + */ + public StandardGenerator() { + super(); + init(); + } + + /** + * + * @param file + */ + public StandardGenerator(String file) { + FILENAME = file; + init(); + } + + /** + * initialize the generator. It should create the following objects. + *

+ *

    + *
  1. ListedHashTree
  2. + *
  3. ThreadGroup
  4. + *
  5. File object
  6. + *
  7. Writer
  8. + *
+ */ + private void init() {// called from ctor, so must not be overridable + generateRequest(); + } + + /** + * Create the FileWriter to save the JMX file. + */ + protected void initStream() { + try { + this.OUTPUT = new FileOutputStream(FILE); + } catch (IOException exception) { + log.error(exception.getMessage()); + } + } + + /** + * {@inheritDoc} + */ + public void close() { + JOrphanUtils.closeQuietly(OUTPUT); + JOrphanUtils.closeQuietly(WRITER); + } + + /** + * {@inheritDoc} + */ + public void setHost(String host) { + SAMPLE.setDomain(host); + } + + /** + * {@inheritDoc} + */ + public void setLabel(String label) { + + } + + /** + * {@inheritDoc} + */ + public void setMethod(String post_get) { + SAMPLE.setMethod(post_get); + } + + /** + * {@inheritDoc} + */ + public void setParams(NVPair[] params) { + for (int idx = 0; idx < params.length; idx++) { + SAMPLE.addArgument(params[idx].getName(), params[idx].getValue()); + } + } + + /** + * {@inheritDoc} + */ + public void setPath(String path) { + SAMPLE.setPath(path); + } + + /** + * {@inheritDoc} + */ + public void setPort(int port) { + SAMPLE.setPort(port); + } + + /** + * {@inheritDoc} + */ + public void setQueryString(String querystring) { + SAMPLE.parseArguments(querystring); + } + + /** + * {@inheritDoc} + */ + public void setSourceLogs(String sourcefile) { + } + + /** + * {@inheritDoc} + */ + public void setTarget(Object target) { + } + + /** + * {@inheritDoc} + */ + public Object generateRequest() { + SAMPLE = HTTPSamplerFactory.newInstance(); + return SAMPLE; + } + + /** + * save must be called to write the jmx file, otherwise it will not be + * saved. + */ + public void save() { + // no implementation at this time, since + // we bypass the idea of having a console + // tool to generate test plans. Instead + // I decided to have a sampler that uses + // the generator and parser directly + } + + /** + * Reset the HTTPSampler to make sure it is a new instance. + *

+ * {@inheritDoc} + */ + public void reset() { + SAMPLE = null; + generateRequest(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java new file mode 100644 index 0000000..f3147b9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/util/accesslog/TCLogParser.java @@ -0,0 +1,558 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.http.util.accesslog; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; +import java.util.zip.GZIPInputStream; + +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +// For JUnit tests, @see TestTCLogParser + +/** + * Description:
+ *
+ * Currently the parser only handles GET/POST requests. It's easy enough to add + * support for other request methods by changing checkMethod. The is a complete + * rewrite of a tool I wrote for myself earlier. The older algorithm was basic + * and did not provide the same level of flexibility I want, so I wrote a new + * one using a totally new algorithm. This implementation reads one line at a + * time using BufferedReader. When it gets to the end of the file and the + * sampler needs to get more requests, the parser will re-initialize the + * BufferedReader. The implementation uses StringTokenizer to create tokens. + *

+ * The parse algorithm is the following: + *

+ *

    + *
  1. cleans the entry by looking for backslash "\" + *
  2. looks to see if GET or POST is in the line + *
  3. tokenizes using quotes " + *
  4. finds the token with the request method + *
  5. gets the string of the token and tokenizes it using space + *
  6. finds the first token beginning with slash character + *
  7. tokenizes the string using question mark "?" + *
  8. get the path from the first token + *
  9. returns the second token and checks it for parameters + *
  10. tokenizes the string using ampersand "&" + *
  11. parses each token to name/value pairs + *
+ *

+ * Extending this class is fairly simple. Most access logs use the same format + * starting from the request method. Therefore, changing the implementation of + * cleanURL(string) method should be sufficient to support new log formats. + * Tomcat uses common log format, so any webserver that uses the format should + * work with this parser. Servers that are known to use non standard formats are + * IIS and Netscape. + *

+ * + */ + +public class TCLogParser implements LogParser { + protected static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String GET = "GET"; + + public static final String POST = "POST"; + + public static final String HEAD = "HEAD"; + + /** protected members * */ + protected String RMETHOD = null; + + /** + * The path to the access log file + */ + protected String URL_PATH = null; + + protected boolean useFILE = true; + + protected File SOURCE = null; + + protected String FILENAME = null; + + protected BufferedReader READER = null; + + /** + * Handles to supporting classes + */ + protected Filter FILTER = null; + + /** + * by default, we probably should decode the parameter values + */ + protected boolean decode = true; + + // TODO downcase UPPER case non-final variables + + /** + * + */ + public TCLogParser() { + super(); + } + + /** + * @param source + */ + public TCLogParser(String source) { + setSourceFile(source); + } + + /** + * by default decode is set to true. if the parameters shouldn't be + * decoded, call the method with false + * @param decodeparams + */ + public void setDecodeParameterValues(boolean decodeparams) { + this.decode = decodeparams; + } + + /** + * decode the parameter values is to true by default + * @return if paramter values should be decoded + */ + public boolean decodeParameterValue() { + return this.decode; + } + + /** + * Calls this method to set whether or not to use the path in the log. We + * may want to provide the ability to filter the log file later on. By + * default, the parser uses the file in the log. + * + * @param file + */ + public void setUseParsedFile(boolean file) { + this.useFILE = file; + } + + /** + * Use the filter to include/exclude files in the access logs. This is + * provided as a convienance and reduce the need to spend hours cleaning up + * log files. + * + * @param filter + */ + public void setFilter(Filter filter) { + FILTER = filter; + } + + /** + * Sets the source file. + * + * @param source + */ + public void setSourceFile(String source) { + this.FILENAME = source; + } + + /** + * parse the entire file. + * + * @return boolean success/failure + */ + public int parse(TestElement el, int parseCount) { + if (this.SOURCE == null) { + this.SOURCE = new File(this.FILENAME); + } + try { + if (this.READER == null) { + this.READER = getReader(this.SOURCE); + } + return parse(this.READER, el, parseCount); + } catch (Exception exception) { + log.error("Problem creating samples", exception); + } + return -1;// indicate that an error occured + } + + private static BufferedReader getReader(File file) throws IOException { + if (! isGZIP(file)) { + return new BufferedReader(new FileReader(file)); + } + GZIPInputStream in = new GZIPInputStream(new FileInputStream(file)); + return new BufferedReader(new InputStreamReader(in)); + } + + private static boolean isGZIP(File file) throws IOException { + FileInputStream in = new FileInputStream(file); + try { + return in.read() == (GZIPInputStream.GZIP_MAGIC & 0xFF) + && in.read() == (GZIPInputStream.GZIP_MAGIC >> 8); + } finally { + in.close(); + } + } + + /** + * parse a set number of lines from the access log. Keep in mind the number + * of lines parsed will depend the filter and number of lines in the log. + * The method returns the actual number of lines parsed. + * + * @param count + * @return lines parsed + */ + public int parseAndConfigure(int count, TestElement el) { + return this.parse(el, count); + } + + /** + * The method is responsible for reading each line, and breaking out of the + * while loop if a set number of lines is given. + * + * @param breader + */ + protected int parse(BufferedReader breader, TestElement el, int parseCount) { + int actualCount = 0; + String line = null; + try { + // read one line at a time using + // BufferedReader + line = breader.readLine(); + while (line != null) { + if (line.length() > 0) { + actualCount += this.parseLine(line, el); + } + // we check the count to see if we have exceeded + // the number of lines to parse. There's no way + // to know where to stop in the file. Therefore + // we use break to escape the while loop when + // we've reached the count. + if (parseCount != -1 && actualCount >= parseCount) { + break; + } + line = breader.readLine(); + } + if (line == null) { + breader.close(); + this.READER = null; + // this.READER = new BufferedReader(new + // FileReader(this.SOURCE)); + // parse(this.READER,el); + } + } catch (IOException ioe) { + log.error("Error reading log file", ioe); + } + return actualCount; + } + + /** + * parseLine calls the other parse methods to parse the given text. + * + * @param line + */ + protected int parseLine(String line, TestElement el) { + int count = 0; + // we clean the line to get + // rid of extra stuff + String cleanedLine = this.cleanURL(line); + log.debug("parsing line: " + line); + // now we set request method + el.setProperty(HTTPSamplerBase.METHOD, RMETHOD); + if (FILTER != null) { + log.debug("filter is not null"); + if (!FILTER.isFiltered(line,el)) { + log.debug("line was not filtered"); + // increment the current count + count++; + // we filter the line first, before we try + // to separate the URL into file and + // parameters. + line = FILTER.filter(cleanedLine); + if (line != null) { + createUrl(line, el); + } + } else { + log.debug("Line was filtered"); + } + } else { + log.debug("filter was null"); + // increment the current count + count++; + // in the case when the filter is not set, we + // parse all the lines + createUrl(cleanedLine, el); + } + return count; + } + + /** + * @param line + */ + private void createUrl(String line, TestElement el) { + String paramString = null; + // check the URL for "?" symbol + paramString = this.stripFile(line, el); + if (paramString != null) { + this.checkParamFormat(line); + // now that we have stripped the file, we can parse the parameters + this.convertStringToJMRequest(paramString, el); + } + } + + /** + * The method cleans the URL using the following algorithm. + *

    + *
  1. check for double quotes + *
  2. check the request method + *
  3. tokenize using double quotes + *
  4. find first token containing request method + *
  5. tokenize string using space + *
  6. find first token that begins with "/" + *
+ * Example Tomcat log entry: + *

+ * 127.0.0.1 - - [08/Jan/2003:07:03:54 -0500] "GET /addrbook/ HTTP/1.1" 200 + * 1981 + *

+ * + * @param entry + * @return cleaned url + */ + public String cleanURL(String entry) { + String url = entry; + // if the string contains atleast one double + // quote and checkMethod is true, go ahead + // and tokenize the string. + if (entry.indexOf("\"") > -1 && checkMethod(entry)) { + StringTokenizer tokens = null; + // we tokenize using double quotes. this means + // for tomcat we should have 3 tokens if there + // isn't any additional information in the logs + tokens = this.tokenize(entry, "\""); + while (tokens.hasMoreTokens()) { + String toke = tokens.nextToken(); + // if checkMethod on the token is true + // we tokenzie it using space and escape + // the while loop. Only the first matching + // token will be used + if (checkMethod(toke)) { + StringTokenizer token2 = this.tokenize(toke, " "); + while (token2.hasMoreTokens()) { + String t = (String) token2.nextElement(); + if (t.equalsIgnoreCase(GET)) { + RMETHOD = GET; + } else if (t.equalsIgnoreCase(POST)) { + RMETHOD = POST; + } else if (t.equalsIgnoreCase(HEAD)) { + RMETHOD = HEAD; + } + // there should only be one token + // that starts with slash character + if (t.startsWith("/")) { + url = t; + break; + } + } + break; + } + } + return url; + } + // we return the original string + return url; + } + + /** + * The method checks for POST and GET methods currently. The other methods + * aren't supported yet. + * + * @param text + * @return if method is supported + */ + public boolean checkMethod(String text) { + if (text.indexOf("GET") > -1) { + this.RMETHOD = GET; + return true; + } else if (text.indexOf("POST") > -1) { + this.RMETHOD = POST; + return true; + } else if (text.indexOf("HEAD") > -1) { + this.RMETHOD = HEAD; + return true; + } else { + return false; + } + } + + /** + * Tokenize the URL into two tokens. If the URL has more than one "?", the + * parse may fail. Only the first two tokens are used. The first token is + * automatically parsed and set at URL_PATH. + * + * @param url + * @return String parameters + */ + public String stripFile(String url, TestElement el) { + if (url.indexOf("?") > -1) { + StringTokenizer tokens = this.tokenize(url, "?"); + this.URL_PATH = tokens.nextToken(); + el.setProperty(HTTPSamplerBase.PATH, URL_PATH); + return tokens.hasMoreTokens() ? tokens.nextToken() : null; + } + el.setProperty(HTTPSamplerBase.PATH, url); + return null; + } + + /** + * Checks the string to make sure it has /path/file?name=value format. If + * the string doesn't have "?", it will return false. + * + * @param url + * @return boolean + */ + public boolean checkURL(String url) { + if (url.indexOf("?") > -1) { + return true; + } + return false; + } + + /** + * Checks the string to see if it contains "&" and "=". If it does, return + * true, so that it can be parsed. + * + * @param text + * @return boolean + */ + public boolean checkParamFormat(String text) { + if (text.indexOf("&") > -1 && text.indexOf("=") > -1) { + return true; + } + return false; + } + + /** + * Convert a single line into XML + * + * @param text + */ + public void convertStringToJMRequest(String text, TestElement el) { + ((HTTPSamplerBase) el).parseArguments(text); + } + + /** + * Parse the string parameters into NVPair[] array. Once they are parsed, it + * is returned. The method uses parseOneParameter(string) to convert each + * pair. + * + * @param stringparams + */ + public NVPair[] convertStringtoNVPair(String stringparams) { + List vparams = this.parseParameters(stringparams); + NVPair[] nvparams = new NVPair[vparams.size()]; + // convert the Parameters + for (int idx = 0; idx < nvparams.length; idx++) { + nvparams[idx] = this.parseOneParameter(vparams.get(idx)); + } + return nvparams; + } + + /** + * Method expects name and value to be separated by an equal sign "=". The + * method uses StringTokenizer to make a NVPair object. If there happens to + * be more than one "=" sign, the others are ignored. The chance of a string + * containing more than one is unlikely and would not conform to HTTP spec. + * I should double check the protocol spec to make sure this is accurate. + * + * @param parameter + * to be parsed + * @return NVPair + */ + protected NVPair parseOneParameter(String parameter) { + String name = ""; // avoid possible NPE when trimming the name + String value = null; + try { + StringTokenizer param = this.tokenize(parameter, "="); + name = param.nextToken(); + value = param.nextToken(); + } catch (Exception e) { + // do nothing. it's naive, but since + // the utility is meant to parse access + // logs the formatting should be correct + } + if (value == null) { + value = ""; + } else { + if (decode) { + try { + value = URLDecoder.decode(value,"UTF-8"); + } catch (UnsupportedEncodingException e) { + log.warn(e.getMessage()); + } + } + } + return new NVPair(name.trim(), value.trim()); + } + + /** + * Method uses StringTokenizer to convert the string into single pairs. The + * string should conform to HTTP protocol spec, which means the name/value + * pairs are separated by the ampersand symbol "&". Some one could write the + * querystrings by hand, but that would be round about and go against the + * purpose of this utility. + * + * @param parameters + * @return List + */ + protected List parseParameters(String parameters) { + List parsedParams = new ArrayList(); + StringTokenizer paramtokens = this.tokenize(parameters, "&"); + while (paramtokens.hasMoreElements()) { + parsedParams.add(paramtokens.nextToken()); + } + return parsedParams; + } + + /** + * Parses the line using java.util.StringTokenizer. + * + * @param line + * line to be parsed + * @param delim + * delimiter + * @return StringTokenizer + */ + public StringTokenizer tokenize(String line, String delim) { + return new StringTokenizer(line, delim); + } + + public void close() { + try { + this.READER.close(); + this.READER = null; + this.SOURCE = null; + } catch (IOException e) { + // do nothing + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java b/ApacheJmeter/src/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java new file mode 100644 index 0000000..e3bb104 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/http/visualizers/RequestViewHTTP.java @@ -0,0 +1,344 @@ +/* +o * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.protocol.http.visualizers; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import javax.swing.JPanel; +import javax.swing.JSplitPane; +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; + +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.TextBoxDialoger.TextBoxDoubleClick; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.RequestView; +import org.apache.jmeter.visualizers.SamplerResultTab.RowResult; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.log.Logger; + +/** + * Specializer panel to view a HTTP request parsed + * + */ +public class RequestViewHTTP implements RequestView { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String KEY_LABEL = "view_results_table_request_tab_http"; //$NON-NLS-1$ + + private static final String CHARSET_DECODE = "ISO-8859-1"; //$NON-NLS-1$ + + private static final String PARAM_CONCATENATE = "&"; //$NON-NLS-1$ + + private JPanel paneParsed; + + private ObjectTableModel requestModel = null; + + private ObjectTableModel paramsModel = null; + + private ObjectTableModel headersModel = null; + + private static final String[] COLUMNS_REQUEST = new String[] { + " ", // one space for blank header // $NON-NLS-1$ + " " }; // one space for blank header // $NON-NLS-1$ + + private static final String[] COLUMNS_PARAMS = new String[] { + "view_results_table_request_params_key", // $NON-NLS-1$ + "view_results_table_request_params_value" }; // $NON-NLS-1$ + + private static final String[] COLUMNS_HEADERS = new String[] { + "view_results_table_request_headers_key", // $NON-NLS-1$ + "view_results_table_request_headers_value" }; // $NON-NLS-1$ + + private JTable tableRequest = null; + + private JTable tableParams = null; + + private JTable tableHeaders = null; + + // Request headers column renderers + private static final TableCellRenderer[] RENDERERS_REQUEST = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + // Request headers column renderers + private static final TableCellRenderer[] RENDERERS_PARAMS = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + // Request headers column renderers + private static final TableCellRenderer[] RENDERERS_HEADERS = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + /** + * Pane to view HTTP request sample in view results tree + */ + public RequestViewHTTP() { + requestModel = new ObjectTableModel(COLUMNS_REQUEST, RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + paramsModel = new ObjectTableModel(COLUMNS_PARAMS, RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + headersModel = new ObjectTableModel(COLUMNS_HEADERS, RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#init() + */ + public void init() { + paneParsed = new JPanel(new BorderLayout(0, 5)); + paneParsed.add(createRequestPane()); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#clearData() + */ + public void clearData() { + requestModel.clearData(); + paramsModel.clearData(); + headersModel.clearData(); // clear results table before filling + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#setSamplerResult(java.lang.Object) + */ + public void setSamplerResult(Object objectResult) { + + if (objectResult instanceof HTTPSampleResult) { + HTTPSampleResult sampleResult = (HTTPSampleResult) objectResult; + + // Display with same order HTTP protocol + requestModel.addRow(new RowResult( + JMeterUtils.getResString("view_results_table_request_http_method"), //$NON-NLS-1$ + sampleResult.getHTTPMethod())); + + URL hUrl = sampleResult.getURL(); + if (hUrl != null){ // can be null - e.g. if URL was invalid + requestModel.addRow(new RowResult(JMeterUtils + .getResString("view_results_table_request_http_protocol"), //$NON-NLS-1$ + hUrl.getProtocol())); + requestModel.addRow(new RowResult( + JMeterUtils.getResString("view_results_table_request_http_host"), //$NON-NLS-1$ + hUrl.getHost())); + int port = hUrl.getPort() == -1 ? hUrl.getDefaultPort() : hUrl.getPort(); + requestModel.addRow(new RowResult( + JMeterUtils.getResString("view_results_table_request_http_port"), //$NON-NLS-1$ + Integer.valueOf(port))); + requestModel.addRow(new RowResult( + JMeterUtils.getResString("view_results_table_request_http_path"), //$NON-NLS-1$ + hUrl.getPath())); + + String queryGet = hUrl.getQuery() == null ? "" : hUrl.getQuery(); //$NON-NLS-1$ + // Concatenate query post if exists + String queryPost = sampleResult.getQueryString(); + if (queryPost != null && queryPost.length() > 0) { + if (queryGet.length() > 0) { + queryGet += PARAM_CONCATENATE; + } + queryGet += queryPost; + } + queryGet = RequestViewHTTP.decodeQuery(queryGet); + if (queryGet != null) { + Set> keys = RequestViewHTTP.getQueryMap(queryGet).entrySet(); + for (Entry entry : keys) { + paramsModel.addRow(new RowResult(entry.getKey(),entry.getValue())); + } + } + } + // Display cookie in headers table (same location on http protocol) + String cookie = sampleResult.getCookies(); + if (cookie != null && cookie.length() > 0) { + headersModel.addRow(new RowResult( + JMeterUtils.getParsedLabel("view_results_table_request_http_cookie"), //$NON-NLS-1$ + sampleResult.getCookies())); + } + // Parsed request headers + LinkedHashMap lhm = JMeterUtils.parseHeaders(sampleResult.getRequestHeaders()); + for (Iterator> iterator = lhm.entrySet().iterator(); iterator.hasNext();) { + Map.Entry entry = iterator.next(); + headersModel.addRow(new RowResult(entry.getKey(), entry.getValue())); + } + + } else { + // add a message when no http sample + requestModel.addRow(new RowResult("", //$NON-NLS-1$ + JMeterUtils.getResString("view_results_table_request_http_nohttp"))); //$NON-NLS-1$ + } + } + + /** + * @param query + * @return Map params and Svalue + */ + //TODO: move to utils class (JMeterUtils?) + public static Map getQueryMap(String query) { + + Map map = new HashMap(); + if (query.trim().startsWith(" 2 ) {// detected invalid syntax (Bug 52491) + // Return as for SOAP above + map.clear(); + map.put(" ", query); //blank name // $NON-NLS-1$ + return map; + } + String name = null; + if (paramSplit.length > 0) { + name = paramSplit[0]; + } + String value = ""; // empty init // $NON-NLS-1$ + if (paramSplit.length > 1) { + value = paramSplit[1]; + } + map.put(name, value); + } + return map; + } + + /** + * Decode a query string + * + * @param query + * to decode + * @return a decode query string + */ + public static String decodeQuery(String query) { + if (query != null && query.length() > 0) { + try { + query = URLDecoder.decode(query, CHARSET_DECODE); // better ISO-8859-1 than UTF-8 + } catch (UnsupportedEncodingException uee) { + log.warn("Error in parse query:" + query, uee); + return null; + } + return query; + } + return null; + } + + public JPanel getPanel() { + return paneParsed; + } + + /** + * Create a pane with three tables (request, params, headers) + * + * @return Pane to display request data + */ + private Component createRequestPane() { + // Set up the 1st table Result with empty headers + tableRequest = new JTable(requestModel); + tableRequest.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableRequest.addMouseListener(new TextBoxDoubleClick(tableRequest)); + + setFirstColumnPreferredSize(tableRequest); + RendererUtils.applyRenderers(tableRequest, RENDERERS_REQUEST); + + // Set up the 2nd table + tableParams = new JTable(paramsModel); + tableParams.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableParams.addMouseListener(new TextBoxDoubleClick(tableParams)); + setFirstColumnPreferredSize(tableParams); + tableParams.getTableHeader().setDefaultRenderer( + new HeaderAsPropertyRenderer()); + RendererUtils.applyRenderers(tableParams, RENDERERS_PARAMS); + + // Set up the 3rd table + tableHeaders = new JTable(headersModel); + tableHeaders.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableHeaders.addMouseListener(new TextBoxDoubleClick(tableHeaders)); + setFirstColumnPreferredSize(tableHeaders); + tableHeaders.getTableHeader().setDefaultRenderer( + new HeaderAsPropertyRenderer()); + RendererUtils.applyRenderers(tableHeaders, RENDERERS_HEADERS); + + // Create the split pane + JSplitPane topSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + GuiUtils.makeScrollPane(tableParams), + GuiUtils.makeScrollPane(tableHeaders)); + topSplit.setOneTouchExpandable(true); + topSplit.setResizeWeight(0.50); // set split ratio + topSplit.setBorder(null); // see bug jdk 4131528 + + JSplitPane paneParsed = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + GuiUtils.makeScrollPane(tableRequest), topSplit); + paneParsed.setOneTouchExpandable(true); + paneParsed.setResizeWeight(0.25); // set split ratio (only 5 lines to display) + paneParsed.setBorder(null); // see bug jdk 4131528 + + // Hint to background color on bottom tabs (grey, not blue) + JPanel panel = new JPanel(new BorderLayout()); + panel.add(paneParsed); + return panel; + } + + private void setFirstColumnPreferredSize(JTable table) { + TableColumn column = table.getColumnModel().getColumn(0); + column.setMaxWidth(300); + column.setPreferredWidth(160); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#getLabel() + */ + public String getLabel() { + return JMeterUtils.getResString(KEY_LABEL); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/config/JavaConfig.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/config/JavaConfig.java new file mode 100644 index 0000000..2413be2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/config/JavaConfig.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.java.config; + +import java.io.Serializable; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.protocol.java.sampler.JavaSampler; +import org.apache.jmeter.testelement.property.TestElementProperty; + +/** + * The JavaConfig class contains the configuration data necessary + * for the Java protocol. This data is used to configure a + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} instance to + * perform performance test samples. + * + * @version $Revision: 905028 $ + */ +public class JavaConfig extends ConfigTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + /** + * Constructor for the JavaConfig object + */ + public JavaConfig() { + setArguments(new Arguments()); + } + + /** + * Sets the class name attribute of the JavaConfig object. This is the class + * name of the + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} + * implementation which will be used to execute the test. + * + * @param classname + * the new classname value + */ + public void setClassname(String classname) { + setProperty(JavaSampler.CLASSNAME, classname); + } + + /** + * Gets the class name attribute of the JavaConfig object. This is the class + * name of the + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} + * implementation which will be used to execute the test. + * + * @return the classname value + */ + public String getClassname() { + return getPropertyAsString(JavaSampler.CLASSNAME); + } + + /** + * Adds an argument to the list of arguments for this JavaConfig object. The + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} + * implementation can access these arguments through the + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerContext}. + * + * @param name + * the name of the argument to be added + * @param value + * the value of the argument to be added + */ + public void addArgument(String name, String value) { + Arguments args = this.getArguments(); + args.addArgument(name, value); + } + + /** + * Removes all of the arguments associated with this JavaConfig object. + */ + public void removeArguments() { + setProperty(new TestElementProperty(JavaSampler.ARGUMENTS, new Arguments())); + } + + /** + * Set all of the arguments for this JavaConfig object. This will replace + * any previously added arguments. The + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} + * implementation can access these arguments through the + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerContext}. + * + * @param args + * the new arguments + */ + public void setArguments(Arguments args) { + setProperty(new TestElementProperty(JavaSampler.ARGUMENTS, args)); + } + + /** + * Gets the arguments for this JavaConfig object. The + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerClient} + * implementation can access these arguments through the + * {@link org.apache.jmeter.protocol.java.sampler.JavaSamplerContext}. + * + * @return the arguments + */ + public Arguments getArguments() { + return (Arguments) getProperty(JavaSampler.ARGUMENTS).getObjectValue(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java new file mode 100644 index 0000000..94f002c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/config/gui/JavaConfigGui.java @@ -0,0 +1,253 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.java.config.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.protocol.java.config.JavaConfig; +import org.apache.jmeter.protocol.java.sampler.JavaSampler; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerClient; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The JavaConfigGui class provides the user interface for the + * {@link JavaConfig} object. + * + */ +public class JavaConfigGui extends AbstractConfigGui implements ActionListener { + private static final long serialVersionUID = 240L; + + /** Logging */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** A combo box allowing the user to choose a test class. */ + private JComboBox classnameCombo; + + /** + * Indicates whether or not the name of this component should be displayed + * as part of the GUI. If true, this is a standalone component. If false, it + * is embedded in some other component. + */ + private boolean displayName = true; + + /** A panel allowing the user to set arguments for this test. */ + private ArgumentsPanel argsPanel; + + /** + * Create a new JavaConfigGui as a standalone component. + */ + public JavaConfigGui() { + this(true); + } + + /** + * Create a new JavaConfigGui as either a standalone or an embedded + * component. + * + * @param displayNameField + * tells whether the component name should be displayed with the + * GUI. If true, this is a standalone component. If false, this + * component is embedded in some other component. + */ + public JavaConfigGui(boolean displayNameField) { + this.displayName = displayNameField; + init(); + } + + /** {@inheritDoc} */ + public String getLabelResource() { + return "java_request_defaults"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout. + */ + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout(0, 5)); + + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + + JPanel classnameRequestPanel = new JPanel(new BorderLayout(0, 5)); + classnameRequestPanel.add(createClassnamePanel(), BorderLayout.NORTH); + classnameRequestPanel.add(createParameterPanel(), BorderLayout.CENTER); + + add(classnameRequestPanel, BorderLayout.CENTER); + } + + /** + * Create a panel with GUI components allowing the user to select a test + * class. + * + * @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); + } + + JLabel label = new JLabel(JMeterUtils.getResString("protocol_java_classname")); // $NON-NLS-1$ + + classnameCombo = new JComboBox(possibleClasses.toArray()); + classnameCombo.addActionListener(this); + classnameCombo.setEditable(false); + label.setLabelFor(classnameCombo); + + HorizontalPanel panel = new HorizontalPanel(); + panel.add(label); + panel.add(classnameCombo); + + return panel; + } + + /** + * Handle action events for this component. This method currently handles + * events for the classname combo box. + * + * @param evt + * the ActionEvent to be handled + */ + public void actionPerformed(ActionEvent evt) { + if (evt.getSource() == classnameCombo) { + String className = ((String) classnameCombo.getSelectedItem()).trim(); + try { + JavaSamplerClient client = (JavaSamplerClient) Class.forName(className, true, + Thread.currentThread().getContextClassLoader()).newInstance(); + + Arguments currArgs = new Arguments(); + argsPanel.modifyTestElement(currArgs); + Map currArgsMap = currArgs.getArgumentsAsMap(); + + Arguments newArgs = new Arguments(); + Arguments testParams = null; + try { + testParams = client.getDefaultParameters(); + } catch (AbstractMethodError e) { + log.warn("JavaSamplerClient doesn't implement " + + "getDefaultParameters. Default parameters won't " + + "be shown. Please update your client class: " + className); + } + + if (testParams != null) { + PropertyIterator i = testParams.getArguments().iterator(); + while (i.hasNext()) { + Argument arg = (Argument) i.next().getObjectValue(); + String name = arg.getName(); + String value = arg.getValue(); + + // If a user has set parameters in one test, and then + // selects a different test which supports the same + // parameters, those parameters should have the same + // values that they did in the original test. + if (currArgsMap.containsKey(name)) { + String newVal = currArgsMap.get(name); + if (newVal != null && newVal.length() > 0) { + value = newVal; + } + } + newArgs.addArgument(name, value); + } + } + + argsPanel.configure(newArgs); + } catch (Exception e) { + log.error("Error getting argument list for " + className, e); + } + } + } + + /** + * Create a panel containing components allowing the user to provide + * arguments to be passed to the test class instance. + * + * @return a panel containing the relevant components + */ + private JPanel createParameterPanel() { + argsPanel = new ArgumentsPanel(JMeterUtils.getResString("paramtable")); // $NON-NLS-1$ + return argsPanel; + } + + /** {@inheritDoc} */ + @Override + public void configure(TestElement config) { + super.configure(config); + + argsPanel.configure((Arguments) config.getProperty(JavaSampler.ARGUMENTS).getObjectValue()); + + classnameCombo.setSelectedItem(config.getPropertyAsString(JavaSampler.CLASSNAME)); + } + + /** {@inheritDoc} */ + public TestElement createTestElement() { + JavaConfig config = new JavaConfig(); + modifyTestElement(config); + return config; + } + + /** {@inheritDoc} */ + public void modifyTestElement(TestElement config) { + configureTestElement(config); + ((JavaConfig) config).setArguments((Arguments) argsPanel.createTestElement()); + ((JavaConfig) config).setClassname(String.valueOf(classnameCombo.getSelectedItem())); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#clearGui() + */ + @Override + public void clearGui() { + super.clearGui(); + this.displayName = true; + argsPanel.clearGui(); + classnameCombo.setSelectedIndex(0); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/BeanShellSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/BeanShellSamplerGui.java new file mode 100644 index 0000000..d27838b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/BeanShellSamplerGui.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.java.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import org.apache.jmeter.protocol.java.sampler.BeanShellSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.util.JMeterUtils; + +public class BeanShellSamplerGui extends AbstractSamplerGui { + + private static final long serialVersionUID = 240L; + + private JCheckBox resetInterpreter;// reset the bsh.Interpreter before each execution + + private JTextField filename;// script file name (if present) + + private JTextField parameters;// parameters to pass to script file (or script) + + private JTextArea scriptField;// script area + + public BeanShellSamplerGui() { + init(); + } + + @Override + public void configure(TestElement element) { + scriptField.setText(element.getPropertyAsString(BeanShellSampler.SCRIPT)); + filename.setText(element.getPropertyAsString(BeanShellSampler.FILENAME)); + parameters.setText(element.getPropertyAsString(BeanShellSampler.PARAMETERS)); + resetInterpreter.setSelected(element.getPropertyAsBoolean(BeanShellSampler.RESET_INTERPRETER)); + super.configure(element); + } + + public TestElement createTestElement() { + BeanShellSampler sampler = new BeanShellSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement te) { + te.clear(); + this.configureTestElement(te); + te.setProperty(BeanShellSampler.SCRIPT, scriptField.getText()); + te.setProperty(BeanShellSampler.FILENAME, filename.getText()); + te.setProperty(BeanShellSampler.PARAMETERS, parameters.getText()); + te.setProperty(new BooleanProperty(BeanShellSampler.RESET_INTERPRETER, resetInterpreter.isSelected())); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + filename.setText(""); //$NON-NLS-1$ + parameters.setText(""); //$NON-NLS-1$ + scriptField.setText(""); //$NON-NLS-1$ + resetInterpreter.setSelected(false); + } + + public String getLabelResource() { + return "bsh_sampler_title"; // $NON-NLS-1$ + } + + private JPanel createFilenamePanel()// TODO ought to be a FileChooser ... + { + JLabel label = new JLabel(JMeterUtils.getResString("bsh_script_file")); // $NON-NLS-1$ + + filename = new JTextField(10); + filename.setName(BeanShellSampler.FILENAME); + label.setLabelFor(filename); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(filename, BorderLayout.CENTER); + return filenamePanel; + } + + private JPanel createParameterPanel() { + JLabel label = new JLabel(JMeterUtils.getResString("bsh_script_parameters")); // $NON-NLS-1$ + + parameters = new JTextField(10); + parameters.setName(BeanShellSampler.PARAMETERS); + label.setLabelFor(parameters); + + JPanel parameterPanel = new JPanel(new BorderLayout(5, 0)); + parameterPanel.add(label, BorderLayout.WEST); + parameterPanel.add(parameters, BorderLayout.CENTER); + return parameterPanel; + } + + private JPanel createResetPanel() { + resetInterpreter = new JCheckBox(JMeterUtils.getResString("bsh_script_reset_interpreter")); // $NON-NLS-1$ + resetInterpreter.setName(BeanShellSampler.PARAMETERS); + + JPanel resetInterpreterPanel = new JPanel(new BorderLayout()); + resetInterpreterPanel.add(resetInterpreter, BorderLayout.WEST); + return resetInterpreterPanel; + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createResetPanel()); + box.add(createParameterPanel()); + box.add(createFilenamePanel()); + add(box, BorderLayout.NORTH); + + JPanel panel = createScriptPanel(); + add(panel, BorderLayout.CENTER); + // Don't let the input field shrink too much + add(Box.createVerticalStrut(panel.getPreferredSize().height), BorderLayout.WEST); + } + + private JPanel createScriptPanel() { + scriptField = new JTextArea(); + scriptField.setRows(4); + scriptField.setLineWrap(true); + scriptField.setWrapStyleWord(true); + + JLabel label = new JLabel(JMeterUtils.getResString("bsh_script")); // $NON-NLS-1$ + label.setLabelFor(scriptField); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(label, BorderLayout.NORTH); + panel.add(new JScrollPane(scriptField), BorderLayout.CENTER); + + JTextArea explain = new JTextArea(JMeterUtils.getResString("bsh_script_variables")); //$NON-NLS-1$ + explain.setLineWrap(true); + explain.setEditable(false); + explain.setBackground(this.getBackground()); + panel.add(explain, BorderLayout.SOUTH); + + return panel; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/ClassFilter.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/ClassFilter.java new file mode 100644 index 0000000..f2204c7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/ClassFilter.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.protocol.java.control.gui; + +import java.util.ArrayList; +import java.util.List; + +class ClassFilter { + + private String[] pkgs = new String[0]; + + ClassFilter() { + super(); + } + + void setPackges(String[] pk) { + this.pkgs = pk; + } + + private boolean include(String text) { + if (pkgs.length == 0) return true; // i.e. no filter + boolean inc = false; + for (int idx=0; idx < pkgs.length; idx++) { + if (text.startsWith(pkgs[idx])){ + inc = true; + break; + } + } + return inc; + } + + Object[] filterArray(List items) { + ArrayList newlist = new ArrayList(); + for (String item : items) { + if (include(item)) { + newlist.add(item); + } + } + if (newlist.size() > 0) { + return newlist.toArray(); + } else { + return new Object[0]; + } + } + + int size(){ + return pkgs.length; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java new file mode 100644 index 0000000..8b69a8d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java @@ -0,0 +1,433 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +package org.apache.jmeter.protocol.java.control.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import junit.framework.TestCase; + +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.java.sampler.JUnitSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * The JUnitTestSamplerGui class provides the user interface + * for the {@link JUnitSampler}. + * + */ +public class JUnitTestSamplerGui extends AbstractSamplerGui +implements ChangeListener, ActionListener, ItemListener +{ + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String TESTMETHOD_PREFIX = "test"; //$NON-NLS-1$ + + // Names of JUnit3 methods + private static final String ONETIMESETUP = "oneTimeSetUp"; //$NON-NLS-1$ + private static final String ONETIMETEARDOWN = "oneTimeTearDown"; //$NON-NLS-1$ + private static final String SUITE = "suite"; //$NON-NLS-1$ + + private static final String[] SPATHS; + + static { + String paths[]; + String ucp = JMeterUtils.getProperty("user.classpath"); + if (ucp!=null){ + String parts[] = ucp.split(File.pathSeparator); + paths = new String[parts.length+1]; + paths[0] = JMeterUtils.getJMeterHome() + "/lib/junit/"; //$NON-NLS-1$ + for(int i=0; i < parts.length; i++){ + paths[i+1]=parts[i]; + } + } else { + paths = new String[]{ + JMeterUtils.getJMeterHome() + "/lib/junit/" //$NON-NLS-1$ + }; + } + SPATHS = paths; + } + + private JLabeledTextField constructorLabel = + new JLabeledTextField( + JMeterUtils.getResString("junit_constructor_string")); //$NON-NLS-1$ + + private JLabel methodLabel = + new JLabel( + JMeterUtils.getResString("junit_test_method")); //$NON-NLS-1$ + + private JLabeledTextField successMsg = + new JLabeledTextField( + JMeterUtils.getResString("junit_success_msg")); //$NON-NLS-1$ + + private JLabeledTextField failureMsg = + new JLabeledTextField( + JMeterUtils.getResString("junit_failure_msg")); //$NON-NLS-1$ + + private JLabeledTextField errorMsg = + new JLabeledTextField( + JMeterUtils.getResString("junit_error_msg")); //$NON-NLS-1$ + + private JLabeledTextField successCode = + new JLabeledTextField( + JMeterUtils.getResString("junit_success_code")); //$NON-NLS-1$ + + private JLabeledTextField failureCode = + new JLabeledTextField( + JMeterUtils.getResString("junit_failure_code")); //$NON-NLS-1$ + + private JLabeledTextField errorCode = + new JLabeledTextField( + JMeterUtils.getResString("junit_error_code")); //$NON-NLS-1$ + + private JLabeledTextField filterpkg = + new JLabeledTextField( + JMeterUtils.getResString("junit_pkg_filter")); //$NON-NLS-1$ + + private JCheckBox doSetup = new JCheckBox(JMeterUtils.getResString("junit_do_setup_teardown")); //$NON-NLS-1$ + private JCheckBox appendError = new JCheckBox(JMeterUtils.getResString("junit_append_error")); //$NON-NLS-1$ + private JCheckBox appendExc = new JCheckBox(JMeterUtils.getResString("junit_append_exception")); //$NON-NLS-1$ + private JCheckBox junit4 = new JCheckBox(JMeterUtils.getResString("junit_junit4")); //$NON-NLS-1$ + private JCheckBox createInstancePerSample = new JCheckBox(JMeterUtils.getResString("junit_create_instance_per_sample")); //$NON-NLS-1$ + + /** A combo box allowing the user to choose a test class. */ + private JComboBox classnameCombo; + private JComboBox methodName; + + private final transient ClassLoader contextClassLoader = + Thread.currentThread().getContextClassLoader(); // Potentially expensive; do it once + + /** + * Constructor for JUnitTestSamplerGui + */ + public JUnitTestSamplerGui() + { + super(); + init(); + } + + public String getLabelResource() + { + return "junit_request"; //$NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout. + */ + private void init() + { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + + add(createClassPanel(), BorderLayout.CENTER); + } + + @SuppressWarnings("unchecked") + private void setupClasslist(){ + classnameCombo.removeAllItems(); + methodName.removeAllItems(); + try + { + List classList; + if (junit4.isSelected()){ + classList = ClassFinder.findAnnotatedClasses(SPATHS, + new Class[] {Test.class}, false); + } else { + classList = ClassFinder.findClassesThatExtend(SPATHS, + new Class[] { TestCase.class }); + } + ClassFilter filter = new ClassFilter(); + filter.setPackges(JOrphanUtils.split(filterpkg.getText(),",")); //$NON-NLS-1$ + // change the classname drop down + Object[] clist = filter.filterArray(classList); + for (int idx=0; idx < clist.length; idx++) { + classnameCombo.addItem(clist[idx]); + } + } + catch (IOException e) + { + log.error("Exception getting interfaces.", e); + } + } + + private JPanel createClassPanel() + { + JLabel label = + new JLabel(JMeterUtils.getResString("protocol_java_classname")); //$NON-NLS-1$ + + classnameCombo = new JComboBox(); + classnameCombo.addActionListener(this); + classnameCombo.setEditable(false); + label.setLabelFor(classnameCombo); + + methodName = new JComboBox(); + methodName.addActionListener(this); + methodLabel.setLabelFor(methodName); + + setupClasslist(); + + VerticalPanel panel = new VerticalPanel(); + panel.add(junit4); + junit4.addItemListener(this); + panel.add(filterpkg); + filterpkg.addChangeListener(this); + + panel.add(label); + panel.add(classnameCombo); + + constructorLabel.setText(""); + panel.add(constructorLabel); + panel.add(methodLabel); + panel.add(methodName); + + panel.add(successMsg); + panel.add(successCode); + panel.add(failureMsg); + panel.add(failureCode); + panel.add(errorMsg); + panel.add(errorCode); + panel.add(doSetup); + panel.add(appendError); + panel.add(appendExc); + panel.add(createInstancePerSample); + return panel; + } + + private void initGui(){ + appendError.setSelected(false); + appendExc.setSelected(false); + createInstancePerSample.setSelected(false); + doSetup.setSelected(false); + junit4.setSelected(false); + filterpkg.setText(""); //$NON-NLS-1$ + constructorLabel.setText(""); //$NON-NLS-1$ + successCode.setText(JMeterUtils.getResString("junit_success_default_code")); //$NON-NLS-1$ + successMsg.setText(JMeterUtils.getResString("junit_success_default_msg")); //$NON-NLS-1$ + failureCode.setText(JMeterUtils.getResString("junit_failure_default_code")); //$NON-NLS-1$ + failureMsg.setText(JMeterUtils.getResString("junit_failure_default_msg")); //$NON-NLS-1$ + errorMsg.setText(JMeterUtils.getResString("junit_error_default_msg")); //$NON-NLS-1$ + errorCode.setText(JMeterUtils.getResString("junit_error_default_code")); //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public void clearGui() { + super.clearGui(); + initGui(); + } + + /** {@inheritDoc} */ + public TestElement createTestElement() + { + JUnitSampler sampler = new JUnitSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** {@inheritDoc} */ + public void modifyTestElement(TestElement el) + { + JUnitSampler sampler = (JUnitSampler)el; + configureTestElement(sampler); + if (classnameCombo.getSelectedItem() != null && + classnameCombo.getSelectedItem() instanceof String) { + sampler.setClassname((String)classnameCombo.getSelectedItem()); + } else { + sampler.setClassname(null); + } + sampler.setConstructorString(constructorLabel.getText()); + if (methodName.getSelectedItem() != null) { + Object mobj = methodName.getSelectedItem(); + sampler.setMethod((String)mobj); + } else { + sampler.setMethod(null); + } + sampler.setFilterString(filterpkg.getText()); + sampler.setSuccess(successMsg.getText()); + sampler.setSuccessCode(successCode.getText()); + sampler.setFailure(failureMsg.getText()); + sampler.setFailureCode(failureCode.getText()); + sampler.setError(errorMsg.getText()); + sampler.setErrorCode(errorCode.getText()); + sampler.setDoNotSetUpTearDown(doSetup.isSelected()); + sampler.setAppendError(appendError.isSelected()); + sampler.setAppendException(appendExc.isSelected()); + sampler.setCreateOneInstancePerSample(createInstancePerSample.isSelected()); + sampler.setJunit4(junit4.isSelected()); + } + + /** {@inheritDoc} */ + @Override + public void configure(TestElement el) + { + super.configure(el); + JUnitSampler sampler = (JUnitSampler)el; + junit4.setSelected(sampler.getJunit4()); + filterpkg.setText(sampler.getFilterString()); + classnameCombo.setSelectedItem(sampler.getClassname()); + setupMethods(); + methodName.setSelectedItem(sampler.getMethod()); + constructorLabel.setText(sampler.getConstructorString()); + if (sampler.getSuccessCode().length() > 0) { + successCode.setText(sampler.getSuccessCode()); + } else { + successCode.setText(JMeterUtils.getResString("junit_success_default_code")); //$NON-NLS-1$ + } + if (sampler.getSuccess().length() > 0) { + successMsg.setText(sampler.getSuccess()); + } else { + successMsg.setText(JMeterUtils.getResString("junit_success_default_msg")); //$NON-NLS-1$ + } + if (sampler.getFailureCode().length() > 0) { + failureCode.setText(sampler.getFailureCode()); + } else { + failureCode.setText(JMeterUtils.getResString("junit_failure_default_code")); //$NON-NLS-1$ + } + if (sampler.getFailure().length() > 0) { + failureMsg.setText(sampler.getFailure()); + } else { + failureMsg.setText(JMeterUtils.getResString("junit_failure_default_msg")); //$NON-NLS-1$ + } + if (sampler.getError().length() > 0) { + errorMsg.setText(sampler.getError()); + } else { + errorMsg.setText(JMeterUtils.getResString("junit_error_default_msg")); //$NON-NLS-1$ + } + if (sampler.getErrorCode().length() > 0) { + errorCode.setText(sampler.getErrorCode()); + } else { + errorCode.setText(JMeterUtils.getResString("junit_error_default_code")); //$NON-NLS-1$ + } + doSetup.setSelected(sampler.getDoNotSetUpTearDown()); + appendError.setSelected(sampler.getAppendError()); + appendExc.setSelected(sampler.getAppendException()); + createInstancePerSample.setSelected(sampler.getCreateOneInstancePerSample()); + } + + private void setupMethods(){ + String className = + ((String) classnameCombo.getSelectedItem()); + methodName.removeAllItems(); + if (className != null) { + try { + // Don't instantiate class + Class testClass = Class.forName(className, false, contextClassLoader); + String [] names = getMethodNames(testClass); + for (int idx=0; idx < names.length; idx++){ + methodName.addItem(names[idx]); + } + methodName.repaint(); + } catch (ClassNotFoundException e) { + } + } + } + + private String[] getMethodNames(Class clazz) + { + Method[] meths = clazz.getMethods(); + List list = new ArrayList(); + for (int idx=0; idx < meths.length; idx++){ + final Method method = meths[idx]; + final String name = method.getName(); + if (junit4.isSelected()){ + if (method.isAnnotationPresent(Test.class) || + method.isAnnotationPresent(BeforeClass.class) || + method.isAnnotationPresent(AfterClass.class)) { + list.add(name); + } + } else { + if (name.startsWith(TESTMETHOD_PREFIX) || + name.equals(ONETIMESETUP) || + name.equals(ONETIMETEARDOWN) || + name.equals(SUITE)) { + list.add(name); + } + } + } + if (list.size() > 0){ + return list.toArray(new String[list.size()]); + } + return new String[0]; + } + + /** + * Handle action events for this component. This method currently handles + * events for the classname combo box, and sets up the associated method names. + * + * @param evt the ActionEvent to be handled + */ + public void actionPerformed(ActionEvent evt) + { + if (evt.getSource() == classnameCombo) + { + setupMethods(); + } + } + + /** + * Handle change events: currently handles events for the JUnit4 + * checkbox, and sets up the relevant class names. + */ + public void itemStateChanged(ItemEvent event) { + if (event.getItem() == junit4){ + setupClasslist(); + } + } + + /** + * the current implementation checks to see if the source + * of the event is the filterpkg field. + */ + public void stateChanged(ChangeEvent event) { + if ( event.getSource() == filterpkg) { + setupClasslist(); + } + } +} + diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/JavaTestSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/JavaTestSamplerGui.java new file mode 100644 index 0000000..6f51abe --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/control/gui/JavaTestSamplerGui.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.java.control.gui; + +import java.awt.BorderLayout; + +import org.apache.jmeter.protocol.java.config.JavaConfig; +import org.apache.jmeter.protocol.java.config.gui.JavaConfigGui; +import org.apache.jmeter.protocol.java.sampler.JavaSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; + +/** + * The JavaTestSamplerGui class provides the user interface for + * the {@link JavaSampler}. + * + */ +public class JavaTestSamplerGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + /** Panel containing the configuration options. */ + private JavaConfigGui javaPanel = null; + + /** + * Constructor for JavaTestSamplerGui + */ + public JavaTestSamplerGui() { + super(); + init(); + } + + public String getLabelResource() { + return "java_request"; // $NON-NLS-1$ + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + + javaPanel = new JavaConfigGui(false); + + add(javaPanel, BorderLayout.CENTER); + } + + /* Implements JMeterGuiComponent.createTestElement() */ + public TestElement createTestElement() { + JavaSampler sampler = new JavaSampler(); + modifyTestElement(sampler); + return sampler; + } + + /* Implements JMeterGuiComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement sampler) { + sampler.clear(); + JavaConfig config = (JavaConfig) javaPanel.createTestElement(); + configureTestElement(sampler); + sampler.addTestElement(config); + } + + /* Overrides AbstractJMeterGuiComponent.configure(TestElement) */ + @Override + public void configure(TestElement el) { + super.configure(el); + javaPanel.configure(el); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#clearGui() + */ + @Override + public void clearGui() { + super.clearGui(); + javaPanel.clearGui(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/AbstractJavaSamplerClient.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/AbstractJavaSamplerClient.java new file mode 100644 index 0000000..dd1d882 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/AbstractJavaSamplerClient.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.java.sampler; + +import org.apache.jmeter.config.Arguments; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * An abstract implementation of the JavaSamplerClient interface. This + * implementation provides default implementations of most of the methods in the + * interface, as well as some convenience methods, in order to simplify + * development of JavaSamplerClient implementations. + *

+ * See {@link org.apache.jmeter.protocol.java.test.SleepTest} for an example of + * how to extend this class. + *

+ * While it may be necessary to make changes to the JavaSamplerClient interface + * from time to time (therefore requiring changes to any implementations of this + * interface), we intend to make this abstract class provide reasonable + * implementations of any new methods so that subclasses do not necessarily need + * to be updated for new versions. Therefore, when creating a new + * JavaSamplerClient implementation, developers are encouraged to subclass this + * abstract class rather than implementing the JavaSamplerClient interface + * directly. Implementing JavaSamplerClient directly will continue to be + * supported for cases where extending this class is not possible (for example, + * when the client class is already a subclass of some other class). + *

+ * The runTest() method of JavaSamplerClient does not have a default + * implementation here, so subclasses must define at least this method. It may + * be useful to override other methods as well. + * + * @see JavaSamplerClient#runTest(JavaSamplerContext) + * + * @version $Revision: 674365 $ + */ +public abstract class AbstractJavaSamplerClient implements JavaSamplerClient { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /* Implements JavaSamplerClient.setupTest(JavaSamplerContext) */ + public void setupTest(JavaSamplerContext context) { + log.debug(getClass().getName() + ": setupTest"); + } + + /* Implements JavaSamplerClient.teardownTest(JavaSamplerContext) */ + public void teardownTest(JavaSamplerContext context) { + log.debug(getClass().getName() + ": teardownTest"); + } + + /* Implements JavaSamplerClient.getDefaultParameters() */ + public Arguments getDefaultParameters() { + return null; + } + + /** + * Get a Logger instance which can be used by subclasses to log information. + * This is the same Logger which is used by the base JavaSampler classes + * (jmeter.protocol.java). + * + * @return a Logger instance which can be used for logging + */ + protected Logger getLogger() { + return log; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BSFSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BSFSampler.java new file mode 100644 index 0000000..9e02096 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BSFSampler.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.java.sampler; + +import java.io.FileInputStream; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.bsf.BSFEngine; +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A sampler which understands BSF + * + */ +public class BSFSampler extends BSFTestElement implements Sampler, TestBean, ConfigMergabilityIndicator { + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public BSFSampler() { + super(); + } + + /** + * Returns a formatted string label describing this sampler + * + * @return a formatted string label describing this sampler + */ + + public String getLabel() { + return getName(); + } + + public SampleResult sample(Entry e)// Entry tends to be ignored ... + { + final String label = getLabel(); + final String request = getScript(); + final String fileName = getFilename(); + log.debug(label + " " + fileName); + SampleResult res = new SampleResult(); + res.setSampleLabel(label); + FileInputStream is = null; + BSFEngine bsfEngine = null; + // There's little point saving the manager between invocations + // as we need to reset most of the beans anyway + BSFManager mgr = new BSFManager(); + + // TODO: find out how to retrieve these from the script + // At present the script has to use SampleResult methods to set them. + res.setResponseCode("200"); // $NON-NLS-1$ + res.setResponseMessage("OK"); // $NON-NLS-1$ + res.setSuccessful(true); + res.setDataType(SampleResult.TEXT); // Default (can be overridden by the script) + + res.sampleStart(); + try { + initManager(mgr); + mgr.declareBean("SampleResult", res, res.getClass()); // $NON-NLS-1$ + + // These are not useful yet, as have not found how to get updated values back + //mgr.declareBean("ResponseCode", "200", String.class); // $NON-NLS-1$ + //mgr.declareBean("ResponseMessage", "OK", String.class); // $NON-NLS-1$ + //mgr.declareBean("IsSuccess", Boolean.TRUE, Boolean.class); // $NON-NLS-1$ + + // N.B. some engines (e.g. Javascript) cannot handle certain declareBean() calls + // after the engine has been initialised, so create the engine last + bsfEngine = mgr.loadScriptingEngine(getScriptLanguage()); + + Object bsfOut = null; + if (fileName.length()>0) { + res.setSamplerData("File: "+fileName); + is = new FileInputStream(fileName); + bsfOut = bsfEngine.eval(fileName, 0, 0, IOUtils.toString(is)); + } else { + res.setSamplerData(request); + bsfOut = bsfEngine.eval("script", 0, 0, request); + } + + if (bsfOut != null) { + res.setResponseData(bsfOut.toString(), null); + } + } catch (BSFException ex) { + log.warn("BSF error", ex); + res.setSuccessful(false); + res.setResponseCode("500"); // $NON-NLS-1$ + res.setResponseMessage(ex.toString()); + } catch (Exception ex) {// Catch evaluation errors + log.warn("Problem evaluating the script", ex); + res.setSuccessful(false); + res.setResponseCode("500"); // $NON-NLS-1$ + res.setResponseMessage(ex.toString()); + } finally { + res.sampleEnd(); + IOUtils.closeQuietly(is); +// Will be done by mgr.terminate() anyway +// if (bsfEngine != null) { +// bsfEngine.terminate(); +// } + mgr.terminate(); + } + + return res; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BSFSamplerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BSFSamplerBeanInfo.java new file mode 100644 index 0000000..bd98b66 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BSFSamplerBeanInfo.java @@ -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.protocol.java.sampler; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +/** + * BSF Sampler Bean Info + */ +public class BSFSamplerBeanInfo extends BSFBeanInfoSupport { + + public BSFSamplerBeanInfo() { + super(BSFSampler.class); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BeanShellSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BeanShellSampler.java new file mode 100644 index 0000000..f1e0bca --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/BeanShellSampler.java @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.java.sampler; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +/** + * A sampler which understands BeanShell + * + */ +public class BeanShellSampler extends BeanShellTestElement implements Sampler, Interruptible, ConfigMergabilityIndicator +{ + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 3; + + public static final String FILENAME = "BeanShellSampler.filename"; //$NON-NLS-1$ + + public static final String SCRIPT = "BeanShellSampler.query"; //$NON-NLS-1$ + + public static final String PARAMETERS = "BeanShellSampler.parameters"; //$NON-NLS-1$ + + public static final String INIT_FILE = "beanshell.sampler.init"; //$NON-NLS-1$ + + public static final String RESET_INTERPRETER = "BeanShellSampler.resetInterpreter"; //$NON-NLS-1$ + + private transient volatile BeanShellInterpreter savedBsh = null; + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + @Override + public String getScript() { + return this.getPropertyAsString(SCRIPT); + } + + @Override + public String getFilename() { + return getPropertyAsString(FILENAME); + } + + @Override + public String getParameters() { + return getPropertyAsString(PARAMETERS); + } + + @Override + public boolean isResetInterpreter() { + return getPropertyAsBoolean(RESET_INTERPRETER); + } + + public SampleResult sample(Entry e)// Entry tends to be ignored ... + { + // log.info(getLabel()+" "+getFilename()); + SampleResult res = new SampleResult(); + boolean isSuccessful = false; + res.setSampleLabel(getName()); + res.sampleStart(); + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + res.sampleEnd(); + res.setResponseCode("503");//$NON-NLS-1$ + res.setResponseMessage("BeanShell Interpreter not found"); + res.setSuccessful(false); + return res; + } + try { + String request = getScript(); + String fileName = getFilename(); + if (fileName.length() == 0) { + res.setSamplerData(request); + } else { + res.setSamplerData(fileName); + } + + bshInterpreter.set("SampleResult", res); //$NON-NLS-1$ + + // Set default values + bshInterpreter.set("ResponseCode", "200"); //$NON-NLS-1$ + bshInterpreter.set("ResponseMessage", "OK");//$NON-NLS-1$ + bshInterpreter.set("IsSuccess", true);//$NON-NLS-1$ + + res.setDataType(SampleResult.TEXT); // assume text output - script can override if necessary + + savedBsh = bshInterpreter; + Object bshOut = processFileOrScript(bshInterpreter); + savedBsh = null; + + if (bshOut != null) {// Set response data + String out = bshOut.toString(); + res.setResponseData(out, null); + } + // script can also use setResponseData() so long as it returns null + + res.setResponseCode(bshInterpreter.get("ResponseCode").toString());//$NON-NLS-1$ + res.setResponseMessage(bshInterpreter.get("ResponseMessage").toString());//$NON-NLS-1$ + isSuccessful = Boolean.valueOf(bshInterpreter.get("IsSuccess") //$NON-NLS-1$ + .toString()).booleanValue(); + } + /* + * To avoid class loading problems when bsh,jar is missing, we don't try + * to catch this error separately catch (bsh.EvalError ex) { + * log.debug("",ex); res.setResponseCode("500");//$NON-NLS-1$ + * res.setResponseMessage(ex.toString()); } + */ + // but we do trap this error to make tests work better + catch (NoClassDefFoundError ex) { + log.error("BeanShell Jar missing? " + ex.toString()); + res.setResponseCode("501");//$NON-NLS-1$ + res.setResponseMessage(ex.toString()); + res.setStopThread(true); // No point continuing + } catch (Exception ex) // Mainly for bsh.EvalError + { + log.warn(ex.toString()); + res.setResponseCode("500");//$NON-NLS-1$ + res.setResponseMessage(ex.toString()); + } finally { + savedBsh = null; + } + + res.sampleEnd(); + + // Set if we were successful or not + res.setSuccessful(isSuccessful); + + return res; + } + + public boolean interrupt() { + if (savedBsh != null) { + try { + savedBsh.evalNoLog("interrupt()"); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + return true; + } + return false; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JSR223Sampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JSR223Sampler.java new file mode 100644 index 0000000..aa828f1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JSR223Sampler.java @@ -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. + * + */ + +package org.apache.jmeter.protocol.java.sampler; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223Sampler extends JSR223TestElement implements Cloneable, Sampler, TestBean, ConfigMergabilityIndicator { + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private static final long serialVersionUID = 234L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public SampleResult sample(Entry entry) { + SampleResult result = new SampleResult(); + result.setSampleLabel(getName()); + final String filename = getFilename(); + if (filename.length() > 0){ + result.setSamplerData("File: "+filename); + } else { + result.setSamplerData(getScript()); + } + result.setDataType(SampleResult.TEXT); + result.sampleStart(); + try { + ScriptEngineManager mgr = getManager(); + if (mgr == null) { + result.setSuccessful(false); + result.setResponseCode("500"); // $NON-NLS-1$ + result.setResponseMessage("Could not instantiate ScriptManager"); + return result; + } + mgr.put("SampleResult",result); + Object ret = processFileOrScript(mgr); + result.setSuccessful(true); + result.setResponseCodeOK(); + result.setResponseMessageOK(); + if (ret != null){ + result.setResponseData(ret.toString(), null); + } + } catch (IOException e) { + log.warn("Problem in JSR223 script "+e); + result.setSuccessful(false); + result.setResponseCode("500"); // $NON-NLS-1$ + result.setResponseMessage(e.toString()); + } catch (ScriptException e) { + log.warn("Problem in JSR223 script "+e); + result.setSuccessful(false); + result.setResponseCode("500"); // $NON-NLS-1$ + result.setResponseMessage(e.toString()); + } + result.sampleEnd(); + return result; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JSR223SamplerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JSR223SamplerBeanInfo.java new file mode 100644 index 0000000..3bd5023 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JSR223SamplerBeanInfo.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.jmeter.protocol.java.sampler; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +public class JSR223SamplerBeanInfo extends BSFBeanInfoSupport { + + public JSR223SamplerBeanInfo() { + super(JSR223Sampler.class); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JUnitSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JUnitSampler.java new file mode 100644 index 0000000..471ca57 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JUnitSampler.java @@ -0,0 +1,708 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.protocol.java.sampler; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Enumeration; + +import junit.framework.AssertionFailedError; +import junit.framework.Protectable; +import junit.framework.TestCase; +import junit.framework.TestFailure; +import junit.framework.TestResult; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.Test.None; + +/** + * + * This is a basic implementation that runs a single test method of + * a JUnit test case. The current implementation will use the string + * constructor first. If the test class does not declare a string + * constructor, the sampler will try empty constructor. + */ +public class JUnitSampler extends AbstractSampler implements ThreadListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; // Remember to change this when the class changes ... + + //++ JMX file attributes - do not change + private static final String CLASSNAME = "junitSampler.classname"; + private static final String CONSTRUCTORSTRING = "junitsampler.constructorstring"; + private static final String METHOD = "junitsampler.method"; + private static final String ERROR = "junitsampler.error"; + private static final String ERRORCODE = "junitsampler.error.code"; + private static final String FAILURE = "junitsampler.failure"; + private static final String FAILURECODE = "junitsampler.failure.code"; + private static final String SUCCESS = "junitsampler.success"; + private static final String SUCCESSCODE = "junitsampler.success.code"; + private static final String FILTER = "junitsampler.pkg.filter"; + private static final String DOSETUP = "junitsampler.exec.setup"; + private static final String APPEND_ERROR = "junitsampler.append.error"; + private static final String APPEND_EXCEPTION = "junitsampler.append.exception"; + private static final String JUNIT4 = "junitsampler.junit4"; + private static final String CREATE_INSTANCE_PER_SAMPLE="junitsampler.createinstancepersample"; + private static final boolean CREATE_INSTANCE_PER_SAMPLE_DEFAULT = false; + //-- JMX file attributes - do not change + + private static final String SETUP = "setUp"; + private static final String TEARDOWN = "tearDown"; + + // the Method objects for setUp (@Before) and tearDown (@After) methods + // Will be null if not provided or not required + private transient Method setUpMethod; + private transient Method tearDownMethod; + + // The TestCase to run + private transient TestCase testCase; + // The test object, i.e. the instance of the class containing the test method + // This is the same as testCase for JUnit3 tests + // but different for JUnit4 tests which use a wrapper + private transient Object testObject; + + // The method name to be invoked + private transient String methodName; + // The name of the class containing the method + private transient String className; + // The wrapper used to invoke the method + private transient Protectable protectable; + + public JUnitSampler(){ + super(); + } + + /** + * Method tries to get the setUp and tearDown method for the class + * @param testObject + */ + private void initMethodObjects(Object testObject){ + setUpMethod = null; + tearDownMethod = null; + if (!getDoNotSetUpTearDown()) { + setUpMethod = getJunit4() ? + getMethodWithAnnotation(testObject, Before.class) + : + getMethod(testObject, SETUP); + tearDownMethod = getJunit4() ? + getMethodWithAnnotation(testObject, After.class) + : + getMethod(testObject, TEARDOWN); + } + } + + /** + * Sets the Classname attribute of the JavaConfig object + * + * @param classname + * the new Classname value + */ + public void setClassname(String classname) + { + setProperty(CLASSNAME, classname); + } + + /** + * Gets the Classname attribute of the JavaConfig object + * + * @return the Classname value + */ + public String getClassname() + { + return getPropertyAsString(CLASSNAME); + } + + /** + * Set the string label used to create an instance of the + * test with the string constructor. + * @param constr + */ + public void setConstructorString(String constr) + { + setProperty(CONSTRUCTORSTRING,constr); + } + + /** + * get the string passed to the string constructor + */ + public String getConstructorString() + { + return getPropertyAsString(CONSTRUCTORSTRING); + } + + /** + * Return the name of the method to test + */ + public String getMethod(){ + return getPropertyAsString(METHOD); + } + + /** + * Method should add the JUnit testXXX method to the list at + * the end, since the sequence matters. + * @param methodName + */ + public void setMethod(String methodName){ + setProperty(METHOD,methodName); + } + + /** + * get the success message + */ + public String getSuccess(){ + return getPropertyAsString(SUCCESS); + } + + /** + * set the success message + * @param success + */ + public void setSuccess(String success){ + setProperty(SUCCESS,success); + } + + /** + * get the success code defined by the user + */ + public String getSuccessCode(){ + return getPropertyAsString(SUCCESSCODE); + } + + /** + * set the succes code. the success code should + * be unique. + * @param code + */ + public void setSuccessCode(String code){ + setProperty(SUCCESSCODE,code); + } + + /** + * get the failure message + */ + public String getFailure(){ + return getPropertyAsString(FAILURE); + } + + /** + * set the failure message + * @param fail + */ + public void setFailure(String fail){ + setProperty(FAILURE,fail); + } + + /** + * The failure code is used by other components + */ + public String getFailureCode(){ + return getPropertyAsString(FAILURECODE); + } + + /** + * Provide some unique code to denote a type of failure + * @param code + */ + public void setFailureCode(String code){ + setProperty(FAILURECODE,code); + } + + /** + * return the descriptive error for the test + */ + public String getError(){ + return getPropertyAsString(ERROR); + } + + /** + * provide a descriptive error for the test method. For + * a description of the difference between failure and + * error, please refer to the following url + * http://junit.sourceforge.net/doc/faq/faq.htm#tests_9 + * @param error + */ + public void setError(String error){ + setProperty(ERROR,error); + } + + /** + * return the error code for the test method. it should + * be an unique error code. + */ + public String getErrorCode(){ + return getPropertyAsString(ERRORCODE); + } + + /** + * provide an unique error code for when the test + * does not pass the assert test. + * @param code + */ + public void setErrorCode(String code){ + setProperty(ERRORCODE,code); + } + + /** + * return the comma separated string for the filter + */ + public String getFilterString(){ + return getPropertyAsString(FILTER); + } + + /** + * set the filter string in comman separated format + * @param text + */ + public void setFilterString(String text){ + setProperty(FILTER,text); + } + + /** + * if the sample shouldn't call setup/teardown, the + * method returns true. It's meant for onetimesetup + * and onetimeteardown. + */ + public boolean getDoNotSetUpTearDown(){ + return getPropertyAsBoolean(DOSETUP); + } + + /** + * set the setup/teardown option + * @param setup + */ + public void setDoNotSetUpTearDown(boolean setup){ + setProperty(DOSETUP,String.valueOf(setup)); + } + + /** + * If append error is not set, by default it is set to false, + * which means users have to explicitly set the sampler to + * append the assert errors. Because of how junit works, there + * should only be one error + */ + public boolean getAppendError() { + return getPropertyAsBoolean(APPEND_ERROR,false); + } + + /** + * Set whether to append errors or not. + * + * @param error the setting to apply + */ + public void setAppendError(boolean error) { + setProperty(APPEND_ERROR,String.valueOf(error)); + } + + /** + * If append exception is not set, by default it is set to false. + * Users have to explicitly set it to true to see the exceptions + * in the result tree. + */ + public boolean getAppendException() { + return getPropertyAsBoolean(APPEND_EXCEPTION,false); + } + + /** + * Set whether to append exceptions or not. + * + * @param exc the setting to apply. + */ + public void setAppendException(boolean exc) { + setProperty(APPEND_EXCEPTION,String.valueOf(exc)); + } + + /** + * Check if JUnit4 (annotations) are to be used instead of + * the JUnit3 style (TestClass and specific method names) + * + * @return true if JUnit4 (annotations) are to be used. + * Default is false. + */ + public boolean getJunit4() { + return getPropertyAsBoolean(JUNIT4, false); + } + + /** + * Set whether to use JUnit4 style or not. + * @param junit4 true if JUnit4 style is to be used. + */ + public void setJunit4(boolean junit4) { + setProperty(JUNIT4, junit4, false); + } + + /** {@inheritDoc} */ + public SampleResult sample(Entry entry) { + if(getCreateOneInstancePerSample()) { + initializeTestObject(); + } + SampleResult sresult = new SampleResult(); + sresult.setSampleLabel(getName());// Bug 41522 - don't use rlabel here + sresult.setSamplerData(className + "." + methodName); + sresult.setDataType(SampleResult.TEXT); + // Assume success + sresult.setSuccessful(true); + sresult.setResponseMessage(getSuccess()); + sresult.setResponseCode(getSuccessCode()); + if (this.testCase != null){ + // create a new TestResult + TestResult tr = new TestResult(); + final TestCase theClazz = this.testCase; + try { + if (setUpMethod != null){ + setUpMethod.invoke(this.testObject,new Object[0]); + } + sresult.sampleStart(); + tr.startTest(this.testCase); + // Do not use TestCase.run(TestResult) method, since it will + // call setUp and tearDown. Doing that will result in calling + // the setUp and tearDown method twice and the elapsed time + // will include setup and teardown. + tr.runProtected(theClazz, protectable); + tr.endTest(this.testCase); + sresult.sampleEnd(); + if (tearDownMethod != null){ + tearDownMethod.invoke(testObject,new Object[0]); + } + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof AssertionFailedError){ + tr.addFailure(theClazz, (AssertionFailedError) cause); + } else if (cause instanceof AssertionError) { + // Convert JUnit4 failure to Junit3 style + AssertionFailedError afe = new AssertionFailedError(cause.toString()); + // copy the original stack trace + afe.setStackTrace(cause.getStackTrace()); + tr.addFailure(theClazz, afe); + } else if (cause != null) { + tr.addError(theClazz, cause); + } else { + tr.addError(theClazz, e); + } + } catch (IllegalAccessException e) { + tr.addError(theClazz, e); + } catch (IllegalArgumentException e) { + tr.addError(theClazz, e); + } + if ( !tr.wasSuccessful() ){ + sresult.setSuccessful(false); + StringBuilder buf = new StringBuilder(); + StringBuilder buftrace = new StringBuilder(); + Enumeration en; + if (getAppendError()) { + en = tr.failures(); + if (en.hasMoreElements()){ + sresult.setResponseCode(getFailureCode()); + buf.append( getFailure() ); + buf.append("\n"); + } + while (en.hasMoreElements()){ + TestFailure item = en.nextElement(); + buf.append( "Failure -- "); + buf.append( item.toString() ); + buf.append("\n"); + buftrace.append( "Failure -- "); + buftrace.append( item.toString() ); + buftrace.append("\n"); + buftrace.append( "Trace -- "); + buftrace.append( item.trace() ); + } + en = tr.errors(); + if (en.hasMoreElements()){ + sresult.setResponseCode(getErrorCode()); + buf.append( getError() ); + buf.append("\n"); + } + while (en.hasMoreElements()){ + TestFailure item = en.nextElement(); + buf.append( "Error -- "); + buf.append( item.toString() ); + buf.append("\n"); + buftrace.append( "Error -- "); + buftrace.append( item.toString() ); + buftrace.append("\n"); + buftrace.append( "Trace -- "); + buftrace.append( item.trace() ); + } + } + sresult.setResponseMessage(buf.toString()); + sresult.setResponseData(buftrace.toString(), null); + } + } else { + // we should log a warning, but allow the test to keep running + sresult.setSuccessful(false); + // this should be externalized to the properties + sresult.setResponseMessage("failed to create an instance of the class"); + sresult.setResponseCode(getErrorCode()); + } + return sresult; + } + + /** + * If the method is not able to create a new instance of the + * class, it returns null and logs all the exceptions at + * warning level. + */ + private static Object getClassInstance(String className, String label){ + Object testclass = null; + if (className != null){ + Constructor con = null; + Constructor strCon = null; + Class theclazz = null; + Object[] strParams = null; + Object[] params = null; + try + { + theclazz = + Thread.currentThread().getContextClassLoader().loadClass(className.trim()); + } catch (ClassNotFoundException e) { + log.warn("ClassNotFoundException:: " + e.getMessage()); + } + if (theclazz != null) { + // first we see if the class declares a string + // constructor. if it is doesn't we look for + // empty constructor. + try { + strCon = theclazz.getDeclaredConstructor( + new Class[] {String.class}); + // we have to check and make sure the constructor is + // accessible. if we didn't it would throw an exception + // and cause a NPE. + if (label == null || label.length() == 0) { + label = className; + } + if (strCon.getModifiers() == Modifier.PUBLIC) { + strParams = new Object[]{label}; + } else { + strCon = null; + } + } catch (NoSuchMethodException e) { + log.info("String constructor:: " + e.getMessage()); + } + try { + con = theclazz.getDeclaredConstructor(new Class[0]); + if (con != null){ + params = new Object[]{}; + } + } catch (NoSuchMethodException e) { + log.info("Empty constructor:: " + e.getMessage()); + } + try { + // if the string constructor is not null, we use it. + // if the string constructor is null, we use the empty + // constructor to get a new instance + if (strCon != null) { + testclass = strCon.newInstance(strParams); + } else if (con != null){ + testclass = con.newInstance(params); + } + } catch (InvocationTargetException e) { + log.warn(e.getMessage()); + } catch (InstantiationException e) { + log.info(e.getMessage()); + } catch (IllegalAccessException e) { + log.info(e.getMessage()); + } + } + } + return testclass; + } + + /** + * Get a method. + * @param clazz the classname (may be null) + * @param method the method name (may be null) + * @return the method or null if an error occurred + * (or either parameter is null) + */ + private Method getMethod(Object clazz, String method){ + if (clazz != null && method != null){ + try { + return clazz.getClass().getMethod(method,new Class[0]); + } catch (NoSuchMethodException e) { + log.warn(e.getMessage()); + } + } + return null; + } + + private Method getMethodWithAnnotation(Object clazz, Class annotation) { + if(null != clazz && null != annotation) { + for(Method m : clazz.getClass().getMethods()) { + if(m.isAnnotationPresent(annotation)) { + return m; + } + } + } + return null; + } + + /* + * Wrapper to convert a JUnit4 class into a TestCase + * + * TODO - work out how to convert JUnit4 assertions so they are treated as failures rather than errors + */ + private class AnnotatedTestCase extends TestCase { + private final Method method; + private final Class expectedException; + private final long timeout; + public AnnotatedTestCase(Method method, Class expectedException2, long timeout) { + this.method = method; + this.expectedException = expectedException2; + this.timeout = timeout; + } + + @Override + protected void runTest() throws Throwable { + try { + long start = System.currentTimeMillis(); + method.invoke(testObject, (Object[])null); + if (expectedException != None.class) { + throw new AssertionFailedError( + "No error was generated for a test case which specifies an error."); + } + if (timeout > 0){ + long elapsed = System.currentTimeMillis() - start; + if (elapsed > timeout) { + throw new AssertionFailedError("Test took longer than the specified timeout."); + } + } + } catch (InvocationTargetException e) { + Throwable thrown = e.getCause(); + if (thrown == null) { // probably should not happen + throw e; + } + if (expectedException == None.class){ + // Convert JUnit4 AssertionError failures to JUnit3 style so + // will be treated as failure rather than error. + if (thrown instanceof AssertionError && !(thrown instanceof AssertionFailedError)){ + AssertionFailedError afe = new AssertionFailedError(thrown.toString()); + // copy the original stack trace + afe.setStackTrace(thrown.getStackTrace()); + throw afe; + } + throw thrown; + } + if (!expectedException.isAssignableFrom(thrown.getClass())){ + throw new AssertionFailedError("The wrong exception was thrown from the test case"); + } + } + } + } + + public void threadFinished() { + } + + /** + * Set up all variables that don't change between samples. + */ + public void threadStarted() { + testObject = null; + testCase = null; + methodName = getMethod(); + className = getClassname(); + protectable = null; + if(!getCreateOneInstancePerSample()) { + // NO NEED TO INITIALIZE WHEN getCreateOneInstancePerSample + // is true cause it will be done in sample + initializeTestObject(); + } + } + + /** + * Initialize test object + */ + private void initializeTestObject() { + String rlabel = getConstructorString(); + if (rlabel.length()== 0) { + rlabel = JUnitSampler.class.getName(); + } + this.testObject = getClassInstance(className, rlabel); + if (this.testObject != null){ + initMethodObjects(this.testObject); + final Method m = getMethod(this.testObject,methodName); + if (getJunit4()){ + Class expectedException = None.class; + long timeout = 0; + Test annotation = m.getAnnotation(Test.class); + if(null != annotation) { + expectedException = annotation.expected(); + timeout = annotation.timeout(); + } + final AnnotatedTestCase at = new AnnotatedTestCase(m, expectedException, timeout); + testCase = at; + protectable = new Protectable() { + public void protect() throws Throwable { + at.runTest(); + } + }; + } else { + this.testCase = (TestCase) this.testObject; + final Object theClazz = this.testObject; // Must be final to create instance + protectable = new Protectable() { + public void protect() throws Throwable { + try { + m.invoke(theClazz,new Object[0]); + } catch (InvocationTargetException e) { + /* + * Calling a method via reflection results in wrapping any + * Exceptions in ITE; unwrap these here so runProtected can + * allocate them correctly. + */ + Throwable t = e.getCause(); + if (t != null) { + throw t; + } + throw e; + } + } + }; + } + if (this.testCase != null){ + this.testCase.setName(methodName); + } + } + } + + /** + * + * @param createOneInstancePerSample + */ + public void setCreateOneInstancePerSample(boolean createOneInstancePerSample) { + this.setProperty(CREATE_INSTANCE_PER_SAMPLE, createOneInstancePerSample, CREATE_INSTANCE_PER_SAMPLE_DEFAULT); + } + + /** + * + * @return boolean create New Instance For Each Call + */ + public boolean getCreateOneInstancePerSample() { + return getPropertyAsBoolean(CREATE_INSTANCE_PER_SAMPLE, CREATE_INSTANCE_PER_SAMPLE_DEFAULT); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSampler.java new file mode 100644 index 0000000..8d8e6f5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSampler.java @@ -0,0 +1,303 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.java.sampler; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A sampler for executing custom Java code in each sample. See + * {@link JavaSamplerClient} and {@link AbstractJavaSamplerClient} for + * information on writing Java code to be executed by this sampler. + * + */ +public class JavaSampler extends AbstractSampler implements TestListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; // Remember to change this when the class changes ... + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.protocol.java.config.gui.JavaConfigGui", + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + /** + * Property key representing the classname of the JavaSamplerClient to user. + */ + public static final String CLASSNAME = "classname"; + + /** + * Property key representing the arguments for the JavaSamplerClient. + */ + public static final String ARGUMENTS = "arguments"; + + /** + * The JavaSamplerClient instance used by this sampler to actually perform + * the sample. + */ + private transient JavaSamplerClient javaClient = null; + + /** + * The JavaSamplerContext instance used by this sampler to hold information + * related to the test run, such as the parameters specified for the sampler + * client. + */ + private transient JavaSamplerContext context = null; + + /** + * Set used to register all active JavaSamplers. This is used so that the + * samplers can be notified when the test ends. + */ + private static final Set allSamplers = new HashSet(); + + /** + * Create a JavaSampler. + */ + public JavaSampler() { + setArguments(new Arguments()); + synchronized (allSamplers) { + allSamplers.add(this); + } + } + + /** + * Set the arguments (parameters) for the JavaSamplerClient to be executed + * with. + * + * @param args + * the new arguments. These replace any existing arguments. + */ + public void setArguments(Arguments args) { + setProperty(new TestElementProperty(ARGUMENTS, args)); + } + + /** + * Get the arguments (parameters) for the JavaSamplerClient to be executed + * with. + * + * @return the arguments + */ + public Arguments getArguments() { + return (Arguments) getProperty(ARGUMENTS).getObjectValue(); + } + + /** + * Releases Java Client. + */ + private void releaseJavaClient() { + if (javaClient != null) { + javaClient.teardownTest(context); + } + javaClient = null; + context = null; + } + + /** + * Sets the Classname attribute of the JavaConfig object + * + * @param classname + * the new Classname value + */ + public void setClassname(String classname) { + setProperty(CLASSNAME, classname); + } + + /** + * Gets the Classname attribute of the JavaConfig object + * + * @return the Classname value + */ + public String getClassname() { + return getPropertyAsString(CLASSNAME); + } + + /** + * Performs a test sample. + * + * The sample() method retrieves the reference to the Java + * client and calls its runTest() method. + * + * @see JavaSamplerClient#runTest(JavaSamplerContext) + * + * @param entry + * the Entry for this sample + * @return test SampleResult + */ + public SampleResult sample(Entry entry) { + Arguments args = getArguments(); + args.addArgument(TestElement.NAME, getName()); // Allow Sampler access + // to test element name + context = new JavaSamplerContext(args); + if (javaClient == null) { + log.debug(whoAmI() + "Creating Java Client"); + createJavaClient(); + javaClient.setupTest(context); + } + + SampleResult result = createJavaClient().runTest(context); + + // Only set the default label if it has not been set + if (result != null && result.getSampleLabel().length() == 0) { + result.setSampleLabel(getName()); + } + + return result; + } + + /** + * Returns reference to JavaSamplerClient. + * + * The createJavaClient() method uses reflection to create an + * instance of the specified Java protocol client. If the class can not be + * found, the method returns a reference to this object. + * + * @return JavaSamplerClient reference. + */ + private JavaSamplerClient createJavaClient() { + if (javaClient == null) { + try { + Class javaClass = Class.forName(getClassname().trim(), false, Thread.currentThread() + .getContextClassLoader()); + javaClient = (JavaSamplerClient) javaClass.newInstance(); + context = new JavaSamplerContext(getArguments()); + + if (log.isDebugEnabled()) { + log.debug(whoAmI() + "\tCreated:\t" + getClassname() + "@" + + Integer.toHexString(javaClient.hashCode())); + } + } catch (Exception e) { + log.error(whoAmI() + "\tException creating: " + getClassname(), e); + javaClient = new ErrorSamplerClient(); + } + } + return javaClient; + } + + /** + * Retrieves reference to JavaSamplerClient. + * + * Convience method used to check for null reference without actually + * creating a JavaSamplerClient + * + * @return reference to JavaSamplerClient NOTUSED private JavaSamplerClient + * retrieveJavaClient() { return javaClient; } + */ + + /** + * Generate a String identifier of this instance for debugging purposes. + * + * @return a String identifier for this sampler instance + */ + private String whoAmI() { + StringBuilder sb = new StringBuilder(); + sb.append(Thread.currentThread().getName()); + sb.append("@"); + sb.append(Integer.toHexString(hashCode())); + sb.append("-"); + sb.append(getName()); + return sb.toString(); + } + + // TestListener implementation + /* Implements TestListener.testStarted() */ + public void testStarted() { + log.debug(whoAmI() + "\ttestStarted"); + } + + /* Implements TestListener.testStarted(String) */ + public void testStarted(String host) { + log.debug(whoAmI() + "\ttestStarted(" + host + ")"); + } + + /** + * Method called at the end of the test. This is called only on one instance + * of JavaSampler. This method will loop through all of the other + * JavaSamplers which have been registered (automatically in the + * constructor) and notify them that the test has ended, allowing the + * JavaSamplerClients to cleanup. + */ + public void testEnded() { + log.debug(whoAmI() + "\ttestEnded"); + synchronized (allSamplers) { + Iterator i = allSamplers.iterator(); + while (i.hasNext()) { + JavaSampler sampler = i.next(); + sampler.releaseJavaClient(); + i.remove(); + } + } + } + + /* Implements TestListener.testEnded(String) */ + public void testEnded(String host) { + testEnded(); + } + + /* Implements TestListener.testIterationStart(LoopIterationEvent) */ + public void testIterationStart(LoopIterationEvent event) { + } + + /** + * A {@link JavaSamplerClient} implementation used for error handling. If an + * error occurs while creating the real JavaSamplerClient object, it is + * replaced with an instance of this class. Each time a sample occurs with + * this class, the result is marked as a failure so the user can see that + * the test failed. + */ + class ErrorSamplerClient extends AbstractJavaSamplerClient { + /** + * Return SampleResult with data on error. + * + * @see JavaSamplerClient#runTest(JavaSamplerContext) + */ + public SampleResult runTest(JavaSamplerContext p_context) { + log.debug(whoAmI() + "\trunTest"); + Thread.yield(); + SampleResult results = new SampleResult(); + results.setSuccessful(false); + results.setResponseData(("Class not found: " + getClassname()), null); + results.setSampleLabel("ERROR: " + getClassname()); + return results; + } + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java new file mode 100644 index 0000000..cfdd627 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSamplerClient.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.java.sampler; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.samplers.SampleResult; + +/** + * This interface defines the interactions between the JavaSampler and external + * Java programs which can be executed by JMeter. Any Java class which wants to + * be executed as a JMeter test must implement this interface (either directly + * or indirectly through AbstractJavaSamplerClient). + *

+ * JMeter will create one instance of a JavaSamplerClient implementation for + * each user/thread in the test. Additional instances may be created for + * internal use by JMeter (for example, to find out what parameters are + * supported by the client). + *

+ * When the test is started, setupTest() will be called on each thread's + * JavaSamplerClient instance to initialize the client. Then runTest() will be + * called for each iteration of the test. Finally, teardownTest() will be called + * to allow the client to do any necessary clean-up. + *

+ * The JMeter JavaSampler GUI allows a list of parameters to be defined for the + * test. These are passed to the various test methods through the + * {@link JavaSamplerContext}. A list of default parameters can be defined + * through the getDefaultParameters() method. These parameters and any default + * values associated with them will be shown in the GUI. Users can add other + * parameters as well. + *

+ * When possible, Java tests should extend {@link AbstractJavaSamplerClient + * AbstractJavaSamplerClient} rather than implementing JavaSamplerClient + * directly. This should protect your tests from future changes to the + * interface. While it may be necessary to make changes to the JavaSamplerClient + * interface from time to time (therefore requiring changes to any + * implementations of this interface), we intend to make this abstract class + * provide reasonable default implementations of any new methods so that + * subclasses do not necessarily need to be updated for new versions. + * Implementing JavaSamplerClient directly will continue to be supported for + * cases where extending this class is not possible (for example, when the + * client class is already a subclass of some other class). + *

+ * See {@link org.apache.jmeter.protocol.java.test.SleepTest} for an example of + * how to implement this interface. + * + * @version $Revision: 674365 $ + */ +public interface JavaSamplerClient { + /** + * Do any initialization required by this client. It is generally + * recommended to do any initialization such as getting parameter values in + * the setupTest method rather than the runTest method in order to add as + * little overhead as possible to the test. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + */ + void setupTest(JavaSamplerContext context); + + /** + * Perform a single sample for each iteration. This method returns a + * SampleResult object. SampleResult has many + * fields which can be used. At a minimum, the test should use + * SampleResult.sampleStart and + * SampleResult.sampleEndto set the time that the test + * required to execute. It is also a good idea to set the sampleLabel and + * the successful flag. + * + * @see org.apache.jmeter.samplers.SampleResult#sampleStart() + * @see org.apache.jmeter.samplers.SampleResult#sampleEnd() + * @see org.apache.jmeter.samplers.SampleResult#setSuccessful(boolean) + * @see org.apache.jmeter.samplers.SampleResult#setSampleLabel(String) + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + * + * @return a SampleResult giving the results of this sample. + */ + SampleResult runTest(JavaSamplerContext context); + + /** + * Do any clean-up required by this test at the end of a test run. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + */ + void teardownTest(JavaSamplerContext context); + + /** + * Provide a list of parameters which this test supports. Any parameter + * names and associated values returned by this method will appear in the + * GUI by default so the user doesn't have to remember the exact names. The + * user can add other parameters which are not listed here. If this method + * returns null then no parameters will be listed. If the value for some + * parameter is null then that parameter will be listed in the GUI with an + * empty value. + * + * @return a specification of the parameters used by this test which should + * be listed in the GUI, or null if no parameters should be listed. + */ + Arguments getDefaultParameters(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSamplerContext.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSamplerContext.java new file mode 100644 index 0000000..9de1a49 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/sampler/JavaSamplerContext.java @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.java.sampler; + +import java.util.Iterator; +import java.util.Map; + +import org.apache.jmeter.config.Arguments; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * JavaSamplerContext is used to provide context information to a + * JavaSamplerClient implementation. This currently consists of the + * initialization parameters which were specified in the GUI. Additional data + * may be accessible through JavaSamplerContext in the future. + * + * @version $Revision: 901937 $ + */ +public class JavaSamplerContext { + /* + * Implementation notes: + * + * All of the methods in this class are currently read-only. If update + * methods are included in the future, they should be defined so that a + * single instance of JavaSamplerContext can be associated with each thread. + * Therefore, no synchronization should be needed. The same instance should + * be used for the call to setupTest, all calls to runTest, and the call to + * teardownTest. + */ + + /** Logging */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Map containing the initialization parameters for the JavaSamplerClient. + */ + private final Map params; + + /** + * Create a new JavaSampler with the specified initialization parameters. + * + * @param args + * the initialization parameters. + */ + public JavaSamplerContext(Arguments args) { + this.params = args.getArgumentsAsMap(); + } + + /** + * Determine whether or not a value has been specified for the parameter + * with this name. + * + * @param name + * the name of the parameter to test + * @return true if the parameter value has been specified, false otherwise. + */ + public boolean containsParameter(String name) { + return params.containsKey(name); + } + + /** + * Get an iterator of the parameter names. Each entry in the Iterator is a + * String. + * + * @return an Iterator of Strings listing the names of the parameters which + * have been specified for this test. + */ + public Iterator getParameterNamesIterator() { + return params.keySet().iterator(); + } + + /** + * Get the value of a specific parameter as a String, or null if the value + * was not specified. + * + * @param name + * the name of the parameter whose value should be retrieved + * @return the value of the parameter, or null if the value was not + * specified + */ + public String getParameter(String name) { + return getParameter(name, null); + } + + /** + * Get the value of a specified parameter as a String, or return the + * specified default value if the value was not specified. + * + * @param name + * the name of the parameter whose value should be retrieved + * @param defaultValue + * the default value to return if the value of this parameter was + * not specified + * @return the value of the parameter, or the default value if the parameter + * was not specified + */ + public String getParameter(String name, String defaultValue) { + if (params == null || !params.containsKey(name)) { + return defaultValue; + } + return params.get(name); + } + + /** + * Get the value of a specified parameter as an integer. An exception will + * be thrown if the parameter is not specified or if it is not an integer. + * The value may be specified in decimal, hexadecimal, or octal, as defined + * by Integer.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @return the value of the parameter + * + * @throws NumberFormatException + * if the parameter is not specified or is not an integer + * + * @see java.lang.Integer#decode(java.lang.String) + */ + public int getIntParameter(String name) throws NumberFormatException { + if (params == null || !params.containsKey(name)) { + throw new NumberFormatException("No value for parameter named '" + name + "'."); + } + + return Integer.decode(params.get(name)).intValue(); + } + + /** + * Get the value of a specified parameter as an integer, or return the + * specified default value if the value was not specified or is not an + * integer. A warning will be logged if the value is not an integer. The + * value may be specified in decimal, hexadecimal, or octal, as defined by + * Integer.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @param defaultValue + * the default value to return if the value of this parameter was + * not specified + * @return the value of the parameter, or the default value if the parameter + * was not specified + * + * @see java.lang.Integer#decode(java.lang.String) + */ + public int getIntParameter(String name, int defaultValue) { + if (params == null || !params.containsKey(name)) { + return defaultValue; + } + + try { + return Integer.decode(params.get(name)).intValue(); + } catch (NumberFormatException e) { + log.warn("Value for parameter '" + name + "' not an integer: '" + params.get(name) + "'. Using default: '" + + defaultValue + "'.", e); + return defaultValue; + } + } + + /** + * Get the value of a specified parameter as a long. An exception will be + * thrown if the parameter is not specified or if it is not a long. The + * value may be specified in decimal, hexadecimal, or octal, as defined by + * Long.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @return the value of the parameter + * + * @throws NumberFormatException + * if the parameter is not specified or is not a long + * + * @see Long#decode(String) + */ + public long getLongParameter(String name) throws NumberFormatException { + if (params == null || !params.containsKey(name)) { + throw new NumberFormatException("No value for parameter named '" + name + "'."); + } + + return Long.decode(params.get(name)).longValue(); + } + + /** + * Get the value of a specified parameter as along, or return the specified + * default value if the value was not specified or is not a long. A warning + * will be logged if the value is not a long. The value may be specified in + * decimal, hexadecimal, or octal, as defined by Long.decode(). + * + * @param name + * the name of the parameter whose value should be retrieved + * @param defaultValue + * the default value to return if the value of this parameter was + * not specified + * @return the value of the parameter, or the default value if the parameter + * was not specified + * + * @see Long#decode(String) + */ + public long getLongParameter(String name, long defaultValue) { + if (params == null || !params.containsKey(name)) { + return defaultValue; + } + try { + return Long.decode(params.get(name)).longValue(); + } catch (NumberFormatException e) { + log.warn("Value for parameter '" + name + "' not a long: '" + params.get(name) + "'. Using default: '" + + defaultValue + "'.", e); + return defaultValue; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/test/JavaTest.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/test/JavaTest.java new file mode 100644 index 0000000..3469891 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/test/JavaTest.java @@ -0,0 +1,359 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.java.test; + +import java.io.Serializable; +import java.util.Iterator; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; + +/** + * The JavaTest class is a simple sampler which is intended for + * use when developing test plans. The sampler generates results internally, so + * does not need access to any external resources such as web, ftp or LDAP + * servers. In addition, because the exact values of most of the SampleResult + * can be directly set, it is possible to easily test most Assertions that use + * the sample results. + * + *

+ * During each sample, this client will sleep for some amount of time. The + * amount of time to sleep is determined from the two parameters Sleep_Time and + * Sleep_Mask using the formula: + * + *

+ * totalSleepTime = Sleep_Time + (System.currentTimeMillis() % Sleep_Mask)
+ * 
+ * + * Thus, the Sleep_Mask provides a way to add a random component to the sleep + * time. + *

+ * The sampler is able to define the precise values of: + * + *

+ *
+ *  - responseCode
+ *  - responseMessage
+ *  - Label
+ *  - success/fail status
+ *
+ * 
+ * + * The elapsed time and end-time cannot be directly controlled. + *

+ * Note: this class was derived from {@link SleepTest}. + * + */ + +public class JavaTest extends AbstractJavaSamplerClient implements Serializable { + private static final long serialVersionUID = 240L; + + /** The base number of milliseconds to sleep during each sample. */ + private long sleepTime; + + /** The default value of the SleepTime parameter, in milliseconds. */ + public static final long DEFAULT_SLEEP_TIME = 100; + + /** The name used to store the SleepTime parameter. */ + private static final String SLEEP_NAME = "Sleep_Time"; + + /** + * A mask to be applied to the current time in order to add a semi-random + * component to the sleep time. + */ + private long sleepMask; + + /** The default value of the SleepMask parameter. */ + public static final long DEFAULT_SLEEP_MASK = 0xff; + + /** Formatted string representation of the default SleepMask. */ + private static final String DEFAULT_MASK_STRING = "0x" + (Long.toHexString(DEFAULT_SLEEP_MASK)).toUpperCase(java.util.Locale.ENGLISH); + + /** The name used to store the SleepMask parameter. */ + private static final String MASK_NAME = "Sleep_Mask"; + + /** The label to store in the sample result. */ + private String label; + + /** The default value of the Label parameter. */ + // private static final String LABEL_DEFAULT = "JavaTest"; + /** The name used to store the Label parameter. */ + private static final String LABEL_NAME = "Label"; + + /** The response message to store in the sample result. */ + private String responseMessage; + + /** The default value of the ResponseMessage parameter. */ + private static final String RESPONSE_MESSAGE_DEFAULT = ""; + + /** The name used to store the ResponseMessage parameter. */ + private static final String RESPONSE_MESSAGE_NAME = "ResponseMessage"; + + /** The response code to be stored in the sample result. */ + private String responseCode; + + /** The default value of the ResponseCode parameter. */ + private static final String RESPONSE_CODE_DEFAULT = ""; + + /** The name used to store the ResponseCode parameter. */ + private static final String RESPONSE_CODE_NAME = "ResponseCode"; + + /** The sampler data (shown as Request Data in the Tree display). */ + private String samplerData; + + /** The default value of the SamplerData parameter. */ + private static final String SAMPLER_DATA_DEFAULT = ""; + + /** The name used to store the SamplerData parameter. */ + private static final String SAMPLER_DATA_NAME = "SamplerData"; + + /** Holds the result data (shown as Response Data in the Tree display). */ + private String resultData; + + /** The default value of the ResultData parameter. */ + private static final String RESULT_DATA_DEFAULT = ""; + + /** The name used to store the ResultData parameter. */ + private static final String RESULT_DATA_NAME = "ResultData"; + + /** The success status to be stored in the sample result. */ + private boolean success; + + /** The default value of the Success Status parameter. */ + private static final String SUCCESS_DEFAULT = "OK"; + + /** The name used to store the Success Status parameter. */ + private static final String SUCCESS_NAME = "Status"; + + /** + * Default constructor for JavaTest. + * + * The Java Sampler uses the default constructor to instantiate an instance + * of the client class. + */ + public JavaTest() { + getLogger().debug(whoAmI() + "\tConstruct"); + } + + /* + * Utility method to set up all the values + */ + private void setupValues(JavaSamplerContext context) { + + sleepTime = context.getLongParameter(SLEEP_NAME, DEFAULT_SLEEP_TIME); + sleepMask = context.getLongParameter(MASK_NAME, DEFAULT_SLEEP_MASK); + + responseMessage = context.getParameter(RESPONSE_MESSAGE_NAME, RESPONSE_MESSAGE_DEFAULT); + + responseCode = context.getParameter(RESPONSE_CODE_NAME, RESPONSE_CODE_DEFAULT); + + success = context.getParameter(SUCCESS_NAME, SUCCESS_DEFAULT).equalsIgnoreCase("OK"); + + label = context.getParameter(LABEL_NAME, ""); + if (label.length() == 0) { + label = context.getParameter(TestElement.NAME); // default to name of element + } + + samplerData = context.getParameter(SAMPLER_DATA_NAME, SAMPLER_DATA_DEFAULT); + + resultData = context.getParameter(RESULT_DATA_NAME, RESULT_DATA_DEFAULT); + } + + /** + * Do any initialization required by this client. + * + * There is none, as it is done in runTest() in order to be able to vary the + * data for each sample. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + */ + @Override + public void setupTest(JavaSamplerContext context) { + getLogger().debug(whoAmI() + "\tsetupTest()"); + listParameters(context); + } + + /** + * Provide a list of parameters which this test supports. Any parameter + * names and associated values returned by this method will appear in the + * GUI by default so the user doesn't have to remember the exact names. The + * user can add other parameters which are not listed here. If this method + * returns null then no parameters will be listed. If the value for some + * parameter is null then that parameter will be listed in the GUI with an + * empty value. + * + * @return a specification of the parameters used by this test which should + * be listed in the GUI, or null if no parameters should be listed. + */ + @Override + public Arguments getDefaultParameters() { + Arguments params = new Arguments(); + params.addArgument(SLEEP_NAME, String.valueOf(DEFAULT_SLEEP_TIME)); + params.addArgument(MASK_NAME, DEFAULT_MASK_STRING); + params.addArgument(LABEL_NAME, ""); + params.addArgument(RESPONSE_CODE_NAME, RESPONSE_CODE_DEFAULT); + params.addArgument(RESPONSE_MESSAGE_NAME, RESPONSE_MESSAGE_DEFAULT); + params.addArgument(SUCCESS_NAME, SUCCESS_DEFAULT); + params.addArgument(SAMPLER_DATA_NAME, SAMPLER_DATA_DEFAULT); + params.addArgument(RESULT_DATA_NAME, SAMPLER_DATA_DEFAULT); + return params; + } + + /** + * Perform a single sample.
+ * In this case, this method will simply sleep for some amount of time. + * + * This method returns a SampleResult object. + * + *

+     *
+     *  The following fields are always set:
+     *  - responseCode (default "")
+     *  - responseMessage (default "")
+     *  - label (set from LABEL_NAME parameter if it exists, else element name)
+     *  - success (default true)
+     *
+     * 
+ * + * The following fields are set from the user-defined parameters, if + * supplied: + * + *
+     * -samplerData - responseData
+     * 
+ * + * @see org.apache.jmeter.samplers.SampleResult#sampleStart() + * @see org.apache.jmeter.samplers.SampleResult#sampleEnd() + * @see org.apache.jmeter.samplers.SampleResult#setSuccessful(boolean) + * @see org.apache.jmeter.samplers.SampleResult#setSampleLabel(String) + * @see org.apache.jmeter.samplers.SampleResult#setResponseCode(String) + * @see org.apache.jmeter.samplers.SampleResult#setResponseMessage(String) + * @see org.apache.jmeter.samplers.SampleResult#setResponseData(byte []) + * @see org.apache.jmeter.samplers.SampleResult#setDataType(String) + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + * + * @return a SampleResult giving the results of this sample. + */ + public SampleResult runTest(JavaSamplerContext context) { + setupValues(context); + + SampleResult results = new SampleResult(); + + results.setResponseCode(responseCode); + results.setResponseMessage(responseMessage); + results.setSampleLabel(label); + + if (samplerData != null && samplerData.length() > 0) { + results.setSamplerData(samplerData); + } + + if (resultData != null && resultData.length() > 0) { + results.setResponseData(resultData, null); + results.setDataType(SampleResult.TEXT); + } + + // Record sample start time. + results.sampleStart(); + + long sleep = sleepTime; + if (sleepTime > 0 && sleepMask > 0) { // / Only do the calculation if + // it is needed + long start = System.currentTimeMillis(); + // Generate a random-ish offset value using the current time. + sleep = sleepTime + (start % sleepMask); + } + + try { + // Execute the sample. In this case sleep for the + // specified time, if any + if (sleep > 0) { + Thread.sleep(sleep); + } + results.setSuccessful(success); + } catch (InterruptedException e) { + getLogger().warn("JavaTest: interrupted."); + results.setSuccessful(true); + } catch (Exception e) { + getLogger().error("JavaTest: error during sample", e); + results.setSuccessful(false); + } finally { + // Record end time and populate the results. + results.sampleEnd(); + } + + if (getLogger().isDebugEnabled()) { + getLogger().debug(whoAmI() + "\trunTest()" + "\tTime:\t" + results.getTime()); + listParameters(context); + } + + return results; + } + + /** + * Do any clean-up required by this test. In this case no clean-up is + * necessary, but some messages are logged for debugging purposes. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + */ + @Override + public void teardownTest(JavaSamplerContext context) { + getLogger().debug(whoAmI() + "\tteardownTest()"); + listParameters(context); + } + + /** + * Dump a list of the parameters in this context to the debug log. + * + * @param context + * the context which contains the initialization parameters. + */ + private void listParameters(JavaSamplerContext context) { + if (getLogger().isDebugEnabled()) { + Iterator argsIt = context.getParameterNamesIterator(); + while (argsIt.hasNext()) { + String name = argsIt.next(); + getLogger().debug(name + "=" + context.getParameter(name)); + } + } + } + + /** + * Generate a String identifier of this test for debugging purposes. + * + * @return a String identifier for this test instance + */ + private String whoAmI() { + StringBuilder sb = new StringBuilder(); + sb.append(Thread.currentThread().toString()); + sb.append("@"); + sb.append(Integer.toHexString(hashCode())); + return sb.toString(); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/java/test/SleepTest.java b/ApacheJmeter/src/org/apache/jmeter/protocol/java/test/SleepTest.java new file mode 100644 index 0000000..b6057dd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/java/test/SleepTest.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.protocol.java.test; + +import java.io.Serializable; +import java.util.Iterator; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; +import org.apache.jmeter.samplers.SampleResult; + +/** + * The SleepTest class is a simple example class for a JMeter + * Java protocol client. The class implements the JavaSamplerClient + * interface. + *

+ * During each sample, this client will sleep for some amount of time. The + * amount of time to sleep is determined from the two parameters SleepTime and + * SleepMask using the formula: + * + *

+ * totalSleepTime = SleepTime + (System.currentTimeMillis() % SleepMask)
+ * 
+ * + * Thus, the SleepMask provides a way to add a random component to the sleep + * time. + * + * @version $Revision: 921767 $ + */ +public class SleepTest extends AbstractJavaSamplerClient implements Serializable { + private static final long serialVersionUID = 240L; + + /** + * The default value of the SleepTime parameter, in milliseconds. + */ + public static final long DEFAULT_SLEEP_TIME = 1000; + + /** + * The default value of the SleepMask parameter. + */ + public static final long DEFAULT_SLEEP_MASK = 0x3ff; + + /** + * The base number of milliseconds to sleep during each sample. + */ + private long sleepTime; + + /** + * A mask to be applied to the current time in order to add a random + * component to the sleep time. + */ + private long sleepMask; + + /** + * Default constructor for SleepTest. + * + * The Java Sampler uses the default constructor to instantiate an instance + * of the client class. + */ + public SleepTest() { + getLogger().debug(whoAmI() + "\tConstruct"); + } + + /** + * Do any initialization required by this client. In this case, + * initialization consists of getting the values of the SleepTime and + * SleepMask parameters. It is generally recommended to do any + * initialization such as getting parameter values in the setupTest method + * rather than the runTest method in order to add as little overhead as + * possible to the test. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + */ + @Override + public void setupTest(JavaSamplerContext context) { + getLogger().debug(whoAmI() + "\tsetupTest()"); + listParameters(context); + + sleepTime = context.getLongParameter("SleepTime", DEFAULT_SLEEP_TIME); + sleepMask = context.getLongParameter("SleepMask", DEFAULT_SLEEP_MASK); + } + + /** + * Perform a single sample. In this case, this method will simply sleep for + * some amount of time. Perform a single sample for each iteration. This + * method returns a SampleResult object. + * SampleResult has many fields which can be used. At a + * minimum, the test should use SampleResult.sampleStart and + * SampleResult.sampleEndto set the time that the test + * required to execute. It is also a good idea to set the sampleLabel and + * the successful flag. + * + * @see org.apache.jmeter.samplers.SampleResult#sampleStart() + * @see org.apache.jmeter.samplers.SampleResult#sampleEnd() + * @see org.apache.jmeter.samplers.SampleResult#setSuccessful(boolean) + * @see org.apache.jmeter.samplers.SampleResult#setSampleLabel(String) + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + * + * @return a SampleResult giving the results of this sample. + */ + public SampleResult runTest(JavaSamplerContext context) { + SampleResult results = new SampleResult(); + + try { + // Record sample start time. + results.sampleStart(); + + long sleep = sleepTime; + // Only do the calculation if it is needed + if (sleepTime > 0 && sleepMask > 0) { + long start = System.currentTimeMillis(); + // Generate a random-ish offset value using the current time. + sleep = sleepTime + (start % sleepMask); + } + + results.setSampleLabel("Sleep Test: time = " + sleep); + + // Execute the sample. In this case sleep for the + // specified time. + Thread.sleep(sleep); + + results.setSuccessful(true); + } catch (InterruptedException e) { + getLogger().warn("SleepTest: interrupted."); + results.setSuccessful(true); + } catch (Exception e) { + getLogger().error("SleepTest: error during sample", e); + results.setSuccessful(false); + } finally { + results.sampleEnd(); + } + + if (getLogger().isDebugEnabled()) { + getLogger().debug(whoAmI() + "\trunTest()" + "\tTime:\t" + results.getTime()); + listParameters(context); + } + + return results; + } + + /** + * Do any clean-up required by this test. In this case no clean-up is + * necessary, but some messages are logged for debugging purposes. + * + * @param context + * the context to run with. This provides access to + * initialization parameters. + */ + @Override + public void teardownTest(JavaSamplerContext context) { + getLogger().debug(whoAmI() + "\tteardownTest()"); + listParameters(context); + } + + /** + * Provide a list of parameters which this test supports. Any parameter + * names and associated values returned by this method will appear in the + * GUI by default so the user doesn't have to remember the exact names. The + * user can add other parameters which are not listed here. If this method + * returns null then no parameters will be listed. If the value for some + * parameter is null then that parameter will be listed in the GUI with an + * empty value. + * + * @return a specification of the parameters used by this test which should + * be listed in the GUI, or null if no parameters should be listed. + */ + @Override + public Arguments getDefaultParameters() { + Arguments params = new Arguments(); + params.addArgument("SleepTime", String.valueOf(DEFAULT_SLEEP_TIME)); + params.addArgument("SleepMask", "0x" + (Long.toHexString(DEFAULT_SLEEP_MASK)).toUpperCase(java.util.Locale.ENGLISH)); + return params; + } + + /** + * Dump a list of the parameters in this context to the debug log. + * + * @param context + * the context which contains the initialization parameters. + */ + private void listParameters(JavaSamplerContext context) { + if (getLogger().isDebugEnabled()) { + Iterator argsIt = context.getParameterNamesIterator(); + while (argsIt.hasNext()) { + String name = argsIt.next(); + getLogger().debug(name + "=" + context.getParameter(name)); + } + } + } + + /** + * Generate a String identifier of this test for debugging purposes. + * + * @return a String identifier for this test instance + */ + private String whoAmI() { + StringBuilder sb = new StringBuilder(); + sb.append(Thread.currentThread().toString()); + sb.append("@"); + sb.append(Integer.toHexString(hashCode())); + return sb.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/AbstractJDBCTestElement.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/AbstractJDBCTestElement.java new file mode 100644 index 0000000..1f4b1bb --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/AbstractJDBCTestElement.java @@ -0,0 +1,665 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jdbc; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A base class for all JDBC test elements handling the basics of a SQL request. + * + */ +public abstract class AbstractJDBCTestElement extends AbstractTestElement implements TestListener{ + private static final long serialVersionUID = 235L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String COMMA = ","; // $NON-NLS-1$ + private static final char COMMA_CHAR = ','; + + private static final String UNDERSCORE = "_"; // $NON-NLS-1$ + + // This value is used for both the connection (perConnCache) and statement (preparedStatementMap) caches. + // TODO - do they have to be the same size? + private static final int MAX_ENTRIES = + JMeterUtils.getPropDefault("jdbcsampler.cachesize",200); // $NON-NLS-1$ + + // String used to indicate a null value + private static final String NULL_MARKER = + JMeterUtils.getPropDefault("jdbcsampler.nullmarker","]NULL["); // $NON-NLS-1$ + + private static final String INOUT = "INOUT"; // $NON-NLS-1$ + + private static final String OUT = "OUT"; // $NON-NLS-1$ + + // TODO - should the encoding be configurable? + protected static final String ENCODING = "UTF-8"; // $NON-NLS-1$ + + // key: name (lowercase) from java.sql.Types; entry: corresponding int value + private static final Map mapJdbcNameToInt; + // read-only after class init + + static { + // based on e291. Getting the Name of a JDBC Type from javaalmanac.com + // http://javaalmanac.com/egs/java.sql/JdbcInt2Str.html + mapJdbcNameToInt = new HashMap(); + + //Get all fields in java.sql.Types and store the corresponding int values + Field[] fields = java.sql.Types.class.getFields(); + for (int i=0; i> perConnCache = + Collections.synchronizedMap(new LinkedHashMap>(MAX_ENTRIES){ + private static final long serialVersionUID = 1L; + @Override + protected boolean removeEldestEntry(Map.Entry> arg0) { + if (size() > MAX_ENTRIES) { + final Map value = arg0.getValue(); + closeAllStatements(value.values()); + return true; + } + return false; + } + }); + + /** + * Creates a JDBCSampler. + */ + protected AbstractJDBCTestElement() { + } + + /** + * Execute the test element. + * + * @param conn a {@link SampleResult} in case the test should sample; null if only execution is requested + * @throws UnsupportedOperationException if the user provided incorrect query type + */ + protected byte[] execute(Connection conn) throws SQLException, UnsupportedEncodingException, IOException, UnsupportedOperationException { + log.debug("executing jdbc"); + Statement stmt = null; + + try { + // Based on query return value, get results + String _queryType = getQueryType(); + if (SELECT.equals(_queryType)) { + stmt = conn.createStatement(); + ResultSet rs = null; + try { + rs = stmt.executeQuery(getQuery()); + return getStringFromResultSet(rs).getBytes(ENCODING); + } finally { + close(rs); + } + } else if (CALLABLE.equals(_queryType)) { + CallableStatement cstmt = getCallableStatement(conn); + int out[]=setArguments(cstmt); + // A CallableStatement can return more than 1 ResultSets + // plus a number of update counts. + boolean hasResultSet = cstmt.execute(); + String sb = resultSetsToString(cstmt,hasResultSet, out); + return sb.getBytes(ENCODING); + } else if (UPDATE.equals(_queryType)) { + stmt = conn.createStatement(); + stmt.executeUpdate(getQuery()); + int updateCount = stmt.getUpdateCount(); + String results = updateCount + " updates"; + return results.getBytes(ENCODING); + } else if (PREPARED_SELECT.equals(_queryType)) { + PreparedStatement pstmt = getPreparedStatement(conn); + setArguments(pstmt); + ResultSet rs = null; + try { + rs = pstmt.executeQuery(); + return getStringFromResultSet(rs).getBytes(ENCODING); + } finally { + close(rs); + } + } else if (PREPARED_UPDATE.equals(_queryType)) { + PreparedStatement pstmt = getPreparedStatement(conn); + setArguments(pstmt); + pstmt.executeUpdate(); + String sb = resultSetsToString(pstmt,false,null); + return sb.getBytes(ENCODING); + } else if (ROLLBACK.equals(_queryType)){ + conn.rollback(); + return ROLLBACK.getBytes(ENCODING); + } else if (COMMIT.equals(_queryType)){ + conn.commit(); + return COMMIT.getBytes(ENCODING); + } else if (AUTOCOMMIT_FALSE.equals(_queryType)){ + conn.setAutoCommit(false); + return AUTOCOMMIT_FALSE.getBytes(ENCODING); + } else if (AUTOCOMMIT_TRUE.equals(_queryType)){ + conn.setAutoCommit(true); + return AUTOCOMMIT_TRUE.getBytes(ENCODING); + } else { // User provided incorrect query type + throw new UnsupportedOperationException("Unexpected query type: "+_queryType); + } + } finally { + close(stmt); + } + } + + private String resultSetsToString(PreparedStatement pstmt, boolean result, int[] out) throws SQLException, UnsupportedEncodingException { + StringBuilder sb = new StringBuilder(); + int updateCount = 0; + if (!result) { + updateCount = pstmt.getUpdateCount(); + } + do { + if (result) { + ResultSet rs = null; + try { + rs = pstmt.getResultSet(); + sb.append(getStringFromResultSet(rs)).append("\n"); // $NON-NLS-1$ + } finally { + close(rs); + } + } else { + sb.append(updateCount).append(" updates.\n"); + } + result = pstmt.getMoreResults(); + if (!result) { + updateCount = pstmt.getUpdateCount(); + } + } while (result || (updateCount != -1)); + if (out!=null && pstmt instanceof CallableStatement){ + ArrayList outputValues = new ArrayList(); + CallableStatement cs = (CallableStatement) pstmt; + sb.append("Output variables by position:\n"); + for(int i=0; i < out.length; i++){ + if (out[i]!=java.sql.Types.NULL){ + Object o = cs.getObject(i+1); + outputValues.add(o); + sb.append("["); + sb.append(i+1); + sb.append("] "); + sb.append(o); + sb.append("\n"); + } + } + String varnames[] = getVariableNames().split(COMMA); + if(varnames.length > 0) { + JMeterVariables jmvars = getThreadContext().getVariables(); + for(int i = 0; i < varnames.length && i < outputValues.size(); i++) { + String name = varnames[i].trim(); + if (name.length()>0){ // Save the value in the variable if present + Object o = outputValues.get(i); + jmvars.put(name, o == null ? null : o.toString()); + } + } + } + } + return sb.toString(); + } + + + private int[] setArguments(PreparedStatement pstmt) throws SQLException, IOException { + if (getQueryArguments().trim().length()==0) { + return new int[]{}; + } + String[] arguments = CSVSaveService.csvSplitString(getQueryArguments(), COMMA_CHAR); + String[] argumentsTypes = getQueryArgumentsTypes().split(COMMA); + if (arguments.length != argumentsTypes.length) { + throw new SQLException("number of arguments ("+arguments.length+") and number of types ("+argumentsTypes.length+") are not equal"); + } + int[] outputs= new int[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + String argument = arguments[i]; + String argumentType = argumentsTypes[i]; + String[] arg = argumentType.split(" "); + String inputOutput=""; + if (arg.length > 1) { + argumentType = arg[1]; + inputOutput=arg[0]; + } + int targetSqlType = getJdbcType(argumentType); + try { + if (!OUT.equalsIgnoreCase(inputOutput)){ + if (argument.equals(NULL_MARKER)){ + pstmt.setNull(i+1, targetSqlType); + } else { + pstmt.setObject(i+1, argument, targetSqlType); + } + } + if (OUT.equalsIgnoreCase(inputOutput)||INOUT.equalsIgnoreCase(inputOutput)) { + CallableStatement cs = (CallableStatement) pstmt; + cs.registerOutParameter(i+1, targetSqlType); + outputs[i]=targetSqlType; + } else { + outputs[i]=java.sql.Types.NULL; // can't have an output parameter type null + } + } catch (NullPointerException e) { // thrown by Derby JDBC (at least) if there are no "?" markers in statement + throw new SQLException("Could not set argument no: "+(i+1)+" - missing parameter marker?"); + } + } + return outputs; + } + + + private static int getJdbcType(String jdbcType) throws SQLException { + Integer entry = mapJdbcNameToInt.get(jdbcType.toLowerCase(java.util.Locale.ENGLISH)); + if (entry == null) { + try { + entry = Integer.decode(jdbcType); + } catch (NumberFormatException e) { + throw new SQLException("Invalid data type: "+jdbcType); + } + } + return (entry).intValue(); + } + + + private CallableStatement getCallableStatement(Connection conn) throws SQLException { + return (CallableStatement) getPreparedStatement(conn,true); + + } + private PreparedStatement getPreparedStatement(Connection conn) throws SQLException { + return getPreparedStatement(conn,false); + } + + private PreparedStatement getPreparedStatement(Connection conn, boolean callable) throws SQLException { + Map preparedStatementMap = perConnCache.get(conn); + if (null == preparedStatementMap ) { + // MRU PreparedStatements cache. + preparedStatementMap = Collections.synchronizedMap(new LinkedHashMap(MAX_ENTRIES) { + private static final long serialVersionUID = 240L; + + @Override + protected boolean removeEldestEntry(Map.Entry arg0) { + final int theSize = size(); + if (theSize > MAX_ENTRIES) { + Object value = arg0.getValue(); + if (value instanceof PreparedStatement) { + PreparedStatement pstmt = (PreparedStatement) value; + close(pstmt); + } + return true; + } + return false; + } + }); + perConnCache.put(conn, preparedStatementMap); + } + PreparedStatement pstmt = preparedStatementMap.get(getQuery()); + if (null == pstmt) { + if (callable) { + pstmt = conn.prepareCall(getQuery()); + } else { + pstmt = conn.prepareStatement(getQuery()); + } + preparedStatementMap.put(getQuery(), pstmt); + } + pstmt.clearParameters(); + return pstmt; + } + + private static void closeAllStatements(Collection collection) { + for (PreparedStatement pstmt : collection) { + close(pstmt); + } + } + + /** + * Gets a Data object from a ResultSet. + * + * @param rs + * ResultSet passed in from a database query + * @return a Data object + * @throws java.sql.SQLException + * @throws UnsupportedEncodingException + */ + private String getStringFromResultSet(ResultSet rs) throws SQLException, UnsupportedEncodingException { + ResultSetMetaData meta = rs.getMetaData(); + + StringBuilder sb = new StringBuilder(); + + int numColumns = meta.getColumnCount(); + for (int i = 1; i <= numColumns; i++) { + sb.append(meta.getColumnName(i)); + if (i==numColumns){ + sb.append('\n'); + } else { + sb.append('\t'); + } + } + + + JMeterVariables jmvars = getThreadContext().getVariables(); + String varnames[] = getVariableNames().split(COMMA); + String resultVariable = getResultVariable().trim(); + List > results = null; + if(resultVariable.length() > 0) { + results = new ArrayList >(); + jmvars.putObject(resultVariable, results); + } + int j = 0; + while (rs.next()) { + Map row = null; + j++; + for (int i = 1; i <= numColumns; i++) { + Object o = rs.getObject(i); + if(results != null) { + if(row == null) { + row = new HashMap(numColumns); + results.add(row); + } + row.put(meta.getColumnName(i), o); + } + if (o instanceof byte[]) { + o = new String((byte[]) o, ENCODING); + } + sb.append(o); + if (i==numColumns){ + sb.append('\n'); + } else { + sb.append('\t'); + } + if (i <= varnames.length) { // i starts at 1 + String name = varnames[i - 1].trim(); + if (name.length()>0){ // Save the value in the variable if present + jmvars.put(name+UNDERSCORE+j, o == null ? null : o.toString()); + } + } + } + } + // Remove any additional values from previous sample + for(int i=0; i < varnames.length; i++){ + String name = varnames[i].trim(); + if (name.length()>0 && jmvars != null){ + final String varCount = name+"_#"; // $NON-NLS-1$ + // Get the previous count + String prevCount = jmvars.get(varCount); + if (prevCount != null){ + int prev = Integer.parseInt(prevCount); + for (int n=j+1; n <= prev; n++ ){ + jmvars.remove(name+UNDERSCORE+n); + } + } + jmvars.put(varCount, Integer.toString(j)); // save the current count + } + } + + return sb.toString(); + } + + public static void close(Connection c) { + try { + if (c != null) { + c.close(); + } + } catch (SQLException e) { + log.warn("Error closing Connection", e); + } + } + + public static void close(Statement s) { + try { + if (s != null) { + s.close(); + } + } catch (SQLException e) { + log.warn("Error closing Statement " + s.toString(), e); + } + } + + public static void close(ResultSet rs) { + try { + if (rs != null) { + rs.close(); + } + } catch (SQLException e) { + log.warn("Error closing ResultSet", e); + } + } + + public String getQuery() { + return query; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(80); + sb.append("["); // $NON-NLS-1$ + sb.append(getQueryType()); + sb.append("] "); // $NON-NLS-1$ + sb.append(getQuery()); + sb.append("\n"); + sb.append(getQueryArguments()); + sb.append("\n"); + sb.append(getQueryArgumentsTypes()); + return sb.toString(); + } + + /** + * @param query + * The query to set. + */ + public void setQuery(String query) { + this.query = query; + } + + /** + * @return Returns the dataSource. + */ + public String getDataSource() { + return dataSource; + } + + /** + * @param dataSource + * The dataSource to set. + */ + public void setDataSource(String dataSource) { + this.dataSource = dataSource; + } + + /** + * @return Returns the queryType. + */ + public String getQueryType() { + return queryType; + } + + /** + * @param queryType The queryType to set. + */ + public void setQueryType(String queryType) { + this.queryType = queryType; + } + + public String getQueryArguments() { + return queryArguments; + } + + public void setQueryArguments(String queryArguments) { + this.queryArguments = queryArguments; + } + + public String getQueryArgumentsTypes() { + return queryArgumentsTypes; + } + + public void setQueryArgumentsTypes(String queryArgumentsType) { + this.queryArgumentsTypes = queryArgumentsType; + } + + /** + * @return the variableNames + */ + public String getVariableNames() { + return variableNames; + } + + /** + * @param variableNames the variableNames to set + */ + public void setVariableNames(String variableNames) { + this.variableNames = variableNames; + } + + /** + * @return the resultVariable + */ + public String getResultVariable() { + return resultVariable ; + } + + /** + * @param resultVariable the variable name in which results will be stored + */ + public void setResultVariable(String resultVariable) { + this.resultVariable = resultVariable; + } + + /** + * {@inheritDoc}} + */ + @Override + public List getSearchableTokens() throws Exception { + List result = super.getSearchableTokens(); + Set properties = new HashSet(); + properties.addAll(Arrays.asList(new String[]{ + "dataSource", + "query", + "queryArguments", + "queryArgumentsTypes", + "queryType", + "resultVariable", + "variableNames" + })); + addPropertiesValues(result, properties); + return result; + } + + /** + * {@inheritDoc} + * @see org.apache.jmeter.testelement.TestListener#testStarted() + */ + public void testStarted() { + testStarted(""); + } + + /** + * {@inheritDoc} + * @see org.apache.jmeter.testelement.TestListener#testStarted(java.lang.String) + */ + public void testStarted(String host) { + cleanCache(); + } + + /** + * {@inheritDoc} + * @see org.apache.jmeter.testelement.TestListener#testEnded() + */ + public void testEnded() { + testEnded(""); + } + + /** + * {@inheritDoc} + * @see org.apache.jmeter.testelement.TestListener#testEnded(java.lang.String) + */ + public void testEnded(String host) { + cleanCache(); + } + + /** + * Clean cache of PreparedStatements + */ + private static final void cleanCache() { + for (Map.Entry> element : perConnCache.entrySet()) { + closeAllStatements(element.getValue().values()); + } + perConnCache.clear(); + } + + /** + * {@inheritDoc} + * @see org.apache.jmeter.testelement.TestListener#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent) + */ + public void testIterationStart(LoopIterationEvent event) { + // NOOP + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/JDBCTestElementBeanInfoSupport.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/JDBCTestElementBeanInfoSupport.java new file mode 100644 index 0000000..667ff3c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/JDBCTestElementBeanInfoSupport.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jdbc; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.TextAreaEditor; + +public abstract class JDBCTestElementBeanInfoSupport extends BeanInfoSupport { + + /** + * + */ + public JDBCTestElementBeanInfoSupport(Class beanClass) { + super(beanClass); + + createPropertyGroup("varName", // $NON-NLS-1$ + new String[]{"dataSource" }); // $NON-NLS-1$ + + createPropertyGroup("sql", // $NON-NLS-1$ + new String[] { + "queryType", // $NON-NLS-1$ + "query", // $NON-NLS-1$ + "queryArguments", // $NON-NLS-1$ + "queryArgumentsTypes", // $NON-NLS-1$ + "variableNames", // $NON-NLS-1$ + "resultVariable", // $NON-NLS-1$ + }); + + PropertyDescriptor p = property("dataSource"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("queryArguments"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("queryArgumentsTypes"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("variableNames"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("resultVariable"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("queryType"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, AbstractJDBCTestElement.SELECT); + p.setValue(NOT_OTHER,Boolean.TRUE); + p.setValue(TAGS,new String[]{ + AbstractJDBCTestElement.SELECT, + AbstractJDBCTestElement.UPDATE, + AbstractJDBCTestElement.CALLABLE, + AbstractJDBCTestElement.PREPARED_SELECT, + AbstractJDBCTestElement.PREPARED_UPDATE, + AbstractJDBCTestElement.COMMIT, + AbstractJDBCTestElement.ROLLBACK, + AbstractJDBCTestElement.AUTOCOMMIT_FALSE, + AbstractJDBCTestElement.AUTOCOMMIT_TRUE, + }); + + p = property("query"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p.setPropertyEditorClass(TextAreaEditor.class); + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/config/DataSourceElement.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/config/DataSourceElement.java new file mode 100644 index 0000000..b85ee44 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/config/DataSourceElement.java @@ -0,0 +1,494 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jmeter.protocol.jdbc.config; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.avalon.excalibur.datasource.DataSourceComponent; +import org.apache.avalon.excalibur.datasource.ResourceLimitingJdbcDataSource; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.avalon.framework.configuration.DefaultConfiguration; +import org.apache.avalon.framework.logger.LogKitLogger; +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.TestBeanHelper; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class DataSourceElement extends AbstractTestElement + implements ConfigElement, TestListener, TestBean + { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + private transient String dataSource, driver, dbUrl, username, password, checkQuery, poolMax, connectionAge, timeout, + trimInterval,transactionIsolation; + + private transient boolean keepAlive, autocommit; + + /* + * The datasource is set up by testStarted and cleared by testEnded. + * These are called from different threads, so access must be synchronized. + * The same instance is called in each case. + */ + private transient ResourceLimitingJdbcDataSource excaliburSource; + + // Keep a record of the pre-thread pools so that they can be disposed of at the end of a test + private transient Set perThreadPoolSet; + + public DataSourceElement() { + } + + public void testEnded() { + synchronized (this) { + if (excaliburSource != null) { + excaliburSource.dispose(); + } + excaliburSource = null; + } + if (perThreadPoolSet != null) {// in case + for(ResourceLimitingJdbcDataSource dsc : perThreadPoolSet){ + log.debug("Disposing pool: "+dsc.getInstrumentableName()+" @"+System.identityHashCode(dsc)); + dsc.dispose(); + } + perThreadPoolSet=null; + } + } + + public void testEnded(String host) { + testEnded(); + } + + public void testIterationStart(LoopIterationEvent event) { + } + + @SuppressWarnings("deprecation") // call to TestBeanHelper.prepare() is intentional + public void testStarted() { + this.setRunningVersion(true); + TestBeanHelper.prepare(this); + JMeterVariables variables = getThreadContext().getVariables(); + String poolName = getDataSource(); + if (variables.getObject(poolName) != null) { + log.error("JDBC data source already defined for: "+poolName); + } else { + String maxPool = getPoolMax(); + perThreadPoolSet = Collections.synchronizedSet(new HashSet()); + if (maxPool.equals("0")){ // i.e. if we want per thread pooling + variables.putObject(poolName, new DataSourceComponentImpl()); // pool will be created later + } else { + ResourceLimitingJdbcDataSource src=initPool(maxPool); + synchronized(this){ + excaliburSource = src; + variables.putObject(poolName, new DataSourceComponentImpl(excaliburSource)); + } + } + } + } + + public void testStarted(String host) { + testStarted(); + } + + @Override + public Object clone() { + DataSourceElement el = (DataSourceElement) super.clone(); + el.excaliburSource = excaliburSource; + el.perThreadPoolSet = perThreadPoolSet; + return el; + } + + /* + * Utility routine to get the connection from the pool. + * Purpose: + * - allows JDBCSampler to be entirely independent of the pooling classes + * - allows the pool storage mechanism to be changed if necessary + */ + public static Connection getConnection(String poolName) throws SQLException{ + DataSourceComponent pool = (DataSourceComponent) + JMeterContextService.getContext().getVariables().getObject(poolName); + if (pool == null) { + throw new SQLException("No pool found named: '" + poolName + "'"); + } + return pool.getConnection(); + } + + /* + * Set up the DataSource - maxPool is a parameter, so the same code can + * also be used for setting up the per-thread pools. + */ + private ResourceLimitingJdbcDataSource initPool(String maxPool) { + ResourceLimitingJdbcDataSource source = null; + source = new ResourceLimitingJdbcDataSource(); + DefaultConfiguration config = new DefaultConfiguration("rl-jdbc"); // $NON-NLS-1$ + + if (log.isDebugEnabled()) { + StringBuilder sb = new StringBuilder(40); + sb.append("MaxPool: "); + sb.append(maxPool); + sb.append(" Timeout: "); + sb.append(getTimeout()); + sb.append(" TrimInt: "); + sb.append(getTrimInterval()); + sb.append(" Auto-Commit: "); + sb.append(isAutocommit()); + log.debug(sb.toString()); + } + DefaultConfiguration poolController = new DefaultConfiguration("pool-controller"); // $NON-NLS-1$ + poolController.setAttribute("max", maxPool); // $NON-NLS-1$ + poolController.setAttribute("max-strict", "true"); // $NON-NLS-1$ $NON-NLS-2$ + poolController.setAttribute("blocking", "true"); // $NON-NLS-1$ $NON-NLS-2$ + poolController.setAttribute("timeout", getTimeout()); // $NON-NLS-1$ + poolController.setAttribute("trim-interval", getTrimInterval()); // $NON-NLS-1$ + config.addChild(poolController); + + DefaultConfiguration autoCommit = new DefaultConfiguration("auto-commit"); // $NON-NLS-1$ + autoCommit.setValue(String.valueOf(isAutocommit())); + config.addChild(autoCommit); + + if (log.isDebugEnabled()) { + StringBuilder sb = new StringBuilder(40); + sb.append("KeepAlive: "); + sb.append(isKeepAlive()); + sb.append(" Age: "); + sb.append(getConnectionAge()); + sb.append(" CheckQuery: "); + sb.append(getCheckQuery()); + log.debug(sb.toString()); + } + DefaultConfiguration cfgKeepAlive = new DefaultConfiguration("keep-alive"); // $NON-NLS-1$ + cfgKeepAlive.setAttribute("disable", String.valueOf(!isKeepAlive())); // $NON-NLS-1$ + cfgKeepAlive.setAttribute("age", getConnectionAge()); // $NON-NLS-1$ + cfgKeepAlive.setValue(getCheckQuery()); + poolController.addChild(cfgKeepAlive); + + String _username = getUsername(); + if (log.isDebugEnabled()) { + StringBuilder sb = new StringBuilder(40); + sb.append("Driver: "); + sb.append(getDriver()); + sb.append(" DbUrl: "); + sb.append(getDbUrl()); + sb.append(" User: "); + sb.append(_username); + log.debug(sb.toString()); + } + DefaultConfiguration cfgDriver = new DefaultConfiguration("driver"); // $NON-NLS-1$ + cfgDriver.setValue(getDriver()); + config.addChild(cfgDriver); + DefaultConfiguration cfgDbUrl = new DefaultConfiguration("dburl"); // $NON-NLS-1$ + cfgDbUrl.setValue(getDbUrl()); + config.addChild(cfgDbUrl); + + if (_username.length() > 0){ + DefaultConfiguration cfgUsername = new DefaultConfiguration("user"); // $NON-NLS-1$ + cfgUsername.setValue(_username); + config.addChild(cfgUsername); + DefaultConfiguration cfgPassword = new DefaultConfiguration("password"); // $NON-NLS-1$ + cfgPassword.setValue(getPassword()); + config.addChild(cfgPassword); + } + + // log is required to ensure errors are available + source.enableLogging(new LogKitLogger(log)); + try { + source.configure(config); + source.setInstrumentableName(getDataSource()); + } catch (ConfigurationException e) { + log.error("Could not configure datasource for pool: "+getDataSource(),e); + } + return source; + } + + // used to hold per-thread singleton connection pools + private static final ThreadLocal> perThreadPoolMap = + new ThreadLocal>(){ + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + /* + * Wrapper class to allow getConnection() to be implemented for both shared + * and per-thread pools. + * + */ + private class DataSourceComponentImpl implements DataSourceComponent{ + + private final ResourceLimitingJdbcDataSource sharedDSC; + + DataSourceComponentImpl(){ + sharedDSC=null; + } + + DataSourceComponentImpl(ResourceLimitingJdbcDataSource p_dsc){ + sharedDSC=p_dsc; + } + + public Connection getConnection() throws SQLException { + Connection conn = null; + ResourceLimitingJdbcDataSource dsc = null; + if (sharedDSC != null){ // i.e. shared pool + dsc = sharedDSC; + } else { + Map poolMap = perThreadPoolMap.get(); + dsc = poolMap.get(getDataSource()); + if (dsc == null){ + dsc = initPool("1"); + poolMap.put(getDataSource(),dsc); + log.debug("Storing pool: "+dsc.getInstrumentableName()+" @"+System.identityHashCode(dsc)); + perThreadPoolSet.add(dsc); + } + } + if (dsc != null) { + conn=dsc.getConnection(); + int transactionIsolation = DataSourceElementBeanInfo.getTransactionIsolationMode(getTransactionIsolation()); + if (transactionIsolation >= 0 && conn.getTransactionIsolation() != transactionIsolation) { + try { + // make sure setting the new isolation mode is done in an auto committed transaction + conn.setTransactionIsolation(transactionIsolation); + log.debug("Setting transaction isolation: " + transactionIsolation + " @" + + System.identityHashCode(dsc)); + } catch (SQLException ex) { + log.error("Could not set transaction isolation: " + transactionIsolation + " @" + + System.identityHashCode(dsc)); + } + } + } + return conn; + } + + public void configure(Configuration arg0) throws ConfigurationException { + } + + } + + public void addConfigElement(ConfigElement config) { + } + + public boolean expectsModification() { + return false; + } + + /** + * @return Returns the checkQuery. + */ + public String getCheckQuery() { + return checkQuery; + } + + /** + * @param checkQuery + * The checkQuery to set. + */ + public void setCheckQuery(String checkQuery) { + this.checkQuery = checkQuery; + } + + /** + * @return Returns the connectionAge. + */ + public String getConnectionAge() { + return connectionAge; + } + + /** + * @param connectionAge + * The connectionAge to set. + */ + public void setConnectionAge(String connectionAge) { + this.connectionAge = connectionAge; + } + + /** + * @return Returns the poolname. + */ + public String getDataSource() { + return dataSource; + } + + /** + * @param dataSource + * The poolname to set. + */ + public void setDataSource(String dataSource) { + this.dataSource = dataSource; + } + + /** + * @return Returns the dbUrl. + */ + public String getDbUrl() { + return dbUrl; + } + + /** + * @param dbUrl + * The dbUrl to set. + */ + public void setDbUrl(String dbUrl) { + this.dbUrl = dbUrl; + } + + /** + * @return Returns the driver. + */ + public String getDriver() { + return driver; + } + + /** + * @param driver + * The driver to set. + */ + public void setDriver(String driver) { + this.driver = driver; + } + + /** + * @return Returns the password. + */ + public String getPassword() { + return password; + } + + /** + * @param password + * The password to set. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * @return Returns the poolMax. + */ + public String getPoolMax() { + return poolMax; + } + + /** + * @param poolMax + * The poolMax to set. + */ + public void setPoolMax(String poolMax) { + this.poolMax = poolMax; + } + + /** + * @return Returns the timeout. + */ + public String getTimeout() { + return timeout; + } + + /** + * @param timeout + * The timeout to set. + */ + public void setTimeout(String timeout) { + this.timeout = timeout; + } + + /** + * @return Returns the trimInterval. + */ + public String getTrimInterval() { + return trimInterval; + } + + /** + * @param trimInterval + * The trimInterval to set. + */ + public void setTrimInterval(String trimInterval) { + this.trimInterval = trimInterval; + } + + /** + * @return Returns the username. + */ + public String getUsername() { + return username; + } + + /** + * @param username + * The username to set. + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * @return Returns the autocommit. + */ + public boolean isAutocommit() { + return autocommit; + } + + /** + * @param autocommit + * The autocommit to set. + */ + public void setAutocommit(boolean autocommit) { + this.autocommit = autocommit; + } + + /** + * @return Returns the keepAlive. + */ + public boolean isKeepAlive() { + return keepAlive; + } + + /** + * @param keepAlive + * The keepAlive to set. + */ + public void setKeepAlive(boolean keepAlive) { + this.keepAlive = keepAlive; + } + + /** + * @return the transaction isolation level + */ + public String getTransactionIsolation() { + return transactionIsolation; + } + + /** + * @param transactionIsolation The transaction isolation level to set. NULL to + * use the default of the driver. + */ + public void setTransactionIsolation(String transactionIsolation) { + this.transactionIsolation = transactionIsolation; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/config/DataSourceElementBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/config/DataSourceElementBeanInfo.java new file mode 100644 index 0000000..eeb4c20 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/config/DataSourceElementBeanInfo.java @@ -0,0 +1,123 @@ +/* + * 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. + * + */ + +/* + * Created on May 15, 2004 + */ +package org.apache.jmeter.protocol.jdbc.config; + +import java.beans.PropertyDescriptor; +import java.sql.Connection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.TypeEditor; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class DataSourceElementBeanInfo extends BeanInfoSupport { + private static final Logger log = LoggingManager.getLoggerForClass(); + private static Map TRANSACTION_ISOLATION_MAP = new HashMap(5); + static { + // Will use default isolation + TRANSACTION_ISOLATION_MAP.put("DEFAULT", Integer.valueOf(-1)); + TRANSACTION_ISOLATION_MAP.put("TRANSACTION_NONE", Integer.valueOf(Connection.TRANSACTION_NONE)); + TRANSACTION_ISOLATION_MAP.put("TRANSACTION_READ_COMMITTED", Integer.valueOf(Connection.TRANSACTION_READ_COMMITTED)); + TRANSACTION_ISOLATION_MAP.put("TRANSACTION_READ_UNCOMMITTED", Integer.valueOf(Connection.TRANSACTION_READ_UNCOMMITTED)); + TRANSACTION_ISOLATION_MAP.put("TRANSACTION_REPEATABLE_READ", Integer.valueOf(Connection.TRANSACTION_REPEATABLE_READ)); + TRANSACTION_ISOLATION_MAP.put("TRANSACTION_SERIALIZABLE", Integer.valueOf(Connection.TRANSACTION_SERIALIZABLE)); + } + + public DataSourceElementBeanInfo() { + super(DataSourceElement.class); + + createPropertyGroup("varName", new String[] { "dataSource" }); + + createPropertyGroup("pool", new String[] { "poolMax", "timeout", + "trimInterval", "autocommit", "transactionIsolation" }); + + createPropertyGroup("keep-alive", new String[] { "keepAlive", "connectionAge", "checkQuery" }); + + createPropertyGroup("database", new String[] { "dbUrl", "driver", "username", "password" }); + + PropertyDescriptor p = property("dataSource"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("poolMax"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "10"); + p = property("timeout"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "10000"); + p = property("trimInterval"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "60000"); + p = property("autocommit"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + p = property("transactionIsolation"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "DEFAULT"); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + Set modesSet = TRANSACTION_ISOLATION_MAP.keySet(); + String[] modes = modesSet.toArray(new String[modesSet.size()]); + p.setValue(TAGS, modes); + p = property("keepAlive"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + p = property("connectionAge"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "5000"); + p = property("checkQuery"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, "Select 1"); + p = property("dbUrl"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("driver"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("username"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p = property("password", TypeEditor.PasswordEditor); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + } + + /** + * @param tag + * @return int value for String + */ + public static int getTransactionIsolationMode(String tag) { + if (!StringUtils.isEmpty(tag)) { + Integer isolationMode = TRANSACTION_ISOLATION_MAP.get(tag); + if (isolationMode == null) { + try { + return Integer.parseInt(tag); + } catch (NumberFormatException e) { + log.warn("Illegal transaction isolation configuration '" + tag + "'"); + } + } + } + return -1; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/AbstractJDBCProcessor.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/AbstractJDBCProcessor.java new file mode 100644 index 0000000..8f14cbd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/AbstractJDBCProcessor.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jdbc.processor; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; + +import org.apache.jmeter.protocol.jdbc.AbstractJDBCTestElement; +import org.apache.jmeter.protocol.jdbc.config.DataSourceElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * As pre- and post-processors essentially do the same this class provides the implmentation. + */ +public abstract class AbstractJDBCProcessor extends AbstractJDBCTestElement { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + /** + * Calls the JDBC code to be executed. + */ + protected void process() { + Connection conn = null; + try { + conn = DataSourceElement.getConnection(getDataSource()); + execute(conn); + } catch (SQLException ex) { + log.warn("SQL Problem in "+ getName() + ": " + ex.toString()); + } catch (IOException ex) { + log.warn("IO Problem in "+ getName() + ": " + ex.toString()); + } catch (UnsupportedOperationException ex) { + log.warn("Execution Problem in "+ getName() + ": " + ex.toString()); + } finally { + close(conn); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessor.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessor.java new file mode 100644 index 0000000..bb066fc --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessor.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.jmeter.protocol.jdbc.processor; + +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.testbeans.TestBean; + +/** + * Post processor handling JDBC Requests + */ +public class JDBCPostProcessor extends AbstractJDBCProcessor implements TestBean, PostProcessor { + + private static final long serialVersionUID = 1L; + + @Override + public void process() { + super.process(); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorBeanInfo.java new file mode 100644 index 0000000..44c561b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorBeanInfo.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. + * + */ + +/* + * Created on May 16, 2004 + * + */ +package org.apache.jmeter.protocol.jdbc.processor; + +import org.apache.jmeter.protocol.jdbc.JDBCTestElementBeanInfoSupport; + + +public class JDBCPostProcessorBeanInfo extends JDBCTestElementBeanInfoSupport { + + /** + * + */ + public JDBCPostProcessorBeanInfo() { + super(JDBCPostProcessor.class); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessor.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessor.java new file mode 100644 index 0000000..bf01b05 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessor.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.jmeter.protocol.jdbc.processor; + +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.testbeans.TestBean; + +/** + * Preprocessor handling JDBC Requests + */ +public class JDBCPreProcessor extends AbstractJDBCProcessor implements TestBean, PreProcessor { + + private static final long serialVersionUID = 1L; + + @Override + public void process() { + super.process(); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorBeanInfo.java new file mode 100644 index 0000000..e42f4bb --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorBeanInfo.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. + * + */ + +/* + * Created on May 16, 2004 + * + */ +package org.apache.jmeter.protocol.jdbc.processor; + +import org.apache.jmeter.protocol.jdbc.JDBCTestElementBeanInfoSupport; + + +public class JDBCPreProcessorBeanInfo extends JDBCTestElementBeanInfoSupport { + + /** + * + */ + public JDBCPreProcessorBeanInfo() { + super(JDBCPreProcessor.class); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/sampler/JDBCSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/sampler/JDBCSampler.java new file mode 100644 index 0000000..e10ad80 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/sampler/JDBCSampler.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jdbc.sampler; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.protocol.jdbc.AbstractJDBCTestElement; +import org.apache.jmeter.protocol.jdbc.config.DataSourceElement; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A sampler which understands JDBC database requests. + * + */ +public class JDBCSampler extends AbstractJDBCTestElement implements Sampler, TestBean, ConfigMergabilityIndicator { + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private static final long serialVersionUID = 234L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Creates a JDBCSampler. + */ + public JDBCSampler() { + } + + public SampleResult sample(Entry e) { + log.debug("sampling jdbc"); + + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.setSamplerData(toString()); + res.setDataType(SampleResult.TEXT); + res.setContentType("text/plain"); // $NON-NLS-1$ + res.setDataEncoding(ENCODING); + + // Assume we will be successful + res.setSuccessful(true); + res.setResponseMessageOK(); + res.setResponseCodeOK(); + + + res.sampleStart(); + Connection conn = null; + + try { + + try { + conn = DataSourceElement.getConnection(getDataSource()); + } finally { + res.latencyEnd(); // use latency to measure connection time + } + res.setResponseHeaders(conn.toString()); + res.setResponseData(execute(conn)); + } catch (SQLException ex) { + final String errCode = Integer.toString(ex.getErrorCode()); + res.setResponseMessage(ex.toString()); + res.setResponseCode(ex.getSQLState()+ " " +errCode); + res.setResponseData(ex.getMessage().getBytes()); + res.setSuccessful(false); + } catch (Exception ex) { + res.setResponseMessage(ex.toString()); + res.setResponseCode("000"); + res.setResponseData(ex.getMessage().getBytes()); + res.setSuccessful(false); + } finally { + close(conn); + } + + // TODO: process warnings? Set Code and Message to success? + res.sampleEnd(); + return res; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerBeanInfo.java new file mode 100644 index 0000000..10e23a5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerBeanInfo.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. + * + */ + +/* + * Created on May 16, 2004 + * + */ +package org.apache.jmeter.protocol.jdbc.sampler; + +import org.apache.jmeter.protocol.jdbc.JDBCTestElementBeanInfoSupport; + + +public class JDBCSamplerBeanInfo extends JDBCTestElementBeanInfoSupport { + + /** + * + */ + public JDBCSamplerBeanInfo() { + super(JDBCSampler.class); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/Utils.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/Utils.java new file mode 100644 index 0000000..d61d25a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/Utils.java @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms; + +import java.util.Enumeration; +import java.util.Map; + +import javax.jms.Connection; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.naming.Context; +import javax.naming.NamingException; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Utility methods for JMS protocol. + * WARNING - the API for this class is likely to change! + */ +public final class Utils { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static void close(MessageConsumer closeable, Logger log){ + if (closeable != null){ + try { + closeable.close(); + } catch (JMSException e) { + log.error("Error during close: ", e); + } + } + } + + public static void close(Session closeable, Logger log) { + if (closeable != null){ + try { + closeable.close(); + } catch (JMSException e) { + log.error("Error during close: ", e); + } + } + } + + public static void close(Connection closeable, Logger log) { + if (closeable != null){ + try { + closeable.close(); + } catch (JMSException e) { + log.error("Error during close: ", e); + } + } + } + + public static void close(MessageProducer closeable, Logger log) { + if (closeable != null){ + try { + closeable.close(); + } catch (JMSException e) { + log.error("Error during close: ", e); + } + } + } + + public static String messageProperties(Message msg){ + return messageProperties(new StringBuilder(), msg).toString(); + } + + public static StringBuilder messageProperties(StringBuilder sb, Message msg){ + requestHeaders(sb, msg); + sb.append("Properties:\n"); + Enumeration rme; + try { + rme = msg.getPropertyNames(); + while(rme.hasMoreElements()){ + String name=(String) rme.nextElement(); + sb.append(name).append('\t'); + String value=msg.getStringProperty(name); + sb.append(value).append('\n'); + } + } catch (JMSException e) { + sb.append("\nError: "+e.toString()); + } + return sb; + } + + public static StringBuilder requestHeaders(StringBuilder sb, Message msg){ + try { + sb.append("JMSCorrelationId ").append(msg.getJMSCorrelationID()).append('\n'); + sb.append("JMSMessageId ").append(msg.getJMSMessageID()).append('\n'); + sb.append("JMSTimestamp ").append(msg.getJMSTimestamp()).append('\n'); + sb.append("JMSType ").append(msg.getJMSType()).append('\n'); + sb.append("JMSExpiration ").append(msg.getJMSExpiration()).append('\n'); + sb.append("JMSPriority ").append(msg.getJMSPriority()).append('\n'); + sb.append("JMSDestination ").append(msg.getJMSDestination()).append('\n'); + } catch (JMSException e) { + sb.append("\nError: "+e.toString()); + } + return sb; + } + + /** + * Method will lookup a given destination (topic/queue) using JNDI. + * + * @param context + * @param name the destination name + * @return the destination, never null + * @throws NamingException if the name cannot be found as a Destination + */ + public static Destination lookupDestination(Context context, String name) throws NamingException { + Object o = context.lookup(name); + if (o instanceof Destination){ + return (Destination) o; + } + throw new NamingException("Found: "+name+"; expected Destination, but was: "+o.getClass().getName()); + } + + /** + * Obtain the queue connection from the context and factory name. + * + * @param ctx + * @param factoryName + * @return the queue connection + * @throws JMSException + * @throws NamingException + */ + public static Connection getConnection(Context ctx, String factoryName) throws JMSException, NamingException { + Object objfac = null; + try { + objfac = ctx.lookup(factoryName); + } catch (NoClassDefFoundError e) { + throw new NamingException("Lookup failed: "+e.toString()); + } + if (objfac instanceof javax.jms.ConnectionFactory) { + @SuppressWarnings("unchecked") // The environment is supposed to use String keys only + Map env = (Map)ctx.getEnvironment(); + if(env.containsKey(Context.SECURITY_PRINCIPAL)) { + String username = (String)env.get(Context.SECURITY_PRINCIPAL); + String password = (String)env.get(Context.SECURITY_CREDENTIALS); + return ((javax.jms.ConnectionFactory) objfac).createConnection(username, password); + } + else { + return ((javax.jms.ConnectionFactory) objfac).createConnection(); + } + } + throw new NamingException("Expected javax.jms.ConnectionFactory, found "+objfac.getClass().getName()); + } + + /** + * Set JMS Properties to msg + * @param msg Message + * @param map Map + * @throws JMSException + */ + public static void addJMSProperties(Message msg, Map map) throws JMSException { + if(map == null) { + return; + } + for (Map.Entry me : map.entrySet()) { + String name = me.getKey(); + String value = me.getValue(); + if (log.isDebugEnabled()) { + log.debug("Adding property [" + name + "=" + value + "]"); + } + + // WebsphereMQ does not allow corr. id. to be set using setStringProperty() + if("JMSCorrelationID".equalsIgnoreCase(name)) { // $NON-NLS-1$ + msg.setJMSCorrelationID(value); + } else { + msg.setStringProperty(name, value); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/ClientPool.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/ClientPool.java new file mode 100644 index 0000000..8f486c5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/ClientPool.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.protocol.jms.client; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + * ClientPool holds the client instances in an ArrayList. The main purpose of + * this is to make it easier to clean up all the instances at the end of a test. + * If we didn't do this, threads might become zombie. + * + * N.B. This class needs to be fully synchronized as it is called from sample threads + * and the thread that runs testEnded() methods. + */ +public class ClientPool { + + //GuardedBy("this") + private static final ArrayList clients = new ArrayList(); + + //GuardedBy("this") + private static final Map client_map = new ConcurrentHashMap(); + + /** + * Add a ReceiveClient to the ClientPool. This is so that we can make sure + * to close all clients and make sure all threads are destroyed. + * + * @param client + */ + public static synchronized void addClient(Closeable client) { + clients.add(client); + } + + /** + * Clear all the clients created by either Publish or Subscribe sampler. We + * need to do this to make sure all the threads creatd during the test are + * destroyed and cleaned up. In some cases, the client provided by the + * manufacturer of the JMS server may have bugs and some threads may become + * zombie. In those cases, it is not the responsibility of JMeter for those + * bugs. + */ + public static synchronized void clearClient() { + for (Closeable client : clients) { + try { + client.close(); + } catch (IOException e) { + // Ignored + } + client = null; + } + clients.clear(); + client_map.clear(); + } + + // TODO Method with 0 reference, really useful ? + public static void put(Object key, Object client) { + client_map.put(key, client); + } + + // TODO Method with 0 reference, really useful ? + public static Object get(Object key) { + return client_map.get(key); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/InitialContextFactory.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/InitialContextFactory.java new file mode 100644 index 0000000..87322a4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/InitialContextFactory.java @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms.client; + +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.apache.commons.lang.StringUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * InitialContextFactory is responsible for getting an instance of the initial context. + */ +public class InitialContextFactory { + + private static final ConcurrentHashMap MAP = new ConcurrentHashMap(); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Look up the context from the local cache, creating it if necessary. + * + * @param initialContextFactory used to set the property {@link Context#INITIAL_CONTEXT_FACTORY} + * @param providerUrl used to set the property {@link Context#PROVIDER_URL} + * @param useAuth set true if security is to be used. + * @param securityPrincipal used to set the property {@link Context#SECURITY_PRINCIPAL} + * @param securityCredentials used to set the property {@link Context#SECURITY_CREDENTIALS} + * @return the context, never null + * @throws NamingException + */ + public static Context lookupContext(String initialContextFactory, + String providerUrl, boolean useAuth, String securityPrincipal, String securityCredentials) throws NamingException { + String cacheKey = createKey(Thread.currentThread().getId(),initialContextFactory ,providerUrl, securityPrincipal, securityCredentials); + Context ctx = MAP.get(cacheKey); + if (ctx == null) { + Properties props = new Properties(); + props.setProperty(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory); + props.setProperty(Context.PROVIDER_URL, providerUrl); + if (useAuth && securityPrincipal != null && securityCredentials != null + && securityPrincipal.length() > 0 && securityCredentials.length() > 0) { + props.setProperty(Context.SECURITY_PRINCIPAL, securityPrincipal); + props.setProperty(Context.SECURITY_CREDENTIALS, securityCredentials); + log.info("authentication properties set"); + } + try { + ctx = new InitialContext(props); + } catch (NoClassDefFoundError e){ + throw new NamingException(e.toString()); + } catch (Exception e) { + throw new NamingException(e.toString()); + } + // we want to return the context that is actually in the map + // if it's the first put we will have a null result + Context oldCtx = MAP.putIfAbsent(cacheKey, ctx); + if(oldCtx != null) { + // There was an object in map, destroy the temporary and return one in map (oldCtx) + try { + ctx.close(); + } catch (Exception e) { + // NOOP + } + ctx = oldCtx; + } + // else No object in Map, ctx is the one + } + return ctx; + } + + /** + * Create cache key + * @param threadId Thread Id + * @param initialContextFactory + * @param providerUrl + * @param securityPrincipal + * @param securityCredentials + * @return + */ + private static String createKey( + long threadId, + String initialContextFactory, + String providerUrl, String securityPrincipal, + String securityCredentials) { + StringBuilder builder = new StringBuilder(); + builder.append(threadId); + builder.append("#"); + builder.append(initialContextFactory); + builder.append("#"); + builder.append(providerUrl); + builder.append("#"); + if(!StringUtils.isEmpty(securityPrincipal)) { + builder.append(securityPrincipal); + builder.append("#"); + } + if(!StringUtils.isEmpty(securityCredentials)) { + builder.append(securityCredentials); + } + return builder.toString(); + } + + /** + * Initialize the JNDI initial context + * + * @param useProps if true, create a new InitialContext; otherwise use the other parameters to call + * {@link #lookupContext(String, String, boolean, String, String)} + * @param initialContextFactory + * @param providerUrl + * @param useAuth + * @param securityPrincipal + * @param securityCredentials + * @return the context, never null + * @throws NamingException + */ + public static Context getContext(boolean useProps, + String initialContextFactory, String providerUrl, + boolean useAuth, String securityPrincipal, String securityCredentials) throws NamingException { + if (useProps) { + try { + return new InitialContext(); + } catch (NoClassDefFoundError e){ + throw new NamingException(e.toString()); + } catch (Exception e) { + throw new NamingException(e.toString()); + } + } else { + return lookupContext(initialContextFactory, providerUrl, useAuth, securityPrincipal, securityCredentials); + } + } + + /** + * clear all the InitialContext objects. + */ + public static void close() { + for (Context ctx : MAP.values()) { + try { + ctx.close(); + } catch (NamingException e) { + log.error(e.getMessage()); + } + } + MAP.clear(); + log.info("InitialContextFactory.close() called and Context instances cleaned up"); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/Publisher.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/Publisher.java new file mode 100644 index 0000000..916dc19 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/Publisher.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms.client; + +import java.io.Closeable; +import java.io.Serializable; +import java.util.Map; +import java.util.Map.Entry; + +import javax.jms.Connection; +import javax.jms.DeliveryMode; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.MapMessage; +import javax.jms.MessageProducer; +import javax.jms.ObjectMessage; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.naming.Context; +import javax.naming.NamingException; + +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class Publisher implements Closeable { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Connection connection; + + private final Session session; + + private final MessageProducer producer; + + private final Context ctx; + + private final boolean staticDest; + + + /** + * Create a publisher using either the jndi.properties file or the provided parameters. + * Uses a static destination and persistent messages(for backward compatibility) + * + * @param useProps true if a jndi.properties file is to be used + * @param initialContextFactory the (ignored if useProps is true) + * @param providerUrl (ignored if useProps is true) + * @param connfactory + * @param destinationName + * @param useAuth (ignored if useProps is true) + * @param securityPrincipal (ignored if useProps is true) + * @param securityCredentials (ignored if useProps is true) + * @throws JMSException if the context could not be initialised, or there was some other error + * @throws NamingException + */ + public Publisher(boolean useProps, String initialContextFactory, String providerUrl, + String connfactory, String destinationName, boolean useAuth, + String securityPrincipal, String securityCredentials) throws JMSException, NamingException { + this(useProps, initialContextFactory, providerUrl, connfactory, + destinationName, useAuth, securityPrincipal, + securityCredentials, true, false); + } + + /** + * Create a publisher using either the jndi.properties file or the provided parameters. + * Uses a static destination (for backward compatibility) + * + * @param useProps true if a jndi.properties file is to be used + * @param initialContextFactory the (ignored if useProps is true) + * @param providerUrl (ignored if useProps is true) + * @param connfactory + * @param destinationName + * @param useAuth (ignored if useProps is true) + * @param securityPrincipal (ignored if useProps is true) + * @param securityCredentials (ignored if useProps is true) + * @param useNonPersistentMessages Flag Delivery Mode as Non persistent if true + * @throws JMSException if the context could not be initialised, or there was some other error + * @throws NamingException + */ + public Publisher(boolean useProps, String initialContextFactory, String providerUrl, + String connfactory, String destinationName, boolean useAuth, + String securityPrincipal, String securityCredentials, boolean useNonPersistentMessages) throws JMSException, NamingException { + this(useProps, initialContextFactory, providerUrl, connfactory, + destinationName, useAuth, securityPrincipal, + securityCredentials, true, useNonPersistentMessages); + } + + /** + * Create a publisher using either the jndi.properties file or the provided parameters + * @param useProps true if a jndi.properties file is to be used + * @param initialContextFactory the (ignored if useProps is true) + * @param providerUrl (ignored if useProps is true) + * @param connfactory + * @param destinationName + * @param useAuth (ignored if useProps is true) + * @param securityPrincipal (ignored if useProps is true) + * @param securityCredentials (ignored if useProps is true) + * @param staticDestination true is the destination is not to change between loops + * @param useNonPersistentMessages Flag Delivery Mode as Non persistent if true + * @throws JMSException if the context could not be initialised, or there was some other error + * @throws NamingException + */ + public Publisher(boolean useProps, String initialContextFactory, String providerUrl, + String connfactory, String destinationName, boolean useAuth, + String securityPrincipal, String securityCredentials, + boolean staticDestination, boolean useNonPersistentMessages) throws JMSException, NamingException { + super(); + boolean initSuccess = false; + try{ + ctx = InitialContextFactory.getContext(useProps, initialContextFactory, + providerUrl, useAuth, securityPrincipal, securityCredentials); + connection = Utils.getConnection(ctx, connfactory); + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + staticDest = staticDestination; + if (staticDest) { + Destination dest = Utils.lookupDestination(ctx, destinationName); + producer = session.createProducer(dest); + } else { + producer = session.createProducer(null); + } + if(useNonPersistentMessages) { + producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); + } + initSuccess = true; + } finally { + if(!initSuccess) { + close(); + } + } + } + + public TextMessage publish(String text) throws JMSException, + NamingException { + return publish(text, null, null); + } + + public TextMessage publish(String text, String destinationName) + throws JMSException, NamingException { + return publish(text, destinationName, null); + } + + public TextMessage publish(String text, String destinationName, Map properties) + throws JMSException, NamingException { + TextMessage msg = session.createTextMessage(text); + Utils.addJMSProperties(msg, properties); + if (staticDest || destinationName == null) { + producer.send(msg); + } else { + Destination dest = Utils.lookupDestination(ctx, destinationName); + producer.send(dest, msg); + } + return msg; + } + + public ObjectMessage publish(Serializable contents) throws JMSException, + NamingException { + return publish(contents, null); + } + + public ObjectMessage publish(Serializable contents, String destinationName) + throws JMSException, NamingException { + return publish(contents, destinationName, null); + } + + public ObjectMessage publish(Serializable contents, String destinationName, Map properties) + throws JMSException, NamingException { + ObjectMessage msg = session.createObjectMessage(contents); + Utils.addJMSProperties(msg, properties); + if (staticDest || destinationName == null) { + producer.send(msg); + } else { + Destination dest = Utils.lookupDestination(ctx, destinationName); + producer.send(dest, msg); + } + return msg; + } + + public MapMessage publish(Map map) throws JMSException, + NamingException { + return publish(map, null, null); + } + + public MapMessage publish(Map map, String destinationName) + throws JMSException, NamingException { + return publish(map, destinationName, null); + } + + public MapMessage publish(Map map, String destinationName, Map properties) + throws JMSException, NamingException { + MapMessage msg = session.createMapMessage(); + Utils.addJMSProperties(msg, properties); + for (Entry me : map.entrySet()) { + msg.setObject(me.getKey(), me.getValue()); + } + if (staticDest || destinationName == null) { + producer.send(msg); + } else { + Destination dest = Utils.lookupDestination(ctx, destinationName); + producer.send(dest, msg); + } + return msg; + } + + /** + * Close will close the session + */ + public void close() { + Utils.close(producer, log); + Utils.close(session, log); + Utils.close(connection, log); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/ReceiveSubscriber.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/ReceiveSubscriber.java new file mode 100644 index 0000000..56239b8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/client/ReceiveSubscriber.java @@ -0,0 +1,305 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms.client; + +import java.io.Closeable; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import javax.jms.Connection; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.Session; +import javax.jms.Topic; +import javax.naming.Context; +import javax.naming.NamingException; + +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Generic MessageConsumer class, which has two possible strategies. + *
    + *
  • Use MessageConsumer.receive(timeout) to fetch messages.
  • + *
  • Use MessageListener.onMessage() to cache messages in a local queue.
  • + *
+ * In both cases, the {@link #getMessage(long)} method is used to return the next message, + * either directly using receive(timeout) or from the queue using poll(timeout). + */ +public class ReceiveSubscriber implements Closeable, MessageListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Connection CONN; + + private final Session SESSION; + + private final MessageConsumer SUBSCRIBER; + + /* + * We use a LinkedBlockingQueue (rather than a ConcurrentLinkedQueue) because it has a + * poll-with-wait method that avoids the need to use a polling loop. + */ + private final LinkedBlockingQueue queue; + + /** + * Constructor takes the necessary JNDI related parameters to create a + * connection and prepare to begin receiving messages. + *
+ * The caller must then invoke {@link #start()} to enable message reception. + * + * @param useProps if true, use jndi.properties instead of + * initialContextFactory, providerUrl, securityPrincipal, securityCredentials + * @param initialContextFactory + * @param providerUrl + * @param connfactory + * @param destinationName + * @param durableSubscriptionId + * @param clientId + * @param jmsSelector Message Selector + * @param useAuth + * @param securityPrincipal + * @param securityCredentials + * @throws JMSException if could not create context or other problem occurred. + * @throws NamingException + */ + public ReceiveSubscriber(boolean useProps, + String initialContextFactory, String providerUrl, String connfactory, String destinationName, + String durableSubscriptionId, String clientId, String jmsSelector, boolean useAuth, + String securityPrincipal, String securityCredentials) throws NamingException, JMSException { + this(0, useProps, + initialContextFactory, providerUrl, connfactory, destinationName, + durableSubscriptionId, clientId, jmsSelector, useAuth, + securityPrincipal, securityCredentials, false); + } + + /** + * Constructor takes the necessary JNDI related parameters to create a + * connection and create an onMessageListener to prepare to begin receiving messages. + *
+ * The caller must then invoke {@link #start()} to enable message reception. + * + * @param queueSize maximum queue size <=0 == no limit + * @param useProps if true, use jndi.properties instead of + * initialContextFactory, providerUrl, securityPrincipal, securityCredentials + * @param initialContextFactory + * @param providerUrl + * @param connfactory + * @param destinationName + * @param durableSubscriptionId + * @param clientId + * @param jmsSelector Message Selector + * @param useAuth + * @param securityPrincipal + * @param securityCredentials + * @throws JMSException if could not create context or other problem occurred. + * @throws NamingException + */ + public ReceiveSubscriber(int queueSize, boolean useProps, + String initialContextFactory, String providerUrl, String connfactory, String destinationName, + String durableSubscriptionId, String clientId, String jmsSelector, boolean useAuth, + String securityPrincipal, String securityCredentials) throws NamingException, JMSException { + this(queueSize, useProps, + initialContextFactory, providerUrl, connfactory, destinationName, + durableSubscriptionId, clientId, jmsSelector, useAuth, + securityPrincipal, securityCredentials, true); + } + + + /** + * Constructor takes the necessary JNDI related parameters to create a + * connection and create an onMessageListener to prepare to begin receiving messages. + *
+ * The caller must then invoke {@link #start()} to enable message reception. + * + * @param queueSize maximum queue size <=0 == no limit + * @param useProps if true, use jndi.properties instead of + * initialContextFactory, providerUrl, securityPrincipal, securityCredentials + * @param initialContextFactory + * @param providerUrl + * @param connfactory + * @param destinationName + * @param durableSubscriptionId + * @param clientId + * @param jmsSelector Message Selector + * @param useAuth + * @param securityPrincipal + * @param securityCredentials + * @param useMessageListener if true create an onMessageListener to prepare to begin receiving messages, otherwise queue will be null + * @throws JMSException if could not create context or other problem occurred. + * @throws NamingException + */ + private ReceiveSubscriber(int queueSize, boolean useProps, + String initialContextFactory, String providerUrl, String connfactory, String destinationName, + String durableSubscriptionId, String clientId, String jmsSelector, boolean useAuth, + String securityPrincipal, String securityCredentials, boolean useMessageListener) throws NamingException, JMSException { + boolean initSuccess = false; + try{ + Context ctx = InitialContextFactory.getContext(useProps, + initialContextFactory, providerUrl, useAuth, securityPrincipal, securityCredentials); + CONN = Utils.getConnection(ctx, connfactory); + if(!isEmpty(clientId)) { + CONN.setClientID(clientId); + } + SESSION = CONN.createSession(false, Session.AUTO_ACKNOWLEDGE); + Destination dest = Utils.lookupDestination(ctx, destinationName); + SUBSCRIBER = createSubscriber(SESSION, dest, durableSubscriptionId, jmsSelector); + if(useMessageListener) { + if (queueSize <=0) { + queue = new LinkedBlockingQueue(); + } else { + queue = new LinkedBlockingQueue(queueSize); + } + SUBSCRIBER.setMessageListener(this); + } else { + queue = null; + } + log.debug(" complete"); + initSuccess = true; + } + finally { + if(!initSuccess) { + close(); + } + } + } + + /** + * Return a simple MessageConsumer or a TopicSubscriber (as a durable subscription) + * @param session + * JMS session + * @param destination + * JMS destination, can be either topic or queue + * @param durableSubscriptionId + * If neither empty nor null, this means that a durable + * subscription will be used + * @param jmsSelector JMS Selector + * @return + * @throws JMSException + */ + private MessageConsumer createSubscriber(Session session, + Destination destination, String durableSubscriptionId, + String jmsSelector) throws JMSException { + if (isEmpty(durableSubscriptionId)) { + if(isEmpty(jmsSelector)) { + return session.createConsumer(destination); + } else { + return session.createConsumer(destination, jmsSelector); + } + } else { + if(isEmpty(jmsSelector)) { + return session.createDurableSubscriber((Topic) destination, durableSubscriptionId); + } else { + return session.createDurableSubscriber((Topic) destination, durableSubscriptionId, jmsSelector, false); + } + } + } + + /** + * Calls Connection.start() to begin receiving inbound messages. + * @throws JMSException + */ + public void start() throws JMSException { + log.debug("start()"); + CONN.start(); + } + + /** + * Calls Connection.stop() to stop receiving inbound messages. + * @throws JMSException + */ + public void stop() throws JMSException { + log.debug("stop()"); + CONN.stop(); + } + + /** + * Get the next message or null. + * Never blocks for longer than the specified timeout. + * + * @param timeout in milliseconds + * @return the next message or null + * + * @throws JMSException + */ + public Message getMessage(long timeout) throws JMSException { + Message message = null; + if (queue != null) { // Using onMessage Listener + try { + if (timeout < 10) { // Allow for short/negative times + message = queue.poll(); + } else { + message = queue.poll(timeout, TimeUnit.MILLISECONDS); + } + } catch (InterruptedException e) { + // Ignored + } + return message; + } + if (timeout < 10) { // Allow for short/negative times + message = SUBSCRIBER.receiveNoWait(); + } else { + message = SUBSCRIBER.receive(timeout); + } + return message; + } + /** + * close() will stop the connection first. + * Then it closes the subscriber, session and connection. + */ + public void close() { // called from threadFinished() thread + log.debug("close()"); + try { + if(CONN != null) { + CONN.stop(); + } + } catch (JMSException e) { + log.error(e.getMessage()); + } + Utils.close(SUBSCRIBER, log); + Utils.close(SESSION, log); + Utils.close(CONN, log); + } + + + /** + * {@inheritDoc} + */ + public void onMessage(Message message) { + if (!queue.offer(message)){ + log.warn("Could not add message to queue"); + } + } + + + /** + * Checks whether string is empty + * + * @param s1 + * @return True if input is null, an empty string, + * or a white space-only string + */ + private boolean isEmpty(String s1) { + return (s1 == null || s1.trim().equals("")); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSPublisherGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSPublisherGui.java new file mode 100644 index 0000000..a288389 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSPublisherGui.java @@ -0,0 +1,333 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms.control.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.gui.util.JLabeledRadioI18N; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.jms.sampler.PublisherSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledPasswordField; +import org.apache.jorphan.gui.JLabeledTextArea; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * This is the GUI for JMS Publisher
+ * Created on: October 13, 2003 + * + */ +public class JMSPublisherGui extends AbstractSamplerGui implements ChangeListener { + + private static final long serialVersionUID = 240L; + + private static final String ALL_FILES = "*.*"; //$NON-NLS-1$ + + //++ These names are used in the JMX files, and must not be changed + /** Take source from the named file */ + public static final String USE_FILE_RSC = "jms_use_file"; //$NON-NLS-1$ + /** Take source from a random file */ + public static final String USE_RANDOM_RSC = "jms_use_random_file"; //$NON-NLS-1$ + /** Take source from the text area */ + private static final String USE_TEXT_RSC = "jms_use_text"; //$NON-NLS-1$ + + /** Create a TextMessage */ + public static final String TEXT_MSG_RSC = "jms_text_message"; //$NON-NLS-1$ + /** Create a MapMessage */ + public static final String MAP_MSG_RSC = "jms_map_message"; //$NON-NLS-1$ + /** Create an ObjectMessage */ + public static final String OBJECT_MSG_RSC = "jms_object_message"; //$NON-NLS-1$ + //-- End of names used in JMX files + + // Button group resources + private static final String[] CONFIG_ITEMS = { USE_FILE_RSC, USE_RANDOM_RSC, USE_TEXT_RSC }; + + private static final String[] MSGTYPES_ITEMS = { TEXT_MSG_RSC, MAP_MSG_RSC, OBJECT_MSG_RSC }; + + private final JCheckBox useProperties = new JCheckBox(JMeterUtils.getResString("jms_use_properties_file"), false); //$NON-NLS-1$ + + private final JLabeledRadioI18N configChoice = new JLabeledRadioI18N("jms_config", CONFIG_ITEMS, USE_TEXT_RSC); //$NON-NLS-1$ + + private final JLabeledTextField jndiICF = new JLabeledTextField(JMeterUtils.getResString("jms_initial_context_factory")); //$NON-NLS-1$ + + private final JLabeledTextField urlField = new JLabeledTextField(JMeterUtils.getResString("jms_provider_url")); //$NON-NLS-1$ + + private final JLabeledTextField jndiConnFac = new JLabeledTextField(JMeterUtils.getResString("jms_connection_factory")); //$NON-NLS-1$ + + private final JLabeledTextField jmsDestination = new JLabeledTextField(JMeterUtils.getResString("jms_topic")); //$NON-NLS-1$ + + private final JCheckBox useAuth = new JCheckBox(JMeterUtils.getResString("jms_use_auth"), false); //$NON-NLS-1$ + + private final JLabeledTextField jmsUser = new JLabeledTextField(JMeterUtils.getResString("jms_user")); //$NON-NLS-1$ + + private final JLabeledTextField jmsPwd = new JLabeledPasswordField(JMeterUtils.getResString("jms_pwd")); //$NON-NLS-1$ + + private final JLabeledTextField iterations = new JLabeledTextField(JMeterUtils.getResString("jms_itertions")); //$NON-NLS-1$ + + private final FilePanel messageFile = new FilePanel(JMeterUtils.getResString("jms_file"), ALL_FILES); //$NON-NLS-1$ + + private final FilePanel randomFile = new FilePanel(JMeterUtils.getResString("jms_random_file"), ALL_FILES); //$NON-NLS-1$ + + private final JLabeledTextArea textMessage = new JLabeledTextArea(JMeterUtils.getResString("jms_text_message")); + + private final JLabeledRadioI18N msgChoice = new JLabeledRadioI18N("jms_message_type", MSGTYPES_ITEMS, TEXT_MSG_RSC); //$NON-NLS-1$ + + private JCheckBox useNonPersistentDelivery; + + // These are the names of properties used to define the labels + private final static String DEST_SETUP_STATIC = "jms_dest_setup_static"; // $NON-NLS-1$ + + private final static String DEST_SETUP_DYNAMIC = "jms_dest_setup_dynamic"; // $NON-NLS-1$ + // Button group resources + private final static String[] DEST_SETUP_ITEMS = { DEST_SETUP_STATIC, DEST_SETUP_DYNAMIC }; + + private final JLabeledRadioI18N destSetup = + new JLabeledRadioI18N("jms_dest_setup", DEST_SETUP_ITEMS, DEST_SETUP_STATIC); // $NON-NLS-1$ + + private ArgumentsPanel jmsPropertiesPanel; + + public JMSPublisherGui() { + init(); + } + + /** + * the name of the property for the JMSPublisherGui is jms_publisher. + */ + public String getLabelResource() { + return "jms_publisher"; //$NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + PublisherSampler sampler = new PublisherSampler(); + setupSamplerProperties(sampler); + + return sampler; + } + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement s) { + PublisherSampler sampler = (PublisherSampler) s; + setupSamplerProperties(sampler); + sampler.setDestinationStatic(destSetup.getText().equals(DEST_SETUP_STATIC)); + } + + /** + * Initialize the provided {@link PublisherSampler} with all the values as configured in the GUI. + * + * @param sampler {@link PublisherSampler} instance + */ + private void setupSamplerProperties(final PublisherSampler sampler) { + this.configureTestElement(sampler); + sampler.setUseJNDIProperties(String.valueOf(useProperties.isSelected())); + sampler.setJNDIIntialContextFactory(jndiICF.getText()); + sampler.setProviderUrl(urlField.getText()); + sampler.setConnectionFactory(jndiConnFac.getText()); + sampler.setDestination(jmsDestination.getText()); + sampler.setUsername(jmsUser.getText()); + sampler.setPassword(jmsPwd.getText()); + sampler.setTextMessage(textMessage.getText()); + sampler.setInputFile(messageFile.getFilename()); + sampler.setRandomPath(randomFile.getFilename()); + sampler.setConfigChoice(configChoice.getText()); + sampler.setMessageChoice(msgChoice.getText()); + sampler.setIterations(iterations.getText()); + sampler.setUseAuth(useAuth.isSelected()); + sampler.setUseNonPersistentDelivery(useNonPersistentDelivery.isSelected()); + + Arguments args = (Arguments) jmsPropertiesPanel.createTestElement(); + sampler.setJMSProperties(args); + } + + /** + * init() adds jndiICF to the mainPanel. The class reuses logic from + * SOAPSampler, since it is common. + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new VerticalPanel(); + add(mainPanel, BorderLayout.CENTER); + + mainPanel.add(useProperties); + mainPanel.add(jndiICF); + mainPanel.add(urlField); + mainPanel.add(jndiConnFac); + mainPanel.add(createDestinationPane()); + mainPanel.add(createAuthPane()); + mainPanel.add(iterations); + + jmsPropertiesPanel = new ArgumentsPanel(JMeterUtils.getResString("jms_props")); //$NON-NLS-1$ + mainPanel.add(jmsPropertiesPanel); + + configChoice.setLayout(new BoxLayout(configChoice, BoxLayout.X_AXIS)); + mainPanel.add(configChoice); + msgChoice.setLayout(new BoxLayout(msgChoice, BoxLayout.X_AXIS)); + mainPanel.add(msgChoice); + mainPanel.add(messageFile); + mainPanel.add(randomFile); + mainPanel.add(textMessage); + Dimension pref = new Dimension(400, 150); + textMessage.setPreferredSize(pref); + + useProperties.addChangeListener(this); + useAuth.addChangeListener(this); + configChoice.addChangeListener(this); + msgChoice.addChangeListener(this); + } + + @Override + public void clearGui(){ + super.clearGui(); + useProperties.setSelected(false); + jndiICF.setText(""); // $NON-NLS-1$ + urlField.setText(""); // $NON-NLS-1$ + jndiConnFac.setText(""); // $NON-NLS-1$ + jmsDestination.setText(""); // $NON-NLS-1$ + jmsUser.setText(""); // $NON-NLS-1$ + jmsPwd.setText(""); // $NON-NLS-1$ + textMessage.setText(""); // $NON-NLS-1$ + messageFile.setFilename(""); // $NON-NLS-1$ + randomFile.setFilename(""); // $NON-NLS-1$ + msgChoice.setText(""); // $NON-NLS-1$ + configChoice.setText(USE_TEXT_RSC); + updateConfig(USE_TEXT_RSC); + iterations.setText("1"); // $NON-NLS-1$ + useAuth.setSelected(false); + jmsUser.setEnabled(false); + jmsPwd.setEnabled(false); + destSetup.setText(DEST_SETUP_STATIC); + useNonPersistentDelivery.setSelected(false); + jmsPropertiesPanel.clear(); + } + + /** + * the implementation loads the URL and the soap action for the request. + */ + @Override + public void configure(TestElement el) { + super.configure(el); + PublisherSampler sampler = (PublisherSampler) el; + useProperties.setSelected(sampler.getUseJNDIPropertiesAsBoolean()); + jndiICF.setText(sampler.getJNDIInitialContextFactory()); + urlField.setText(sampler.getProviderUrl()); + jndiConnFac.setText(sampler.getConnectionFactory()); + jmsDestination.setText(sampler.getDestination()); + jmsUser.setText(sampler.getUsername()); + jmsPwd.setText(sampler.getPassword()); + textMessage.setText(sampler.getTextMessage()); + messageFile.setFilename(sampler.getInputFile()); + randomFile.setFilename(sampler.getRandomPath()); + configChoice.setText(sampler.getConfigChoice()); + msgChoice.setText(sampler.getMessageChoice()); + updateConfig(sampler.getConfigChoice()); + iterations.setText(sampler.getIterations()); + useAuth.setSelected(sampler.isUseAuth()); + jmsUser.setEnabled(useAuth.isSelected()); + jmsPwd.setEnabled(useAuth.isSelected()); + destSetup.setText(sampler.isDestinationStatic() ? DEST_SETUP_STATIC : DEST_SETUP_DYNAMIC); + useNonPersistentDelivery.setSelected(sampler.getUseNonPersistentDelivery()); + jmsPropertiesPanel.configure(sampler.getJMSProperties()); + } + + /** + * When a widget state changes, it will notify this class so we can + * enable/disable the correct items. + */ + public void stateChanged(ChangeEvent event) { + if (event.getSource() == configChoice) { + updateConfig(configChoice.getText()); + } else if (event.getSource() == useProperties) { + jndiICF.setEnabled(!useProperties.isSelected()); + urlField.setEnabled(!useProperties.isSelected()); + } else if (event.getSource() == useAuth) { + jmsUser.setEnabled(useAuth.isSelected()); + jmsPwd.setEnabled(useAuth.isSelected()); + } + } + + /** + * Update config contains the actual logic for enabling or disabling text + * message, file or random path. + * + * @param command + */ + private void updateConfig(String command) { + if (command.equals(USE_TEXT_RSC)) { + textMessage.setEnabled(true); + messageFile.enableFile(false); + randomFile.enableFile(false); + } else if (command.equals(USE_RANDOM_RSC)) { + textMessage.setEnabled(false); + messageFile.enableFile(false); + randomFile.enableFile(true); + } else { + textMessage.setEnabled(false); + messageFile.enableFile(true); + randomFile.enableFile(false); + } + } + + /** + * @return JPanel that contains destination infos + */ + private JPanel createDestinationPane() { + JPanel pane = new JPanel(new BorderLayout(3, 0)); + pane.add(jmsDestination, BorderLayout.WEST); + destSetup.setLayout(new BoxLayout(destSetup, BoxLayout.X_AXIS)); + pane.add(destSetup, BorderLayout.CENTER); + useNonPersistentDelivery = new JCheckBox(JMeterUtils.getResString("jms_use_non_persistent_delivery"),false); //$NON-NLS-1$ + pane.add(useNonPersistentDelivery, BorderLayout.EAST); + return pane; + } + + /** + * @return JPanel Panel with checkbox to choose auth , user and password + */ + private JPanel createAuthPane() { + JPanel pane = new JPanel(); + pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS)); + pane.add(useAuth); + pane.add(Box.createHorizontalStrut(10)); + pane.add(jmsUser); + pane.add(Box.createHorizontalStrut(10)); + pane.add(jmsPwd); + return pane; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSSamplerGui.java new file mode 100644 index 0000000..b0da812 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSSamplerGui.java @@ -0,0 +1,284 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JPanel; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.protocol.jms.sampler.JMSSampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.JLabeledTextArea; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * Configuration screen for Java Messaging Point-to-Point requests.
+ * Created on: October 28, 2004 + * + */ +public class JMSSamplerGui extends AbstractSamplerGui { + + private static final long serialVersionUID = 240L; + + private JLabeledTextField queueConnectionFactory = new JLabeledTextField( + JMeterUtils.getResString("jms_queue_connection_factory")); //$NON-NLS-1$ + + private JLabeledTextField sendQueue = new JLabeledTextField(JMeterUtils.getResString("jms_send_queue")); //$NON-NLS-1$ + + private JLabeledTextField receiveQueue = new JLabeledTextField(JMeterUtils.getResString("jms_receive_queue")); //$NON-NLS-1$ + + private JLabeledTextField timeout = new JLabeledTextField(JMeterUtils.getResString("jms_timeout")); //$NON-NLS-1$ + + private JLabeledTextField jmsSelector = new JLabeledTextField(JMeterUtils.getResString("jms_selector")); //$NON-NLS-1$ + + private JLabeledTextArea soapXml = new JLabeledTextArea(JMeterUtils.getResString("jms_msg_content")); //$NON-NLS-1$ + + private JLabeledTextField initialContextFactory = new JLabeledTextField( + JMeterUtils.getResString("jms_initial_context_factory")); //$NON-NLS-1$ + + private JLabeledTextField providerUrl = new JLabeledTextField(JMeterUtils.getResString("jms_provider_url")); //$NON-NLS-1$ + + private String[] labels = new String[] { JMeterUtils.getResString("jms_request"), //$NON-NLS-1$ + JMeterUtils.getResString("jms_requestreply") }; //$NON-NLS-1$ + + private JLabeledChoice oneWay = new JLabeledChoice(JMeterUtils.getResString("jms_communication_style"), labels); //$NON-NLS-1$ + + private ArgumentsPanel jmsPropertiesPanel; + + private ArgumentsPanel jndiPropertiesPanel; + + private JCheckBox useNonPersistentDelivery; + + private JCheckBox useReqMsgIdAsCorrelId; + + private JCheckBox useResMsgIdAsCorrelId; + + public JMSSamplerGui() { + init(); + } + + /** + * Clears all fields. + */ + @Override + public void clearGui() {// renamed from clear + super.clearGui(); + queueConnectionFactory.setText(""); // $NON-NLS-1$ + sendQueue.setText(""); // $NON-NLS-1$ + receiveQueue.setText(""); // $NON-NLS-1$ + ((JComboBox) oneWay.getComponentList().get(1)).setSelectedItem(JMeterUtils.getResString("jms_request")); //$NON-NLS-1$ + timeout.setText(""); // $NON-NLS-1$ + jmsSelector.setText(""); // $NON-NLS-1$ + soapXml.setText(""); // $NON-NLS-1$ + initialContextFactory.setText(""); // $NON-NLS-1$ + providerUrl.setText(""); // $NON-NLS-1$ + jmsPropertiesPanel.clear(); + jndiPropertiesPanel.clear(); + } + + public TestElement createTestElement() { + JMSSampler sampler = new JMSSampler(); + this.configureTestElement(sampler); + transfer(sampler); + return sampler; + } + + private void transfer(JMSSampler element) { + element.setQueueConnectionFactory(queueConnectionFactory.getText()); + element.setSendQueue(sendQueue.getText()); + element.setReceiveQueue(receiveQueue.getText()); + + boolean isOneway = oneWay.getText().equals(JMeterUtils.getResString("jms_request")); //$NON-NLS-1$ + element.setIsOneway(isOneway); + + element.setNonPersistent(useNonPersistentDelivery.isSelected()); + element.setUseReqMsgIdAsCorrelId(useReqMsgIdAsCorrelId.isSelected()); + element.setUseResMsgIdAsCorrelId(useResMsgIdAsCorrelId.isSelected()); + element.setTimeout(timeout.getText()); + element.setJMSSelector(jmsSelector.getText()); + element.setContent(soapXml.getText()); + + element.setInitialContextFactory(initialContextFactory.getText()); + element.setContextProvider(providerUrl.getText()); + Arguments jndiArgs = (Arguments) jndiPropertiesPanel.createTestElement(); + element.setJNDIProperties(jndiArgs); + + Arguments args = (Arguments) jmsPropertiesPanel.createTestElement(); + element.setJMSProperties(args); + + } + + /** + * + * @param element + */ + public void modifyTestElement(TestElement element) { + this.configureTestElement(element); + if (!(element instanceof JMSSampler)) return; + JMSSampler sampler = (JMSSampler) element; + transfer(sampler); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + if (!(el instanceof JMSSampler)) return; + JMSSampler sampler = (JMSSampler) el; + queueConnectionFactory.setText(sampler.getQueueConnectionFactory()); + sendQueue.setText(sampler.getSendQueue()); + receiveQueue.setText(sampler.getReceiveQueue()); + + JComboBox box = (JComboBox) oneWay.getComponentList().get(1); + String selected = null; + if (sampler.isOneway()) { + selected = JMeterUtils.getResString("jms_request"); //$NON-NLS-1$ + } else { + selected = JMeterUtils.getResString("jms_requestreply"); //$NON-NLS-1$ + } + box.setSelectedItem(selected); + + useNonPersistentDelivery.setSelected(sampler.isNonPersistent()); + useReqMsgIdAsCorrelId.setSelected(sampler.isUseReqMsgIdAsCorrelId()); + useResMsgIdAsCorrelId.setSelected(sampler.isUseResMsgIdAsCorrelId()); + + timeout.setText(sampler.getTimeout()); + jmsSelector.setText(sampler.getJMSSelector()); + soapXml.setText(sampler.getContent()); + initialContextFactory.setText(sampler.getInitialContextFactory()); + providerUrl.setText(sampler.getContextProvider()); + + jmsPropertiesPanel.configure(sampler.getJMSProperties()); + // (TestElement) + // el.getProperty(JMSSampler.JMS_PROPERTIES).getObjectValue()); + + jndiPropertiesPanel.configure(sampler.getJNDIProperties()); + // (TestElement) + // el.getProperty(JMSSampler.JNDI_PROPERTIES).getObjectValue()); + } + + /** + * Initializes the configuration screen. + * + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel jmsQueueingPanel = new JPanel(new BorderLayout()); + jmsQueueingPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("jms_queueing"))); //$NON-NLS-1$ + + JPanel qcfPanel = new JPanel(new BorderLayout(5, 0)); + qcfPanel.add(queueConnectionFactory, BorderLayout.CENTER); + jmsQueueingPanel.add(qcfPanel, BorderLayout.NORTH); + + JPanel sendQueuePanel = new JPanel(new BorderLayout(5, 0)); + sendQueuePanel.add(sendQueue); + jmsQueueingPanel.add(sendQueuePanel, BorderLayout.CENTER); + + JPanel receiveQueuePanel = new JPanel(new BorderLayout(5, 0)); + receiveQueuePanel.add(jmsSelector,BorderLayout.EAST); + receiveQueuePanel.add(receiveQueue,BorderLayout.WEST); + jmsQueueingPanel.add(receiveQueuePanel, BorderLayout.SOUTH); + + JPanel messagePanel = new JPanel(new BorderLayout()); + messagePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("jms_message_title"))); //$NON-NLS-1$ + + JPanel correlationPanel = new HorizontalPanel(); + correlationPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("jms_correlation_title"))); //$NON-NLS-1$ + + useReqMsgIdAsCorrelId = new JCheckBox(JMeterUtils.getResString("jms_use_req_msgid_as_correlid"),false); //$NON-NLS-1$ + + useResMsgIdAsCorrelId = new JCheckBox(JMeterUtils.getResString("jms_use_res_msgid_as_correlid"),false); //$NON-NLS-1$ + + correlationPanel.add(useReqMsgIdAsCorrelId); + correlationPanel.add(useResMsgIdAsCorrelId); + + JPanel messageNorthPanel = new JPanel(new BorderLayout()); + JPanel onewayPanel = new HorizontalPanel(); + onewayPanel.add(oneWay); + onewayPanel.add(correlationPanel); + messageNorthPanel.add(onewayPanel, BorderLayout.NORTH); + + useNonPersistentDelivery = new JCheckBox(JMeterUtils.getResString("jms_use_non_persistent_delivery"),false); //$NON-NLS-1$ + + JPanel timeoutPanel = new HorizontalPanel(); + timeoutPanel.add(timeout); + timeoutPanel.add(useNonPersistentDelivery); + messageNorthPanel.add(timeoutPanel, BorderLayout.SOUTH); + + messagePanel.add(messageNorthPanel, BorderLayout.NORTH); + + JPanel soapXmlPanel = new JPanel(new BorderLayout()); + soapXmlPanel.add(soapXml); + messagePanel.add(soapXmlPanel, BorderLayout.CENTER); + + jmsPropertiesPanel = new ArgumentsPanel(JMeterUtils.getResString("jms_props")); //$NON-NLS-1$ + messagePanel.add(jmsPropertiesPanel, BorderLayout.SOUTH); + + Box mainPanel = Box.createVerticalBox(); + add(mainPanel, BorderLayout.CENTER); + mainPanel.add(jmsQueueingPanel, BorderLayout.NORTH); + mainPanel.add(messagePanel, BorderLayout.CENTER); + JPanel jndiPanel = createJNDIPanel(); + mainPanel.add(jndiPanel, BorderLayout.SOUTH); + + } + + /** + * Creates the panel for the JNDI configuration. + * + * @return the JNDI Panel + */ + private JPanel createJNDIPanel() { + JPanel jndiPanel = new JPanel(new BorderLayout()); + jndiPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("jms_jndi_props"))); //$NON-NLS-1$ + + JPanel contextPanel = new JPanel(new BorderLayout(10, 0)); + contextPanel.add(initialContextFactory); + jndiPanel.add(contextPanel, BorderLayout.NORTH); + + JPanel providerPanel = new JPanel(new BorderLayout(10, 0)); + providerPanel.add(providerUrl); + jndiPanel.add(providerPanel, BorderLayout.SOUTH); + + jndiPropertiesPanel = new ArgumentsPanel(JMeterUtils.getResString("jms_jndi_props")); //$NON-NLS-1$ + jndiPanel.add(jndiPropertiesPanel); + return jndiPanel; + } + + public String getLabelResource() { + return "jms_point_to_point"; //$NON-NLS-1$ // TODO - probably wrong + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSSubscriberGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSSubscriberGui.java new file mode 100644 index 0000000..0d73d7b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/control/gui/JMSSubscriberGui.java @@ -0,0 +1,280 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms.control.gui; + +import java.awt.BorderLayout; + +import javax.naming.Context; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.JLabeledRadioI18N; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.jms.sampler.SubscriberSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledPasswordField; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * This is the GUI for JMS Subscriber
+ * + */ +public class JMSSubscriberGui extends AbstractSamplerGui implements ChangeListener { + + private static final long serialVersionUID = 240L; + + private final JCheckBox useProperties = + new JCheckBox(JMeterUtils.getResString("jms_use_properties_file"), false); // $NON-NLS-1$ + + private final JLabeledTextField jndiICF = + new JLabeledTextField(JMeterUtils.getResString("jms_initial_context_factory")); // $NON-NLS-1$ + + private final JLabeledTextField urlField = + new JLabeledTextField(JMeterUtils.getResString("jms_provider_url")); // $NON-NLS-1$ + + private final JLabeledTextField jndiConnFac = + new JLabeledTextField(JMeterUtils.getResString("jms_connection_factory")); // $NON-NLS-1$ + + private final JLabeledTextField jmsDestination = + new JLabeledTextField(JMeterUtils.getResString("jms_topic")); // $NON-NLS-1$ + + private final JLabeledTextField jmsDurableSubscriptionId = + new JLabeledTextField(JMeterUtils.getResString("jms_durable_subscription_id")); // $NON-NLS-1$ + + private final JLabeledTextField jmsClientId = + new JLabeledTextField(JMeterUtils.getResString("jms_client_id")); // $NON-NLS-1$ + + private final JLabeledTextField jmsSelector = + new JLabeledTextField(JMeterUtils.getResString("jms_selector")); // $NON-NLS-1$ + + private final JLabeledTextField jmsUser = + new JLabeledTextField(JMeterUtils.getResString("jms_user")); // $NON-NLS-1$ + + private final JLabeledTextField jmsPwd = + new JLabeledPasswordField(JMeterUtils.getResString("jms_pwd")); // $NON-NLS-1$ + + private final JLabeledTextField iterations = + new JLabeledTextField(JMeterUtils.getResString("jms_itertions")); // $NON-NLS-1$ + + private final JCheckBox useAuth = + new JCheckBox(JMeterUtils.getResString("jms_use_auth"), false); //$NON-NLS-1$ + + private final JCheckBox readResponse = + new JCheckBox(JMeterUtils.getResString("jms_read_response"), true); // $NON-NLS-1$ + + private final JLabeledTextField timeout = + new JLabeledTextField(JMeterUtils.getResString("jms_timeout")); //$NON-NLS-1$ + + private final JLabeledTextField separator = + new JLabeledTextField(JMeterUtils.getResString("jms_separator")); //$NON-NLS-1$ + + //++ Do not change these strings; they are used in JMX files to record the button settings + public final static String RECEIVE_RSC = "jms_subscriber_receive"; // $NON-NLS-1$ + + public final static String ON_MESSAGE_RSC = "jms_subscriber_on_message"; // $NON-NLS-1$ + //-- + + // Button group resources + private final static String[] CLIENT_ITEMS = { RECEIVE_RSC, ON_MESSAGE_RSC }; + + private final JLabeledRadioI18N clientChoice = + new JLabeledRadioI18N("jms_client_type", CLIENT_ITEMS, RECEIVE_RSC); // $NON-NLS-1$ + + private final JCheckBox stopBetweenSamples = + new JCheckBox(JMeterUtils.getResString("jms_stop_between_samples"), true); // $NON-NLS-1$ + + // These are the names of properties used to define the labels + private final static String DEST_SETUP_STATIC = "jms_dest_setup_static"; // $NON-NLS-1$ + + private final static String DEST_SETUP_DYNAMIC = "jms_dest_setup_dynamic"; // $NON-NLS-1$ + // Button group resources + private final static String[] DEST_SETUP_ITEMS = { DEST_SETUP_STATIC, DEST_SETUP_DYNAMIC }; + + private final JLabeledRadioI18N destSetup = + new JLabeledRadioI18N("jms_dest_setup", DEST_SETUP_ITEMS, DEST_SETUP_STATIC); // $NON-NLS-1$ + + public JMSSubscriberGui() { + init(); + } + + public String getLabelResource() { + return "jms_subscriber_title"; // $NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + SubscriberSampler sampler = new SubscriberSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement s) { + SubscriberSampler sampler = (SubscriberSampler) s; + this.configureTestElement(sampler); + sampler.setUseJNDIProperties(String.valueOf(useProperties.isSelected())); + sampler.setJNDIIntialContextFactory(jndiICF.getText()); + sampler.setProviderUrl(urlField.getText()); + sampler.setConnectionFactory(jndiConnFac.getText()); + sampler.setDestination(jmsDestination.getText()); + sampler.setDurableSubscriptionId(jmsDurableSubscriptionId.getText()); + sampler.setClientID(jmsClientId.getText()); + sampler.setJmsSelector(jmsSelector.getText()); + sampler.setUsername(jmsUser.getText()); + sampler.setPassword(jmsPwd.getText()); + sampler.setUseAuth(useAuth.isSelected()); + sampler.setIterations(iterations.getText()); + sampler.setReadResponse(String.valueOf(readResponse.isSelected())); + sampler.setClientChoice(clientChoice.getText()); + sampler.setStopBetweenSamples(stopBetweenSamples.isSelected()); + sampler.setTimeout(timeout.getText()); + sampler.setDestinationStatic(destSetup.getText().equals(DEST_SETUP_STATIC)); + sampler.setSeparator(separator.getText()); + } + + /** + * init() adds jndiICF to the mainPanel. The class reuses logic from + * SOAPSampler, since it is common. + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + JPanel mainPanel = new VerticalPanel(); + add(mainPanel, BorderLayout.CENTER); + + jndiICF.setToolTipText(Context.INITIAL_CONTEXT_FACTORY); + urlField.setToolTipText(Context.PROVIDER_URL); + jmsUser.setToolTipText(Context.SECURITY_PRINCIPAL); + jmsPwd.setToolTipText(Context.SECURITY_CREDENTIALS); + mainPanel.add(useProperties); + mainPanel.add(jndiICF); + mainPanel.add(urlField); + mainPanel.add(jndiConnFac); + mainPanel.add(createDestinationPane()); + mainPanel.add(jmsDurableSubscriptionId); + mainPanel.add(jmsClientId); + mainPanel.add(jmsSelector); + mainPanel.add(useAuth); + mainPanel.add(jmsUser); + mainPanel.add(jmsPwd); + mainPanel.add(iterations); + + mainPanel.add(readResponse); + mainPanel.add(timeout); + + JPanel choice = new HorizontalPanel(); + choice.add(clientChoice); + choice.add(stopBetweenSamples); + mainPanel.add(choice); + mainPanel.add(separator); + + useProperties.addChangeListener(this); + useAuth.addChangeListener(this); + } + + /** + * the implementation loads the URL and the soap action for the request. + */ + @Override + public void configure(TestElement el) { + super.configure(el); + SubscriberSampler sampler = (SubscriberSampler) el; + useProperties.setSelected(sampler.getUseJNDIPropertiesAsBoolean()); + jndiICF.setText(sampler.getJNDIInitialContextFactory()); + urlField.setText(sampler.getProviderUrl()); + jndiConnFac.setText(sampler.getConnectionFactory()); + jmsDestination.setText(sampler.getDestination()); + jmsDurableSubscriptionId.setText(sampler.getDurableSubscriptionId()); + jmsClientId.setText(sampler.getClientId()); + jmsSelector.setText(sampler.getJmsSelector()); + jmsUser.setText(sampler.getUsername()); + jmsPwd.setText(sampler.getPassword()); + iterations.setText(sampler.getIterations()); + useAuth.setSelected(sampler.isUseAuth()); + jmsUser.setEnabled(useAuth.isSelected()); + jmsPwd.setEnabled(useAuth.isSelected()); + readResponse.setSelected(sampler.getReadResponseAsBoolean()); + clientChoice.setText(sampler.getClientChoice()); + stopBetweenSamples.setSelected(sampler.isStopBetweenSamples()); + timeout.setText(sampler.getTimeout()); + separator.setText(sampler.getSeparator()); + destSetup.setText(sampler.isDestinationStatic() ? DEST_SETUP_STATIC : DEST_SETUP_DYNAMIC); + } + + @Override + public void clearGui(){ + super.clearGui(); + useProperties.setSelected(false); // $NON-NLS-1$ + jndiICF.setText(""); // $NON-NLS-1$ + urlField.setText(""); // $NON-NLS-1$ + jndiConnFac.setText(""); // $NON-NLS-1$ + jmsDestination.setText(""); // $NON-NLS-1$ + jmsDurableSubscriptionId.setText(""); // $NON-NLS-1$ + jmsClientId.setText(""); // $NON-NLS-1$ + jmsSelector.setText(""); // $NON-NLS-1$ + jmsUser.setText(""); // $NON-NLS-1$ + jmsPwd.setText(""); // $NON-NLS-1$ + iterations.setText("1"); // $NON-NLS-1$ + timeout.setText(""); // $NON-NLS-1$ + separator.setText(""); // $NON-NLS-1$ + useAuth.setSelected(false); + jmsUser.setEnabled(false); + jmsPwd.setEnabled(false); + readResponse.setSelected(true); + clientChoice.setText(RECEIVE_RSC); + stopBetweenSamples.setSelected(false); + destSetup.setText(DEST_SETUP_STATIC); + } + + /** + * When the state of a widget changes, it will notify the gui. the method + * then enables or disables certain parameters. + */ + public void stateChanged(ChangeEvent event) { + if (event.getSource() == useProperties) { + jndiICF.setEnabled(!useProperties.isSelected()); + urlField.setEnabled(!useProperties.isSelected()); + } else if (event.getSource() == useAuth) { + jmsUser.setEnabled(useAuth.isSelected()); + jmsPwd.setEnabled(useAuth.isSelected()); + } + } + + private JPanel createDestinationPane() { + JPanel pane = new JPanel(new BorderLayout(3, 0)); + pane.add(jmsDestination, BorderLayout.CENTER); + destSetup.setLayout(new BoxLayout(destSetup, BoxLayout.X_AXIS)); + pane.add(destSetup, BorderLayout.EAST); + return pane; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/BaseJMSSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/BaseJMSSampler.java new file mode 100644 index 0000000..a6e9603 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/BaseJMSSampler.java @@ -0,0 +1,370 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.protocol.jms.sampler; + +import java.util.Date; + +import javax.jms.DeliveryMode; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; + +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +/** + * + * BaseJMSSampler is an abstract class which provides implementation for common + * properties. Rather than duplicate the code, it's contained in the base class. + */ +public abstract class BaseJMSSampler extends AbstractSampler { + + private static final long serialVersionUID = 240L; + + //++ These are JMX file attribute names and must not be changed + private static final String JNDI_INITIAL_CONTEXT_FAC = "jms.initial_context_factory"; // $NON-NLS-1$ + + private static final String PROVIDER_URL = "jms.provider_url"; // $NON-NLS-1$ + + private static final String CONN_FACTORY = "jms.connection_factory"; // $NON-NLS-1$ + + // N.B. Cannot change value, as that is used in JMX files + private static final String DEST = "jms.topic"; // $NON-NLS-1$ + + private static final String PRINCIPAL = "jms.security_principle"; // $NON-NLS-1$ + + private static final String CREDENTIALS = "jms.security_credentials"; // $NON-NLS-1$ + + private static final String ITERATIONS = "jms.iterations"; // $NON-NLS-1$ + + private static final String USE_AUTH = "jms.authenticate"; // $NON-NLS-1$ + + private static final String USE_PROPERTIES_FILE = "jms.jndi_properties"; // $NON-NLS-1$ + + private static final String READ_RESPONSE = "jms.read_response"; // $NON-NLS-1$ + + // Is Destination setup static? else dynamic + private static final String DESTINATION_STATIC = "jms.destination_static"; // $NON-NLS-1$ + private static final boolean DESTINATION_STATIC_DEFAULT = true; // default to maintain compatibility + + //-- End of JMX file attribute names + + // See BUG 45460. We need to keep the resource in order to interpret existing files + private static final String REQUIRED = JMeterUtils.getResString("jms_auth_required"); // $NON-NLS-1$ + + public BaseJMSSampler() { + } + + /** + * {@inheritDoc} + */ + public SampleResult sample(Entry e) { + return this.sample(); + } + + public abstract SampleResult sample(); + + // ------------- get/set properties ----------------------// + /** + * set the initial context factory + * + * @param icf + */ + public void setJNDIIntialContextFactory(String icf) { + setProperty(JNDI_INITIAL_CONTEXT_FAC, icf); + } + + /** + * method returns the initial context factory for jndi initial context + * lookup. + * + * @return the initial context factory + */ + public String getJNDIInitialContextFactory() { + return getPropertyAsString(JNDI_INITIAL_CONTEXT_FAC); + } + + /** + * set the provider user for jndi + * + * @param url the provider URL + */ + public void setProviderUrl(String url) { + setProperty(PROVIDER_URL, url); + } + + /** + * method returns the provider url for jndi to connect to + * + * @return the provider URL + */ + public String getProviderUrl() { + return getPropertyAsString(PROVIDER_URL); + } + + /** + * set the connection factory for + * + * @param factory + */ + public void setConnectionFactory(String factory) { + setProperty(CONN_FACTORY, factory); + } + + /** + * return the connection factory parameter used to lookup the connection + * factory from the JMS server + * + * @return the connection factory + */ + public String getConnectionFactory() { + return getPropertyAsString(CONN_FACTORY); + } + + /** + * set the destination (topic or queue name) + * + * @param dest the destination + */ + public void setDestination(String dest) { + setProperty(DEST, dest); + } + + /** + * return the destination (topic or queue name) + * + * @return the destination + */ + public String getDestination() { + return getPropertyAsString(DEST); + } + + /** + * set the username to login into the jms server if needed + * + * @param user + */ + public void setUsername(String user) { + setProperty(PRINCIPAL, user); + } + + /** + * return the username used to login to the jms server + * + * @return the username used to login to the jms server + */ + public String getUsername() { + return getPropertyAsString(PRINCIPAL); + } + + /** + * Set the password to login to the jms server + * + * @param pwd + */ + public void setPassword(String pwd) { + setProperty(CREDENTIALS, pwd); + } + + /** + * return the password used to login to the jms server + * + * @return the password used to login to the jms server + */ + public String getPassword() { + return getPropertyAsString(CREDENTIALS); + } + + /** + * set the number of iterations the sampler should aggregate + * + * @param count + */ + public void setIterations(String count) { + setProperty(ITERATIONS, count); + } + + /** + * get the iterations as string + * + * @return the number of iterations + */ + public String getIterations() { + return getPropertyAsString(ITERATIONS); + } + + /** + * return the number of iterations as int instead of string + * + * @return the number of iterations as int instead of string + */ + public int getIterationCount() { + return getPropertyAsInt(ITERATIONS); + } + + /** + * Set whether authentication is required for JNDI + * + * @param useAuth + */ + public void setUseAuth(boolean useAuth) { + setProperty(USE_AUTH, useAuth); + } + + /** + * return whether jndi requires authentication + * + * @return whether jndi requires authentication + */ + public boolean isUseAuth() { + final String useAuth = getPropertyAsString(USE_AUTH); + return useAuth.equalsIgnoreCase("true") || useAuth.equals(REQUIRED); // $NON-NLS-1$ + } + + /** + * set whether the sampler should read the response or not + * + * @param read whether the sampler should read the response or not + */ + public void setReadResponse(String read) { + setProperty(READ_RESPONSE, read); + } + + /** + * return whether the sampler should read the response + * + * @return whether the sampler should read the response + */ + public String getReadResponse() { + return getPropertyAsString(READ_RESPONSE); + } + + /** + * return whether the sampler should read the response as a boolean value + * + * @return whether the sampler should read the response as a boolean value + */ + public boolean getReadResponseAsBoolean() { + return getPropertyAsBoolean(READ_RESPONSE); + } + + /** + * if the sampler should use jndi.properties file, call the method with true + * + * @param properties + */ + public void setUseJNDIProperties(String properties) { + setProperty(USE_PROPERTIES_FILE, properties); + } + + /** + * return whether the sampler should use properties file instead of UI + * parameters. + * + * @return whether the sampler should use properties file instead of UI parameters. + */ + public String getUseJNDIProperties() { + return getPropertyAsString(USE_PROPERTIES_FILE); + } + + /** + * return the properties as boolean true/false. + * + * @return whether the sampler should use properties file instead of UI parameters. + */ + public boolean getUseJNDIPropertiesAsBoolean() { + return getPropertyAsBoolean(USE_PROPERTIES_FILE); + } + + /** + * if the sampler should use a static destination, call the method with true + * + * @param isStatic + */ + public void setDestinationStatic(boolean isStatic) { + setProperty(DESTINATION_STATIC, isStatic, DESTINATION_STATIC_DEFAULT); + } + + /** + * return whether the sampler should use a static destination. + * + * @return whether the sampler should use a static destination. + */ + public boolean isDestinationStatic(){ + return getPropertyAsBoolean(DESTINATION_STATIC, DESTINATION_STATIC_DEFAULT); + } + + /** + * Returns a String with the JMS Message Header values. + * + * @param message JMS Message + * @return String with message header values. + */ + public static String getMessageHeaders(Message message) { + final StringBuilder response = new StringBuilder(256); + try { + response.append("JMS Message Header Attributes:"); + response.append("\n Correlation ID: "); + response.append(message.getJMSCorrelationID()); + + response.append("\n Delivery Mode: "); + if (message.getJMSDeliveryMode() == DeliveryMode.PERSISTENT) { + response.append("PERSISTANT"); + } else { + response.append("NON-PERSISTANT"); + } + + final Destination destination = message.getJMSDestination(); + + response.append("\n Destination: "); + response.append((destination == null ? null : destination + .toString())); + + response.append("\n Expiration: "); + response.append(new Date(message.getJMSExpiration())); + + response.append("\n Message ID: "); + response.append(message.getJMSMessageID()); + + response.append("\n Priority: "); + response.append(message.getJMSPriority()); + + response.append("\n Redelivered: "); + response.append(message.getJMSRedelivered()); + + final Destination replyTo = message.getJMSReplyTo(); + response.append("\n Reply to: "); + response.append((replyTo == null ? null : replyTo.toString())); + + response.append("\n Timestamp: "); + response.append(new Date(message.getJMSTimestamp())); + + response.append("\n Type: "); + response.append(message.getJMSType()); + + response.append("\n\n"); + + } catch (JMSException e) { + e.printStackTrace(); + } + + return new String(response); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/FixedQueueExecutor.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/FixedQueueExecutor.java new file mode 100644 index 0000000..7d00173 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/FixedQueueExecutor.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms.sampler; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageProducer; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Request/reply executor with a fixed reply queue.
+ * + * Used by JMS Sampler (Point to Point) + * + * Created on: October 28, 2004 + * + */ +public class FixedQueueExecutor implements QueueExecutor { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** Sender. */ + private final MessageProducer producer; + + /** Timeout used for waiting on message. */ + private final int timeout; + + private final boolean useReqMsgIdAsCorrelId; + + /** + * Constructor. + * + * @param producer + * the queue to send the message on + * @param timeout + * timeout to use for the return message + * @param useReqMsgIdAsCorrelId + * whether to use the request message id as the correlation id + */ + public FixedQueueExecutor(MessageProducer producer, int timeout, boolean useReqMsgIdAsCorrelId) { + this.producer = producer; + this.timeout = timeout; + this.useReqMsgIdAsCorrelId = useReqMsgIdAsCorrelId; + } + + /** + * {@inheritDoc} + */ + public Message sendAndReceive(Message request) throws JMSException { + String id = request.getJMSCorrelationID(); + if(id == null && !useReqMsgIdAsCorrelId){ + throw new IllegalArgumentException("Correlation id is null. Set the JMSCorrelationID header."); + } + + final MessageAdmin admin = MessageAdmin.getAdmin(); + if(useReqMsgIdAsCorrelId) {// msgId not available until after send() is called + // Note: there is only one admin object which is shared between all threads + synchronized (admin) {// interlock with Receiver + producer.send(request); + id=request.getJMSMessageID(); + admin.putRequest(id, request); + } + } else { + admin.putRequest(id, request); + producer.send(request); + } + + try { + if (log.isDebugEnabled()) { + log.debug("wait for reply " + id + " started on " + System.currentTimeMillis()); + } + synchronized (request) { + request.wait(timeout); + } + if (log.isDebugEnabled()) { + log.debug("done waiting for " + id + " ended on " + System.currentTimeMillis()); + } + + } catch (InterruptedException e) { + log.warn("Interrupt exception caught", e); + } + return admin.get(id); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/JMSSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/JMSSampler.java new file mode 100644 index 0000000..94b17e2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/JMSSampler.java @@ -0,0 +1,533 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.protocol.jms.sampler; + +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Map; + +import javax.jms.DeliveryMode; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.Queue; +import javax.jms.QueueConnection; +import javax.jms.QueueConnectionFactory; +import javax.jms.QueueSender; +import javax.jms.QueueSession; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class implements the JMS Point-to-Point sampler + * + */ +public class JMSSampler extends AbstractSampler implements ThreadListener { + + private static final Logger LOGGER = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + private static final int DEFAULT_TIMEOUT = 2000; + private static final String DEFAULT_TIMEOUT_STRING = Integer.toString(DEFAULT_TIMEOUT); + + //++ These are JMX names, and must not be changed + private static final String JNDI_INITIAL_CONTEXT_FACTORY = "JMSSampler.initialContextFactory"; // $NON-NLS-1$ + + private static final String JNDI_CONTEXT_PROVIDER_URL = "JMSSampler.contextProviderUrl"; // $NON-NLS-1$ + + private static final String JNDI_PROPERTIES = "JMSSampler.jndiProperties"; // $NON-NLS-1$ + + private static final String TIMEOUT = "JMSSampler.timeout"; // $NON-NLS-1$ + + private static final String JMS_SELECTOR = "JMSSampler.jmsSelector"; // $NON-NLS-1$ + + private static final String JMS_SELECTOR_DEFAULT = ""; // $NON-NLS-1$ + + private static final String IS_ONE_WAY = "JMSSampler.isFireAndForget"; // $NON-NLS-1$ + + private static final String JMS_PROPERTIES = "arguments"; // $NON-NLS-1$ + + private static final String RECEIVE_QUEUE = "JMSSampler.ReceiveQueue"; // $NON-NLS-1$ + + private static final String XML_DATA = "HTTPSamper.xml_data"; // $NON-NLS-1$ + + private final static String SEND_QUEUE = "JMSSampler.SendQueue"; // $NON-NLS-1$ + + private final static String QUEUE_CONNECTION_FACTORY_JNDI = "JMSSampler.queueconnectionfactory"; // $NON-NLS-1$ + + private static final String IS_NON_PERSISTENT = "JMSSampler.isNonPersistent"; // $NON-NLS-1$ + + private static final String USE_REQ_MSGID_AS_CORRELID = "JMSSampler.useReqMsgIdAsCorrelId"; // $NON-NLS-1$ + + private static final String USE_RES_MSGID_AS_CORRELID = "JMSSampler.useResMsgIdAsCorrelId"; // $NON-NLS-1$ + + private static final boolean USE_RES_MSGID_AS_CORRELID_DEFAULT = false; // Default to be applied + + + //-- + + // Should we use java.naming.security.[principal|credentials] to create the QueueConnection? + private static final boolean USE_SECURITY_PROPERTIES = + JMeterUtils.getPropDefault("JMSSampler.useSecurity.properties", true); // $NON-NLS-1$ + + // + // Member variables + // + /** Queue for receiving messages (if applicable). */ + private transient Queue receiveQueue; + + /** The session with the queueing system. */ + private transient QueueSession session; + + /** Connection to the queueing system. */ + private transient QueueConnection connection; + + /** The executor for (pseudo) synchronous communication. */ + private transient QueueExecutor executor; + + /** Producer of the messages. */ + private transient QueueSender producer; + + private transient Receiver receiverThread = null; + + private transient Throwable thrown = null; + + /** + * {@inheritDoc} + */ + public SampleResult sample(Entry entry) { + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.setSamplerData(getContent()); + res.setSuccessful(false); // Assume failure + res.setDataType(SampleResult.TEXT); + res.sampleStart(); + + try { + TextMessage msg = createMessage(); + if (isOneway()) { + producer.send(msg); + res.setRequestHeaders(Utils.messageProperties(msg)); + res.setResponseOK(); + res.setResponseData("Oneway request has no response data", null); + } else { + if (!useTemporyQueue()) { + msg.setJMSReplyTo(receiveQueue); + } + + Message replyMsg = executor.sendAndReceive(msg); + res.setRequestHeaders(Utils.messageProperties(msg)); + if (replyMsg == null) { + res.setResponseMessage("No reply message received"); + } else { + if (replyMsg instanceof TextMessage) { + res.setResponseData(((TextMessage) replyMsg).getText(), null); + } else { + res.setResponseData(replyMsg.toString(), null); + } + res.setResponseHeaders(Utils.messageProperties(replyMsg)); + res.setResponseOK(); + } + } + } catch (Exception e) { + LOGGER.warn(e.getLocalizedMessage(), e); + if (thrown != null){ + res.setResponseMessage(thrown.toString()); + } else { + res.setResponseMessage(e.getLocalizedMessage()); + } + } + res.sampleEnd(); + return res; + } + + private TextMessage createMessage() throws JMSException { + if (session == null) { + throw new IllegalStateException("Session may not be null while creating message"); + } + TextMessage msg = session.createTextMessage(); + msg.setText(getContent()); + addJMSProperties(msg); + return msg; + } + + private void addJMSProperties(TextMessage msg) throws JMSException { + Map map = getArguments(JMSSampler.JMS_PROPERTIES).getArgumentsAsMap(); + Utils.addJMSProperties(msg, map); + } + + public Arguments getJMSProperties() { + return getArguments(JMSSampler.JMS_PROPERTIES); + } + + public void setJMSProperties(Arguments args) { + setProperty(new TestElementProperty(JMSSampler.JMS_PROPERTIES, args)); + } + + public Arguments getJNDIProperties() { + return getArguments(JMSSampler.JNDI_PROPERTIES); + } + + public void setJNDIProperties(Arguments args) { + setProperty(new TestElementProperty(JMSSampler.JNDI_PROPERTIES, args)); + } + + public String getQueueConnectionFactory() { + return getPropertyAsString(QUEUE_CONNECTION_FACTORY_JNDI); + } + + public void setQueueConnectionFactory(String qcf) { + setProperty(QUEUE_CONNECTION_FACTORY_JNDI, qcf); + } + + public String getSendQueue() { + return getPropertyAsString(SEND_QUEUE); + } + + public void setSendQueue(String name) { + setProperty(SEND_QUEUE, name); + } + + public String getReceiveQueue() { + return getPropertyAsString(RECEIVE_QUEUE); + } + + public void setReceiveQueue(String name) { + setProperty(RECEIVE_QUEUE, name); + } + + public String getContent() { + return getPropertyAsString(XML_DATA); + } + + public void setContent(String content) { + setProperty(XML_DATA, content); + } + + public boolean isOneway() { + return getPropertyAsBoolean(IS_ONE_WAY); + } + + public boolean isNonPersistent() { + return getPropertyAsBoolean(IS_NON_PERSISTENT); + } + + /** + * Which request field to use for correlation? + * + * @return true if correlation should use the request JMSMessageID rather than JMSCorrelationID + */ + public boolean isUseReqMsgIdAsCorrelId() { + return getPropertyAsBoolean(USE_REQ_MSGID_AS_CORRELID); + } + + /** + * Which response field to use for correlation? + * + * @return true if correlation should use the response JMSMessageID rather than JMSCorrelationID + */ + public boolean isUseResMsgIdAsCorrelId() { + return getPropertyAsBoolean(USE_RES_MSGID_AS_CORRELID, USE_RES_MSGID_AS_CORRELID_DEFAULT); + } + + public String getInitialContextFactory() { + return getPropertyAsString(JMSSampler.JNDI_INITIAL_CONTEXT_FACTORY); + } + + public String getContextProvider() { + return getPropertyAsString(JMSSampler.JNDI_CONTEXT_PROVIDER_URL); + } + + public void setIsOneway(boolean isOneway) { + setProperty(new BooleanProperty(IS_ONE_WAY, isOneway)); + } + + public void setNonPersistent(boolean value) { + setProperty(new BooleanProperty(IS_NON_PERSISTENT, value)); + } + + public void setUseReqMsgIdAsCorrelId(boolean value) { + setProperty(new BooleanProperty(USE_REQ_MSGID_AS_CORRELID, value)); + } + + public void setUseResMsgIdAsCorrelId(boolean value) { + setProperty(USE_RES_MSGID_AS_CORRELID, value, USE_RES_MSGID_AS_CORRELID_DEFAULT); + } + + @Override + public String toString() { + return getQueueConnectionFactory() + ", queue: " + getSendQueue(); + } + + public void testIterationStart(LoopIterationEvent event) { + // LOGGER.debug("testIterationStart"); + } + + public void threadStarted() { + logThreadStart(); + + Context context = null; + thrown = null; + try { + context = getInitialContext(); + Object obj = context.lookup(getQueueConnectionFactory()); + if (!(obj instanceof QueueConnectionFactory)) { + String msg = "QueueConnectionFactory expected, but got " + + obj.getClass().getName(); + LOGGER.fatalError(msg); + throw new IllegalStateException(msg); + } + QueueConnectionFactory factory = (QueueConnectionFactory) obj; + Queue sendQueue = (Queue) context.lookup(getSendQueue()); + + if (!useTemporyQueue()) { + receiveQueue = (Queue) context.lookup(getReceiveQueue()); + receiverThread = Receiver.createReceiver(factory, receiveQueue, getPrincipal(context), getCredentials(context) + , isUseResMsgIdAsCorrelId(), getJMSSelector()); + } + + String principal = null; + String credentials = null; + if (USE_SECURITY_PROPERTIES){ + principal = getPrincipal(context); + credentials = getCredentials(context); + } + if (principal != null && credentials != null) { + connection = factory.createQueueConnection(principal, credentials); + } else { + connection = factory.createQueueConnection(); + } + + session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Session created"); + } + + if (getPropertyAsBoolean(IS_ONE_WAY)) { + producer = session.createSender(sendQueue); + if (isNonPersistent()) { + producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); + } + } else { + + if (useTemporyQueue()) { + executor = new TemporaryQueueExecutor(session, sendQueue); + } else { + producer = session.createSender(sendQueue); + if (isNonPersistent()) { + producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); + } + executor = new FixedQueueExecutor(producer, getTimeoutAsInt(), isUseReqMsgIdAsCorrelId()); + } + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Starting connection"); + } + + connection.start(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Connection started"); + } + } catch (Exception e) { + thrown = e; + LOGGER.error(e.getLocalizedMessage(), e); + } catch (NoClassDefFoundError e) { + thrown = e; + LOGGER.error(e.getLocalizedMessage(), e); + } finally { + if (context != null) { + try { + context.close(); + } catch (NamingException ignored) { + // ignore + } + } + } + } + + private Context getInitialContext() throws NamingException { + Hashtable table = new Hashtable(); + + if (getInitialContextFactory() != null && getInitialContextFactory().trim().length() > 0) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Using InitialContext [" + getInitialContextFactory() + "]"); + } + table.put(Context.INITIAL_CONTEXT_FACTORY, getInitialContextFactory()); + } + if (getContextProvider() != null && getContextProvider().trim().length() > 0) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Using Provider [" + getContextProvider() + "]"); + } + table.put(Context.PROVIDER_URL, getContextProvider()); + } + Map map = getArguments(JMSSampler.JNDI_PROPERTIES).getArgumentsAsMap(); + if (LOGGER.isDebugEnabled()) { + if (map.isEmpty()) { + LOGGER.debug("Empty JNDI properties"); + } else { + LOGGER.debug("Number of JNDI properties: " + map.size()); + } + } + for (Map.Entry me : map.entrySet()) { + table.put(me.getKey(), me.getValue()); + } + + Context context = new InitialContext(table); + if (LOGGER.isDebugEnabled()) { + printEnvironment(context); + } + return context; + } + + private void printEnvironment(Context context) throws NamingException { + Hashtable env = context.getEnvironment(); + LOGGER.debug("Initial Context Properties"); + @SuppressWarnings("unchecked") + Enumeration keys = (Enumeration) env.keys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + LOGGER.debug(key + "=" + env.get(key)); + } + } + + private void logThreadStart() { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Thread started " + new Date()); + LOGGER.debug("JMSSampler: [" + Thread.currentThread().getName() + "], hashCode=[" + hashCode() + "]"); + LOGGER.debug("QCF: [" + getQueueConnectionFactory() + "], sendQueue=[" + getSendQueue() + "]"); + LOGGER.debug("Timeout = " + getTimeout() + "]"); + LOGGER.debug("Use temporary queue =" + useTemporyQueue() + "]"); + LOGGER.debug("Reply queue =" + getReceiveQueue() + "]"); + } + } + + private int getTimeoutAsInt() { + if (getPropertyAsInt(TIMEOUT) < 1) { + return DEFAULT_TIMEOUT; + } + return getPropertyAsInt(TIMEOUT); + } + + public String getTimeout() { + return getPropertyAsString(TIMEOUT, DEFAULT_TIMEOUT_STRING); + } + + /** + * {@inheritDoc} + */ + public void threadFinished() { + LOGGER.debug("Thread ended " + new Date()); + + Utils.close(session, LOGGER); + Utils.close(connection, LOGGER); + if (receiverThread != null) { + receiverThread.deactivate(); + } + } + + private boolean useTemporyQueue() { + String recvQueue = getReceiveQueue(); + return recvQueue == null || recvQueue.trim().length() == 0; + } + + public void setArguments(Arguments args) { + setProperty(new TestElementProperty(JMSSampler.JMS_PROPERTIES, args)); + } + + public Arguments getArguments(String name) { + return (Arguments) getProperty(name).getObjectValue(); + } + + public void setTimeout(String s) { + setProperty(JMSSampler.TIMEOUT, s); + } + + /** + * @return String JMS Selector + */ + public String getJMSSelector() { + return getPropertyAsString(JMSSampler.JMS_SELECTOR, JMS_SELECTOR_DEFAULT); + } + + /** + * @param selector String selector + */ + public void setJMSSelector(String selector) { + setProperty(JMSSampler.JMS_SELECTOR, selector, JMS_SELECTOR_DEFAULT); + } + + /** + * @param string + */ + public void setInitialContextFactory(String string) { + setProperty(JNDI_INITIAL_CONTEXT_FACTORY, string); + + } + + /** + * @param string + */ + public void setContextProvider(String string) { + setProperty(JNDI_CONTEXT_PROVIDER_URL, string); + + } + + /** + * get the principal from the context property java.naming.security.principal + * + * @param context + * @return + * @throws NamingException + */ + private String getPrincipal(Context context) throws NamingException{ + Hashtable env = context.getEnvironment(); + return (String) env.get("java.naming.security.principal"); // $NON-NLS-1$ + } + + /** + * get the credentials from the context property java.naming.security.credentials + * + * @param context + * @return + * @throws NamingException + */ + private String getCredentials(Context context) throws NamingException{ + Hashtable env = context.getEnvironment(); + return (String) env.get("java.naming.security.credentials"); // $NON-NLS-1$ + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/MessageAdmin.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/MessageAdmin.java new file mode 100644 index 0000000..41c3e3c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/MessageAdmin.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms.sampler; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.jms.Message; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Administration of messages. + * + */ +public class MessageAdmin { + private static final MessageAdmin SINGLETON = new MessageAdmin(); + + private final Map table = new ConcurrentHashMap(); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private MessageAdmin() { + } + + public static MessageAdmin getAdmin() { + return SINGLETON; + } + + /** + * @param request + */ + public void putRequest(String id, Message request) { + if (log.isDebugEnabled()) { + log.debug("REQ_ID [" + id + "]"); + } + table.put(id, new PlaceHolder(request)); + } + + public void putReply(String id, Message reply) { + PlaceHolder holder = table.get(id); + if (log.isDebugEnabled()) { + log.debug("RPL_ID [" + id + "] for holder " + holder); + } + if (holder != null) { + holder.setReply(reply); + Object obj = holder.getRequest(); + // Findbugs : False positive + synchronized (obj) { + obj.notify(); + } + } else { + if (log.isDebugEnabled()) { + log.debug("Failed to match reply: " + reply); + } + } + } + + /** + * Get the reply message. + * + * @param id + * the id of the message + * @return the received message or null + */ + public Message get(String id) { + PlaceHolder holder = table.remove(id); + if (log.isDebugEnabled()) { + log.debug("GET_ID [" + id + "] for " + holder); + } + if (holder == null || !holder.hasReply()) { + log.debug("Message with " + id + " not found."); + } + return holder==null ? null : (Message) holder.getReply(); + } +} + +class PlaceHolder { + private final Object request; + + private Object reply; + + PlaceHolder(Object original) { + this.request = original; + } + + void setReply(Object reply) { + this.reply = reply; + } + + public Object getReply() { + return reply; + } + + public Object getRequest() { + return request; + } + + boolean hasReply() { + return reply != null; + } + + @Override + public String toString() { + return "request=" + request + ", reply=" + reply; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/PublisherSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/PublisherSampler.java new file mode 100644 index 0000000..17ad0b4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/PublisherSampler.java @@ -0,0 +1,479 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.protocol.jms.sampler; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.naming.NamingException; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jmeter.protocol.jms.client.ClientPool; +import org.apache.jmeter.protocol.jms.client.InitialContextFactory; +import org.apache.jmeter.protocol.jms.client.Publisher; +import org.apache.jmeter.protocol.jms.control.gui.JMSPublisherGui; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.io.TextFile; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.XStream; + +/** + * This class implements the JMS Publisher sampler. + */ +public class PublisherSampler extends BaseJMSSampler implements TestListener { + + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + //++ These are JMX file names and must not be changed + private static final String INPUT_FILE = "jms.input_file"; //$NON-NLS-1$ + + private static final String RANDOM_PATH = "jms.random_path"; //$NON-NLS-1$ + + private static final String TEXT_MSG = "jms.text_message"; //$NON-NLS-1$ + + private static final String CONFIG_CHOICE = "jms.config_choice"; //$NON-NLS-1$ + + private static final String MESSAGE_CHOICE = "jms.config_msg_type"; //$NON-NLS-1$ + + private static final String NON_PERSISTENT_DELIVERY = "jms.non_persistent"; //$NON-NLS-1$ + + private static final String JMS_PROPERTIES = "jms.jmsProperties"; // $NON-NLS-1$ + + //-- + + // Does not need to be synch. because it is only accessed from the sampler thread + // The ClientPool does access it in a different thread, but ClientPool is fully synch. + private transient Publisher publisher = null; + + private static final FileServer FSERVER = FileServer.getFileServer(); + + // Cache for file. Only used by sample() in a single thread + private String file_contents = null; + // Cache for object-message, only used when parsing from a file because in text-area + // property replacement might have been used + private Serializable object_msg_file_contents = null; + + public PublisherSampler() { + } + + /** + * the implementation calls testStarted() without any parameters. + */ + public void testStarted(String test) { + testStarted(); + } + + /** + * the implementation calls testEnded() without any parameters. + */ + public void testEnded(String host) { + testEnded(); + } + + /** + * endTest cleans up the client + * + * @see junit.framework.TestListener#endTest(junit.framework.Test) + */ + public void testEnded() { + log.debug("PublisherSampler.testEnded called"); + ClientPool.clearClient(); + InitialContextFactory.close(); + } + + public void testStarted() { + } + + /** + * NO implementation provided for the sampler. It is necessary in this case. + */ + public void testIterationStart(LoopIterationEvent event) { + } + + /** + * initialize the Publisher client. + * @throws JMSException + * @throws NamingException + * + */ + private void initClient() throws JMSException, NamingException { + publisher = new Publisher(getUseJNDIPropertiesAsBoolean(), getJNDIInitialContextFactory(), + getProviderUrl(), getConnectionFactory(), getDestination(), isUseAuth(), getUsername(), + getPassword(), isDestinationStatic(), getUseNonPersistentDelivery()); + ClientPool.addClient(publisher); + log.debug("PublisherSampler.initClient called"); + } + + /** + * The implementation will publish n messages within a for loop. Once n + * messages are published, it sets the attributes of SampleResult. + * + * @return the populated sample result + */ + @Override + public SampleResult sample() { + SampleResult result = new SampleResult(); + result.setSampleLabel(getName()); + result.setSuccessful(false); // Assume it will fail + result.setResponseCode("000"); // ditto $NON-NLS-1$ + if (publisher == null) { + try { + initClient(); + } catch (JMSException e) { + result.setResponseMessage(e.toString()); + return result; + } catch (NamingException e) { + result.setResponseMessage(e.toString()); + return result; + } + } + StringBuilder buffer = new StringBuilder(); + StringBuilder propBuffer = new StringBuilder(); + int loop = getIterationCount(); + result.sampleStart(); + String type = getMessageChoice(); + try { + for (int idx = 0; idx < loop; idx++) { + if (JMSPublisherGui.TEXT_MSG_RSC.equals(type)){ + String tmsg = getMessageContent(); + Message msg = publisher.publish(tmsg, getDestination(), getJMSProperties().getArgumentsAsMap()); + buffer.append(tmsg); + Utils.messageProperties(propBuffer, msg); + } else if (JMSPublisherGui.MAP_MSG_RSC.equals(type)){ + Map m = getMapContent(); + Message msg = publisher.publish(m, getDestination(), getJMSProperties().getArgumentsAsMap()); + Utils.messageProperties(propBuffer, msg); + } else if (JMSPublisherGui.OBJECT_MSG_RSC.equals(type)){ + Serializable omsg = getObjectContent(); + Message msg = publisher.publish(omsg, getDestination(), getJMSProperties().getArgumentsAsMap()); + Utils.messageProperties(propBuffer, msg); + } else { + throw new JMSException(type+ " is not recognised"); + } + } + result.setResponseCodeOK(); + result.setResponseMessage(loop + " messages published"); + result.setSuccessful(true); + result.setSamplerData(buffer.toString()); + result.setSampleCount(loop); + result.setRequestHeaders(propBuffer.toString()); + } catch (Exception e) { + result.setResponseMessage(e.toString()); + } finally { + result.sampleEnd(); + } + return result; + } + + + private Map getMapContent() throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { + Map m = new HashMap(); + String text = getMessageContent(); + String[] lines = text.split("\n"); + for (String line : lines){ + String[] parts = line.split(",",3); + if (parts.length != 3) { + throw new IllegalArgumentException("line must have 3 parts: "+line); + } + String name = parts[0]; + String type = parts[1]; + if (!type.contains(".")){// Allow shorthand names + type = "java.lang."+type; + } + String value = parts[2]; + Object obj; + if (type.equals("java.lang.String")){ + obj = value; + } else { + Class clazz = Class.forName(type); + Method method = clazz.getMethod("valueOf", new Class[]{String.class}); + obj = method.invoke(clazz, value); + } + m.put(name, obj); + } + return m; + } + + /** + * Method will check the setting and get the contents for the message. + * + * @return the contents for the message + */ + private String getMessageContent() { + if (getConfigChoice().equals(JMSPublisherGui.USE_FILE_RSC)) { + // in the case the test uses a file, we set it locally and + // prevent loading the file repeatedly + if (file_contents == null) { + file_contents = getFileContent(getInputFile()); + } + return file_contents; + } else if (getConfigChoice().equals(JMSPublisherGui.USE_RANDOM_RSC)) { + // Maybe we should consider creating a global cache for the + // random files to make JMeter more efficient. + String fname = FSERVER.getRandomFile(getRandomPath(), new String[] { ".txt", ".obj" }) + .getAbsolutePath(); + return getFileContent(fname); + } else { + return getTextMessage(); + } + } + + /** + * The implementation uses TextFile to load the contents of the file and + * returns a string. + * + * @param path + * @return the contents of the file + */ + public String getFileContent(String path) { + TextFile tf = new TextFile(path); + return tf.getText(); + } + + /** + * This method will load the contents for the JMS Object Message. + * The contents are either loaded from file (might be cached), random file + * or from the GUI text-area. + * + * @return Serialized object as loaded from the specified input file + */ + private Serializable getObjectContent() { + if (getConfigChoice().equals(JMSPublisherGui.USE_FILE_RSC)) { + // in the case the test uses a file, we set it locally and + // prevent loading the file repeatedly + if (object_msg_file_contents == null) { + object_msg_file_contents = getFileObjectContent(getInputFile()); + } + + return object_msg_file_contents; + } else if (getConfigChoice().equals(JMSPublisherGui.USE_RANDOM_RSC)) { + // Maybe we should consider creating a global cache for the + // random files to make JMeter more efficient. + final String fname = FSERVER.getRandomFile(getRandomPath(), new String[] {".txt", ".obj"}) + .getAbsolutePath(); + + return getFileObjectContent(fname); + } else { + final String xmlMessage = getTextMessage(); + return transformXmlToObjectMessage(xmlMessage); + } + } + + /** + * Try to load an object from a provided file, so that it can be used as body + * for a JMS message. + * An {@link IllegalStateException} will be thrown if loading the object fails. + * + * @param path Path to the file that will be serialized + * @return Serialized object instance + */ + private static Serializable getFileObjectContent(final String path) { + Serializable readObject = null; + InputStream inputStream = null; + try { + inputStream = new FileInputStream(path); + XStream xstream = new XStream(); + readObject = (Serializable) xstream.fromXML(inputStream, readObject); + } catch (Exception e) { + log.error(e.getLocalizedMessage(), e); + throw new IllegalStateException("Unable to load object instance from file", e); + } finally { + JOrphanUtils.closeQuietly(inputStream); + } + return readObject; + } + + /** + * Try to load an object via XStream from XML text, so that it can be used as body + * for a JMS message. + * An {@link IllegalStateException} will be thrown if transforming the XML to an object fails. + * + * @param xmlMessage String containing XML text as input for the transformation + * @return Serialized object instance + */ + private static Serializable transformXmlToObjectMessage(final String xmlMessage) { + Serializable readObject = null; + try { + XStream xstream = new XStream(); + readObject = (Serializable) xstream.fromXML(xmlMessage, readObject); + } catch (Exception e) { + log.error(e.getLocalizedMessage(), e); + throw new IllegalStateException("Unable to load object instance from text", e); + } + return readObject; + } + + // ------------- get/set properties ----------------------// + /** + * set the source of the message + * + * @param choice + */ + public void setConfigChoice(String choice) { + setProperty(CONFIG_CHOICE, choice); + } + + // These static variables are only used to convert existing files + private static final String USE_FILE_LOCALNAME = JMeterUtils.getResString(JMSPublisherGui.USE_FILE_RSC); + private static final String USE_RANDOM_LOCALNAME = JMeterUtils.getResString(JMSPublisherGui.USE_RANDOM_RSC); + + /** + * return the source of the message + * Converts from old JMX files which used the local language string + */ + public String getConfigChoice() { + // Allow for the old JMX file which used the local language string + String config = getPropertyAsString(CONFIG_CHOICE); + if (config.equals(USE_FILE_LOCALNAME) + || config.equals(JMSPublisherGui.USE_FILE_RSC)){ + return JMSPublisherGui.USE_FILE_RSC; + } + if (config.equals(USE_RANDOM_LOCALNAME) + || config.equals(JMSPublisherGui.USE_RANDOM_RSC)){ + return JMSPublisherGui.USE_RANDOM_RSC; + } + return config; // will be the 3rd option, which is not checked specifically + } + + /** + * set the type of the message + * + * @param choice + */ + public void setMessageChoice(String choice) { + setProperty(MESSAGE_CHOICE, choice); + } + + /** + * return the type of the message (Text, Object, Map) + * + */ + public String getMessageChoice() { + return getPropertyAsString(MESSAGE_CHOICE); + } + + /** + * set the input file for the publisher + * + * @param file + */ + public void setInputFile(String file) { + setProperty(INPUT_FILE, file); + } + + /** + * return the path of the input file + * + */ + public String getInputFile() { + return getPropertyAsString(INPUT_FILE); + } + + /** + * set the random path for the messages + * + * @param path + */ + public void setRandomPath(String path) { + setProperty(RANDOM_PATH, path); + } + + /** + * return the random path for messages + * + */ + public String getRandomPath() { + return getPropertyAsString(RANDOM_PATH); + } + + /** + * set the text for the message + * + * @param message + */ + public void setTextMessage(String message) { + setProperty(TEXT_MSG, message); + } + + /** + * return the text for the message + * + */ + public String getTextMessage() { + return getPropertyAsString(TEXT_MSG); + } + + /** + * @param value boolean use NON_PERSISTENT + */ + public void setUseNonPersistentDelivery(boolean value) { + setProperty(NON_PERSISTENT_DELIVERY, value, false); + } + + /** + * @return true if NON_PERSISTENT delivery must be used + */ + public boolean getUseNonPersistentDelivery() { + return getPropertyAsBoolean(NON_PERSISTENT_DELIVERY, false); + } + + public void setArguments(Arguments args) { + setProperty(new TestElementProperty(JMS_PROPERTIES, args)); + } + + public Arguments getArguments(String name) { + return (Arguments) getProperty(name).getObjectValue(); + } + + /** + * @return Arguments JMS Properties + */ + public Arguments getJMSProperties() { + Arguments arguments = getArguments(JMS_PROPERTIES); + if(arguments == null) { + arguments = new Arguments(); + setArguments(arguments); + } + return arguments; + } + + /** + * @param args Arguments JMS Properties + */ + public void setJMSProperties(Arguments args) { + setProperty(new TestElementProperty(JMS_PROPERTIES, args)); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/QueueExecutor.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/QueueExecutor.java new file mode 100644 index 0000000..cd1f496 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/QueueExecutor.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms.sampler; + +import javax.jms.JMSException; +import javax.jms.Message; + +/** + * Executor for (pseudo) synchronous communication.
+ * Created on: October 28, 2004 + * + * @version $Revision: 674365 $ + */ +public interface QueueExecutor { + /** + * Sends and receives a message. + * + * @param request + * the message to send + * @return the received message or null + * @throws JMSException + * in case of an exception from the messaging system + */ + public abstract Message sendAndReceive(Message request) throws JMSException; + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/Receiver.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/Receiver.java new file mode 100644 index 0000000..0bde3ad --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/Receiver.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms.sampler; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.Session; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Receiver of pseudo-synchronous reply messages. + * + */ +public class Receiver implements Runnable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private volatile boolean active; + + private final Session session; + + private final MessageConsumer consumer; + + private final Connection conn; + + private final boolean useResMsgIdAsCorrelId; + + + /** + * Constructor + * @param factory + * @param receiveQueue Receive Queue + * @param principal Username + * @param credentials Password + * @param useResMsgIdAsCorrelId + * @param jmsSelector JMS Selector + * @throws JMSException + */ + private Receiver(ConnectionFactory factory, Destination receiveQueue, String principal, String credentials, boolean useResMsgIdAsCorrelId, String jmsSelector) throws JMSException { + if (null != principal && null != credentials) { + log.info("creating receiver WITH authorisation credentials. UseResMsgId="+useResMsgIdAsCorrelId); + conn = factory.createConnection(principal, credentials); + }else{ + log.info("creating receiver without authorisation credentials. UseResMsgId="+useResMsgIdAsCorrelId); + conn = factory.createConnection(); + } + session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + if(log.isDebugEnabled()) { + log.debug("Receiver - ctor. Creating consumer with JMS Selector:"+jmsSelector); + } + if(StringUtils.isEmpty(jmsSelector)) { + consumer = session.createConsumer(receiveQueue); + } else { + consumer = session.createConsumer(receiveQueue, jmsSelector); + } + this.useResMsgIdAsCorrelId = useResMsgIdAsCorrelId; + log.debug("Receiver - ctor. Starting connection now"); + conn.start(); + log.info("Receiver - ctor. Connection to messaging system established"); + } + + /** + * Create a receiver to process responses. + * + * @param factory + * @param receiveQueue + * @param principal + * @param credentials + * @param useResMsgIdAsCorrelId true if should use JMSMessageId, false if should use JMSCorrelationId + * @param jmsSelector JMS selector + * @return the Receiver which will process the responses + * @throws JMSException + */ + public static Receiver createReceiver(ConnectionFactory factory, Destination receiveQueue, + String principal, String credentials, boolean useResMsgIdAsCorrelId, String jmsSelector) + throws JMSException { + Receiver receiver = new Receiver(factory, receiveQueue, principal, credentials, useResMsgIdAsCorrelId, jmsSelector); + Thread thread = new Thread(receiver, Thread.currentThread().getName()+"-JMS-Receiver"); + thread.start(); + return receiver; + } + + public void run() { + active = true; + Message reply; + + while (active) { + reply = null; + try { + reply = consumer.receive(5000); + if (reply != null) { + String messageKey; + final MessageAdmin admin = MessageAdmin.getAdmin(); + if (useResMsgIdAsCorrelId){ + messageKey = reply.getJMSMessageID(); + synchronized (admin) {// synchronize with FixedQueueExecutor + admin.putReply(messageKey, reply); + } + } else { + messageKey = reply.getJMSCorrelationID(); + if (messageKey == null) {// JMSMessageID cannot be null + log.warn("Received message with correlation id null. Discarding message ..."); + } else { + admin.putReply(messageKey, reply); + } + } + } + + } catch (JMSException e1) { + log.error("Error handling receive",e1); + } + } + Utils.close(consumer, log); + Utils.close(session, log); + Utils.close(conn, log); + } + + public void deactivate() { + active = false; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/SubscriberSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/SubscriberSampler.java new file mode 100644 index 0000000..de5cac1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/SubscriberSampler.java @@ -0,0 +1,485 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.protocol.jms.sampler; + +import java.util.Enumeration; + +import javax.jms.JMSException; +import javax.jms.MapMessage; +import javax.jms.Message; +import javax.jms.TextMessage; +import javax.naming.NamingException; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.protocol.jms.Utils; +import org.apache.jmeter.protocol.jms.client.InitialContextFactory; +import org.apache.jmeter.protocol.jms.client.ReceiveSubscriber; +import org.apache.jmeter.protocol.jms.control.gui.JMSSubscriberGui; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class implements the JMS Subscriber sampler. + * It supports both receive and onMessage strategies via the ReceiveSubscriber class. + * + */ +// TODO: do we need to implement any kind of connection pooling? +// If so, which connections should be shared? +// Should threads share connections to the same destination? +// What about cross-thread sharing? + +// Note: originally the code did use the ClientPool to "share" subscribers, however since the +// key was "this" and each sampler is unique - nothing was actually shared. + +public class SubscriberSampler extends BaseJMSSampler implements Interruptible, ThreadListener, TestListener { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Default wait (ms) for a message if timeouts are not enabled + // This is the maximum time the sampler can be blocked. + private static final long DEFAULT_WAIT = 500L; + + // No need to synch/ - only used by sampler and ClientPool (which does its own synch) + private transient ReceiveSubscriber SUBSCRIBER = null; + + private transient volatile boolean interrupted = false; + + private transient long timeout; + + private transient boolean useReceive; + + // This will be null if initialization succeeds. + private transient Exception exceptionDuringInit; + + // If true, start/stop subscriber for each sample + private transient boolean stopBetweenSamples; + + // Don't change the string, as it is used in JMX files + private static final String CLIENT_CHOICE = "jms.client_choice"; // $NON-NLS-1$ + private static final String TIMEOUT = "jms.timeout"; // $NON-NLS-1$ + private static final String TIMEOUT_DEFAULT = ""; // $NON-NLS-1$ + private static final String DURABLE_SUBSCRIPTION_ID = "jms.durableSubscriptionId"; // $NON-NLS-1$ + private static final String CLIENT_ID = "jms.clientId"; // $NON-NLS-1$ + private static final String JMS_SELECTOR = "jms.selector"; // $NON-NLS-1$ + private static final String DURABLE_SUBSCRIPTION_ID_DEFAULT = ""; + private static final String CLIENT_ID_DEFAULT = ""; // $NON-NLS-1$ + private static final String JMS_SELECTOR_DEFAULT = ""; // $NON-NLS-1$ + private static final String STOP_BETWEEN = "jms.stop_between_samples"; // $NON-NLS-1$ + private static final String SEPARATOR = "jms.separator"; // $NON-NLS-1$ + private static final String SEPARATOR_DEFAULT = ""; // $NON-NLS-1$ + + + private transient boolean START_ON_SAMPLE = false; + + private transient String separator; + + public SubscriberSampler() { + super(); + } + + /** + * Create the OnMessageSubscriber client and set the sampler as the message + * listener. + * @throws JMSException + * @throws NamingException + * + */ + private void initListenerClient() throws JMSException, NamingException { + SUBSCRIBER = new ReceiveSubscriber(0, getUseJNDIPropertiesAsBoolean(), getJNDIInitialContextFactory(), + getProviderUrl(), getConnectionFactory(), getDestination(), getDurableSubscriptionId(), + getClientId(), getJmsSelector(), isUseAuth(), getUsername(), getPassword()); + setupSeparator(); + log.debug("SubscriberSampler.initListenerClient called"); + } + + /** + * Create the ReceiveSubscriber client for the sampler. + * @throws NamingException + * @throws JMSException + */ + private void initReceiveClient() throws NamingException, JMSException { + SUBSCRIBER = new ReceiveSubscriber(getUseJNDIPropertiesAsBoolean(), + getJNDIInitialContextFactory(), getProviderUrl(), getConnectionFactory(), getDestination(), + getDurableSubscriptionId(), getClientId(), getJmsSelector(), isUseAuth(), getUsername(), getPassword()); + setupSeparator(); + log.debug("SubscriberSampler.initReceiveClient called"); + } + + /** + * sample method will check which client it should use and call the + * appropriate client specific sample method. + * + * @return the appropriate sample result + */ + // TODO - should we call start() and stop()? + @Override + public SampleResult sample() { + // run threadStarted only if Destination setup on each sample + if (!isDestinationStatic()) { + threadStarted(true); + } + SampleResult result = new SampleResult(); + result.setDataType(SampleResult.TEXT); + result.setSampleLabel(getName()); + result.sampleStart(); + if (exceptionDuringInit != null) { + result.sampleEnd(); + result.setSuccessful(false); + result.setResponseCode("000"); + result.setResponseMessage(exceptionDuringInit.toString()); + return result; + } + if (stopBetweenSamples){ // If so, we need to start collection here + try { + SUBSCRIBER.start(); + } catch (JMSException e) { + log.warn("Problem starting subscriber", e); + } + } + StringBuilder buffer = new StringBuilder(); + StringBuilder propBuffer = new StringBuilder(); + + int loop = getIterationCount(); + int read = 0; + + long until = 0L; + long now = System.currentTimeMillis(); + if (timeout > 0) { + until = timeout + now; + } + while (!interrupted + && (until == 0 || now < until) + && read < loop) { + Message msg; + try { + msg = SUBSCRIBER.getMessage(calculateWait(until, now)); + if (msg != null){ + read++; + extractContent(buffer, propBuffer, msg, (read == loop)); + } + } catch (JMSException e) { + log.warn("Error "+e.toString()); + } + now = System.currentTimeMillis(); + } + result.sampleEnd(); + result.setResponseMessage(read + " samples messages received"); + if (getReadResponseAsBoolean()) { + result.setResponseData(buffer.toString().getBytes()); // TODO - charset? + } else { + result.setBytes(buffer.toString().getBytes().length); // TODO - charset? + } + result.setResponseHeaders(propBuffer.toString()); + if (read == 0) { + result.setResponseCode("404"); // Not found + result.setSuccessful(false); + } else { // TODO set different status if not enough messages found? + result.setResponseCodeOK(); + result.setSuccessful(true); + } + result.setResponseMessage(read + " message(s) received successfully"); + result.setSamplerData(loop + " messages expected"); + result.setSampleCount(read); + + if (stopBetweenSamples){ + try { + SUBSCRIBER.stop(); + } catch (JMSException e) { + log.warn("Problem stopping subscriber", e); + } + } + // run threadFinished only if Destination setup on each sample (stop Listen queue) + if (!isDestinationStatic()) { + threadFinished(true); + } + return result; + } + + /** + * Calculate the wait time, will never be more than DEFAULT_WAIT. + * + * @param until target end time or 0 if timeouts not active + * @param now current time + * @return wait time + */ + private long calculateWait(long until, long now) { + if (until == 0) return DEFAULT_WAIT; // Timeouts not active + long wait = until - now; // How much left + return wait > DEFAULT_WAIT ? DEFAULT_WAIT : wait; + } + + private void extractContent(StringBuilder buffer, StringBuilder propBuffer, + Message msg, boolean isLast) { + if (msg != null) { + try { + if (msg instanceof TextMessage){ + buffer.append(((TextMessage) msg).getText()); + } else if (msg instanceof MapMessage){ + MapMessage mapm = (MapMessage) msg; + @SuppressWarnings("unchecked") // MapNames are Strings + Enumeration enumb = mapm.getMapNames(); + while(enumb.hasMoreElements()){ + String name = enumb.nextElement(); + Object obj = mapm.getObject(name); + buffer.append(name); + buffer.append(","); + buffer.append(obj.getClass().getCanonicalName()); + buffer.append(","); + buffer.append(obj); + buffer.append("\n"); + } + } + Utils.messageProperties(propBuffer, msg); + if(!isLast && !StringUtils.isEmpty(separator)) { + propBuffer.append(separator); + buffer.append(separator); + } + } catch (JMSException e) { + log.error(e.getMessage()); + } + } + } + + /** + * Initialise the thread-local variables. + *
+ * {@inheritDoc} + */ + public void threadStarted() { + // Disabled thread start if listen on sample choice + if (isDestinationStatic() || START_ON_SAMPLE) { + timeout = getTimeoutAsLong(); + interrupted = false; + exceptionDuringInit = null; + useReceive = getClientChoice().equals(JMSSubscriberGui.RECEIVE_RSC); + stopBetweenSamples = isStopBetweenSamples(); + if (useReceive) { + try { + initReceiveClient(); + if (!stopBetweenSamples){ // Don't start yet if stop between samples + SUBSCRIBER.start(); + } + } catch (NamingException e) { + exceptionDuringInit = e; + } catch (JMSException e) { + exceptionDuringInit = e; + } + } else { + try { + initListenerClient(); + if (!stopBetweenSamples){ // Don't start yet if stop between samples + SUBSCRIBER.start(); + } + } catch (JMSException e) { + exceptionDuringInit = e; + } catch (NamingException e) { + exceptionDuringInit = e; + } + } + if (exceptionDuringInit != null){ + log.error("Could not initialise client",exceptionDuringInit); + } + } + } + + public void threadStarted(boolean wts) { + if (wts) { + START_ON_SAMPLE = true; // listen on sample + } + threadStarted(); + } + + /** + * Close subscriber. + *
+ * {@inheritDoc} + */ + public void threadFinished() { + if (SUBSCRIBER != null){ // Can be null if init fails + SUBSCRIBER.close(); + } + } + + public void threadFinished(boolean wts) { + if (wts) { + START_ON_SAMPLE = false; // listen on sample + } + threadFinished(); + } + + /** + * Handle an interrupt of the test. + */ + public boolean interrupt() { + boolean oldvalue = interrupted; + interrupted = true; // so we break the loops in SampleWithListener and SampleWithReceive + return !oldvalue; + } + + // ----------- get/set methods ------------------- // + /** + * Set the client choice. There are two options: ReceiveSusbscriber and + * OnMessageSubscriber. + */ + public void setClientChoice(String choice) { + setProperty(CLIENT_CHOICE, choice); + } + + /** + * Return the client choice. + * + * @return the client choice, either RECEIVE_RSC or ON_MESSAGE_RSC + */ + public String getClientChoice() { + String choice = getPropertyAsString(CLIENT_CHOICE); + // Convert the old test plan entry (which is the language dependent string) to the resource name + if (choice.equals(RECEIVE_STR)){ + choice = JMSSubscriberGui.RECEIVE_RSC; + } else if (!choice.equals(JMSSubscriberGui.RECEIVE_RSC)){ + choice = JMSSubscriberGui.ON_MESSAGE_RSC; + } + return choice; + } + + public String getTimeout(){ + return getPropertyAsString(TIMEOUT, TIMEOUT_DEFAULT); + } + + public long getTimeoutAsLong(){ + return getPropertyAsLong(TIMEOUT, 0L); + } + + public void setTimeout(String timeout){ + setProperty(TIMEOUT, timeout, TIMEOUT_DEFAULT); + } + + public String getDurableSubscriptionId(){ + return getPropertyAsString(DURABLE_SUBSCRIPTION_ID); + } + + /** + * @return JMS Client ID + */ + public String getClientId() { + return getPropertyAsString(CLIENT_ID, CLIENT_ID_DEFAULT); + } + + /** + * @return JMS selector + */ + public String getJmsSelector() { + return getPropertyAsString(JMS_SELECTOR, JMS_SELECTOR_DEFAULT); + } + + public void setDurableSubscriptionId(String durableSubscriptionId){ + setProperty(DURABLE_SUBSCRIPTION_ID, durableSubscriptionId, DURABLE_SUBSCRIPTION_ID_DEFAULT); + } + + /** + * @param clientId JMS CLient id + */ + public void setClientID(String clientId) { + setProperty(CLIENT_ID, clientId, CLIENT_ID_DEFAULT); + } + + /** + * @param jmsSelector JMS Selector + */ + public void setJmsSelector(String jmsSelector) { + setProperty(JMS_SELECTOR, jmsSelector, JMS_SELECTOR_DEFAULT); + } + + /** + * @return Separator for sampler results + */ + public String getSeparator() { + return getPropertyAsString(SEPARATOR, SEPARATOR_DEFAULT); + } + + /** + * Separator for sampler results + * @param text + */ + public void setSeparator(String text) { + setProperty(SEPARATOR, text, SEPARATOR_DEFAULT); + } + + // This was the old value that was checked for + private final static String RECEIVE_STR = JMeterUtils.getResString(JMSSubscriberGui.RECEIVE_RSC); // $NON-NLS-1$ + + public boolean isStopBetweenSamples() { + return getPropertyAsBoolean(STOP_BETWEEN, false); + } + + public void setStopBetweenSamples(boolean selected) { + setProperty(STOP_BETWEEN, selected, false); + } + + /** + * {@inheritDoc} + */ + public void testEnded() { + InitialContextFactory.close(); + } + + /** + * {@inheritDoc} + */ + public void testEnded(String host) { + testEnded(); + } + + /** + * {@inheritDoc} + */ + public void testIterationStart(LoopIterationEvent event) { + // NOOP + } + + /** + * {@inheritDoc} + */ + public void testStarted() { + testStarted(""); + } + + /** + * {@inheritDoc} + */ + public void testStarted(String host) { + // NOOP + } + + /** + * + */ + private void setupSeparator() { + separator = getSeparator(); + separator = separator.replace("\\t", "\t"); + separator = separator.replace("\\n", "\n"); + separator = separator.replace("\\r", "\r"); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/TemporaryQueueExecutor.java b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/TemporaryQueueExecutor.java new file mode 100644 index 0000000..5ea2035 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/jms/sampler/TemporaryQueueExecutor.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.jms.sampler; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.Queue; +import javax.jms.QueueRequestor; +import javax.jms.QueueSession; + +/** + * Request/reply executor with a temporary reply queue.
+ * + * Used by JMS Sampler (Point to Point) + * + * Created on: October 28, 2004 + * + * @version $Revision: 908219 $ + */ +public class TemporaryQueueExecutor implements QueueExecutor { + /** The sender and receiver. */ + private final QueueRequestor requestor; + + /** + * Constructor. + * + * @param session + * the session to use to send the message + * @param destination + * the queue to send the message on + * @throws JMSException + */ + public TemporaryQueueExecutor(QueueSession session, Queue destination) throws JMSException { + requestor = new QueueRequestor(session, destination); + } + + /** + * {@inheritDoc} + */ + public Message sendAndReceive(Message request) throws JMSException { + return requestor.request(request); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgument.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgument.java new file mode 100644 index 0000000..bd77c0a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgument.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ldap.config.gui; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.property.StringProperty; + +/******************************************************************************* + * + * Class representing an argument. Each argument consists of a name/value and + * opcode combination, as well as (optional) metadata. + * + * author Dolf Smits(Dolf.Smits@Siemens.com) created Aug 09 2003 11:00 AM + * company Siemens Netherlands N.V.. + * + * Based on the work of: + * + * author Michael Stover author Mark Walsh + */ + +public class LDAPArgument extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + // ** These constants are used in the JMX files, and so must not be changed ** + + /** Name used to store the argument's name. */ + private static final String ARG_NAME = "Argument.name"; //$NON-NLS$ + + /** Name used to store the argument's value. */ + private static final String VALUE = "Argument.value"; //$NON-NLS$ + + /** Name used to store the argument's value. */ + private static final String OPCODE = "Argument.opcode"; //$NON-NLS$ + + /** Name used to store the argument's metadata. */ + private static final String METADATA = "Argument.metadata"; //$NON-NLS$ + + /** + * Create a new Argument without a name, value, or metadata. + */ + public LDAPArgument() { + } + + /** + * Create a new Argument with the specified name and value, and no metadata. + * + * @param name + * the argument name + * @param value + * the argument value + */ + public LDAPArgument(String name, String value, String opcode) { + setProperty(new StringProperty(ARG_NAME, name)); + setProperty(new StringProperty(VALUE, value)); + setProperty(new StringProperty(OPCODE, opcode)); + } + + /** + * Create a new Argument with the specified name, value, and metadata. + * + * @param name + * the argument name + * @param value + * the argument value + * @param metadata + * the argument metadata + */ + public LDAPArgument(String name, String value, String opcode, String metadata) { + setProperty(new StringProperty(ARG_NAME, name)); + setProperty(new StringProperty(VALUE, value)); + setProperty(new StringProperty(OPCODE, opcode)); + setProperty(new StringProperty(METADATA, metadata)); + } + + /** + * Set the name of the Argument. + * + * @param newName + * the new name + */ + @Override + public void setName(String newName) { + setProperty(new StringProperty(ARG_NAME, newName)); + } + + /** + * Get the name of the Argument. + * + * @return the attribute's name + */ + @Override + public String getName() { + return getPropertyAsString(ARG_NAME); + } + + /** + * Sets the value of the Argument. + * + * @param newValue + * the new value + */ + public void setValue(String newValue) { + setProperty(new StringProperty(VALUE, newValue)); + } + + /** + * Gets the value of the Argument object. + * + * @return the attribute's value + */ + public String getValue() { + return getPropertyAsString(VALUE); + } + + /** + * Sets the opcode of the Argument. + * + * @param newOpcode + * the new value + */ + public void setOpcode(String newOpcode) { + setProperty(new StringProperty(OPCODE, newOpcode)); + } + + /** + * Gets the opcode of the Argument object. + * + * @return the attribute's value + */ + public String getOpcode() { + return getPropertyAsString(OPCODE); + } + + /** + * Sets the Meta Data attribute of the Argument. + * + * @param newMetaData + * the new metadata + */ + public void setMetaData(String newMetaData) { + setProperty(new StringProperty(METADATA, newMetaData)); + } + + /** + * Gets the Meta Data attribute of the Argument. + * + * @return the MetaData value + */ + public String getMetaData() { + return getPropertyAsString(METADATA); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArguments.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArguments.java new file mode 100644 index 0000000..c295589 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArguments.java @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ldap.config.gui; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.config.ConfigTestElement; + +/** + * A set of LDAPArgument objects. author Dolf Smits(Dolf.Smits@Siemens.com) + * created Aug 09 2003 11:00 AM company Siemens Netherlands N.V.. + * + * Based on the work of: + * + * author Michael Stover author Mark Walsh + */ + +public class LDAPArguments extends ConfigTestElement implements Serializable { + private static final long serialVersionUID = 240L; + + /** The name of the property used to store the arguments. */ + public static final String ARGUMENTS = "Arguments.arguments"; //$NON-NLS$ + + /** + * Create a new Arguments object with no arguments. + */ + public LDAPArguments() { + setProperty(new CollectionProperty(ARGUMENTS, new ArrayList())); + } + + /** + * Get the arguments. + * + * @return the arguments + */ + public CollectionProperty getArguments() { + return (CollectionProperty) getProperty(ARGUMENTS); + } + + /** + * Clear the arguments. + */ + @Override + public void clear() { + super.clear(); + setProperty(new CollectionProperty(ARGUMENTS, new ArrayList())); + } + + /** + * Set the list of arguments. Any existing arguments will be lost. + * + * @param arguments + * the new arguments + */ + public void setArguments(List arguments) { + setProperty(new CollectionProperty(ARGUMENTS, arguments)); + } + + /** + * Get the arguments as a Map. Each argument name is used as the key, and + * its value as the value. + * + * @return a new Map with String keys and values containing the arguments + */ + public Map getArgumentsAsMap() { + PropertyIterator iter = getArguments().iterator(); + Map argMap = new HashMap(); + while (iter.hasNext()) { + LDAPArgument arg = (LDAPArgument) iter.next().getObjectValue(); + argMap.put(arg.getName(), arg.getValue()); + } + return argMap; + } + + /** + * Add a new argument with the given name and value. + * + * @param name + * the name of the argument + * @param value + * the value of the argument + */ + public void addArgument(String name, String value, String opcode) { + addArgument(new LDAPArgument(name, value, opcode, null)); + } + + /** + * Add a new argument. + * + * @param arg + * the new argument + */ + public void addArgument(LDAPArgument arg) { + TestElementProperty newArg = new TestElementProperty(arg.getName(), arg); + if (isRunningVersion()) { + this.setTemporary(newArg); + } + getArguments().addItem(newArg); + } + + /** + * Add a new argument with the given name, value, and metadata. + * + * @param name + * the name of the argument + * @param value + * the value of the argument + * @param metadata + * the metadata for the argument + */ + public void addArgument(String name, String value, String opcode, String metadata) { + addArgument(new LDAPArgument(name, value, opcode, metadata)); + } + + /** + * Get a PropertyIterator of the arguments. + * + * @return an iteration of the arguments + */ + public PropertyIterator iterator() { + return getArguments().iterator(); + } + + /** + * Create a string representation of the arguments. + * + * @return the string representation of the arguments + */ + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + LDAPArgument arg = (LDAPArgument) iter.next().getObjectValue(); + final String metaData = arg.getMetaData(); + str.append(arg.getName()); + if (metaData == null) { + str.append("="); //$NON-NLS$ + } else { + str.append(metaData); + } + str.append(arg.getValue()); + if (iter.hasNext()) { + str.append("&"); //$NON-NLS$ + } + } + return str.toString(); + } + + /** + * Remove the specified argument from the list. + * + * @param row + * the index of the argument to remove + */ + public void removeArgument(int row) { + if (row < getArguments().size()) { + getArguments().remove(row); + } + } + + /** + * Remove the specified argument from the list. + * + * @param arg + * the argument to remove + */ + public void removeArgument(LDAPArgument arg) { + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + LDAPArgument item = (LDAPArgument) iter.next().getObjectValue(); + if (arg.equals(item)) { + iter.remove(); + } + } + } + + /** + * Remove the argument with the specified name. + * + * @param argName + * the name of the argument to remove + */ + public void removeArgument(String argName) { + PropertyIterator iter = getArguments().iterator(); + while (iter.hasNext()) { + LDAPArgument arg = (LDAPArgument) iter.next().getObjectValue(); + if (arg.getName().equals(argName)) { + iter.remove(); + } + } + } + + /** + * Remove all arguments from the list. + */ + public void removeAllArguments() { + getArguments().clear(); + } + + /** + * Add a new empty argument to the list. The new argument will have the + * empty string as its name and value, and null metadata. + */ + public void addEmptyArgument() { + addArgument(new LDAPArgument("", "", "", null)); + } + + /** + * Get the number of arguments in the list. + * + * @return the number of arguments + */ + public int getArgumentCount() { + return getArguments().size(); + } + + /** + * Get a single argument. + * + * @param row + * the index of the argument to return. + * @return the argument at the specified index, or null if no argument + * exists at that index. + */ + public LDAPArgument getArgument(int row) { + LDAPArgument argument = null; + + if (row < getArguments().size()) { + argument = (LDAPArgument) getArguments().get(row).getObjectValue(); + } + + return argument; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgumentsPanel.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgumentsPanel.java new file mode 100644 index 0000000..062b822 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LDAPArgumentsPanel.java @@ -0,0 +1,381 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ldap.config.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.Iterator; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableCellEditor; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +/** + * A GUI panel allowing the user to enter name-value argument pairs. These + * arguments (or parameters) are usually used to provide configuration values + * for some other component. + * + */ + +public class LDAPArgumentsPanel extends AbstractConfigGui implements ActionListener { + + private static final long serialVersionUID = 240L; + + /** Logging. */ + //private static final Logger log = LoggingManager.getLoggerForClass(); + + /** The title label for this component. */ + private JLabel tableLabel; + + /** The table containing the list of arguments. */ + private transient JTable table; + + /** The model for the arguments table. */ + // needs to be accessible from test code + transient ObjectTableModel tableModel; // Only contains LDAPArgument entries + + /** A button for adding new arguments to the table. */ + private JButton add; + + /** A button for removing arguments from the table. */ + private JButton delete; + + /** Command for adding a row to the table. */ + private static final String ADD = "add"; //$NON-NLS-1$ + + /** Command for removing a row from the table. */ + private static final String DELETE = "delete"; //$NON-NLS-1$ + + private static final String[] COLUMN_NAMES = { + "attribute", //$NON-NLS-1$ + "value", //$NON-NLS-1$ + "opcode", //$NON-NLS-1$ + "metadata" }; //$NON-NLS-1$ + + /** + * Create a new LDAPArgumentsPanel, using the default title. + */ + public LDAPArgumentsPanel() { + this(JMeterUtils.getResString("paramtable")); //$NON-NLS-1$ + } + + /** + * Create a new LDAPArgumentsPanel, using the specified title. + * + * @param label + * the title of the component + */ + public LDAPArgumentsPanel(String label) { + tableLabel = new JLabel(label); + init(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. The LDAPArgumentsPanel is not intended to be used as a standalone + * component, so this inplementation returns null. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + @Override + public Collection getMenuCategories() { + return null; + } + + public String getLabelResource() { + return "ldapext_sample_title"; // $NON-NLS-1$ + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + LDAPArguments args = new LDAPArguments(); + modifyTestElement(args); + // TODO: Why do we clone the return value? This is the only reference + // to it (right?) so we shouldn't need a separate copy. + return (TestElement) args.clone(); + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement args) { + stopTableEditing(); + LDAPArguments arguments = null; + if (args instanceof LDAPArguments) { + arguments = (LDAPArguments) args; + arguments.clear(); + @SuppressWarnings("unchecked") // Only contains LDAPArgument entries + Iterator modelData = (Iterator) tableModel.iterator(); + while (modelData.hasNext()) { + LDAPArgument arg = modelData.next(); + arg.setMetaData("="); + arguments.addArgument(arg); + } + } + this.configureTestElement(args); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param el + * the TestElement to configure + */ + @Override + public void configure(TestElement el) { + super.configure(el); + if (el instanceof LDAPArguments) { + tableModel.clearData(); + PropertyIterator iter = ((LDAPArguments) el).iterator(); + while (iter.hasNext()) { + LDAPArgument arg = (LDAPArgument) iter.next().getObjectValue(); + tableModel.addRow(arg); + } + } + checkDeleteStatus(); + } + + /** + * Enable or disable the delete button depending on whether or not there is + * a row to be deleted. + */ + private void checkDeleteStatus() { + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } else { + delete.setEnabled(true); + } + } + + /** + * Clear all rows from the table. T.Elanjchezhiyan(chezhiyan@siptech.co.in) + */ + public void clear() { + tableModel.clearData(); + } + + /** + * Invoked when an action occurs. This implementation supports the add and + * delete buttons. + * + * @param e + * the event that has occurred + */ + public void actionPerformed(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(DELETE)) { + deleteArgument(); + } else if (action.equals(ADD)) { + addArgument(); + } + } + + /** + * Remove the currently selected argument from the table. + */ + private void deleteArgument() { + // If a table cell is being edited, we must cancel the editing before + // deleting the row + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.cancelCellEditing(); + } + + int rowSelected = table.getSelectedRow(); + if (rowSelected >= 0) { + tableModel.removeRow(rowSelected); + tableModel.fireTableDataChanged(); + + // Disable DELETE if there are no rows in the table to delete. + if (tableModel.getRowCount() == 0) { + delete.setEnabled(false); + } + + // Table still contains one or more rows, so highlight (select) + // the appropriate one. + else { + int rowToSelect = rowSelected; + + if (rowSelected >= tableModel.getRowCount()) { + rowToSelect = rowSelected - 1; + } + + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + } + } + + /** + * Add a new argument row to the table. + */ + private void addArgument() { + // If a table cell is being edited, we should accept the current value + // and stop the editing before adding a new row. + stopTableEditing(); + + tableModel.addRow(makeNewLDAPArgument()); + + // Enable DELETE (which may already be enabled, but it won't hurt) + delete.setEnabled(true); + + // Highlight (select) the appropriate row. + int rowToSelect = tableModel.getRowCount() - 1; + table.setRowSelectionInterval(rowToSelect, rowToSelect); + } + + /** + * Create a new LDAPArgument object. + * + * @return a new LDAPArgument object + */ + private LDAPArgument makeNewLDAPArgument() { + return new LDAPArgument("", "", ""); + } + + /** + * Stop any editing that is currently being done on the table. This will + * save any changes that have already been made. + */ + private void stopTableEditing() { + if (table.isEditing()) { + TableCellEditor cellEditor = table.getCellEditor(table.getEditingRow(), table.getEditingColumn()); + cellEditor.stopCellEditing(); + } + } + + /** + * Initialize the table model used for the arguments table. + */ + private void initializeTableModel() { + tableModel = new ObjectTableModel(new String[] { COLUMN_NAMES[0], COLUMN_NAMES[1], COLUMN_NAMES[2] }, + LDAPArgument.class, + new Functor[] { new Functor("getName"), new Functor("getValue"), new Functor("getOpcode") }, + new Functor[] { new Functor("setName"), new Functor("setValue"), new Functor("setOpcode") }, + new Class[] { String.class, String.class, String.class }); + } + + public static boolean testFunctors(){ + LDAPArgumentsPanel instance = new LDAPArgumentsPanel(); + instance.initializeTableModel(); + return instance.tableModel.checkFunctors(null,instance.getClass()); + } + + /* + * protected void initializeTableModel() { tableModel = new + * ObjectTableModel( new String[] { ArgumentsPanel.COLUMN_NAMES_0, + * ArgumentsPanel.COLUMN_NAMES_1, ENCODE_OR_NOT, INCLUDE_EQUALS }, new + * Functor[] { new Functor("getName"), new Functor("getValue"), new + * Functor("isAlwaysEncoded"), new Functor("isUseEquals") }, new Functor[] { + * new Functor("setName"), new Functor("setValue"), new + * Functor("setAlwaysEncoded"), new Functor("setUseEquals") }, new Class[] { + * String.class, String.class, Boolean.class, Boolean.class }); } + */ + /** + * Resize the table columns to appropriate widths. + * + * @param _table + * the table to resize columns for + */ + private void sizeColumns(JTable _table) { + } + + /** + * Create the main GUI panel which contains the argument table. + * + * @return the main GUI panel + */ + private Component makeMainPanel() { + initializeTableModel(); + table = new JTable(tableModel); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + return makeScrollPane(table); + } + + /** + * Create a panel containing the title label for the table. + * + * @return a panel containing the title label + */ + private Component makeLabelPanel() { + JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + labelPanel.add(tableLabel); + return labelPanel; + } + + /** + * Create a panel containing the add and delete buttons. + * + * @return a GUI panel containing the buttons + */ + private JPanel makeButtonPanel() { + add = new JButton(JMeterUtils.getResString("add")); //$NON-NLS-1$ + add.setActionCommand(ADD); + add.setEnabled(true); + + delete = new JButton(JMeterUtils.getResString("delete")); //$NON-NLS-1$ + delete.setActionCommand(DELETE); + + checkDeleteStatus(); + + JPanel buttonPanel = new JPanel(); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); + add.addActionListener(this); + delete.addActionListener(this); + buttonPanel.add(add); + buttonPanel.add(delete); + return buttonPanel; + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout()); + + add(makeLabelPanel(), BorderLayout.NORTH); + add(makeMainPanel(), BorderLayout.CENTER); + // Force a minimum table height of 70 pixels + add(Box.createVerticalStrut(70), BorderLayout.WEST); + add(makeButtonPanel(), BorderLayout.SOUTH); + + table.revalidate(); + sizeColumns(table); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LdapConfigGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LdapConfigGui.java new file mode 100644 index 0000000..c161d0e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LdapConfigGui.java @@ -0,0 +1,440 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ldap.config.gui; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.ldap.sampler.LDAPSampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class LdapConfigGui is user interface gui for getting all the + * configuration values from the user. + * + * Created Apr 29 2003 11:45 AM + * + */ +public class LdapConfigGui extends AbstractConfigGui implements ItemListener { + + private static final long serialVersionUID = 240L; + + private JTextField rootdn = new JTextField(20); + + private JTextField searchbase = new JTextField(20); + + private JTextField searchfilter = new JTextField(20); + + private JTextField delete = new JTextField(20); + + private JTextField add = new JTextField(20); + + private JTextField modify = new JTextField(20); + + private JTextField servername = new JTextField(20); + + private JTextField port = new JTextField(20); + + private JCheckBox user_Defined = new JCheckBox(JMeterUtils.getResString("user_defined_test")); // $NON-NLS-1$ + + private JRadioButton addTest = new JRadioButton(JMeterUtils.getResString("add_test")); // $NON-NLS-1$ + + private JRadioButton modifyTest = new JRadioButton(JMeterUtils.getResString("modify_test")); // $NON-NLS-1$ + + private JRadioButton deleteTest = new JRadioButton(JMeterUtils.getResString("delete_test")); // $NON-NLS-1$ + + private JRadioButton searchTest = new JRadioButton(JMeterUtils.getResString("search_test")); // $NON-NLS-1$ + + private ButtonGroup bGroup = new ButtonGroup(); + + private boolean displayName = true; + + private ArgumentsPanel tableAddPanel = new ArgumentsPanel(JMeterUtils.getResString("add_test")); // $NON-NLS-1$ + + private ArgumentsPanel tableModifyPanel = new ArgumentsPanel(JMeterUtils.getResString("modify_test")); // $NON-NLS-1$ + + private JPanel cards; + + /** + * Default constructor for LdapConfigGui. + */ + public LdapConfigGui() { + this(true); + } + + public String getLabelResource() { + return "ldap_sample_title"; // $NON-NLS-1$ + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + servername.setText(element.getPropertyAsString(LDAPSampler.SERVERNAME)); + port.setText(element.getPropertyAsString(LDAPSampler.PORT)); + rootdn.setText(element.getPropertyAsString(LDAPSampler.ROOTDN)); + CardLayout cl = (CardLayout) (cards.getLayout()); + final String testType = element.getPropertyAsString(LDAPSampler.TEST); + if (testType.equals(LDAPSampler.ADD)) { + addTest.setSelected(true); + add.setText(element.getPropertyAsString(LDAPSampler.BASE_ENTRY_DN)); + tableAddPanel.configure((TestElement) element.getProperty(LDAPSampler.ARGUMENTS).getObjectValue()); + cl.show(cards, "Add"); + } else if (testType.equals(LDAPSampler.MODIFY)) { + modifyTest.setSelected(true); + modify.setText(element.getPropertyAsString(LDAPSampler.BASE_ENTRY_DN)); + tableModifyPanel.configure((TestElement) element.getProperty(LDAPSampler.ARGUMENTS).getObjectValue()); + cl.show(cards, "Modify"); + } else if (testType.equals(LDAPSampler.DELETE)) { + deleteTest.setSelected(true); + delete.setText(element.getPropertyAsString(LDAPSampler.DELETE)); + cl.show(cards, "Delete"); + } else if (testType.equals(LDAPSampler.SEARCHBASE)) { + searchTest.setSelected(true); + searchbase.setText(element.getPropertyAsString(LDAPSampler.SEARCHBASE)); + searchfilter.setText(element.getPropertyAsString(LDAPSampler.SEARCHFILTER)); + cl.show(cards, "Search"); + } + + if (element.getPropertyAsBoolean(LDAPSampler.USER_DEFINED)) { + user_Defined.setSelected(true); + } else { + user_Defined.setSelected(false); + cl.show(cards, ""); // $NON-NLS-1$ + } + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + ConfigTestElement element = new ConfigTestElement(); + modifyTestElement(element); + return element; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement element) { + element.clear(); + configureTestElement(element); + element.setProperty(LDAPSampler.SERVERNAME, servername.getText()); + element.setProperty(LDAPSampler.PORT, port.getText()); + element.setProperty(LDAPSampler.ROOTDN, rootdn.getText()); + element.setProperty(new BooleanProperty(LDAPSampler.USER_DEFINED, user_Defined.isSelected())); + + if (addTest.isSelected()) { + element.setProperty(new StringProperty(LDAPSampler.TEST, LDAPSampler.ADD)); + element.setProperty(new StringProperty(LDAPSampler.BASE_ENTRY_DN, add.getText())); + element.setProperty(new TestElementProperty(LDAPSampler.ARGUMENTS, tableAddPanel.createTestElement())); + } + + if (modifyTest.isSelected()) { + element.setProperty(new StringProperty(LDAPSampler.TEST, LDAPSampler.MODIFY)); + element.setProperty(new StringProperty(LDAPSampler.BASE_ENTRY_DN, modify.getText())); + element.setProperty(new TestElementProperty(LDAPSampler.ARGUMENTS, tableModifyPanel.createTestElement())); + } + + if (deleteTest.isSelected()) { + element.setProperty(new StringProperty(LDAPSampler.TEST, LDAPSampler.DELETE)); + element.setProperty(new StringProperty(LDAPSampler.DELETE, delete.getText())); + } + + if (searchTest.isSelected()) { + element.setProperty(new StringProperty(LDAPSampler.TEST, LDAPSampler.SEARCHBASE)); + element.setProperty(new StringProperty(LDAPSampler.SEARCHBASE, searchbase.getText())); + element.setProperty(new StringProperty(LDAPSampler.SEARCHFILTER, searchfilter.getText())); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + rootdn.setText(""); //$NON-NLS-1$ + searchbase.setText(""); //$NON-NLS-1$ + searchfilter.setText(""); //$NON-NLS-1$ + delete.setText(""); //$NON-NLS-1$ + add.setText(""); //$NON-NLS-1$ + modify.setText(""); //$NON-NLS-1$ + servername.setText(""); //$NON-NLS-1$ + port.setText(""); //$NON-NLS-1$ + user_Defined.setSelected(false); + addTest.setSelected(true); + modifyTest.setSelected(false); + deleteTest.setSelected(false); + searchTest.setSelected(false); + } + + /** + * This itemStateChanged listener for changing the card layout for based on\ + * the test selected in the User defined test case. + */ + public void itemStateChanged(ItemEvent ie) { + CardLayout cl = (CardLayout) (cards.getLayout()); + if (user_Defined.isSelected()) { + if (addTest.isSelected()) { + cl.show(cards, "Add"); + tableModifyPanel.clear(); + modify.setText(""); // $NON-NLS-1$ + searchbase.setText(""); // $NON-NLS-1$ + searchfilter.setText(""); // $NON-NLS-1$ + delete.setText(""); + } else if (deleteTest.isSelected()) { + cl.show(cards, "Delete"); + tableModifyPanel.clear(); + modify.setText(""); // $NON-NLS-1$ + tableAddPanel.clear(); + add.setText(""); // $NON-NLS-1$ + searchbase.setText(""); // $NON-NLS-1$ + searchfilter.setText(""); // $NON-NLS-1$ + } else if (searchTest.isSelected()) { + cl.show(cards, "Search"); + delete.setText(""); // $NON-NLS-1$ + tableModifyPanel.clear(); + modify.setText(""); // $NON-NLS-1$ + tableAddPanel.clear(); + add.setText(""); // $NON-NLS-1$ + } else if (modifyTest.isSelected()) { + cl.show(cards, "Modify"); + tableAddPanel.clear(); + add.setText(""); // $NON-NLS-1$ + searchbase.setText(""); // $NON-NLS-1$ + searchfilter.setText(""); // $NON-NLS-1$ + delete.setText(""); + } else { + cl.show(cards, ""); // $NON-NLS-1$ + tableAddPanel.clear(); + add.setText(""); // $NON-NLS-1$ + tableModifyPanel.clear(); + modify.setText(""); // $NON-NLS-1$ + searchbase.setText(""); // $NON-NLS-1$ + searchfilter.setText(""); // $NON-NLS-1$ + delete.setText(""); // $NON-NLS-1$ + } + } else { + cl.show(cards, ""); // $NON-NLS-1$ + tableAddPanel.clear(); + add.setText(""); // $NON-NLS-1$ + tableModifyPanel.clear(); + modify.setText(""); // $NON-NLS-1$ + searchbase.setText(""); // $NON-NLS-1$ + searchfilter.setText(""); // $NON-NLS-1$ + delete.setText(""); // $NON-NLS-1$ + } + } + + public LdapConfigGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + /** + * This will create the servername panel in the LdapConfigGui. + */ + private JPanel createServernamePanel() { + JPanel serverPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("servername")); // $NON-NLS-1$ + label.setLabelFor(servername); + serverPanel.add(label, BorderLayout.WEST); + serverPanel.add(servername, BorderLayout.CENTER); + return serverPanel; + } + + /** + * This will create the port panel in the LdapConfigGui. + */ + private JPanel createPortPanel() { + JPanel portPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("port")); // $NON-NLS-1$ + label.setLabelFor(port); + portPanel.add(label, BorderLayout.WEST); + portPanel.add(port, BorderLayout.CENTER); + return portPanel; + } + + /** + * This will create the Root distinguised name panel in the LdapConfigGui. + */ + private JPanel createRootdnPanel() { + JPanel rootdnPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("dn")); // $NON-NLS-1$ + label.setLabelFor(rootdn); + rootdnPanel.add(label, BorderLayout.WEST); + rootdnPanel.add(rootdn, BorderLayout.CENTER); + return rootdnPanel; + } + + /** + * This will create the Search panel in the LdapConfigGui. + */ + private JPanel createSearchPanel() { + VerticalPanel searchPanel = new VerticalPanel(); + JPanel searchBPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("search_base")); // $NON-NLS-1$ + label.setLabelFor(searchbase); + searchBPanel.add(label, BorderLayout.WEST); + searchBPanel.add(searchbase, BorderLayout.CENTER); + JPanel searchFPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label2 = new JLabel(JMeterUtils.getResString("search_filter")); // $NON-NLS-1$ + label2.setLabelFor(searchfilter); + searchFPanel.add(label2, BorderLayout.WEST); + searchFPanel.add(searchfilter, BorderLayout.CENTER); + searchPanel.add(searchBPanel); + searchPanel.add(searchFPanel); + return searchPanel; + } + + /** + * This will create the Delete panel in the LdapConfigGui. + */ + private JPanel createDeletePanel() { + VerticalPanel panel = new VerticalPanel(); + JPanel deletePanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + label.setLabelFor(delete); + deletePanel.add(label, BorderLayout.WEST); + deletePanel.add(delete, BorderLayout.CENTER); + panel.add(deletePanel); + return panel; + } + + /** + * This will create the Add test panel in the LdapConfigGui. + */ + private JPanel createAddPanel() { + JPanel addPanel = new JPanel(new BorderLayout(5, 0)); + JPanel addInnerPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("entry_dn")); // $NON-NLS-1$ + label.setLabelFor(add); + addInnerPanel.add(label, BorderLayout.WEST); + addInnerPanel.add(add, BorderLayout.CENTER); + addPanel.add(addInnerPanel, BorderLayout.NORTH); + addPanel.add(tableAddPanel, BorderLayout.CENTER); + return addPanel; + } + + /** + * This will create the Modify panel in the LdapConfigGui. + */ + private JPanel createModifyPanel() { + JPanel modifyPanel = new JPanel(new BorderLayout(5, 0)); + JPanel modifyInnerPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("entry_dn")); // $NON-NLS-1$ + label.setLabelFor(modify); + modifyInnerPanel.add(label, BorderLayout.WEST); + modifyInnerPanel.add(modify, BorderLayout.CENTER); + modifyPanel.add(modifyInnerPanel, BorderLayout.NORTH); + modifyPanel.add(tableModifyPanel, BorderLayout.CENTER); + return modifyPanel; + } + + /** + * This will create the user defined test panel for create or modify or + * delete or search based on the panel selected in the itemevent in the + * LdapConfigGui. + */ + private JPanel testPanel() { + cards = new JPanel(new CardLayout()); + cards.add(new JPanel(), ""); + cards.add(createAddPanel(), "Add"); + cards.add(createModifyPanel(), "Modify"); + cards.add(createDeletePanel(), "Delete"); + cards.add(createSearchPanel(), "Search"); + return cards; + } + + /** + * This will create the test panel in the LdapConfigGui. + */ + private JPanel createTestPanel() { + JPanel testPanel = new JPanel(new BorderLayout()); + testPanel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("test_configuration"))); // $NON-NLS-1$ + + testPanel.add(new JLabel(JMeterUtils.getResString("test"))); // $NON-NLS-1$ + JPanel rowPanel = new JPanel(); + + rowPanel.add(addTest); + bGroup.add(addTest); + rowPanel.add(deleteTest); + bGroup.add(deleteTest); + rowPanel.add(searchTest); + bGroup.add(searchTest); + rowPanel.add(modifyTest); + bGroup.add(modifyTest); + testPanel.add(rowPanel, BorderLayout.NORTH); + testPanel.add(user_Defined, BorderLayout.CENTER); + return testPanel; + } + + /** + * This will initialise all the panel in the LdapConfigGui. + */ + private void init() { + setLayout(new BorderLayout(0, 5)); + + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + VerticalPanel mainPanel = new VerticalPanel(); + mainPanel.add(createServernamePanel()); + mainPanel.add(createPortPanel()); + mainPanel.add(createRootdnPanel()); + mainPanel.add(createTestPanel()); + mainPanel.add(testPanel()); + add(mainPanel, BorderLayout.CENTER); + + user_Defined.addItemListener(this); + addTest.addItemListener(this); + modifyTest.addItemListener(this); + deleteTest.addItemListener(this); + searchTest.addItemListener(this); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LdapExtConfigGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LdapExtConfigGui.java new file mode 100644 index 0000000..da261f0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/config/gui/LdapExtConfigGui.java @@ -0,0 +1,704 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ldap.config.gui; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.JPasswordField; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.ldap.sampler.LDAPExtSampler; +import org.apache.jmeter.protocol.ldap.config.gui.LDAPArgumentsPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledChoice; + +/******************************************************************************* + * author Dolf Smits(Dolf.Smits@Siemens.com) created Aug 09 2003 11:00 AM + * company Siemens Netherlands N.V.. + * + * Based on the work of: author T.Elanjchezhiyan(chezhiyan@siptech.co.in) + * created Apr 29 2003 11:00 AM company Sip Technologies and Exports Ltd. + ******************************************************************************/ + +/******************************************************************************* + * This class LdapConfigGui is user interface gui for getting all the + * configuration value from the user + ******************************************************************************/ + +public class LdapExtConfigGui extends AbstractConfigGui implements ItemListener { + + private static final long serialVersionUID = 240L; + + // private final static String ROOTDN = "rootDn"; + // private final static String TEST = "tesT"; + // private static String testValue="NNNN"; + + private JTextField rootdn = new JTextField(20); + + private JTextField searchbase = new JTextField(20); + + private JTextField searchfilter = new JTextField(20); + + private JTextField delete = new JTextField(20); + + private JTextField add = new JTextField(20); + + private JTextField modify = new JTextField(20); + + private JTextField servername = new JTextField(20); + + private JTextField port = new JTextField(20); + + /* + * N.B. These entry indexes MUST agree with the SearchControls SCOPE_LEVELS, i.e. + * + * javax.naming.directory.SearchControls.OBJECT_SCOPE, ONELEVEL_SCOPE, SUBTREE_SCOPE + * + * These have the values 0,1,2 so can be used as indexes in the array + * as well as the value for the search itself. + * + * N.B. Although the strings are used to set and get the options, language change + * does not currently cause a problem, because that always saves the current settings first, + * and then recreates all the GUI classes. + */ + private final String[] SCOPE_STRINGS = new String[]{ + JMeterUtils.getResString("ldap_search_baseobject"),// $NON-NLS-1$ + JMeterUtils.getResString("ldap_search_onelevel"),// $NON-NLS-1$ + JMeterUtils.getResString("ldap_search_subtree"),// $NON-NLS-1$ + }; + + // Names for the cards + private static final String CARDS_DEFAULT = ""; // $NON-NLS-1$ + private static final String CARDS_ADD = "Add"; // $NON-NLS-1$ + private static final String CARDS_DELETE = "Delete"; // $NON-NLS-1$ + private static final String CARDS_BIND = "Bind"; // $NON-NLS-1$ + private static final String CARDS_RENAME = "Rename"; // $NON-NLS-1$ + private static final String CARDS_COMPARE = "Compare"; // $NON-NLS-1$ + private static final String CARDS_SEARCH = "Search"; // $NON-NLS-1$ + private static final String CARDS_MODIFY = "Modify"; // $NON-NLS-1$ + + private JLabeledChoice scope = + new JLabeledChoice(JMeterUtils.getResString("scope"), // $NON-NLS-1$ + SCOPE_STRINGS); + + private JTextField countlim = new JTextField(20); + + private JTextField timelim = new JTextField(20); + + private JTextField attribs = new JTextField(20); + + private JCheckBox retobj = new JCheckBox(JMeterUtils.getResString("retobj")); // $NON-NLS-1$ + + private JCheckBox deref = new JCheckBox(JMeterUtils.getResString("deref")); // $NON-NLS-1$ + + private JTextField userdn = new JTextField(20); + + private JTextField userpw = new JPasswordField(20); + + private JTextField comparedn = new JTextField(20); + + private JTextField comparefilt = new JTextField(20); + + private JTextField modddn = new JTextField(20); + + private JTextField newdn = new JTextField(20); + + private JTextField connto = new JTextField(20); + + private JCheckBox parseflag = new JCheckBox(JMeterUtils.getResString("ldap_parse_results")); // $NON-NLS-1$ + + private JCheckBox secure = new JCheckBox(JMeterUtils.getResString("ldap_secure")); // $NON-NLS-1$ + + private JRadioButton addTest = new JRadioButton(JMeterUtils.getResString("addtest")); // $NON-NLS-1$ + + private JRadioButton modifyTest = new JRadioButton(JMeterUtils.getResString("modtest")); // $NON-NLS-1$ + + private JRadioButton deleteTest = new JRadioButton(JMeterUtils.getResString("deltest")); // $NON-NLS-1$ + + private JRadioButton searchTest = new JRadioButton(JMeterUtils.getResString("searchtest")); // $NON-NLS-1$ + + private JRadioButton bind = new JRadioButton(JMeterUtils.getResString("bind")); // $NON-NLS-1$ + + private JRadioButton rename = new JRadioButton(JMeterUtils.getResString("rename")); // $NON-NLS-1$ + + private JRadioButton unbind = new JRadioButton(JMeterUtils.getResString("unbind")); // $NON-NLS-1$ + + private JRadioButton sbind = new JRadioButton(JMeterUtils.getResString("sbind")); // $NON-NLS-1$ + + private JRadioButton compare = new JRadioButton(JMeterUtils.getResString("compare")); // $NON-NLS-1$ + + private ButtonGroup bGroup = new ButtonGroup(); + + private boolean displayName = true; + + private ArgumentsPanel tableAddPanel = new ArgumentsPanel(JMeterUtils.getResString("addtest")); // $NON-NLS-1$ + + private LDAPArgumentsPanel tableModifyPanel = new LDAPArgumentsPanel(JMeterUtils.getResString("modtest")); // $NON-NLS-1$ + + private JPanel cards; + + /*************************************************************************** + * Default constructor for LdapConfigGui + **************************************************************************/ + public LdapExtConfigGui() { + this(true); + } + + /*************************************************************************** + * !ToDo (Constructor description) + * + * @param displayName + * !ToDo (Parameter description) + **************************************************************************/ + public LdapExtConfigGui(boolean displayName) { + this.displayName = displayName; + init(); + } + + public String getLabelResource() { + return "ldapext_sample_title"; // $NON-NLS-1$ + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + servername.setText(element.getPropertyAsString(LDAPExtSampler.SERVERNAME)); + port.setText(element.getPropertyAsString(LDAPExtSampler.PORT)); + rootdn.setText(element.getPropertyAsString(LDAPExtSampler.ROOTDN)); + scope.setSelectedIndex(element.getPropertyAsInt(LDAPExtSampler.SCOPE)); + countlim.setText(element.getPropertyAsString(LDAPExtSampler.COUNTLIM)); + timelim.setText(element.getPropertyAsString(LDAPExtSampler.TIMELIM)); + attribs.setText(element.getPropertyAsString(LDAPExtSampler.ATTRIBS)); + retobj.setSelected(element.getPropertyAsBoolean(LDAPExtSampler.RETOBJ)); + deref.setSelected(element.getPropertyAsBoolean(LDAPExtSampler.DEREF)); + connto.setText(element.getPropertyAsString(LDAPExtSampler.CONNTO)); + parseflag.setSelected(element.getPropertyAsBoolean(LDAPExtSampler.PARSEFLAG)); + secure.setSelected(element.getPropertyAsBoolean(LDAPExtSampler.SECURE)); + userpw.setText(element.getPropertyAsString(LDAPExtSampler.USERPW)); + userdn.setText(element.getPropertyAsString(LDAPExtSampler.USERDN)); + comparedn.setText(element.getPropertyAsString(LDAPExtSampler.COMPAREDN)); + comparefilt.setText(element.getPropertyAsString(LDAPExtSampler.COMPAREFILT)); + modddn.setText(element.getPropertyAsString(LDAPExtSampler.MODDDN)); + newdn.setText(element.getPropertyAsString(LDAPExtSampler.NEWDN)); + CardLayout cl = (CardLayout) (cards.getLayout()); + final String testType = element.getPropertyAsString(LDAPExtSampler.TEST); + if (testType.equals(LDAPExtSampler.UNBIND)) { + unbind.setSelected(true); + cl.show(cards, CARDS_DEFAULT); + } else if (testType.equals(LDAPExtSampler.BIND)) { + bind.setSelected(true); + cl.show(cards, CARDS_BIND); + } else if (testType.equals(LDAPExtSampler.SBIND)) { + sbind.setSelected(true); + cl.show(cards, CARDS_BIND); + } else if (testType.equals(LDAPExtSampler.COMPARE)) { + compare.setSelected(true); + cl.show(cards, CARDS_COMPARE); + } else if (testType.equals(LDAPExtSampler.ADD)) { + addTest.setSelected(true); + add.setText(element.getPropertyAsString(LDAPExtSampler.BASE_ENTRY_DN)); + tableAddPanel.configure((TestElement) element.getProperty(LDAPExtSampler.ARGUMENTS).getObjectValue()); + cl.show(cards, CARDS_ADD); + } else if (testType.equals(LDAPExtSampler.MODIFY)) { + modifyTest.setSelected(true); + modify.setText(element.getPropertyAsString(LDAPExtSampler.BASE_ENTRY_DN)); + tableModifyPanel + .configure((TestElement) element.getProperty(LDAPExtSampler.LDAPARGUMENTS).getObjectValue()); + cl.show(cards, CARDS_MODIFY); + } else if (testType.equals(LDAPExtSampler.DELETE)) { + deleteTest.setSelected(true); + delete.setText(element.getPropertyAsString(LDAPExtSampler.DELETE)); + cl.show(cards, CARDS_DELETE); + } else if (testType.equals(LDAPExtSampler.RENAME)) { + rename.setSelected(true); + cl.show(cards, CARDS_RENAME); + } else if (testType.equals(LDAPExtSampler.SEARCH)) { + searchTest.setSelected(true); + searchbase.setText(element.getPropertyAsString(LDAPExtSampler.SEARCHBASE)); + searchfilter.setText(element.getPropertyAsString(LDAPExtSampler.SEARCHFILTER)); + cl.show(cards, CARDS_SEARCH); + } + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + ConfigTestElement element = new ConfigTestElement(); + modifyTestElement(element); + return element; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement element) { + element.clear(); + configureTestElement(element); + element.setProperty(LDAPExtSampler.SERVERNAME, servername.getText()); + element.setProperty(LDAPExtSampler.PORT, port.getText()); + element.setProperty(LDAPExtSampler.ROOTDN, rootdn.getText()); + element.setProperty(LDAPExtSampler.SCOPE,String.valueOf(scope.getSelectedIndex())); + element.setProperty(LDAPExtSampler.COUNTLIM, countlim.getText()); + element.setProperty(LDAPExtSampler.TIMELIM, timelim.getText()); + element.setProperty(LDAPExtSampler.ATTRIBS, attribs.getText()); + element.setProperty(LDAPExtSampler.RETOBJ,Boolean.toString(retobj.isSelected())); + element.setProperty(LDAPExtSampler.DEREF,Boolean.toString(deref.isSelected())); + element.setProperty(LDAPExtSampler.CONNTO, connto.getText()); + element.setProperty(LDAPExtSampler.PARSEFLAG,Boolean.toString(parseflag.isSelected())); + element.setProperty(LDAPExtSampler.SECURE,Boolean.toString(secure.isSelected())); + element.setProperty(LDAPExtSampler.USERDN, userdn.getText()); + element.setProperty(LDAPExtSampler.USERPW, userpw.getText()); + element.setProperty(LDAPExtSampler.COMPAREDN, comparedn.getText()); + element.setProperty(LDAPExtSampler.COMPAREFILT, comparefilt.getText()); + element.setProperty(LDAPExtSampler.MODDDN, modddn.getText()); + element.setProperty(LDAPExtSampler.NEWDN, newdn.getText()); + if (addTest.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.ADD)); + element.setProperty(new StringProperty(LDAPExtSampler.BASE_ENTRY_DN, add.getText())); + element.setProperty(new TestElementProperty(LDAPExtSampler.ARGUMENTS, tableAddPanel.createTestElement())); + } + if (modifyTest.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.MODIFY)); + element.setProperty(new StringProperty(LDAPExtSampler.BASE_ENTRY_DN, modify.getText())); + element.setProperty(new TestElementProperty(LDAPExtSampler.LDAPARGUMENTS, tableModifyPanel + .createTestElement())); + } + if (deleteTest.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.DELETE)); + element.setProperty(new StringProperty(LDAPExtSampler.DELETE, delete.getText())); + } + if (searchTest.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.SEARCH)); + element.setProperty(new StringProperty(LDAPExtSampler.SEARCHBASE, searchbase.getText())); + element.setProperty(new StringProperty(LDAPExtSampler.SEARCHFILTER, searchfilter.getText())); + } + if (bind.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.BIND)); + } + if (sbind.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.SBIND)); + } + if (compare.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.COMPARE)); + } + if (rename.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.RENAME)); + } + if (unbind.isSelected()) { + element.setProperty(new StringProperty(LDAPExtSampler.TEST, LDAPExtSampler.UNBIND)); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + rootdn.setText(""); //$NON-NLS-1$ + searchbase.setText(""); //$NON-NLS-1$ + searchfilter.setText(""); //$NON-NLS-1$ + delete.setText(""); //$NON-NLS-1$ + add.setText(""); //$NON-NLS-1$ + modify.setText(""); //$NON-NLS-1$ + servername.setText(""); //$NON-NLS-1$ + port.setText(""); //$NON-NLS-1$ + add.setText(""); //$NON-NLS-1$ + scope.setSelectedIndex(SCOPE_STRINGS.length - 1); + countlim.setText(""); //$NON-NLS-1$ + timelim.setText(""); //$NON-NLS-1$ + attribs.setText(""); //$NON-NLS-1$ + userdn.setText(""); //$NON-NLS-1$ + userpw.setText(""); //$NON-NLS-1$ + comparedn.setText(""); //$NON-NLS-1$ + comparefilt.setText(""); //$NON-NLS-1$ + modddn.setText(""); //$NON-NLS-1$ + newdn.setText(""); //$NON-NLS-1$ + connto.setText(""); //$NON-NLS-1$ + retobj.setSelected(false); + deref.setSelected(false); + parseflag.setSelected(false); + secure.setSelected(false); + addTest.setSelected(false); + modifyTest.setSelected(false); + deleteTest.setSelected(false); + searchTest.setSelected(false); + bind.setSelected(false); + rename.setSelected(false); + unbind.setSelected(false); + sbind.setSelected(false); + compare.setSelected(false); + + tableAddPanel.clear(); + tableModifyPanel.clear(); + } + + /*************************************************************************** + * This itemStateChanged listener for changing the card layout for based on + * the test selected in the User defined test case. + **************************************************************************/ + public void itemStateChanged(ItemEvent ie) { + CardLayout cl = (CardLayout) (cards.getLayout()); + if (addTest.isSelected()) { + cl.show(cards, CARDS_ADD); + } else if (deleteTest.isSelected()) { + cl.show(cards, CARDS_DELETE); + } else if (bind.isSelected()) { + cl.show(cards, CARDS_BIND); + } else if (sbind.isSelected()) { + cl.show(cards, CARDS_BIND); + } else if (rename.isSelected()) { + cl.show(cards, CARDS_RENAME); + } else if (compare.isSelected()) { + cl.show(cards, CARDS_COMPARE); + } else if (searchTest.isSelected()) { + cl.show(cards, CARDS_SEARCH); + } else if (modifyTest.isSelected()) { + cl.show(cards, CARDS_MODIFY); + } else { // e.g unbind + cl.show(cards, CARDS_DEFAULT); + } + } + + /*************************************************************************** + * This will create the servername panel in the LdapConfigGui + **************************************************************************/ + private JPanel createServernamePanel() { + JPanel serverPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("servername")); // $NON-NLS-1$ + label.setLabelFor(servername); + serverPanel.add(label, BorderLayout.WEST); + serverPanel.add(servername, BorderLayout.CENTER); + return serverPanel; + } + + /*************************************************************************** + * This will create the port panel in the LdapConfigGui + **************************************************************************/ + private JPanel createPortPanel() { + JPanel portPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("port")); // $NON-NLS-1$ + label.setLabelFor(port); + portPanel.add(label, BorderLayout.WEST); + portPanel.add(port, BorderLayout.CENTER); + return portPanel; + } + + /*************************************************************************** + * This will create the Root distinguised name panel in the LdapConfigGui + **************************************************************************/ + private JPanel createRootdnPanel() { + JPanel rootdnPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("ddn")); // $NON-NLS-1$ + label.setLabelFor(rootdn); + rootdnPanel.add(label, BorderLayout.WEST); + rootdnPanel.add(rootdn, BorderLayout.CENTER); + return rootdnPanel; + } + + /*************************************************************************** + * This will create the bind/sbind panel in the LdapConfigGui + **************************************************************************/ + private JPanel createBindPanel() { + VerticalPanel bindPanel = new VerticalPanel(); + bindPanel.add(createServernamePanel()); + bindPanel.add(createPortPanel()); + bindPanel.add(createRootdnPanel()); + + JPanel BPanel = new JPanel(new BorderLayout(5, 0)); + JLabel Blabel0 = new JLabel(JMeterUtils.getResString("userdn")); // $NON-NLS-1$ + Blabel0.setLabelFor(userdn); + BPanel.add(Blabel0, BorderLayout.WEST); + BPanel.add(userdn, BorderLayout.CENTER); + bindPanel.add(BPanel); + + JPanel B1Panel = new JPanel(new BorderLayout(5, 0)); + JLabel Blabel1 = new JLabel(JMeterUtils.getResString("userpw")); // $NON-NLS-1$ + Blabel1.setLabelFor(userpw); + B1Panel.add(Blabel1, BorderLayout.WEST); + B1Panel.add(userpw, BorderLayout.CENTER); + bindPanel.add(B1Panel); + + JPanel B2Panel = new JPanel(new BorderLayout(5, 0)); + JLabel Blabel2 = new JLabel(JMeterUtils.getResString("ldap_connto")); // $NON-NLS-1$ + Blabel2.setLabelFor(connto); + B2Panel.add(Blabel2, BorderLayout.WEST); + B2Panel.add(connto, BorderLayout.CENTER); + bindPanel.add(B2Panel); + + bindPanel.add(secure); + return bindPanel; + } + + /*************************************************************************** + * This will create the bind panel in the LdapConfigGui + **************************************************************************/ + private JPanel createComparePanel() { + VerticalPanel cbindPanel = new VerticalPanel(); + JPanel cBPanel = new JPanel(new BorderLayout(5, 0)); + JLabel cBlabel0 = new JLabel(JMeterUtils.getResString("entrydn")); // $NON-NLS-1$ + cBlabel0.setLabelFor(comparedn); + cBPanel.add(cBlabel0, BorderLayout.WEST); + cBPanel.add(comparedn, BorderLayout.CENTER); + cbindPanel.add(cBPanel); + + JPanel cBPanel1 = new JPanel(new BorderLayout(5, 0)); + JLabel cBlabel1 = new JLabel(JMeterUtils.getResString("comparefilt")); // $NON-NLS-1$ + cBlabel1.setLabelFor(comparefilt); + cBPanel1.add(cBlabel1, BorderLayout.WEST); + cBPanel1.add(comparefilt, BorderLayout.CENTER); + cbindPanel.add(cBPanel1); + + return cbindPanel; + } + + /*************************************************************************** + * This will create the Search controls panel in the LdapConfigGui + **************************************************************************/ + private JPanel createSCPanel() { + VerticalPanel SCPanel = new VerticalPanel(); + + SCPanel.add(scope); + + JPanel SC1Panel = new JPanel(new BorderLayout(5, 0)); + JLabel label1 = new JLabel(JMeterUtils.getResString("countlim")); // $NON-NLS-1$ + label1.setLabelFor(countlim); + SC1Panel.add(label1, BorderLayout.WEST); + SC1Panel.add(countlim, BorderLayout.CENTER); + SCPanel.add(SC1Panel); + + JPanel SC2Panel = new JPanel(new BorderLayout(5, 0)); + JLabel label2 = new JLabel(JMeterUtils.getResString("timelim")); // $NON-NLS-1$ + label2.setLabelFor(timelim); + SC2Panel.add(label2, BorderLayout.WEST); + SC2Panel.add(timelim, BorderLayout.CENTER); + SCPanel.add(SC2Panel); + + JPanel SC3Panel = new JPanel(new BorderLayout(5, 0)); + JLabel label3 = new JLabel(JMeterUtils.getResString("attrs")); // $NON-NLS-1$ + label3.setLabelFor(attribs); + SC3Panel.add(label3, BorderLayout.WEST); + SC3Panel.add(attribs, BorderLayout.CENTER); + SCPanel.add(SC3Panel); + + SCPanel.add(retobj); + SCPanel.add(deref); + SCPanel.add(parseflag); + + return SCPanel; + } + + /*************************************************************************** + * This will create the Search panel in the LdapConfigGui + **************************************************************************/ + + private JPanel createSearchPanel() { + VerticalPanel searchPanel = new VerticalPanel(); + + JPanel searchBPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("searchbase")); // $NON-NLS-1$ + label.setLabelFor(searchbase); + searchBPanel.add(label, BorderLayout.WEST); + searchBPanel.add(searchbase, BorderLayout.CENTER); + + JPanel searchFPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label20 = new JLabel(JMeterUtils.getResString("searchfilter")); // $NON-NLS-1$ + label20.setLabelFor(searchfilter); + searchFPanel.add(label20, BorderLayout.WEST); + searchFPanel.add(searchfilter, BorderLayout.CENTER); + + searchPanel.add(searchBPanel); + searchPanel.add(searchFPanel); + searchPanel.add(createSCPanel()); + + return searchPanel; + } + + /*************************************************************************** + * This will create the Moddn panel in the LdapConfigGui + **************************************************************************/ + + private JPanel createModdnPanel() { + VerticalPanel modPanel = new VerticalPanel(); + + JPanel renamePanel = new JPanel(new BorderLayout(5, 0)); + JLabel labelmod = new JLabel(JMeterUtils.getResString("modddn")); // $NON-NLS-1$ + labelmod.setLabelFor(modddn); + renamePanel.add(labelmod, BorderLayout.WEST); + renamePanel.add(modddn, BorderLayout.CENTER); + + JPanel rename2Panel = new JPanel(new BorderLayout(5, 0)); + JLabel labelmod2 = new JLabel(JMeterUtils.getResString("newdn")); // $NON-NLS-1$ + labelmod2.setLabelFor(newdn); + rename2Panel.add(labelmod2, BorderLayout.WEST); + rename2Panel.add(newdn, BorderLayout.CENTER); + + modPanel.add(renamePanel); + modPanel.add(rename2Panel); + return modPanel; + } + + /*************************************************************************** + * This will create the Delete panel in the LdapConfigGui + **************************************************************************/ + private JPanel createDeletePanel() { + VerticalPanel panel = new VerticalPanel(); + JPanel deletePanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("delete")); // $NON-NLS-1$ + label.setLabelFor(delete); + deletePanel.add(label, BorderLayout.WEST); + deletePanel.add(delete, BorderLayout.CENTER); + panel.add(deletePanel); + return panel; + } + + /*************************************************************************** + * This will create the Add test panel in the LdapConfigGui + **************************************************************************/ + private JPanel createAddPanel() { + JPanel addPanel = new JPanel(new BorderLayout(5, 0)); + JPanel addInnerPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("entrydn")); // $NON-NLS-1$ + label.setLabelFor(add); + addInnerPanel.add(label, BorderLayout.WEST); + addInnerPanel.add(add, BorderLayout.CENTER); + addPanel.add(addInnerPanel, BorderLayout.NORTH); + addPanel.add(tableAddPanel, BorderLayout.CENTER); + return addPanel; + } + + /*************************************************************************** + * This will create the Modify panel in the LdapConfigGui + **************************************************************************/ + private JPanel createModifyPanel() { + JPanel modifyPanel = new JPanel(new BorderLayout(5, 0)); + JPanel modifyInnerPanel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("entrydn")); // $NON-NLS-1$ + label.setLabelFor(modify); + modifyInnerPanel.add(label, BorderLayout.WEST); + modifyInnerPanel.add(modify, BorderLayout.CENTER); + modifyPanel.add(modifyInnerPanel, BorderLayout.NORTH); + modifyPanel.add(tableModifyPanel, BorderLayout.CENTER); + return modifyPanel; + } + + /*************************************************************************** + * This will create the user defined test panel for create or modify or + * delete or search based on the panel selected in the itemevent in the + * LdapConfigGui + **************************************************************************/ + private JPanel testPanel() { + cards = new JPanel(new CardLayout()); + cards.add(new JPanel(), CARDS_DEFAULT); + cards.add(createAddPanel(), CARDS_ADD); + cards.add(createModifyPanel(), CARDS_MODIFY); + cards.add(createModdnPanel(), CARDS_RENAME); + cards.add(createDeletePanel(), CARDS_DELETE); + cards.add(createSearchPanel(), CARDS_SEARCH); + cards.add(createBindPanel(), CARDS_BIND); + cards.add(createComparePanel(), CARDS_COMPARE); + return cards; + } + + /*************************************************************************** + * This will create the test panel in the LdapConfigGui + **************************************************************************/ + private JPanel createTestPanel() { + JPanel testPanel = new JPanel(new BorderLayout()); + testPanel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("test_configuration"))); // $NON-NLS-1$ + + testPanel.add(new JLabel(JMeterUtils.getResString("testt"))); // $NON-NLS-1$ + JPanel rowPanel = new JPanel(); + JPanel row2Panel = new JPanel(); + + rowPanel.add(bind); + bGroup.add(bind); + rowPanel.add(unbind); + bGroup.add(unbind); + rowPanel.add(sbind); + bGroup.add(sbind); + rowPanel.add(rename); + bGroup.add(rename); + row2Panel.add(addTest); + bGroup.add(addTest); + row2Panel.add(deleteTest); + bGroup.add(deleteTest); + row2Panel.add(searchTest); + bGroup.add(searchTest); + row2Panel.add(compare); + bGroup.add(compare); + row2Panel.add(modifyTest); + bGroup.add(modifyTest); + testPanel.add(rowPanel, BorderLayout.NORTH); + testPanel.add(row2Panel, BorderLayout.SOUTH); + return testPanel; + } + + /*************************************************************************** + * This will initalise all the panel in the LdapConfigGui + **************************************************************************/ + private void init() { + setLayout(new BorderLayout(0, 5)); + if (displayName) { + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + } + VerticalPanel mainPanel = new VerticalPanel(); + mainPanel.add(createTestPanel()); + mainPanel.add(testPanel()); + add(mainPanel, BorderLayout.CENTER); + // Take note of when buttong are changed so can change panel + bind.addItemListener(this); + sbind.addItemListener(this); + unbind.addItemListener(this); + compare.addItemListener(this); + addTest.addItemListener(this); + modifyTest.addItemListener(this); + rename.addItemListener(this); + deleteTest.addItemListener(this); + searchTest.addItemListener(this); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/control/gui/LdapExtTestSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/control/gui/LdapExtTestSamplerGui.java new file mode 100644 index 0000000..a189ffb --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/control/gui/LdapExtTestSamplerGui.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ldap.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.JPanel; + +import org.apache.jmeter.protocol.ldap.config.gui.LdapExtConfigGui; +import org.apache.jmeter.protocol.ldap.sampler.LDAPExtSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; + +/******************************************************************************* + * + * author Dolf Smits(Dolf.Smits@Siemens.com) created Aug 09 2003 11:00 AM + * company Siemens Netherlands N.V.. + * + * Based on the work of: author T.Elanjchezhiyan(chezhiyan@siptech.co.in) + * created Apr 29 2003 11:00 AM company Sip Technologies and Exports Ltd. + * + ******************************************************************************/ + +public class LdapExtTestSamplerGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + private LdapExtConfigGui ldapDefaultPanel; + + /*************************************************************************** + * !ToDo (Constructor description) + **************************************************************************/ + public LdapExtTestSamplerGui() { + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + ldapDefaultPanel.configure(element); + } + + public TestElement createTestElement() { + LDAPExtSampler sampler = new LDAPExtSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement sampler) { + sampler.clear(); + ((LDAPExtSampler) sampler).addTestElement(ldapDefaultPanel.createTestElement()); + this.configureTestElement(sampler); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + ldapDefaultPanel.clearGui(); + } + + public String getLabelResource() { + return "ldapext_testing_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + // MAIN PANEL + JPanel mainPanel = new JPanel(new BorderLayout(0, 5)); + ldapDefaultPanel = new LdapExtConfigGui(false); + mainPanel.add(ldapDefaultPanel); + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/control/gui/LdapTestSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/control/gui/LdapTestSamplerGui.java new file mode 100644 index 0000000..38ea380 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/control/gui/LdapTestSamplerGui.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ldap.control.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; + +import org.apache.jmeter.config.gui.LoginConfigGui; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.ldap.config.gui.LdapConfigGui; +import org.apache.jmeter.protocol.ldap.sampler.LDAPSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class LdapTestSamplerGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + private LoginConfigGui loginPanel; + + private LdapConfigGui ldapDefaultPanel; + + public LdapTestSamplerGui() { + init(); + } + + /** + * A newly created component can be initialized with the contents of a Test + * Element object by calling this method. The component is responsible for + * querying the Test Element object for the relevant information to display + * in its GUI. + * + * @param element + * the TestElement to configure + */ + @Override + public void configure(TestElement element) { + super.configure(element); + loginPanel.configure(element); + ldapDefaultPanel.configure(element); + } + + public TestElement createTestElement() { + LDAPSampler sampler = new LDAPSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement sampler) { + sampler.clear(); + ((LDAPSampler) sampler).addTestElement(ldapDefaultPanel.createTestElement()); + ((LDAPSampler) sampler).addTestElement(loginPanel.createTestElement()); + this.configureTestElement(sampler); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + ldapDefaultPanel.clearGui(); + loginPanel.clearGui(); + } + + public String getLabelResource() { + return "ldap_testing_title"; // $NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + // MAIN PANEL + VerticalPanel mainPanel = new VerticalPanel(); + loginPanel = new LoginConfigGui(false); + ldapDefaultPanel = new LdapConfigGui(false); + loginPanel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("login_config"))); // $NON-NLS-1$ + add(makeTitlePanel(), BorderLayout.NORTH); + mainPanel.add(loginPanel); + mainPanel.add(ldapDefaultPanel); + add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LDAPExtSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LDAPExtSampler.java new file mode 100644 index 0000000..4e3e5e9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LDAPExtSampler.java @@ -0,0 +1,1098 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ldap.sampler; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchResult; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.protocol.ldap.config.gui.LDAPArgument; +import org.apache.jmeter.protocol.ldap.config.gui.LDAPArguments; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.XMLBuffer; +import org.apache.log.Logger; + +/******************************************************************************* + * Ldap Sampler class is main class for the LDAP test. This will control all the + * test available in the LDAP Test. + ******************************************************************************/ + +public class LDAPExtSampler extends AbstractSampler implements TestListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.protocol.ldap.config.gui.LdapConfigGui", + "org.apache.jmeter.protocol.ldap.config.gui.LdapExtConfigGui", + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + /* + * The following strings are used in the test plan, and the values must not be changed + * if test plans are to be upwardly compatible. + */ + public static final String SERVERNAME = "servername"; // $NON-NLS-1$ + + public static final String PORT = "port"; // $NON-NLS-1$ + + public static final String SECURE = "secure"; // $NON-NLS-1$ + + public static final String ROOTDN = "rootdn"; // $NON-NLS-1$ + + public static final String TEST = "test"; // $NON-NLS-1$ + + // These are values for the TEST attribute above + public static final String ADD = "add"; // $NON-NLS-1$ + + public static final String MODIFY = "modify"; // $NON-NLS-1$ + + public static final String BIND = "bind"; // $NON-NLS-1$ + + public static final String UNBIND = "unbind"; // $NON-NLS-1$ + + public static final String DELETE = "delete"; // $NON-NLS-1$ + + public static final String SEARCH = "search"; // $NON-NLS-1$ + // end of TEST values + + public static final String SEARCHBASE = "search"; // $NON-NLS-1$ + + public static final String SEARCHFILTER = "searchfilter"; // $NON-NLS-1$ + + public static final String ARGUMENTS = "arguments"; // $NON-NLS-1$ + + public static final String LDAPARGUMENTS = "ldaparguments"; // $NON-NLS-1$ + + public static final String BASE_ENTRY_DN = "base_entry_dn"; // $NON-NLS-1$ + + public static final String SCOPE = "scope"; // $NON-NLS-1$ + + public static final String COUNTLIM = "countlimit"; // $NON-NLS-1$ + + public static final String TIMELIM = "timelimit"; // $NON-NLS-1$ + + public static final String ATTRIBS = "attributes"; // $NON-NLS-1$ + + public static final String RETOBJ = "return_object"; // $NON-NLS-1$ + + public static final String DEREF = "deref_aliases"; // $NON-NLS-1$ + + public static final String USERDN = "user_dn"; // $NON-NLS-1$ + + public static final String USERPW = "user_pw"; // $NON-NLS-1$ + + public static final String SBIND = "sbind"; // $NON-NLS-1$ + + public static final String COMPARE = "compare"; // $NON-NLS-1$ + + public static final String CONNTO = "connection_timeout"; // $NON-NLS-1$ + + public static final String COMPAREDN = "comparedn"; // $NON-NLS-1$ + + public static final String COMPAREFILT = "comparefilt"; // $NON-NLS-1$ + + public static final String PARSEFLAG = "parseflag"; // $NON-NLS-1$ + + public static final String RENAME = "rename"; // $NON-NLS-1$ + + public static final String MODDDN = "modddn"; // $NON-NLS-1$ + + public static final String NEWDN = "newdn"; // $NON-NLS-1$ + + private static final String SEMI_COLON = ";"; // $NON-NLS-1$ + + + private static final ConcurrentHashMap ldapContexts = + new ConcurrentHashMap(); + + private static final int MAX_SORTED_RESULTS = + JMeterUtils.getPropDefault("ldapsampler.max_sorted_results", 1000); // $NON-NLS-1$ + + /*************************************************************************** + * !ToDo (Constructor description) + **************************************************************************/ + public LDAPExtSampler() { + } + + public void setConnTimeOut(String connto) { + setProperty(new StringProperty(CONNTO, connto)); + } + + public String getConnTimeOut() { + return getPropertyAsString(CONNTO); + } + + public void setSecure(String sec) { + setProperty(new StringProperty(SECURE, sec)); + } + + public boolean isSecure() { + return getPropertyAsBoolean(SECURE); + } + + + public boolean isParseFlag() { + return getPropertyAsBoolean(PARSEFLAG); + } + + public void setParseFlag(String parseFlag) { + setProperty(new StringProperty(PARSEFLAG, parseFlag)); + } + + /*************************************************************************** + * Gets the username attribute of the LDAP object + * + * @return The username + **************************************************************************/ + + public String getUserDN() { + return getPropertyAsString(USERDN); + } + + /*************************************************************************** + * Sets the username attribute of the LDAP object + * + **************************************************************************/ + + public void setUserDN(String newUserDN) { + setProperty(new StringProperty(USERDN, newUserDN)); + } + + /*************************************************************************** + * Gets the password attribute of the LDAP object + * + * @return The password + **************************************************************************/ + + public String getUserPw() { + return getPropertyAsString(USERPW); + } + + /*************************************************************************** + * Sets the password attribute of the LDAP object + * + **************************************************************************/ + + public void setUserPw(String newUserPw) { + setProperty(new StringProperty(USERPW, newUserPw)); + } + + /*************************************************************************** + * Sets the Servername attribute of the ServerConfig object + * + * @param servername + * The new servername value + **************************************************************************/ + public void setServername(String servername) { + setProperty(new StringProperty(SERVERNAME, servername)); + } + + /*************************************************************************** + * Sets the Port attribute of the ServerConfig object + * + * @param port + * The new Port value + **************************************************************************/ + public void setPort(String port) { + setProperty(new StringProperty(PORT, port)); + } + + /*************************************************************************** + * Gets the servername attribute of the LDAPSampler object + * + * @return The Servername value + **************************************************************************/ + + public String getServername() { + return getPropertyAsString(SERVERNAME); + } + + /*************************************************************************** + * Gets the Port attribute of the LDAPSampler object + * + * @return The Port value + **************************************************************************/ + + public String getPort() { + return getPropertyAsString(PORT); + } + + /*************************************************************************** + * Sets the Rootdn attribute of the LDAPSampler object + * + * @param newRootdn + * The new rootdn value + **************************************************************************/ + public void setRootdn(String newRootdn) { + this.setProperty(ROOTDN, newRootdn); + } + + /*************************************************************************** + * Gets the Rootdn attribute of the LDAPSampler object + * + * @return The Rootdn value + **************************************************************************/ + public String getRootdn() { + return getPropertyAsString(ROOTDN); + } + + /*************************************************************************** + * Gets the search scope attribute of the LDAPSampler object + * + * @return The scope value + **************************************************************************/ + public String getScope() { + return getPropertyAsString(SCOPE); + } + + public int getScopeAsInt() { + return getPropertyAsInt(SCOPE); + } + + /*************************************************************************** + * Sets the search scope attribute of the LDAPSampler object + * + * @param newScope + * The new scope value + **************************************************************************/ + public void setScope(String newScope) { + this.setProperty(SCOPE, newScope); + } + + /*************************************************************************** + * Gets the size limit attribute of the LDAPSampler object + * + * @return The size limit + **************************************************************************/ + public String getCountlim() { + return getPropertyAsString(COUNTLIM); + } + + public long getCountlimAsLong() { + return getPropertyAsLong(COUNTLIM); + } + + /*************************************************************************** + * Sets the size limit attribute of the LDAPSampler object + * + * @param newClim + * The new size limit value + **************************************************************************/ + public void setCountlim(String newClim) { + this.setProperty(COUNTLIM, newClim); + } + + /*************************************************************************** + * Gets the time limit attribute of the LDAPSampler object + * + * @return The time limit + **************************************************************************/ + public String getTimelim() { + return getPropertyAsString(TIMELIM); + } + + public int getTimelimAsInt() { + return getPropertyAsInt(TIMELIM); + } + + /*************************************************************************** + * Sets the time limit attribute of the LDAPSampler object + * + * @param newTlim + * The new time limit value + **************************************************************************/ + public void setTimelim(String newTlim) { + this.setProperty(TIMELIM, newTlim); + } + + /*************************************************************************** + * Gets the return objects attribute of the LDAPSampler object + * + * @return if the object(s) are to be returned + **************************************************************************/ + public boolean isRetobj() { + return getPropertyAsBoolean(RETOBJ); + } + + /*************************************************************************** + * Sets the return objects attribute of the LDAPSampler object + * + **************************************************************************/ + public void setRetobj(String newRobj) { + this.setProperty(RETOBJ, newRobj); + } + + /*************************************************************************** + * Gets the deref attribute of the LDAPSampler object + * + * @return if dereferencing is required + **************************************************************************/ + public boolean isDeref() { + return getPropertyAsBoolean(DEREF); + } + + /*************************************************************************** + * Sets the deref attribute of the LDAPSampler object + * + * @param newDref + * The new deref value + **************************************************************************/ + public void setDeref(String newDref) { + this.setProperty(DEREF, newDref); + } + + /*************************************************************************** + * Sets the Test attribute of the LdapConfig object + * + * @param newTest + * The new test value(Add,Modify,Delete and search) + **************************************************************************/ + public void setTest(String newTest) { + this.setProperty(TEST, newTest); + } + + /*************************************************************************** + * Gets the test attribute of the LDAPSampler object + * + * @return The test value (Add,Modify,Delete and search) + **************************************************************************/ + public String getTest() { + return getPropertyAsString(TEST); + } + + /*************************************************************************** + * Sets the attributes of the LdapConfig object + * + * @param newAttrs + * The new attributes value + **************************************************************************/ + public void setAttrs(String newAttrs) { + this.setProperty(ATTRIBS, newAttrs); + } + + /*************************************************************************** + * Gets the attributes of the LDAPSampler object + * + * @return The attributes + **************************************************************************/ + public String getAttrs() { + return getPropertyAsString(ATTRIBS); + } + + /*************************************************************************** + * Sets the Base Entry DN attribute of the LDAPSampler object + * + * @param newbaseentry + * The new Base entry DN value + **************************************************************************/ + public void setBaseEntryDN(String newbaseentry) { + setProperty(new StringProperty(BASE_ENTRY_DN, newbaseentry)); + } + + /*************************************************************************** + * Gets the BaseEntryDN attribute of the LDAPSampler object + * + * @return The Base entry DN value + **************************************************************************/ + public String getBaseEntryDN() { + return getPropertyAsString(BASE_ENTRY_DN); + } + + /*************************************************************************** + * Sets the Arguments attribute of the LdapConfig object This will collect + * values from the table for user defined test case + * + * @param value + * The arguments + **************************************************************************/ + public void setArguments(Arguments value) { + setProperty(new TestElementProperty(ARGUMENTS, value)); + } + + /*************************************************************************** + * Gets the Arguments attribute of the LdapConfig object + * + * @return The arguments user defined test case + **************************************************************************/ + public Arguments getArguments() { + return (Arguments) getProperty(ARGUMENTS).getObjectValue(); + } + + /*************************************************************************** + * Sets the Arguments attribute of the LdapConfig object This will collect + * values from the table for user defined test case + * + * @param value + * The arguments + **************************************************************************/ + public void setLDAPArguments(LDAPArguments value) { + setProperty(new TestElementProperty(LDAPARGUMENTS, value)); + } + + /*************************************************************************** + * Gets the LDAPArguments attribute of the LdapConfig object + * + * @return The LDAParguments user defined modify test case + **************************************************************************/ + public LDAPArguments getLDAPArguments() { + return (LDAPArguments) getProperty(LDAPARGUMENTS).getObjectValue(); + } + + /*************************************************************************** + * Collect all the values from the table (Arguments), using this create the + * Attributes, this will create the Attributes for the User + * defined TestCase for Add Test + * + * @return The Attributes + **************************************************************************/ + private Attributes getUserAttributes() { + Attributes attrs = new BasicAttributes(true); + Attribute attr; + PropertyIterator iter = getArguments().iterator(); + + while (iter.hasNext()) { + Argument item = (Argument) iter.next().getObjectValue(); + attr = attrs.get(item.getName()); + if (attr == null) { + attr = getBasicAttribute(item.getName(), item.getValue()); + } else { + attr.add(item.getValue()); + } + attrs.put(attr); + } + return attrs; + } + + /*************************************************************************** + * Collect all the value from the table (Arguments), using this create the + * basicAttributes This will create the Basic Attributes for the User + * defined TestCase for Modify test + * + * @return The BasicAttributes + **************************************************************************/ + private ModificationItem[] getUserModAttributes() { + ModificationItem[] mods = new ModificationItem[getLDAPArguments().getArguments().size()]; + BasicAttribute attr; + PropertyIterator iter = getLDAPArguments().iterator(); + int count = 0; + while (iter.hasNext()) { + LDAPArgument item = (LDAPArgument) iter.next().getObjectValue(); + if ((item.getValue()).length()==0) { + attr = new BasicAttribute(item.getName()); + } else { + attr = getBasicAttribute(item.getName(), item.getValue()); + } + + final String opcode = item.getOpcode(); + if ("add".equals(opcode)) { // $NON-NLS-1$ + mods[count++] = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr); + } else if ("delete".equals(opcode) // $NON-NLS-1$ + || "remove".equals(opcode)) { // $NON-NLS-1$ + mods[count++] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr); + } else if("replace".equals(opcode)) { // $NON-NLS-1$ + mods[count++] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr); + } else { + log.warn("Invalid opCode: "+opcode); + } + } + return mods; + } + + /*************************************************************************** + * Collect all the value from the table (Arguments), using this create the + * Attributes This will create the Basic Attributes for the User defined + * TestCase for search test + * + * @return The BasicAttributes + **************************************************************************/ + private String[] getRequestAttributes(String reqAttr) { + int index; + String[] mods; + int count = 0; + if (reqAttr.length() == 0) { + return null; + } + if (!reqAttr.endsWith(SEMI_COLON)) { + reqAttr = reqAttr + SEMI_COLON; + } + String attr = reqAttr; + + while (attr.length() > 0) { + index = attr.indexOf(SEMI_COLON); + count += 1; + attr = attr.substring(index + 1); + } + if (count > 0) { + mods = new String[count]; + attr = reqAttr; + count = 0; + while (attr.length() > 0) { + index = attr.indexOf(SEMI_COLON); + mods[count] = attr.substring(0, index); + count += 1; + attr = attr.substring(index + 1); + } + } else { + mods = null; + } + return mods; + } + + /*************************************************************************** + * This will create the Basic Attribute for the give name value pair + * + * @return The BasicAttribute + **************************************************************************/ + private BasicAttribute getBasicAttribute(String name, String value) { + BasicAttribute attr = new BasicAttribute(name, value); + return attr; + } + + /** + * Returns a formatted string label describing this sampler Example output: + * + * @return a formatted string label describing this sampler + */ + public String getLabel() { + return ("ldap://" + this.getServername() //$NON-NLS-1$ + + ":" + getPort() //$NON-NLS-1$ + + "/" + this.getRootdn()); //$NON-NLS-1$ + } + + /*************************************************************************** + * This will do the add test for the User defined TestCase + * + **************************************************************************/ + private void addTest(DirContext dirContext, SampleResult res) throws NamingException { + try { + res.sampleStart(); + DirContext ctx = LdapExtClient.createTest(dirContext, getUserAttributes(), getBaseEntryDN()); + ctx.close(); // the createTest() method creates an extra context which needs to be closed + } finally { + res.sampleEnd(); + } + } + + /*************************************************************************** + * This will do the delete test for the User defined TestCase + * + **************************************************************************/ + private void deleteTest(DirContext dirContext, SampleResult res) throws NamingException { + try { + res.sampleStart(); + LdapExtClient.deleteTest(dirContext, getPropertyAsString(DELETE)); + } finally { + res.sampleEnd(); + } + } + + /*************************************************************************** + * This will do the modify test for the User defined TestCase + * + **************************************************************************/ + private void modifyTest(DirContext dirContext, SampleResult res) throws NamingException { + try { + res.sampleStart(); + LdapExtClient.modifyTest(dirContext, getUserModAttributes(), getBaseEntryDN()); + } finally { + res.sampleEnd(); + } + } + + /*************************************************************************** + * This will do the bind for the User defined Thread, this bind is used for + * the whole context + * + **************************************************************************/ + private void bindOp(DirContext dirContext, SampleResult res) throws NamingException { + DirContext ctx = ldapContexts.remove(getThreadName()); + if (ctx != null) { + log.warn("Closing previous context for thread: " + getThreadName()); + ctx.close(); + } + try { + res.sampleStart(); + ctx = LdapExtClient.connect(getServername(), getPort(), getRootdn(), getUserDN(), getUserPw(),getConnTimeOut(),isSecure()); + } finally { + res.sampleEnd(); + } + ldapContexts.put(getThreadName(), ctx); + } + + /*************************************************************************** + * This will do the bind and unbind for the User defined TestCase + * + **************************************************************************/ + private void singleBindOp(SampleResult res) throws NamingException { + try { + res.sampleStart(); + DirContext ctx = LdapExtClient.connect(getServername(), getPort(), getRootdn(), getUserDN(), getUserPw(),getConnTimeOut(),isSecure()); + LdapExtClient.disconnect(ctx); + } finally { + res.sampleEnd(); + } + } + + /*************************************************************************** + * This will do a moddn Opp for the User new DN defined + * + **************************************************************************/ + private void renameTest(DirContext dirContext, SampleResult res) throws NamingException { + try { + res.sampleStart(); + LdapExtClient.moddnOp(dirContext, getPropertyAsString(MODDDN), getPropertyAsString(NEWDN)); + } finally { + res.sampleEnd(); + } + } + + /*************************************************************************** + * This will do the unbind for the User defined TestCase as well as inbuilt + * test case + * + **************************************************************************/ + private void unbindOp(DirContext dirContext, SampleResult res) { + try { + res.sampleStart(); + LdapExtClient.disconnect(dirContext); + } finally { + res.sampleEnd(); + } + ldapContexts.remove(getThreadName()); + log.info("context and LdapExtClients removed"); + } + + /*************************************************************************** + * !ToDo (Method description) + * + * @param e + * !ToDo (Parameter description) + * @return !ToDo (Return description) + **************************************************************************/ + public SampleResult sample(Entry e) { + XMLBuffer xmlBuffer = new XMLBuffer(); + xmlBuffer.openTag("ldapanswer"); // $NON-NLS-1$ + SampleResult res = new SampleResult(); + res.setResponseData("successfull", null); + res.setResponseMessage("Success"); // $NON-NLS-1$ + res.setResponseCode("0"); // $NON-NLS-1$ + res.setContentType("text/xml");// $NON-NLS-1$ + boolean isSuccessful = true; + res.setSampleLabel(getName()); + DirContext dirContext = ldapContexts.get(getThreadName()); + + try { + xmlBuffer.openTag("operation"); // $NON-NLS-1$ + final String testType = getTest(); + xmlBuffer.tag("opertype", testType); // $NON-NLS-1$ + log.debug("performing test: " + testType); + if (testType.equals(UNBIND)) { + res.setSamplerData("Unbind"); + xmlBuffer.tag("baseobj",getRootdn()); // $NON-NLS-1$ + xmlBuffer.tag("binddn",getUserDN()); // $NON-NLS-1$ + unbindOp(dirContext, res); + } else if (testType.equals(BIND)) { + res.setSamplerData("Bind as "+getUserDN()); + xmlBuffer.tag("baseobj",getRootdn()); // $NON-NLS-1$ + xmlBuffer.tag("binddn",getUserDN()); // $NON-NLS-1$ + xmlBuffer.tag("connectionTO",getConnTimeOut()); // $NON-NLS-1$ + bindOp(dirContext, res); + } else if (testType.equals(SBIND)) { + res.setSamplerData("SingleBind as "+getUserDN()); + xmlBuffer.tag("baseobj",getRootdn()); // $NON-NLS-1$ + xmlBuffer.tag("binddn",getUserDN()); // $NON-NLS-1$ + xmlBuffer.tag("connectionTO",getConnTimeOut()); // $NON-NLS-1$ + singleBindOp(res); + } else if (testType.equals(COMPARE)) { + res.setSamplerData("Compare "+getPropertyAsString(COMPAREFILT) + " " + + getPropertyAsString(COMPAREDN)); + xmlBuffer.tag("comparedn",getPropertyAsString(COMPAREDN)); // $NON-NLS-1$ + xmlBuffer.tag("comparefilter",getPropertyAsString(COMPAREFILT)); // $NON-NLS-1$ + NamingEnumeration cmp=null; + try { + res.sampleStart(); + cmp = LdapExtClient.compare(dirContext, getPropertyAsString(COMPAREFILT), + getPropertyAsString(COMPAREDN)); + if (!cmp.hasMore()) { + res.setResponseCode("5"); // $NON-NLS-1$ + res.setResponseMessage("compareFalse"); + isSuccessful = false; + } + } finally { + res.sampleEnd(); + if (cmp != null) { + cmp.close(); + } + } + } else if (testType.equals(ADD)) { + res.setSamplerData("Add object " + getBaseEntryDN()); + xmlBuffer.tag("attributes",getArguments().toString()); // $NON-NLS-1$ + xmlBuffer.tag("dn",getBaseEntryDN()); // $NON-NLS-1$ + addTest(dirContext, res); + } else if (testType.equals(DELETE)) { + res.setSamplerData("Delete object " + getBaseEntryDN()); + xmlBuffer.tag("dn",getBaseEntryDN()); // $NON-NLS-1$ + deleteTest(dirContext, res); + } else if (testType.equals(MODIFY)) { + res.setSamplerData("Modify object " + getBaseEntryDN()); + xmlBuffer.tag("dn",getBaseEntryDN()); // $NON-NLS-1$ + xmlBuffer.tag("attributes",getLDAPArguments().toString()); // $NON-NLS-1$ + modifyTest(dirContext, res); + } else if (testType.equals(RENAME)) { + res.setSamplerData("ModDN object " + getPropertyAsString(MODDDN) + " to " + getPropertyAsString(NEWDN)); + xmlBuffer.tag("dn",getPropertyAsString(MODDDN)); // $NON-NLS-1$ + xmlBuffer.tag("newdn",getPropertyAsString(NEWDN)); // $NON-NLS-1$ + renameTest(dirContext, res); + } else if (testType.equals(SEARCH)) { + final String scopeStr = getScope(); + final int scope = getScopeAsInt(); + final String searchFilter = getPropertyAsString(SEARCHFILTER); + final String searchBase = getPropertyAsString(SEARCHBASE); + final String timeLimit = getTimelim(); + final String countLimit = getCountlim(); + + res.setSamplerData("Search with filter " + searchFilter); + xmlBuffer.tag("searchfilter",searchFilter); // $NON-NLS-1$ + xmlBuffer.tag("baseobj",getRootdn()); // $NON-NLS-1$ + xmlBuffer.tag("searchbase",searchBase);// $NON-NLS-1$ + xmlBuffer.tag("scope" , scopeStr); // $NON-NLS-1$ + xmlBuffer.tag("countlimit",countLimit); // $NON-NLS-1$ + xmlBuffer.tag("timelimit",timeLimit); // $NON-NLS-1$ + + NamingEnumeration srch=null; + try { + res.sampleStart(); + srch = LdapExtClient.searchTest( + dirContext, searchBase, searchFilter, + scope, getCountlimAsLong(), + getTimelimAsInt(), + getRequestAttributes(getAttrs()), + isRetobj(), + isDeref()); + if (isParseFlag()) { + try { + xmlBuffer.openTag("searchresults"); // $NON-NLS-1$ + writeSearchResults(xmlBuffer, srch); + } finally { + xmlBuffer.closeTag("searchresults"); // $NON-NLS-1$ + } + } else { + xmlBuffer.tag("searchresults", // $NON-NLS-1$ + "hasElements="+srch.hasMoreElements()); // $NON-NLS-1$ + } + } finally { + if (srch != null){ + srch.close(); + } + res.sampleEnd(); + } + + } + + } catch (NamingException ex) { + //log.warn("DEBUG",ex); +// e.g. javax.naming.SizeLimitExceededException: [LDAP: error code 4 - Sizelimit Exceeded]; remaining name '' +// 123456789012345678901 + // TODO: tidy this up + String returnData = ex.toString(); + final int indexOfLDAPErrCode = returnData.indexOf("LDAP: error code"); + if (indexOfLDAPErrCode >= 0) { + res.setResponseMessage(returnData.substring(indexOfLDAPErrCode + 21, returnData + .indexOf("]"))); // $NON-NLS-1$ + res.setResponseCode(returnData.substring(indexOfLDAPErrCode + 17, indexOfLDAPErrCode + 19)); + } else { + res.setResponseMessage(returnData); + res.setResponseCode("800"); // $NON-NLS-1$ + } + isSuccessful = false; + } finally { + xmlBuffer.closeTag("operation"); // $NON-NLS-1$ + xmlBuffer.tag("responsecode",res.getResponseCode()); // $NON-NLS-1$ + xmlBuffer.tag("responsemessage",res.getResponseMessage()); // $NON-NLS-1$ + res.setResponseData(xmlBuffer.toString(), null); + res.setDataType(SampleResult.TEXT); + res.setSuccessful(isSuccessful); + } + return res; + } + + /* + * Write out search results in a stable order (including order of all subelements which might + * be reordered like attributes and their values) so that simple textual comparison can be done, + * unless the number of results exceeds {@link #MAX_SORTED_RESULTS} in which case just stream + * the results out without sorting. + */ + private void writeSearchResults(final XMLBuffer xmlb, final NamingEnumeration srch) + throws NamingException + { + + final ArrayList sortedResults = new ArrayList(MAX_SORTED_RESULTS); + final String searchBase = getPropertyAsString(SEARCHBASE); + final String rootDn = getRootdn(); + + // read all sortedResults into memory so we can guarantee ordering + try { + while (srch.hasMore() && (sortedResults.size() < MAX_SORTED_RESULTS)) { + final SearchResult sr = srch.next(); + + // must be done prior to sorting + normaliseSearchDN(sr, searchBase, rootDn); + sortedResults.add(sr); + } + } finally { // show what we did manage to retrieve + + sortResults(sortedResults); + + for (Iterator it = sortedResults.iterator(); it.hasNext();) + { + final SearchResult sr = it.next(); + writeSearchResult(sr, xmlb); + } + } + + while (srch.hasMore()) { // If there's anything left ... + final SearchResult sr = srch.next(); + + normaliseSearchDN(sr, searchBase, rootDn); + writeSearchResult(sr, xmlb); + } + } + + private void writeSearchResult(final SearchResult sr, final XMLBuffer xmlb) + throws NamingException + { + final Attributes attrs = sr.getAttributes(); + final int size = attrs.size(); + final ArrayList sortedAttrs = new ArrayList(size); + + xmlb.openTag("searchresult"); // $NON-NLS-1$ + xmlb.tag("dn", sr.getName()); // $NON-NLS-1$ + xmlb.tag("returnedattr",Integer.toString(size)); // $NON-NLS-1$ + xmlb.openTag("attributes"); // $NON-NLS-1$ + + try { + for (NamingEnumeration en = attrs.getAll(); en.hasMore(); ) + { + final Attribute attr = en.next(); + + sortedAttrs.add(attr); + } + sortAttributes(sortedAttrs); + for (Iterator ait = sortedAttrs.iterator(); ait.hasNext();) + { + final Attribute attr = ait.next(); + + StringBuilder sb = new StringBuilder(); + if (attr.size() == 1) { + sb.append(getWriteValue(attr.get())); + } else { + final ArrayList sortedVals = new ArrayList(attr.size()); + boolean first = true; + + for (NamingEnumeration ven = attr.getAll(); ven.hasMore(); ) + { + final Object value = getWriteValue(ven.next()); + sortedVals.add(value.toString()); + } + + Collections.sort(sortedVals); + + for (Iterator vit = sortedVals.iterator(); vit.hasNext();) + { + final String value = vit.next(); + if (first) { + first = false; + } else { + sb.append(", "); // $NON-NLS-1$ + } + sb.append(value); + } + } + xmlb.tag(attr.getID(),sb); + } + } finally { + xmlb.closeTag("attributes"); // $NON-NLS-1$ + xmlb.closeTag("searchresult"); // $NON-NLS-1$ + } + } + + private void sortAttributes(final ArrayList sortedAttrs) { + Collections.sort(sortedAttrs, new Comparator() + { + public int compare(Attribute o1, Attribute o2) + { + String nm1 = o1.getID(); + String nm2 = o2.getID(); + + return nm1.compareTo(nm2); + } + }); + } + + private void sortResults(final ArrayList sortedResults) { + Collections.sort(sortedResults, new Comparator() + { + private int compareToReverse(final String s1, final String s2) + { + int len1 = s1.length(); + int len2 = s2.length(); + int s1i = len1 - 1; + int s2i = len2 - 1; + + for ( ; (s1i >= 0) && (s2i >= 0); s1i--, s2i--) + { + char c1 = s1.charAt(s1i); + char c2 = s2.charAt(s2i); + + if (c1 != c2) { + return c1 - c2; + } + } + return len1 - len2; + } + + public int compare(SearchResult o1, SearchResult o2) + { + String nm1 = o1.getName(); + String nm2 = o2.getName(); + + if (nm1 == null) { + nm1 = ""; + } + if (nm2 == null) { + nm2 = ""; + } + return compareToReverse(nm1, nm2); + } + }); + } + + private String normaliseSearchDN(final SearchResult sr, final String searchBase, final String rootDn) + { + String srName = sr.getName(); + + if (!srName.endsWith(searchBase)) + { + if (srName.length() > 0) { + srName = srName + ','; + } + srName = srName + searchBase; + } + if ((rootDn.length() > 0) && !srName.endsWith(rootDn)) + { + if (srName.length() > 0) { + srName = srName + ','; + } + srName = srName + rootDn; + } + sr.setName(srName); + return srName; + } + + private String getWriteValue(final Object value) + { + if (value instanceof String) { + // assume it's senstive data + return StringEscapeUtils.escapeXml((String)value); + } + if (value instanceof byte[]) { + try + { + return StringEscapeUtils.escapeXml(new String((byte[])value, "UTF-8")); //$NON-NLS-1$ + } + catch (UnsupportedEncodingException e) + { + log.error("this can't happen: UTF-8 character encoding not supported", e); + } + } + return StringEscapeUtils.escapeXml(value.toString()); + } + + public void testStarted() { + testStarted(""); // $NON-NLS-1$ + } + + public void testEnded() { + testEnded(""); // $NON-NLS-1$ + } + + public void testStarted(String host) { + // ignored + } + + // Ensure any remaining contexts are closed + public void testEnded(String host) { + for (Map.Entry entry : ldapContexts.entrySet()) { + DirContext dc = entry.getValue(); + try { + log.warn("Tidying old Context for thread: " + entry.getKey()); + dc.close(); + } catch (NamingException ignored) { + // ignored + } + } + ldapContexts.clear(); + } + + public void testIterationStart(LoopIterationEvent event) { + // ignored + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LDAPSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LDAPSampler.java new file mode 100644 index 0000000..a0a872c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LDAPSampler.java @@ -0,0 +1,490 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ldap.sampler; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Ldap Sampler class is main class for the LDAP test. This will control all the + * test available in the LDAP Test. + * + */ +public class LDAPSampler extends AbstractSampler { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.LoginConfigGui", + "org.apache.jmeter.protocol.ldap.config.gui.LdapConfigGui", + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + public static final String SERVERNAME = "servername"; //$NON-NLS-1$ + + public static final String PORT = "port"; //$NON-NLS-1$ + + public static final String ROOTDN = "rootdn"; //$NON-NLS-1$ + + public static final String TEST = "test"; //$NON-NLS-1$ + + public static final String ADD = "add"; //$NON-NLS-1$ + + public static final String MODIFY = "modify"; //$NON-NLS-1$ + + public static final String DELETE = "delete"; //$NON-NLS-1$ + + public static final String SEARCHBASE = "search"; //$NON-NLS-1$ + + public static final String SEARCHFILTER = "searchfilter"; //$NON-NLS-1$ + + public static final String USER_DEFINED = "user_defined"; //$NON-NLS-1$ + + public static final String ARGUMENTS = "arguments"; //$NON-NLS-1$ + + public static final String BASE_ENTRY_DN = "base_entry_dn"; //$NON-NLS-1$ + + // For In build test case using this counter + // create the new entry in the server + private static volatile int counter = 0; + + private boolean searchFoundEntries;// TODO turn into parameter? + + public LDAPSampler() { + } + + /** + * Gets the username attribute of the LDAP object. + * + * @return the username + */ + public String getUsername() { + return getPropertyAsString(ConfigTestElement.USERNAME); + } + + /** + * Gets the password attribute of the LDAP object. + * + * @return the password + */ + public String getPassword() { + return getPropertyAsString(ConfigTestElement.PASSWORD); + } + + /** + * Sets the Servername attribute of the ServerConfig object. + * + * @param servername + * the new servername value + */ + public void setServername(String servername) { + setProperty(new StringProperty(SERVERNAME, servername)); + } + + /** + * Sets the Port attribute of the ServerConfig object. + * + * @param port + * the new Port value + */ + public void setPort(String port) { + setProperty(new StringProperty(PORT, port)); + } + + /** + * Gets the servername attribute of the LDAPSampler object. + * + * @return the Servername value + */ + public String getServername() { + return getPropertyAsString(SERVERNAME); + } + + /** + * Gets the Port attribute of the LDAPSampler object. + * + * @return the Port value + */ + public String getPort() { + return getPropertyAsString(PORT); + } + + /** + * Sets the Rootdn attribute of the LDAPSampler object. + * + * @param newRootdn + * the new rootdn value + */ + public void setRootdn(String newRootdn) { + this.setProperty(ROOTDN, newRootdn); + } + + /** + * Gets the Rootdn attribute of the LDAPSampler object. + * + * @return the Rootdn value + */ + public String getRootdn() { + return getPropertyAsString(ROOTDN); + } + + /** + * Sets the Test attribute of the LdapConfig object. + * + * @param newTest + * the new test value(Add,Modify,Delete and search) + */ + public void setTest(String newTest) { + this.setProperty(TEST, newTest); + } + + /** + * Gets the test attribute of the LDAPSampler object. + * + * @return the test value (Add, Modify, Delete and search) + */ + public String getTest() { + return getPropertyAsString(TEST); + } + + /** + * Sets the UserDefinedTest attribute of the LDAPSampler object. + * + * @param value + * the new UserDefinedTest value + */ + public void setUserDefinedTest(boolean value) { + setProperty(new BooleanProperty(USER_DEFINED, value)); + } + + /** + * Gets the UserDefinedTest attribute of the LDAPSampler object. + * + * @return the test value true or false. If true it will do the + * UserDefinedTest else our own inbuild test case. + */ + public boolean getUserDefinedTest() { + return getPropertyAsBoolean(USER_DEFINED); + } + + /** + * Sets the Base Entry DN attribute of the LDAPSampler object. + * + * @param newbaseentry + * the new Base entry DN value + */ + public void setBaseEntryDN(String newbaseentry) { + setProperty(new StringProperty(BASE_ENTRY_DN, newbaseentry)); + } + + /** + * Gets the BaseEntryDN attribute of the LDAPSampler object. + * + * @return the Base entry DN value + */ + public String getBaseEntryDN() { + return getPropertyAsString(BASE_ENTRY_DN); + } + + /** + * Sets the Arguments attribute of the LdapConfig object. This will collect + * values from the table for user defined test case. + * + * @param value + * the arguments + */ + public void setArguments(Arguments value) { + setProperty(new TestElementProperty(ARGUMENTS, value)); + } + + /** + * Gets the Arguments attribute of the LdapConfig object. + * + * @return the arguments. User defined test case. + */ + public Arguments getArguments() { + return (Arguments) getProperty(ARGUMENTS).getObjectValue(); + } + + /** + * Collect all the value from the table (Arguments), using this create the + * basicAttributes. This will create the Basic Attributes for the User + * defined TestCase for Add Test. + * + * @return the BasicAttributes + */ + private BasicAttributes getUserAttributes() { + BasicAttribute basicattribute = new BasicAttribute("objectclass"); //$NON-NLS-1$ + basicattribute.add("top"); //$NON-NLS-1$ + basicattribute.add("person"); //$NON-NLS-1$ + basicattribute.add("organizationalPerson"); //$NON-NLS-1$ + basicattribute.add("inetOrgPerson"); //$NON-NLS-1$ + BasicAttributes attrs = new BasicAttributes(true); + attrs.put(basicattribute); + BasicAttribute attr; + PropertyIterator iter = getArguments().iterator(); + + while (iter.hasNext()) { + Argument item = (Argument) iter.next().getObjectValue(); + attr = getBasicAttribute(item.getName(), item.getValue()); + attrs.put(attr); + } + return attrs; + } + + /** + * Collect all the value from the table (Arguments), using this create the + * basicAttributes. This will create the Basic Attributes for the User + * defined TestCase for Modify test. + * + * @return the BasicAttributes + */ + private ModificationItem[] getUserModAttributes() { + ModificationItem[] mods = new ModificationItem[getArguments().getArguments().size()]; + BasicAttribute attr; + PropertyIterator iter = getArguments().iterator(); + int count = 0; + while (iter.hasNext()) { + Argument item = (Argument) iter.next().getObjectValue(); + attr = getBasicAttribute(item.getName(), item.getValue()); + mods[count] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr); + count = +1; + } + return mods; + } + + /** + * This will create the Basic Attributes for the Inbuilt TestCase for Modify + * test. + * + * @return the BasicAttributes + */ + private ModificationItem[] getModificationItem() { + ModificationItem[] mods = new ModificationItem[2]; + // replace (update) attribute + Attribute mod0 = new BasicAttribute("userpassword", "secret"); //$NON-NLS-1$ //$NON-NLS-2$ + // add mobile phone number attribute + Attribute mod1 = new BasicAttribute("mobile", "123-456-1234"); //$NON-NLS-1$ //$NON-NLS-2$ + + mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0); + mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1); + + return mods; + } + + /** + * This will create the Basic Attributes for the In build TestCase for Add + * Test. + * + * @return the BasicAttributes + */ + private BasicAttributes getBasicAttributes() { + BasicAttributes basicattributes = new BasicAttributes(); + BasicAttribute basicattribute = new BasicAttribute("objectclass"); //$NON-NLS-1$ + basicattribute.add("top"); //$NON-NLS-1$ + basicattribute.add("person"); //$NON-NLS-1$ + basicattribute.add("organizationalPerson"); //$NON-NLS-1$ + basicattribute.add("inetOrgPerson"); //$NON-NLS-1$ + basicattributes.put(basicattribute); + String s1 = "User"; //$NON-NLS-1$ + String s3 = "Test"; //$NON-NLS-1$ + String s5 = "user"; //$NON-NLS-1$ + String s6 = "test"; //$NON-NLS-1$ + counter += 1; + basicattributes.put(new BasicAttribute("givenname", s1)); //$NON-NLS-1$ + basicattributes.put(new BasicAttribute("sn", s3)); //$NON-NLS-1$ + basicattributes.put(new BasicAttribute("cn", "TestUser" + counter)); //$NON-NLS-1$ //$NON-NLS-2$ + basicattributes.put(new BasicAttribute("uid", s5)); //$NON-NLS-1$ + basicattributes.put(new BasicAttribute("userpassword", s6)); //$NON-NLS-1$ + setProperty(new StringProperty(ADD, "cn=TestUser" + counter)); //$NON-NLS-1$ + return basicattributes; + } + + /** + * This will create the Basic Attribute for the given name value pair. + * + * @return the BasicAttribute + */ + private BasicAttribute getBasicAttribute(String name, String value) { + BasicAttribute attr = new BasicAttribute(name, value); + return attr; + } + + /** + * Returns a formatted string label describing this sampler + * + * @return a formatted string label describing this sampler + */ + public String getLabel() { + return ("ldap://" + this.getServername() + ":" + getPort() + "/" + this.getRootdn()); + } + + /** + * This will do the add test for the User defined TestCase as well as + * inbuilt test case. + * + */ + private void addTest(LdapClient ldap, SampleResult res) throws NamingException { + if (getPropertyAsBoolean(USER_DEFINED)) { + res.sampleStart(); + ldap.createTest(getUserAttributes(), getPropertyAsString(BASE_ENTRY_DN)); + res.sampleEnd(); + } else { + res.sampleStart(); + ldap.createTest(getBasicAttributes(), getPropertyAsString(ADD)); + res.sampleEnd(); + ldap.deleteTest(getPropertyAsString(ADD)); + } + } + + /** + * This will do the delete test for the User defined TestCase as well as + * inbuilt test case. + * + */ + private void deleteTest(LdapClient ldap, SampleResult res) throws NamingException { + if (!getPropertyAsBoolean(USER_DEFINED)) { + ldap.createTest(getBasicAttributes(), getPropertyAsString(ADD)); + setProperty(new StringProperty(DELETE, getPropertyAsString(ADD))); + } + res.sampleStart(); + ldap.deleteTest(getPropertyAsString(DELETE)); + res.sampleEnd(); + } + + /** + * This will do the search test for the User defined TestCase as well as + * inbuilt test case. + * + */ + private void searchTest(LdapClient ldap, SampleResult res) throws NamingException { + if (!getPropertyAsBoolean(USER_DEFINED)) { + ldap.createTest(getBasicAttributes(), getPropertyAsString(ADD)); + setProperty(new StringProperty(SEARCHBASE, getPropertyAsString(ADD))); + setProperty(new StringProperty(SEARCHFILTER, getPropertyAsString(ADD))); + } + res.sampleStart(); + searchFoundEntries = ldap.searchTest(getPropertyAsString(SEARCHBASE), getPropertyAsString(SEARCHFILTER)); + res.sampleEnd(); + if (!getPropertyAsBoolean(USER_DEFINED)) { + ldap.deleteTest(getPropertyAsString(ADD)); + } + } + + /** + * This will do the search test for the User defined TestCase as well as + * inbuilt test case. + * + */ + private void modifyTest(LdapClient ldap, SampleResult res) throws NamingException { + if (getPropertyAsBoolean(USER_DEFINED)) { + res.sampleStart(); + ldap.modifyTest(getUserModAttributes(), getPropertyAsString(BASE_ENTRY_DN)); + res.sampleEnd(); + } else { + ldap.createTest(getBasicAttributes(), getPropertyAsString(ADD)); + setProperty(new StringProperty(MODIFY, getPropertyAsString(ADD))); + res.sampleStart(); + ldap.modifyTest(getModificationItem(), getPropertyAsString(MODIFY)); + res.sampleEnd(); + ldap.deleteTest(getPropertyAsString(ADD)); + } + } + + public SampleResult sample(Entry e) { + SampleResult res = new SampleResult(); + boolean isSuccessful = false; + res.setSampleLabel(getName()); + res.setSamplerData(getPropertyAsString(TEST));// TODO improve this + LdapClient ldap = new LdapClient(); + + try { + ldap.connect(getServername(), getPort(), getRootdn(), getUsername(), getPassword()); + + if (getPropertyAsString(TEST).equals(ADD)) { + addTest(ldap, res); + } else if (getPropertyAsString(TEST).equals(DELETE)) { + deleteTest(ldap, res); + } else if (getPropertyAsString(TEST).equals(MODIFY)) { + modifyTest(ldap, res); + } else if (getPropertyAsString(TEST).equals(SEARCHBASE)) { + searchTest(ldap, res); + } + + // TODO - needs more work ... + if (getPropertyAsString(TEST).equals(SEARCHBASE) && !searchFoundEntries) { + res.setResponseCode("201");// TODO is this a sensible number? //$NON-NLS-1$ + res.setResponseMessage("OK - no results"); + res.setResponseData("successful - no results", null); + } else { + res.setResponseCodeOK(); + res.setResponseMessage("OK"); //$NON-NLS-1$ + res.setResponseData("successful", null); + } + res.setDataType(SampleResult.TEXT); + isSuccessful = true; + } catch (Exception ex) { + log.error("Ldap client - ", ex); + // Could time this + // res.sampleEnd(); + // if sampleEnd() is not called, elapsed time will remain zero + res.setResponseCode("500");// TODO distinguish errors better //$NON-NLS-1$ + res.setResponseMessage(ex.toString()); + isSuccessful = false; + } finally { + ldap.disconnect(); + } + + // Set if we were successful or not + res.setSuccessful(isSuccessful); + return res; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LdapClient.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LdapClient.java new file mode 100644 index 0000000..4a3b882 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LdapClient.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ldap.sampler; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +// import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +// import javax.naming.directory.SearchResult; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Ldap Client class is main class to create, modify, search and delete all the + * LDAP functionality available. + * + */ +public class LdapClient { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private DirContext dirContext = null; + + /** + * Constructor for the LdapClient object. + */ + public LdapClient() { + } + + /** + * Connect to server. + */ + public void connect(String host, String port, String rootdn, String username, String password) + throws NamingException { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); //$NON-NLS-1$ + env.put(Context.PROVIDER_URL, "ldap://" + host + ":" + port + "/" + rootdn); //$NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ + env.put(Context.REFERRAL, "throw"); //$NON-NLS-1$ + env.put(Context.SECURITY_CREDENTIALS, password); + env.put(Context.SECURITY_PRINCIPAL, username); + dirContext = new InitialDirContext(env); + } + + /** + * Disconnect from the server. + */ + public void disconnect() { + try { + if (dirContext != null) { + dirContext.close(); + dirContext = null; + } + } catch (NamingException e) { + log.error("Ldap client - ", e); + } + } + + /** + * Filter the data in the ldap directory for the given search base. + * + * @param searchBase + * where the search should start + * @param searchFilter + * filter this value from the base + */ + public boolean searchTest(String searchBase, String searchFilter) throws NamingException { + // System.out.println("Base="+searchBase+" Filter="+searchFilter); + SearchControls searchcontrols = new SearchControls(SearchControls.SUBTREE_SCOPE, + 1L, // count limit + 0, // time limit + null,// attributes (null = all) + false,// return object ? + false);// dereference links? + NamingEnumeration ne = dirContext.search(searchBase, searchFilter, searchcontrols); + // System.out.println("Loop "+ne.toString()+" "+ne.hasMore()); + // while (ne.hasMore()){ + // Object tmp = ne.next(); + // System.out.println(tmp.getClass().getName()); + // SearchResult sr = (SearchResult) tmp; + // Attributes at = sr.getAttributes(); + // System.out.println(at.get("cn")); + // } + // System.out.println("Done "+ne.hasMore()); + return ne.hasMore(); + } + + /** + * Modify the attribute in the ldap directory for the given string. + * + * @param mods + * add all the entry in to the ModificationItem + * @param string + * the string (dn) value + */ + public void modifyTest(ModificationItem[] mods, String string) throws NamingException { + dirContext.modifyAttributes(string, mods); + } + + /** + * Create the attribute in the ldap directory for the given string. + * + * @param basicattributes + * add all the entry in to the basicattribute + * @param string + * the string (dn) value + */ + public void createTest(BasicAttributes basicattributes, String string) throws NamingException { + // DirContext dc = //TODO perhaps return this? + dirContext.createSubcontext(string, basicattributes); + } + + /** + * Delete the attribute from the ldap directory. + * + * @param string + * the string (dn) value + */ + public void deleteTest(String string) throws NamingException { + dirContext.destroySubcontext(string); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LdapExtClient.java b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LdapExtClient.java new file mode 100644 index 0000000..51edee6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/ldap/sampler/LdapExtClient.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.ldap.sampler; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.NamingEnumeration; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/******************************************************************************* + * + * author Dolf Smits(Dolf.Smits@Siemens.com) created Aug 09 2003 11:00 AM + * company Siemens Netherlands N.V.. + * + * Based on the work of: author T.Elanjchezhiyan(chezhiyan@siptech.co.in) + * created Apr 29 2003 11:00 AM company Sip Technologies and Exports Ltd. + * + ******************************************************************************/ + +/******************************************************************************* + * Ldap Client class is main class to create ,modify, search and delete all the + * LDAP functionality available + ******************************************************************************/ +public class LdapExtClient { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String CONTEXT_IS_NULL = "Context is null"; + + /** + * Constructor for the LdapClient object + */ + public LdapExtClient() { + } + + /** + * connect to server + * + * @param host + * Description of Parameter + * @param username + * Description of Parameter + * @param password + * Description of Parameter + * @exception NamingException + * Description of Exception + */ + public static DirContext connect(String host, String port, String rootdn, String username, String password, String connTimeOut, boolean secure) + throws NamingException { + DirContext dirContext; + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); // $NON-NLS-1$ + StringBuilder sb = new StringBuilder(80); + if (secure) { + sb.append("ldaps://"); // $NON-NLS-1$ + } else { + sb.append("ldap://"); // $NON-NLS-1$ + } + sb.append(host); + if (port.length()>0){ + sb.append(":"); // $NON-NLS-1$ + sb.append(port); + } + sb.append("/"); // $NON-NLS-1$ + sb.append(rootdn); + env.put(Context.PROVIDER_URL,sb.toString()); + log.info("prov_url= " + env.get(Context.PROVIDER_URL)); // $NON-NLS-1$ + if (connTimeOut.length()> 0) { + env.put("com.sun.jndi.ldap.connect.timeout", connTimeOut); // $NON-NLS-1$ + } + env.put(Context.REFERRAL, "throw"); // $NON-NLS-1$ + env.put("java.naming.batchsize", "0"); // $NON-NLS-1$ // $NON-NLS-2$ + env.put(Context.SECURITY_CREDENTIALS, password); + env.put(Context.SECURITY_PRINCIPAL, username); + dirContext = new InitialDirContext(env); + return dirContext; + } + + /** + * disconnect from the server + */ + public static void disconnect(DirContext dirContext) { + if (dirContext == null) { + log.info("Cannot disconnect null context"); + return; + } + + try { + dirContext.close(); + } catch (NamingException e) { + log.warn("Ldap client disconnect - ", e); + } + } + + /*************************************************************************** + * Filter the data in the ldap directory for the given search base + * + * @param searchBase + * base where the search should start + * @param searchFilter + * filter filter this value from the base + **************************************************************************/ + public static NamingEnumeration searchTest(DirContext dirContext, String searchBase, String searchFilter, int scope, long countlim, + int timelim, String[] attrs, boolean retobj, boolean deref) throws NamingException { + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + if (log.isDebugEnabled()){ + log.debug( + "searchBase=" + searchBase + + " scope=" + scope + + " countlim=" + countlim + + " timelim=" + timelim + + " attrs=" + JMeterUtils.unsplit(attrs,",") + + " retobj=" + retobj + + " deref=" + deref + + " filter=" + searchFilter + ); + } + SearchControls searchcontrols = null; + searchcontrols = new SearchControls(scope, countlim, timelim, attrs, retobj, deref); + return dirContext.search(searchBase, searchFilter, searchcontrols); + } + + /*************************************************************************** + * Filter the data in the ldap directory + * + * @param filter + * filter this value from the base + **************************************************************************/ + public static NamingEnumeration compare(DirContext dirContext, String filter, String entrydn) throws NamingException { + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + SearchControls searchcontrols = new SearchControls(0, 1, 0, new String[0], false, false); + return dirContext.search(entrydn, filter, searchcontrols); + } + + /*************************************************************************** + * ModDN the data in the ldap directory for the given search base + * + **************************************************************************/ + public static void moddnOp(DirContext dirContext, String ddn, String newdn) throws NamingException { + log.debug("ddn and newDn= " + ddn + "@@@@" + newdn); + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + dirContext.rename(ddn, newdn); + } + + /*************************************************************************** + * Modify the attribute in the ldap directory for the given string + * + * @param mods + * add all the entry in to the ModificationItem + * @param string + * The string (dn) value + **************************************************************************/ + public static void modifyTest(DirContext dirContext, ModificationItem[] mods, String string) throws NamingException { + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + dirContext.modifyAttributes(string, mods); + + } + + /*************************************************************************** + * Create the entry in the ldap directory for the given string + * + * @param attributes + * add all the attributes and values from the attributes object + * @param string + * The string (dn) value + **************************************************************************/ + public static DirContext createTest(DirContext dirContext, Attributes attributes, String string) + throws NamingException { + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + return dirContext.createSubcontext(string, attributes); + } + + /*************************************************************************** + * Delete the attribute from the ldap directory + * + * @param string + * The string (dn) value + **************************************************************************/ + public static void deleteTest(DirContext dirContext, String string) throws NamingException { + if (dirContext == null) { + throw new NamingException(CONTEXT_IS_NULL); + } + dirContext.destroySubcontext(string); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileFolder.java b/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileFolder.java new file mode 100644 index 0000000..593ec12 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileFolder.java @@ -0,0 +1,189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.mail.sampler; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; + +import javax.mail.Flags; +import javax.mail.Folder; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Store; +import javax.mail.URLName; + +import org.apache.commons.io.IOUtils; + +public class MailFileFolder extends Folder { + + private static final String FILENAME_FORMAT = "%d.msg"; + private static final String FILENAME_REGEX = "\\d+\\.msg"; + private boolean isOpen; + private final File folderPath;// Parent folder (or single message file) + private final boolean isFile; + private static final FilenameFilter FILENAME_FILTER = new FilenameFilter(){ + public boolean accept(File dir, String name) { + return name.matches(FILENAME_REGEX); + } + + }; + + public MailFileFolder(Store store, String path) { + super(store); + String base = store.getURLName().getHost(); // == ServerName from mail sampler + File parentFolder = new File(base); + isFile = parentFolder.isFile(); + if (isFile){ + folderPath = new File(base); + } else { + folderPath = new File(base,path); + } + } + + public MailFileFolder(Store store, URLName path) { + this(store, path.getFile()); + } + + @Override + public void appendMessages(Message[] messages) throws MessagingException { + throw new MessagingException("Not supported"); + } + + @Override + public void close(boolean expunge) throws MessagingException { + this.store.close(); + isOpen = false; + } + + @Override + public boolean create(int type) throws MessagingException { + return false; + } + + @Override + public boolean delete(boolean recurse) throws MessagingException { + return false; + } + + @Override + public boolean exists() throws MessagingException { + return true; + } + + @Override + public Message[] expunge() throws MessagingException { + return null; + } + + @Override + public Folder getFolder(String name) throws MessagingException { + return this; + } + + @Override + public String getFullName() { + return this.toString(); + } + + @Override + public Message getMessage(int index) throws MessagingException { + File f; + if (isFile) { + f = folderPath; + } else { + f = new File(folderPath,String.format(FILENAME_FORMAT, Integer.valueOf(index))); + } + try { + FileInputStream fis = new FileInputStream(f); + try { + Message m = new MailFileMessage(this, fis, index); + return m; + } finally { + IOUtils.closeQuietly(fis); + } + } catch (FileNotFoundException e) { + throw new MessagingException("Cannot open folder: "+e.getMessage()); + } + } + + @Override + public int getMessageCount() throws MessagingException { + if (!isOpen) return -1; + if (isFile) return 1; + File[] listFiles = folderPath.listFiles(FILENAME_FILTER); + return listFiles != null ? listFiles.length : 0; + } + + @Override + public String getName() { + return this.toString(); + } + + @Override + public Folder getParent() throws MessagingException { + return null; + } + + @Override + public Flags getPermanentFlags() { + return null; + } + + @Override + public char getSeparator() throws MessagingException { + return '/'; + } + + @Override + public int getType() throws MessagingException { + return HOLDS_MESSAGES; + } + + @Override + public boolean hasNewMessages() throws MessagingException { + return false; + } + + @Override + public boolean isOpen() { + return isOpen; + } + + @Override + public Folder[] list(String pattern) throws MessagingException { + return new Folder[]{this}; + } + + @Override + public void open(int mode) throws MessagingException { + if (mode != READ_ONLY) { + throw new MessagingException("Implementation only supports read-only access"); + } + this.mode = mode; + isOpen = true; + } + + @Override + public boolean renameTo(Folder newName) throws MessagingException { + return false; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileMessage.java b/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileMessage.java new file mode 100644 index 0000000..5748228 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileMessage.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.mail.sampler; + +import java.io.InputStream; + +import javax.mail.Folder; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +public class MailFileMessage extends MimeMessage { + + protected MailFileMessage(Folder folder, InputStream in, int number) + throws MessagingException { + super(folder, in, number); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileStore.java b/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileStore.java new file mode 100644 index 0000000..6589a0f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailFileStore.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.mail.sampler; + +import java.io.File; + +import javax.mail.Folder; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Store; +import javax.mail.URLName; + +public class MailFileStore extends Store { + + public MailFileStore(Session s, URLName u){ + super(s,u); + } + + @Override + protected boolean protocolConnect(String host, int port, String user, String password) + throws MessagingException { + File base = new File(host); + if (base.isDirectory() || base.isFile()) { + return true; + } + throw new MessagingException("Host must be a valid directory or file"); + } + + @Override + public Folder getDefaultFolder() throws MessagingException { + return new MailFileFolder(this,""); + } + + @Override + public Folder getFolder(String path) throws MessagingException { + return new MailFileFolder(this, path); + } + + @Override + public Folder getFolder(URLName path) throws MessagingException { + return new MailFileFolder(this, path); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailReaderSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailReaderSampler.java new file mode 100644 index 0000000..18eb457 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/MailReaderSampler.java @@ -0,0 +1,581 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.protocol.mail.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +import javax.mail.Address; +import javax.mail.BodyPart; +import javax.mail.Flags; +import javax.mail.Folder; +import javax.mail.Header; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Store; +import javax.mail.internet.MimeMultipart; +import javax.mail.internet.MimeUtility; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.protocol.smtp.sampler.gui.SecuritySettingsPanel; +import org.apache.jmeter.protocol.smtp.sampler.protocol.LocalTrustStoreSSLSocketFactory; +import org.apache.jmeter.protocol.smtp.sampler.protocol.TrustAllSSLSocketFactory; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Sampler that can read from POP3 and IMAP mail servers + */ +public class MailReaderSampler extends AbstractSampler implements Interruptible { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + //+ JMX attributes - do not change the values + private final static String SERVER_TYPE = "host_type"; // $NON-NLS-1$ + private final static String SERVER = "host"; // $NON-NLS-1$ + private final static String PORT = "port"; // $NON-NLS-1$ + private final static String USERNAME = "username"; // $NON-NLS-1$ + private final static String PASSWORD = "password"; // $NON-NLS-1$ + private final static String FOLDER = "folder"; // $NON-NLS-1$ + private final static String DELETE = "delete"; // $NON-NLS-1$ + private final static String NUM_MESSAGES = "num_messages"; // $NON-NLS-1$ + private static final String NEW_LINE = "\n"; // $NON-NLS-1$ + private final static String STORE_MIME_MESSAGE = "storeMimeMessage"; + //- + + private static final String RFC_822_DEFAULT_ENCODING = "iso-8859-1"; // RFC 822 uses ascii per default + + public static final String DEFAULT_PROTOCOL = "pop3"; // $NON-NLS-1$ + + // Use the actual class so the name must be correct. + private static final String TRUST_ALL_SOCKET_FACTORY = TrustAllSSLSocketFactory.class.getName(); + + public boolean isUseLocalTrustStore() { + return getPropertyAsBoolean(SecuritySettingsPanel.USE_LOCAL_TRUSTSTORE); + } + + public String getTrustStoreToUse() { + return getPropertyAsString(SecuritySettingsPanel.TRUSTSTORE_TO_USE); + } + + + public boolean isUseSSL() { + return getPropertyAsBoolean(SecuritySettingsPanel.USE_SSL); + } + + + public boolean isUseStartTLS() { + return getPropertyAsBoolean(SecuritySettingsPanel.USE_STARTTLS); + } + + + public boolean isTrustAllCerts() { + return getPropertyAsBoolean(SecuritySettingsPanel.SSL_TRUST_ALL_CERTS); + } + + + public boolean isEnforceStartTLS() { + return getPropertyAsBoolean(SecuritySettingsPanel.ENFORCE_STARTTLS); + + } + + public static final int ALL_MESSAGES = -1; // special value + + private volatile boolean busy; + + public MailReaderSampler() { + setServerType(DEFAULT_PROTOCOL); + setFolder("INBOX"); // $NON-NLS-1$ + setNumMessages(ALL_MESSAGES); + setDeleteMessages(false); + } + + /** + * {@inheritDoc} + */ + public SampleResult sample(Entry e) { + SampleResult parent = new SampleResult(); + boolean isOK = false; // Did sample succeed? + boolean deleteMessages = getDeleteMessages(); + + parent.setSampleLabel(getName()); + + String samplerString = toString(); + parent.setSamplerData(samplerString); + + /* + * Perform the sampling + */ + parent.sampleStart(); // Start timing + try { + // Create empty properties + Properties props = new Properties(); + + if (isUseStartTLS()) { + props.setProperty("mail.pop3s.starttls.enable", "true"); + if (isEnforceStartTLS()){ + // Requires JavaMail 1.4.2+ + props.setProperty("mail.pop3s.starttls.require", "true"); + } + } + + if (isTrustAllCerts()) { + if (isUseSSL()) { + props.setProperty("mail.pop3s.ssl.socketFactory.class", TRUST_ALL_SOCKET_FACTORY); + props.setProperty("mail.pop3s.ssl.socketFactory.fallback", "false"); + } else if (isUseStartTLS()) { + props.setProperty("mail.pop3s.ssl.socketFactory.class", TRUST_ALL_SOCKET_FACTORY); + props.setProperty("mail.pop3s.ssl.socketFactory.fallback", "false"); + } + } else if (isUseLocalTrustStore()){ + File truststore = new File(getTrustStoreToUse()); + log.info("load local truststore - try to load truststore from: "+truststore.getAbsolutePath()); + if(!truststore.exists()){ + log.info("load local truststore -Failed to load truststore from: "+truststore.getAbsolutePath()); + truststore = new File(FileServer.getFileServer().getBaseDir(), getTrustStoreToUse()); + log.info("load local truststore -Attempting to read truststore from: "+truststore.getAbsolutePath()); + if(!truststore.exists()){ + log.info("load local truststore -Failed to load truststore from: "+truststore.getAbsolutePath() + ". Local truststore not available, aborting execution."); + throw new IOException("Local truststore file not found. Also not available under : " + truststore.getAbsolutePath()); + } + } + if (isUseSSL()) { + // Requires JavaMail 1.4.2+ + props.put("mail.pop3s.ssl.socketFactory", new LocalTrustStoreSSLSocketFactory(truststore)); + props.put("mail.pop3s.ssl.socketFactory.fallback", "false"); + } else if (isUseStartTLS()) { + // Requires JavaMail 1.4.2+ + props.put("mail.pop3s.ssl.socketFactory", new LocalTrustStoreSSLSocketFactory(truststore)); + props.put("mail.pop3s.ssl.socketFactory.fallback", "false"); + } + } + + // Get session + Session session = Session.getInstance(props, null); + + // Get the store + Store store = session.getStore(getServerType()); + store.connect(getServer(), getPortAsInt(), getUserName(), getPassword()); + + // Get folder + Folder folder = store.getFolder(getFolder()); + if (deleteMessages) { + folder.open(Folder.READ_WRITE); + } else { + folder.open(Folder.READ_ONLY); + } + + // Get directory + Message messages[] = folder.getMessages(); + StringBuilder pdata = new StringBuilder(); + pdata.append(messages.length); + pdata.append(" messages found\n"); + parent.setResponseData(pdata.toString(),null); + parent.setDataType(SampleResult.TEXT); + parent.setContentType("text/plain"); // $NON-NLS-1$ + + int n = getNumMessages(); + if (n == ALL_MESSAGES || n > messages.length) { + n = messages.length; + } + + parent.setSampleCount(n); // TODO is this sensible? + + busy = true; + for (int i = 0; busy && i < n; i++) { + StringBuilder cdata = new StringBuilder(); + SampleResult child = new SampleResult(); + child.sampleStart(); + Message message = messages[i]; + + cdata.append("Message "); // $NON-NLS-1$ + cdata.append(message.getMessageNumber()); + child.setSampleLabel(cdata.toString()); + child.setSamplerData(cdata.toString()); + cdata.setLength(0); + + final String contentType = message.getContentType(); + child.setContentType(contentType);// Store the content-type + child.setDataEncoding(RFC_822_DEFAULT_ENCODING); // RFC 822 uses ascii per default + child.setEncodingAndType(contentType);// Parse the content-type + + if (isStoreMimeMessage()) { + // Don't save headers - they are already in the raw message + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + message.writeTo(bout); + child.setResponseData(bout.toByteArray()); // Save raw message + child.setDataType(SampleResult.TEXT); + } else { + @SuppressWarnings("unchecked") // Javadoc for the API says this is OK + Enumeration
hdrs = message.getAllHeaders(); + while(hdrs.hasMoreElements()){ + Header hdr = hdrs.nextElement(); + String value = hdr.getValue(); + try { + value = MimeUtility.decodeText(value); + } catch (UnsupportedEncodingException uce) { + // ignored + } + cdata.append(hdr.getName()).append(": ").append(value).append("\n"); + } + child.setResponseHeaders(cdata.toString()); + cdata.setLength(0); + appendMessageData(child, message); + } + + if (deleteMessages) { + message.setFlag(Flags.Flag.DELETED, true); + } + child.setResponseOK(); + if (child.getEndTime()==0){// Avoid double-call if addSubResult was called. + child.sampleEnd(); + } + parent.addSubResult(child); + } + + // Close connection + folder.close(true); + store.close(); + + parent.setResponseCodeOK(); + parent.setResponseMessageOK(); + isOK = true; + } catch (NoClassDefFoundError ex) { + log.debug("",ex);// No need to log normally, as we set the status + parent.setResponseCode("500"); // $NON-NLS-1$ + parent.setResponseMessage(ex.toString()); + } catch (MessagingException ex) { + log.debug("", ex);// No need to log normally, as we set the status + parent.setResponseCode("500"); // $NON-NLS-1$ + parent.setResponseMessage(ex.toString() + "\n" + samplerString); // $NON-NLS-1$ + } catch (IOException ex) { + log.debug("", ex);// No need to log normally, as we set the status + parent.setResponseCode("500"); // $NON-NLS-1$ + parent.setResponseMessage(ex.toString()); + } finally { + busy = false; + } + + if (parent.getEndTime()==0){// not been set by any child samples + parent.sampleEnd(); + } + parent.setSuccessful(isOK); + return parent; + } + + private void appendMessageData(SampleResult child, Message message) + throws MessagingException, IOException { + StringBuilder cdata = new StringBuilder(); + cdata.append("Date: "); // $NON-NLS-1$ + cdata.append(message.getSentDate());// TODO - use a different format here? + cdata.append(NEW_LINE); + + cdata.append("To: "); // $NON-NLS-1$ + Address[] recips = message.getAllRecipients(); // may be null + for (int j = 0; recips != null && j < recips.length; j++) { + cdata.append(recips[j].toString()); + if (j < recips.length - 1) { + cdata.append("; "); // $NON-NLS-1$ + } + } + cdata.append(NEW_LINE); + + cdata.append("From: "); // $NON-NLS-1$ + Address[] from = message.getFrom(); // may be null + for (int j = 0; from != null && j < from.length; j++) { + cdata.append(from[j].toString()); + if (j < from.length - 1) { + cdata.append("; "); // $NON-NLS-1$ + } + } + cdata.append(NEW_LINE); + + cdata.append("Subject: "); // $NON-NLS-1$ + cdata.append(message.getSubject()); + cdata.append(NEW_LINE); + + cdata.append(NEW_LINE); + Object content = message.getContent(); + if (content instanceof MimeMultipart) { + appendMultiPart(child, cdata, (MimeMultipart) content); + } else if (content instanceof InputStream){ + child.setResponseData(IOUtils.toByteArray((InputStream) content)); + } else { + cdata.append(content); + child.setResponseData(cdata.toString(),child.getDataEncodingNoDefault()); + } + } + + private void appendMultiPart(SampleResult child, StringBuilder cdata, + MimeMultipart mmp) throws MessagingException, IOException { + String preamble = mmp.getPreamble(); + if (preamble != null ){ + cdata.append(preamble); + } + child.setResponseData(cdata.toString(),child.getDataEncodingNoDefault()); + int count = mmp.getCount(); + for (int j=0; j 0){ + sb.append(name); + sb.append("@"); + } + sb.append(getServer()); + int port=getPortAsInt(); + if (port != -1){ + sb.append(":").append(port); + } + sb.append("/"); + sb.append(getFolder()); + sb.append("["); + sb.append(getNumMessages()); + sb.append("]"); + return sb.toString(); + } + + /** + * {@inheritDoc} + */ + public boolean interrupt() { + boolean wasbusy = busy; + busy = false; + return wasbusy; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/gui/MailReaderSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/gui/MailReaderSamplerGui.java new file mode 100644 index 0000000..e65143e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/mail/sampler/gui/MailReaderSamplerGui.java @@ -0,0 +1,317 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.protocol.mail.sampler.gui; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.protocol.mail.sampler.MailReaderSampler; +import org.apache.jmeter.protocol.smtp.sampler.gui.SecuritySettingsPanel; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class MailReaderSamplerGui extends AbstractSamplerGui implements ActionListener, FocusListener { + + private static final long serialVersionUID = 240L; + + // Gui Components + private JTextField serverTypeBox; + + private JTextField serverBox; + + private JTextField portBox; + + private JTextField usernameBox; + + private JTextField passwordBox; + + private JTextField folderBox; + + private JLabel folderLabel; + + private JRadioButton allMessagesButton; + + private JRadioButton someMessagesButton; + + private JTextField someMessagesField; + + private JCheckBox deleteBox; + + private JCheckBox storeMimeMessageBox; + + // Labels - don't make these static, else language change will not work + + private final String ServerTypeLabel = JMeterUtils.getResString("mail_reader_server_type");// $NON-NLS-1$ + + private final String ServerLabel = JMeterUtils.getResString("mail_reader_server");// $NON-NLS-1$ + + private final String PortLabel = JMeterUtils.getResString("mail_reader_port");// $NON-NLS-1$ + + private final String AccountLabel = JMeterUtils.getResString("mail_reader_account");// $NON-NLS-1$ + + private final String PasswordLabel = JMeterUtils.getResString("mail_reader_password");// $NON-NLS-1$ + + private final String NumMessagesLabel = JMeterUtils.getResString("mail_reader_num_messages");// $NON-NLS-1$ + + private final String AllMessagesLabel = JMeterUtils.getResString("mail_reader_all_messages");// $NON-NLS-1$ + + private final String DeleteLabel = JMeterUtils.getResString("mail_reader_delete");// $NON-NLS-1$ + + private final String FolderLabel = JMeterUtils.getResString("mail_reader_folder");// $NON-NLS-1$ + + private final String STOREMIME = JMeterUtils.getResString("mail_reader_storemime");// $NON-NLS-1$ + + private static final String INBOX = "INBOX"; // $NON-NLS-1$ + + private SecuritySettingsPanel securitySettingsPanel; + + public MailReaderSamplerGui() { + init(); + initGui(); + } + + public String getLabelResource() { + return "mail_reader_title"; // $NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement element) { + MailReaderSampler mrs = (MailReaderSampler) element; + serverTypeBox.setText(mrs.getServerType()); + folderBox.setText(mrs.getFolder()); + serverBox.setText(mrs.getServer()); + portBox.setText(mrs.getPort()); + usernameBox.setText(mrs.getUserName()); + passwordBox.setText(mrs.getPassword()); + if (mrs.getNumMessages() == MailReaderSampler.ALL_MESSAGES) { + allMessagesButton.setSelected(true); + someMessagesField.setText("0"); // $NON-NLS-1$ + } else { + someMessagesButton.setSelected(true); + someMessagesField.setText(mrs.getNumMessagesString()); + } + deleteBox.setSelected(mrs.getDeleteMessages()); + storeMimeMessageBox.setSelected(mrs.isStoreMimeMessage()); + securitySettingsPanel.configure(element); + super.configure(element); + } + + /** + * {@inheritDoc} + */ + public TestElement createTestElement() { + MailReaderSampler sampler = new MailReaderSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * {@inheritDoc} + */ + public void modifyTestElement(TestElement te) { + te.clear(); + configureTestElement(te); + + MailReaderSampler mrs = (MailReaderSampler) te; + + mrs.setServerType(serverTypeBox.getText()); + mrs.setFolder(folderBox.getText()); + mrs.setServer(serverBox.getText()); + mrs.setPort(portBox.getText()); + mrs.setUserName(usernameBox.getText()); + mrs.setPassword(passwordBox.getText()); + if (allMessagesButton.isSelected()) { + mrs.setNumMessages(MailReaderSampler.ALL_MESSAGES); + } else { + mrs.setNumMessages(someMessagesField.getText()); + } + mrs.setDeleteMessages(deleteBox.isSelected()); + mrs.setStoreMimeMessage(storeMimeMessageBox.isSelected()); + + securitySettingsPanel.modifyTestElement(te); + } + + /* + * Helper method to set up the GUI screen + */ + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + JPanel settingsPanel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = getConstraints(); + + serverTypeBox = new JTextField(20); + serverTypeBox.addActionListener(this); + serverTypeBox.addFocusListener(this); + addField(settingsPanel, ServerTypeLabel, serverTypeBox, gbc); + + serverBox = new JTextField(20); + addField(settingsPanel, ServerLabel, serverBox, gbc); + + portBox = new JTextField(20); + addField(settingsPanel, PortLabel, portBox, gbc); + + usernameBox = new JTextField(20); + addField(settingsPanel, AccountLabel, usernameBox, gbc); + + passwordBox = new JPasswordField(20); + addField(settingsPanel, PasswordLabel, passwordBox, gbc); + + folderLabel = new JLabel(FolderLabel); + folderBox = new JTextField(INBOX, 20); + addField(settingsPanel, folderLabel, folderBox, gbc); + + HorizontalPanel numMessagesPanel = new HorizontalPanel(); + numMessagesPanel.add(new JLabel(NumMessagesLabel)); + ButtonGroup nmbg = new ButtonGroup(); + allMessagesButton = new JRadioButton(AllMessagesLabel); + allMessagesButton.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + if (allMessagesButton.isSelected()) { + someMessagesField.setEnabled(false); + } + } + }); + someMessagesButton = new JRadioButton(); + someMessagesButton.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + if (someMessagesButton.isSelected()) { + someMessagesField.setEnabled(true); + } + } + }); + nmbg.add(allMessagesButton); + nmbg.add(someMessagesButton); + someMessagesField = new JTextField(5); + allMessagesButton.setSelected(true); + numMessagesPanel.add(allMessagesButton); + numMessagesPanel.add(someMessagesButton); + numMessagesPanel.add(someMessagesField); + + deleteBox = new JCheckBox(DeleteLabel); + + storeMimeMessageBox = new JCheckBox(STOREMIME); + + securitySettingsPanel = new SecuritySettingsPanel(); + + JPanel settings = new VerticalPanel(); + settings.add(Box.createVerticalStrut(5)); + settings.add(settingsPanel); + settings.add(numMessagesPanel); + settings.add(deleteBox); + settings.add(storeMimeMessageBox); + settings.add(securitySettingsPanel); + + add(makeTitlePanel(), BorderLayout.NORTH); + add(settings, BorderLayout.CENTER); + } + + private void addField(JPanel panel, JLabel label, JComponent field, GridBagConstraints gbc) { + gbc.fill=GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + panel.add(label, gbc); + gbc.gridx++; + gbc.weightx = 1; + gbc.fill=GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + panel.add(field, gbc); + nextLine(gbc); + } + + private void addField(JPanel panel, String text, JComponent field, GridBagConstraints gbc) { + addField(panel, new JLabel(text), field, gbc); + } + + private void nextLine(GridBagConstraints gbc) { + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0; + } + + private GridBagConstraints getConstraints() { + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridheight = 1; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0; + gbc.weighty = 0; + return gbc; + } + + @Override + public void clearGui() { + super.clearGui(); + initGui(); + } + + private void initGui() { + allMessagesButton.setSelected(true); + deleteBox.setSelected(false); + storeMimeMessageBox.setSelected(false); + folderBox.setText(INBOX); + serverTypeBox.setText(MailReaderSampler.DEFAULT_PROTOCOL); + passwordBox.setText("");// $NON-NLS-1$ + serverBox.setText("");// $NON-NLS-1$ + portBox.setText("");// $NON-NLS-1$ + usernameBox.setText("");// $NON-NLS-1$ + } + + public void actionPerformed(ActionEvent e) { + final String item = serverTypeBox.getText(); + if (item.equals("pop3")||item.equals("pop3s")) { + folderLabel.setEnabled(false); + folderBox.setText(INBOX); + folderBox.setEnabled(false); + } else { + folderLabel.setEnabled(true); + folderBox.setEnabled(true); + } + } + + public void focusGained(FocusEvent e) { + } + + public void focusLost(FocusEvent e) { + actionPerformed(null); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/SmtpSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/SmtpSampler.java new file mode 100644 index 0000000..26e98b6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/SmtpSampler.java @@ -0,0 +1,393 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.smtp.sampler; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.mail.AuthenticationFailedException; +import javax.mail.BodyPart; +import javax.mail.Header; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.internet.AddressException; +import javax.mail.internet.ContentType; +import javax.mail.internet.InternetAddress; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.protocol.smtp.sampler.gui.SecuritySettingsPanel; +import org.apache.jmeter.protocol.smtp.sampler.protocol.SendMailCommand; +import org.apache.jmeter.protocol.smtp.sampler.tools.CounterOutputStream; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Sampler-Class for JMeter - builds, starts and interprets the results of the + * sampler. Has to implement some standard-methods for JMeter in order to be + * integrated in the framework. All getter/setter methods just deliver/set + * values from/to the sampler, not from/to the message-object. Therefore, all + * these methods are also present in class SendMailCommand. + */ +public class SmtpSampler extends AbstractSampler { + + private static final long serialVersionUID = 1L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private static final Logger log = LoggingManager.getLoggerForClass(); + + //+JMX file attribute names - do not change any values! + public final static String SERVER = "SMTPSampler.server"; // $NON-NLS-1$ + + + public final static String SERVER_PORT = "SMTPSampler.serverPort"; // $NON-NLS-1$ + public final static String USE_AUTH = "SMTPSampler.useAuth"; // $NON-NLS-1$ + public final static String USERNAME = "SMTPSampler.username"; // $NON-NLS-1$ + public final static String PASSWORD = "SMTPSampler.password"; // $NON-NLS-1$ + public final static String MAIL_FROM = "SMTPSampler.mailFrom"; // $NON-NLS-1$ + public static final String MAIL_REPLYTO = "SMTPSampler.replyTo"; // $NON-NLS-1$ + public final static String RECEIVER_TO = "SMTPSampler.receiverTo"; // $NON-NLS-1$ + public final static String RECEIVER_CC = "SMTPSampler.receiverCC"; // $NON-NLS-1$ + public final static String RECEIVER_BCC = "SMTPSampler.receiverBCC"; // $NON-NLS-1$ + + public final static String SUBJECT = "SMTPSampler.subject"; // $NON-NLS-1$ + public final static String SUPPRESS_SUBJECT = "SMTPSampler.suppressSubject"; // $NON-NLS-1$ + public final static String MESSAGE = "SMTPSampler.message"; // $NON-NLS-1$ + public final static String PLAIN_BODY = "SMTPSampler.plainBody"; // $NON-NLS-1$ + public final static String INCLUDE_TIMESTAMP = "SMTPSampler.include_timestamp"; // $NON-NLS-1$ + public final static String ATTACH_FILE = "SMTPSampler.attachFile"; // $NON-NLS-1$ + public final static String MESSAGE_SIZE_STATS = "SMTPSampler.messageSizeStatistics"; // $NON-NLS-1$ + public static final String HEADER_FIELDS = "SMTPSampler.headerFields"; // $NON-NLS-1$ + + public final static String USE_EML = "SMTPSampler.use_eml"; // $NON-NLS-1$ + public final static String EML_MESSAGE_TO_SEND = "SMTPSampler.emlMessageToSend"; // $NON-NLS-1$ + public static final String ENABLE_DEBUG = "SMTPSampler.enableDebug"; // $NON-NLS-1$ + + // Used to separate attachment file names in JMX fields - do not change! + public static final String FILENAME_SEPARATOR = ";"; + //-JMX file attribute names + + + public SmtpSampler() { + } + + /** + * Performs the sample, and returns the result + * + * @param e + * Standard-method-header from JMeter + * @return sampleresult Result of the sample + * @see org.apache.jmeter.samplers.Sampler#sample(org.apache.jmeter.samplers.Entry) + */ + public SampleResult sample(Entry e) { + Message message = null; + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + boolean isOK = false; // Did sample succeed? + SendMailCommand instance = new SendMailCommand(); + instance.setSmtpServer(getPropertyAsString(SmtpSampler.SERVER)); + instance.setSmtpPort(getPropertyAsString(SmtpSampler.SERVER_PORT)); + + instance.setUseSSL(getPropertyAsBoolean(SecuritySettingsPanel.USE_SSL)); + instance.setUseStartTLS(getPropertyAsBoolean(SecuritySettingsPanel.USE_STARTTLS)); + instance.setTrustAllCerts(getPropertyAsBoolean(SecuritySettingsPanel.SSL_TRUST_ALL_CERTS)); + instance.setEnforceStartTLS(getPropertyAsBoolean(SecuritySettingsPanel.ENFORCE_STARTTLS)); + + instance.setUseAuthentication(getPropertyAsBoolean(USE_AUTH)); + instance.setUsername(getPropertyAsString(USERNAME)); + instance.setPassword(getPropertyAsString(PASSWORD)); + + instance.setUseLocalTrustStore(getPropertyAsBoolean(SecuritySettingsPanel.USE_LOCAL_TRUSTSTORE)); + instance.setTrustStoreToUse(getPropertyAsString(SecuritySettingsPanel.TRUSTSTORE_TO_USE)); + instance.setEmlMessage(getPropertyAsString(EML_MESSAGE_TO_SEND)); + instance.setUseEmlMessage(getPropertyAsBoolean(USE_EML)); + + instance.setEnableDebug(getPropertyAsBoolean(ENABLE_DEBUG)); + + if (getPropertyAsString(MAIL_FROM).matches(".*@.*")) { + instance.setSender(getPropertyAsString(MAIL_FROM)); + } + + final String receiverTo = getPropertyAsString(SmtpSampler.RECEIVER_TO).trim(); + final String receiverCC = getPropertyAsString(SmtpSampler.RECEIVER_CC).trim(); + final String receiverBcc = getPropertyAsString(SmtpSampler.RECEIVER_BCC).trim(); + final String replyTo = getPropertyAsString(SmtpSampler.MAIL_REPLYTO).trim(); + + try { + // Process address lists + instance.setReceiverTo(getPropNameAsAddresses(receiverTo)); + instance.setReceiverCC(getPropNameAsAddresses(receiverCC)); + instance.setReceiverBCC(getPropNameAsAddresses(receiverBcc)); + instance.setReplyTo(getPropNameAsAddresses(replyTo)); + + if(getPropertyAsBoolean(SUPPRESS_SUBJECT)){ + instance.setSubject(null); + }else{ + String subject = getPropertyAsString(SUBJECT); + if (getPropertyAsBoolean(INCLUDE_TIMESTAMP)){ + StringBuilder sb = new StringBuilder(subject); + sb.append(" <<< current timestamp: "); + sb.append(new Date().getTime()); + sb.append(" >>>"); + subject = sb.toString(); + } + instance.setSubject(subject); + } + + if (!getPropertyAsBoolean(USE_EML)) { // part is only needed if we + // don't send an .eml-file + instance.setMailBody(getPropertyAsString(MESSAGE)); + instance.setPlainBody(getPropertyAsBoolean(PLAIN_BODY)); + final String filesToAttach = getPropertyAsString(ATTACH_FILE); + if (!filesToAttach.equals("")) { + String[] attachments = filesToAttach.split(FILENAME_SEPARATOR); + for (String attachment : attachments) { + File file = new File(attachment); + if(!file.isAbsolute() && !file.exists()){ + log.debug("loading file with relative path: " +attachment); + file = new File(FileServer.getFileServer().getBaseDir(), attachment); + log.debug("file path set to: "+attachment); + } + instance.addAttachment(file); + } + } + + } + + // needed for measuring sending time + instance.setSynchronousMode(true); + + instance.setHeaderFields((CollectionProperty)getProperty(SmtpSampler.HEADER_FIELDS)); + message = instance.prepareMessage(); + + if (getPropertyAsBoolean(MESSAGE_SIZE_STATS)) { + // calculate message size + CounterOutputStream cs = new CounterOutputStream(); + message.writeTo(cs); + res.setBytes(cs.getCount()); + } else { + res.setBytes(-1); + } + + } catch (Exception ex) { + log.warn("Error while preparing message", ex); + res.setResponseCode("500"); + res.setResponseMessage(ex.toString()); + return res; + } + + // Set up the sample result details + res.setDataType(SampleResult.TEXT); + try { + res.setRequestHeaders(getRequestHeaders(message)); + res.setSamplerData(getSamplerData(message)); + } catch (MessagingException e1) { + res.setSamplerData("Error occurred trying to save request info: "+e1); + log.warn("Error occurred trying to save request info",e1); + } catch (IOException e1) { + res.setSamplerData("Error occurred trying to save request info: "+e1); + log.warn("Error occurred trying to save request info",e1); + } + + // Perform the sampling + res.sampleStart(); + + try { + instance.execute(message); + + res.setResponseCodeOK(); + /* + * TODO if(instance.getSMTPStatusCode == 250) + * res.setResponseMessage("Message successfully sent!"); else + * res.setResponseMessage(instance.getSMTPStatusCodeIncludingMessage); + */ + res.setResponseMessage("Message successfully sent!\n" + + instance.getServerResponse()); + isOK = true; + } + // username / password incorrect + catch (AuthenticationFailedException afex) { + log.warn("", afex); + res.setResponseCode("500"); + res.setResponseMessage("AuthenticationFailedException: authentication failed - wrong username / password!\n" + + afex); + // SSL not supported, startTLS not supported, other messagingException + } catch (MessagingException mex) { + log.warn("",mex); + res.setResponseCode("500"); + if (mex.getMessage().matches(".*Could not connect to SMTP host.*465.*") + && mex.getCause().getMessage().matches(".*Connection timed out.*")) { + res.setResponseMessage("MessagingException: Probably, SSL is not supported by the SMTP-Server!\n" + + mex); + } else if (mex.getMessage().matches(".*StartTLS failed.*")) { + res.setResponseMessage("MessagingException: StartTLS not supported by server or initializing failed!\n" + + mex); + } else if (mex.getMessage().matches(".*send command to.*") + && mex.getCause().getMessage().matches( + ".*unable to find valid certification path to requested target.*")) { + res.setResponseMessage("MessagingException: Server certificate not trusted - perhaps you have to restart JMeter!\n" + + mex); + } else { + res.setResponseMessage("Other MessagingException: " + mex.toString()); + } + } catch (Exception ex) { // general exception + log.warn("",ex); + res.setResponseCode("500"); + if (null != ex.getMessage() + && ex.getMessage().matches("Failed to build truststore")) { + res.setResponseMessage("Failed to build truststore - did not try to send mail!"); + } else { + res.setResponseMessage("Other Exception: " + ex.toString()); + } + } + + res.sampleEnd(); + + try { + // process the sampler result + InputStream is = message.getInputStream(); + StringBuilder sb = new StringBuilder(); + byte[] buf = new byte[1024]; + int read = is.read(buf); + while (read > 0) { + sb.append(new String(buf, 0, read)); // TODO - charset? + read = is.read(buf); + } + // TODO - charset? + res.setResponseData(sb.toString().getBytes()); // TODO this should really be request data, but there is none + } catch (IOException ex) { + log.warn("",ex); + } catch (MessagingException ex) { + log.warn("",ex); + } + + res.setSuccessful(isOK); + + return res; + } + + private String getRequestHeaders(Message message) throws MessagingException { + StringBuilder sb = new StringBuilder(); + @SuppressWarnings("unchecked") // getAllHeaders() is not yet genericised + Enumeration
headers = message.getAllHeaders(); // throws ME + writeHeaders(headers, sb); + return sb.toString(); + } + + private String getSamplerData(Message message) throws MessagingException, IOException { + StringBuilder sb = new StringBuilder(); + Object content = message.getContent(); // throws ME + if (content instanceof Multipart) { + Multipart multipart = (Multipart) content; + String contentType = multipart.getContentType(); + ContentType ct = new ContentType(contentType); + String boundary=ct.getParameter("boundary"); + for (int i = 0; i < multipart.getCount(); i++) { // throws ME + sb.append("--"); + sb.append(boundary); + sb.append("\n"); + BodyPart bodyPart = multipart.getBodyPart(i); // throws ME + writeBodyPart(sb, bodyPart); // throws IOE, ME + } + sb.append("--"); + sb.append(boundary); + sb.append("--"); + sb.append("\n"); + } else if(content instanceof BodyPart){ + BodyPart bodyPart = (BodyPart) content; + writeBodyPart(sb, bodyPart); // throws IOE, ME + } else if (content instanceof String){ + sb.append(content); + } else { + sb.append("Content has class: "+content.getClass().getCanonicalName()); + } + return sb.toString(); + } + + private void writeHeaders(Enumeration
headers, StringBuilder sb) { + while (headers.hasMoreElements()) { + Header header = headers.nextElement(); + sb.append(header.getName()); + sb.append(": "); + sb.append(header.getValue()); + sb.append("\n"); + } + } + + private void writeBodyPart(StringBuilder sb, BodyPart bodyPart) + throws MessagingException, IOException { + @SuppressWarnings("unchecked") // API not yet generic + Enumeration
allHeaders = bodyPart.getAllHeaders(); // throws ME + writeHeaders(allHeaders, sb); + String disposition = bodyPart.getDisposition(); // throws ME + sb.append("\n"); + if (BodyPart.ATTACHMENT.equals(disposition)) { + sb.append(""); + } else { + sb.append(bodyPart.getContent()); // throws IOE, ME + } + sb.append("\n"); + } + + /** + * Get the list of addresses or null. + * Null is treated differently from an empty list. + * @param propValue addresses separated by ";" + * @return the list or null if the input was the empty string + * @throws AddressException + */ + private List getPropNameAsAddresses(String propValue) throws AddressException{ + if (propValue.length() > 0){ // we have at least one potential address + List addresses = new ArrayList(); + for (String address : propValue.split(";")){ + addresses.add(new InternetAddress(address.trim())); + } + return addresses; + } else { + return null; + } + } + + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SecuritySettingsPanel.java b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SecuritySettingsPanel.java new file mode 100644 index 0000000..4f5364b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SecuritySettingsPanel.java @@ -0,0 +1,413 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.smtp.sampler.gui; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class SecuritySettingsPanel extends JPanel{ + + private static final long serialVersionUID = 1L; + + //++JMX attribute names - do not change the values! + // These were moved from SMTPSampler, which is why the prefix is still SMTSampler + public final static String USE_SSL = "SMTPSampler.useSSL"; // $NON-NLS-1$ + public final static String USE_STARTTLS = "SMTPSampler.useStartTLS"; // $NON-NLS-1$ + public final static String SSL_TRUST_ALL_CERTS = "SMTPSampler.trustAllCerts"; // $NON-NLS-1$ + public final static String ENFORCE_STARTTLS = "SMTPSampler.enforceStartTLS"; // $NON-NLS-1$ + public final static String USE_LOCAL_TRUSTSTORE = "SMTPSampler.useLocalTrustStore"; // $NON-NLS-1$ + public final static String TRUSTSTORE_TO_USE = "SMTPSampler.trustStoreToUse"; // $NON-NLS-1$ + //--JMX attribute names + + private ButtonGroup bgSecuritySettings; + + private JRadioButton rbUseNone; + + private JRadioButton rbUseSSL; + + private JRadioButton rbUseStartTLS; + + private JCheckBox cbTrustAllCerts; + + private JCheckBox cbEnforceStartTLS; + + private JCheckBox cbUseLocalTrustStore; + + private JLabel jlTrustStoreToUse; + + private JTextField tfTrustStoreToUse; + + + public SecuritySettingsPanel() { + super(); + init(); + } + + public void init(){ + this.setLayout(new GridBagLayout()); + this.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_security_settings"))); // $NON-NLS-1$ + + GridBagConstraints gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2); + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.WEST; + gridBagConstraints.weightx = 0.5; + + rbUseNone = new JRadioButton(JMeterUtils.getResString("smtp_usenone")); // $NON-NLS-1$ + rbUseSSL = new JRadioButton(JMeterUtils.getResString("smtp_usessl")); // $NON-NLS-1$ + rbUseStartTLS = new JRadioButton(JMeterUtils.getResString("smtp_usestarttls")); // $NON-NLS-1$ + + cbTrustAllCerts = new JCheckBox(JMeterUtils.getResString("smtp_trustall")); // $NON-NLS-1$ + cbEnforceStartTLS = new JCheckBox(JMeterUtils.getResString("smtp_enforcestarttls")); // $NON-NLS-1$ + cbUseLocalTrustStore = new JCheckBox(JMeterUtils.getResString("smtp_usetruststore")); // $NON-NLS-1$ + + jlTrustStoreToUse = new JLabel(JMeterUtils.getResString("smtp_truststore")); // $NON-NLS-1$ + + tfTrustStoreToUse = new JTextField(20); + + rbUseNone.setSelected(true); + bgSecuritySettings = new ButtonGroup(); + bgSecuritySettings.add(rbUseNone); + bgSecuritySettings.add(rbUseSSL); + bgSecuritySettings.add(rbUseStartTLS); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + this.add(rbUseNone, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + this.add(rbUseSSL, gridBagConstraints); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 0; + this.add(rbUseStartTLS, gridBagConstraints); + + rbUseNone.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent evt) { + rbSecuritySettingsItemStateChanged(evt); + } + }); + rbUseSSL.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent evt) { + rbSecuritySettingsItemStateChanged(evt); + } + }); + rbUseStartTLS.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent evt) { + rbSecuritySettingsItemStateChanged(evt); + } + }); + + cbTrustAllCerts.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbTrustAllCerts.setMargin(new java.awt.Insets(0, 0, 0, 0)); + cbTrustAllCerts.setEnabled(false); + cbTrustAllCerts.setToolTipText(JMeterUtils.getResString("smtp_trustall_tooltip")); // $NON-NLS-1$ + cbTrustAllCerts.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + cbTrustAllCertsActionPerformed(evt); + } + }); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + this.add(cbTrustAllCerts, gridBagConstraints); + + cbEnforceStartTLS.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbEnforceStartTLS.setMargin(new java.awt.Insets(0, 0, 0, 0)); + cbEnforceStartTLS.setEnabled(false); + cbEnforceStartTLS.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + cbEnforceStartTLSActionPerformed(evt); + } + }); + cbEnforceStartTLS.setToolTipText(JMeterUtils.getResString("smtp_enforcestarttls_tooltip")); // $NON-NLS-1$ + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 1; + this.add(cbEnforceStartTLS, gridBagConstraints); + + cbUseLocalTrustStore.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbUseLocalTrustStore.setMargin(new java.awt.Insets(0, 0, 0, 0)); + cbUseLocalTrustStore.setEnabled(false); + cbUseLocalTrustStore.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + cbUseLocalTrustStoreActionPerformed(evt); + } + }); + + cbUseLocalTrustStore.setToolTipText(JMeterUtils.getResString("smtp_usetruststore_tooltip")); // $NON-NLS-1$ + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 2; + this.add(cbUseLocalTrustStore, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.gridwidth = 1; + jlTrustStoreToUse.setToolTipText(JMeterUtils.getResString("smtp_truststore_tooltip")); + this.add(jlTrustStoreToUse, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + tfTrustStoreToUse.setToolTipText(JMeterUtils.getResString("smtp_truststore_tooltip")); + this.add(tfTrustStoreToUse, gridBagConstraints); + } + + /** + * ActionPerformed-method for checkbox "useLocalTrustStore" + * + * @param evt + * ActionEvent to be handled + */ + private void cbUseLocalTrustStoreActionPerformed( + ActionEvent evt) { + final boolean selected = cbUseLocalTrustStore.isSelected(); + tfTrustStoreToUse.setEditable(selected); // must follow the checkbox setting + if (selected) { + cbTrustAllCerts.setSelected(false); // not compatible + } + } + /** + * ActionPerformed-method for checkbox "cbTrustAllCerts" + * + * @param evt + * ActionEvent to be handled + */ + private void cbTrustAllCertsActionPerformed( + ActionEvent evt) { + final boolean selected = cbTrustAllCerts.isSelected(); + if (selected) { + cbUseLocalTrustStore.setSelected(false); // not compatible + tfTrustStoreToUse.setEditable(false); // must follow the checkbox setting + } + } + + /** + * ActionPerformed-method for checkbox "enforceStartTLS", empty method + * header + * + * @param evt + * ActionEvent to be handled + */ + private void cbEnforceStartTLSActionPerformed(ActionEvent evt) { + } + + /** + * ItemStateChanged-method for radiobutton "securitySettings" + * + * @param evt + * ItemEvent to be handled + */ + private void rbSecuritySettingsItemStateChanged(ItemEvent evt) { + final Object source = evt.getSource(); + if (source == rbUseNone) { + cbTrustAllCerts.setEnabled(false); + cbTrustAllCerts.setSelected(false); + cbEnforceStartTLS.setEnabled(false); + cbEnforceStartTLS.setSelected(false); + cbUseLocalTrustStore.setSelected(false); + cbUseLocalTrustStore.setEnabled(false); + tfTrustStoreToUse.setEditable(false); + } else if (source == rbUseSSL) { + cbTrustAllCerts.setEnabled(true); + cbEnforceStartTLS.setEnabled(false); + cbEnforceStartTLS.setSelected(false); + cbUseLocalTrustStore.setEnabled(true); + tfTrustStoreToUse.setEditable(false); + } else if (source == rbUseStartTLS) { + cbTrustAllCerts.setEnabled(true); + cbTrustAllCerts.setSelected(false); + cbEnforceStartTLS.setEnabled(true); + cbUseLocalTrustStore.setEnabled(true); + cbUseLocalTrustStore.setSelected(false); + tfTrustStoreToUse.setEditable(false); + } + } + /** + * Returns if SSL is used to secure the SMTP-connection (checkbox) + * + * @return true if SSL is used to secure the SMTP-connection + */ + public boolean isUseSSL() { + return rbUseSSL.isSelected(); + } + + /** + * Sets SSL to be used to secure the SMTP-connection (checkbox) + * + * @param useSSL + * Use SSL to secure the connection + */ + public void setUseSSL(boolean useSSL) { + rbUseSSL.setSelected(useSSL); + } + + /** + * Returns if StartTLS is used to secure the connection (checkbox) + * + * @return true if StartTLS is used to secure the connection + */ + public boolean isUseStartTLS() { + return rbUseStartTLS.isSelected(); + } + + /** + * Sets StartTLS to be used to secure the SMTP-connection (checkbox) + * + * @param useStartTLS + * Use StartTLS to secure the connection + */ + public void setUseStartTLS(boolean useStartTLS) { + rbUseStartTLS.setSelected(useStartTLS); + } + + /** + * Returns if StartTLS is enforced (normally, SMTP uses plain + * SMTP-connection as fallback if "250-STARTTLS" isn't sent from the + * mailserver) (checkbox) + * + * @return true if StartTLS is enforced + */ + public boolean isEnforceStartTLS() { + return cbEnforceStartTLS.isSelected(); + } + + /** + * Enforces StartTLS to secure the SMTP-connection (checkbox) + * + * @param enforceStartTLS + * Enforce the use of StartTLS to secure the connection + * @see #isEnforceStartTLS() + */ + public void setEnforceStartTLS(boolean enforceStartTLS) { + cbEnforceStartTLS.setSelected(enforceStartTLS); + } + /** + * Returns if local (pre-installed) truststore is used to avoid + * SSL-connection-exceptions (checkbox) + * + * @return true if a local truststore is used + */ + public boolean isUseLocalTrustStore() { + return cbUseLocalTrustStore.isSelected(); + } + + /** + * Set the use of a local (pre-installed) truststore to avoid + * SSL-connection-exceptions (checkbox) + * + * @param useLocalTrustStore + * Use local keystore + */ + public void setUseLocalTrustStore(boolean useLocalTrustStore) { + cbUseLocalTrustStore.setSelected(useLocalTrustStore); + tfTrustStoreToUse.setEditable(useLocalTrustStore); // ensure correctly set on initial display + } + + /** + * Returns the path to the local (pre-installed) truststore to be used to + * avoid SSL-connection-exceptions + * + * @return Path to local truststore + */ + public String getTrustStoreToUse() { + return tfTrustStoreToUse.getText(); + } + + /** + * Set the path to local (pre-installed) truststore to be used to avoid + * SSL-connection-exceptions + * + * @param trustStoreToUse + * Path to local truststore + */ + public void setTrustStoreToUse(String trustStoreToUse) { + tfTrustStoreToUse.setText(trustStoreToUse); + } + public void setUseNoSecurity(boolean selected) { + rbUseNone.setSelected(selected); + } + /** + * Returns if all certificates are blindly trusted (using according + * SocketFactory) (checkbox) + * + * @return true if all certificates are blindly trusted + */ + public boolean isTrustAllCerts() { + return cbTrustAllCerts.isSelected(); + } + + /** + * Enforces JMeter to trust all certificates, no matter what CA is issuer + * (checkbox) + * + * @param trustAllCerts + * Trust all certificates + * @see #isTrustAllCerts() + */ + public void setTrustAllCerts(boolean trustAllCerts) { + cbTrustAllCerts.setSelected(trustAllCerts); + } + + public void clear() { + tfTrustStoreToUse.setText(""); + rbUseNone.setSelected(true); + } + + public void configure(TestElement element) { + setUseSSL(element.getPropertyAsBoolean(USE_SSL)); + setUseStartTLS(element.getPropertyAsBoolean(USE_STARTTLS)); + if(!element.getPropertyAsBoolean(USE_STARTTLS) && !element.getPropertyAsBoolean(USE_SSL)){ + setUseNoSecurity(true); + } + setTrustAllCerts(element.getPropertyAsBoolean(SSL_TRUST_ALL_CERTS)); + setEnforceStartTLS(element.getPropertyAsBoolean(ENFORCE_STARTTLS)); + setUseLocalTrustStore(element.getPropertyAsBoolean(USE_LOCAL_TRUSTSTORE)); + setTrustStoreToUse(element.getPropertyAsString(TRUSTSTORE_TO_USE)); + } + + public void modifyTestElement(TestElement te) { + te.setProperty(USE_SSL, Boolean.toString(isUseSSL())); + te.setProperty(USE_STARTTLS, Boolean.toString(isUseStartTLS())); + te.setProperty(SSL_TRUST_ALL_CERTS, Boolean.toString(isTrustAllCerts())); + te.setProperty(ENFORCE_STARTTLS, Boolean.toString(isEnforceStartTLS())); + te.setProperty(USE_LOCAL_TRUSTSTORE, Boolean.toString(isUseLocalTrustStore())); + te.setProperty(TRUSTSTORE_TO_USE, getTrustStoreToUse()); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpPanel.java b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpPanel.java new file mode 100644 index 0000000..027a856 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpPanel.java @@ -0,0 +1,1103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.smtp.sampler.gui; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.protocol.smtp.sampler.SmtpSampler; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Class to build gui-components for SMTP-sampler. Getter-methods serve the + * input-data to the sampler-object, which provides them to the + * SendMailCommand-object. + */ +public class SmtpPanel extends JPanel { + + private static final long serialVersionUID = 1L; + + // local vars + private JTextField tfMailFrom; + private JTextField tfMailReplyTo; + private JButton browseButton; + private JButton emlBrowseButton; + private JCheckBox cbUseAuth; + private JTextField tfMailServer; + private JTextField tfMailServerPort; + private JTextField tfMailTo; + private JTextField tfMailToCC; + private JTextField tfMailToBCC; + private JTextField tfAttachment; + private JTextField tfEmlMessage; + private JTextArea taMessage; + private JCheckBox cbPlainBody; + + private JLabel jlAddressFrom; + private JLabel jlAddressReplyTo; + private JLabel jlAddressTo; + private JLabel jlAddressToCC; + private JLabel jlAddressToBCC; + private JLabel jlMailServerPort; + private JLabel jlMailServer; + private JLabel jlAttachFile; + private JLabel jlDutPortStandard; + private JLabel jlPassword; + private JLabel jlSubject; + private JLabel jlUsername; + private JLabel jlMessage; + + private JFileChooser attachmentFileChooser; + private JFileChooser emlFileChooser; + private JTextField tfAuthPassword; + private JTextField tfAuthUsername; + private JTextField tfSubject; + private JCheckBox cbSuppressSubject; + private JCheckBox cbIncludeTimestamp; + private JCheckBox cbMessageSizeStats; + private JCheckBox cbEnableDebug; + private JCheckBox cbUseEmlMessage; + + private JPanel headerFieldsPanel; + private JButton addHeaderFieldButton; + private JLabel headerFieldName; + private JLabel headerFieldValue; + private Map headerFields = new HashMap(); + private Map removeButtons = new HashMap(); + private int headerGridY = 0; + + private SecuritySettingsPanel securitySettingsPanel; + + /** + * Creates new form SmtpPanel, standard constructer. Calls + * initComponents();. + */ + public SmtpPanel() { + initComponents(); + } + + /** + * Returns sender-address for e-mail from textfield + * + * @return Sender + */ + public String getMailFrom() { + return tfMailFrom.getText(); + } + + /** + * Returns receiver in field "to" from textfield + * + * @return Receiver "to" + */ + public String getReceiverTo() { + return tfMailTo.getText(); + } + + /** + * Returns receiver in field "cc" from textfield + * + * @return Receiver "cc" + */ + public String getReceiverCC() { + return tfMailToCC.getText(); + } + + /** + * Returns receiver in field "bcc" from textfield + * + * @return Receiver "bcc" + */ + public String getReceiverBCC() { + return tfMailToBCC.getText(); + } + + /** + * Returns message body, i.e. main-mime-part of message (from textfield) + * + * @return Message body + */ + public String getBody() { + return taMessage.getText(); + } + + /** + * Sets message body, i.e. main-mime-part of message in textfield + * + * @param messageBodyText + * Message body + */ + public void setBody(String messageBodyText) { + taMessage.setText(messageBodyText); + } + + /** + * Sets sender-address of e-mail in textfield + * + * @param mailFrom + * Sender + */ + public void setMailFrom(String mailFrom) { + tfMailFrom.setText(mailFrom); + } + + /** + * Sets receiver in textfield "to" + * + * @param mailTo + * Receiver "to" + */ + public void setReceiverTo(String mailTo) { + tfMailTo.setText(mailTo); + } + + /** + * Sets receiver in textfield "cc" + * + * @param mailToCC + * Receiver "cc" + */ + public void setReceiverCC(String mailToCC) { + tfMailToCC.setText(mailToCC); + } + + /** + * Sets receiver in textfield "bcc" + * + * @param mailToBCC + * Receiver "bcc" + */ + public void setReceiverBCC(String mailToBCC) { + tfMailToBCC.setText(mailToBCC); + } + + /** + * Returns path of file(s) to be attached in e-mail from textfield + * + * @return File to attach + */ + public String getAttachments() { + return tfAttachment.getText(); + } + + /** + * Sets path of file to be attached in e-mail in textfield + * + * @param attachments + * File to attach + */ + public void setAttachments(String attachments) { + tfAttachment.setText(attachments); + } + + /** + * Returns port of mail-server (standard 25 for SMTP/SMTP with StartTLS, 465 + * for SSL) from textfield + * + * @return Mail-server port + */ + public String getPort() { + return tfMailServerPort.getText(); + } + + /** + * Sets port of mail-server + * + * @param port + * Mail-server port + */ + public void setPort(String port) { + tfMailServerPort.setText(port); + } + + /** + * Returns mail-server to be used to send message (from textfield) + * + * @return FQDN or IP of mail-server + */ + public String getServer() { + return tfMailServer.getText(); + } + + /** + * Sets mail-server to be used to send message in textfield + * + * @param server + * FQDN or IP of mail-server + */ + public void setServer(String server) { + tfMailServer.setText(server); + } + + /** + * Returns subject of the e-mail from textfield + * + * @return Subject of e-mail + */ + public String getSubject() { + return tfSubject.getText(); + } + + /** + * Sets subject of the e-mail in textfield + * + * @param subject + * Subject of e-mail + */ + public void setSubject(String subject) { + tfSubject.setText(subject); + } + + /** + * Returns true if subject header should be suppressed + * + * @return true if subject header should be suppressed + */ + public boolean isSuppressSubject() { + return cbSuppressSubject.isSelected(); + } + + /** + * Sets the property that defines if the subject header should be suppressed + * + * @param emptySubject + * + */ + public void setSuppressSubject(boolean emptySubject) { + cbSuppressSubject.setSelected(emptySubject); + } + + /** + * Returns true if message body should be plain (i.e. not multipart/mixed) + * + * @return true if using plain message body (i.e. not multipart/mixed) + */ + public boolean isPlainBody() { + return cbPlainBody.isSelected(); + } + + /** + * Sets the property that defines if the body should be plain (i.e. not multipart/mixed) + * + * @param plainBody whether to use a plain body (i.e. not multipart/mixed) + */ + public void setPlainBody(boolean plainBody) { + cbPlainBody.setSelected(plainBody); + } + + /** + * Returns if mail-server needs authentication (checkbox) + * + * @return true if authentication is used + */ + public boolean isUseAuth() { + return cbUseAuth.isSelected(); + } + + /** + * Set whether mail server needs auth. + * + * @param selected + */ + public void setUseAuth(boolean selected){ + cbUseAuth.setSelected(selected); + tfAuthPassword.setEditable(selected); // ensure correctly set on initial display + tfAuthUsername.setEditable(selected); // ensure correctly set on initial display + } + + + public boolean isEnableDebug() { + return cbEnableDebug.isSelected(); + } + + public void setEnableDebug(boolean selected){ + cbEnableDebug.setSelected(selected); + } + + + + /** + * Returns if an .eml-message is sent instead of the content of message-text + * area + * + * @return true if .eml is sent, false if text area content is sent in + * e-mail + */ + public boolean isUseEmlMessage() { + return cbUseEmlMessage.isSelected(); + } + + /** + * Set the use of an .eml-message instead of the content of message-text + * area + * + * @param useEmlMessage + * Use eml message + */ + public void setUseEmlMessage(boolean useEmlMessage) { + cbUseEmlMessage.setSelected(useEmlMessage); + } + + /** + * Returns path to eml message to be sent + * + * @return path to eml message to be sent + */ + public String getEmlMessage() { + return tfEmlMessage.getText(); + } + + /** + * Set path to eml message to be sent + * + * @param emlMessage + * path to eml message to be sent + */ + public void setEmlMessage(String emlMessage) { + tfEmlMessage.setText(emlMessage); + } + + /** + * Returns if current timestamp is included in the subject (checkbox) + * + * @return true if current timestamp is included in subject + */ + public boolean isIncludeTimestamp() { + return cbIncludeTimestamp.isSelected(); + } + + /** + * Set timestamp to be included in the message-subject (checkbox) + * + * @param includeTimestamp + * Should timestamp be included in subject? + */ + public void setIncludeTimestamp(boolean includeTimestamp) { + cbIncludeTimestamp.setSelected(includeTimestamp); + } + + /** + * Returns if message size statistics are processed. Output of processing + * will be included in sample result. (checkbox) + * + * @return True if message size will be calculated + */ + public boolean isMessageSizeStatistics() { + return cbMessageSizeStats.isSelected(); + } + + /** + * Set message size to be calculated and included in sample result + * (checkbox) + * + * @param val + * Schould message size be calculated? + */ + public void setMessageSizeStatistic(boolean val) { + cbMessageSizeStats.setSelected(val); + } + + public String getPassword() { + return tfAuthPassword.getText(); + } + + public void setPassword(String authPassword) { + tfAuthPassword.setText(authPassword); + } + + public String getUsername() { + return tfAuthUsername.getText(); + } + + public void setUsername(String username) { + tfAuthUsername.setText(username); + } + + public CollectionProperty getHeaderFields() { + CollectionProperty result = new CollectionProperty(); + result.setName(SmtpSampler.HEADER_FIELDS); + for (Iterator iterator = headerFields.keySet().iterator(); iterator.hasNext();) { + JTextField headerName = iterator.next(); + String name = headerName.getText(); + String value = headerFields.get(headerName).getText(); + Argument argument = new Argument(name, value); + result.addItem(argument); + } + return result; + } + + public void setHeaderFields(CollectionProperty fields) { + clearHeaderFields(); + for (int i = 0; i < fields.size(); i++) { + Argument argument = (Argument)((TestElementProperty)fields.get(i)).getObjectValue(); + String name = argument.getName(); + JButton removeButton = addHeaderActionPerformed(null); + JTextField nameTF = removeButtons.get(removeButton); + nameTF.setText(name); + JTextField valueTF = headerFields.get(nameTF); + valueTF.setText(argument.getValue()); + } + validate(); + } + + public String getMailReplyTo() { + return tfMailReplyTo.getText(); + } + + public void setMailReplyTo(String replyTo) { + tfMailReplyTo.setText(replyTo); + } + + + /** + * Main method of class, builds all gui-components for SMTP-sampler. + */ + private void initComponents() { + GridBagConstraints gridBagConstraints, gridBagConstraintsMain; + + jlAddressReplyTo = new JLabel(JMeterUtils.getResString("smtp_replyto")); // $NON-NLS-1$ + jlAddressFrom = new JLabel(JMeterUtils.getResString("smtp_from")); // $NON-NLS-1$ + jlAddressTo = new JLabel(JMeterUtils.getResString("smtp_to")); // $NON-NLS-1$ + jlAddressToCC = new JLabel(JMeterUtils.getResString("smtp_cc")); // $NON-NLS-1$ + jlAddressToBCC = new JLabel(JMeterUtils.getResString("smtp_bcc")); // $NON-NLS-1$ + jlMailServerPort = new JLabel(JMeterUtils.getResString("smtp_server_port")); // $NON-NLS-1$ + jlMailServer = new JLabel(JMeterUtils.getResString("smtp_server")); // $NON-NLS-1$ + jlAttachFile = new JLabel(JMeterUtils.getResString("smtp_attach_file")); // $NON-NLS-1$ + jlDutPortStandard = new JLabel(JMeterUtils.getResString("smtp_default_port")); // $NON-NLS-1$ + jlUsername = new JLabel(JMeterUtils.getResString("smtp_username")); // $NON-NLS-1$ + jlPassword = new JLabel(JMeterUtils.getResString("smtp_password")); // $NON-NLS-1$ + jlSubject = new JLabel(JMeterUtils.getResString("smtp_subject")); // $NON-NLS-1$ + jlMessage = new JLabel(JMeterUtils.getResString("smtp_message")); // $NON-NLS-1$ + + tfMailServer = new JTextField(30); + tfMailServerPort = new JTextField(6); + tfMailFrom = new JTextField(25); + tfMailReplyTo = new JTextField(25); + tfMailTo = new JTextField(25); + tfMailToCC = new JTextField(25); + tfMailToBCC = new JTextField(25); + tfAuthUsername = new JTextField(20); + tfAuthPassword = new JPasswordField(20); + tfSubject = new JTextField(20); + tfAttachment = new JTextField(30); + tfEmlMessage = new JTextField(30); + + taMessage = new JTextArea(5, 20); + + cbPlainBody = new JCheckBox(JMeterUtils.getResString("smtp_plainbody")); // $NON-NLS-1$ + + cbSuppressSubject = new JCheckBox(JMeterUtils.getResString("smtp_suppresssubj")); // $NON-NLS-1$ + cbSuppressSubject.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent evt) { + emptySubjectActionPerformed(evt); + } + }); + + cbUseAuth = new JCheckBox(JMeterUtils.getResString("smtp_useauth")); // $NON-NLS-1$ + + cbIncludeTimestamp = new JCheckBox(JMeterUtils.getResString("smtp_timestamp")); // $NON-NLS-1$ + cbMessageSizeStats = new JCheckBox(JMeterUtils.getResString("smtp_messagesize")); // $NON-NLS-1$ + cbEnableDebug = new JCheckBox(JMeterUtils.getResString("smtp_enabledebug")); // $NON-NLS-1$ + cbUseEmlMessage = new JCheckBox(JMeterUtils.getResString("smtp_eml")); // $NON-NLS-1$ + + attachmentFileChooser = new JFileChooser(); + emlFileChooser = new JFileChooser(); + + browseButton = new JButton(JMeterUtils.getResString("browse")); // $NON-NLS-1$ + emlBrowseButton = new JButton(JMeterUtils.getResString("browse")); // $NON-NLS-1$ + + attachmentFileChooser + .addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + attachmentFolderFileChooserActionPerformed(evt); + } + }); + + emlFileChooser.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + emlFileChooserActionPerformed(evt); + } + }); + + setLayout(new GridBagLayout()); + + gridBagConstraintsMain = new GridBagConstraints(); + gridBagConstraintsMain.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraintsMain.anchor = GridBagConstraints.WEST; + gridBagConstraintsMain.weightx = 0.5; + + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2); + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.WEST; + gridBagConstraints.weightx = 0.5; + + /* + * Server Settings + */ + JPanel panelServerSettings = new JPanel(new GridBagLayout()); + panelServerSettings.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_server_settings"))); // $NON-NLS-1$ + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panelServerSettings.add(jlMailServer, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + panelServerSettings.add(tfMailServer, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panelServerSettings.add(jlMailServerPort, gridBagConstraints); + + JPanel panelServerPortSettings = new JPanel(new GridBagLayout()); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panelServerPortSettings.add(tfMailServerPort, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + panelServerPortSettings.add(jlDutPortStandard, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panelServerSettings.add(panelServerPortSettings, gridBagConstraints); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 0; + add(panelServerSettings, gridBagConstraintsMain); + + /* + * E-Mail Settings + */ + JPanel panelMailSettings = new JPanel(new GridBagLayout()); + panelMailSettings.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_mail_settings"))); // $NON-NLS-1$ + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panelMailSettings.add(jlAddressFrom, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + panelMailSettings.add(tfMailFrom, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panelMailSettings.add(jlAddressTo, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panelMailSettings.add(tfMailTo, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + panelMailSettings.add(jlAddressToCC, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + panelMailSettings.add(tfMailToCC, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + panelMailSettings.add(jlAddressToBCC, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 3; + panelMailSettings.add(tfMailToBCC, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + panelMailSettings.add(jlAddressReplyTo, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 4; + panelMailSettings.add(tfMailReplyTo, gridBagConstraints); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 1; + add(panelMailSettings, gridBagConstraintsMain); + + /* + * Auth Settings + */ + JPanel panelAuthSettings = new JPanel(new GridBagLayout()); + panelAuthSettings.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_auth_settings"))); // $NON-NLS-1$ + + cbUseAuth.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbUseAuth.setMargin(new java.awt.Insets(0, 0, 0, 0)); + cbUseAuth.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + cbUseAuthActionPerformed(evt); + } + }); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panelAuthSettings.add(cbUseAuth, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 1; + gridBagConstraints.weightx = 0; + panelAuthSettings.add(jlUsername, gridBagConstraints); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.weightx = 0.5; + panelAuthSettings.add(tfAuthUsername, gridBagConstraints); + tfAuthUsername.setEditable(false); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 1; + gridBagConstraints.weightx = 0; + panelAuthSettings.add(jlPassword, gridBagConstraints); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 1; + gridBagConstraints.weightx = 0.5; + panelAuthSettings.add(tfAuthPassword, gridBagConstraints); + tfAuthPassword.setEditable(false); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 2; + add(panelAuthSettings, gridBagConstraintsMain); + + /* + * Security Settings + */ + securitySettingsPanel = new SecuritySettingsPanel(); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 3; + add(securitySettingsPanel, gridBagConstraintsMain); + + /* + * (non-Javadoc) Message Settings + */ + JPanel panelMessageSettings = new JPanel(new GridBagLayout()); + panelMessageSettings.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_message_settings"))); // $NON-NLS-1$ + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panelMessageSettings.add(jlSubject, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + panelMessageSettings.add(tfSubject, gridBagConstraints); + + cbSuppressSubject.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbSuppressSubject.setMargin(new java.awt.Insets(0, 0, 0, 0)); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(cbSuppressSubject, gridBagConstraints); + + cbIncludeTimestamp.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbIncludeTimestamp.setMargin(new java.awt.Insets(0, 0, 0, 0)); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(cbIncludeTimestamp, gridBagConstraints); + + /* + * Add the header panel + */ + + addHeaderFieldButton = new JButton(JMeterUtils.getResString("smtp_header_add")); + addHeaderFieldButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + addHeaderActionPerformed(evt); + } + }); + headerFieldName = new JLabel(JMeterUtils.getResString("smtp_header_name")); + headerFieldValue = new JLabel(JMeterUtils.getResString("smtp_header_value")); + headerFieldsPanel = new JPanel(new GridBagLayout()); + + headerFieldName.setVisible(false); + headerFieldValue.setVisible(false); + + headerGridY=0; + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = headerGridY++; + headerFieldsPanel.add(addHeaderFieldButton, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = headerGridY; + headerFieldsPanel.add(headerFieldName, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = headerGridY++; + headerFieldsPanel.add(headerFieldValue, gridBagConstraints); + + gridBagConstraintsMain.gridx = 1; + gridBagConstraintsMain.gridy = 2; + panelMessageSettings.add(headerFieldsPanel, gridBagConstraintsMain); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + panelMessageSettings.add(jlMessage, gridBagConstraints); + + taMessage.setBorder(BorderFactory.createBevelBorder(1)); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 3; + gridBagConstraints.fill = GridBagConstraints.BOTH; + panelMessageSettings.add(taMessage, gridBagConstraints); + + cbPlainBody.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbPlainBody.setMargin(new java.awt.Insets(0, 0, 0, 0)); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 3; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(cbPlainBody, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(jlAttachFile, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 4; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + panelMessageSettings.add(tfAttachment, gridBagConstraints); + tfAttachment.setToolTipText(JMeterUtils.getResString("smtp_attach_file_tooltip")); // $NON-NLS-1$ + + browseButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + browseButtonActionPerformed(evt); + } + }); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 4; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(browseButton, gridBagConstraints); + + cbUseEmlMessage.setSelected(false); + cbUseEmlMessage.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + cbUseEmlMessageActionPerformed(evt); + } + }); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 5; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(cbUseEmlMessage, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 5; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + tfEmlMessage.setEnabled(false); + panelMessageSettings.add(tfEmlMessage, gridBagConstraints); + + emlBrowseButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + emlBrowseButtonActionPerformed(evt); + } + }); + emlBrowseButton.setEnabled(false); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 5; + gridBagConstraints.fill = GridBagConstraints.NONE; + panelMessageSettings.add(emlBrowseButton, gridBagConstraints); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 6; + add(panelMessageSettings, gridBagConstraintsMain); + + /* + * Additional Settings + */ + JPanel panelAdditionalSettings = new JPanel(new GridBagLayout()); + panelAdditionalSettings.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("smtp_additional_settings"))); // $NON-NLS-1$ + + cbMessageSizeStats.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + cbMessageSizeStats.setMargin(new java.awt.Insets(0, 0, 0, 0)); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panelAdditionalSettings.add(cbMessageSizeStats, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + panelAdditionalSettings.add(cbEnableDebug, gridBagConstraints); + + gridBagConstraintsMain.gridx = 0; + gridBagConstraintsMain.gridy = 7; + add(panelAdditionalSettings, gridBagConstraintsMain); + } + + /** + * ActionPerformed-method for checkbox "useAuth" + * + * @param evt + * ActionEvent to be handled + */ + private void cbUseAuthActionPerformed(ActionEvent evt) { + tfAuthUsername.setEditable(cbUseAuth.isSelected()); + tfAuthPassword.setEditable(cbUseAuth.isSelected()); + } + + + + /** + * ActionPerformed-method for filechoser "attachmentFileChoser", creates + * FileChoser-Object + * + * @param evt + * ActionEvent to be handled + */ + private void attachmentFolderFileChooserActionPerformed(ActionEvent evt) { + File chosen = attachmentFileChooser.getSelectedFile(); + if (chosen == null){ + return; + } + final String attachments = tfAttachment.getText().trim(); + if (null != attachments && attachments.length() > 0) { + tfAttachment.setText(attachments + + SmtpSampler.FILENAME_SEPARATOR + + chosen.getAbsolutePath()); + } else { + tfAttachment.setText(chosen.getAbsolutePath()); + } + + } + + /** + * ActionPerformed-method for button "browseButton", opens FileDialog-Object + * + * @param evt + * ActionEvent to be handled + */ + private void browseButtonActionPerformed(ActionEvent evt) { + attachmentFileChooser.showOpenDialog(this); + } + + private void cbUseEmlMessageActionPerformed(ActionEvent evt) { + if (cbUseEmlMessage.isSelected()) { + tfEmlMessage.setEnabled(true); + emlBrowseButton.setEnabled(true); + + /*tfMailFrom.setEnabled(false); + tfMailTo.setEnabled(false); + tfMailToCC.setEnabled(false); + tfMailToBCC.setEnabled(false); + tfSubject.setEnabled(false);*/ + taMessage.setEnabled(false); + tfAttachment.setEnabled(false); + browseButton.setEnabled(false); + } else { + tfEmlMessage.setEnabled(false); + emlBrowseButton.setEnabled(false); + + /*tfMailFrom.setEnabled(true); + tfMailTo.setEnabled(true); + tfMailToCC.setEnabled(true); + tfMailToBCC.setEnabled(true); + tfSubject.setEnabled(true);*/ + taMessage.setEnabled(true); + tfAttachment.setEnabled(true); + browseButton.setEnabled(true); + } + } + + /** + * ActionPerformed-method for filechoser "emlFileChoser", creates + * FileChoser-Object + * + * @param evt + * ActionEvent to be handled + */ + private void emlFileChooserActionPerformed(ActionEvent evt) { + tfEmlMessage.setText(emlFileChooser.getSelectedFile().getAbsolutePath()); + } + + /** + * ActionPerformed-method for button "emlButton", opens FileDialog-Object + * + * @param evt + * ActionEvent to be handled + */ + private void emlBrowseButtonActionPerformed(ActionEvent evt) { + emlFileChooser.showOpenDialog(this); + } + + + + /** + * Reset all the Gui fields. + */ + public void clear() { + cbIncludeTimestamp.setSelected(false); + cbMessageSizeStats.setSelected(false); + cbEnableDebug.setSelected(false); + cbUseEmlMessage.setSelected(false); + cbUseAuth.setSelected(false); + taMessage.setText(""); + tfAttachment.setText(""); + tfAuthPassword.setText(""); + tfAuthUsername.setText(""); + tfEmlMessage.setText(""); + tfMailFrom.setText(""); + tfMailReplyTo.setText(""); + tfMailServer.setText(""); + tfMailServerPort.setText(""); + tfMailTo.setText(""); + tfMailToBCC.setText(""); + tfMailToCC.setText(""); + tfSubject.setText(""); + cbPlainBody.setSelected(false); + cbSuppressSubject.setSelected(false); + securitySettingsPanel.clear(); + clearHeaderFields(); + validate(); + } + + private void clearHeaderFields() { + headerFieldName.setVisible(false); + headerFieldValue.setVisible(false); + + for (Iterator iterator = removeButtons.keySet().iterator(); iterator.hasNext();) { + JButton removeButton = iterator.next(); + JTextField headerName = removeButtons.get(removeButton); + JTextField headerValue = headerFields.get(headerName); + + headerFieldsPanel.remove(headerName); + if (headerValue != null){ // Can be null (not sure why) + headerFieldsPanel.remove(headerValue); + } + headerFieldsPanel.remove(removeButton); + headerFields.remove(headerName); + iterator.remove(); + } + } + + private JButton addHeaderActionPerformed(ActionEvent evt){ + if(headerFields.size() == 0){ + headerFieldName.setVisible(true); + headerFieldValue.setVisible(true); + } + JTextField nameTF = new JTextField(); + JTextField valueTF = new JTextField(); + JButton removeButton = new JButton(JMeterUtils.getResString("smtp_header_remove")); + headerFields.put(nameTF, valueTF); + removeButtons.put(removeButton, nameTF); + + removeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + removeHeaderActionPerformed(evt); + } + }); + + GridBagConstraints gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2); + gridBagConstraints.weightx = 0.5; + gridBagConstraints.anchor = GridBagConstraints.WEST; + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = headerGridY; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + headerFieldsPanel.add(nameTF, gridBagConstraints); + + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = headerGridY; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + headerFieldsPanel.add(valueTF, gridBagConstraints); + + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = headerGridY++; + gridBagConstraints.fill = GridBagConstraints.NONE; + headerFieldsPanel.add(removeButton, gridBagConstraints); + + validate(); + return removeButton; + } + public SecuritySettingsPanel getSecuritySettingsPanel() { + return securitySettingsPanel; + } + + public void setSecuritySettingsPanel(SecuritySettingsPanel securitySettingsPanel) { + this.securitySettingsPanel = securitySettingsPanel; + } + + private void removeHeaderActionPerformed(ActionEvent evt){ + final Object source = evt.getSource(); + if(source != null && source instanceof JButton){ + if(headerFields.size() == 1){ + headerFieldName.setVisible(false); + headerFieldValue.setVisible(false); + } + JTextField nameTF = removeButtons.get(source); + JTextField valueTF = headerFields.get(nameTF); + headerFields.remove(nameTF); + + headerFieldsPanel.remove(nameTF); + headerFieldsPanel.remove(valueTF); + headerFieldsPanel.remove((JButton)source); + validate(); + } + } + private void emptySubjectActionPerformed(ChangeEvent evt) { + final Object source = evt.getSource(); + if(source != null && source instanceof JCheckBox){ + if(cbSuppressSubject.isSelected()){ + tfSubject.setEnabled(false); + cbIncludeTimestamp.setEnabled(false); + }else{ + tfSubject.setEnabled(true); + cbIncludeTimestamp.setEnabled(true); + } + } + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpSamplerGui.java new file mode 100644 index 0000000..824b560 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/gui/SmtpSamplerGui.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package org.apache.jmeter.protocol.smtp.sampler.gui; + +import java.awt.BorderLayout; +import java.awt.Component; + +import org.apache.jmeter.protocol.smtp.sampler.SmtpSampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; + +/** + * Class to build superstructure-gui for SMTP-panel, sets/gets value for a JMeter's testElement-object (i.e. also for save/load-purposes). + * This class extends AbstractSamplerGui, therefor most implemented methods are defined by JMeter's structure. + */ +public class SmtpSamplerGui extends AbstractSamplerGui { + + /** + * + */ + private static final long serialVersionUID = 1L; + private SmtpPanel smtpPanel; + + /** + * Creates new SmtpSamplerGui, standard constructer. Calls init(); + */ + public SmtpSamplerGui() { + init(); + } + + /** + * Method to be implemented by interface, overwritten by getStaticLabel(). Method has to be implemented by interface + * @return Null-String + * @see org.apache.jmeter.gui.JMeterGUIComponent#getLabelResource() + */ + public String getLabelResource() { + return "smtp_sampler_title"; + } + + /** + * Copy the data from the test element to the GUI, method has to be implemented by interface + * @param element Test-element to be used as data-input + * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#configure(org.apache.jmeter.testelement.TestElement) + */ + @Override + public void configure(TestElement element) { + if (smtpPanel == null){ + smtpPanel = new SmtpPanel(); + } + smtpPanel.setServer(element.getPropertyAsString(SmtpSampler.SERVER)); + smtpPanel.setPort(element.getPropertyAsString(SmtpSampler.SERVER_PORT)); + smtpPanel.setMailFrom(element.getPropertyAsString(SmtpSampler.MAIL_FROM)); + smtpPanel.setMailReplyTo(element.getPropertyAsString(SmtpSampler.MAIL_REPLYTO)); + smtpPanel.setReceiverTo(element.getPropertyAsString(SmtpSampler.RECEIVER_TO)); + smtpPanel.setReceiverCC(element.getPropertyAsString(SmtpSampler.RECEIVER_CC)); + smtpPanel.setReceiverBCC(element.getPropertyAsString(SmtpSampler.RECEIVER_BCC)); + + smtpPanel.setBody(element.getPropertyAsString(SmtpSampler.MESSAGE)); + smtpPanel.setPlainBody(element.getPropertyAsBoolean(SmtpSampler.PLAIN_BODY)); + smtpPanel.setSubject(element.getPropertyAsString(SmtpSampler.SUBJECT)); + smtpPanel.setSuppressSubject(element.getPropertyAsBoolean(SmtpSampler.SUPPRESS_SUBJECT)); + smtpPanel.setIncludeTimestamp(element.getPropertyAsBoolean(SmtpSampler.INCLUDE_TIMESTAMP)); + JMeterProperty headers = element.getProperty(SmtpSampler.HEADER_FIELDS); + if (headers instanceof CollectionProperty) { // Might be NullProperty + smtpPanel.setHeaderFields((CollectionProperty)headers); + } else { + smtpPanel.setHeaderFields(new CollectionProperty()); + } + smtpPanel.setAttachments(element.getPropertyAsString(SmtpSampler.ATTACH_FILE)); + + smtpPanel.setUseEmlMessage(element.getPropertyAsBoolean(SmtpSampler.USE_EML)); + smtpPanel.setEmlMessage(element.getPropertyAsString(SmtpSampler.EML_MESSAGE_TO_SEND)); + + SecuritySettingsPanel secPanel = smtpPanel.getSecuritySettingsPanel(); + secPanel.configure(element); + + smtpPanel.setUseAuth(element.getPropertyAsBoolean(SmtpSampler.USE_AUTH)); + smtpPanel.setUsername(element.getPropertyAsString(SmtpSampler.USERNAME)); + smtpPanel.setPassword(element.getPropertyAsString(SmtpSampler.PASSWORD)); + + smtpPanel.setMessageSizeStatistic(element.getPropertyAsBoolean(SmtpSampler.MESSAGE_SIZE_STATS)); + smtpPanel.setEnableDebug(element.getPropertyAsBoolean(SmtpSampler.ENABLE_DEBUG)); + + super.configure(element); + } + + /** + * Creates a new TestElement and set up its data + * @return Test-element for JMeter + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + SmtpSampler sampler = new SmtpSampler(); + modifyTestElement(sampler); + return sampler; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components + * @param te TestElement for JMeter + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(org.apache.jmeter.testelement.TestElement) + */ + public void modifyTestElement(TestElement te) { + te.clear(); + super.configureTestElement(te); + te.setProperty(SmtpSampler.SERVER, smtpPanel.getServer()); + te.setProperty(SmtpSampler.SERVER_PORT, smtpPanel.getPort()); + te.setProperty(SmtpSampler.MAIL_FROM, smtpPanel.getMailFrom()); + te.setProperty(SmtpSampler.MAIL_REPLYTO, smtpPanel.getMailReplyTo()); + te.setProperty(SmtpSampler.RECEIVER_TO, smtpPanel.getReceiverTo()); + te.setProperty(SmtpSampler.RECEIVER_CC, smtpPanel.getReceiverCC()); + te.setProperty(SmtpSampler.RECEIVER_BCC, smtpPanel.getReceiverBCC()); + te.setProperty(SmtpSampler.SUBJECT, smtpPanel.getSubject()); + te.setProperty(SmtpSampler.SUPPRESS_SUBJECT, Boolean.toString(smtpPanel.isSuppressSubject())); + te.setProperty(SmtpSampler.INCLUDE_TIMESTAMP, Boolean.toString(smtpPanel.isIncludeTimestamp())); + te.setProperty(SmtpSampler.MESSAGE, smtpPanel.getBody()); + te.setProperty(SmtpSampler.PLAIN_BODY, Boolean.toString(smtpPanel.isPlainBody())); + te.setProperty(SmtpSampler.ATTACH_FILE, smtpPanel.getAttachments()); + + SecuritySettingsPanel secPanel = smtpPanel.getSecuritySettingsPanel(); + secPanel.modifyTestElement(te); + + te.setProperty(SmtpSampler.USE_EML, smtpPanel.isUseEmlMessage()); + te.setProperty(SmtpSampler.EML_MESSAGE_TO_SEND, smtpPanel.getEmlMessage()); + + te.setProperty(SmtpSampler.USE_AUTH, Boolean.toString(smtpPanel.isUseAuth())); + te.setProperty(SmtpSampler.PASSWORD, smtpPanel.getPassword()); + te.setProperty(SmtpSampler.USERNAME, smtpPanel.getUsername()); + + te.setProperty(SmtpSampler.MESSAGE_SIZE_STATS, Boolean.toString(smtpPanel.isMessageSizeStatistics())); + te.setProperty(SmtpSampler.ENABLE_DEBUG, Boolean.toString(smtpPanel.isEnableDebug())); + + te.setProperty(smtpPanel.getHeaderFields()); + } + + /** + * Helper method to set up the GUI screen + */ + private void init() { + // Standard setup + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); // Add the standard title + add(makeDataPanel(), BorderLayout.CENTER); + } + + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + if (smtpPanel != null) { + smtpPanel.clear(); + } + } + /** + * Creates a sampler-gui-object, singleton-method + * @return Panel for entering the data + */ + private Component makeDataPanel() { + if (smtpPanel == null) + smtpPanel = new SmtpPanel(); + return smtpPanel; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/LocalTrustStoreSSLSocketFactory.java b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/LocalTrustStoreSSLSocketFactory.java new file mode 100644 index 0000000..9e41a72 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/LocalTrustStoreSSLSocketFactory.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.smtp.sampler.protocol; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.security.KeyStore; +import java.security.SecureRandom; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import org.apache.commons.io.IOUtils; + +/** + * This class implements an SSLSocketFactory which supports a local truststore. + */ +public class LocalTrustStoreSSLSocketFactory extends SSLSocketFactory { + + private final SSLSocketFactory factory; + + public LocalTrustStoreSSLSocketFactory(File truststore){ + SSLContext sslcontext = null; + try { + KeyStore ks = KeyStore.getInstance("JKS"); // $NON-NLS-1$ + FileInputStream stream = null; + try { + stream = new FileInputStream(truststore); + ks.load(stream, null); + } finally { + IOUtils.closeQuietly(stream); + } + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + TrustManager[] trustmanagers = tmf.getTrustManagers(); + sslcontext = SSLContext.getInstance("TLS"); // $NON-NLS-1$ + sslcontext.init( null, trustmanagers, new SecureRandom()); + } catch (Exception e) { + throw new RuntimeException("Could not create the SSL context",e); + } + factory = sslcontext.getSocketFactory(); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( Socket socket, String s, int i, boolean + flag) + throws IOException { + return factory.createSocket( socket, s, i, flag); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( InetAddress inaddr, int i, + InetAddress inaddr1, int j) throws IOException { + return factory.createSocket( inaddr, i, inaddr1, j); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( InetAddress inaddr, int i) throws + IOException { + return factory.createSocket( inaddr, i); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( String s, int i, InetAddress inaddr, int j) + throws IOException { + return factory.createSocket( s, i, inaddr, j); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( String s, int i) throws IOException { + return factory.createSocket( s, i); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket() throws IOException { + return factory.createSocket(); + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getDefaultCipherSuites() { + return factory.getSupportedCipherSuites(); + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getSupportedCipherSuites() { + return factory.getSupportedCipherSuites(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/SendMailCommand.java b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/SendMailCommand.java new file mode 100644 index 0000000..507cbbc --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/SendMailCommand.java @@ -0,0 +1,774 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.smtp.sampler.protocol; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import javax.activation.DataHandler; +import javax.activation.FileDataSource; +import javax.mail.BodyPart; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class performs all tasks necessary to send a message (build message, + * prepare connection, send message). Provides getter-/setter-methods for an + * SmtpSampler-object to configure transport and message settings. The + * send-mail-command itself is started by the SmtpSampler-object. + */ +public class SendMailCommand { + + // local vars + private static final Logger logger = LoggingManager.getLoggerForClass(); + + // Use the actual class so the name must be correct. + private static final String TRUST_ALL_SOCKET_FACTORY = TrustAllSSLSocketFactory.class.getName(); + + private boolean useSSL = false; + private boolean useStartTLS = false; + private boolean trustAllCerts = false; + private boolean enforceStartTLS = false; + private boolean sendEmlMessage = false; + private boolean enableDebug; + private String smtpServer; + private String smtpPort; + private String sender; + private List replyTo; + private String emlMessage; + private List receiverTo; + private List receiverCC; + private List receiverBCC; + private CollectionProperty headerFields; + private String subject = ""; + + private boolean useAuthentication = false; + private String username; + private String password; + + private boolean useLocalTrustStore; + private String trustStoreToUse; + + private List attachments; + + private String mailBody; + + // case we are measuring real time of spedition + private boolean synchronousMode; + + private Session session; + + private StringBuilder serverResponse = new StringBuilder(); // TODO this is not populated currently + + /** send plain body, i.e. not multipart/mixed */ + private boolean plainBody; + + /** + * Standard-Constructor + */ + public SendMailCommand() { + headerFields = new CollectionProperty(); + attachments = new ArrayList(); + } + + /** + * Prepares message prior to be sent via execute()-method, i.e. sets + * properties such as protocol, authentication, etc. + * + * @return Message-object to be sent to execute()-method + * @throws MessagingException + * @throws IOException + */ + public Message prepareMessage() throws MessagingException, IOException { + + Properties props = new Properties(); + + String protocol = getProtocol(); + + // set properties using JAF + props.setProperty("mail." + protocol + ".host", smtpServer); + props.setProperty("mail." + protocol + ".port", getPort()); + props.setProperty("mail." + protocol + ".auth", Boolean.toString(useAuthentication)); + + if (enableDebug) { + props.setProperty("mail.debug","true"); + } + + if (useStartTLS) { + props.setProperty("mail.smtp.starttls.enable", "true"); + if (enforceStartTLS){ + // Requires JavaMail 1.4.2+ + props.setProperty("mail.smtp.starttls.require", "true"); + } + } + + if (trustAllCerts) { + if (useSSL) { + props.setProperty("mail.smtps.ssl.socketFactory.class", TRUST_ALL_SOCKET_FACTORY); + props.setProperty("mail.smtps.ssl.socketFactory.fallback", "false"); + } else if (useStartTLS) { + props.setProperty("mail.smtp.ssl.socketFactory.class", TRUST_ALL_SOCKET_FACTORY); + props.setProperty("mail.smtp.ssl.socketFactory.fallback", "false"); + } + } else if (useLocalTrustStore){ + File truststore = new File(trustStoreToUse); + logger.info("load local truststore - try to load truststore from: "+truststore.getAbsolutePath()); + if(!truststore.exists()){ + logger.info("load local truststore -Failed to load truststore from: "+truststore.getAbsolutePath()); + truststore = new File(FileServer.getFileServer().getBaseDir(), trustStoreToUse); + logger.info("load local truststore -Attempting to read truststore from: "+truststore.getAbsolutePath()); + if(!truststore.exists()){ + logger.info("load local truststore -Failed to load truststore from: "+truststore.getAbsolutePath() + ". Local truststore not available, aborting execution."); + throw new IOException("Local truststore file not found. Also not available under : " + truststore.getAbsolutePath()); + } + } + if (useSSL) { + // Requires JavaMail 1.4.2+ + props.put("mail.smtps.ssl.socketFactory", new LocalTrustStoreSSLSocketFactory(truststore)); + props.put("mail.smtps.ssl.socketFactory.fallback", "false"); + } else if (useStartTLS) { + // Requires JavaMail 1.4.2+ + props.put("mail.smtp.ssl.socketFactory", new LocalTrustStoreSSLSocketFactory(truststore)); + props.put("mail.smtp.ssl.socketFactory.fallback", "false"); + } + } + + session = Session.getInstance(props, null); + + Message message; + + if (sendEmlMessage) { + message = new MimeMessage(session, new FileInputStream(emlMessage)); + } else { + message = new MimeMessage(session); + // handle body and attachments + Multipart multipart = new MimeMultipart(); + final int attachmentCount = attachments.size(); + if (plainBody && + (attachmentCount == 0 || (mailBody.length() == 0 && attachmentCount == 1))) { + if (attachmentCount == 1) { // i.e. mailBody is empty + File first = attachments.get(0); + InputStream is = null; + try { + is = new FileInputStream(first); + message.setText(IOUtils.toString(is)); + } finally { + IOUtils.closeQuietly(is); + } + } else { + message.setText(mailBody); + } + } else { + BodyPart body = new MimeBodyPart(); + body.setText(mailBody); + multipart.addBodyPart(body); + for (File f : attachments) { + BodyPart attach = new MimeBodyPart(); + attach.setFileName(f.getName()); + attach.setDataHandler(new DataHandler(new FileDataSource(f.getAbsolutePath()))); + multipart.addBodyPart(attach); + } + message.setContent(multipart); + } + } + + // set from field and subject + if (null != sender) { + message.setFrom(new InternetAddress(sender)); + } + + if (null != replyTo) { + InternetAddress[] to = new InternetAddress[replyTo.size()]; + message.setReplyTo(replyTo.toArray(to)); + } + + message.setSubject(subject); + + if (receiverTo != null) { + InternetAddress[] to = new InternetAddress[receiverTo.size()]; + receiverTo.toArray(to); + message.setRecipients(Message.RecipientType.TO, to); + } + + if (receiverCC != null) { + InternetAddress[] cc = new InternetAddress[receiverCC.size()]; + receiverCC.toArray(cc); + message.setRecipients(Message.RecipientType.CC, cc); + } + + if (receiverBCC != null) { + InternetAddress[] bcc = new InternetAddress[receiverBCC.size()]; + receiverBCC.toArray(bcc); + message.setRecipients(Message.RecipientType.BCC, bcc); + } + + for (int i = 0; i < headerFields.size(); i++) { + Argument argument = (Argument)((TestElementProperty)headerFields.get(i)).getObjectValue(); + message.setHeader(argument.getName(), argument.getValue()); + } + + message.saveChanges(); + return message; + } + + /** + * Sends message to mailserver, waiting for delivery if using synchronous mode. + * + * @param message + * Message prior prepared by prepareMessage() + * @throws MessagingException + * @throws IOException + * @throws InterruptedException + */ + public void execute(Message message) throws MessagingException, IOException, InterruptedException { + + Transport tr = session.getTransport(getProtocol()); + SynchronousTransportListener listener = null; + + if (synchronousMode) { + listener = new SynchronousTransportListener(); + tr.addTransportListener(listener); + } + + if (useAuthentication) { + tr.connect(smtpServer, username, password); + } else { + tr.connect(); + } + + tr.sendMessage(message, message.getAllRecipients()); + + if (synchronousMode) { + listener.attend(); // listener cannot be null here + } + + tr.close(); + logger.debug("transport closed"); + + logger.debug("message sent"); + return; + } + + /** + * Processes prepareMessage() and execute() + * + * @throws Exception + */ + public void execute() throws Exception { + execute(prepareMessage()); + } + + /** + * Returns FQDN or IP of SMTP-server to be used to send message - standard + * getter + * + * @return FQDN or IP of SMTP-server + */ + public String getSmtpServer() { + return smtpServer; + } + + /** + * Sets FQDN or IP of SMTP-server to be used to send message - to be called + * by SmtpSampler-object + * + * @param smtpServer + * FQDN or IP of SMTP-server + */ + public void setSmtpServer(String smtpServer) { + this.smtpServer = smtpServer; + } + + /** + * Returns sender-address for current message - standard getter + * + * @return sender-address + */ + public String getSender() { + return sender; + } + + /** + * Sets the sender-address for the current message - to be called by + * SmtpSampler-object + * + * @param sender + * Sender-address for current message + */ + public void setSender(String sender) { + this.sender = sender; + } + + /** + * Returns subject for current message - standard getter + * + * @return Subject of current message + */ + public String getSubject() { + return subject; + } + + /** + * Sets subject for current message - called by SmtpSampler-object + * + * @param subject + * Subject for message of current message - may be null + */ + public void setSubject(String subject) { + this.subject = subject; + } + + /** + * Returns username to authenticate at the mailserver - standard getter + * + * @return Username for mailserver + */ + public String getUsername() { + return username; + } + + /** + * Sets username to authenticate at the mailserver - to be called by + * SmtpSampler-object + * + * @param username + * Username for mailserver + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Returns password to authenticate at the mailserver - standard getter + * + * @return Password for mailserver + */ + public String getPassword() { + return password; + } + + /** + * Sets password to authenticate at the mailserver - to be called by + * SmtpSampler-object + * + * @param password + * Password for mailserver + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Sets receivers of current message ("to") - to be called by + * SmtpSampler-object + * + * @param receiverTo + * List of receivers + */ + public void setReceiverTo(List receiverTo) { + this.receiverTo = receiverTo; + } + + /** + * Returns receivers of current message ("cc") - standard + * getter + * + * @return List of receivers + */ + public List getReceiverCC() { + return receiverCC; + } + + /** + * Sets receivers of current message ("cc") - to be called by + * SmtpSampler-object + * + * @param receiverCC + * List of receivers + */ + public void setReceiverCC(List receiverCC) { + this.receiverCC = receiverCC; + } + + /** + * Returns receivers of current message ("bcc") - standard + * getter + * + * @return List of receivers + */ + public List getReceiverBCC() { + return receiverBCC; + } + + /** + * Sets receivers of current message ("bcc") - to be called by + * SmtpSampler-object + * + * @param receiverBCC + * List of receivers + */ + public void setReceiverBCC(List receiverBCC) { + this.receiverBCC = receiverBCC; + } + + /** + * Returns if authentication is used to access the mailserver - standard + * getter + * + * @return True if authentication is used to access mailserver + */ + public boolean isUseAuthentication() { + return useAuthentication; + } + + /** + * Sets if authentication should be used to access the mailserver - to be + * called by SmtpSampler-object + * + * @param useAuthentication + * Should authentication be used to access mailserver? + */ + public void setUseAuthentication(boolean useAuthentication) { + this.useAuthentication = useAuthentication; + } + + /** + * Returns if SSL is used to send message - standard getter + * + * @return True if SSL is used to transmit message + */ + public boolean getUseSSL() { + return useSSL; + } + + /** + * Sets SSL to secure the delivery channel for the message - to be called by + * SmtpSampler-object + * + * @param useSSL + * Should StartTLS be used to secure SMTP-connection? + */ + public void setUseSSL(boolean useSSL) { + this.useSSL = useSSL; + } + + /** + * Returns if StartTLS is used to transmit message - standard getter + * + * @return True if StartTLS is used to transmit message + */ + public boolean getUseStartTLS() { + return useStartTLS; + } + + /** + * Sets StartTLS to secure the delivery channel for the message - to be + * called by SmtpSampler-object + * + * @param useStartTLS + * Should StartTLS be used to secure SMTP-connection? + */ + public void setUseStartTLS(boolean useStartTLS) { + this.useStartTLS = useStartTLS; + } + + /** + * Returns port to be used for SMTP-connection (standard 25 or 465) - + * standard getter + * + * @return Port to be used for SMTP-connection + */ + public String getSmtpPort() { + return smtpPort; + } + + /** + * Sets port to be used for SMTP-connection (standard 25 or 465) - to be + * called by SmtpSampler-object + * + * @param smtpPort + * Port to be used for SMTP-connection + */ + public void setSmtpPort(String smtpPort) { + this.smtpPort = smtpPort; + } + + /** + * Returns if sampler should trust all certificates - standard getter + * + * @return True if all Certificates are trusted + */ + public boolean isTrustAllCerts() { + return trustAllCerts; + } + + /** + * Determines if SMTP-sampler should trust all certificates, no matter what + * CA - to be called by SmtpSampler-object + * + * @param trustAllCerts + * Should all certificates be trusted? + */ + public void setTrustAllCerts(boolean trustAllCerts) { + this.trustAllCerts = trustAllCerts; + } + + /** + * Instructs object to enforce StartTLS and not to fallback to plain + * SMTP-connection - to be called by SmtpSampler-object + * + * @param enforceStartTLS + * Should StartTLS be enforced? + */ + public void setEnforceStartTLS(boolean enforceStartTLS) { + this.enforceStartTLS = enforceStartTLS; + } + + /** + * Returns if StartTLS is enforced to secure the connection, i.e. no + * fallback is used (plain SMTP) - standard getter + * + * @return True if StartTLS is enforced + */ + public boolean isEnforceStartTLS() { + return enforceStartTLS; + } + + /** + * Returns headers for current message - standard getter + * + * @return CollectionProperty of headers for current message + */ + public CollectionProperty getHeaders() { + return headerFields; + } + + /** + * Sets headers for current message + * + * @param headerFields + * CollectionProperty of headers for current message + */ + public void setHeaderFields(CollectionProperty headerFields) { + this.headerFields = headerFields; + } + + /** + * Adds a header-part to current HashMap of headers - to be called by + * SmtpSampler-object + * + * @param headerName + * Key for current header + * @param headerValue + * Value for current header + */ + public void addHeader(String headerName, String headerValue) { + if (this.headerFields == null){ + this.headerFields = new CollectionProperty(); + } + Argument argument = new Argument(headerName, headerValue); + this.headerFields.addItem(argument); + } + + /** + * Deletes all current headers in HashMap + */ + public void clearHeaders() { + if (this.headerFields == null){ + this.headerFields = new CollectionProperty(); + }else{ + this.headerFields.clear(); + } + } + + /** + * Returns all attachment for current message - standard getter + * + * @return List of attachments for current message + */ + public List getAttachments() { + return attachments; + } + + /** + * Adds attachments to current message + * + * @param attachments + * List of files to be added as attachments to current message + */ + public void setAttachments(List attachments) { + this.attachments = attachments; + } + + /** + * Adds an attachment to current message - to be called by + * SmtpSampler-object + * + * @param attachment + * File-object to be added as attachment to current message + */ + public void addAttachment(File attachment) { + this.attachments.add(attachment); + } + + /** + * Clear all attachments for current message + */ + public void clearAttachments() { + this.attachments.clear(); + } + + /** + * Returns if synchronous-mode is used for current message (i.e. time for + * delivery, ... is measured) - standard getter + * + * @return True if synchronous-mode is used + */ + public boolean isSynchronousMode() { + return synchronousMode; + } + + /** + * Sets the use of synchronous-mode (i.e. time for delivery, ... is + * measured) - to be called by SmtpSampler-object + * + * @param synchronousMode + * Should synchronous-mode be used? + */ + public void setSynchronousMode(boolean synchronousMode) { + this.synchronousMode = synchronousMode; + } + + /** + * Returns which protocol should be used to transport message (smtps for + * SSL-secured connections or smtp for plain SMTP / StartTLS) + * + * @return Protocol that is used to transport message + */ + private String getProtocol() { + return (useSSL) ? "smtps" : "smtp"; + } + + /** + * Returns port to be used for SMTP-connection - returns the + * default port for the protocol if no port has been supplied. + * + * @return Port to be used for SMTP-connection + */ + private String getPort() { + String port = smtpPort.trim(); + if (port.length() > 0) { // OK, it has been supplied + return port; + } + if (useSSL){ + return "465"; + } + if (useStartTLS) { + return "587"; + } + return "25"; + } + + /** + * Assigns the object to use a local truststore for SSL / StartTLS - to be + * called by SmtpSampler-object + * + * @param useLocalTrustStore + * Should a local truststore be used? + */ + public void setUseLocalTrustStore(boolean useLocalTrustStore) { + this.useLocalTrustStore = useLocalTrustStore; + } + + /** + * Sets the path to the local truststore to be used for SSL / StartTLS - to + * be called by SmtpSampler-object + * + * @param trustStoreToUse + * Path to local truststore + */ + public void setTrustStoreToUse(String trustStoreToUse) { + this.trustStoreToUse = trustStoreToUse; + } + + public void setUseEmlMessage(boolean sendEmlMessage) { + this.sendEmlMessage = sendEmlMessage; + } + + /** + * Sets eml-message to be sent + * + * @param emlMessage + * path to eml-message + */ + public void setEmlMessage(String emlMessage) { + this.emlMessage = emlMessage; + } + + /** + * Set the mail body. + * + * @param body + */ + public void setMailBody(String body){ + mailBody = body; + } + + /** + * Set whether to send a plain body (i.e. not multipart/mixed) + * + * @param plainBody true if sending a plain body (i.e. not multipart/mixed) + */ + public void setPlainBody(boolean plainBody){ + this.plainBody = plainBody; + } + + public String getServerResponse() { + return this.serverResponse.toString(); + } + + public void setEnableDebug(boolean selected) { + enableDebug = selected; + + } + + public void setReplyTo(List replyTo) { + this.replyTo = replyTo; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/SynchronousTransportListener.java b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/SynchronousTransportListener.java new file mode 100644 index 0000000..b0d5c16 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/SynchronousTransportListener.java @@ -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. + * + */ + +package org.apache.jmeter.protocol.smtp.sampler.protocol; + +import javax.mail.event.TransportAdapter; +import javax.mail.event.TransportEvent; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; // this comes out of logkit.jar and not + +// commons-logger + +/** + * This class implements a listener for SMTP events and a monitor for all + * threads sending mail. The main purpose is to synchronize the send action with + * the end of communication with remote smtp server, so that sending time can be + * measured. + */ +public class SynchronousTransportListener extends TransportAdapter { + + private static final Logger logger = LoggingManager.getLoggerForClass(); + + private boolean finished = false; + + private final Object LOCK = new Object(); + + /** + * Creates a new instance of SynchronousTransportListener + */ + public SynchronousTransportListener() { + } + + /** + * {@inheritDoc} + */ + @Override + public void messageDelivered(TransportEvent e) { + logger.debug("Message delivered"); + finish(); + } + + /** + * {@inheritDoc} + */ + @Override + public void messageNotDelivered(TransportEvent e) { + logger.debug("Message not delivered"); + finish(); + } + + /** + * {@inheritDoc} + */ + @Override + public void messagePartiallyDelivered(TransportEvent e) { + logger.debug("Message partially delivered"); + finish(); + } + + /** + * Synchronized-method + * + * @throws InterruptedException + */ + public void attend() throws InterruptedException { + synchronized(LOCK) { + while (!finished) { + LOCK.wait(); + } + } + } + + /** + * Synchronized-method + */ + public void finish() { + finished = true; + synchronized(LOCK) { + LOCK.notify(); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/TrustAllSSLSocketFactory.java b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/TrustAllSSLSocketFactory.java new file mode 100644 index 0000000..e1061ee --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/protocol/TrustAllSSLSocketFactory.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.smtp.sampler.protocol; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +/** + * This class can be used as a SocketFactory with SSL-connections. + * Its purpose is to ensure that all certificates - no matter from which CA - are accepted to secure the SSL-connection. + */ +public class TrustAllSSLSocketFactory extends SSLSocketFactory { + + private final SSLSocketFactory factory; + + /** + * Standard constructor + */ + public TrustAllSSLSocketFactory(){ + SSLContext sslcontext = null; + try { + sslcontext = SSLContext.getInstance("TLS"); // $NON-NLS-1$ + sslcontext.init( null, new TrustManager[]{ + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + } + }, + new java.security.SecureRandom()); + } catch (Exception e) { + throw new RuntimeException("Could not create the SSL context",e); + } + factory = sslcontext.getSocketFactory(); + } + + /** + * Factory method + * @return New TrustAllSSLSocketFactory + */ + public static SocketFactory getDefault() { + return new TrustAllSSLSocketFactory(); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( Socket socket, String s, int i, boolean + flag) + throws IOException { + return factory.createSocket( socket, s, i, flag); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( InetAddress inaddr, int i, + InetAddress inaddr1, int j) throws IOException { + return factory.createSocket( inaddr, i, inaddr1, j); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( InetAddress inaddr, int i) throws + IOException { + return factory.createSocket( inaddr, i); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( String s, int i, InetAddress inaddr, int j) + throws IOException { + return factory.createSocket( s, i, inaddr, j); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket( String s, int i) throws IOException { + return factory.createSocket( s, i); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket() throws IOException { + return factory.createSocket(); + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getDefaultCipherSuites() { + return factory.getSupportedCipherSuites(); + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getSupportedCipherSuites() { + return factory.getSupportedCipherSuites(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/tools/CounterOutputStream.java b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/tools/CounterOutputStream.java new file mode 100644 index 0000000..34ffa3f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/smtp/sampler/tools/CounterOutputStream.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.smtp.sampler.tools; + +import java.io.OutputStream; + +/** + * Utility-class to calculate message size. + */ +public class CounterOutputStream extends OutputStream { + int count = 0; + + /** + * {@inheritDoc} + */ + @Override + + public void close() {} + /** + * {@inheritDoc} + */ + @Override + public void flush() {} + + /** + * {@inheritDoc} + */ + @Override + public void write(byte[] b, int off, int len) { + count += len; + } + + /** + * {@inheritDoc} + */ + @Override + public void write(int b) { + count++; + } + + /** + * Returns message size + * @return Message size + */ + public int getCount() { + return count; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/system/NativeCommand.java b/ApacheJmeter/src/org/apache/jmeter/protocol/system/NativeCommand.java new file mode 100644 index 0000000..f3a1288 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/system/NativeCommand.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.system; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +//tms import ch.ethz.ssh2.StreamGobbler; + + +/** + * Native Command + */ +public class NativeCommand { + + private StreamGobbler outputGobbler; + private final File directory; + private final Map env; + private Map executionEnvironment; + + /** + * @param env Environment variables appended to environment + * @param directory File working directory + */ + public NativeCommand(File directory, Map env) { + super(); + this.directory = directory; + this.env = env; + } + + /** + * @param arguments List + * @return return code + * @throws InterruptedException + * @throws IOException + */ + public int run(List arguments) throws InterruptedException, IOException { + Process proc = null; + try + { + ProcessBuilder procBuild = new ProcessBuilder(arguments); + procBuild.environment().putAll(env); + this.executionEnvironment = Collections.unmodifiableMap(procBuild.environment()); + procBuild.directory(directory); + procBuild.redirectErrorStream(true); + proc = procBuild.start(); + this.outputGobbler = new + StreamGobbler(proc.getInputStream()); + outputGobbler.start(); + + int exitVal = proc.waitFor(); + + outputGobbler.join(); + return exitVal; + } + finally + { + if(proc != null) + { + try { + proc.destroy(); + } catch (Exception ignored) { + // Ignored + } + } + } + } + + /** + * @return Out/Err stream contents + */ + public String getOutResult() { + if(outputGobbler != null) { + return null; //tms outputGobbler.getResult(); + } else { + return ""; + } + } + + /** + * @return the executionEnvironment + */ + public Map getExecutionEnvironment() { + return executionEnvironment; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/system/StreamGobbler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/system/StreamGobbler.java new file mode 100644 index 0000000..72a7311 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/system/StreamGobbler.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.system; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Thread that eats Output and Error Stream to avoid Deadlock on Windows Machines + * Inspired from: + * http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html + */ +class StreamGobbler extends Thread { + private final InputStream is; + private final StringBuilder buffer = new StringBuilder(); + /** + * @param is {@link InputStream} + */ + StreamGobbler(InputStream is) { + this.is = is; + } + + /** + * @see java.lang.Thread#run() + */ + @Override + public void run() { + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(is)); + String line = null; + while ((line = br.readLine()) != null) + { + buffer.append(line); + buffer.append("\r\n"); + } + } catch (IOException e) { + buffer.append(e.getMessage()); + } + finally + { + JOrphanUtils.closeQuietly(br); + } + } + + /** + * @return Output + */ + public String getResult() + { + return buffer.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/system/SystemSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/system/SystemSampler.java new file mode 100644 index 0000000..ad15c60 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/system/SystemSampler.java @@ -0,0 +1,281 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.system; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * A sampler for executing a System function. + */ +public class SystemSampler extends AbstractSampler { + private static final long serialVersionUID = 1; + + public static final String COMMAND = "SystemSampler.command"; + + public static final String DIRECTORY = "SystemSampler.directory"; + + public static final String ARGUMENTS = "SystemSampler.arguments"; + + public static final String ENVIRONMENT = "SystemSampler.environment"; + + public static final String CHECK_RETURN_CODE = "SystemSampler.checkReturnCode"; + + public static final String EXPECTED_RETURN_CODE = "SystemSampler.expectedReturnCode"; + + /** + * Logging + */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + public static final int DEFAULT_RETURN_CODE = 0; + + + /** + * Create a SystemSampler. + */ + public SystemSampler() { + super(); + } + + /** + * Performs a test sample. + * + * @param entry + * the Entry for this sample + * @return test SampleResult + */ + public SampleResult sample(Entry entry) { + SampleResult results = new SampleResult(); + results.setDataType(SampleResult.TEXT); + + try { + String command = getCommand(); + Arguments args = getArguments(); + Arguments environment = getEnvironmentVariables(); + boolean checkReturnCode = getCheckReturnCode(); + int expectedReturnCode = getExpectedReturnCode(); + + List cmds = new ArrayList(args.getArgumentCount()+1); + StringBuilder cmdLine = new StringBuilder((null == command) ? "" : command); + cmds.add(command); + for (int i=0;i env = new HashMap(); + for (int i=0;i= Byte.MIN_VALUE && eolInt <= Byte.MAX_VALUE) { + this.eolByte = (byte) eolInt; + useEolByte = true; + } else { + useEolByte = false; + } + } + + /** + * {@inheritDoc} + */ + public void setupTest() { + } + + /** + * {@inheritDoc} + */ + public void teardownTest() { + } + + /** + * @return the charset + */ + public String getCharset() { + return charset; + } + + /** + * @param charset the charset to set + */ + public void setCharset(String charset) { + this.charset = charset; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImpl.java b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImpl.java new file mode 100644 index 0000000..d63d29b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/BinaryTCPClientImpl.java @@ -0,0 +1,141 @@ +/* + * 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. + * + */ + +/* + * TCP Sampler Client implementation which reads and writes binary data. + * + * Input/Output strings are passed as hex-encoded binary strings. + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * TCPClient implementation. + * Reads data until the defined EOM byte is reached. + * If there is no EOM byte defined, then reads until + * the end of the stream is reached. + * The EOM byte is defined by the property "tcp.BinaryTCPClient.eomByte". + * + * Input data is assumed to be in hex, and is converted to binary + */ +public class BinaryTCPClientImpl extends AbstractTCPClient { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int eomInt = JMeterUtils.getPropDefault("tcp.BinaryTCPClient.eomByte", 1000); // $NON_NLS-1$ + + public BinaryTCPClientImpl() { + super(); + setEolByte(eomInt); + if (useEolByte) { + log.info("Using eomByte=" + eolByte); + } + } + + /** + * Convert hex string to binary byte array. + * + * @param hexEncodedBinary - hex-encoded binary string + * @return Byte array containing binary representation of input hex-encoded string + * @throws IllegalArgumentException if string is not an even number of hex digits + */ + public static final byte[] hexStringToByteArray(String hexEncodedBinary) { + if (hexEncodedBinary.length() % 2 == 0) { + char[] sc = hexEncodedBinary.toCharArray(); + byte[] ba = new byte[sc.length / 2]; + + for (int i = 0; i < ba.length; i++) { + int nibble0 = Character.digit(sc[i * 2], 16); + int nibble1 = Character.digit(sc[i * 2 + 1], 16); + if (nibble0 == -1 || nibble1 == -1){ + throw new IllegalArgumentException( + "Hex-encoded binary string contains an invalid hex digit in '"+sc[i * 2]+sc[i * 2 + 1]+"'"); + } + ba[i] = (byte) ((nibble0 << 4) | (nibble1)); + } + + return ba; + } else { + throw new IllegalArgumentException( + "Hex-encoded binary string contains an uneven no. of digits"); + } + } + + /** + * Input (hex) string is converted to binary and written to the output stream. + * @param os output stream + * @param hexEncodedBinary hex-encoded binary + */ + public void write(OutputStream os, String hexEncodedBinary) throws IOException{ + os.write(hexStringToByteArray(hexEncodedBinary)); + os.flush(); + if(log.isDebugEnabled()) { + log.debug("Wrote: " + hexEncodedBinary); + } + } + + /** + * {@inheritDoc} + */ + public void write(OutputStream os, InputStream is) { + throw new UnsupportedOperationException( + "Method not supported for Length-Prefixed data."); + } + + /** + * Reads data until the defined EOM byte is reached. + * If there is no EOM byte defined, then reads until + * the end of the stream is reached. + * Response data is converted to hex-encoded binary + * @return hex-encoded binary string + * @throws ReadException + */ + public String read(InputStream is) throws ReadException { + ByteArrayOutputStream w = new ByteArrayOutputStream(); + try { + byte[] buffer = new byte[4096]; + int x = 0; + while ((x = is.read(buffer)) > -1) { + w.write(buffer, 0, x); + if (useEolByte && (buffer[x - 1] == eolByte)) { + break; + } + } + + IOUtils.closeQuietly(w); // For completeness + final String hexString = JOrphanUtils.baToHexString(w.toByteArray()); + if(log.isDebugEnabled()) { + log.debug("Read: " + w.size() + "\n" + hexString); + } + return hexString; + } catch (IOException e) { + throw new ReadException("", e, JOrphanUtils.baToHexString(w.toByteArray())); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImpl.java b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImpl.java new file mode 100644 index 0000000..64d8a0e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/LengthPrefixedBinaryTCPClientImpl.java @@ -0,0 +1,118 @@ +/* + * 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. + * + */ + +/* + * TCP Sampler Client implementation which reads and writes length-prefixed binary data. + * + * Input/Output strings are passed as hex-encoded binary strings. + * + * 2-Byte or 4-Byte length prefixes are supported. + * + * Length prefix is binary of length specified by property "tcp.length.prefix.length". + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Implements binary length-prefixed binary data. + * This is used in ISO8583 for example. + */ +public class LengthPrefixedBinaryTCPClientImpl extends TCPClientDecorator { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final int lengthPrefixLen = JMeterUtils.getPropDefault("tcp.binarylength.prefix.length", 2); // $NON-NLS-1$ + + public LengthPrefixedBinaryTCPClientImpl() { + super(new BinaryTCPClientImpl()); + tcpClient.setEolByte(Byte.MAX_VALUE+1); + } + + + /** + * {@inheritDoc} + */ + public void write(OutputStream os, String s) throws IOException{ + os.write(intToByteArray(s.length()/2,lengthPrefixLen)); + if(log.isDebugEnabled()) { + log.debug("Wrote: " + s.length()/2 + " bytes"); + } + this.tcpClient.write(os, s); + } + + /** + * {@inheritDoc} + */ + public void write(OutputStream os, InputStream is) throws IOException { + this.tcpClient.write(os, is); + } + + /** + * {@inheritDoc} + */ + public String read(InputStream is) throws ReadException{ + byte[] msg = new byte[0]; + int msgLen = 0; + byte[] lengthBuffer = new byte[lengthPrefixLen]; + try { + if (is.read(lengthBuffer, 0, lengthPrefixLen) == lengthPrefixLen) { + msgLen = byteArrayToInt(lengthBuffer); + msg = new byte[msgLen]; + int bytes = JOrphanUtils.read(is, msg, 0, msgLen); + if (bytes < msgLen) { + log.warn("Incomplete message read, expected: "+msgLen+" got: "+bytes); + } + } + + String buffer = JOrphanUtils.baToHexString(msg); + if(log.isDebugEnabled()) { + log.debug("Read: " + msgLen + "\n" + buffer); + } + return buffer; + } + catch(IOException e) { + throw new ReadException("", e, JOrphanUtils.baToHexString(msg)); + } + } + + /** + * Not useful, as the byte is never used. + *

+ * {@inheritDoc} + */ + @Override + public byte getEolByte() { + return tcpClient.getEolByte(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setEolByte(int eolInt) { + throw new UnsupportedOperationException("Cannot set eomByte for prefixed messages"); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/ReadException.java b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/ReadException.java new file mode 100644 index 0000000..ac273a5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/ReadException.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +/** + * Exception that contains partial response (Text read until exception occured) + */ +public class ReadException extends Exception { + + private static final long serialVersionUID = -2770054697780959330L; + private final String partialResponse; + + /** + * @deprecated For use by test code only (serialisation tests) + */ + @Deprecated + public ReadException() { + this(null, null, null); + } + + /** + * Constructor + * @param message Message + * @param cause Source cause + * @param partialResponse Text read until error occured + */ + public ReadException(String message, Throwable cause, String partialResponse) { + super(message, cause); + this.partialResponse = partialResponse; + } + + /** + * @return the partialResponse Text read until error occured + */ + public String getPartialResponse() { + return partialResponse; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClient.java b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClient.java new file mode 100644 index 0000000..d304f31 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClient.java @@ -0,0 +1,94 @@ +/* + * 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. + * + */ + +/* + * Created on 24-Sep-2003 + * + * Interface for generic TCP protocol handler + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Interface required by TCPSampler for TCPClient implementations. + */ +public interface TCPClient { + + /** + * Versions of JMeter after 2.3.2 invoke this method when the thread starts. + */ + void setupTest(); + + /** + * Versions of JMeter after 2.3.2 invoke this method when the thread ends. + */ + void teardownTest(); + + /** + * + * @param os - + * OutputStream for socket + * @param is - + * InputStream to be written to Socket + */ + void write(OutputStream os, InputStream is) throws IOException; + + /** + * + * @param os - + * OutputStream for socket + * @param s - + * String to write + */ + void write(OutputStream os, String s) throws IOException; + + /** + * + * @param is - + * InputStream for socket + * @return String read from socket + * @throws ReadException exception that can contain partial response (Response until error occured) + */ + String read(InputStream is) throws ReadException; + + /** + * Get the end-of-line/end-of-message byte. + * @return Returns the eolByte. + */ + public byte getEolByte(); + + + /** + * Get the charset. + * @return Returns the charset. + */ + public String getCharset(); + + /** + * Set the end-of-line/end-of-message byte. + * If the value is out of range of a byte, then it is to be ignored. + * + * @param eolInt + * The value to set + */ + public void setEolByte(int eolInt); +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecorator.java b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecorator.java new file mode 100644 index 0000000..785fc89 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClientDecorator.java @@ -0,0 +1,85 @@ +/* + * 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. + * + */ + +/* + * TCP Sampler Client decorator to permit wrapping base client implementations with length prefixes. + * For example, character data or binary data with character length or binary length + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +public abstract class TCPClientDecorator extends AbstractTCPClient { + + protected final TCPClient tcpClient; // the data implementation + + public TCPClientDecorator(TCPClient tcpClient) { + this.tcpClient = tcpClient; + } + + /** + * Convert int to byte array. + * + * @param value + * - int to be converted + * @param len + * - length of required byte array + * @return Byte array representation of input value + * @throws IllegalArgumentException if not length 2 or 4 or outside range of a short int. + */ + public static byte[] intToByteArray(int value, int len) { + if (len == 2 || len == 4) { + if (len == 2 && (value < Short.MIN_VALUE || value > Short.MAX_VALUE)) { + throw new IllegalArgumentException("Value outside range for signed short int."); + } else { + byte[] b = new byte[len]; + for (int i = 0; i < len; i++) { + int offset = (b.length - 1 - i) * 8; + b[i] = (byte) ((value >>> offset) & 0xFF); + } + return b; + } + } else { + throw new IllegalArgumentException( + "Length must be specified as either 2 or 4."); + } + } + + /** + * Convert byte array to int. + * + * @param b + * - Byte array to be converted + * @return Integer value of input byte array + * @throws IllegalArgumentException if ba is null or not length 2 or 4 + */ + public static int byteArrayToInt(byte[] b) { + if (b != null && (b.length == 2 || b.length == 4)) { + // Preserve sign on first byte + int value = b[0] << ((b.length - 1) * 8); + + for (int i = 1; i < b.length; i++) { + int offset = (b.length - 1 - i) * 8; + value += (b[i] & 0xFF) << offset; + } + return value; + } else { + throw new IllegalArgumentException( + "Byte array is null or invalid length."); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClientImpl.java b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClientImpl.java new file mode 100644 index 0000000..9889ae5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPClientImpl.java @@ -0,0 +1,117 @@ +/* + * 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. + * + */ + +/* + * Basic TCP Sampler Client class + * + * Can be used to test the TCP Sampler against an HTTP server + * + * The protocol handler class name is defined by the property tcp.handler + * + */ +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Sample TCPClient implementation. + * Reads data until the defined EOL byte is reached. + * If there is no EOL byte defined, then reads until + * the end of the stream is reached. + * The EOL byte is defined by the property "tcp.eolByte". + */ +public class TCPClientImpl extends AbstractTCPClient { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private int eolInt = JMeterUtils.getPropDefault("tcp.eolByte", 1000); // $NON-NLS-1$ + private String charset = JMeterUtils.getPropDefault("tcp.charset", Charset.defaultCharset().name()); // $NON-NLS-1$ + // default is not in range of a byte + + public TCPClientImpl() { + super(); + setEolByte(eolInt); + if (useEolByte) { + log.info("Using eolByte=" + eolByte); + } + setCharset(charset); + String configuredCharset = JMeterUtils.getProperty("tcp.charset"); + if(StringUtils.isEmpty(configuredCharset)) { + log.info("Using platform default charset:"+charset); + } else { + log.info("Using charset:"+configuredCharset); + } + } + + /** + * {@inheritDoc} + */ + public void write(OutputStream os, String s) throws IOException{ + os.write(s.getBytes(charset)); + os.flush(); + if(log.isDebugEnabled()) { + log.debug("Wrote: " + s); + } + } + + /** + * {@inheritDoc} + */ + public void write(OutputStream os, InputStream is) throws IOException{ + byte buff[]=new byte[512]; + while(is.read(buff) > 0){ + os.write(buff); + os.flush(); + } + } + + /** + * Reads data until the defined EOL byte is reached. + * If there is no EOL byte defined, then reads until + * the end of the stream is reached. + */ + public String read(InputStream is) throws ReadException{ + ByteArrayOutputStream w = new ByteArrayOutputStream(); + try { + byte[] buffer = new byte[4096]; + int x = 0; + while ((x = is.read(buffer)) > -1) { + w.write(buffer, 0, x); + if (useEolByte && (buffer[x - 1] == eolByte)) { + break; + } + } + + // do we need to close byte array (or flush it?) + if(log.isDebugEnabled()) { + log.debug("Read: " + w.size() + "\n" + w.toString()); + } + return w.toString(charset); + } catch (IOException e) { + throw new ReadException("", e, w.toString()); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPSampler.java b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPSampler.java new file mode 100644 index 0000000..be7811a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/protocol/tcp/sampler/TCPSampler.java @@ -0,0 +1,511 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.protocol.tcp.sampler; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * A sampler which understands Tcp requests. + * + */ +public class TCPSampler extends AbstractSampler implements ThreadListener { + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.LoginConfigGui", + "org.apache.jmeter.protocol.tcp.config.gui.TCPConfigGui", + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + //++ JMX file constants - do not change + public static final String SERVER = "TCPSampler.server"; //$NON-NLS-1$ + + public static final String PORT = "TCPSampler.port"; //$NON-NLS-1$ + + public static final String FILENAME = "TCPSampler.filename"; //$NON-NLS-1$ + + public static final String CLASSNAME = "TCPSampler.classname";//$NON-NLS-1$ + + public static final String NODELAY = "TCPSampler.nodelay"; //$NON-NLS-1$ + + public static final String TIMEOUT = "TCPSampler.timeout"; //$NON-NLS-1$ + + public static final String TIMEOUT_CONNECT = "TCPSampler.ctimeout"; //$NON-NLS-1$ + + public static final String REQUEST = "TCPSampler.request"; //$NON-NLS-1$ + + public static final String RE_USE_CONNECTION = "TCPSampler.reUseConnection"; //$NON-NLS-1$ + //-- JMX file constants - do not change + + private static final String TCPKEY = "TCP"; //$NON-NLS-1$ key for HashMap + + private static final String ERRKEY = "ERR"; //$NON-NLS-1$ key for HashMap + + // If set, this is the regex that is used to extract the status from the + // response + // NOT implemented yet private static final String STATUS_REGEX = + // JMeterUtils.getPropDefault("tcp.status.regex",""); + + // Otherwise, the response is scanned for these strings + private static final String STATUS_PREFIX = JMeterUtils.getPropDefault("tcp.status.prefix", ""); //$NON-NLS-1$ + + private static final String STATUS_SUFFIX = JMeterUtils.getPropDefault("tcp.status.suffix", ""); //$NON-NLS-1$ + + private static final String STATUS_PROPERTIES = JMeterUtils.getPropDefault("tcp.status.properties", ""); //$NON-NLS-1$ + + private static final Properties statusProps = new Properties(); + + private static final boolean haveStatusProps; + + static { + boolean hsp = false; + log.debug("Status prefix=" + STATUS_PREFIX); //$NON-NLS-1$ + log.debug("Status suffix=" + STATUS_SUFFIX); //$NON-NLS-1$ + log.debug("Status properties=" + STATUS_PROPERTIES); //$NON-NLS-1$ + if (STATUS_PROPERTIES.length() > 0) { + File f = new File(STATUS_PROPERTIES); + FileInputStream fis = null; + try { + fis = new FileInputStream(f); + statusProps.load(fis); + log.debug("Successfully loaded properties"); //$NON-NLS-1$ + hsp = true; + } catch (FileNotFoundException e) { + log.debug("Property file not found"); //$NON-NLS-1$ + } catch (IOException e) { + log.debug("Property file error " + e.toString()); //$NON-NLS-1$ + } finally { + JOrphanUtils.closeQuietly(fis); + } + } + haveStatusProps = hsp; + } + + /** the cache of TCP Connections */ + // KEY = TCPKEY or ERRKEY, Entry= Socket or String + private static final ThreadLocal> tp = + new ThreadLocal>() { + @Override + protected Map initialValue() { + return new HashMap(); + } + }; + + private transient TCPClient protocolHandler; + + private transient boolean firstSample; // Are we processing the first sample? + + public TCPSampler() { + log.debug("Created " + this); //$NON-NLS-1$ + } + + private String getError() { + Map cp = tp.get(); + return (String) cp.get(ERRKEY); + } + + private Socket getSocket(String socketKey) { + Map cp = tp.get(); + Socket con = null; + if (isReUseConnection()) { + con = (Socket) cp.get(socketKey); + if (con != null) { + log.debug(this + " Reusing connection " + con); //$NON-NLS-1$ + } + } + if (con == null) { + // Not in cache, so create new one and cache it + try { + closeSocket(socketKey); // Bug 44910 - close previous socket (if any) + SocketAddress sockaddr = new InetSocketAddress(getServer(), getPort()); + con = new Socket(); + con.connect(sockaddr, getConnectTimeout()); + if(log.isDebugEnabled()) { + log.debug("Created new connection " + con); //$NON-NLS-1$ + } + cp.put(socketKey, con); + } catch (UnknownHostException e) { + log.warn("Unknown host for " + getLabel(), e);//$NON-NLS-1$ + cp.put(ERRKEY, e.toString()); + return null; + } catch (IOException e) { + log.warn("Could not create socket for " + getLabel(), e); //$NON-NLS-1$ + cp.put(ERRKEY, e.toString()); + return null; + } + } + // (re-)Define connection params - Bug 50977 + try { + con.setSoTimeout(getTimeout()); + con.setTcpNoDelay(getNoDelay()); + if(log.isDebugEnabled()) { + log.debug(this + " Timeout " + getTimeout() + " NoDelay " + getNoDelay()); //$NON-NLS-1$ + } + } catch (SocketException se) { + log.warn("Could not set timeout or nodelay for " + getLabel(), se); //$NON-NLS-1$ + cp.put(ERRKEY, se.toString()); + } + return con; + } + + /** + * @return String socket key in cache Map + */ + private final String getSocketKey() { + return TCPKEY+"#"+getServer()+"#"+getPort()+"#"+getUsername()+"#"+getPassword(); + } + + public String getUsername() { + return getPropertyAsString(ConfigTestElement.USERNAME); + } + + public String getPassword() { + return getPropertyAsString(ConfigTestElement.PASSWORD); + } + + public void setServer(String newServer) { + this.setProperty(SERVER, newServer); + } + + public String getServer() { + return getPropertyAsString(SERVER); + } + + public void setReUseConnection(String reuse) { + this.setProperty(RE_USE_CONNECTION, reuse); + } + + public boolean isReUseConnection() { + return getPropertyAsBoolean(RE_USE_CONNECTION); + } + + public void setPort(String newFilename) { + this.setProperty(PORT, newFilename); + } + + public int getPort() { + return getPropertyAsInt(PORT); + } + + public void setFilename(String newFilename) { + this.setProperty(FILENAME, newFilename); + } + + public String getFilename() { + return getPropertyAsString(FILENAME); + } + + public void setRequestData(String newRequestData) { + this.setProperty(REQUEST, newRequestData); + } + + public String getRequestData() { + return getPropertyAsString(REQUEST); + } + + public void setTimeout(String newTimeout) { + this.setProperty(TIMEOUT, newTimeout); + } + + public int getTimeout() { + return getPropertyAsInt(TIMEOUT); + } + + public void setConnectTimeout(String newTimeout) { + this.setProperty(TIMEOUT_CONNECT, newTimeout, ""); + } + + public int getConnectTimeout() { + return getPropertyAsInt(TIMEOUT_CONNECT, 0); + } + + public void setNoDelay(String newNoDelay) { + this.setProperty(NODELAY, newNoDelay); + } + + public boolean getNoDelay() { + return getPropertyAsBoolean(NODELAY); + } + + public void setClassname(String classname) { + this.setProperty(CLASSNAME, classname, ""); //$NON-NLS-1$ + } + + public String getClassname() { + String clazz = getPropertyAsString(CLASSNAME,""); + if (clazz==null || clazz.length()==0){ + clazz = JMeterUtils.getPropDefault("tcp.handler", "TCPClientImpl"); //$NON-NLS-1$ $NON-NLS-2$ + } + return clazz; + } + + /** + * Returns a formatted string label describing this sampler Example output: + * Tcp://Tcp.nowhere.com/pub/README.txt + * + * @return a formatted string label describing this sampler + */ + public String getLabel() { + return ("tcp://" + this.getServer() + ":" + this.getPort());//$NON-NLS-1$ $NON-NLS-2$ + } + + private static final String protoPrefix = "org.apache.jmeter.protocol.tcp.sampler."; //$NON-NLS-1$ + + private Class getClass(String className) { + Class c = null; + try { + c = Class.forName(className, false, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e) { + try { + c = Class.forName(protoPrefix + className, false, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e1) { + log.error("Could not find protocol class '" + className+"'"); //$NON-NLS-1$ + } + } + return c; + + } + + private TCPClient getProtocol() { + TCPClient TCPClient = null; + Class javaClass = getClass(getClassname()); + if (javaClass == null){ + return null; + } + try { + TCPClient = (TCPClient) javaClass.newInstance(); + if (log.isDebugEnabled()) { + log.debug(this + "Created: " + getClassname() + "@" + Integer.toHexString(TCPClient.hashCode())); //$NON-NLS-1$ + } + } catch (Exception e) { + log.error(this + " Exception creating: " + getClassname(), e); //$NON-NLS-1$ + } + return TCPClient; + } + + public SampleResult sample(Entry e)// Entry tends to be ignored ... + { + if (firstSample) { // Do stuff we cannot do as part of threadStarted() + initSampling(); + firstSample=false; + } + String socketKey = getSocketKey(); + log.debug(getLabel() + " " + getFilename() + " " + getUsername() + " " + getPassword()); + SampleResult res = new SampleResult(); + boolean isSuccessful = false; + res.setSampleLabel(getName());// Use the test element name for the label + res.setSamplerData("Host: " + getServer() + " Port: " + getPort()); //$NON-NLS-1$ $NON-NLS-2$ + res.sampleStart(); + try { + Socket sock = getSocket(socketKey); + if (sock == null) { + res.setResponseCode("500"); //$NON-NLS-1$ + res.setResponseMessage(getError()); + } else if (protocolHandler == null){ + res.setResponseCode("500"); //$NON-NLS-1$ + res.setResponseMessage("Protocol handler not found"); + } else { + InputStream is = sock.getInputStream(); + OutputStream os = sock.getOutputStream(); + String req = getRequestData(); + // TODO handle filenames + res.setSamplerData(req); + protocolHandler.write(os, req); + String in = protocolHandler.read(is); + isSuccessful = setupSampleResult(res, in, null, protocolHandler.getCharset()); + } + } catch (ReadException ex) { + log.error("", ex); + isSuccessful=setupSampleResult(res, ex.getPartialResponse(), ex,protocolHandler.getCharset()); + closeSocket(socketKey); + } catch (Exception ex) { + log.error("", ex); + isSuccessful=setupSampleResult(res, "", ex, protocolHandler.getCharset()); + closeSocket(socketKey); + } finally { + // Calculate response time + res.sampleEnd(); + + // Set if we were successful or not + res.setSuccessful(isSuccessful); + + if (!isReUseConnection()) { + closeSocket(socketKey); + } + } + return res; + } + + /** + * Fills SampleResult object + * @param sampleResult {@link SampleResult} + * @param readResponse Response read until error occured + * @param exception Source exception + * @param encoding sample encoding + * @return boolean if sample is considered as successful + */ + private boolean setupSampleResult(SampleResult sampleResult, + String readResponse, + Exception exception, + String encoding) { + sampleResult.setResponseData(readResponse, encoding); + sampleResult.setDataType(SampleResult.TEXT); + if(exception==null) { + sampleResult.setResponseCodeOK(); + sampleResult.setResponseMessage("OK"); //$NON-NLS-1$ + } else { + sampleResult.setResponseCode("500"); //$NON-NLS-1$ + sampleResult.setResponseMessage(exception.toString()); //$NON-NLS-1$ + } + boolean isSuccessful = exception == null; + // Reset the status code if the message contains one + if (!StringUtils.isEmpty(readResponse) && STATUS_PREFIX.length() > 0) { + int i = readResponse.indexOf(STATUS_PREFIX); + int j = readResponse.indexOf(STATUS_SUFFIX, i + STATUS_PREFIX.length()); + if (i != -1 && j > i) { + String rc = readResponse.substring(i + STATUS_PREFIX.length(), j); + sampleResult.setResponseCode(rc); + isSuccessful = isSuccessful && checkResponseCode(rc); + if (haveStatusProps) { + sampleResult.setResponseMessage(statusProps.getProperty(rc, "Status code not found in properties")); //$NON-NLS-1$ + } else { + sampleResult.setResponseMessage("No status property file"); + } + } else { + sampleResult.setResponseCode("999"); //$NON-NLS-1$ + sampleResult.setResponseMessage("Status value not found"); + isSuccessful = false; + } + } + return isSuccessful; + } + + /** + * @param rc response code + * @return whether this represents success or not + */ + private boolean checkResponseCode(String rc) { + if (rc.compareTo("400") >= 0 && rc.compareTo("499") <= 0) { //$NON-NLS-1$ $NON-NLS-2$ + return false; + } + if (rc.compareTo("500") >= 0 && rc.compareTo("599") <= 0) { //$NON-NLS-1$ $NON-NLS-2$ + return false; + } + return true; + } + + public void threadStarted() { + log.debug("Thread Started"); //$NON-NLS-1$ + firstSample = true; + } + + // Cannot do this as part of threadStarted() because the Config elements have not been processed. + private void initSampling(){ + protocolHandler = getProtocol(); + log.debug("Using Protocol Handler: " + //$NON-NLS-1$ + (protocolHandler == null ? "NONE" : protocolHandler.getClass().getName())); //$NON-NLS-1$ + if (protocolHandler != null){ + protocolHandler.setupTest(); + } + } + + /** + * Close socket of current sampler + */ + private void closeSocket(String socketKey) { + Map cp = tp.get(); + Socket con = (Socket) cp.remove(socketKey); + if (con != null) { + log.debug(this + " Closing connection " + con); //$NON-NLS-1$ + try { + con.close(); + } catch (IOException e) { + log.warn("Error closing socket "+e); //$NON-NLS-1$ + } + } + } + + /** + * {@inheritDoc} + */ + public void threadFinished() { + log.debug("Thread Finished"); //$NON-NLS-1$ + tearDown(); + if (protocolHandler != null){ + protocolHandler.teardownTest(); + } + } + + /** + * Closes all connections, clears Map and remove thread local Map + */ + private void tearDown() { + Map cp = tp.get(); + for (Map.Entry element : cp.entrySet()) { + if(element.getKey().startsWith(TCPKEY)) { + try { + ((Socket)element.getValue()).close(); + } catch (IOException e) { + // NOOP + } + } + } + cp.clear(); + tp.remove(); + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/DataSet.java b/ApacheJmeter/src/org/apache/jmeter/report/DataSet.java new file mode 100644 index 0000000..07b82b7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/DataSet.java @@ -0,0 +1,114 @@ +/* + * 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.report; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.apache.jmeter.visualizers.SamplingStatCalculator; +import org.apache.jmeter.visualizers.Visualizer; + +/** + * + * DataSet extends Visualizer so that it can be used with ResultCollector. + * Classes implementing the interface should create a new instance of + * ResultCollector and call setListener(Visualizer) passing itself. + * When the ResultCollector.loadExistingFile is called, it will pass + * the SampleResults. + */ +public interface DataSet extends Visualizer { + + /** + * Depending on the implementation, the datasouce could be a file + * or a RDBMS. It's up to the implementing class to decide. + * @param datasource + */ + public void setDataSource(String datasource); + /** + * Return the datasource. For files, it should be the absolute path. + * For databases, it should be the datasource name created in jmeter. + */ + public String getDataSource(); + /** + * In some cases, we may want to return a string that isn't the full + * datasource string or something different. For example, we may + * want to return just the filename and not the absolutePath of + * a JTL file. + */ + public String getDataSourceName(); + /** + * Set the timestamp using the first result from the datasource + * @param stamp + */ + public void setStartTimestamp(long stamp); + /** + * return the timestamp in millisecond format. + */ + public long getStartTimestamp(); + /** + * Set the timestamp using the last result from the datasource + * @param stamp + */ + public void setEndTimestamp(long stamp); + /** + * return the timestamp in millisecond format. + */ + public long getEndTimestamp(); + /** + * Return the Date object using the start timestamp + */ + public Date getDate(); + /** + * convienance method for getting the date in mmdd format + */ + public String getMonthDayDate(); + /** + * convienant method for getting the date in yyyymmdd format + */ + public String getMonthDayYearDate(); + /** + * Classes implementing the method should return the URL's in the + * DataSet. It is up to the class to return Strings or URL. + */ + public Set getURLs(); + /** + * Classes implementing the method should return instance of + * SamplingStatCalculator. + * @return the set of statistics + */ + public Set getStats(); + /** + * Return the SamplingStatCalculator for a specific URL. + * @param url + */ + public SamplingStatCalculator getStatistics(String url); + /** + * Convenience method for getting all the SamplingStatCalculators for + * a given URL. + * @param urls + */ + public List getStats(@SuppressWarnings("rawtypes") // Method is broken anyway + List urls); + /** + * Classes implementing the method should load the data from + * the target location. It doesn't necessarily have to be a + * file. It could be from a database. + */ + public void loadData(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/ReportChart.java b/ApacheJmeter/src/org/apache/jmeter/report/ReportChart.java new file mode 100644 index 0000000..a43bdb6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/ReportChart.java @@ -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.report; + +import java.util.List; +import javax.swing.JComponent; + + +public interface ReportChart { + /** + * The method takes a list of the DataSet items. It is up to the chart + * class to extract the data and use it to render a graphic. + * @param data list of DataSet + * @return the component + */ + JComponent renderChart(List data); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/ReportTable.java b/ApacheJmeter/src/org/apache/jmeter/report/ReportTable.java new file mode 100644 index 0000000..ec39dac --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/ReportTable.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.report; + +import java.util.List; + +public interface ReportTable { + String[][] getTableData(@SuppressWarnings("rawtypes") + // TODO fix this when there is a real implementation + List data); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/engine/ReportEngine.java b/ApacheJmeter/src/org/apache/jmeter/report/engine/ReportEngine.java new file mode 100644 index 0000000..f5b9781 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/engine/ReportEngine.java @@ -0,0 +1,37 @@ +/* + * 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.report.engine; + +import org.apache.jmeter.engine.JMeterEngineException; +import org.apache.jorphan.collections.HashTree; + +/** + * + * ReportEngine is the base interface for report engines + */ +public interface ReportEngine { + void configure(HashTree testPlan); + + void runReport() throws JMeterEngineException; + + void stopReport(); + + void reset(); + + void exit(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/engine/StandardReportEngine.java b/ApacheJmeter/src/org/apache/jmeter/report/engine/StandardReportEngine.java new file mode 100644 index 0000000..7a7eed9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/engine/StandardReportEngine.java @@ -0,0 +1,74 @@ +/* + * 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.report.engine; + +import java.io.Serializable; + +import org.apache.jmeter.engine.JMeterEngineException; +import org.apache.jorphan.collections.HashTree; + +public class StandardReportEngine implements Runnable, Serializable, + ReportEngine { + + private static final long serialVersionUID = 240L; + + /** + * + */ + public StandardReportEngine() { + super(); + } + + /* (non-Javadoc) + * @see java.lang.Runnable#run() + */ + public void run() { + + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.engine.ReportEngine#configure(org.apache.jorphan.collections.HashTree) + */ + public void configure(HashTree testPlan) { + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.engine.ReportEngine#runReport() + */ + public void runReport() throws JMeterEngineException { + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.engine.ReportEngine#stopReport() + */ + public void stopReport() { + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.engine.ReportEngine#reset() + */ + public void reset() { + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.engine.ReportEngine#exit() + */ + public void exit() { + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/engine/ValueReplacer.java b/ApacheJmeter/src/org/apache/jmeter/report/engine/ValueReplacer.java new file mode 100644 index 0000000..3563511 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/engine/ValueReplacer.java @@ -0,0 +1,148 @@ +/* + * 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.report.engine; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.functions.InvalidVariableException; +import org.apache.jmeter.testelement.ReportPlan; +import org.apache.jmeter.testelement.TestElement; + +public class ValueReplacer { +// private static final Logger log = LoggingManager.getLoggerForClass(); + + private Map variables = new HashMap(); + + public ValueReplacer() { + } + + public ValueReplacer(ReportPlan tp) { + setUserDefinedVariables(tp.getUserDefinedVariables()); + } + + public void setUserDefinedVariables(Map variables) { + this.variables = variables; + } + + /** + * @throws InvalidVariableException not thrown currently + */ + public void replaceValues(TestElement el) throws InvalidVariableException { + /** + Collection newProps = replaceValues(el.propertyIterator(), new ReplaceStringWithFunctions(masterFunction, + variables)); + setProperties(el, newProps); + **/ + } + +// private void setProperties(TestElement el, Collection newProps) { +// Iterator iter = newProps.iterator(); +// el.clear(); +// while (iter.hasNext()) { +// el.setProperty((JMeterProperty) iter.next()); +// } +// } + + /** + * @throws InvalidVariableException not thrown currently + */ + public void reverseReplace(TestElement el) throws InvalidVariableException { + /** + Collection newProps = replaceValues(el.propertyIterator(), new ReplaceFunctionsWithStrings(masterFunction, + variables)); + setProperties(el, newProps); + **/ + } + + /** + * @throws InvalidVariableException not thrown currently + */ + public void reverseReplace(TestElement el, boolean regexMatch) throws InvalidVariableException { + /** + Collection newProps = replaceValues(el.propertyIterator(), new ReplaceFunctionsWithStrings(masterFunction, + variables, regexMatch)); + setProperties(el, newProps); + **/ + } + + /** + * @throws InvalidVariableException not thrown currently + */ + public void undoReverseReplace(TestElement el) throws InvalidVariableException { + /** + Collection newProps = replaceValues(el.propertyIterator(), new UndoVariableReplacement(masterFunction, + variables)); + setProperties(el, newProps); + **/ + } + + public void addVariable(String name, String value) { + variables.put(name, value); + } + + /** + * Add all the given variables to this replacer's variables map. + * + * @param vars + * A map of variable name-value pairs (String-to-String). + */ + public void addVariables(Map vars) { + variables.putAll(vars); + } + + /** + private Collection replaceValues(PropertyIterator iter, ValueTransformer transform) throws InvalidVariableException { + List props = new LinkedList(); + while (iter.hasNext()) { + JMeterProperty val = iter.next(); + if (log.isDebugEnabled()) { + log.debug("About to replace in property of type: " + val.getClass() + ": " + val); + } + if (val instanceof StringProperty) { + // Must not convert TestElement.gui_class etc + if (!val.getName().equals(TestElement.GUI_CLASS) && + !val.getName().equals(TestElement.TEST_CLASS)) { + val = transform.transformValue(val); + if (log.isDebugEnabled()) { + log.debug("Replacement result: " + val); + } + } + } else if (val instanceof MultiProperty) { + MultiProperty multiVal = (MultiProperty) val; + Collection newValues = replaceValues(multiVal.iterator(), transform); + multiVal.clear(); + Iterator propIter = newValues.iterator(); + while (propIter.hasNext()) { + multiVal.addProperty((JMeterProperty) propIter.next()); + } + if (log.isDebugEnabled()) { + log.debug("Replacement result: " + multiVal); + } + } else { + if (log.isDebugEnabled()) { + log.debug("Won't replace " + val); + } + } + props.add(val); + } + return props; + } + **/ +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/AbstractReportGui.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/AbstractReportGui.java new file mode 100644 index 0000000..890bb51 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/AbstractReportGui.java @@ -0,0 +1,114 @@ +/* + * 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.report.gui; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Font; +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.NamePanel; +import org.apache.jmeter.gui.util.ReportMenuFactory; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * + * This is the abstract base for report gui's + */ +public abstract class AbstractReportGui extends AbstractJMeterGuiComponent +{ + private static final long serialVersionUID = 240L; + + /** + * + */ + public AbstractReportGui() { + this.namePanel = new NamePanel(); + this.namePanel.setBackground(Color.white); + setName(getStaticLabel()); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.JMeterGUIComponent#getLabelResource() + */ + public String getLabelResource() { + return "report_page"; + } + + @Override + public void configureTestElement(TestElement element) { + super.configureTestElement(element); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.JMeterGUIComponent#createPopupMenu() + */ + public JPopupMenu createPopupMenu() { + JPopupMenu pop = new JPopupMenu(); + JMenu addMenu = new JMenu(JMeterUtils.getResString("Add")); + addMenu.add(ReportMenuFactory.makeMenu(ReportMenuFactory.CONFIG_ELEMENTS, "Add")); + addMenu.add(ReportMenuFactory.makeMenu(ReportMenuFactory.PRE_PROCESSORS, "Add")); + addMenu.add(ReportMenuFactory.makeMenu(ReportMenuFactory.POST_PROCESSORS, "Add")); + pop.add(addMenu); + ReportMenuFactory.addFileMenu(pop); + return pop; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.JMeterGUIComponent#getMenuCategories() + */ + public Collection getMenuCategories() { + return Arrays.asList(new String[] { ReportMenuFactory.TABLES }); + } + + /** + * This implementaion differs a bit from the normal jmeter gui. it uses + * a white background instead of the default grey. + * @return a panel containing the component title and name panel + */ + @Override + protected Container makeTitlePanel() { + VerticalPanel titlePanel = new VerticalPanel(); + titlePanel.setBackground(Color.white); + titlePanel.add(createTitleLabel()); + titlePanel.add(getNamePanel()); + return titlePanel; + } + + /** + * This implementaion differs a bit from the normal jmeter gui. it uses + * a white background instead of the default grey. + */ + @Override + protected Component createTitleLabel() { + JLabel titleLabel = new JLabel(getStaticLabel()); + Font curFont = titleLabel.getFont(); + titleLabel.setFont(curFont.deriveFont((float) curFont.getSize() + 4)); + titleLabel.setBackground(Color.white); + return titleLabel; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/BarChartGui.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/BarChartGui.java new file mode 100644 index 0000000..2670d3b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/BarChartGui.java @@ -0,0 +1,153 @@ +/* + * 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.report.gui; + +import javax.swing.border.EmptyBorder; +import java.awt.BorderLayout; +import java.awt.Color; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.util.ReportMenuFactory; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.AbstractChart; +import org.apache.jmeter.testelement.AbstractTable; +import org.apache.jmeter.testelement.BarChart; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.JLabeledTextField; + +public class BarChartGui extends AbstractReportGui { + + private static final long serialVersionUID = 240L; + + private JLabeledChoice xAxisLabel = new JLabeledChoice(); + + private JLabeledTextField yAxisLabel = + new JLabeledTextField(JMeterUtils.getResString("report_chart_y_axis_label")); + + private JLabeledTextField caption = + new JLabeledTextField(JMeterUtils.getResString("report_chart_caption"), + Color.white); + private JLabeledTextField url = + new JLabeledTextField(JMeterUtils.getResString("report_bar_graph_url"), + Color.white); + + private JLabeledChoice yItems = new JLabeledChoice(); + private JLabeledChoice xItems = new JLabeledChoice(); + + public BarChartGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "report_bar_chart"; + } + + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu pop = new JPopupMenu(); + ReportMenuFactory.addFileMenu(pop); + ReportMenuFactory.addEditMenu(pop,true); + return pop; + } + + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout(10, 10)); + setBorder(makeBorder()); + setBackground(Color.white); + + JPanel pane = new JPanel(); + pane.setLayout(new BorderLayout(10,10)); + pane.setBackground(Color.white); + pane.add(this.getNamePanel(),BorderLayout.NORTH); + + VerticalPanel options = new VerticalPanel(Color.white); + xAxisLabel.setBackground(Color.white); + yAxisLabel.setBackground(Color.white); + + JLabel xLabel = new JLabel(JMeterUtils.getResString("report_chart_x_axis")); + HorizontalPanel xpanel = new HorizontalPanel(Color.white); + xLabel.setBorder(new EmptyBorder(5,2,5,2)); + xItems.setBackground(Color.white); + xItems.setValues(AbstractTable.xitems); + xpanel.add(xLabel); + xpanel.add(xItems); + options.add(xpanel); + + JLabel xALabel = new JLabel(JMeterUtils.getResString("report_chart_x_axis_label")); + HorizontalPanel xApanel = new HorizontalPanel(Color.white); + xALabel.setBorder(new EmptyBorder(5,2,5,2)); + xAxisLabel.setBackground(Color.white); + xAxisLabel.setValues(AbstractChart.X_LABELS); + xApanel.add(xALabel); + xApanel.add(xAxisLabel); + options.add(xApanel); + + JLabel yLabel = new JLabel(JMeterUtils.getResString("report_chart_y_axis")); + HorizontalPanel ypanel = new HorizontalPanel(Color.white); + yLabel.setBorder(new EmptyBorder(5,2,5,2)); + yItems.setBackground(Color.white); + yItems.setValues(AbstractTable.items); + ypanel.add(yLabel); + ypanel.add(yItems); + options.add(ypanel); + options.add(yAxisLabel); + options.add(caption); + options.add(url); + + add(pane,BorderLayout.NORTH); + add(options,BorderLayout.CENTER); + } + + public TestElement createTestElement() { + BarChart element = new BarChart(); + modifyTestElement(element); + return element; + } + + public void modifyTestElement(TestElement element) { + this.configureTestElement(element); + BarChart bc = (BarChart)element; + bc.setXAxis(xItems.getText()); + bc.setYAxis(yItems.getText()); + bc.setXLabel(xAxisLabel.getText()); + bc.setYLabel(yAxisLabel.getText()); + bc.setCaption(caption.getText()); + bc.setURL(url.getText()); + } + + @Override + public void configure(TestElement element) { + super.configure(element); + BarChart bc = (BarChart)element; + xItems.setText(bc.getXAxis()); + yItems.setText(bc.getYAxis()); + xAxisLabel.setText(bc.getXLabel()); + yAxisLabel.setText(bc.getYLabel()); + caption.setText(bc.getCaption()); + url.setText(bc.getURL()); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/LineGraphGui.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/LineGraphGui.java new file mode 100644 index 0000000..aeebe7e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/LineGraphGui.java @@ -0,0 +1,152 @@ +/* + * 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.report.gui; + +import java.awt.BorderLayout; +import java.awt.Color; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.gui.util.ReportMenuFactory; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.AbstractTable; +import org.apache.jmeter.testelement.AbstractChart; +import org.apache.jmeter.testelement.LineChart; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledChoice; +import org.apache.jorphan.gui.JLabeledTextField; + +public class LineGraphGui extends AbstractReportGui { + + private static final long serialVersionUID = 240L; + + private JLabeledChoice xAxisLabel = new JLabeledChoice(); + + private JLabeledTextField yAxisLabel = + new JLabeledTextField(JMeterUtils.getResString("report_chart_y_axis_label")); + + private JLabeledTextField caption = + new JLabeledTextField(JMeterUtils.getResString("report_chart_caption"), + Color.white); + + private JLabeledTextField urls = + new JLabeledTextField(JMeterUtils.getResString("report_line_graph_urls"), + Color.white); + + private JLabeledChoice yItems = new JLabeledChoice(); + private JLabeledChoice xItems = new JLabeledChoice(); + + public LineGraphGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "report_line_graph"; + } + + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu pop = new JPopupMenu(); + ReportMenuFactory.addFileMenu(pop); + ReportMenuFactory.addEditMenu(pop,true); + return pop; + } + + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout(10, 10)); + setBorder(makeBorder()); + setBackground(Color.white); + + JPanel pane = new JPanel(); + pane.setLayout(new BorderLayout(10,10)); + pane.setBackground(Color.white); + pane.add(this.getNamePanel(),BorderLayout.NORTH); + + VerticalPanel options = new VerticalPanel(Color.white); + yAxisLabel.setBackground(Color.white); + + JLabel xLabel = new JLabel(JMeterUtils.getResString("report_chart_x_axis")); + HorizontalPanel xpanel = new HorizontalPanel(Color.white); + xLabel.setBorder(new EmptyBorder(5,2,5,2)); + xItems.setBackground(Color.white); + xItems.setValues(AbstractTable.xitems); + xpanel.add(xLabel); + xpanel.add(xItems); + options.add(xpanel); + + JLabel xALabel = new JLabel(JMeterUtils.getResString("report_chart_x_axis_label")); + HorizontalPanel xApanel = new HorizontalPanel(Color.white); + xALabel.setBorder(new EmptyBorder(5,2,5,2)); + xAxisLabel.setBackground(Color.white); + xAxisLabel.setValues(AbstractChart.X_LABELS); + xApanel.add(xALabel); + xApanel.add(xAxisLabel); + options.add(xApanel); + + JLabel yLabel = new JLabel(JMeterUtils.getResString("report_chart_y_axis")); + HorizontalPanel ypanel = new HorizontalPanel(Color.white); + yLabel.setBorder(new EmptyBorder(5,2,5,2)); + yItems.setBackground(Color.white); + yItems.setValues(AbstractTable.items); + ypanel.add(yLabel); + ypanel.add(yItems); + options.add(ypanel); + options.add(yAxisLabel); + options.add(caption); + options.add(urls); + + add(pane,BorderLayout.NORTH); + add(options,BorderLayout.CENTER); + } + + public TestElement createTestElement() { + LineChart element = new LineChart(); + modifyTestElement(element); + return element; + } + + public void modifyTestElement(TestElement element) { + this.configureTestElement(element); + LineChart bc = (LineChart)element; + bc.setXAxis(xItems.getText()); + bc.setYAxis(yItems.getText()); + bc.setXLabel(xAxisLabel.getText()); + bc.setYLabel(yAxisLabel.getText()); + bc.setCaption(caption.getText()); + bc.setURLs(urls.getText()); + } + + @Override + public void configure(TestElement element) { + super.configure(element); + LineChart bc = (LineChart)element; + xItems.setText(bc.getXAxis()); + yItems.setText(bc.getYAxis()); + xAxisLabel.setText(bc.getXLabel()); + yAxisLabel.setText(bc.getYLabel()); + caption.setText(bc.getCaption()); + urls.setText(bc.getURLs()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/ReportPageGui.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/ReportPageGui.java new file mode 100644 index 0000000..b030522 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/ReportPageGui.java @@ -0,0 +1,146 @@ +/* + * 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.report.gui; + +import java.awt.BorderLayout; +import java.awt.Color; + +import javax.swing.JCheckBox; +import javax.swing.JMenu; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.util.ReportMenuFactory; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.ReportPage; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.JLabeledTextArea; +import org.apache.jorphan.gui.JLabeledTextField; + +public class ReportPageGui extends AbstractReportGui { + + private static final long serialVersionUID = 240L; + + private JLabeledTextField pageTitle = new JLabeledTextField(JMeterUtils.getResString("report_page_title")); + + private JCheckBox makeIndex = new JCheckBox(JMeterUtils.getResString("report_page_index")); + + private JLabeledTextField cssURL = + new JLabeledTextField(JMeterUtils.getResString("report_page_style_url")); + + private JLabeledTextField headerURL = + new JLabeledTextField(JMeterUtils.getResString("report_page_header")); + + private JLabeledTextField footerURL = + new JLabeledTextField(JMeterUtils.getResString("report_page_footer")); + + private JLabeledTextArea introduction = + new JLabeledTextArea(JMeterUtils.getResString("report_page_intro")); + + /** + * + */ + public ReportPageGui() { + init(); + } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + setLayout(new BorderLayout(10, 10)); + setBorder(makeBorder()); + setBackground(Color.white); + + JPanel pane = new JPanel(); + pane.setLayout(new BorderLayout(10,10)); + pane.setBackground(Color.white); + pane.add(this.getNamePanel(),BorderLayout.NORTH); + + VerticalPanel options = new VerticalPanel(Color.white); + pageTitle.setBackground(Color.white); + makeIndex.setBackground(Color.white); + cssURL.setBackground(Color.white); + headerURL.setBackground(Color.white); + footerURL.setBackground(Color.white); + introduction.setBackground(Color.white); + options.add(pageTitle); + options.add(makeIndex); + options.add(cssURL); + options.add(headerURL); + options.add(footerURL); + options.add(introduction); + add(pane,BorderLayout.NORTH); + add(options,BorderLayout.CENTER); + } + + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu pop = new JPopupMenu(); + JMenu addMenu = new JMenu(JMeterUtils.getResString("Add")); + addMenu.add(ReportMenuFactory.makeMenuItem(new TableGui().getStaticLabel(), + TableGui.class.getName(), + "Add")); + addMenu.add(ReportMenuFactory.makeMenuItem(new BarChartGui().getStaticLabel(), + BarChartGui.class.getName(), + "Add")); + addMenu.add(ReportMenuFactory.makeMenuItem(new LineGraphGui().getStaticLabel(), + LineGraphGui.class.getName(), + "Add")); + pop.add(addMenu); + ReportMenuFactory.addFileMenu(pop); + ReportMenuFactory.addEditMenu(pop,true); + return pop; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + ReportPage element = new ReportPage(); + modifyTestElement(element); + return element; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(org.apache.jmeter.testelement.TestElement) + */ + public void modifyTestElement(TestElement element) { + super.configureTestElement(element); + ReportPage page = (ReportPage)element; + page.setCSS(cssURL.getText()); + page.setFooterURL(footerURL.getText()); + page.setHeaderURL(headerURL.getText()); + page.setIndex(String.valueOf(makeIndex.isSelected())); + page.setIntroduction(introduction.getText()); + page.setTitle(pageTitle.getText()); + } + + @Override + public void configure(TestElement element) { + super.configure(element); + ReportPage page = (ReportPage)element; + cssURL.setText(page.getCSS()); + footerURL.setText(page.getFooterURL()); + headerURL.setText(page.getHeaderURL()); + makeIndex.setSelected(page.getIndex()); + introduction.setText(page.getIntroduction()); + pageTitle.setText(page.getTitle()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/TableGui.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/TableGui.java new file mode 100644 index 0000000..2f674f5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/TableGui.java @@ -0,0 +1,149 @@ +/* + * 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.report.gui; + +import java.awt.BorderLayout; +import java.awt.Color; + +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.util.ReportMenuFactory; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.Table; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class TableGui extends AbstractReportGui implements ChangeListener { + + private static final long serialVersionUID = 240L; + + private JCheckBox meanCheck = new JCheckBox(JMeterUtils.getResString("average")); + private JCheckBox medianCheck = new JCheckBox(JMeterUtils.getResString("graph_results_median")); + private JCheckBox maxCheck = new JCheckBox(JMeterUtils.getResString("aggregate_report_max")); + private JCheckBox minCheck = new JCheckBox(JMeterUtils.getResString("aggregate_report_min")); + private JCheckBox responseRateCheck = + new JCheckBox(JMeterUtils.getResString("aggregate_report_rate")); + private JCheckBox transferRateCheck = + new JCheckBox(JMeterUtils.getResString("aggregate_report_bandwidth")); + private JCheckBox fiftypercentCheck = + new JCheckBox(JMeterUtils.getResString("monitor_label_left_middle")); + private JCheckBox nintypercentCheck = + new JCheckBox(JMeterUtils.getResString("aggregate_report_90")); + private JCheckBox errorRateCheck = + new JCheckBox(JMeterUtils.getResString("aggregate_report_error")); + + public TableGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "report_table"; + } + + /** + * Initialize the components and layout of this component. + */ + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout(10, 10)); + setBorder(makeBorder()); + setBackground(Color.white); + + JPanel pane = new JPanel(); + pane.setLayout(new BorderLayout(10,10)); + pane.setBackground(Color.white); + pane.add(this.getNamePanel(),BorderLayout.NORTH); + + meanCheck.addChangeListener(this); + VerticalPanel options = new VerticalPanel(Color.white); + meanCheck.setBackground(Color.white); + medianCheck.setBackground(Color.white); + maxCheck.setBackground(Color.white); + minCheck.setBackground(Color.white); + responseRateCheck.setBackground(Color.white); + transferRateCheck.setBackground(Color.white); + fiftypercentCheck.setBackground(Color.white); + nintypercentCheck.setBackground(Color.white); + errorRateCheck.setBackground(Color.white); + options.add(meanCheck); + options.add(medianCheck); + options.add(maxCheck); + options.add(minCheck); + options.add(responseRateCheck); + options.add(transferRateCheck); + options.add(fiftypercentCheck); + options.add(nintypercentCheck); + options.add(errorRateCheck); + + add(pane,BorderLayout.NORTH); + add(options,BorderLayout.CENTER); + } + + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu pop = new JPopupMenu(); + ReportMenuFactory.addFileMenu(pop); + ReportMenuFactory.addEditMenu(pop,true); + return pop; + } + + public TestElement createTestElement() { + Table element = new Table(); + modifyTestElement(element); + return element; + } + + public void modifyTestElement(TestElement element) { + this.configureTestElement(element); + Table tb = (Table)element; + tb.set50Percent(String.valueOf(fiftypercentCheck.isSelected())); + tb.set90Percent(String.valueOf(nintypercentCheck.isSelected())); + tb.setErrorRate(String.valueOf(errorRateCheck.isSelected())); + tb.setMax(String.valueOf(maxCheck.isSelected())); + tb.setMean(String.valueOf(meanCheck.isSelected())); + tb.setMedian(String.valueOf(medianCheck.isSelected())); + tb.setMin(String.valueOf(minCheck.isSelected())); + tb.setResponseRate(String.valueOf(responseRateCheck.isSelected())); + tb.setTransferRate(String.valueOf(transferRateCheck.isSelected())); + } + + @Override + public void configure(TestElement element) { + super.configure(element); + Table tb = (Table)element; + meanCheck.setSelected(tb.getMean()); + medianCheck.setSelected(tb.getMedian()); + maxCheck.setSelected(tb.getMax()); + minCheck.setSelected(tb.getMin()); + fiftypercentCheck.setSelected(tb.get50Percent()); + nintypercentCheck.setSelected(tb.get90Percent()); + errorRateCheck.setSelected(tb.getErrorRate()); + responseRateCheck.setSelected(tb.getResponseRate()); + transferRateCheck.setSelected(tb.getTransferRate()); + } + + public void stateChanged(ChangeEvent e) { + modifyTestElement(ReportGuiPackage.getInstance().getCurrentElement()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/AbstractAction.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/AbstractAction.java new file mode 100644 index 0000000..290e4c2c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/AbstractAction.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.Set; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Parent class for implementing Menu item commands + */ +public abstract class AbstractAction implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * @see Command#doAction(ActionEvent) + */ + public void doAction(ActionEvent e) { + } + + /** + * @see Command#getActionNames() + */ + abstract public Set getActionNames(); + + /** + * @param e + */ + protected void popupShouldSave(ActionEvent e) { + log.debug("popupShouldSave"); + if (ReportGuiPackage.getInstance().getReportPlanFile() == null) { + if (JOptionPane.showConfirmDialog(ReportGuiPackage.getInstance().getMainFrame(), JMeterUtils + .getResString("should_save"), JMeterUtils.getResString("warning"), JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) { + ReportActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ReportSave.SAVE)); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportActionRouter.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportActionRouter.java new file mode 100644 index 0000000..68f2dbe --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportActionRouter.java @@ -0,0 +1,313 @@ +/* + * 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.report.gui.action; + +import java.awt.HeadlessException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.SwingUtilities; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; + +public final class ReportActionRouter implements ActionListener { + private Map> commands = new HashMap>(); + + private static volatile ReportActionRouter router; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Object LOCK = new Object(); + + private Map> preActionListeners = + new HashMap>(); + + private Map> postActionListeners = + new HashMap>(); + + private ReportActionRouter() { + } + + public void actionPerformed(final ActionEvent e) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + performAction(e); + } + + }); + } + + private void performAction(final ActionEvent e) { + try { + ReportGuiPackage.getInstance().updateCurrentNode(); + Set commandObjects = commands.get(e.getActionCommand()); + Iterator iter = commandObjects.iterator(); + while (iter.hasNext()) { + try { + Command c = iter.next(); + preActionPerformed(c.getClass(), e); + c.doAction(e); + postActionPerformed(c.getClass(), e); + } catch (IllegalUserActionException err) { + JMeterUtils.reportErrorToUser(err.toString()); + } catch (Exception err) { + log.error("", err); + } + } + } catch (NullPointerException er) { + log.error("performAction(" + e.getActionCommand() + ") " + e.toString() + " caused", er); + JMeterUtils.reportErrorToUser("Sorry, this feature (" + e.getActionCommand() + ") not yet implemented"); + } + } + + /** + * To execute an action immediately in the current thread. + * + * @param e + * the action to execute + */ + public void doActionNow(ActionEvent e) { + performAction(e); + } + + public Set getAction(String actionName) { + Set set = new HashSet(); + Set commandObjects = commands.get(actionName); + Iterator iter = commandObjects.iterator(); + while (iter.hasNext()) { + try { + set.add(iter.next()); + } catch (Exception err) { + log.error("", err); + } + } + return set; + } + + public Command getAction(String actionName, Class actionClass) { + Set commandObjects = commands.get(actionName); + Iterator iter = commandObjects.iterator(); + while (iter.hasNext()) { + try { + Command com = iter.next(); + if (com.getClass().equals(actionClass)) { + return com; + } + } catch (Exception err) { + log.error("", err); + } + } + return null; + } + + public Command getAction(String actionName, String className) { + Set commandObjects = commands.get(actionName); + Iterator iter = commandObjects.iterator(); + while (iter.hasNext()) { + try { + Command com = iter.next(); + if (com.getClass().getName().equals(className)) { + return com; + } + } catch (Exception err) { + log.error("", err); + } + } + return null; + } + + /** + * Allows an ActionListener to receive notification of a command being + * executed prior to the actual execution of the command. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.report.gui.action.Command. + * @param listener + * the ActionListener to receive the notifications + */ + public void addPreActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = preActionListeners.get(action.getName()); + if (set == null) { + set = new HashSet(); + } + set.add(listener); + preActionListeners.put(action.getName(), set); + } + } + + /** + * Allows an ActionListener to be removed from receiving notifications of a + * command being executed prior to the actual execution of the command. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.report.gui.action.Command. + * @param listener + * the ActionListener to receive the notifications + */ + public void removePreActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = preActionListeners.get(action.getName()); + if (set != null) { + set.remove(listener); + preActionListeners.put(action.getName(), set); + } + } + } + + /** + * Allows an ActionListener to receive notification of a command being + * executed after the command has executed. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.report.gui.action.Command. + * @param listener + */ + public void addPostActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = postActionListeners.get(action.getName()); + if (set == null) { + set = new HashSet(); + } + set.add(listener); + postActionListeners.put(action.getName(), set); + } + } + + /** + * Allows an ActionListener to be removed from receiving notifications of a + * command being executed after the command has executed. + * + * @param action + * the Class of the command for which the listener will + * notifications for. Class must extend + * org.apache.jmeter.report.gui.action.Command. + * @param listener + */ + public void removePostActionListener(Class action, ActionListener listener) { + if (action != null) { + HashSet set = postActionListeners.get(action.getName()); + if (set != null) { + set.remove(listener); + postActionListeners.put(action.getName(), set); + } + } + } + + protected void preActionPerformed(Class action, ActionEvent e) { + if (action != null) { + HashSet listenerSet = preActionListeners.get(action.getName()); + if (listenerSet != null && listenerSet.size() > 0) { + Object[] listeners = listenerSet.toArray(); + for (int i = 0; i < listeners.length; i++) { + ((ActionListener) listeners[i]).actionPerformed(e); + } + } + } + } + + protected void postActionPerformed(Class action, ActionEvent e) { + if (action != null) { + HashSet listenerSet = postActionListeners.get(action.getName()); + if (listenerSet != null && listenerSet.size() > 0) { + Object[] listeners = listenerSet.toArray(); + for (int i = 0; i < listeners.length; i++) { + ((ActionListener) listeners[i]).actionPerformed(e); + } + } + } + } + + private void populateCommandMap() { + log.info("populateCommandMap called"); + List listClasses; + Command command; + Iterator iterClasses; + Class commandClass; + try { + listClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { Class + .forName("org.apache.jmeter.gui.action.Command") }); + commands = new HashMap>(listClasses.size()); + if (listClasses.size() == 0) { + log.warn("!!!!!Uh-oh, didn't find any action handlers!!!!!"); + } + iterClasses = listClasses.iterator(); + while (iterClasses.hasNext()) { + String strClassName = iterClasses.next(); + if (strClassName.startsWith("org.apache.jmeter.report.gui.action")) { + // log.info("classname:: " + strClassName); + commandClass = Class.forName(strClassName); + if (!Modifier.isAbstract(commandClass.getModifiers())) { + command = (Command) commandClass.newInstance(); + Iterator iter = command.getActionNames().iterator(); + while (iter.hasNext()) { + String commandName = iter.next(); + Set commandObjects = commands.get(commandName); + if (commandObjects == null) { + commandObjects = new HashSet(); + commands.put(commandName, commandObjects); + } + commandObjects.add(command); + } + } + } + } + } catch (HeadlessException e){ + log.warn(e.toString()); + } catch (Exception e) { + log.error("exception finding action handlers", e); + } + } + + /** + * Gets the Instance attribute of the ActionRouter class + * + * @return The Instance value + */ + public static ReportActionRouter getInstance() { + if (router == null) { + synchronized (LOCK) { + if(router == null) { + router = new ReportActionRouter(); + router.populateCommandMap(); + } + } + } + return router; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportAddParent.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportAddParent.java new file mode 100644 index 0000000..c919447 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportAddParent.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.report.gui.action; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class ReportAddParent implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + static { + commands.add("Add Parent"); + } + + public ReportAddParent() { + } + + public void doAction(ActionEvent e) { + String name = ((Component) e.getSource()).getName(); + try { + TestElement controller = ReportGuiPackage.getInstance() + .createTestElement(name); + addParentToTree(controller); + } catch (Exception err) { + log.error("", err); + } + + } + + public Set getActionNames() { + return commands; + } + + protected void addParentToTree(TestElement newParent) { + ReportGuiPackage guiPackage = ReportGuiPackage.getInstance(); + ReportTreeNode newNode = new ReportTreeNode(newParent, guiPackage + .getTreeModel()); + ReportTreeNode currentNode = guiPackage.getTreeListener() + .getCurrentNode(); + ReportTreeNode parentNode = (ReportTreeNode) currentNode.getParent(); + int index = parentNode.getIndex(currentNode); + guiPackage.getTreeModel().insertNodeInto(newNode, parentNode, index); + ReportTreeNode[] nodes = guiPackage.getTreeListener() + .getSelectedNodes(); + for (int i = 0; i < nodes.length; i++) { + moveNode(guiPackage, nodes[i], newNode); + } + } + + private void moveNode(ReportGuiPackage guiPackage, ReportTreeNode node, + ReportTreeNode newParentNode) { + guiPackage.getTreeModel().removeNodeFromParent(node); + guiPackage.getTreeModel().insertNodeInto(node, newParentNode, + newParentNode.getChildCount()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportAddToTree.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportAddToTree.java new file mode 100644 index 0000000..696507b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportAddToTree.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.swing.JComponent; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class ReportAddToTree implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private Map allJMeterComponentCommands; + + public ReportAddToTree() { + allJMeterComponentCommands = new HashMap(); + allJMeterComponentCommands.put("Add", "Add"); + } + + /** + * Gets the Set of actions this Command class responds to. + * + * @return the ActionNames value + */ + public Set getActionNames() { + return allJMeterComponentCommands.keySet(); + } + + /** + * Adds the specified class to the current node of the tree. + */ + public void doAction(ActionEvent e) { + try { + TestElement node = ReportGuiPackage.getInstance() + .createTestElement(((JComponent) e.getSource()).getName()); + addObjectToTree(node); + } catch (Exception err) { + log.error("", err); + } + } + + protected void addObjectToTree(TestElement el) { + ReportGuiPackage guiPackage = ReportGuiPackage.getInstance(); + ReportTreeNode node = new ReportTreeNode(el, guiPackage.getTreeModel()); + guiPackage.getTreeModel().insertNodeInto(node, + guiPackage.getTreeListener().getCurrentNode(), + guiPackage.getTreeListener().getCurrentNode().getChildCount()); + TestElement curNode = + (TestElement)guiPackage.getTreeListener().getCurrentNode().getUserObject(); + if (curNode != null) { + curNode.addTestElement(el); + guiPackage.getMainFrame().getTree().setSelectionPath( + new TreePath(node.getPath())); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCheckDirty.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCheckDirty.java new file mode 100644 index 0000000..b5f9933 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCheckDirty.java @@ -0,0 +1,161 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.report.gui.action.AbstractAction; +import org.apache.jmeter.report.gui.action.ReportActionRouter; +import org.apache.jmeter.report.gui.action.ReportExitCommand; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.collections.ListedHashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class ReportCheckDirty extends AbstractAction implements HashTreeTraverser, ActionListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static Map previousGuiItems; + + public static final String CHECK_DIRTY = "check_dirty"; + + public static final String SUB_TREE_SAVED = "sub_tree_saved"; + + public static final String SUB_TREE_LOADED = "sub_tree_loaded"; + + public static final String ADD_ALL = "add_all"; + + // Not implemented: public static final String SAVE = "save_as"; + // Not implemented: public static final String SAVE_ALL = "save_all"; + // Not implemented: public static final String SAVE_TO_PREVIOUS = "save"; + public static final String REMOVE = "check_remove"; + + boolean checkMode = false; + + boolean removeMode = false; + + boolean dirty = false; + + private static final Set commands = new HashSet(); + static { + commands.add(CHECK_DIRTY); + commands.add(SUB_TREE_SAVED); + commands.add(SUB_TREE_LOADED); + commands.add(ADD_ALL); + commands.add(REMOVE); + } + + public ReportCheckDirty() { + previousGuiItems = new HashMap(); + ReportActionRouter.getInstance().addPreActionListener(ReportExitCommand.class, this); + } + + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals(ReportExitCommand.EXIT)) { + doAction(e); + } + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + String action = e.getActionCommand(); + if (action.equals(SUB_TREE_SAVED)) { + HashTree subTree = (HashTree) e.getSource(); + subTree.traverse(this); + } else if (action.equals(SUB_TREE_LOADED)) { + ListedHashTree addTree = (ListedHashTree) e.getSource(); + addTree.traverse(this); + } else if (action.equals(ADD_ALL)) { + previousGuiItems.clear(); + ReportGuiPackage.getInstance().getTreeModel().getReportPlan().traverse(this); + } else if (action.equals(REMOVE)) { + ReportGuiPackage guiPackage = ReportGuiPackage.getInstance(); + ReportTreeNode[] nodes = guiPackage.getTreeListener().getSelectedNodes(); + removeMode = true; + for (int i = nodes.length - 1; i >= 0; i--) { + guiPackage.getTreeModel().getCurrentSubTree(nodes[i]).traverse(this); + } + removeMode = false; + } + checkMode = true; + dirty = false; + HashTree wholeTree = ReportGuiPackage.getInstance().getTreeModel().getReportPlan(); + wholeTree.traverse(this); + ReportGuiPackage.getInstance().setDirty(dirty); + checkMode = false; + } + + /** + * The tree traverses itself depth-first, calling processNode for each + * object it encounters as it goes. + */ + public void addNode(Object node, HashTree subTree) { + log.debug("Node is class:" + node.getClass()); + ReportTreeNode treeNode = (ReportTreeNode) node; + if (checkMode) { + if (previousGuiItems.containsKey(treeNode)) { + if (!previousGuiItems.get(treeNode).equals(treeNode.getTestElement())) { + dirty = true; + } + } else { + dirty = true; + } + } else if (removeMode) { + previousGuiItems.remove(treeNode); + } else { + previousGuiItems.put(treeNode, (TestElement) treeNode.getTestElement().clone()); + } + } + + /** + * Indicates traversal has moved up a step, and the visitor should remove + * the top node from it's stack structure. + */ + public void subtractNode() { + } + + /** + * Process path is called when a leaf is reached. If a visitor wishes to + * generate Lists of path elements to each leaf, it should keep a Stack data + * structure of nodes passed to it with addNode, and removing top items for + * every subtractNode() call. + */ + public void processPath() { + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportClose.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportClose.java new file mode 100644 index 0000000..84d8c7c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportClose.java @@ -0,0 +1,89 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.util.JMeterUtils; + +/** + * This command clears the existing test plan, allowing the creation of a New + * test plan. + * + */ +public class ReportClose implements Command { + + private static final Set commands = new HashSet(); + static { + commands.add("close"); + } + + /** + * Constructor for the Close object. + */ + public ReportClose() { + } + + /** + * Gets the ActionNames attribute of the Close object. + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commands; + } + + /** + * This method performs the actual command processing. + * + * @param e + * the generic UI action event + */ + public void doAction(ActionEvent e) { + ReportActionRouter.getInstance().doActionNow( + new ActionEvent(e.getSource(), e.getID(), + ReportCheckDirty.CHECK_DIRTY)); + ReportGuiPackage guiPackage = ReportGuiPackage.getInstance(); + if (guiPackage.isDirty()) { + if (JOptionPane.showConfirmDialog(ReportGuiPackage.getInstance() + .getMainFrame(), JMeterUtils + .getResString("cancel_new_to_save"), JMeterUtils + .getResString("Save?"), JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) { + ReportActionRouter.getInstance().doActionNow( + new ActionEvent(e.getSource(), e.getID(), ActionNames.SAVE)); + } + } + guiPackage.getTreeModel().clearTestPlan(); + guiPackage.getTreeListener().getJTree().setSelectionRow(1); + + // Clear the name of the test plan file + ReportGuiPackage.getInstance().setReportPlanFile(null); + + ReportActionRouter.getInstance().actionPerformed( + new ActionEvent(e.getSource(), e.getID(), ActionNames.ADD_ALL)); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCopy.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCopy.java new file mode 100644 index 0000000..8893d70 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCopy.java @@ -0,0 +1,114 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.report.gui.action.AbstractAction; +import org.apache.jmeter.report.gui.tree.ReportTreeListener; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; +import org.apache.jmeter.testelement.TestElement; + +public class ReportCopy extends AbstractAction { + private static ReportTreeNode copiedNode = null; + + private static ReportTreeNode copiedNodes[] = null; + + private static final String COPY = "Copy"; + + private static final HashSet commands = new HashSet(); + static { + commands.add(COPY); + } + + /* + * @see org.apache.jmeter.report.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + ReportTreeListener treeListener = ReportGuiPackage.getInstance() + .getTreeListener(); + ReportTreeNode[] nodes = treeListener.getSelectedNodes(); + setCopiedNodes(nodes); + } + + public static ReportTreeNode[] getCopiedNodes() { + for (int i = 0; i < copiedNodes.length; i++) { + if (copiedNodes[i] == null) { + return null; + } + } + return cloneTreeNodes(copiedNodes); + } + + public static ReportTreeNode getCopiedNode() { + if (copiedNode == null) { + return null; + } + return cloneTreeNode(copiedNode); + } + + public static void setCopiedNode(ReportTreeNode node) { + copiedNode = cloneTreeNode(node); + } + + public static ReportTreeNode cloneTreeNode(ReportTreeNode node) { + ReportTreeNode treeNode = (ReportTreeNode) node.clone(); + treeNode.setUserObject(((TestElement) node.getUserObject()).clone()); + cloneChildren(treeNode, node); + return treeNode; + } + + public static void setCopiedNodes(ReportTreeNode nodes[]) { + copiedNodes = new ReportTreeNode[nodes.length]; + for (int i = 0; i < nodes.length; i++) { + copiedNodes[i] = cloneTreeNode(nodes[i]); + } + } + + public static ReportTreeNode[] cloneTreeNodes(ReportTreeNode nodes[]) { + ReportTreeNode treeNodes[] = new ReportTreeNode[nodes.length]; + for (int i = 0; i < nodes.length; i++) { + treeNodes[i] = cloneTreeNode(nodes[i]); + } + return treeNodes; + } + + private static void cloneChildren(ReportTreeNode to, ReportTreeNode from) { + @SuppressWarnings("unchecked") // OK + Enumeration enumFrom = from.children(); + while (enumFrom.hasMoreElements()) { + ReportTreeNode child = enumFrom.nextElement(); + ReportTreeNode childClone = (ReportTreeNode) child.clone(); + childClone.setUserObject(((TestElement) child.getUserObject()) + .clone()); + to.add(childClone); + cloneChildren((ReportTreeNode) to.getLastChild(), child); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCut.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCut.java new file mode 100644 index 0000000..2ea6cf8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportCut.java @@ -0,0 +1,59 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.report.gui.action.AbstractAction; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; + +public class ReportCut extends AbstractAction { + public static final String CUT = "Cut";//$NON-NLS-1$ + + private static final Set commands = new HashSet(); + static { + commands.add(CUT); + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + ReportGuiPackage guiPack = ReportGuiPackage.getInstance(); + ReportTreeNode[] currentNodes = guiPack.getTreeListener().getSelectedNodes(); + + ReportCopy.setCopiedNodes(currentNodes); + for (int i = 0; i < currentNodes.length; i++) { + guiPack.getTreeModel().removeNodeFromParent(currentNodes[i]); + } + guiPack.getMainFrame().repaint(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportDragNDrop.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportDragNDrop.java new file mode 100644 index 0000000..d378721 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportDragNDrop.java @@ -0,0 +1,131 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.report.gui.action.AbstractAction; +import org.apache.jmeter.report.gui.tree.ReportTreeListener; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.WorkBench; + +public class ReportDragNDrop extends AbstractAction { + public static final String ADD = "drag_n_drop.add";//$NON-NLS-1$ + + public static final String INSERT_BEFORE = "drag_n_drop.insert_before";//$NON-NLS-1$ + + public static final String INSERT_AFTER = "drag_n_drop.insert_after";//$NON-NLS-1$ + + private static final Set commands = new HashSet(); + static { + commands.add(ADD); + commands.add(INSERT_BEFORE); + commands.add(INSERT_AFTER); + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + String action = e.getActionCommand(); + ReportGuiPackage guiPackage = ReportGuiPackage.getInstance(); + ReportTreeNode[] draggedNodes = guiPackage.getTreeListener().getDraggedNodes(); + ReportTreeListener treeListener = guiPackage.getTreeListener(); + ReportTreeNode currentNode = treeListener.getCurrentNode(); + ReportTreeNode parentNode = (ReportTreeNode) currentNode.getParent(); + TestElement te = currentNode.getTestElement(); + if (te instanceof TestPlan || te instanceof WorkBench) { + parentNode = null; // So elements can only be added as children + } + // System.out.println(action+" "+te.getClass().getName()); + + if (ADD.equals(action) && canAddTo(currentNode)) { + removeNodesFromParents(draggedNodes); + for (int i = 0; i < draggedNodes.length; i++) { + ReportGuiPackage.getInstance().getTreeModel().insertNodeInto(draggedNodes[i], currentNode, + currentNode.getChildCount()); + } + } else if (INSERT_BEFORE.equals(action) && canAddTo(parentNode)) { + removeNodesFromParents(draggedNodes); + for (int i = 0; i < draggedNodes.length; i++) { + @SuppressWarnings("null") + int index = parentNode.getIndex(currentNode); // can't be null - this is checked by canAddTo + ReportGuiPackage.getInstance().getTreeModel().insertNodeInto(draggedNodes[i], parentNode, index); + } + } else if (INSERT_AFTER.equals(action) && canAddTo(parentNode)) { + removeNodesFromParents(draggedNodes); + for (int i = 0; i < draggedNodes.length; i++) { + @SuppressWarnings("null") + int index = parentNode.getIndex(currentNode) + 1; // can't be null - this is checked by canAddTo + ReportGuiPackage.getInstance().getTreeModel().insertNodeInto(draggedNodes[i], parentNode, index); + } + } + ReportGuiPackage.getInstance().getMainFrame().repaint(); + } + + /** + * Determine whether or not dragged nodes can be added to this parent. Also + * used by Paste TODO tighten rules TODO move to MenuFactory? + * + * @param parentNode + * @return whether it is OK to add the dragged nodes to this parent + */ + static boolean canAddTo(ReportTreeNode parentNode) { + if (null == parentNode) { + return false; + } + TestElement te = parentNode.getTestElement(); + // System.out.println("Add to: "+te.getClass().getName()); + if (te instanceof Controller) { + return true; + } + if (te instanceof Sampler) { + return true; + } + if (te instanceof WorkBench) { + return true; + } + if (te instanceof TestPlan) { + return true; + } + return false; + } + + protected void removeNodesFromParents(ReportTreeNode[] nodes) { + for (int i = 0; i < nodes.length; i++) { + ReportGuiPackage.getInstance().getTreeModel().removeNodeFromParent(nodes[i]); + } + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportEditCommand.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportEditCommand.java new file mode 100644 index 0000000..7bbfcb3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportEditCommand.java @@ -0,0 +1,57 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.NamePanel; + +public class ReportEditCommand implements Command { + private static final Set commands = new HashSet(); + static { + commands.add("edit"); + } + + public ReportEditCommand() { + } + + public void doAction(ActionEvent e) { + ReportGuiPackage guiPackage = ReportGuiPackage.getInstance(); + guiPackage.getMainFrame().setMainPanel((javax.swing.JComponent) guiPackage.getCurrentGui()); + guiPackage.getMainFrame().setEditMenu(guiPackage.getTreeListener().getCurrentNode().createPopupMenu()); + // TODO: I believe the following code (to the end of the method) is + // obsolete, + // since NamePanel no longer seems to be the GUI for any component: + if (!(guiPackage.getCurrentGui() instanceof NamePanel)) { + guiPackage.getMainFrame().setFileLoadEnabled(true); + guiPackage.getMainFrame().setFileSaveEnabled(true); + } else { + guiPackage.getMainFrame().setFileLoadEnabled(false); + guiPackage.getMainFrame().setFileSaveEnabled(false); + } + } + + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportEnableComponent.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportEnableComponent.java new file mode 100644 index 0000000..fea7532 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportEnableComponent.java @@ -0,0 +1,73 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class ReportEnableComponent implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String ENABLE = "enable"; + + public static final String DISABLE = "disable"; + + private static final Set commands = new HashSet(); + static { + commands.add(ENABLE); + commands.add(DISABLE); + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + public void doAction(ActionEvent e) { + ReportTreeNode[] nodes = ReportGuiPackage.getInstance().getTreeListener().getSelectedNodes(); + + if (e.getActionCommand().equals(ENABLE)) { + log.debug("enabling currently selected gui objects"); + enableComponents(nodes, true); + } else if (e.getActionCommand().equals(DISABLE)) { + log.debug("disabling currently selected gui objects"); + enableComponents(nodes, false); + } + } + + private void enableComponents(ReportTreeNode[] nodes, boolean enable) { + ReportGuiPackage pack = ReportGuiPackage.getInstance(); + for (int i = 0; i < nodes.length; i++) { + nodes[i].setEnabled(enable); + pack.getGui(nodes[i].getTestElement()).setEnabled(enable); + } + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportExitCommand.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportExitCommand.java new file mode 100644 index 0000000..addaa27 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportExitCommand.java @@ -0,0 +1,87 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.report.gui.action.ReportActionRouter; +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.report.gui.action.ReportSave; +import org.apache.jmeter.util.JMeterUtils; + +public class ReportExitCommand implements Command { + + public static final String EXIT = "exit"; + + private static final Set commands = new HashSet(); + static { + commands.add(EXIT); + } + + /** + * Constructor for the ExitCommand object + */ + public ReportExitCommand() { + } + + /** + * Gets the ActionNames attribute of the ExitCommand object + * + * @return The ActionNames value + */ + public Set getActionNames() { + return commands; + } + + /** + * Description of the Method + * + * @param e + * Description of Parameter + */ + public void doAction(ActionEvent e) { + ReportActionRouter.getInstance().doActionNow( + new ActionEvent(e.getSource(), e.getID(), + ReportCheckDirty.CHECK_DIRTY)); + if (ReportGuiPackage.getInstance().isDirty()) { + int chosenOption = JOptionPane.showConfirmDialog(ReportGuiPackage + .getInstance().getMainFrame(), JMeterUtils + .getResString("cancel_exit_to_save"), JMeterUtils + .getResString("Save?"), JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (chosenOption == JOptionPane.NO_OPTION) { + System.exit(0); + } else if (chosenOption == JOptionPane.YES_OPTION) { + ReportActionRouter.getInstance().doActionNow( + new ActionEvent(e.getSource(), e.getID(), + ReportSave.SAVE_ALL_AS)); + if (!ReportGuiPackage.getInstance().isDirty()) { + System.exit(0); + } + } + } else { + System.exit(0); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportHelp.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportHelp.java new file mode 100644 index 0000000..65b3f9c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportHelp.java @@ -0,0 +1,121 @@ +/* + * 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.report.gui.action; + +import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JDialog; +import javax.swing.JScrollPane; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.swing.HtmlPane; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.gui.action.Command; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class ReportHelp implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String HELP = "help"; + + private static final Set commands = new HashSet(); + + public static final String HELP_DOCS = "file:///" + JMeterUtils.getJMeterHome() + "/printable_docs/usermanual/"; + + public static final String HELP_PAGE = HELP_DOCS + "component_reference.html"; + + public static final String HELP_FUNCTIONS = HELP_DOCS + "functions.html"; + + private static JDialog helpWindow; + + private static HtmlPane helpDoc; + + private static JScrollPane scroller; + + private static String currentPage; + + static { + commands.add(HELP); + helpDoc = new HtmlPane(); + scroller = new JScrollPane(helpDoc); + helpDoc.setEditable(false); + try { + helpDoc.setPage(HELP_PAGE); + currentPage = HELP_PAGE; + } catch (IOException err) { + String msg = "Couldn't load help file " + err.toString(); + log.error(msg); + currentPage = "";// Avoid NPE in resetPage() + } + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + public void doAction(ActionEvent e) { + if (helpWindow == null) { + helpWindow = new JDialog(new Frame(),// independent frame to + // allow it to be overlaid + // by the main frame + JMeterUtils.getResString("help"),//$NON-NLS-1$ + false); + helpWindow.getContentPane().setLayout(new GridLayout(1, 1)); + ComponentUtil.centerComponentInWindow(helpWindow, 60); + } + helpWindow.getContentPane().removeAll(); + helpWindow.getContentPane().add(scroller); + helpWindow.setVisible(true); + if (e.getSource() instanceof String[]) { + String[] source = (String[]) e.getSource(); + resetPage(source[0]); + helpDoc.scrollToReference(source[1]); + } else { + resetPage(HELP_PAGE); + helpDoc.scrollToReference(ReportGuiPackage.getInstance().getTreeListener().getCurrentNode().getDocAnchor()); + + } + } + + private void resetPage(String source) { + if (!currentPage.equals(source)) { + try { + helpDoc.setPage(source); + currentPage = source; + } catch (IOException err) { + log.error(err.toString()); + JMeterUtils.reportErrorToUser("Problem loading a help page - see log for details"); + currentPage = ""; + } + } + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + public Set getActionNames() { + return commands; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportLoad.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportLoad.java new file mode 100644 index 0000000..0d673ca --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportLoad.java @@ -0,0 +1,140 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JFileChooser; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.gui.util.ReportFileDialoger; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.ReportPlan; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class ReportLoad implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + static { + commands.add("open"); + commands.add("merge"); + } + + public ReportLoad() { + super(); + } + + public Set getActionNames() { + return commands; + } + + public void doAction(ActionEvent e) { + boolean merging = e.getActionCommand().equals("merge"); + + if (!merging) { + ReportActionRouter.getInstance().doActionNow( + new ActionEvent(e.getSource(), e.getID(), "close")); + } + + JFileChooser chooser = ReportFileDialoger + .promptToOpenFile(new String[] { ".jmr" }); + if (chooser == null) { + return; + } + boolean isTestPlan = false; + InputStream reader = null; + File f = null; + try { + f = chooser.getSelectedFile(); + if (f != null) { + if (merging) { + log.info("Merging file: " + f); + } else { + log.info("Loading file: " + f); + FileServer.getFileServer().setBaseForScript(f); + } + reader = new FileInputStream(f); + HashTree tree = SaveService.loadTree(reader); + isTestPlan = insertLoadedTree(e.getID(), tree); + } + } catch (NoClassDefFoundError ex) // Allow for missing optional jars + { + String msg = ex.getMessage(); + if (msg == null) { + msg = "Missing jar file - see log for details"; + log.warn("Missing jar file", ex); + } + JMeterUtils.reportErrorToUser(msg); + } catch (Exception ex) { + String msg = ex.getMessage(); + if (msg == null) { + msg = "Unexpected error - see log for details"; + log.warn("Unexpected error", ex); + } + JMeterUtils.reportErrorToUser(msg); + } finally { + JOrphanUtils.closeQuietly(reader); + ReportGuiPackage.getInstance().updateCurrentGui(); + ReportGuiPackage.getInstance().getMainFrame().repaint(); + } + // don't change name if merging + if (!merging && isTestPlan && f != null) { + ReportGuiPackage.getInstance().setReportPlanFile(f.getAbsolutePath()); + } + } + + /** + * Returns a boolean indicating whether the loaded tree was a full test plan + */ + public boolean insertLoadedTree(int id, HashTree tree) throws Exception, + IllegalUserActionException { + // convertTree(tree); + if (tree == null) { + throw new Exception("Error in TestPlan - see log file"); + } + boolean isTestPlan = tree.getArray()[0] instanceof ReportPlan; + HashTree newTree = ReportGuiPackage.getInstance().addSubTree(tree); + ReportGuiPackage.getInstance().updateCurrentGui(); + ReportGuiPackage.getInstance().getMainFrame().getTree() + .setSelectionPath( + new TreePath(((ReportTreeNode) newTree.getArray()[0]) + .getPath())); + tree = ReportGuiPackage.getInstance().getCurrentSubTree(); + ReportActionRouter.getInstance().actionPerformed( + new ActionEvent(tree.get(tree.getArray()[tree.size() - 1]), id, + ReportCheckDirty.SUB_TREE_LOADED)); + + return isTestPlan; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportLookAndFeelCommand.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportLookAndFeelCommand.java new file mode 100644 index 0000000..5e84727 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportLookAndFeelCommand.java @@ -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.report.gui.action; + +import org.apache.jmeter.gui.action.LookAndFeelCommand; + +public class ReportLookAndFeelCommand extends LookAndFeelCommand { +// same code as for default JMeter class +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportPaste.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportPaste.java new file mode 100644 index 0000000..bffcc29 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportPaste.java @@ -0,0 +1,69 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.report.gui.action.AbstractAction; +import org.apache.jmeter.report.gui.tree.ReportTreeListener; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; + +/** + * Places a copied JMeterTreeNode under the selected node. + * + */ +public class ReportPaste extends AbstractAction { + + public static final String PASTE = "Paste"; //$NON-NLS-1$ + + private static final Set commands = new HashSet(); + static { + commands.add(PASTE); + } + + /** + * @see org.apache.jmeter.gui.action.Command#getActionNames() + */ + @Override + public Set getActionNames() { + return commands; + } + + /** + * @see org.apache.jmeter.gui.action.Command#doAction(ActionEvent) + */ + @Override + public void doAction(ActionEvent e) { + ReportTreeNode draggedNodes[] = ReportCopy.getCopiedNodes(); + ReportTreeListener treeListener = ReportGuiPackage.getInstance().getTreeListener(); + ReportTreeNode currentNode = treeListener.getCurrentNode(); + if (ReportDragNDrop.canAddTo(currentNode)) { + for (int i = 0; i < draggedNodes.length; i++) { + if (draggedNodes[i] != null) { + ReportGuiPackage.getInstance().getTreeModel().insertNodeInto(draggedNodes[i], currentNode, + currentNode.getChildCount()); + } + } + } + ReportGuiPackage.getInstance().getMainFrame().repaint(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportRemove.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportRemove.java new file mode 100644 index 0000000..25fdf51 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportRemove.java @@ -0,0 +1,83 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JOptionPane; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.report.gui.action.ReportCheckDirty; +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; +import org.apache.jmeter.testelement.TestElement; + +public class ReportRemove implements Command { + private static final Set commands = new HashSet(); + static { + commands.add("remove"); + } + + /** + * Constructor for the Remove object + */ + public ReportRemove() { + } + + /** + * Gets the ActionNames attribute of the Remove object. + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commands; + } + + public void doAction(ActionEvent e) { + ReportActionRouter.getInstance().actionPerformed( + new ActionEvent(e.getSource(), e.getID(), + ReportCheckDirty.REMOVE)); + ReportGuiPackage guiPackage = ReportGuiPackage.getInstance(); + ReportTreeNode[] nodes = guiPackage.getTreeListener() + .getSelectedNodes(); + TreePath newTreePath = // Save parent node for later + guiPackage.getTreeListener().removedSelectedNode(); + for (int i = nodes.length - 1; i >= 0; i--) { + removeNode(nodes[i]); + } + guiPackage.getTreeListener().getJTree().setSelectionPath(newTreePath); + guiPackage.updateCurrentGui(); + } + + public static void removeNode(ReportTreeNode node) { + TestElement testElement = node.getTestElement(); + if (testElement.canRemove()) { + ReportGuiPackage.getInstance().getTreeModel().removeNodeFromParent( + node); + ReportGuiPackage.getInstance().removeNode(testElement); + } else { + String message = testElement.getClass().getName() + " is busy"; + JOptionPane.showMessageDialog(null, message, "Cannot remove item", + JOptionPane.ERROR_MESSAGE); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportSave.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportSave.java new file mode 100644 index 0000000..01d84b7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportSave.java @@ -0,0 +1,131 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.io.FileOutputStream; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Set; + +import javax.swing.JFileChooser; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.report.gui.action.ReportActionRouter; +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.gui.util.ReportFileDialoger; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class ReportSave implements Command { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String SAVE_ALL_AS = "save_all_as"; + + public static final String SAVE_AS = "save_as"; + + public static final String SAVE = "save"; + + // NOTUSED private String chosenFile; + + private static final Set commands = new HashSet(); + static { + commands.add(SAVE_AS); + commands.add(SAVE_ALL_AS); + commands.add(SAVE); + } + + /** + * Constructor for the Save object. + */ + public ReportSave() { + } + + /** + * Gets the ActionNames attribute of the Save object. + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commands; + } + + public void doAction(ActionEvent e) throws IllegalUserActionException { + HashTree subTree = null; + if (!commands.contains(e.getActionCommand())) { + throw new IllegalUserActionException("Invalid user command:" + e.getActionCommand()); + } + if (e.getActionCommand().equals(SAVE_AS)) { + subTree = ReportGuiPackage.getInstance().getCurrentSubTree(); + } else { + subTree = ReportGuiPackage.getInstance().getTreeModel().getReportPlan(); + } + + String updateFile = ReportGuiPackage.getInstance().getReportPlanFile(); + if (!SAVE.equals(e.getActionCommand()) || updateFile == null) { + JFileChooser chooser = ReportFileDialoger.promptToSaveFile(ReportGuiPackage.getInstance().getTreeListener() + .getCurrentNode().getName() + + ".jmr"); + if (chooser == null) { + return; + } + updateFile = chooser.getSelectedFile().getAbsolutePath(); + if (!e.getActionCommand().equals(SAVE_AS)) { + ReportGuiPackage.getInstance().setReportPlanFile(updateFile); + } + } + // TODO: doesn't putting this here mark the tree as + // saved even though a failure may occur later? + + ReportActionRouter.getInstance().doActionNow(new ActionEvent(subTree, e.getID(), ReportCheckDirty.SUB_TREE_SAVED)); + try { + convertSubTree(subTree); + } catch (Exception err) { + } + FileOutputStream ostream = null; + try { + ostream = new FileOutputStream(updateFile); + SaveService.saveTree(subTree, ostream); + log.info("saveTree"); + } catch (Exception ex) { + ReportGuiPackage.getInstance().setReportPlanFile(null); + log.error("", ex); + throw new IllegalUserActionException("Couldn't save test plan to file: " + updateFile); + } finally { + JOrphanUtils.closeQuietly(ostream); + } + } + + private void convertSubTree(HashTree tree) { + Iterator iter = new LinkedList(tree.list()).iterator(); + while (iter.hasNext()) { + ReportTreeNode item = (ReportTreeNode) iter.next(); + convertSubTree(tree.getTree(item)); + TestElement testElement = item.getTestElement(); + tree.replace(item, testElement); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportSaveGraphics.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportSaveGraphics.java new file mode 100644 index 0000000..11ca972 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportSaveGraphics.java @@ -0,0 +1,109 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JComponent; +import javax.swing.JFileChooser; + +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.action.Command; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.util.ReportFileDialoger; +import org.apache.jmeter.save.SaveGraphicsService; +import org.apache.jmeter.visualizers.Printable; +//import org.apache.jorphan.logging.LoggingManager; +//import org.apache.log.Logger; + +/** + * 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 + * element implements Printable interface. If it does, it call getPrintable() to + * get the JComponent. By default, it will use SaveGraphicsService to save a PNG + * file if no extension is provided. If either .png or .tif is in the filename, + * it will call SaveGraphicsService to save in the format. + */ +public class ReportSaveGraphics implements Command { + //transient private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String SAVE_GRAPHICS = "save_graphics"; // $NON-NLS-1$ + + private static final Set commands = new HashSet(); + static { + commands.add(SAVE_GRAPHICS); + } + + private static final String[] extensions = { SaveGraphicsService.TIFF_EXTENSION, SaveGraphicsService.PNG_EXTENSION }; + + /** + * Constructor for the Save object. + */ + public ReportSaveGraphics() { + } + + /** + * Gets the ActionNames attribute of the Save object. + * + * @return the ActionNames value + */ + public Set getActionNames() { + return commands; + } + + public void doAction(ActionEvent e) throws IllegalUserActionException { + JMeterGUIComponent component = null; + JComponent comp = null; + if (!commands.contains(e.getActionCommand())) { + throw new IllegalUserActionException("Invalid user command:" + e.getActionCommand()); + } + if (e.getActionCommand().equals(SAVE_GRAPHICS)) { + component = ReportGuiPackage.getInstance().getCurrentGui(); + // get the JComponent from the visualizer + if (component instanceof Printable) { + comp = ((Printable) component).getPrintableComponent(); + + String filename; + JFileChooser chooser = ReportFileDialoger.promptToSaveFile(ReportGuiPackage.getInstance().getTreeListener() + .getCurrentNode().getName(), extensions); + if (chooser == null) { + return; + } + // Get the string given from the choose and check + // the file extension. + filename = chooser.getSelectedFile().getAbsolutePath(); + if (filename != null) { + SaveGraphicsService save = new SaveGraphicsService(); + String ext = filename.substring(filename.length() - 4); + String name = filename.substring(0, filename.length() - 4); + if (ext.equals(SaveGraphicsService.PNG_EXTENSION)) { + save.saveJComponent(name, SaveGraphicsService.PNG, comp); + } else if (ext.equals(SaveGraphicsService.TIFF_EXTENSION)) { + save.saveJComponent(name, SaveGraphicsService.TIFF, comp); + } else { + save.saveJComponent(filename, SaveGraphicsService.PNG, comp); + } + } + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportStart.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportStart.java new file mode 100644 index 0000000..fcc33a7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/action/ReportStart.java @@ -0,0 +1,105 @@ +/* + * 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.report.gui.action; + +import java.awt.event.ActionEvent; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.gui.action.ActionNames; + +/** + * FIXME BROKEN CODE + */ +public class ReportStart extends AbstractAction { + //private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Set commands = new HashSet(); + static { + commands.add(ActionNames.ACTION_START); + commands.add(ActionNames.ACTION_STOP); + commands.add(ActionNames.ACTION_SHUTDOWN); + } + // FIXME Due to startEngine being commented engine will always be null + //private StandardJMeterEngine engine; + + /** + * Constructor for the Start object. + */ + public ReportStart() { + } + + /** + * Gets the ActionNames attribute of the Start object. + * + * @return the ActionNames value + */ + @Override + public Set getActionNames() { + return commands; + } + + @Override + public void doAction(ActionEvent e) { + if (e.getActionCommand().equals(ActionNames.ACTION_START)) { + popupShouldSave(e); + startEngine(); +// } else if (e.getActionCommand().equals(ActionNames.ACTION_STOP)) { + // FIXME engine is always null +// if (engine != null) { +// ReportGuiPackage.getInstance().getMainFrame().showStoppingMessage(""); +// engine.stopTest(); +// engine = null; +// } +// } else if (e.getActionCommand().equals(ActionNames.ACTION_SHUTDOWN)) { + // FIXME engine is always null +// if (engine != null) { +// ReportGuiPackage.getInstance().getMainFrame().showStoppingMessage(""); +// engine.askThreadsToStop(); +// engine = null; +// } + } + } + + protected void startEngine() { + /** + * this will need to be changed + ReportGuiPackage gui = ReportGuiPackage.getInstance(); + engine = new StandardJMeterEngine(); + HashTree testTree = gui.getTreeModel().getTestPlan(); + convertSubTree(testTree); + DisabledComponentRemover remover = new DisabledComponentRemover(testTree); + testTree.traverse(remover); + testTree.add(testTree.getArray()[0], gui.getMainFrame()); + log.debug("test plan before cloning is running version: " + + ((TestPlan) testTree.getArray()[0]).isRunningVersion()); + TreeCloner cloner = new TreeCloner(false); + testTree.traverse(cloner); + engine.configure(cloner.getClonedTree()); + try { + engine.runTest(); + } catch (JMeterEngineException e) { + JOptionPane.showMessageDialog(gui.getMainFrame(), e.getMessage(), JMeterUtils + .getResString("Error Occurred"), JOptionPane.ERROR_MESSAGE); + } + log.debug("test plan after cloning and running test is running version: " + + ((TestPlan) testTree.getArray()[0]).isRunningVersion()); + */ + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportCellRenderer.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportCellRenderer.java new file mode 100644 index 0000000..6098200 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportCellRenderer.java @@ -0,0 +1,60 @@ +/* + * 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.report.gui.tree; + +import java.awt.Component; + +import javax.swing.ImageIcon; +import javax.swing.JTree; +import javax.swing.tree.DefaultTreeCellRenderer; + +public class ReportCellRenderer extends DefaultTreeCellRenderer { + private static final long serialVersionUID = 240L; + + public ReportCellRenderer() { + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, + boolean leaf, int row, boolean p_hasFocus) { + super.getTreeCellRendererComponent(tree, ((ReportTreeNode) value).getName(), sel, expanded, leaf, row, + p_hasFocus); + boolean enabled = ((ReportTreeNode) value).isEnabled(); + ImageIcon ic = ((ReportTreeNode) value).getIcon(enabled); + if (ic != null) { + if (enabled) { + setIcon(ic); + } else { + setDisabledIcon(ic); + } + } else { + if (!enabled)// i.e. no disabled icon found + { + // Must therefore set the enabled icon so there is at least some + // icon + ic = ((ReportTreeNode) value).getIcon(); + if (ic != null) { + setIcon(ic); + } + } + } + this.setEnabled(enabled); + return this; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeListener.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeListener.java new file mode 100644 index 0000000..acd0644 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeListener.java @@ -0,0 +1,314 @@ +/* + * 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.report.gui.tree; + +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; + +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JTree; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import org.apache.jmeter.control.gui.ReportGui; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.ReportMainFrame; +import org.apache.jmeter.report.gui.action.ReportDragNDrop; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class ReportTreeListener implements TreeSelectionListener, MouseListener, KeyListener, MouseMotionListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Container endWindow; + // JPopupMenu pop; + private TreePath currentPath; + + private ActionListener actionHandler; + + private ReportTreeModel model; + + private JTree tree; + + private boolean dragging = false; + + private ReportTreeNode[] draggedNodes; + + private JLabel dragIcon = new JLabel(JMeterUtils.getImage("leafnode.gif")); + + /** + * Constructor for the JMeterTreeListener object. + */ + public ReportTreeListener(ReportTreeModel model) { + this.model = model; + dragIcon.validate(); + dragIcon.setVisible(true); + } + + public ReportTreeListener() { + dragIcon.validate(); + dragIcon.setVisible(true); + } + + public void setModel(ReportTreeModel m) { + model = m; + } + + /** + * Sets the ActionHandler attribute of the JMeterTreeListener object. + * + * @param ah + * the new ActionHandler value + */ + public void setActionHandler(ActionListener ah) { + actionHandler = ah; + } + + /** + * Sets the JTree attribute of the JMeterTreeListener object. + * + * @param tree + * the new JTree value + */ + public void setJTree(JTree tree) { + this.tree = tree; + } + + /** + * Sets the EndWindow attribute of the JMeterTreeListener object. + * + * @param window + * the new EndWindow value + */ + public void setEndWindow(Container window) { + // endWindow = window; + } + + /** + * Gets the JTree attribute of the JMeterTreeListener object. + * + * @return tree the current JTree value. + */ + public JTree getJTree() { + return tree; + } + + /** + * Gets the CurrentNode attribute of the JMeterTreeListener object. + * + * @return the CurrentNode value + */ + public ReportTreeNode getCurrentNode() { + if (currentPath != null) { + if (currentPath.getLastPathComponent() != null) { + return (ReportTreeNode) currentPath.getLastPathComponent(); + } else { + return (ReportTreeNode) currentPath.getParentPath().getLastPathComponent(); + } + } else { + return (ReportTreeNode) model.getRoot(); + } + } + + public ReportTreeNode[] getSelectedNodes() { + TreePath[] paths = tree.getSelectionPaths(); + if (paths == null) { + return new ReportTreeNode[] { getCurrentNode() }; + } + ReportTreeNode[] nodes = new ReportTreeNode[paths.length]; + for (int i = 0; i < paths.length; i++) { + nodes[i] = (ReportTreeNode) paths[i].getLastPathComponent(); + } + + return nodes; + } + + public TreePath removedSelectedNode() { + currentPath = currentPath.getParentPath(); + return currentPath; + } + + public void valueChanged(TreeSelectionEvent e) { + log.debug("value changed, updating currentPath"); + currentPath = e.getNewLeadSelectionPath(); + actionHandler.actionPerformed(new ActionEvent(this, 3333, "edit")); + } + + public void mouseClicked(MouseEvent ev) { + } + + public void mouseReleased(MouseEvent e) { + if (dragging && isValidDragAction(draggedNodes, getCurrentNode())) { + dragging = false; + JPopupMenu dragNdrop = new JPopupMenu(); + JMenuItem item = new JMenuItem(JMeterUtils.getResString("Insert Before")); + item.addActionListener(actionHandler); + item.setActionCommand(ReportDragNDrop.INSERT_BEFORE); + dragNdrop.add(item); + item = new JMenuItem(JMeterUtils.getResString("Insert After")); + item.addActionListener(actionHandler); + item.setActionCommand(ReportDragNDrop.INSERT_AFTER); + dragNdrop.add(item); + item = new JMenuItem(JMeterUtils.getResString("Add as Child")); + item.addActionListener(actionHandler); + item.setActionCommand(ReportDragNDrop.ADD); + dragNdrop.add(item); + dragNdrop.addSeparator(); + item = new JMenuItem(JMeterUtils.getResString("Cancel")); + dragNdrop.add(item); + displayPopUp(e, dragNdrop); + } else { + ReportGuiPackage.getInstance().getMainFrame().repaint(); + } + dragging = false; + } + + public ReportTreeNode[] getDraggedNodes() { + return draggedNodes; + } + + /** + * Tests if the node is being dragged into one of it's own sub-nodes, or + * into itself. + */ + private boolean isValidDragAction(ReportTreeNode[] source, ReportTreeNode dest) { + boolean isValid = true; + TreeNode[] path = dest.getPath(); + for (int i = 0; i < path.length; i++) { + if (contains(source, path[i])) { + isValid = false; + } + } + return isValid; + } + + public void mouseEntered(MouseEvent e) { + } + + private void changeSelectionIfDragging(MouseEvent e) { + if (dragging) { + ReportGuiPackage.getInstance().getMainFrame().drawDraggedComponent(dragIcon, e.getX(), e.getY()); + if (tree.getPathForLocation(e.getX(), e.getY()) != null) { + currentPath = tree.getPathForLocation(e.getX(), e.getY()); + if (!contains(draggedNodes, getCurrentNode())) { + tree.setSelectionPath(currentPath); + } + } + } + } + + private boolean contains(Object[] container, Object item) { + for (int i = 0; i < container.length; i++) { + if (container[i] == item) { + return true; + } + } + return false; + } + + public void mousePressed(MouseEvent e) { + // Get the Main Frame. + ReportMainFrame mainFrame = ReportGuiPackage.getInstance().getMainFrame(); + // Close any Main Menu that is open + mainFrame.closeMenu(); + int selRow = tree.getRowForLocation(e.getX(), e.getY()); + if (tree.getPathForLocation(e.getX(), e.getY()) != null) { + log.debug("mouse pressed, updating currentPath"); + currentPath = tree.getPathForLocation(e.getX(), e.getY()); + } + if (selRow != -1) { + // updateMainMenu(((JMeterGUIComponent) + // getCurrentNode().getUserObject()).createPopupMenu()); + if (isRightClick(e)) { + if (tree.getSelectionCount() < 2) { + tree.setSelectionPath(currentPath); + } + if (getCurrentNode() != null) { + log.debug("About to display pop-up"); + displayPopUp(e); + } + } + } + } + + public void mouseDragged(MouseEvent e) { + if (!dragging) { + dragging = true; + draggedNodes = getSelectedNodes(); + if (draggedNodes[0].getUserObject() instanceof ReportGui) { + dragging = false; + } + + } + changeSelectionIfDragging(e); + } + + public void mouseMoved(MouseEvent e) { + } + + public void mouseExited(MouseEvent ev) { + } + + public void keyPressed(KeyEvent e) { + } + + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + } + + private boolean isRightClick(MouseEvent e) { + return e.isPopupTrigger() || (MouseEvent.BUTTON2_MASK & e.getModifiers()) > 0 || (MouseEvent.BUTTON3_MASK == e.getModifiers()); + } + + /* + * NOTUSED private void updateMainMenu(JPopupMenu menu) { try { MainFrame + * mainFrame = GuiPackage.getInstance().getMainFrame(); + * mainFrame.setEditMenu(menu); } catch (NullPointerException e) { + * log.error("Null pointer: JMeterTreeListener.updateMenuItem()", e); + * log.error("", e); } } + */ + + private void displayPopUp(MouseEvent e) { + JPopupMenu pop = getCurrentNode().createPopupMenu(); + ReportGuiPackage.getInstance().displayPopUp(e, pop); + } + + private void displayPopUp(MouseEvent e, JPopupMenu popup) { + log.warn("Shouldn't be here"); + if (popup != null) { + popup.pack(); + popup.show(tree, e.getX(), e.getY()); + popup.setVisible(true); + popup.requestFocus(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeModel.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeModel.java new file mode 100644 index 0000000..f65f1af --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeModel.java @@ -0,0 +1,182 @@ +/* + * 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.report.gui.tree; + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.tree.DefaultTreeModel; + +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.control.gui.ReportGui; +import org.apache.jmeter.exceptions.IllegalUserActionException; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.report.gui.tree.ReportTreeNode; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.ReportPlan; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; + +public class ReportTreeModel extends DefaultTreeModel { + + private static final long serialVersionUID = 240L; + + public ReportTreeModel() { + super(new ReportTreeNode(new ReportGui().createTestElement(), null)); + initTree(); + } + + /** + * Returns a list of tree nodes that hold objects of the given class type. + * If none are found, an empty list is returned. + */ + public List getNodesOfType(Class type) { + List nodeList = new LinkedList(); + traverseAndFind(type, (ReportTreeNode) this.getRoot(), nodeList); + return nodeList; + } + + /** + * Get the node for a given TestElement object. + */ + public ReportTreeNode getNodeOf(TestElement userObject) { + return traverseAndFind(userObject, (ReportTreeNode) getRoot()); + } + + /** + * Adds the sub tree at the given node. Returns a boolean indicating whether + * the added sub tree was a full test plan. + */ + public HashTree addSubTree(HashTree subTree, ReportTreeNode current) + throws IllegalUserActionException { + Iterator iter = subTree.list().iterator(); + while (iter.hasNext()) { + TestElement item = (TestElement) iter.next(); + if (item instanceof ReportPlan) { + current = (ReportTreeNode) ((ReportTreeNode) getRoot()) + .getChildAt(0); + ((TestElement) current.getUserObject()).addTestElement(item); + ((ReportPlan) current.getUserObject()).setName(item.getName()); + addSubTree(subTree.getTree(item), current); + } else { + if (subTree.getTree(item) != null) { + addSubTree(subTree.getTree(item), addComponent(item, current)); + } + } + } + return getCurrentSubTree(current); + } + + public ReportTreeNode addComponent(TestElement component, + ReportTreeNode node) throws IllegalUserActionException { + if (node.getUserObject() instanceof AbstractConfigGui) { + throw new IllegalUserActionException( + "This node cannot hold sub-elements"); + } + ReportGuiPackage.getInstance().updateCurrentNode(); + JMeterGUIComponent guicomp = ReportGuiPackage.getInstance().getGui(component); + guicomp.configure(component); + guicomp.modifyTestElement(component); + ReportGuiPackage.getInstance().getCurrentGui(); // put the gui object back + // to the way it was. + ReportTreeNode newNode = new ReportTreeNode(component, this); + + // This check the state of the TestElement and if returns false it + // disable the loaded node + try { + if (component.getProperty(TestElement.ENABLED) instanceof NullProperty + || component.getPropertyAsBoolean(TestElement.ENABLED)) { + newNode.setEnabled(true); + } else { + newNode.setEnabled(false); + } + } catch (Exception e) { + newNode.setEnabled(true); + } + + this.insertNodeInto(newNode, node, node.getChildCount()); + return newNode; + } + + public void removeNodeFromParent(ReportTreeNode node) { + if (!(node.getUserObject() instanceof ReportPlan)) { + super.removeNodeFromParent(node); + } + } + + private void traverseAndFind(Class type, ReportTreeNode node, List nodeList) { + if (type.isInstance(node.getUserObject())) { + nodeList.add(node); + } + @SuppressWarnings("unchecked") // OK + Enumeration enumNode = node.children(); + while (enumNode.hasMoreElements()) { + ReportTreeNode child = enumNode.nextElement(); + traverseAndFind(type, child, nodeList); + } + } + + private ReportTreeNode traverseAndFind(TestElement userObject, + ReportTreeNode node) { + if (userObject == node.getUserObject()) { + return node; + } + @SuppressWarnings("unchecked") // OK + Enumeration enumNode = node.children(); + while (enumNode.hasMoreElements()) { + ReportTreeNode child = enumNode.nextElement(); + ReportTreeNode result = traverseAndFind(userObject, child); + if (result != null) { + return result; + } + } + return null; + } + + public HashTree getCurrentSubTree(ReportTreeNode node) { + ListedHashTree hashTree = new ListedHashTree(node); + @SuppressWarnings("unchecked") // OK + Enumeration enumNode = node.children(); + while (enumNode.hasMoreElements()) { + ReportTreeNode child = enumNode.nextElement(); + hashTree.add(node, getCurrentSubTree(child)); + } + return hashTree; + } + + public HashTree getReportPlan() { + return getCurrentSubTree((ReportTreeNode) ((ReportTreeNode) this + .getRoot()).getChildAt(0)); + } + + public void clearTestPlan() { + super.removeNodeFromParent((ReportTreeNode) getChild(getRoot(), 0)); + initTree(); + } + + private void initTree() { + TestElement rp = new ReportGui().createTestElement(); + this.insertNodeInto(new ReportTreeNode(rp, this), + (ReportTreeNode) getRoot(), 0); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeNode.java b/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeNode.java new file mode 100644 index 0000000..93ec133 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/gui/tree/ReportTreeNode.java @@ -0,0 +1,161 @@ +/* + * 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.report.gui.tree; + +import java.awt.Image; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.util.Collection; + +import javax.swing.ImageIcon; +import javax.swing.JPopupMenu; +import javax.swing.tree.DefaultMutableTreeNode; + +import org.apache.jmeter.gui.GUIFactory; +import org.apache.jmeter.gui.ReportGuiPackage; +import org.apache.jmeter.gui.tree.NamedTreeNode; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class ReportTreeNode extends DefaultMutableTreeNode implements + NamedTreeNode { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final ReportTreeModel treeModel; + + // boolean enabled = true; + + public ReportTreeNode() {// Allow serializable test to work + // TODO: is the serializable test necessary now that JMeterTreeNode is + // no longer a GUI component? + this(null, null); + } + + public ReportTreeNode(TestElement userObj, ReportTreeModel treeModel) { + super(userObj); + this.treeModel = treeModel; + } + + public boolean isEnabled() { + return ((AbstractTestElement) getTestElement()) + .getPropertyAsBoolean(TestElement.ENABLED); + } + + public void setEnabled(boolean enabled) { + getTestElement().setProperty( + new BooleanProperty(TestElement.ENABLED, enabled)); + treeModel.nodeChanged(this); + } + + public ImageIcon getIcon() { + return getIcon(true); + } + + public ImageIcon getIcon(boolean enabled) { + try { + if (getTestElement() instanceof TestBean) { + try { + Image img = Introspector.getBeanInfo( + getTestElement().getClass()).getIcon( + BeanInfo.ICON_COLOR_16x16); + // If icon has not been defined, then use GUI_CLASS property + if (img == null) {// + Object clazz = Introspector.getBeanInfo( + getTestElement().getClass()) + .getBeanDescriptor().getValue( + TestElement.GUI_CLASS); + if (clazz == null) { + log.error("Can't obtain GUI class for " + + getTestElement().getClass().getName()); + return null; + } + return GUIFactory.getIcon( + Class.forName((String) clazz), enabled); + } + return new ImageIcon(img); + } catch (IntrospectionException e1) { + log.error("Can't obtain icon", e1); + throw new org.apache.jorphan.util.JMeterError(e1); + } + } else { + return GUIFactory.getIcon(Class.forName(getTestElement() + .getPropertyAsString(TestElement.GUI_CLASS)), enabled); + } + } catch (ClassNotFoundException e) { + log.warn("Can't get icon for class " + getTestElement(), e); + return null; + } + } + + public Collection getMenuCategories() { + try { + return ReportGuiPackage.getInstance().getGui(getTestElement()) + .getMenuCategories(); + } catch (Exception e) { + log.error("Can't get popup menu for gui", e); + return null; + } + } + + public JPopupMenu createPopupMenu() { + try { + return ReportGuiPackage.getInstance().getGui(getTestElement()) + .createPopupMenu(); + } catch (Exception e) { + log.error("Can't get popup menu for gui", e); + return null; + } + } + + public TestElement getTestElement() { + return (TestElement) getUserObject(); + } + + public String getStaticLabel() { + return ReportGuiPackage.getInstance().getGui((TestElement) getUserObject()) + .getStaticLabel(); + } + + public String getDocAnchor() { + return ReportGuiPackage.getInstance().getGui((TestElement) getUserObject()) + .getDocAnchor(); + } + + /** {@inheritDoc} */ + public void setName(String name) { + ((TestElement) getUserObject()).setName(name); + } + + /** {@inheritDoc} */ + public String getName() { + return ((TestElement) getUserObject()).getName(); + } + + /** {@inheritDoc} */ + public void nameChanged() { + treeModel.nodeChanged(this); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/writers/AbstractReportWriter.java b/ApacheJmeter/src/org/apache/jmeter/report/writers/AbstractReportWriter.java new file mode 100644 index 0000000..18b7887 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/writers/AbstractReportWriter.java @@ -0,0 +1,99 @@ +/* + * 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.report.writers; + +import java.io.File; +import java.util.Calendar; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; + +/** + * The abstract report writer provides the common implementation for subclasses + * to reuse. + */ +public abstract class AbstractReportWriter extends AbstractTestElement implements ReportWriter { + + private static final long serialVersionUID = 240L; + public static final String TARGET_DIRECTORY = "ReportWriter.target.directory"; + + /** + * + */ + public AbstractReportWriter() { + super(); + } + + /** + * Subclasses need to implement this method and provide the necessary + * logic to produce a ReportSummary object and write the report + */ + public abstract ReportSummary writeReport(TestElement element); + + /** + * The method simply returns the target directory and doesn't + * validate it. the abstract class expects some other class will + * validate the target directory. + */ + public String getTargetDirectory() { + return getPropertyAsString(TARGET_DIRECTORY); + } + + /** + * Set the target directory where the report should be saved + */ + public void setTargetDirectory(String directory) { + setProperty(TARGET_DIRECTORY,directory); + } + + public void makeDirectory() { + File output = new File(getTargetDirectory()); + if (!output.exists() || !output.isDirectory()) { + if(!output.mkdir()) { + throw new IllegalStateException("Could not create directory:"+output.getAbsolutePath()); + } + } + } + + /** + * if the target output directory already exists, archive it + */ + public void archiveDirectory() { + File output = new File(getTargetDirectory()); + if (output.exists() && output.isDirectory()) { + // if the directory already exists and is a directory, + // we just renamed to "archive.date" + if(!output.renameTo(new File("archive." + getDayString()))) { + throw new IllegalStateException("Could not rename directory:"+output.getAbsolutePath()+ + " to archive." + getDayString()); + } + } + } + + /** + * return the day in YYYYMMDD format + * @return the date + */ + public String getDayString() { + Calendar today = Calendar.getInstance(); + String year = String.valueOf(today.get(Calendar.YEAR)); + String month = String.valueOf(today.get(Calendar.MONTH)); + String day = String.valueOf(today.get(Calendar.DATE)); + return year + month + day; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/writers/DefaultPageSummary.java b/ApacheJmeter/src/org/apache/jmeter/report/writers/DefaultPageSummary.java new file mode 100644 index 0000000..3840be0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/writers/DefaultPageSummary.java @@ -0,0 +1,109 @@ +/* + * 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.report.writers; + +/** + * This is a basic implementation of PageSummary interface. + */ +public class DefaultPageSummary implements PageSummary { + + private long START = 0; + private long END = 0; + private String title; + private String fileName; + private boolean success; + + /** + * + */ + public DefaultPageSummary() { + super(); + } + + /** + * Returns the elapsed time in milliseconds + */ + public long getElapsedTime() { + return END - START; + } + + public long getEndTimeStamp() { + return END; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.writers.PageSummary#getFileName() + */ + public String getFileName() { + return fileName; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.writers.PageSummary#getPageTitle() + */ + public String getPageTitle() { + return title; + } + + public long getStartTimeStamp() { + return START; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.writers.PageSummary#isSuccessful() + */ + public boolean isSuccessful() { + return success; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.writers.PageSummary#pageStarted() + */ + public void pageStarted() { + START = System.currentTimeMillis(); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.writers.PageSummary#pageEnded() + */ + public void pageEnded() { + END = System.currentTimeMillis(); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.writers.PageSummary#setFileName(java.lang.String) + */ + public void setFileName(String file) { + this.fileName = file; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.writers.PageSummary#setPageTitle(java.lang.String) + */ + public void setPageTitle(String title) { + this.title = title; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.report.writers.PageSummary#setSuccessful(boolean) + */ + public void setSuccessful(boolean success) { + this.success = success; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/writers/DefaultReportSummary.java b/ApacheJmeter/src/org/apache/jmeter/report/writers/DefaultReportSummary.java new file mode 100644 index 0000000..ced3107 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/writers/DefaultReportSummary.java @@ -0,0 +1,75 @@ +/* + * 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.report.writers; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * The default implementation of ReportSummary just contains the stats + * and basic information. It doesn't contain the actual report. In the + * future we may want to implement a version with all the details to + * display in a Swing GUI. + */ +public class DefaultReportSummary implements ReportSummary { + + private final ArrayList pages = new ArrayList(); + + /** + * + */ + public DefaultReportSummary() { + super(); + } + + /** + * Add a PageSummary to the report + */ + public void addPageSummary(PageSummary summary) { + this.pages.add(summary); + } + + /** + * current implementation simply iterates over the Page summaries + * and adds the times. + */ + public long getElapsedTime() { + long elpasedTime = 0; + Iterator itr = this.pages.iterator(); + while (itr.hasNext()) { + elpasedTime += itr.next().getElapsedTime(); + } + return elpasedTime; + } + + /** + * The current implementation calls ArrayList.toArray(Object[]) + */ + public PageSummary[] getPagesSummaries() { + PageSummary[] ps = new PageSummary[this.pages.size()]; + return this.pages.toArray(ps); + } + + /** + * remove a PageSummary + */ + public void removePageSummary(PageSummary summary) { + this.pages.remove(summary); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/writers/HTMLReportWriter.java b/ApacheJmeter/src/org/apache/jmeter/report/writers/HTMLReportWriter.java new file mode 100644 index 0000000..48ee273 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/writers/HTMLReportWriter.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.jmeter.report.writers; + +import org.apache.jmeter.testelement.TestElement; + +/** + * HTMLReportWriter is a basic report writer that produces HTML pages. + * It contains all the necessary helper method to write out the report. + * + */ +public class HTMLReportWriter extends AbstractReportWriter { + + private static final long serialVersionUID = 240L; + + public HTMLReportWriter() { + super(); + } + + @Override + public ReportSummary writeReport(TestElement element) { + return null; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/writers/PageSummary.java b/ApacheJmeter/src/org/apache/jmeter/report/writers/PageSummary.java new file mode 100644 index 0000000..9094c8e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/writers/PageSummary.java @@ -0,0 +1,38 @@ +/* + * 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.report.writers; + +/** + * PageSummary defines the summary of a report page and the runtime + * information for debugging and logging purposes. It's a good idea + * to return summary so that automated process can start the report + * and a summary of how the reports ran. + */ +public interface PageSummary extends Cloneable { + long getElapsedTime(); + long getEndTimeStamp(); + String getFileName(); + String getPageTitle(); + long getStartTimeStamp(); + boolean isSuccessful(); + void pageStarted(); + void pageEnded(); + void setFileName(String file); + void setPageTitle(String title); + void setSuccessful(boolean success); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/writers/ReportSummary.java b/ApacheJmeter/src/org/apache/jmeter/report/writers/ReportSummary.java new file mode 100644 index 0000000..ff714b1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/writers/ReportSummary.java @@ -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. + * + */ +package org.apache.jmeter.report.writers; + +/** + * + * The purpose of ReportSummary is to provide a detailed description of the + * reports generated, how long it took and where the generated files are + * located. + */ +public interface ReportSummary extends Cloneable { + /** + * Add a page summary to the report summary + * @param summary + */ + void addPageSummary(PageSummary summary); + /** + * This should be the elapsed time to run all the reports. Classes + * implementing it should simply add up the elapsed time for each + * report page. + * @return elapsed time + */ + long getElapsedTime(); + /** + * The method should return a list of the pages generated for the + * report and whether it succeeded or not + * @return page summary array + */ + PageSummary[] getPagesSummaries(); + /** + * Remove a page summary from the report summary. + * @param summary + */ + void removePageSummary(PageSummary summary); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/writers/ReportWriter.java b/ApacheJmeter/src/org/apache/jmeter/report/writers/ReportWriter.java new file mode 100644 index 0000000..3eb19b2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/writers/ReportWriter.java @@ -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.report.writers; + +import org.apache.jmeter.testelement.TestElement; + +/** + * + * ReportWriter defines the basic operations of a report writer. A report + * plan may have multiple report writers. it might be nice to have a pdf + * writer in the future. + */ +public interface ReportWriter { + ReportSummary writeReport(TestElement element); + String getTargetDirectory(); + void setTargetDirectory(String directory); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/report/writers/gui/HTMLReportWriterGui.java b/ApacheJmeter/src/org/apache/jmeter/report/writers/gui/HTMLReportWriterGui.java new file mode 100644 index 0000000..a0461e9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/report/writers/gui/HTMLReportWriterGui.java @@ -0,0 +1,101 @@ +/* + * 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.report.writers.gui; + +import java.awt.BorderLayout; +import java.awt.Color; + +import javax.swing.JPanel; +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.util.ReportFilePanel; +import org.apache.jmeter.gui.util.ReportMenuFactory; +import org.apache.jmeter.report.gui.AbstractReportGui; +import org.apache.jmeter.report.writers.HTMLReportWriter; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +public class HTMLReportWriterGui extends AbstractReportGui { + + private static final long serialVersionUID = 240L; + + private ReportFilePanel outputDirectory = new ReportFilePanel( + JMeterUtils.getResString("report_output_directory"), "*"); + + public HTMLReportWriterGui() { + super(); + init(); + } + + @Override + public String getLabelResource() { + return "report_writer_html"; + } + + @Override + public JPopupMenu createPopupMenu() { + JPopupMenu pop = new JPopupMenu(); + ReportMenuFactory.addFileMenu(pop); + ReportMenuFactory.addEditMenu(pop,true); + return pop; + } + + /** + * init creates the necessary gui stuff. + */ + private void init() {// called from ctor, so must not be overridable + setLayout(new BorderLayout(10, 10)); + setBorder(makeBorder()); + setBackground(Color.white); + + JPanel pane = new JPanel(); + pane.setLayout(new BorderLayout(10,10)); + pane.setBackground(Color.white); + pane.add(this.getNamePanel(),BorderLayout.NORTH); + + outputDirectory.setBackground(Color.white); + + pane.add(outputDirectory,BorderLayout.SOUTH); + add(pane,BorderLayout.NORTH); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + HTMLReportWriter element = new HTMLReportWriter(); + modifyTestElement(element); + return element; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(org.apache.jmeter.testelement.TestElement) + */ + public void modifyTestElement(TestElement element) { + this.configureTestElement(element); + HTMLReportWriter wr = (HTMLReportWriter)element; + wr.setTargetDirectory(outputDirectory.getFilename()); + } + + @Override + public void configure(TestElement element) { + super.configure(element); + HTMLReportWriter wr = (HTMLReportWriter)element; + outputDirectory.setFilename(wr.getTargetDirectory()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/AbstractListenerElement.java b/ApacheJmeter/src/org/apache/jmeter/reporters/AbstractListenerElement.java new file mode 100644 index 0000000..f45dd95 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/AbstractListenerElement.java @@ -0,0 +1,57 @@ +/* + * 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.reporters; + +import java.lang.ref.WeakReference; + +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.visualizers.Visualizer; + +/** + * Base class for Listeners + */ + +public abstract class AbstractListenerElement extends AbstractTestElement { + private static final long serialVersionUID = 240L; + + // TODO should class implement SampleListener? + private transient WeakReference listener; + + public AbstractListenerElement() { + } + + protected final Visualizer getVisualizer() { + if (listener == null){ // e.g. in non-GUI mode + return null; + } + return listener.get(); + } + + public void setListener(Visualizer vis) { + listener = new WeakReference(vis); + } + + @Override + public Object clone() { + AbstractListenerElement clone = (AbstractListenerElement) super.clone(); + + clone.listener=this.listener; + return clone; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/FileReporter.java b/ApacheJmeter/src/org/apache/jmeter/reporters/FileReporter.java new file mode 100644 index 0000000..cda294f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/FileReporter.java @@ -0,0 +1,378 @@ +/* + * 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.reporters; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * This class loads data from a saved file and displays statistics about it. + * + * + * @version $Revision: 1226919 $ + */ +public class FileReporter extends JPanel { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Map> data = new ConcurrentHashMap>(); + + /** initalize a file reporter from a file */ + public void init(String file) throws IOException { + File datafile = new File(file); + BufferedReader reader = null; + + try { + if (datafile.canRead()) { + reader = new BufferedReader(new FileReader(datafile)); + } else { + JOptionPane.showMessageDialog(null, "The file you specified cannot be read.", "Information", + JOptionPane.INFORMATION_MESSAGE); + return; + } + String line; + + while ((line = reader.readLine()) != null) { + try { + line = line.trim(); + if (line.startsWith("#") || line.length() == 0) { + continue; + } + int splitter = line.lastIndexOf(' '); + String key = line.substring(0, splitter); + int len = line.length() - 1; + Integer value = null; + + if (line.charAt(len) == ',') { + value = Integer.valueOf(line.substring(splitter + 1, len)); + } else { + value = Integer.valueOf(line.substring(splitter + 1)); + } + List v = getData(key); + + if (v == null) { + v = Collections.synchronizedList(new ArrayList()); + this.data.put(key, v); + } + v.add(value); + } catch (NumberFormatException nfe) { + log.error("This line could not be parsed: " + line, nfe); + } catch (Exception e) { + log.error("This line caused a problem: " + line, e); + } + } + } finally { + JOrphanUtils.closeQuietly(reader); + } + showPanel(); + } + + public List getData(String key) { + return data.get(key); + } + + /** + * Show main panel with length, graph, and stats. + */ + public void showPanel() { + JFrame f = new JFrame("Data File Report"); + + setLayout(new BorderLayout()); + GraphPanel gp = new GraphPanel(data); + + add(gp, "Center"); + add(gp.getStats(), BorderLayout.EAST); + add(gp.getLegend(), BorderLayout.NORTH); + f.setSize(500, 300); + f.getContentPane().add(this); + f.setVisible(true); + } + +/** + * Graph panel generates all the panels for this reporter. Data is organized + * based on thread name in a hashtable. The data itself is a List of Integer + * objects + */ +private static class GraphPanel extends JPanel { + private static final long serialVersionUID = 240L; + + // boolean autoScale = true; + private final Map> data; + + private final List keys = Collections.synchronizedList(new ArrayList()); + + private final List colorList = Collections.synchronizedList(new ArrayList()); + + public GraphPanel(Map> data) { + this.data = data; + for (String key : data.keySet()) { + keys.add(key); + } + for (int a = 0x33; a < 0xFF; a += 0x66) { + for (int b = 0x33; b < 0xFF; b += 0x66) { + for (int c = 0x33; c < 0xFF; c += 0x66) { + colorList.add(new Color(a, b, c)); + } + } + } + } + + /** + * Get the maximum for all the data. + */ + public float getMax() { + float maxValue = 0; + + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + List temp = data.get(key); + + for (int j = 0; j < temp.size(); j++) { + float f = temp.get(j).intValue(); + + maxValue = Math.max(f, maxValue); + } + } + return (float) (maxValue + maxValue * 0.1); + } + + /** + * Get the minimum for all the data. + */ + public float getMin() { + float minValue = 9999999; + + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + List temp = data.get(key); + + for (int j = 0; j < temp.size(); j++) { + float f = temp.get(j).intValue(); + + minValue = Math.min(f, minValue); + } + } + return (float) (minValue - minValue * 0.1); + } + + /** + * Get the legend panel. + */ + public JPanel getLegend() { + JPanel main = new JPanel(); + GridBagLayout g = new GridBagLayout(); + + main.setLayout(g); + GridBagConstraints c = new GridBagConstraints(); + + c.insets = new Insets(3, 3, 3, 3); + c.fill = GridBagConstraints.BOTH; + c.gridwidth = 1; + c.gridheight = 1; + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + JLabel colorSwatch = new JLabel(" "); + + colorSwatch.setBackground(colorList.get(t % colorList.size())); + colorSwatch.setOpaque(true); + c.gridx = 1; + c.gridy = t; + g.setConstraints(colorSwatch, c); + main.add(colorSwatch); + JLabel name = new JLabel(key); + + c.gridx = 2; + c.gridy = t; + g.setConstraints(name, c); + main.add(name); + } + return main; + } + + /** + * Get the stats panel. + */ + public JPanel getStats() { + int total = 0; + float totalValue = 0; + float maxValue = 0; + float minValue = 999999; + + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + List temp = data.get(key); + + for (int j = 0; j < temp.size(); j++) { + float f = temp.get(j).intValue(); + + minValue = Math.min(f, minValue); + maxValue = Math.max(f, maxValue); + totalValue += f; + total++; + } + } + float averageValue = totalValue / total; + JPanel main = new JPanel(); + GridBagLayout g = new GridBagLayout(); + + main.setLayout(g); + DecimalFormat df = new DecimalFormat("#0.0"); + GridBagConstraints c = new GridBagConstraints(); + + c.insets = new Insets(3, 6, 3, 6); + c.fill = GridBagConstraints.BOTH; + c.gridwidth = 1; + c.gridheight = 1; + JLabel count = new JLabel("Count: " + total); + + c.gridx = 1; + c.gridy = 1; + g.setConstraints(count, c); + JLabel min = new JLabel("Min: " + df.format(Float.valueOf(minValue))); + + c.gridx = 1; + c.gridy = 2; + g.setConstraints(min, c); + JLabel max = new JLabel("Max: " + df.format(Float.valueOf(maxValue))); + + c.gridx = 1; + c.gridy = 3; + g.setConstraints(max, c); + JLabel average = new JLabel("Average: " + df.format(Float.valueOf(averageValue))); + + c.gridx = 1; + c.gridy = 4; + g.setConstraints(average, c); + main.add(count); + main.add(min); + main.add(max); + main.add(average); + return main; + } + + /** + * Gets the size of the biggest List. + */ + public int getDataWidth() { + int size = 0; + + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + size = Math.max(size, data.get(key).size()); + } + return size; + } + + /** + * Draws the graph. + */ + @Override + public void update(Graphics g) { + // setup drawing area + int base = 10; + + g.setColor(Color.white); + g.fillRect(0, 0, getSize().width, getSize().height); + int width = getSize().width; + int height = getSize().height; + float maxValue = getMax(); + float minValue = getMin(); + + // draw grid + g.setColor(Color.gray); + int dataWidth = getDataWidth(); + int increment = Math.round((float)(width - 1) / (dataWidth - 1)); + + /* + * for (int t = 0; t < dataWidth; t++) { g.drawLine(t * increment, 0, t * + * increment, height); } + */ + int yIncrement = Math.round(((float) height - (1 + base)) / (10 - 1)); + + /* + * for (int t = 0; t < 10; t++) { g.drawLine(0, height - t * yIncrement, + * width, height - t * yIncrement); } + */ + // draw axis + for (int t = 1; t < dataWidth; t += (dataWidth / 25 + 1)) { + g.drawString((Integer.valueOf(t)).toString(), t * increment + 2, height - 2); + } + float incrementValue = (maxValue - minValue) / (10 - 1); + + for (int t = 0; t < 10; t++) { + g.drawString(Integer.valueOf(Math.round(minValue + (t * incrementValue))).toString(), 2, height - t + * yIncrement - 2 - base); + } + // draw data lines + int start = 0; + + for (int t = 0; t < keys.size(); t++) { + String key = keys.get(t); + List v = data.get(key); + + start = 0; + g.setColor(colorList.get(t % colorList.size())); + for (int i = 0; i < v.size() - 1; i++) { + float y1 = v.get(i).intValue(); + float y2 = v.get(i + 1).intValue(); + + y1 = y1 - minValue; + y2 = y2 - minValue; + int Y1 = Math.round((height * y1) / (maxValue - minValue)); + int Y2 = Math.round((height * y2) / (maxValue - minValue)); + + Y1 = height - Y1 - base; + Y2 = height - Y2 - base; + g.drawLine(start, Y1, start + increment, Y2); + + start += increment; + } + } + } + + @Override + public void paint(Graphics g) { + update(g); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/MailerModel.java b/ApacheJmeter/src/org/apache/jmeter/reporters/MailerModel.java new file mode 100644 index 0000000..c16e7da --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/MailerModel.java @@ -0,0 +1,526 @@ +/* + * 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.reporters; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.StringTokenizer; + +import javax.mail.Authenticator; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The model for a MailerVisualizer. + * + */ +public class MailerModel extends AbstractTestElement implements Serializable { + public static enum MailAuthType { + SSL, + TLS, + NONE; + } + + private static final long serialVersionUID = 270L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String MAIL_SMTP_HOST = "mail.smtp.host"; //$NON-NLS-1$ + + private static final String MAIL_SMTP_PORT = "mail.smtp.port"; //$NON-NLS-1$ + + private static final String MAIL_SMTP_AUTH = "mail.smtp.auth"; //$NON-NLS-1$ + + private static final String MAIL_SMTP_SOCKETFACTORY_CLASS = "mail.smtp.socketFactory.class"; //$NON-NLS-1$ + + private static final String MAIL_SMTP_STARTTLS = "mail.smtp.starttls.enable"; //$NON-NLS-1$ + + private long failureCount = 0; + + private long successCount = 0; + + private boolean failureMsgSent = false; + + private boolean siteDown = false; + + private boolean successMsgSent = false; + + private static final String FROM_KEY = "MailerModel.fromAddress"; //$NON-NLS-1$ + + private static final String TO_KEY = "MailerModel.addressie"; //$NON-NLS-1$ + + private static final String HOST_KEY = "MailerModel.smtpHost"; //$NON-NLS-1$ + + private static final String PORT_KEY = "MailerModel.smtpPort"; //$NON-NLS-1$ + + private static final String SUCCESS_SUBJECT = "MailerModel.successSubject"; //$NON-NLS-1$ + + private static final String FAILURE_SUBJECT = "MailerModel.failureSubject"; //$NON-NLS-1$ + + private static final String FAILURE_LIMIT_KEY = "MailerModel.failureLimit"; //$NON-NLS-1$ + + private static final String SUCCESS_LIMIT_KEY = "MailerModel.successLimit"; //$NON-NLS-1$ + + private static final String LOGIN = "MailerModel.login"; //$NON-NLS-1$ + + private static final String PASSWORD = "MailerModel.password"; //$NON-NLS-1$ + + private static final String MAIL_AUTH_TYPE = "MailerModel.authType"; //$NON-NLS-1$ + + private static final String DEFAULT_LIMIT = "2"; //$NON-NLS-1$ + + private static final String DEFAULT_SMTP_PORT = "25"; + + private static final String DEFAULT_PASSWORD_VALUE = ""; //$NON-NLS-1$ + + private static final String DEFAULT_MAIL_AUTH_TYPE_VALUE = MailAuthType.NONE.toString(); //$NON-NLS-1$ + + private static final String DEFAULT_LOGIN_VALUE = ""; //$NON-NLS-1$ + + /** The listener for changes. */ + private transient ChangeListener changeListener; + + /** + * Constructs a MailerModel. + */ + public MailerModel() { + super(); + + setProperty(SUCCESS_LIMIT_KEY, JMeterUtils.getPropDefault("mailer.successlimit", DEFAULT_LIMIT)); //$NON-NLS-1$ + setProperty(FAILURE_LIMIT_KEY, JMeterUtils.getPropDefault("mailer.failurelimit", DEFAULT_LIMIT)); //$NON-NLS-1$ + } + + public void addChangeListener(ChangeListener list) { + changeListener = list; + } + + /** {@inheritDoc} */ + @Override + public Object clone() { + MailerModel m = (MailerModel) super.clone(); + m.changeListener = changeListener; + return m; + } + + public void notifyChangeListeners() { + if (changeListener != null) { + changeListener.stateChanged(new ChangeEvent(this)); + } + } + + /** + * Gets a List of String-objects. Each String is one mail-address of the + * addresses-String set by setToAddress(str). The addresses + * must be seperated by commas. Only String-objects containing a "@" are + * added to the returned List. + * + * @return a List of String-objects wherein each String represents a + * mail-address. + */ + public List getAddressList() { + String addressees = getToAddress(); + List addressList = new ArrayList(); + + if (addressees != null) { + + StringTokenizer next = new StringTokenizer(addressees, ","); //$NON-NLS-1$ + + while (next.hasMoreTokens()) { + String theToken = next.nextToken().trim(); + + if (theToken.indexOf("@") > 0) { //$NON-NLS-1$ + addressList.add(theToken); + } else { + log.warn("Ignored unexpected e-mail address: "+theToken); + } + } + } + + return addressList; + } + + /** + * Adds a SampleResult for display in the Visualizer. + * + * @param sample + * the SampleResult encapsulating informations about the last + * sample. + */ + public void add(SampleResult sample) { + add(sample, false); + } + + /** + * Adds a SampleResult. If SampleResult represents a change concerning the + * failure/success of the sampling a message might be sent to the addressies + * according to the settings of successCount and + * failureCount. + * + * @param sample + * the SampleResult encapsulating information about the last + * sample. + * @param sendMails whether or not to send e-mails + */ + public synchronized void add(SampleResult sample, boolean sendMails) { + + // -1 is the code for a failed sample. + // + if (!sample.isSuccessful()) { + failureCount++; + successCount = 0; + } else { + successCount++; + } + + if (sendMails && (failureCount > getFailureLimit()) && !siteDown && !failureMsgSent) { + // Send the mail ... + List addressList = getAddressList(); + + if (addressList.size() != 0) { + try { + sendMail(getFromAddress(), addressList, getFailureSubject(), "URL Failed: " + + sample.getSampleLabel(), getSmtpHost(), + getSmtpPort(), getLogin(), getPassword(), + getMailAuthType(), false); + } catch (Exception e) { + log.error("Problem sending mail: "+e); + } + siteDown = true; + failureMsgSent = true; + successCount = 0; + successMsgSent = false; + } + } + + if (sendMails && siteDown && (sample.getTime() != -1) && !successMsgSent) { + // Send the mail ... + if (successCount > getSuccessLimit()) { + List addressList = getAddressList(); + + try { + sendMail(getFromAddress(), addressList, getSuccessSubject(), "URL Restarted: " + + sample.getSampleLabel(), getSmtpHost(), + getSmtpPort(), getLogin(), getPassword(), + getMailAuthType(), false); + } catch (Exception e) { + log.error("Problem sending mail", e); + } + siteDown = false; + successMsgSent = true; + failureCount = 0; + failureMsgSent = false; + } + } + + if (successMsgSent && failureMsgSent) { + clear(); + } + notifyChangeListeners(); + } + + + + /** + * Resets the state of this object to its default. But: This method does not + * reset any mail-specific attributes (like sender, mail-subject...) since + * they are independent of the sampling. + */ + @Override + public synchronized void clear() {// TODO: should this be clearData()? + failureCount = 0; + successCount = 0; + siteDown = false; + successMsgSent = false; + failureMsgSent = false; + notifyChangeListeners(); + } + + /** + * Returns a String-representation of this object. Returns always + * "E-Mail-Notification". Might be enhanced in future versions to return + * some kind of String-representation of the mail-parameters (like sender, + * addressies, smtpHost...). + * + * @return A String-representation of this object. + */ + @Override + public String toString() { + return "E-Mail Notification"; + } + + /** + * Sends a mail with the given parameters using SMTP. + * + * @param from + * the sender of the mail as shown in the mail-client. + * @param vEmails + * all receivers of the mail. The receivers are seperated by + * commas. + * @param subject + * the subject of the mail. + * @param attText + * the message-body. + * @param smtpHost + * the smtp-server used to send the mail. + * @throws MessagingException + * @throws AddressException + */ + public void sendMail(String from, List vEmails, String subject, String attText, String smtpHost) + throws AddressException, MessagingException { + sendMail(from, vEmails, subject, attText, smtpHost, DEFAULT_SMTP_PORT, null, null, null, false); + } + + /** + * Sends a mail with the given parameters using SMTP. + * + * @param from + * the sender of the mail as shown in the mail-client. + * @param vEmails + * all receivers of the mail. The receivers are seperated by + * commas. + * @param subject + * the subject of the mail. + * @param attText + * the message-body. + * @param smtpHost + * the smtp-server used to send the mail. + * @param smtpPort the smtp-server port used to send the mail. + * @param user the login used to authenticate + * @param password the password used to authenticate + * @param mailAuthType {@link MailAuthType} Security policy + * @throws AddressException If mail address is wrong + * @throws MessagingException If building MimeMessage fails + */ + public void sendMail(String from, List vEmails, String subject, + String attText, String smtpHost, + String smtpPort, + final String user, + final String password, + MailAuthType mailAuthType, + boolean debug) + throws AddressException, MessagingException{ + + InternetAddress[] address = new InternetAddress[vEmails.size()]; + + for (int k = 0; k < vEmails.size(); k++) { + address[k] = new InternetAddress(vEmails.get(k)); + } + + // create some properties and get the default Session + Properties props = new Properties(); + + props.put(MAIL_SMTP_HOST, smtpHost); + props.put(MAIL_SMTP_PORT, smtpPort); // property values are strings + Authenticator authenticator = null; + if(mailAuthType != MailAuthType.NONE) { + props.put(MAIL_SMTP_AUTH, "true"); + switch (mailAuthType) { + case SSL: + props.put(MAIL_SMTP_SOCKETFACTORY_CLASS, + "javax.net.ssl.SSLSocketFactory"); + break; + case TLS: + props.put(MAIL_SMTP_STARTTLS, + "true"); + break; + + default: + break; + } + } + + if(!StringUtils.isEmpty(user)) { + authenticator = + new javax.mail.Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(user,password); + } + }; + } + Session session = Session.getInstance(props, authenticator); + session.setDebug(debug); + + // create a message + Message msg = new MimeMessage(session); + + msg.setFrom(new InternetAddress(from)); + msg.setRecipients(Message.RecipientType.TO, address); + msg.setSubject(subject); + msg.setText(attText); + Transport.send(msg); + } + + /** + * Send a Test Mail to check configuration + * @throws AddressException If mail address is wrong + * @throws MessagingException If building MimeMessage fails + */ + public synchronized void sendTestMail() throws AddressException, MessagingException { + String to = getToAddress(); + String from = getFromAddress(); + String subject = "Testing mail-addresses"; + String smtpHost = getSmtpHost(); + String attText = "JMeter-Testmail" + "\n" + "To: " + to + "\n" + "From: " + from + "\n" + "Via: " + smtpHost + + "\n" + "Fail Subject: " + getFailureSubject() + "\n" + "Success Subject: " + getSuccessSubject(); + + log.info(attText); + + sendMail(from, getAddressList(), subject, attText, smtpHost, + getSmtpPort(), + getLogin(), + getPassword(), + getMailAuthType(), + true); + log.info("Test mail sent successfully!!"); + } + + // //////////////////////////////////////////////////////////// + // + // setter/getter - JavaDoc-Comments not needed... + // + // //////////////////////////////////////////////////////////// + + public void setToAddress(String str) { + setProperty(TO_KEY, str); + } + + public void setFromAddress(String str) { + setProperty(FROM_KEY, str); + } + + public void setSmtpHost(String str) { + setProperty(HOST_KEY, str); + } + + public void setSmtpPort(String value) { + if(StringUtils.isEmpty(value)) { + value = DEFAULT_SMTP_PORT; + } + setProperty(PORT_KEY, value, DEFAULT_SMTP_PORT); + } + + public void setLogin(String login) { + setProperty(LOGIN, login, DEFAULT_LOGIN_VALUE); + } + + public void setPassword(String password) { + setProperty(PASSWORD, password, DEFAULT_PASSWORD_VALUE); + } + + public void setMailAuthType(String value) { + setProperty(MAIL_AUTH_TYPE, value, DEFAULT_MAIL_AUTH_TYPE_VALUE); + } + + public void setFailureSubject(String str) { + setProperty(FAILURE_SUBJECT, str); + } + + public void setSuccessSubject(String str) { + setProperty(SUCCESS_SUBJECT, str); + } + + public void setSuccessLimit(String limit) { + setProperty(SUCCESS_LIMIT_KEY, limit); + } + + // private void setSuccessCount(long count) + // { + // this.successCount = count; + // } + + public void setFailureLimit(String limit) { + setProperty(FAILURE_LIMIT_KEY, limit); + } + + // private void setFailureCount(long count) + // { + // this.failureCount = count; + // } + + public String getToAddress() { + return getPropertyAsString(TO_KEY); + } + + public String getFromAddress() { + return getPropertyAsString(FROM_KEY); + } + + public String getSmtpHost() { + return getPropertyAsString(HOST_KEY); + } + + public String getSmtpPort() { + return getPropertyAsString(PORT_KEY, DEFAULT_SMTP_PORT); + } + + public String getFailureSubject() { + return getPropertyAsString(FAILURE_SUBJECT); + } + + public String getSuccessSubject() { + return getPropertyAsString(SUCCESS_SUBJECT); + } + + public long getSuccessLimit() { + return getPropertyAsLong(SUCCESS_LIMIT_KEY); + } + + public long getSuccessCount() { + return successCount; + } + + public long getFailureLimit() { + return getPropertyAsLong(FAILURE_LIMIT_KEY); + } + + public long getFailureCount() { + return this.failureCount; + } + + public String getLogin() { + return getPropertyAsString(LOGIN, DEFAULT_LOGIN_VALUE); + } + + public String getPassword() { + return getPropertyAsString(PASSWORD, DEFAULT_PASSWORD_VALUE); + } + + public MailAuthType getMailAuthType() { + String authType = getPropertyAsString(MAIL_AUTH_TYPE, DEFAULT_MAIL_AUTH_TYPE_VALUE); + return MailAuthType.valueOf(authType); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/MailerResultCollector.java b/ApacheJmeter/src/org/apache/jmeter/reporters/MailerResultCollector.java new file mode 100644 index 0000000..2d36188 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/MailerResultCollector.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.jmeter.reporters; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.testelement.property.TestElementProperty; + + +public class MailerResultCollector extends ResultCollector implements Serializable { + private static final long serialVersionUID = 240L; + + public static final String MAILER_MODEL = "MailerResultCollector.mailer_model"; //$NON-NLS-1$ + + public MailerResultCollector() { + super(); + setProperty(new TestElementProperty(MAILER_MODEL, new MailerModel())); + } + + /** {@inheritDoc} */ + @Override + public void clear() { + super.clear(); + setProperty(new TestElementProperty(MAILER_MODEL, new MailerModel())); + } + + /** {@inheritDoc} */ + @Override + public void sampleOccurred(SampleEvent e) { + super.sampleOccurred(e); // sends the result to the visualiser + getMailerModel().add(e.getResult(), true); // updates the model used for sending e-mails + } + + public MailerModel getMailerModel() { + return (MailerModel) getProperty(MAILER_MODEL).getObjectValue(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/ResultAction.java b/ApacheJmeter/src/org/apache/jmeter/reporters/ResultAction.java new file mode 100644 index 0000000..5d28fea --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/ResultAction.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.reporters; + +import java.io.Serializable; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.OnErrorTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * ResultAction - take action based on the status of the last Result + * + */ +public class ResultAction extends OnErrorTestElement implements Serializable, SampleListener { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /* + * Constructor is initially called once for each occurrence in the test plan + * For GUI, several more instances are created Then clear is called at start + * of test Called several times during test startup The name will not + * necessarily have been set at this point. + */ + public ResultAction() { + super(); + // log.debug(Thread.currentThread().getName()); + // System.out.println(">> "+me+" "+this.getName()+" + // "+Thread.currentThread().getName()); + } + + /** + * Examine the sample(s) and take appropriate action + * + * @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent) + */ + public void sampleOccurred(SampleEvent e) { + SampleResult s = e.getResult(); + log.debug(s.getSampleLabel() + " OK? " + s.isSuccessful()); + if (!s.isSuccessful()) { + if (isStopTestNow()) { + s.setStopTestNow(true); + } + if (isStopTest()) { + s.setStopTest(true); + } + if (isStopThread()) { + s.setStopThread(true); + } + } + } + + /** + * {@inheritDoc} + */ + public void sampleStarted(SampleEvent e) { + // not used + } + + /** + * {@inheritDoc} + */ + public void sampleStopped(SampleEvent e) { + // not used + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/ResultCollector.java b/ApacheJmeter/src/org/apache/jmeter/reporters/ResultCollector.java new file mode 100644 index 0000000..0a07d54 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/ResultCollector.java @@ -0,0 +1,604 @@ +/* + * 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.reporters; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.save.OldSaveService; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.ObjectProperty; +import org.apache.jmeter.visualizers.Visualizer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.xml.sax.SAXException; + +import com.thoughtworks.xstream.converters.ConversionException; + +/** + * This class handles all saving of samples. + * The class must be thread-safe because it is shared between threads (NoThreadClone). + */ +public class ResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable, + TestListener, Remoteable, NoThreadClone { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 233L; + + // This string is used to identify local test runs, so must not be a valid host name + private static final String TEST_IS_LOCAL = "*local*"; // $NON-NLS-1$ + + private static final String TESTRESULTS_START = ""; // $NON-NLS-1$ + + private static final String TESTRESULTS_START_V1_1_PREVER = ""; // $NON-NLS-1$ + + private static final String TESTRESULTS_END = ""; // $NON-NLS-1$ + + private static final String XML_HEADER = ""; // $NON-NLS-1$ + + private static final int MIN_XML_FILE_LEN = XML_HEADER.length() + TESTRESULTS_START.length() + + TESTRESULTS_END.length(); + + public static final String FILENAME = "filename"; // $NON-NLS-1$ + + private static final String SAVE_CONFIG = "saveConfig"; // $NON-NLS-1$ + + private static final String ERROR_LOGGING = "ResultCollector.error_logging"; // $NON-NLS-1$ + + private static final String SUCCESS_ONLY_LOGGING = "ResultCollector.success_only_logging"; // $NON-NLS-1$ + + // Static variables + + // Lock used to guard static mutable variables + private static final Object LOCK = new Object(); + + //@GuardedBy("LOCK") + private static final Map files = new HashMap(); + + /* + * Keep track of the file writer and the configuration, + * as the instance used to close them is not the same as the instance that creates + * them. This means one cannot use the saved PrintWriter or use getSaveConfig() + */ + private static class FileEntry{ + final PrintWriter pw; + final SampleSaveConfiguration config; + FileEntry(PrintWriter _pw, SampleSaveConfiguration _config){ + pw =_pw; + config = _config; + } + } + + /** + * The instance count is used to keep track of whether any tests are currently running. + * It's not possible to use the constructor or threadStarted etc as tests may overlap + * e.g. a remote test may be started, + * and then a local test started whilst the remote test is still running. + */ + //@GuardedBy("LOCK") + private static int instanceCount; // Keep track of how many instances are active + + // Instance variables (guarded by volatile) + + private transient volatile PrintWriter out; + + private volatile boolean inTest = false; + + private volatile boolean isStats = false; + + /** the summarizer to which this result collector will forward the samples */ + private volatile Summariser summariser; + + /** + * No-arg constructor. + */ + public ResultCollector() { + this(null); + } + + public ResultCollector(Summariser summer) { + setErrorLogging(false); + setSuccessOnlyLogging(false); + setProperty(new ObjectProperty(SAVE_CONFIG, new SampleSaveConfiguration())); + summariser = summer; + } + + // Ensure that the sample save config is not shared between copied nodes + // N.B. clone only seems to be used for client-server tests + @Override + public Object clone(){ + ResultCollector clone = (ResultCollector) super.clone(); + clone.setSaveConfig((SampleSaveConfiguration)clone.getSaveConfig().clone()); + // Unfortunately AbstractTestElement does not call super.clone() + clone.summariser = this.summariser; + return clone; + } + + private void setFilenameProperty(String f) { + setProperty(FILENAME, f); + } + + public String getFilename() { + return getPropertyAsString(FILENAME); + } + + public boolean isErrorLogging() { + return getPropertyAsBoolean(ERROR_LOGGING); + } + + public final void setErrorLogging(boolean errorLogging) { + setProperty(new BooleanProperty(ERROR_LOGGING, errorLogging)); + } + + public final void setSuccessOnlyLogging(boolean value) { + if (value) { + setProperty(new BooleanProperty(SUCCESS_ONLY_LOGGING, true)); + } else { + removeProperty(SUCCESS_ONLY_LOGGING); + } + } + + public boolean isSuccessOnlyLogging() { + return getPropertyAsBoolean(SUCCESS_ONLY_LOGGING,false); + } + + /** + * Decides whether or not to a sample is wanted based on:
+ * - errorOnly
+ * - successOnly
+ * - sample success
+ * Should only be called for single samples. + * + * @param success is sample successful + * @return whether to log/display the sample + */ + public boolean isSampleWanted(boolean success){ + boolean errorOnly = isErrorLogging(); + boolean successOnly = isSuccessOnlyLogging(); + return isSampleWanted(success, errorOnly, successOnly); + } + + /** + * Decides whether or not to a sample is wanted based on:
+ * - errorOnly
+ * - successOnly
+ * - sample success
+ * This version is intended to be called by code that loops over many samples; + * it is cheaper than fetching the settings each time. + * @param success status of sample + * @param errorOnly if errors only wanted + * @param successOnly if success only wanted + * @return whether to log/display the sample + */ + public static boolean isSampleWanted(boolean success, boolean errorOnly, + boolean successOnly) { + return (!errorOnly && !successOnly) || + (success && successOnly) || + (!success && errorOnly); + // successOnly and errorOnly cannot both be set + } + /** + * Sets the filename attribute of the ResultCollector object. + * + * @param f + * the new filename value + */ + public void setFilename(String f) { + if (inTest) { + return; + } + setFilenameProperty(f); + } + + public void testEnded(String host) { + synchronized(LOCK){ + instanceCount--; + if (instanceCount <= 0) { + finalizeFileOutput(); + inTest = false; + } + } + + if(summariser != null) { + summariser.testEnded(host); + } + } + + public void testStarted(String host) { + synchronized(LOCK){ + instanceCount++; + try { + initializeFileOutput(); + if (getVisualizer() != null) { + this.isStats = getVisualizer().isStats(); + } + } catch (Exception e) { + log.error("", e); + } + } + inTest = true; + + if(summariser != null) { + summariser.testStarted(host); + } + } + + public void testEnded() { + testEnded(TEST_IS_LOCAL); + } + + public void testStarted() { + testStarted(TEST_IS_LOCAL); + } + + /** + * Loads an existing sample data (JTL) file. + * This can be one of: + * - XStream format + * - Avalon format + * - CSV format + * + */ + public void loadExistingFile() { + final Visualizer visualizer = getVisualizer(); + if (visualizer == null) { + return; // No point reading the file if there's no visualiser + } + boolean parsedOK = false; + String filename = getFilename(); + File file = new File(filename); + if (file.exists()) { + BufferedReader dataReader = null; + BufferedInputStream bufferedInputStream = null; + try { + dataReader = new BufferedReader(new FileReader(file)); + // Get the first line, and see if it is XML + String line = dataReader.readLine(); + dataReader.close(); + dataReader = null; + if (line == null) { + log.warn(filename+" is empty"); + } else { + if (!line.startsWith(" 0) { + writer.println(pi); + } + // Can't do it as a static initialisation, because SaveService + // is being constructed when this is called + writer.print(TESTRESULTS_START_V1_1_PREVER); + writer.print(SaveService.getVERSION()); + writer.print(TESTRESULTS_START_V1_1_POSTVER); + // Write the EOL separately so we generate LF line ends on Unix and Windows + writer.print("\n"); // $NON-NLS-1$ + } else if (saveConfig.saveFieldNames()) { + writer.println(CSVSaveService.printableFieldNamesToString(saveConfig)); + } + } + + private static void writeFileEnd(PrintWriter pw, SampleSaveConfiguration saveConfig) { + if (saveConfig.saveAsXml()) { + pw.print("\n"); // $NON-NLS-1$ + pw.print(TESTRESULTS_END); + pw.print("\n");// Added in version 1.1 // $NON-NLS-1$ + } + } + + private static PrintWriter getFileWriter(String filename, SampleSaveConfiguration saveConfig) + throws IOException { + if (filename == null || filename.length() == 0) { + return null; + } + filename = FileServer.resolveBaseRelativeName(filename); + FileEntry fe = files.get(filename); + PrintWriter writer = null; + boolean trimmed = true; + + if (fe == null) { + if (saveConfig.saveAsXml()) { + trimmed = trimLastLine(filename); + } else { + trimmed = new File(filename).exists(); + } + // Find the name of the directory containing the file + // and create it - if there is one + File pdir = new File(filename).getParentFile(); + if (pdir != null) { + // returns false if directory already exists, so need to check again + if(pdir.mkdirs()){ + log.info("Folder "+pdir.getAbsolutePath()+" was created"); + } // else if might have been created by another process so not a problem + if (!pdir.exists()){ + log.warn("Error creating directories for "+pdir.toString()); + } + } + writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(filename, + trimmed)), SaveService.getFileEncoding("UTF-8")), true); // $NON-NLS-1$ + log.debug("Opened file: "+filename); + files.put(filename, new FileEntry(writer, saveConfig)); + } else { + writer = fe.pw; + } + if (!trimmed) { + writeFileStart(writer, saveConfig); + } + return writer; + } + + // returns false if the file did not contain the terminator + private static boolean trimLastLine(String filename) { + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(filename, "rw"); // $NON-NLS-1$ + long len = raf.length(); + if (len < MIN_XML_FILE_LEN) { + return false; + } + raf.seek(len - TESTRESULTS_END.length() - 10);// TODO: may not work on all OSes? + String line; + long pos = raf.getFilePointer(); + int end = 0; + while ((line = raf.readLine()) != null)// reads to end of line OR end of file + { + end = line.indexOf(TESTRESULTS_END); + if (end >= 0) // found the string + { + break; + } + pos = raf.getFilePointer(); + } + if (line == null) { + log.warn("Unexpected EOF trying to find XML end marker in " + filename); + raf.close(); + return false; + } + raf.setLength(pos + end);// Truncate the file + raf.close(); + raf = null; + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + log.warn("Error trying to find XML terminator " + e.toString()); + return false; + } finally { + try { + if (raf != null) { + raf.close(); + } + } catch (IOException e1) { + log.info("Could not close " + filename + " " + e1.getLocalizedMessage()); + } + } + return true; + } + + public void sampleStarted(SampleEvent e) { + } + + public void sampleStopped(SampleEvent e) { + } + + /** + * When a test result is received, display it and save it. + * + * @param event + * the sample event that was received + */ + public void sampleOccurred(SampleEvent event) { + SampleResult result = event.getResult(); + + if (isSampleWanted(result.isSuccessful())) { + sendToVisualizer(result); + if (out != null && !isResultMarked(result) && !this.isStats) { + SampleSaveConfiguration config = getSaveConfig(); + result.setSaveConfig(config); + try { + if (config.saveAsXml()) { + SaveService.saveSampleResult(event, out); + } else { // !saveAsXml + String savee = CSVSaveService.resultToDelimitedString(event); + out.println(savee); + } + } catch (Exception err) { + log.error("Error trying to record a sample", err); // should throw exception back to caller + } + } + } + + if(summariser != null) { + summariser.sampleOccurred(event); + } + } + + protected final void sendToVisualizer(SampleResult r) { + if (getVisualizer() != null) { + getVisualizer().add(r); + } + } + + /** + * recordStats is used to save statistics generated by visualizers + * + * @param e + * @throws Exception + */ + // Used by: MonitorHealthVisualizer.add(SampleResult res) + public void recordStats(TestElement e) throws Exception { + if (out != null) { + SaveService.saveTestElement(e, out); + } + } + + /** + * Checks if the sample result is marked or not, and marks it + * @param res - the sample result to check + * @return true if the result was marked + */ + private boolean isResultMarked(SampleResult res) { + String filename = getFilename(); + return res.markFile(filename); + } + + private void initializeFileOutput() throws IOException { + + String filename = getFilename(); + if (filename != null) { + if (out == null) { + try { + out = getFileWriter(filename, getSaveConfig()); + } catch (FileNotFoundException e) { + out = null; + } + } + } + } + + private void finalizeFileOutput() { + for(Map.Entry me : files.entrySet()){ + log.debug("Closing: "+me.getKey()); + FileEntry fe = me.getValue(); + writeFileEnd(fe.pw, fe.config); + fe.pw.close(); + if (fe.pw.checkError()){ + log.warn("Problem detected during use of "+me.getKey()); + } + } + files.clear(); + } + + /** + * {@inheritDoc} + */ + public void testIterationStart(LoopIterationEvent event) { + } + + /** + * @return Returns the saveConfig. + */ + public SampleSaveConfiguration getSaveConfig() { + try { + return (SampleSaveConfiguration) getProperty(SAVE_CONFIG).getObjectValue(); + } catch (ClassCastException e) { + setSaveConfig(new SampleSaveConfiguration()); + return getSaveConfig(); + } + } + + /** + * @param saveConfig + * The saveConfig to set. + */ + public void setSaveConfig(SampleSaveConfiguration saveConfig) { + getProperty(SAVE_CONFIG).setObjectValue(saveConfig); + } + + // This is required so that + // @see org.apache.jmeter.gui.tree.JMeterTreeModel.getNodesOfType() + // can find the Clearable nodes - the userObject has to implement the interface. + public void clearData() { + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/ResultCollectorHelper.java b/ApacheJmeter/src/org/apache/jmeter/reporters/ResultCollectorHelper.java new file mode 100644 index 0000000..a599b88 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/ResultCollectorHelper.java @@ -0,0 +1,46 @@ +/* + * 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.reporters; + +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.visualizers.Visualizer; + +/** + * Helper class to allow TestResultWrapperConverter to send samples + * directly to the visualiser if required. + */ +public class ResultCollectorHelper { + + private final Visualizer visualizer; + private final boolean errorsOnly; + private final boolean successOnly; + + public ResultCollectorHelper(ResultCollector resultCollector, Visualizer visualizer) { + this.visualizer = visualizer; + this.errorsOnly = resultCollector.isErrorLogging(); + this.successOnly = resultCollector.isSuccessOnlyLogging(); + } + + public void add(SampleResult sample){ + if (ResultCollector.isSampleWanted(sample.isSuccessful(), errorsOnly, successOnly)){ + visualizer.add(sample); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/ResultSaver.java b/ApacheJmeter/src/org/apache/jmeter/reporters/ResultSaver.java new file mode 100644 index 0000000..39f8afa --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/ResultSaver.java @@ -0,0 +1,284 @@ +/* + * 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.reporters; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Save Result responseData to a set of files + * + * + * This is mainly intended for validation tests + * + */ +// TODO - perhaps save other items such as headers? +public class ResultSaver extends AbstractTestElement implements Serializable, SampleListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Object LOCK = new Object(); + + // File name sequence number + //@GuardedBy("LOCK") + private static long sequenceNumber = 0; + + //@GuardedBy("LOCK") + private static String timeStamp; + + private static final String TIMESTAMP_FORMAT = "yyyyMMdd-HHmm_"; // $NON-NLS-1$ + + //@GuardedBy("LOCK") + private static int numberPadLength; + + //+ JMX property names; do not change + + public static final String FILENAME = "FileSaver.filename"; // $NON-NLS-1$ + + public static final String VARIABLE_NAME = "FileSaver.variablename"; // $NON-NLS-1$ + + public static final String ERRORS_ONLY = "FileSaver.errorsonly"; // $NON-NLS-1$ + + public static final String SUCCESS_ONLY = "FileSaver.successonly"; // $NON-NLS-1$ + + public static final String SKIP_AUTO_NUMBER = "FileSaver.skipautonumber"; // $NON-NLS-1$ + + public static final String SKIP_SUFFIX = "FileSaver.skipsuffix"; // $NON-NLS-1$ + + public static final String ADD_TIMESTAMP = "FileSaver.addTimstamp"; // $NON-NLS-1$ + + public static final String NUMBER_PAD_LENGTH = "FileSaver.numberPadLen"; // $NON-NLS-1$ + + //- JMX property names + + private synchronized long nextNumber() { + return ++sequenceNumber; + } + + /* + * Constructor is initially called once for each occurrence in the test plan + * For GUI, several more instances are created Then clear is called at start + * of test Called several times during test startup The name will not + * necessarily have been set at this point. + */ + public ResultSaver() { + super(); + // log.debug(Thread.currentThread().getName()); + // System.out.println(">> "+me+" "+this.getName()+" + // "+Thread.currentThread().getName()); + } + + /* + * Constructor for use during startup (intended for non-GUI use) @param name + * of summariser + */ + public ResultSaver(String name) { + this(); + setName(name); + } + + /* + * This is called once for each occurrence in the test plan, before the + * start of the test. The super.clear() method clears the name (and all + * other properties), so it is called last. + */ + @Override + public void clear() { + synchronized(LOCK){ + sequenceNumber = 0; // TODO is this the right thing to do? + if (getAddTimeStamp()) { + DateFormat format = new SimpleDateFormat(TIMESTAMP_FORMAT); + timeStamp = format.format(new Date()); + } else { + timeStamp = ""; + } + numberPadLength=getNumberPadLen(); + } + super.clear(); + } + + /** + * Saves the sample result (and any sub results) in files + * + * @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent) + */ + public void sampleOccurred(SampleEvent e) { + processSample(e.getResult(), new Counter()); + } + + /** + * Recurse the whole (sub)result hierarchy. + * + * @param s Sample result + * @param c sample counter + */ + private void processSample(SampleResult s, Counter c) { + saveSample(s, c.num++); + SampleResult[] sr = s.getSubResults(); + for (int i = 0; i < sr.length; i++) { + processSample(sr[i], c); + } + } + + /** + * @param s SampleResult to save + * @param num number to append to variable (if >0) + */ + private void saveSample(SampleResult s, int num) { + // Should we save the sample? + if (s.isSuccessful()){ + if (getErrorsOnly()){ + return; + } + } else { + if (getSuccessOnly()){ + return; + } + } + + String fileName = makeFileName(s.getContentType(), getSkipAutoNumber(), getSkipSuffix()); + log.debug("Saving " + s.getSampleLabel() + " in " + fileName); + s.setResultFileName(fileName);// Associate sample with file name + String variable = getVariableName(); + if (variable.length()>0){ + if (num > 0) { + StringBuilder sb = new StringBuilder(variable); + sb.append(num); + variable=sb.toString(); + } + JMeterContextService.getContext().getVariables().put(variable, fileName); + } + File out = new File(fileName); + FileOutputStream pw = null; + try { + pw = new FileOutputStream(out); + pw.write(s.getResponseData()); + } catch (FileNotFoundException e1) { + log.error("Error creating sample file for " + s.getSampleLabel(), e1); + } catch (IOException e1) { + log.error("Error saving sample " + s.getSampleLabel(), e1); + } finally { + JOrphanUtils.closeQuietly(pw); + } + } + + /** + * @return fileName composed of fixed prefix, a number, and a suffix derived + * from the contentType e.g. Content-Type: + * text/html;charset=ISO-8859-1 + */ + private String makeFileName(String contentType, boolean skipAutoNumber, boolean skipSuffix) { + StringBuilder sb = new StringBuilder(FileServer.resolveBaseRelativeName(getFilename())); + sb.append(timeStamp); // may be the empty string + if (!skipAutoNumber){ + String number = Long.toString(nextNumber()); + for(int i=number.length(); i < numberPadLength; i++) { + sb.append('0'); + } + sb.append(number); + } + if (!skipSuffix){ + sb.append('.'); + if (contentType != null) { + int i = contentType.indexOf("/"); // $NON-NLS-1$ + if (i != -1) { + int j = contentType.indexOf(";"); // $NON-NLS-1$ + if (j != -1) { + sb.append(contentType.substring(i + 1, j)); + } else { + sb.append(contentType.substring(i + 1)); + } + } else { + sb.append("unknown"); + } + } else { + sb.append("unknown"); + } + } + return sb.toString(); + } + + /** + * {@inheritDoc} + */ + public void sampleStarted(SampleEvent e) { + // not used + } + + /** + * {@inheritDoc} + */ + public void sampleStopped(SampleEvent e) { + // not used + } + + private String getFilename() { + return getPropertyAsString(FILENAME); + } + + private String getVariableName() { + return getPropertyAsString(VARIABLE_NAME,""); // $NON-NLS-1$ + } + + private boolean getErrorsOnly() { + return getPropertyAsBoolean(ERRORS_ONLY); + } + + private boolean getSkipAutoNumber() { + return getPropertyAsBoolean(SKIP_AUTO_NUMBER); + } + + private boolean getSkipSuffix() { + return getPropertyAsBoolean(SKIP_SUFFIX); + } + + private boolean getSuccessOnly() { + return getPropertyAsBoolean(SUCCESS_ONLY); + } + + private boolean getAddTimeStamp() { + return getPropertyAsBoolean(ADD_TIMESTAMP); + } + + private int getNumberPadLen() { + return getPropertyAsInt(NUMBER_PAD_LENGTH, 0); + } + + // Mutable int to keep track of sample count + private static class Counter{ + int num; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/Summariser.java b/ApacheJmeter/src/org/apache/jmeter/reporters/Summariser.java new file mode 100644 index 0000000..bf06484 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/Summariser.java @@ -0,0 +1,374 @@ +/* + * 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.reporters; + +import java.io.Serializable; +import java.text.DecimalFormat; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.RunningSample; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Generate a summary of the test run so far to the log file and/or standard + * output. Both running and differential totals are shown. Output is generated + * every n seconds (default 3 minutes) on the appropriate time boundary, so that + * multiple test runs on the same time will be synchronised. + * + * This is mainly intended for batch (non-GUI) runs + * + * Note that the RunningSample start and end times relate to the samples, + * not the reporting interval. + * + * Since the first sample in a delta is likely to have started in the previous reporting interval, + * this means that the delta interval is likely to be longer than the reporting interval. + * + * Also, the sum of the delta intervals will be larger than the overall elapsed time. + * + * Data is accumulated according to the test element name. + * + */ +public class Summariser extends AbstractTestElement + implements Serializable, SampleListener, TestListener, NoThreadClone, Remoteable { + + /* + * N.B. NoThreadClone is used to ensure that the testStarted() methods will share the same + * instance as the sampleOccured() methods, so the testStarted() method can fetch the + * Totals accumulator object for the samples to be stored in. + */ + + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** interval between summaries (in seconds) default 3 minutes */ + private static final long INTERVAL = JMeterUtils.getPropDefault("summariser.interval", 3 * 60); //$NON-NLS-1$ + + /** Write messages to log file ? */ + private static final boolean TOLOG = JMeterUtils.getPropDefault("summariser.log", true); //$NON-NLS-1$ + + /** Write messages to System.out ? */ + private static final boolean TOOUT = JMeterUtils.getPropDefault("summariser.out", true); //$NON-NLS-1$ + + /* + * Ensure that a report is not skipped if we are slightly late in checking + * the time. + */ + private static final int INTERVAL_WINDOW = 5; // in seconds + + /** + * Lock used to protect accumulators update + instanceCount update + */ + private static final Object lock = new Object(); + + /* + * This map allows summarisers with the same name to contribute to the same totals. + */ + //@GuardedBy("accumulators") - needed to ensure consistency between this and instanceCount + private static final Map accumulators = new ConcurrentHashMap(); + + //@GuardedBy("accumulators") + private static int instanceCount; // number of active tests + + /* + * Cached copy of Totals for this instance. + * The variables do not need to be synchronised, + * as they are not shared between threads + * However the contents do need to be synchronized. + */ + //@GuardedBy("myTotals") + private transient Totals myTotals = null; + + // Name of the accumulator. Set up by testStarted(). + private transient String myName; + + /* + * Constructor is initially called once for each occurrence in the test plan. + * For GUI, several more instances are created. + * Then clear is called at start of test. + * Called several times during test startup. + * The name will not necessarily have been set at this point. + */ + public Summariser() { + super(); + synchronized (lock) { + accumulators.clear(); + instanceCount=0; + } + } + + /** + * Constructor for use during startup (intended for non-GUI use) + * + * @param name of summariser + */ + public Summariser(String name) { + this(); + setName(name); + } + + /* + * Contains the items needed to collect stats for a summariser + * + */ + private static class Totals { + + /** Time of last summary (to prevent double reporting) */ + private long last = 0; + + private final RunningSample delta = new RunningSample("DELTA",0); + + private final RunningSample total = new RunningSample("TOTAL",0); + + /** + * Add the delta values to the total values and clear the delta + */ + private void moveDelta() { + total.addSample(delta); + delta.clear(); + } + } + + /** + * Accumulates the sample in two SampleResult objects - one for running + * totals, and the other for deltas. + * + * @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent) + */ + public void sampleOccurred(SampleEvent e) { + SampleResult s = e.getResult(); + + long now = System.currentTimeMillis() / 1000;// in seconds + + RunningSample myDelta = null; + RunningSample myTotal = null; + boolean reportNow = false; + + /* + * Have we reached the reporting boundary? + * Need to allow for a margin of error, otherwise can miss the slot. + * Also need to check we've not hit the window already + */ + synchronized (myTotals) { + if (s != null) { + myTotals.delta.addSample(s); + } + + if ((now > myTotals.last + INTERVAL_WINDOW) && (now % INTERVAL <= INTERVAL_WINDOW)) { + reportNow = true; + + // copy the data to minimise the synch time + myDelta = new RunningSample(myTotals.delta); + myTotals.moveDelta(); + myTotal = new RunningSample(myTotals.total); + + myTotals.last = now; // stop double-reporting + } + } + if (reportNow) { + String str; + str = format(myName, myDelta, "+"); + if (TOLOG) { + log.info(str); + } + if (TOOUT) { + System.out.println(str); + } + + // Only if we have updated them + if (myTotal != null && myDelta != null &&myTotal.getNumSamples() != myDelta.getNumSamples()) { + str = format(myName, myTotal, "="); + if (TOLOG) { + log.info(str); + } + if (TOOUT) { + System.out.println(str); + } + } + } + } + + private static StringBuilder longToSb(StringBuilder sb, long l, int len) { + sb.setLength(0); + sb.append(l); + return JOrphanUtils.rightAlign(sb, len); + } + + private static final DecimalFormat dfDouble = new DecimalFormat("#0.0"); // $NON-NLS-1$ + + private static StringBuilder doubleToSb(StringBuilder sb, double d, int len, int frac) { + sb.setLength(0); + dfDouble.setMinimumFractionDigits(frac); + dfDouble.setMaximumFractionDigits(frac); + sb.append(dfDouble.format(d)); + return JOrphanUtils.rightAlign(sb, len); + } + + /** + * @param myTotal + * @param string + * @return + */ + private String format(String name, RunningSample s, String type) { + StringBuilder tmp = new StringBuilder(20); // for intermediate use + StringBuilder sb = new StringBuilder(100); // output line buffer + sb.append(name); + sb.append(" "); + sb.append(type); + sb.append(" "); + sb.append(longToSb(tmp, s.getNumSamples(), 5)); + sb.append(" in "); + long elapsed = s.getElapsed(); + sb.append(doubleToSb(tmp, elapsed / 1000.0, 5, 1)); + sb.append("s = "); + if (elapsed > 0) { + sb.append(doubleToSb(tmp, s.getRate(), 6, 1)); + } else { + sb.append("******");// Rate is effectively infinite + } + sb.append("/s Avg: "); + sb.append(longToSb(tmp, s.getAverage(), 5)); + sb.append(" Min: "); + sb.append(longToSb(tmp, s.getMin(), 5)); + sb.append(" Max: "); + sb.append(longToSb(tmp, s.getMax(), 5)); + sb.append(" Err: "); + sb.append(longToSb(tmp, s.getErrorCount(), 5)); + sb.append(" ("); + sb.append(s.getErrorPercentageString()); + sb.append(")"); + return sb.toString(); + } + + /** {@inheritDoc} */ + public void sampleStarted(SampleEvent e) { + // not used + } + + /** {@inheritDoc} */ + public void sampleStopped(SampleEvent e) { + // not used + } + + /* + * The testStarted/testEnded methods are called at the start and end of a test. + * + * However, when a test is run on multiple nodes, there is no guarantee that all the + * testStarted() methods will be called before all the threadStart() or sampleOccurred() + * methods for other threads - nor that testEnded() will only be called after all + * sampleOccurred() calls. The ordering is only guaranteed within a single test. + * + */ + + + /** {@inheritDoc} */ + public void testStarted() { + testStarted("local"); + } + + /** {@inheritDoc} */ + public void testEnded() { + testEnded("local"); + } + + /** + * Called once for each Summariser in the test plan. + * There may be more than one summariser with the same name, + * however they will all be called before the test proper starts. + *

+ * However, note that this applies to a single test only. + * When running in client-server mode, testStarted() may be + * invoked after sampleOccurred(). + *

+ * {@inheritDoc} + */ + public void testStarted(String host) { + synchronized (lock) { + myName = getName(); + myTotals = accumulators.get(myName); + if (myTotals == null){ + myTotals = new Totals(); + accumulators.put(myName, myTotals); + } + instanceCount++; + } + } + + /** + * Called from a different thread as testStarted() but using the same instance. + * So synch is needed to fetch the accumulator, and the myName field will already be set up. + *

+ * {@inheritDoc} + */ + public void testEnded(String host) { + Set> totals = null; + synchronized (lock) { + instanceCount--; + if (instanceCount <= 0){ + totals = accumulators.entrySet(); + } + } + if (totals == null) {// We're not done yet + return; + } + for(Map.Entry entry : totals){ + String str; + String name = entry.getKey(); + Totals total = entry.getValue(); + // Only print final delta if there were some samples in the delta + // and there has been at least one sample reported previously + if (total.delta.getNumSamples() > 0 && total.total.getNumSamples() > 0) { + str = format(name, total.delta, "+"); + if (TOLOG) { + log.info(str); + } + if (TOOUT) { + System.out.println(str); + } + } + total.moveDelta(); + str = format(name, total.total, "="); + if (TOLOG) { + log.info(str); + } + if (TOOUT) { + System.out.println(str); + } + } + } + + /** {@inheritDoc} */ + public void testIterationStart(LoopIterationEvent event) { + // not used + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/gui/ResultActionGui.java b/ApacheJmeter/src/org/apache/jmeter/reporters/gui/ResultActionGui.java new file mode 100644 index 0000000..e4240b1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/gui/ResultActionGui.java @@ -0,0 +1,100 @@ +/* + * 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.reporters.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; + +import org.apache.jmeter.reporters.ResultAction; +import org.apache.jmeter.gui.OnErrorPanel; +import org.apache.jmeter.processor.gui.AbstractPostProcessorGui; +import org.apache.jmeter.testelement.OnErrorTestElement; +import org.apache.jmeter.testelement.TestElement; + +/** + * Create a Result Action Test Element + * + */ +public class ResultActionGui extends AbstractPostProcessorGui { + + private static final long serialVersionUID = 240L; + + private OnErrorPanel errorPanel; + + public ResultActionGui() { + super(); + init(); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#getStaticLabel() + */ + public String getLabelResource() { + return "resultaction_title"; //$NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) + */ + @Override + public void configure(TestElement el) { + super.configure(el); + errorPanel.configure(((OnErrorTestElement) el).getErrorAction()); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + ResultAction resultAction = new ResultAction(); + modifyTestElement(resultAction); + return resultAction; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement te) { + super.configureTestElement(te); + ((OnErrorTestElement) te).setErrorAction(errorPanel.getOnErrorSetting()); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + errorPanel.configure(OnErrorTestElement.ON_ERROR_CONTINUE); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + errorPanel = new OnErrorPanel(); + box.add(errorPanel); + add(box, BorderLayout.NORTH); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/gui/ResultSaverGui.java b/ApacheJmeter/src/org/apache/jmeter/reporters/gui/ResultSaverGui.java new file mode 100644 index 0000000..0c77f5d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/gui/ResultSaverGui.java @@ -0,0 +1,189 @@ +/* + * 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.reporters.gui; + +import java.awt.BorderLayout; + +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.reporters.ResultSaver; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractListenerGui; +import org.apache.jorphan.gui.JLabeledTextField; + +/** + * Create a ResultSaver test element, which saves the sample information in set + * of files + * + */ +public class ResultSaverGui extends AbstractListenerGui implements Clearable { + + private static final long serialVersionUID = 240L; + + private JTextField filename; + + private JTextField variableName; + + private JCheckBox errorsOnly; + + private JCheckBox successOnly; + + private JCheckBox skipAutoNumber; + + private JCheckBox skipSuffix; + + private JCheckBox addTimestamp; + + private JLabeledTextField numberPadLength; + + public ResultSaverGui() { + super(); + init(); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#getStaticLabel() + */ + public String getLabelResource() { + return "resultsaver_title"; // $NON-NLS-1$ + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) + */ + @Override + public void configure(TestElement el) { + super.configure(el); + filename.setText(el.getPropertyAsString(ResultSaver.FILENAME)); + errorsOnly.setSelected(el.getPropertyAsBoolean(ResultSaver.ERRORS_ONLY)); + successOnly.setSelected(el.getPropertyAsBoolean(ResultSaver.SUCCESS_ONLY)); + skipAutoNumber.setSelected(el.getPropertyAsBoolean(ResultSaver.SKIP_AUTO_NUMBER)); + skipSuffix.setSelected(el.getPropertyAsBoolean(ResultSaver.SKIP_SUFFIX)); + variableName.setText(el.getPropertyAsString(ResultSaver.VARIABLE_NAME,"")); + addTimestamp.setSelected(el.getPropertyAsBoolean(ResultSaver.ADD_TIMESTAMP)); + numberPadLength.setText(el.getPropertyAsString(ResultSaver.NUMBER_PAD_LENGTH,"")); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + ResultSaver resultSaver = new ResultSaver(); + modifyTestElement(resultSaver); + return resultSaver; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement te) { + super.configureTestElement(te); + te.setProperty(ResultSaver.FILENAME, filename.getText()); + te.setProperty(ResultSaver.ERRORS_ONLY, errorsOnly.isSelected()); + te.setProperty(ResultSaver.SKIP_AUTO_NUMBER, skipAutoNumber.isSelected()); + te.setProperty(ResultSaver.SKIP_SUFFIX, skipSuffix.isSelected()); + te.setProperty(ResultSaver.SUCCESS_ONLY, successOnly.isSelected()); + te.setProperty(ResultSaver.ADD_TIMESTAMP, addTimestamp.isSelected(), false); + AbstractTestElement at = (AbstractTestElement) te; + at.setProperty(ResultSaver.VARIABLE_NAME, variableName.getText(),""); //$NON-NLS-1$ + at.setProperty(ResultSaver.NUMBER_PAD_LENGTH, numberPadLength.getText(),""); //$NON-NLS-1$ + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + skipAutoNumber.setSelected(false); + skipSuffix.setSelected(false); + filename.setText(""); //$NON-NLS-1$ + errorsOnly.setSelected(false); + successOnly.setSelected(false); + addTimestamp.setSelected(false); + variableName.setText(""); //$NON-NLS-1$ + numberPadLength.setText(""); //$NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createFilenamePrefixPanel()); + box.add(createVariableNamePanel()); + errorsOnly = new JCheckBox(JMeterUtils.getResString("resultsaver_errors")); // $NON-NLS-1$ + box.add(errorsOnly); + successOnly = new JCheckBox(JMeterUtils.getResString("resultsaver_success")); // $NON-NLS-1$ + box.add(successOnly); + skipAutoNumber = new JCheckBox(JMeterUtils.getResString("resultsaver_skipautonumber")); // $NON-NLS-1$ + box.add(skipAutoNumber); + skipSuffix = new JCheckBox(JMeterUtils.getResString("resultsaver_skipsuffix")); // $NON-NLS-1$ + box.add(skipSuffix); + addTimestamp = new JCheckBox(JMeterUtils.getResString("resultsaver_addtimestamp")); // $NON-NLS-1$ + box.add(addTimestamp); + numberPadLength = new JLabeledTextField(JMeterUtils.getResString("resultsaver_numberpadlen"));// $NON-NLS-1$ + box.add(numberPadLength); + add(box, BorderLayout.NORTH); + } + + private JPanel createFilenamePrefixPanel() + { + JLabel label = new JLabel(JMeterUtils.getResString("resultsaver_prefix")); // $NON-NLS-1$ + + filename = new JTextField(10); + filename.setName(ResultSaver.FILENAME); + label.setLabelFor(filename); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(filename, BorderLayout.CENTER); + return filenamePanel; + } + + + private JPanel createVariableNamePanel() + { + JLabel label = new JLabel(JMeterUtils.getResString("resultsaver_variable")); // $NON-NLS-1$ + + variableName = new JTextField(10); + variableName.setName(ResultSaver.VARIABLE_NAME); + label.setLabelFor(variableName); + + JPanel filenamePanel = new JPanel(new BorderLayout(5, 0)); + filenamePanel.add(label, BorderLayout.WEST); + filenamePanel.add(variableName, BorderLayout.CENTER); + return filenamePanel; + } + + // Needed to avoid Class cast error in Clear.java + public void clearData() { + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/reporters/gui/SummariserGui.java b/ApacheJmeter/src/org/apache/jmeter/reporters/gui/SummariserGui.java new file mode 100644 index 0000000..cf913e8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/reporters/gui/SummariserGui.java @@ -0,0 +1,73 @@ +/* + * 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.reporters.gui; + +import java.awt.BorderLayout; + +import org.apache.jmeter.reporters.Summariser; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.visualizers.gui.AbstractListenerGui; + +/** + * Create a summariser test element GUI. + * + */ +public class SummariserGui extends AbstractListenerGui { + + private static final long serialVersionUID = 240L; + + public SummariserGui() { + super(); + init(); + } + + public String getLabelResource() { + return "summariser_title"; //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + Summariser summariser = new Summariser(); + modifyTestElement(summariser); + return summariser; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement summariser) { + super.configureTestElement(summariser); + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/sampler/DebugSampler.java b/ApacheJmeter/src/org/apache/jmeter/sampler/DebugSampler.java new file mode 100644 index 0000000..28e8c43 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/sampler/DebugSampler.java @@ -0,0 +1,142 @@ +/* + * 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.sampler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; + +/** + * The Debug Sampler can be used to "sample" JMeter variables, JMeter properties and System Properties. + * + */ +public class DebugSampler extends AbstractSampler implements TestBean { + + private static final long serialVersionUID = 232L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + private boolean displayJMeterVariables; + + private boolean displayJMeterProperties; + + private boolean displaySystemProperties; + + public SampleResult sample(Entry e) { + SampleResult res = new SampleResult(); + res.setSampleLabel(getName()); + res.sampleStart(); + StringBuilder sb = new StringBuilder(100); + StringBuilder rd = new StringBuilder(20); // for request Data + if (isDisplayJMeterVariables()){ + rd.append("JMeterVariables\n"); + sb.append("JMeterVariables:\n"); + formatSet(sb, JMeterContextService.getContext().getVariables().entrySet()); + sb.append("\n"); + } + + if (isDisplayJMeterProperties()){ + rd.append("JMeterProperties\n"); + sb.append("JMeterProperties:\n"); + formatSet(sb, JMeterUtils.getJMeterProperties().entrySet()); + sb.append("\n"); + } + + if (isDisplaySystemProperties()){ + rd.append("SystemProperties\n"); + sb.append("SystemProperties:\n"); + formatSet(sb, System.getProperties().entrySet()); + sb.append("\n"); + } + + res.setResponseData(sb.toString(), null); + res.setDataType(SampleResult.TEXT); + res.setSamplerData(rd.toString()); + res.setResponseOK(); + res.sampleEnd(); + return res; + } + + private void formatSet(StringBuilder sb, @SuppressWarnings("rawtypes") Set s) { + @SuppressWarnings("unchecked") + ArrayList> al = new ArrayList>(s); + Collections.sort(al, new Comparator>(){ + public int compare(Map.Entry o1, Map.Entry o2) { + String m1,m2; + m1=(String)o1.getKey(); + m2=(String)o2.getKey(); + return m1.compareTo(m2); + } + }); + for(Map.Entry me : al){ + sb.append(me.getKey()); + sb.append("="); + sb.append(me.getValue()); + sb.append("\n"); + } + } + + public boolean isDisplayJMeterVariables() { + return displayJMeterVariables; + } + + public void setDisplayJMeterVariables(boolean displayJMeterVariables) { + this.displayJMeterVariables = displayJMeterVariables; + } + + public boolean isDisplayJMeterProperties() { + return displayJMeterProperties; + } + + public void setDisplayJMeterProperties(boolean displayJMeterPropterties) { + this.displayJMeterProperties = displayJMeterPropterties; + } + + public boolean isDisplaySystemProperties() { + return displaySystemProperties; + } + + public void setDisplaySystemProperties(boolean displaySystemProperties) { + this.displaySystemProperties = displaySystemProperties; + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/sampler/DebugSamplerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/sampler/DebugSamplerBeanInfo.java new file mode 100644 index 0000000..2459f7b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/sampler/DebugSamplerBeanInfo.java @@ -0,0 +1,48 @@ +/* + * 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.sampler; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +public class DebugSamplerBeanInfo extends BeanInfoSupport { + public DebugSamplerBeanInfo() { + super(DebugSampler.class); + PropertyDescriptor p; + + p = property("displayJMeterVariables"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + + p = property("displayJMeterProperties"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + + p = property("displaySystemProperties"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/sampler/TestAction.java b/ApacheJmeter/src/org/apache/jmeter/sampler/TestAction.java new file mode 100644 index 0000000..13ab186 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/sampler/TestAction.java @@ -0,0 +1,168 @@ +/* + * 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.sampler; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.samplers.AbstractSampler; +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Dummy Sampler used to pause or stop a thread or the test; + * intended for use in Conditional Controllers. + * + */ +public class TestAction extends AbstractSampler implements Interruptible { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final Set APPLIABLE_CONFIG_CLASSES = new HashSet( + Arrays.asList(new String[]{ + "org.apache.jmeter.config.gui.SimpleConfigGui"})); + + // Actions + public final static int STOP = 0; + public final static int PAUSE = 1; + public final static int STOP_NOW = 2; + public final static int RESTART_NEXT_LOOP = 3; + + // Action targets + public final static int THREAD = 0; + // public final static int THREAD_GROUP = 1; + public final static int TEST = 2; + + // Identifiers + private final static String TARGET = "ActionProcessor.target"; //$NON-NLS-1$ + private final static String ACTION = "ActionProcessor.action"; //$NON-NLS-1$ + private final static String DURATION = "ActionProcessor.duration"; //$NON-NLS-1$ + + private volatile transient Thread pauseThread; + + public TestAction() { + super(); + } + + /** + * {@inheritDoc} + */ + public SampleResult sample(Entry e) { + JMeterContext context = JMeterContextService.getContext(); + + int target = getTarget(); + int action = getAction(); + if (action == PAUSE) { + pause(getDurationAsString()); + } else if (action == STOP || action == STOP_NOW || action == RESTART_NEXT_LOOP) { + if (target == THREAD) { + if(action == STOP || action == STOP_NOW) { + log.info("Stopping current thread"); + context.getThread().stop(); + } else { + log.info("Restarting next loop"); + context.setRestartNextLoop(true); + } +// //Not yet implemented +// } else if (target==THREAD_GROUP) { + } else if (target == TEST) { + if (action == STOP_NOW) { + log.info("Stopping all threads now"); + context.getEngine().stopTest(); + } else { + log.info("Stopping all threads"); + context.getEngine().askThreadsToStop(); + } + } + } + + return null; // This means no sample is saved + } + + private void pause(String mili_s) { + int milis; + try { + milis=Integer.parseInt(mili_s); + } catch (NumberFormatException e){ + log.warn("Could not create number from "+mili_s); + milis=0; + } + try { + pauseThread = Thread.currentThread(); + Thread.sleep(milis); + } catch (InterruptedException e) { + // NOOP + } finally { + pauseThread = null; + } + } + + public void setTarget(int target) { + setProperty(new IntegerProperty(TARGET, target)); + } + + public int getTarget() { + return getPropertyAsInt(TARGET); + } + + public void setAction(int action) { + setProperty(new IntegerProperty(ACTION, action)); + } + + public int getAction() { + return getPropertyAsInt(ACTION); + } + + public void setDuration(String duration) { + setProperty(new StringProperty(DURATION, duration)); + } + + public String getDurationAsString() { + return getPropertyAsString(DURATION); + } + + /** + * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement) + */ + @Override + public boolean applies(ConfigTestElement configElement) { + String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue(); + return APPLIABLE_CONFIG_CLASSES.contains(guiClass); + } + + public boolean interrupt() { + Thread thrd = pauseThread; // take copy so cannot get NPE + if (thrd!= null) { + thrd.interrupt(); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/sampler/gui/TestActionGui.java b/ApacheJmeter/src/org/apache/jmeter/sampler/gui/TestActionGui.java new file mode 100644 index 0000000..101e106 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/sampler/gui/TestActionGui.java @@ -0,0 +1,262 @@ +/* + * 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.sampler.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.ButtonGroup; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.sampler.TestAction; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +public class TestActionGui extends AbstractSamplerGui { + private static final long serialVersionUID = 240L; + + // Gui components + private JComboBox targetBox; + + // private ButtonGroup actionButtons; + private JRadioButton pauseButton; + + private JRadioButton stopButton; + + private JRadioButton stopNowButton; + + private JRadioButton restartNextLoopButton; + + private JTextField durationField; + + // State variables + private int target; + + private int action; + + private String durationString; + + // String in the panel + // Do not make these static, otherwise language changes don't work + private final String targetLabel = JMeterUtils.getResString("test_action_target"); // $NON-NLS-1$ + + private final String threadTarget = JMeterUtils.getResString("test_action_target_thread"); // $NON-NLS-1$ + + private final String testTarget = JMeterUtils.getResString("test_action_target_test"); // $NON-NLS-1$ + + private final String actionLabel = JMeterUtils.getResString("test_action_action"); // $NON-NLS-1$ + + private final String pauseAction = JMeterUtils.getResString("test_action_pause"); // $NON-NLS-1$ + + private final String stopAction = JMeterUtils.getResString("test_action_stop"); // $NON-NLS-1$ + + private final String stopNowAction = JMeterUtils.getResString("test_action_stop_now"); // $NON-NLS-1$ + + private final String restartNextLoopAction = JMeterUtils.getResString("test_action_restart_next_loop"); // $NON-NLS-1$ + + private final String durationLabel = JMeterUtils.getResString("test_action_duration"); // $NON-NLS-1$ + + public TestActionGui() { + super(); + target = TestAction.THREAD; + action = TestAction.PAUSE; + durationString = ""; // $NON-NLS-1$ + init(); + } + + public String getLabelResource() { + return "test_action_title"; // $NON-NLS-1$ + } + + @Override + public void configure(TestElement element) { + super.configure(element); + TestAction ta = (TestAction) element; + + target = ta.getTarget(); + if (target == TestAction.THREAD) { + targetBox.setSelectedItem(threadTarget); + } else { + targetBox.setSelectedItem(testTarget); + } + action = ta.getAction(); + if (action == TestAction.PAUSE) { + pauseButton.setSelected(true); + } else if (action == TestAction.STOP_NOW) { + stopNowButton.setSelected(true); + } else if(action == TestAction.STOP ){ + stopButton.setSelected(true); + } else { + restartNextLoopButton.setSelected(true); + } + + durationString = ta.getDurationAsString(); + durationField.setText(durationString); + } + + /** + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + TestAction ta = new TestAction(); + modifyTestElement(ta); + return ta; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement element) { + super.configureTestElement(element); + TestAction ta = (TestAction) element; + ta.setAction(action); + ta.setTarget(target); + ta.setDuration(durationString); + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + + targetBox.setSelectedIndex(0); + durationString = ""; //$NON-NLS-1$ + durationField.setText(""); //$NON-NLS-1$ + pauseButton.setSelected(true); + action = TestAction.PAUSE; + target = TestAction.THREAD; + + } + + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + setBorder(makeBorder()); + add(makeTitlePanel()); + + // Target + HorizontalPanel targetPanel = new HorizontalPanel(); + targetPanel.add(new JLabel(targetLabel)); + DefaultComboBoxModel targetModel = new DefaultComboBoxModel(); + targetModel.addElement(threadTarget); + targetModel.addElement(testTarget); + targetBox = new JComboBox(targetModel); + targetBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (((String) targetBox.getSelectedItem()).equals(threadTarget)) { + target = TestAction.THREAD; + } else { + target = TestAction.TEST; + } + } + }); + targetPanel.add(targetBox); + add(targetPanel); + + // Action + HorizontalPanel actionPanel = new HorizontalPanel(); + ButtonGroup actionButtons = new ButtonGroup(); + pauseButton = new JRadioButton(pauseAction, true); + pauseButton.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + if (pauseButton.isSelected()) { + action = TestAction.PAUSE; + durationField.setEnabled(true); + targetBox.setEnabled(true); + } + + } + }); + stopButton = new JRadioButton(stopAction, false); + stopButton.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + if (stopButton.isSelected()) { + action = TestAction.STOP; + durationField.setEnabled(false); + targetBox.setEnabled(true); + } + } + }); + stopNowButton = new JRadioButton(stopNowAction, false); + stopNowButton.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + if (stopNowButton.isSelected()) { + action = TestAction.STOP_NOW; + durationField.setEnabled(false); + targetBox.setEnabled(true); + } + } + }); + + restartNextLoopButton = new JRadioButton(restartNextLoopAction, false); + restartNextLoopButton.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + if (restartNextLoopButton.isSelected()) { + action = TestAction.RESTART_NEXT_LOOP; + durationField.setEnabled(false); + targetBox.setSelectedIndex(TestAction.THREAD); + targetBox.setEnabled(false); + } + } + }); + + actionButtons.add(pauseButton); + actionButtons.add(stopButton); + actionButtons.add(stopNowButton); + actionButtons.add(restartNextLoopButton); + + actionPanel.add(new JLabel(actionLabel)); + actionPanel.add(pauseButton); + actionPanel.add(stopButton); + actionPanel.add(stopNowButton); + actionPanel.add(restartNextLoopButton); + add(actionPanel); + + // Duration + HorizontalPanel durationPanel = new HorizontalPanel(); + durationField = new JTextField(15); + durationField.setText(""); + durationField.addFocusListener(new FocusListener() { + public void focusLost(FocusEvent e) { + durationString = durationField.getText(); + } + + public void focusGained(FocusEvent e) { + } + }); + durationPanel.add(new JLabel(durationLabel)); + durationPanel.add(durationField); + add(durationPanel); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/AbstractSampleSender.java b/ApacheJmeter/src/org/apache/jmeter/samplers/AbstractSampleSender.java new file mode 100644 index 0000000..a5de2fc --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/AbstractSampleSender.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.jmeter.samplers; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Base class for SampleSender implementations + */ +public abstract class AbstractSampleSender implements SampleSender { + + // Note: this is an instance field (and is not transient), so is created by the JMeter client + // and propagated to the server instance by RMI. + // [a static field would be recreated on the server, and would pick up the server properties] + private final boolean isClientConfigured = JMeterUtils.getPropDefault("sample_sender_client_configured", true); // $NON-NLS-1$ + + /** + * @return boolean indicates how SampleSender configuration is done, true means use client properties and send to servers, false means use server configurations + */ + public boolean isClientConfigured() { + return isClientConfigured; + } + + /** + * + */ + public AbstractSampleSender() { + super(); + } + + public void testEnded() { + // Not used + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/AbstractSampler.java b/ApacheJmeter/src/org/apache/jmeter/samplers/AbstractSampler.java new file mode 100644 index 0000000..2894c63 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/AbstractSampler.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.testelement.AbstractTestElement; + +public abstract class AbstractSampler extends AbstractTestElement implements Sampler, ConfigMergabilityIndicator { + private static final long serialVersionUID = 240L; + + /** + * {@inheritDoc} + */ + public boolean applies(ConfigTestElement configElement) { + return true; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/AsynchSampleSender.java b/ApacheJmeter/src/org/apache/jmeter/samplers/AsynchSampleSender.java new file mode 100644 index 0000000..029d307 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/AsynchSampleSender.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.log.Logger; + +public class AsynchSampleSender extends AbstractSampleSender implements Serializable { + + private static final long serialVersionUID = 251L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Create unique object as marker for end of queue + private transient static final SampleEvent FINAL_EVENT = new SampleEvent(); + + private static final int serverConfiguredCapacity = JMeterUtils.getPropDefault("asynch.batch.queue.size", 100); // $NON-NLS-1$ + + private final int clientConfiguredCapacity = JMeterUtils.getPropDefault("asynch.batch.queue.size", 100); // $NON-NLS-1$ + + // created by client + private final RemoteSampleListener listener; + + private transient BlockingQueue queue; // created by server in readResolve method + + private transient long queueWaits; // how many times we had to wait to queue a sample + + private transient long queueWaitTime; // how long we had to wait (nanoSeconds) + + /** + * Processed by the RMI server code. + * @throws ObjectStreamException + */ + private Object readResolve() throws ObjectStreamException{ + int capacity = getCapacity(); + log.info("Using batch queue size (asynch.batch.queue.size): " + capacity); // server log file + queue = new ArrayBlockingQueue(capacity); + Worker worker = new Worker(queue, listener); + worker.setDaemon(true); + worker.start(); + return this; + } + + /** + * @deprecated only for use by test code + */ + @Deprecated + public AsynchSampleSender(){ + this(null); + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + + // Created by SampleSenderFactory + protected AsynchSampleSender(RemoteSampleListener listener) { + this.listener = listener; + log.info("Using Asynch Remote Sampler for this test run, queue size "+getCapacity()); // client log file + } + + /** + * @return capacity + */ + private int getCapacity() { + return isClientConfigured() ? + clientConfiguredCapacity : serverConfiguredCapacity; + } + + public void testEnded(String host) { + log.debug("Test Ended on " + host); + try { + listener.testEnded(host); + queue.put(FINAL_EVENT); + } catch (Exception ex) { + log.warn("testEnded(host)"+ex); + } + if (queueWaits > 0) { + log.info("QueueWaits: "+queueWaits+"; QueueWaitTime: "+queueWaitTime+" (nanoseconds)"); + } + } + + public void sampleOccurred(SampleEvent e) { + try { + if (!queue.offer(e)){ // we failed to add the element first time + queueWaits++; + long t1 = System.nanoTime(); + queue.put(e); + long t2 = System.nanoTime(); + queueWaitTime += t2-t1; + } + } catch (Exception err) { + log.error("sampleOccurred; failed to queue the sample", err); + } + } + + private static class Worker extends Thread { + + private final BlockingQueue queue; + + private final RemoteSampleListener listener; + + private Worker(BlockingQueue q, RemoteSampleListener l){ + queue = q; + listener = l; + } + + @Override + public void run() { + try { + boolean eof = false; + while (!eof) { + List l = new ArrayList(); + SampleEvent e = queue.take(); + while (!(eof = (e == FINAL_EVENT)) && e != null) { // try to process as many as possible + l.add(e); + e = queue.poll(); // returns null if nothing on queue currently + } + int size = l.size(); + if (size > 0) { + try { + listener.processBatch(l); + } catch (RemoteException err) { + if (err.getCause() instanceof java.net.ConnectException){ + throw new JMeterError("Could not return sample",err); + } + log.error("Failed to return sample", err); + } + } + } + } catch (InterruptedException e) { + } + log.debug("Worker ended"); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/BatchSampleSender.java b/ApacheJmeter/src/org/apache/jmeter/samplers/BatchSampleSender.java new file mode 100644 index 0000000..fa9a13a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/BatchSampleSender.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.log.Logger; +import org.apache.jorphan.logging.LoggingManager; + +import java.util.List; +import java.util.ArrayList; +import java.rmi.RemoteException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +/** + * Implements batch reporting for remote testing. + * + */ +public class BatchSampleSender extends AbstractSampleSender implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private static final int DEFAULT_NUM_SAMPLE_THRESHOLD = 100; + + private static final long DEFAULT_TIME_THRESHOLD = 60000L; + + // Static fields are resolved on the server + private static final int NUM_SAMPLES_THRESHOLD = + JMeterUtils.getPropDefault("num_sample_threshold", DEFAULT_NUM_SAMPLE_THRESHOLD); // $NON-NLS-1$ + + private static final long TIME_THRESHOLD_MS = + JMeterUtils.getPropDefault("time_threshold", DEFAULT_TIME_THRESHOLD); // $NON-NLS-1$ + + // instance fields are copied from the client instance + private final int clientConfiguredNumSamplesThreshold = + JMeterUtils.getPropDefault("num_sample_threshold", DEFAULT_NUM_SAMPLE_THRESHOLD); // $NON-NLS-1$ + + private final long clientConfiguredTimeThresholdMs = + JMeterUtils.getPropDefault("time_threshold", DEFAULT_TIME_THRESHOLD); // $NON-NLS-1$ + + private final RemoteSampleListener listener; + + private final List sampleStore = new ArrayList(); + + // Server-only work item + private transient long batchSendTime = -1; + + // Configuration items, set up by readResolve + private transient volatile int numSamplesThreshold; + + private transient volatile long timeThresholdMs; + + + /** + * @deprecated only for use by test code + */ + @Deprecated + public BatchSampleSender(){ + this(null); + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + /** + * Constructor + * + * @param listener + * that the List of sample events will be sent to. + */ + // protected added: Bug 50008 - allow BatchSampleSender to be subclassed + protected BatchSampleSender(RemoteSampleListener listener) { + this.listener = listener; + if (isClientConfigured()) { + log.info("Using batching (client settings) for this run." + + " Thresholds: num=" + clientConfiguredNumSamplesThreshold + + ", time=" + clientConfiguredTimeThresholdMs); + } else { + log.info("Using batching (server settings) for this run."); + } + } + + /** + * @return the listener + */ + // added: Bug 50008 - allow BatchSampleSender to be subclassed + protected RemoteSampleListener getListener() { + return listener; + } + + /** + * @return the sampleStore + */ + // added: Bug 50008 - allow BatchSampleSender to be subclassed + protected List getSampleStore() { + return sampleStore; + } + + /** + * Checks if any sample events are still present in the sampleStore and + * sends them to the listener. Informs the listener of the testended. + * + * @param host + * the host that the test has ended on. + */ + public void testEnded(String host) { + log.info("Test Ended on " + host); + try { + if (sampleStore.size() != 0) { + listener.processBatch(sampleStore); + sampleStore.clear(); + } + listener.testEnded(host); + } catch (RemoteException err) { + log.error("testEnded(host)", err); + } + } + + /** + * Stores sample events untill either a time or sample threshold is + * breached. Both thresholds are reset if one fires. If only one threshold + * is set it becomes the only value checked against. When a threhold is + * breached the list of sample events is sent to a listener where the event + * are fired locally. + * + * @param e + * a Sample Event + */ + public void sampleOccurred(SampleEvent e) { + synchronized (sampleStore) { + sampleStore.add(e); + final int sampleCount = sampleStore.size(); + + boolean sendNow = false; + if (numSamplesThreshold != -1) { + if (sampleCount >= numSamplesThreshold) { + sendNow = true; + } + } + + long now = 0; + if (timeThresholdMs != -1) { + now = System.currentTimeMillis(); + // Checking for and creating initial timestamp to check against + if (batchSendTime == -1) { + this.batchSendTime = now + timeThresholdMs; + } + if (batchSendTime < now && sampleCount > 0) { + sendNow = true; + } + } + + if (sendNow){ + try { + log.debug("Firing sample"); + listener.processBatch(sampleStore); + sampleStore.clear(); + if (timeThresholdMs != -1) { + this.batchSendTime = now + timeThresholdMs; + } + } catch (RemoteException err) { + log.error("sampleOccurred", err); + } + } + } // synchronized(sampleStore) + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * @throws ObjectStreamException + */ + private Object readResolve() throws ObjectStreamException{ + if (isClientConfigured()) { + numSamplesThreshold = clientConfiguredNumSamplesThreshold; + timeThresholdMs = clientConfiguredTimeThresholdMs; + } else { + numSamplesThreshold = NUM_SAMPLES_THRESHOLD; + timeThresholdMs = TIME_THRESHOLD_MS; + } + log.info("Using batching for this run." + + " Thresholds: num=" + numSamplesThreshold + + ", time=" + timeThresholdMs); + return this; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/Clearable.java b/ApacheJmeter/src/org/apache/jmeter/samplers/Clearable.java new file mode 100644 index 0000000..ce37929 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/Clearable.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +/** + * Identifies an object which supports the clearing of run-time data + * using the clearData() method. + * + * Intended for implementation by Listeners. + */ +public interface Clearable { + /** + * Clears the current data of the object. + */ + public void clearData(); + // N.B. originally called clear() + // @see also JMeterGUIComponent +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/DataStrippingSampleSender.java b/ApacheJmeter/src/org/apache/jmeter/samplers/DataStrippingSampleSender.java new file mode 100644 index 0000000..6454f4c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/DataStrippingSampleSender.java @@ -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. + * + */ + +package org.apache.jmeter.samplers; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.rmi.RemoteException; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The standard remote sample reporting should be more friendly to the main purpose of + * remote testing - which is scalability. To increase scalability, this class strips out the + * response data before sending. + * + * + */ +public class DataStrippingSampleSender extends AbstractSampleSender implements Serializable { + private static final long serialVersionUID = 1; + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final RemoteSampleListener listener; + private final SampleSender decoratedSender; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public DataStrippingSampleSender(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + listener = null; + decoratedSender = null; + } + + DataStrippingSampleSender(RemoteSampleListener listener) { + this.listener = listener; + decoratedSender = null; + log.info("Using DataStrippingSampleSender for this run"); + } + + DataStrippingSampleSender(SampleSender decorate) + { + this.decoratedSender = decorate; + this.listener = null; + log.info("Using DataStrippingSampleSender for this run"); + } + + public void testEnded(String host) { + log.info("Test Ended on " + host); + if(decoratedSender != null) decoratedSender.testEnded(host); + } + + public void sampleOccurred(SampleEvent event) { + //Strip the response data before writing, but only for a successful request. + SampleResult result = event.getResult(); + if(result.isSuccessful()) { + result.setResponseData(new byte[0]); + } + if(decoratedSender == null) + { + try { + listener.sampleOccurred(event); + } catch (RemoteException e) { + log.error("Error sending sample result over network ",e); + } + } + else + { + decoratedSender.sampleOccurred(event); + } + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * @throws ObjectStreamException + */ + private Object readResolve() throws ObjectStreamException{ + log.info("Using DataStrippingSampleSender for this run"); + return this; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/DiskStoreSampleSender.java b/ApacheJmeter/src/org/apache/jmeter/samplers/DiskStoreSampleSender.java new file mode 100644 index 0000000..8c06cc1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/DiskStoreSampleSender.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import org.apache.log.Logger; +import org.apache.commons.io.IOUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; + +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamException; +import java.io.OutputStream; +import java.io.Serializable; +import java.rmi.RemoteException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Version of HoldSampleSender that stores the samples on disk as a serialised stream. + */ + +public class DiskStoreSampleSender extends AbstractSampleSender implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 252L; + + private final RemoteSampleListener listener; + + private transient volatile ObjectOutputStream oos; + private transient volatile File temporaryFile; + private transient volatile ExecutorService singleExecutor; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public DiskStoreSampleSender(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + listener = null; + } + + DiskStoreSampleSender(RemoteSampleListener listener) { + this.listener = listener; + log.info("Using DiskStoreSampleSender for this test run"); // client log file + } + + public void testEnded(String host) { + log.info("Test Ended on " + host); + singleExecutor.submit(new Runnable(){ + public void run() { + try { + oos.close(); // ensure output is flushed + } catch (IOException e) { + log.error("Failed to close data file ", e); + } + }}); + singleExecutor.shutdown(); // finish processing samples + try { + if (!singleExecutor.awaitTermination(3, TimeUnit.SECONDS)) { + log.error("Executor did not terminate in a timely fashion"); + } + } catch (InterruptedException e1) { + log.error("Executor did not terminate in a timely fashion", e1); + } + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new FileInputStream(temporaryFile)); + Object obj = null; + while((obj = ois.readObject()) != null) { + if (obj instanceof SampleEvent) { + try { + listener.sampleOccurred((SampleEvent) obj); + } catch (RemoteException err) { + if (err.getCause() instanceof java.net.ConnectException){ + throw new JMeterError("Could not return sample",err); + } + log.error("returning sample", err); + } + } else { + log.error("Unexpected object type found in data file "+obj.getClass().getName()); + } + } + } catch (EOFException err) { + // expected + } catch (IOException err) { + log.error("returning sample", err); + } catch (ClassNotFoundException err) { + log.error("returning sample", err); + } finally { + try { + listener.testEnded(host); + } catch (RemoteException e) { + log.error("returning sample", e); + } + IOUtils.closeQuietly(ois); + if(!temporaryFile.delete()) { + log.warn("Could not delete file:"+temporaryFile.getAbsolutePath()); + } + } + } + + public void sampleOccurred(final SampleEvent e) { + // sampleOccurred is called from multiple threads; not safe to write from multiple threads. + // also decouples the file IO from sample generation + singleExecutor.submit(new Runnable() { + public void run() { + try { + oos.writeObject(e); + } catch (IOException err) { + log.error("sampleOccurred", err); + } + } + + } + ); + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * @throws ObjectStreamException + */ + // TODO should errors be thrown back through RMI? + private Object readResolve() throws ObjectStreamException{ + log.info("Using DiskStoreSampleSender for this test run"); // server log file + singleExecutor = Executors.newSingleThreadExecutor(); + try { + temporaryFile = File.createTempFile("SerialisedSampleSender", ".ser"); + temporaryFile.deleteOnExit(); + singleExecutor.submit(new Runnable(){ + public void run() { + OutputStream anOutputStream; + try { + anOutputStream = new FileOutputStream(temporaryFile); + oos = new ObjectOutputStream(anOutputStream); + } catch (IOException e) { + log.error("Failed to create output Stream", e); + } + }}); + } catch (IOException e) { + log.error("Failed to create output file", e); + } + return this; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/Entry.java b/ApacheJmeter/src/org/apache/jmeter/samplers/Entry.java new file mode 100644 index 0000000..8375f1e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/Entry.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.assertions.Assertion; +import org.apache.jmeter.config.ConfigElement; + +// TODO - not used at present - could perhaps be removed +public class Entry { + + private Map, ConfigElement> configSet; + + // Set clonedSet; + private Class sampler; + + private List assertions; + + public Entry() { + configSet = new HashMap, ConfigElement>(); + // clonedSet = new HashSet(); + assertions = new LinkedList(); + } + + public void addAssertion(Assertion assertion) { + assertions.add(assertion); + } + + public List getAssertions() { + return assertions; + } + + public void setSamplerClass(Class samplerClass) { + this.sampler = samplerClass; + } + + public Class getSamplerClass() { + return this.sampler; + } + + public ConfigElement getConfigElement(Class configClass) { + return configSet.get(configClass); + } + + public void addConfigElement(ConfigElement config) { + addConfigElement(config, config.getClass()); + } + + /** + * Add a config element as a specific class. Usually this is done to add a + * subclass as one of it's parent classes. + */ + public void addConfigElement(ConfigElement config, Class asClass) { + if (config != null) { + ConfigElement current = configSet.get(asClass); + if (current == null) { + configSet.put(asClass, cloneIfNecessary(config)); + } else { + current.addConfigElement(config); + } + } + } + + private ConfigElement cloneIfNecessary(ConfigElement config) { + if (config.expectsModification()) { + return config; + } + return (ConfigElement) config.clone(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/HoldSampleSender.java b/ApacheJmeter/src/org/apache/jmeter/samplers/HoldSampleSender.java new file mode 100644 index 0000000..547eaa2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/HoldSampleSender.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import org.apache.log.Logger; +import org.apache.jorphan.logging.LoggingManager; + +import java.util.List; +import java.util.ArrayList; +import java.io.ObjectStreamException; +import java.io.Serializable; + +/** + * Lars-Erik Helander provided the idea (and original implementation) for the + * caching functionality (sampleStore). + */ + +public class HoldSampleSender extends AbstractSampleSender implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private final List sampleStore = new ArrayList(); + + private final RemoteSampleListener listener; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public HoldSampleSender(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + listener = null; + } + + HoldSampleSender(RemoteSampleListener listener) { + this.listener = listener; + log.info("Using HoldSampleSender for this test run"); // client + } + + public void testEnded(String host) { + log.info("Test Ended on " + host); + try { + for (SampleEvent se : sampleStore) { + listener.sampleOccurred(se); + } + listener.testEnded(host); + sampleStore.clear(); + } catch (Throwable ex) { + log.error("testEnded(host)", ex); + if (ex instanceof Error){ + throw (Error) ex; + } + if (ex instanceof RuntimeException){ + throw (RuntimeException) ex; + } + } + + } + + public void sampleOccurred(SampleEvent e) { + synchronized (sampleStore) { + sampleStore.add(e); + } + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * @throws ObjectStreamException + */ + private Object readResolve() throws ObjectStreamException{ + log.info("Using HoldSampleSender for this test run"); // server + return this; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/Interruptible.java b/ApacheJmeter/src/org/apache/jmeter/samplers/Interruptible.java new file mode 100644 index 0000000..cd2cc08 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/Interruptible.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +/** + * Samplers which are able to interrupt a potentially long-running operation should + * implement this interface. + * + */ +public interface Interruptible { + + /** + * Interrupt the current operation if possible. + * + * @return true if there was an operation to interrupt. + */ + public boolean interrupt(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteListenerWrapper.java b/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteListenerWrapper.java new file mode 100644 index 0000000..77e2209 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteListenerWrapper.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.io.Serializable; +import java.rmi.RemoteException; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * + * Lars-Erik Helander provided the idea (and original implementation) for the + * caching functionality (sampleStore). + * + * @version $Revision: 1335273 $ + */ +public class RemoteListenerWrapper extends AbstractTestElement implements SampleListener, TestListener, Serializable, + NoThreadClone { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private final RemoteSampleListener listener; + + private final SampleSender sender; + + public RemoteListenerWrapper(RemoteSampleListener l) { + listener = l; + // Get appropriate sender class governed by the behaviour set in the JMeter property + sender = SampleSenderFactory.getInstance(listener); + } + + public RemoteListenerWrapper() // TODO: not used - make private? + { + listener = null; + sender = null; + } + + public void testStarted() { + log.debug("Test Started()"); + try { + listener.testStarted(); + } catch (Throwable ex) { + log.warn("testStarted()", ex); + if (ex instanceof Error){ + throw (Error) ex; + } + if (ex instanceof RuntimeException){ + throw (RuntimeException) ex; + } + } + + } + + public void testEnded() { + sender.testEnded(); + } + + public void testStarted(String host) { + log.debug("Test Started on " + host); + try { + listener.testStarted(host); + } catch (Throwable ex) { + log.error("testStarted(host)", ex); + if (ex instanceof Error){ + throw (Error) ex; + } + if (ex instanceof RuntimeException){ + throw (RuntimeException) ex; + } + } + } + + public void testEnded(String host) { + sender.testEnded(host); + } + + public void sampleOccurred(SampleEvent e) { + sender.sampleOccurred(e); + } + + // Note that sampleStarted() and sampleStopped() is not made to appear + // in synch with sampleOccured() when replaying held samples. + // For now this is not critical since sampleStarted() and sampleStopped() + // is not used, but it may become an issue in the future. Then these + // events must also be stored so that replay of all events may occur and + // in the right order. Each stored event must then be tagged with something + // that lets you distinguish between occured, started and ended. + + public void sampleStarted(SampleEvent e) { + log.debug("Sample started"); + try { + listener.sampleStarted(e); + } catch (RemoteException err) { + log.error("sampleStarted", err); + } + } + + public void sampleStopped(SampleEvent e) { + log.debug("Sample stopped"); + try { + listener.sampleStopped(e); + } catch (RemoteException err) { + log.error("sampleStopped", err); + } + } + + /** + * {@inheritDoc} + */ + public void testIterationStart(LoopIterationEvent event) { + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListener.java b/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListener.java new file mode 100644 index 0000000..7675f62 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListener.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.rmi.RemoteException; +import java.util.List; + +/** + * Allows notification on events occuring during the sampling process. + * Specifically, when sampling is started, when a specific sample is obtained, + * and when sampling is stopped. + * + * @version $Revision: 804660 $ + */ +public interface RemoteSampleListener extends java.rmi.Remote { + public void testStarted() throws RemoteException; + + public void testStarted(String host) throws RemoteException; + + public void testEnded() throws RemoteException; + + public void testEnded(String host) throws RemoteException; + + /** + * This method is called remotely and fires a list of samples events + * recieved locally. The function is to reduce network load when using + * remote testing. + * + * @param samples + * the list of sample events to be fired locally. + * @throws RemoteException + */ + public void processBatch(List samples) throws RemoteException; + + /** + * A sample has started and stopped. + */ + public void sampleOccurred(SampleEvent e) throws RemoteException; + + /** + * A sample has started. + */ + public void sampleStarted(SampleEvent e) throws RemoteException; + + /** + * A sample has stopped. + */ + public void sampleStopped(SampleEvent e) throws RemoteException; +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java b/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java new file mode 100644 index 0000000..0b4ae06 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.rmi.RemoteException; +import java.util.List; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Implementation of remote sampler listener, also supports TestListener + */ +public class RemoteSampleListenerImpl extends java.rmi.server.UnicastRemoteObject + implements RemoteSampleListener, SampleListener, TestListener { + + private static final long serialVersionUID = 240L; + + private final TestListener testListener; + + private final SampleListener sampleListener; + + private static final int DEFAULT_LOCAL_PORT = + JMeterUtils.getPropDefault("client.rmi.localport", 0); // $NON-NLS-1$ + + public RemoteSampleListenerImpl(Object listener) throws RemoteException { + super(DEFAULT_LOCAL_PORT); + if (listener instanceof TestListener) { + testListener = (TestListener) listener; + } else { + testListener = null; + } + if (listener instanceof SampleListener) { + sampleListener = (SampleListener) listener; + } else { + sampleListener = null; + } + } + + public void testStarted() { + if (testListener != null) { + testListener.testStarted(); + } + } + + public void testStarted(String host) { + if (testListener != null) { + testListener.testStarted(host); + } + } + + public void testEnded() { + if (testListener != null) { + testListener.testEnded(); + } + } + + public void testEnded(String host) { + if (testListener != null) { + testListener.testEnded(host); + } + } + + public void testIterationStart(LoopIterationEvent event) { + if (testListener != null) { + testListener.testIterationStart(event); + } + } + + /** + * This method is called remotely and fires a list of samples events + * received locally. The function is to reduce network load when using + * remote testing. + * + * @param samples + * the list of sample events to be fired locally + */ + public void processBatch(List samples) { + if (samples != null && sampleListener != null) { + for (SampleEvent e : samples) { + sampleListener.sampleOccurred(e); + } + } + } + + public void sampleOccurred(SampleEvent e) { + if (sampleListener != null) { + sampleListener.sampleOccurred(e); + } + } + + /** + * A sample has started. + */ + public void sampleStarted(SampleEvent e) { + if (sampleListener != null) { + sampleListener.sampleStarted(e); + } + } + + /** + * A sample has stopped. + */ + public void sampleStopped(SampleEvent e) { + if (sampleListener != null) { + sampleListener.sampleStopped(e); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListenerWrapper.java b/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListenerWrapper.java new file mode 100644 index 0000000..5dea312 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteSampleListenerWrapper.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.io.Serializable; +import java.rmi.RemoteException; + +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * @version $Revision: 905027 $ + */ + +public class RemoteSampleListenerWrapper extends AbstractTestElement implements SampleListener, Serializable, + NoThreadClone { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private RemoteSampleListener listener; + + public RemoteSampleListenerWrapper(RemoteSampleListener l) { + listener = l; + } + + public RemoteSampleListenerWrapper() { + } + + public void sampleOccurred(SampleEvent e) { + try { + listener.sampleOccurred(e); + } catch (RemoteException err) { + log.error("", err); // $NON-NLS-1$ + } + } + + public void sampleStarted(SampleEvent e) { + try { + listener.sampleStarted(e); + } catch (RemoteException err) { + log.error("", err); // $NON-NLS-1$ + } + } + + public void sampleStopped(SampleEvent e) { + try { + listener.sampleStopped(e); + } catch (RemoteException err) { + log.error("", err); // $NON-NLS-1$ + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteTestListenerWrapper.java b/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteTestListenerWrapper.java new file mode 100644 index 0000000..4661966 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/RemoteTestListenerWrapper.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.io.Serializable; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * @version $Revision: 908219 $ + */ +public class RemoteTestListenerWrapper extends AbstractTestElement implements TestListener, Serializable, NoThreadClone { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private final RemoteSampleListener listener; + + public RemoteTestListenerWrapper() { + log.warn("Only intended for use in testing"); + listener = null; + } + + public RemoteTestListenerWrapper(RemoteSampleListener l) { + listener = l; + } + + public void testStarted() { + try { + listener.testStarted(); + } catch (Exception ex) { + log.error("", ex); // $NON-NLS-1$ + } + + } + + public void testEnded() { + try { + listener.testEnded(); + } catch (Exception ex) { + log.error("", ex); // $NON-NLS-1$ + } + } + + public void testStarted(String host) { + try { + listener.testStarted(host); + } catch (Exception ex) { + log.error("", ex); // $NON-NLS-1$ + } + } + + public void testEnded(String host) { + try { + listener.testEnded(host); + } catch (Exception ex) { + log.error("", ex); // $NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + public void testIterationStart(LoopIterationEvent event) { + //listener.testIterationStart(event); + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/Remoteable.java b/ApacheJmeter/src/org/apache/jmeter/samplers/Remoteable.java new file mode 100644 index 0000000..1707d56 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/Remoteable.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +/** + * Marker interface used by ConvertListeners to determine which test elements to wrap + * so that the results are processed by the client rather than the server + */ +public interface Remoteable { +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/SampleEvent.java b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleEvent.java new file mode 100644 index 0000000..2827999 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleEvent.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.io.Serializable; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.log.Logger; + +/** + * Packages information regarding the target of a sample event, such as the + * result from that event and the thread group it ran in. + */ +public class SampleEvent implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 232L; + + public static final String SAMPLE_VARIABLES = "sample_variables"; // $NON-NLS-1$ + + public static final String HOSTNAME; + + // List of variable names to be saved in JTL files + private static final String[] variableNames; + // Number of variable names + private static final int varCount; + + // The values. Entries be null, but there will be the correct number. + private final String[] values; + + static { + String hn=""; + try { + hn = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + log.error("Cannot obtain local host name "+e); + } + HOSTNAME=hn; + + String vars = JMeterUtils.getProperty(SAMPLE_VARIABLES); + variableNames=vars != null ? vars.split(",") : new String[0]; + varCount=variableNames.length; + if (varCount>0){ + log.info(varCount + " sample_variables have been declared: "+vars); + } + } + + + private final SampleResult result; + + private final String threadGroup; // TODO appears to duplicate the threadName field in SampleResult + + private final String hostname; + + private final boolean isTransactionSampleEvent; + + /* + * Only for Unit tests + */ + public SampleEvent() { + this(null, null); + } + + /** + * Creates SampleEvent without saving any variables. + * + * Use by Proxy and StatisticalSampleSender. + * + * @param result SampleResult + * @param threadGroup name + */ + public SampleEvent(SampleResult result, String threadGroup) { + this(result, threadGroup, HOSTNAME, false); + } + + /** + * Contructor used for normal samples, saves variable values if any are defined. + * + * @param result + * @param threadGroup name + * @param jmvars Jmeter variables + */ + public SampleEvent(SampleResult result, String threadGroup, JMeterVariables jmvars) { + this(result, threadGroup, jmvars, false); + } + + /** + * Only intended for use when loading results from a file. + * + * @param result + * @param threadGroup + * @param hostname + */ + public SampleEvent(SampleResult result, String threadGroup, String hostname) { + this(result, threadGroup, hostname, false); + } + + private SampleEvent(SampleResult result, String threadGroup, String hostname, boolean isTransactionSampleEvent) { + this.result = result; + this.threadGroup = threadGroup; + this.hostname = hostname; + values = new String[variableNames.length]; + this.isTransactionSampleEvent = isTransactionSampleEvent; + } + + /** + * @param result + * @param threadGroup + * @param jmvars + * @param isTransactionSampleEvent + */ + public SampleEvent(SampleResult result, String threadGroup, JMeterVariables jmvars, boolean isTransactionSampleEvent) { + this(result, threadGroup, HOSTNAME, isTransactionSampleEvent); + saveVars(jmvars); + } + + private void saveVars(JMeterVariables vars){ + for(int i = 0; i < variableNames.length; i++){ + values[i] = vars.get(variableNames[i]); + } + } + + /** Return the number of variables defined */ + public static int getVarCount(){ + return varCount; + } + + /** Get the nth variable name (zero-based) */ + public static String getVarName(int i){ + return variableNames[i]; + } + + /** Get the nth variable value (zero-based) */ + public String getVarValue(int i){ + try { + return values[i]; + } catch (ArrayIndexOutOfBoundsException e) { + throw new JMeterError("Check the sample_variable settings!", e); + } + } + + public SampleResult getResult() { + return result; + } + + public String getThreadGroup() { + return threadGroup; + } + + public String getHostname() { + return hostname; + } + + /** + * @return the isTransactionSampleEvent + */ + public boolean isTransactionSampleEvent() { + return isTransactionSampleEvent; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/SampleListener.java b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleListener.java new file mode 100644 index 0000000..2bbb2a0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleListener.java @@ -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.samplers; + +/** + * Allows notification on events occuring during the sampling process. + * Specifically, when sampling is started, when a specific sample is obtained, + * and when sampling is stopped. + * + * @version $Revision: 674365 $ + */ +public interface SampleListener { + /** + * A sample has started and stopped. + */ + public void sampleOccurred(SampleEvent e); + + /** + * A sample has started. + */ + public void sampleStarted(SampleEvent e); + + /** + * A sample has stopped. + */ + public void sampleStopped(SampleEvent e); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/SampleResult.java b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleResult.java new file mode 100644 index 0000000..d676f76 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleResult.java @@ -0,0 +1,1301 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +// For unit tests, @see TestSampleResult + +/** + * This is a nice packaging for the various information returned from taking a + * sample of an entry. + * + */ +public class SampleResult implements Serializable { + + private static final long serialVersionUID = 241L; + + // Needs to be accessible from Test code + static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * The default encoding to be used if not overridden. + * The value is ISO-8859-1. + */ + public static final String DEFAULT_HTTP_ENCODING = "ISO-8859-1"; // $NON-NLS-1$ + + // Bug 33196 - encoding ISO-8859-1 is only suitable for Western countries + // However the suggested System.getProperty("file.encoding") is Cp1252 on + // Windows + // So use a new property with the original value as default + // needs to be accessible from test code + /** + * The default encoding to be used to decode the responseData byte array. + * The value is defined by the property "sampleresult.default.encoding" + * with a default of DEFAULT_HTTP_ENCODING if that is not defined. + */ + static final String DEFAULT_ENCODING + = JMeterUtils.getPropDefault("sampleresult.default.encoding", // $NON-NLS-1$ + DEFAULT_HTTP_ENCODING); + + /* The default used by {@link #setResponseData(String, String)} */ + private static final String DEFAULT_CHARSET = Charset.defaultCharset().name(); + + /** + * Data type value indicating that the response data is text. + * + * @see #getDataType + * @see #setDataType(java.lang.String) + */ + public final static String TEXT = "text"; // $NON-NLS-1$ + + /** + * Data type value indicating that the response data is binary. + * + * @see #getDataType + * @see #setDataType(java.lang.String) + */ + public final static String BINARY = "bin"; // $NON-NLS-1$ + + /* empty arrays which can be returned instead of null */ + private static final byte[] EMPTY_BA = new byte[0]; + + private static final SampleResult[] EMPTY_SR = new SampleResult[0]; + + private static final AssertionResult[] EMPTY_AR = new AssertionResult[0]; + + private static final boolean GETBYTES_BODY_REALSIZE = + JMeterUtils.getPropDefault("sampleresult.getbytes.body_real_size", true); // $NON-NLS-1$ + + private static final boolean GETBYTES_HEADERS_SIZE = + JMeterUtils.getPropDefault("sampleresult.getbytes.headers_size", true); // $NON-NLS-1$ + + private static final boolean GETBYTES_NETWORK_SIZE = + GETBYTES_HEADERS_SIZE && GETBYTES_BODY_REALSIZE ? true : false; + + private SampleSaveConfiguration saveConfig; + + private SampleResult parent = null; + + /** + * @param propertiesToSave + * The propertiesToSave to set. + */ + public void setSaveConfig(SampleSaveConfiguration propertiesToSave) { + this.saveConfig = propertiesToSave; + } + + public SampleSaveConfiguration getSaveConfig() { + return saveConfig; + } + + private byte[] responseData = EMPTY_BA; + + private String responseCode = "";// Never return null + + private String label = "";// Never return null + + /** Filename used by ResultSaver */ + private String resultFileName = ""; + + /** The data used by the sampler */ + private String samplerData; + + private String threadName = ""; // Never return null + + private String responseMessage = ""; + + private String responseHeaders = ""; // Never return null + + private String contentType = ""; // e.g. text/html; charset=utf-8 + + private String requestHeaders = ""; + + // TODO timeStamp == 0 means either not yet initialised or no stamp available (e.g. when loading a results file) + /** the time stamp - can be start or end */ + private long timeStamp = 0; + + private long startTime = 0; + + private long endTime = 0; + + private long idleTime = 0;// Allow for non-sample time + + /** Start of pause (if any) */ + private long pauseTime = 0; + + private List assertionResults; + + private List subResults; + + private String dataType=""; // Don't return null if not set + + private boolean success; + + //@GuardedBy("this"") + /** files that this sample has been saved in */ + private final Set files = new HashSet(); + + private String dataEncoding;// (is this really the character set?) e.g. + // ISO-8895-1, UTF-8 + + /** elapsed time */ + private long time = 0; + + /** time to first response */ + private long latency = 0; + + /** Should thread terminate? */ + private boolean stopThread = false; + + /** Should test terminate? */ + private boolean stopTest = false; + + /** Should test terminate abruptly? */ + private boolean stopTestNow = false; + + /** Is the sampler acting as a monitor? */ + private boolean isMonitor = false; + + private int sampleCount = 1; + + private int bytes = 0; // Allows override of sample size in case sampler does not want to store all the data + + private int headersSize = 0; + + private int bodySize = 0; + + /** Currently active threads in this thread group */ + private volatile int groupThreads = 0; + + /** Currently active threads in all thread groups */ + private volatile int allThreads = 0; + + // TODO do contentType and/or dataEncoding belong in HTTPSampleResult instead? + + private static final boolean startTimeStamp + = JMeterUtils.getPropDefault("sampleresult.timestamp.start", false); // $NON-NLS-1$ + + // Allow read-only access from test code + static final boolean USENANOTIME + = JMeterUtils.getPropDefault("sampleresult.useNanoTime", true); // $NON-NLS-1$ + + // How long between checks of nanotime; default 5000ms; set to <=0 to disable the thread + private static final long NANOTHREAD_SLEEP = + JMeterUtils.getPropDefault("sampleresult.nanoThreadSleep", 5000); // $NON-NLS-1$; + + static { + if (startTimeStamp) { + log.info("Note: Sample TimeStamps are START times"); + } else { + log.info("Note: Sample TimeStamps are END times"); + } + log.info("sampleresult.default.encoding is set to " + DEFAULT_ENCODING); + log.info("sampleresult.useNanoTime="+USENANOTIME); + log.info("sampleresult.nanoThreadSleep="+NANOTHREAD_SLEEP); + + if (USENANOTIME && NANOTHREAD_SLEEP > 0) { + // Make sure we start with a reasonable value + NanoOffset.nanoOffset = System.currentTimeMillis() - SampleResult.sampleNsClockInMs(); + NanoOffset nanoOffset = new NanoOffset(); + nanoOffset.setDaemon(true); + nanoOffset.setName("NanoOffset"); + nanoOffset.start(); + } + } + + + private final long nanoTimeOffset; + + // Allow testcode access to the settings + final boolean useNanoTime; + + final long nanoThreadSleep; + + private long initOffset(){ + if (useNanoTime){ + return nanoThreadSleep > 0 ? NanoOffset.getNanoOffset() : System.currentTimeMillis() - sampleNsClockInMs(); + } else { + return Long.MIN_VALUE; + } + } + + public SampleResult() { + this(USENANOTIME, NANOTHREAD_SLEEP); + } + + // Allow test code to change the default useNanoTime setting + SampleResult(boolean nanoTime) { + this(nanoTime, NANOTHREAD_SLEEP); + } + + // Allow test code to change the default useNanoTime and nanoThreadSleep settings + SampleResult(boolean nanoTime, long nanoThreadSleep) { + this.time = 0; + this.useNanoTime = nanoTime; + this.nanoThreadSleep = nanoThreadSleep; + this.nanoTimeOffset = initOffset(); + } + + /** + * Copy constructor. + * + * @param res existing sample result + */ + public SampleResult(SampleResult res) { + this(); + allThreads = res.allThreads;//OK + assertionResults = res.assertionResults;// TODO ?? + bytes = res.bytes; + headersSize = res.headersSize; + bodySize = res.bodySize; + contentType = res.contentType;//OK + dataEncoding = res.dataEncoding;//OK + dataType = res.dataType;//OK + endTime = res.endTime;//OK + // files is created automatically, and applies per instance + groupThreads = res.groupThreads;//OK + idleTime = res.idleTime; + isMonitor = res.isMonitor; + label = res.label;//OK + latency = res.latency; + location = res.location;//OK + parent = res.parent; // TODO ?? + pauseTime = res.pauseTime; + requestHeaders = res.requestHeaders;//OK + responseCode = res.responseCode;//OK + responseData = res.responseData;//OK + responseHeaders = res.responseHeaders;//OK + responseMessage = res.responseMessage;//OK + // Don't copy this; it is per instance resultFileName = res.resultFileName; + sampleCount = res.sampleCount; + samplerData = res.samplerData; + saveConfig = res.saveConfig; + startTime = res.startTime;//OK + stopTest = res.stopTest; + stopTestNow = res.stopTestNow; + stopThread = res.stopThread; + subResults = res.subResults; // TODO ?? + success = res.success;//OK + threadName = res.threadName;//OK + time = res.time; + timeStamp = res.timeStamp; + } + + public boolean isStampedAtStart() { + return startTimeStamp; + } + + /** + * Create a sample with a specific elapsed time but don't allow the times to + * be changed later + * + * (only used by HTTPSampleResult) + * + * @param elapsed + * time + * @param atend + * create the sample finishing now, else starting now + */ + protected SampleResult(long elapsed, boolean atend) { + this(); + long now = currentTimeInMillis(); + if (atend) { + setTimes(now - elapsed, now); + } else { + setTimes(now, now + elapsed); + } + } + + /** + * Create a sample with specific start and end times for test purposes, but + * don't allow the times to be changed later + * + * (used by StatVisualizerModel.Test) + * + * @param start + * start time + * @param end + * end time + */ + public static SampleResult createTestSample(long start, long end) { + SampleResult res = new SampleResult(); + res.setStartTime(start); + res.setEndTime(end); + return res; + } + + /** + * Create a sample with a specific elapsed time for test purposes, but don't + * allow the times to be changed later + * + * @param elapsed - + * desired elapsed time + */ + public static SampleResult createTestSample(long elapsed) { + long now = System.currentTimeMillis(); + return createTestSample(now, now + elapsed); + } + + /** + * Allow users to create a sample with specific timestamp and elapsed times + * for cloning purposes, but don't allow the times to be changed later + * + * Currently used by OldSaveService, CSVSaveService and StatisticalSampleResult + * + * @param stamp - + * this may be a start time or an end time + * @param elapsed + */ + public SampleResult(long stamp, long elapsed) { + this(); + stampAndTime(stamp, elapsed); + } + + private static long sampleNsClockInMs() { + return System.nanoTime() / 1000000; + } + + // Helper method to get 1 ms resolution timing. + public long currentTimeInMillis() { + if (useNanoTime){ + if (nanoTimeOffset == Long.MIN_VALUE){ + throw new RuntimeException("Invalid call; nanoTimeOffset as not been set"); + } + return sampleNsClockInMs() + nanoTimeOffset; + } + return System.currentTimeMillis(); + } + + // Helper method to maintain timestamp relationships + private void stampAndTime(long stamp, long elapsed) { + if (startTimeStamp) { + startTime = stamp; + endTime = stamp + elapsed; + } else { + startTime = stamp - elapsed; + endTime = stamp; + } + timeStamp = stamp; + time = elapsed; + } + + /* + * For use by SaveService only. + * + * @param stamp - + * this may be a start time or an end time + * @param elapsed + */ + public void setStampAndTime(long stamp, long elapsed) { + if (startTime != 0 || endTime != 0){ + throw new RuntimeException("Calling setStampAndTime() after start/end times have been set"); + } + stampAndTime(stamp, elapsed); + } + + /** + * Set the "marked" flag to show that the result has been written to the file. + * + * @param filename + * @return true if the result was previously marked + */ + public synchronized boolean markFile(String filename) { + return !files.add(filename); + } + + public String getResponseCode() { + return responseCode; + } + + private static final String OK_CODE = Integer.toString(HttpURLConnection.HTTP_OK); + private static final String OK_MSG = "OK"; // $NON-NLS-1$ + + /** + * Set response code to OK, i.e. "200" + * + */ + public void setResponseCodeOK(){ + responseCode=OK_CODE; + } + + public void setResponseCode(String code) { + responseCode = code; + } + + public boolean isResponseCodeOK(){ + return responseCode.equals(OK_CODE); + } + public String getResponseMessage() { + return responseMessage; + } + + public void setResponseMessage(String msg) { + responseMessage = msg; + } + + public void setResponseMessageOK() { + responseMessage = OK_MSG; + } + + /** + * Set result statuses OK - shorthand method to set: + *

    + *
  • ResponseCode
  • + *
  • ResponseMessage
  • + *
  • Successful status
  • + *
+ */ + public void setResponseOK(){ + setResponseCodeOK(); + setResponseMessageOK(); + setSuccessful(true); + } + + public String getThreadName() { + return threadName; + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + /** + * Get the sample timestamp, which may be either the start time or the end time. + * + * @see #getStartTime() + * @see #getEndTime() + * + * @return timeStamp in milliseconds + */ + public long getTimeStamp() { + return timeStamp; + } + + public String getSampleLabel() { + return label; + } + + /** + * Get the sample label for use in summary reports etc. + * + * @param includeGroup whether to include the thread group name + * @return the label + */ + public String getSampleLabel(boolean includeGroup) { + if (includeGroup) { + StringBuilder sb = new StringBuilder(threadName.substring(0,threadName.lastIndexOf(" "))); //$NON-NLS-1$ + return sb.append(":").append(label).toString(); //$NON-NLS-1$ + } + return label; + } + + public void setSampleLabel(String label) { + this.label = label; + } + + public void addAssertionResult(AssertionResult assertResult) { + if (assertionResults == null) { + assertionResults = new ArrayList(); + } + assertionResults.add(assertResult); + } + + /** + * Gets the assertion results associated with this sample. + * + * @return an array containing the assertion results for this sample. + * Returns empty array if there are no assertion results. + */ + public AssertionResult[] getAssertionResults() { + if (assertionResults == null) { + return EMPTY_AR; + } + return assertionResults.toArray(new AssertionResult[assertionResults.size()]); + } + + /** + * Add a subresult and adjust the parent byte count and end-time. + * + * @param subResult + */ + public void addSubResult(SampleResult subResult) { + String tn = getThreadName(); + if (tn.length()==0) { + tn=Thread.currentThread().getName();//TODO do this more efficiently + this.setThreadName(tn); + } + subResult.setThreadName(tn); // TODO is this really necessary? + + // Extend the time to the end of the added sample + setEndTime(Math.max(getEndTime(), subResult.getEndTime() + nanoTimeOffset - subResult.nanoTimeOffset)); // Bug 51855 + // Include the byte count for the added sample + setBytes(getBytes() + subResult.getBytes()); + setHeadersSize(getHeadersSize() + subResult.getHeadersSize()); + setBodySize(getBodySize() + subResult.getBodySize()); + addRawSubResult(subResult); + } + + /** + * Add a subresult to the collection without updating any parent fields. + * + * @param subResult + */ + public void addRawSubResult(SampleResult subResult){ + if (subResults == null) { + subResults = new ArrayList(); + } + subResults.add(subResult); + subResult.setParent(this); + } + + /** + * Add a subresult read from a results file. + * + * As for addSubResult(), except that the fields don't need to be accumulated + * + * @param subResult + */ + public void storeSubResult(SampleResult subResult) { + if (subResults == null) { + subResults = new ArrayList(); + } + subResults.add(subResult); + subResult.setParent(this); + } + + /** + * Gets the subresults associated with this sample. + * + * @return an array containing the subresults for this sample. Returns an + * empty array if there are no subresults. + */ + public SampleResult[] getSubResults() { + if (subResults == null) { + return EMPTY_SR; + } + return subResults.toArray(new SampleResult[subResults.size()]); + } + + /** + * Sets the responseData attribute of the SampleResult object. + * + * If the parameter is null, then the responseData is set to an empty byte array. + * This ensures that getResponseData() can never be null. + * + * @param response + * the new responseData value + */ + public void setResponseData(byte[] response) { + responseData = response == null ? EMPTY_BA : response; + } + + /** + * Sets the responseData attribute of the SampleResult object. + * Should only be called after setting the dataEncoding (if necessary) + * + * @param response + * the new responseData value (String) + * + * @deprecated - only intended for use from BeanShell code + */ + @Deprecated + public void setResponseData(String response) { + try { + responseData = response.getBytes(getDataEncodingWithDefault()); + } catch (UnsupportedEncodingException e) { + log.warn("Could not convert string, using default encoding. "+e.getLocalizedMessage()); + responseData = response.getBytes(); // N.B. default charset is used deliberately here + } + } + + /** + * Sets the encoding and responseData attributes of the SampleResult object. + * + * @param response the new responseData value (String) + * @param encoding the encoding to set and then use (if null, use platform default) + * + */ + public void setResponseData(final String response, final String encoding) { + String encodeUsing = encoding != null? encoding : DEFAULT_CHARSET; + try { + responseData = response.getBytes(encodeUsing); + setDataEncoding(encodeUsing); + } catch (UnsupportedEncodingException e) { + log.warn("Could not convert string using '"+encodeUsing+ + "', using default encoding: "+DEFAULT_CHARSET,e); + responseData = response.getBytes(); // N.B. default charset is used deliberately here + setDataEncoding(DEFAULT_CHARSET); + } + } + + /** + * Gets the responseData attribute of the SampleResult object. + *

+ * Note that some samplers may not store all the data, in which case + * getResponseData().length will be incorrect. + * + * Instead, always use {@link #getBytes()} to obtain the sample result byte count. + *

+ * @return the responseData value (cannot be null) + */ + public byte[] getResponseData() { + return responseData; + } + + /** + * Gets the responseData of the SampleResult object as a String + * + * @return the responseData value as a String, converted according to the encoding + */ + public String getResponseDataAsString() { + try { + return new String(responseData,getDataEncodingWithDefault()); + } catch (UnsupportedEncodingException e) { + log.warn("Using platform default as "+getDataEncodingWithDefault()+" caused "+e); + return new String(responseData); // N.B. default charset is used deliberately here + } + } + + public void setSamplerData(String s) { + samplerData = s; + } + + public String getSamplerData() { + return samplerData; + } + + /** + * Get the time it took this sample to occur. + * + * @return elapsed time in milliseonds + * + */ + public long getTime() { + return time; + } + + public boolean isSuccessful() { + return success; + } + + public void setDataType(String dataType) { + this.dataType = dataType; + } + + public String getDataType() { + return dataType; + } + /** + * Extract and save the DataEncoding and DataType from the parameter provided. + * Does not save the full content Type. + * @see #setContentType(String) which should be used to save the full content-type string + * + * @param ct - content type (may be null) + */ + public void setEncodingAndType(String ct){ + if (ct != null) { + // Extract charset and store as DataEncoding + // N.B. The meta tag: + // + // is now processed by HTTPSampleResult#getDataEncodingWithDefault + final String CS_PFX = "charset="; // $NON-NLS-1$ + int cset = ct.toLowerCase(java.util.Locale.ENGLISH).indexOf(CS_PFX); + if (cset >= 0) { + String charSet = ct.substring(cset + CS_PFX.length()); + // handle: ContentType: text/plain; charset=ISO-8859-1; format=flowed + int semiColon = charSet.indexOf(';'); + if (semiColon >= 0) { + charSet=charSet.substring(0, semiColon); + } + // Check for quoted string + if (charSet.startsWith("\"")){ // $NON-NLS-1$ + setDataEncoding(charSet.substring(1, charSet.length()-1)); // remove quotes + } else { + setDataEncoding(charSet); + } + } + if (isBinaryType(ct)) { + setDataType(BINARY); + } else { + setDataType(TEXT); + } + } + } + + // List of types that are known to be binary + private static final String[] BINARY_TYPES = { + "image/", //$NON-NLS-1$ + "audio/", //$NON-NLS-1$ + "video/", //$NON-NLS-1$ + }; + + /* + * Determine if content-type is known to be binary, i.e. not displayable as text. + * + * @param ct content type + * @return true if content-type is of type binary. + */ + private static boolean isBinaryType(String ct){ + for (int i = 0; i < BINARY_TYPES.length; i++){ + if (ct.startsWith(BINARY_TYPES[i])){ + return true; + } + } + return false; + } + + /** + * Sets the successful attribute of the SampleResult object. + * + * @param success + * the new successful value + */ + public void setSuccessful(boolean success) { + this.success = success; + } + + /** + * Returns the display name. + * + * @return display name of this sample result + */ + @Override + public String toString() { + return getSampleLabel(); + } + + /** + * Returns the dataEncoding or the default if no dataEncoding was provided. + * + * @return the value of the dataEncoding or DEFAULT_ENCODING + */ + public String getDataEncodingWithDefault() { + return getDataEncodingWithDefault(DEFAULT_ENCODING); + } + + /** + * Returns the dataEncoding or the default if no dataEncoding was provided. + * + * @param defaultEncoding the default to be applied + * @return the value of the dataEncoding or the provided default + */ + protected String getDataEncodingWithDefault(String defaultEncoding) { + if (dataEncoding != null && dataEncoding.length() > 0) { + return dataEncoding; + } + return defaultEncoding; + } + + /** + * Returns the dataEncoding. May be null or the empty String. + * @return the value of the dataEncoding + */ + public String getDataEncodingNoDefault() { + return dataEncoding; + } + + /** + * Sets the dataEncoding. + * + * @param dataEncoding + * the dataEncoding to set, e.g. ISO-8895-1, UTF-8 + */ + public void setDataEncoding(String dataEncoding) { + this.dataEncoding = dataEncoding; + } + + /** + * @return whether to stop the test + */ + public boolean isStopTest() { + return stopTest; + } + + /** + * @return whether to stop the test now + */ + public boolean isStopTestNow() { + return stopTestNow; + } + + /** + * @return whether to stop this thread + */ + public boolean isStopThread() { + return stopThread; + } + + public void setStopTest(boolean b) { + stopTest = b; + } + + public void setStopTestNow(boolean b) { + stopTestNow = b; + } + + public void setStopThread(boolean b) { + stopThread = b; + } + + /** + * @return the request headers + */ + public String getRequestHeaders() { + return requestHeaders; + } + + /** + * @return the response headers + */ + public String getResponseHeaders() { + return responseHeaders; + } + + /** + * @param string - + * request headers + */ + public void setRequestHeaders(String string) { + requestHeaders = string; + } + + /** + * @param string - + * response headers + */ + public void setResponseHeaders(String string) { + responseHeaders = string; + } + + /** + * @return the full content type - e.g. text/html [;charset=utf-8 ] + */ + public String getContentType() { + return contentType; + } + + /** + * Get the media type from the Content Type + * @return the media type - e.g. text/html (without charset, if any) + */ + public String getMediaType() { + return JOrphanUtils.trim(contentType," ;").toLowerCase(java.util.Locale.ENGLISH); + } + + /** + * Stores the content-type string, e.g. "text/xml; charset=utf-8" + * @see #setEncodingAndType(String) which can be used to extract the charset. + * + * @param string + */ + public void setContentType(String string) { + contentType = string; + } + + /** + * @return idleTime + */ + public long getIdleTime() { + return idleTime; + } + + /** + * @return the end time + */ + public long getEndTime() { + return endTime; + } + + /** + * @return the start time + */ + public long getStartTime() { + return startTime; + } + + /* + * Helper methods N.B. setStartTime must be called before setEndTime + * + * setStartTime is used by HTTPSampleResult to clone the parent sampler and + * allow the original start time to be kept + */ + protected final void setStartTime(long start) { + startTime = start; + if (startTimeStamp) { + timeStamp = startTime; + } + } + + public void setEndTime(long end) { + endTime = end; + if (!startTimeStamp) { + timeStamp = endTime; + } + if (startTime == 0) { + log.error("setEndTime must be called after setStartTime", new Throwable("Invalid call sequence")); + // TODO should this throw an error? + } else { + time = endTime - startTime - idleTime; + } + } + + /** + * Set idle time pause. + * For use by SampleResultConverter/CSVSaveService. + * @param idle long + */ + public void setIdleTime(long idle) { + idleTime = idle; + } + + private void setTimes(long start, long end) { + setStartTime(start); + setEndTime(end); + } + + /** + * Record the start time of a sample + * + */ + public void sampleStart() { + if (startTime == 0) { + setStartTime(currentTimeInMillis()); + } else { + log.error("sampleStart called twice", new Throwable("Invalid call sequence")); + } + } + + /** + * Record the end time of a sample and calculate the elapsed time + * + */ + public void sampleEnd() { + if (endTime == 0) { + setEndTime(currentTimeInMillis()); + } else { + log.error("sampleEnd called twice", new Throwable("Invalid call sequence")); + } + } + + /** + * Pause a sample + * + */ + public void samplePause() { + if (pauseTime != 0) { + log.error("samplePause called twice", new Throwable("Invalid call sequence")); + } + pauseTime = currentTimeInMillis(); + } + + /** + * Resume a sample + * + */ + public void sampleResume() { + if (pauseTime == 0) { + log.error("sampleResume without samplePause", new Throwable("Invalid call sequence")); + } + idleTime += currentTimeInMillis() - pauseTime; + pauseTime = 0; + } + + /** + * When a Sampler is working as a monitor + * + * @param monitor + */ + public void setMonitor(boolean monitor) { + isMonitor = monitor; + } + + /** + * If the sampler is a monitor, method will return true. + * + * @return true if the sampler is a monitor + */ + public boolean isMonitor() { + return isMonitor; + } + + /** + * The statistical sample sender aggregates several samples to save on + * transmission costs. + * + * @param count number of samples represented by this instance + */ + public void setSampleCount(int count) { + sampleCount = count; + } + + /** + * return the sample count. by default, the value is 1. + * + * @return the sample count + */ + public int getSampleCount() { + return sampleCount; + } + + /** + * Returns the count of errors. + * + * @return 0 - or 1 if the sample failed + * + * TODO do we need allow for nested samples? + */ + public int getErrorCount(){ + return success ? 0 : 1; + } + + public void setErrorCount(int i){// for reading from CSV files + // ignored currently + } + + /* + * TODO: error counting needs to be sorted out. + * + * At present the Statistical Sampler tracks errors separately + * It would make sense to move the error count here, but this would + * mean lots of changes. + * It's also tricky maintaining the count - it can't just be incremented/decremented + * when the success flag is set as this may be done multiple times. + * The work-round for now is to do the work in the StatisticalSampleResult, + * which overrides this method. + * Note that some JMS samplers also create samples with > 1 sample count + * Also the Transaction Controller probably needs to be changed to do + * proper sample and error accounting. + * The purpose of this work-round is to allow at least minimal support for + * errors in remote statistical batch mode. + * + */ + /** + * In the event the sampler does want to pass back the actual contents, we + * still want to calculate the throughput. The bytes is the bytes of the + * response data. + * + * @param length + */ + public void setBytes(int length) { + bytes = length; + } + + /** + * return the bytes returned by the response. + * + * @return byte count + */ + public int getBytes() { + if (GETBYTES_NETWORK_SIZE) { + int tmpSum = this.getHeadersSize() + this.getBodySize(); + return tmpSum == 0 ? bytes : tmpSum; + } else if (GETBYTES_HEADERS_SIZE) { + return this.getHeadersSize(); + } else if (GETBYTES_BODY_REALSIZE) { + return this.getBodySize(); + } + return bytes == 0 ? responseData.length : bytes; + } + + /** + * @return Returns the latency. + */ + public long getLatency() { + return latency; + } + + /** + * Set the time to the first response + * + */ + public void latencyEnd() { + latency = currentTimeInMillis() - startTime - idleTime; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param latency + * The latency to set. + */ + public void setLatency(long latency) { + this.latency = latency; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param timeStamp + * The timeStamp to set. + */ + public void setTimeStamp(long timeStamp) { + this.timeStamp = timeStamp; + } + + private URL location; + + public void setURL(URL location) { + this.location = location; + } + + public URL getURL() { + return location; + } + + /** + * Get a String representation of the URL (if defined). + * + * @return ExternalForm of URL, or empty string if url is null + */ + public String getUrlAsString() { + return location == null ? "" : location.toExternalForm(); + } + + /** + * @return Returns the parent. + */ + public SampleResult getParent() { + return parent; + } + + /** + * @param parent + * The parent to set. + */ + public void setParent(SampleResult parent) { + this.parent = parent; + } + + public String getResultFileName() { + return resultFileName; + } + + public void setResultFileName(String resultFileName) { + this.resultFileName = resultFileName; + } + + public int getGroupThreads() { + return groupThreads; + } + + public void setGroupThreads(int n) { + this.groupThreads = n; + } + + public int getAllThreads() { + return allThreads; + } + + public void setAllThreads(int n) { + this.allThreads = n; + } + + // Bug 47394 + /** + * Allow custom SampleSenders to drop unwanted assertionResults + */ + public void removeAssertionResults() { + this.assertionResults = null; + } + + /** + * Allow custom SampleSenders to drop unwanted subResults + */ + public void removeSubResults() { + this.subResults = null; + } + + /** + * Set the headers size in bytes + * + * @param size + */ + public void setHeadersSize(int size) { + this.headersSize = size; + } + + /** + * Get the headers size in bytes + * + * @return the headers size + */ + public int getHeadersSize() { + return headersSize; + } + + /** + * @return the body size in bytes + */ + public int getBodySize() { + return bodySize == 0 ? responseData.length : bodySize; + } + + /** + * @param bodySize the body size to set + */ + public void setBodySize(int bodySize) { + this.bodySize = bodySize; + } + + private static class NanoOffset extends Thread { + + private static volatile long nanoOffset; + + static long getNanoOffset() { + return nanoOffset; + } + + @Override + public void run() { + // Wait longer than a clock pulse (generally 10-15ms) + getOffset(30L); // Catch an early clock pulse to reduce slop. + while(true) { + getOffset(NANOTHREAD_SLEEP); // Can now afford to wait a bit longer between checks + } + + } + + private void getOffset(long wait) { + try { + Thread.sleep(wait); + long clock = System.currentTimeMillis(); + long nano = SampleResult.sampleNsClockInMs(); + nanoOffset = clock - nano; + } catch (InterruptedException ignore) { + // ignored + } + } + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/SampleSaveConfiguration.java b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleSaveConfiguration.java new file mode 100644 index 0000000..94679b2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleSaveConfiguration.java @@ -0,0 +1,830 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 7, 2004 + */ +package org.apache.jmeter.samplers; + +import java.io.Serializable; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Properties; + +import org.apache.commons.lang.CharUtils; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JMeterError; + +/* + * N.B. to add a new field, remember the following + * - static _xyz + * - instance xyz=_xyz + * - clone s.xyz = xyz (perhaps) + * - setXyz(boolean) + * - saveXyz() + * - update SampleSaveConfigurationConverter to add new fields to marshall() and shouldSerialiseMember() + * - update SampleResultConverter and/or HTTPSampleConverter + * - update CSVSaveService: CSV_XXXX, makeResultFromDelimitedString, printableFieldNamesToString, static{} + * - update messages.properties to add save_xyz entry + * - update jmeter.properties to add new property + * - update listeners.xml to add new property, CSV and XML names etc. + * - take screenshot sample_result_config.png + * - update listeners.xml and component_reference.xml with new dimensions (might not change) + * + */ +/** + * Holds details of which sample attributes to save. + * + * The pop-up dialogue for this is created by the class SavePropertyDialog, which assumes: + * For each field XXX + * - methods have the signature "boolean saveXXX()" + * - a corresponding "void setXXX(boolean)" method + * - messages.properties contains the key save_XXX + * + * + */ +public class SampleSaveConfiguration implements Cloneable, Serializable { + private static final long serialVersionUID = 7L; + + // --------------------------------------------------------------------- + // PROPERTY FILE CONSTANTS + // --------------------------------------------------------------------- + + /** Indicates that the results file should be in XML format. * */ + private static final String XML = "xml"; // $NON_NLS-1$ + + /** Indicates that the results file should be in CSV format. * */ + //NOTUSED private static final String CSV = "csv"; // $NON_NLS-1$ + + /** Indicates that the results should be stored in a database. * */ + //NOTUSED private static final String DATABASE = "db"; // $NON_NLS-1$ + + /** A properties file indicator for true. * */ + private static final String TRUE = "true"; // $NON_NLS-1$ + + /** A properties file indicator for false. * */ + private static final String FALSE = "false"; // $NON_NLS-1$ + + /** A properties file indicator for milliseconds. * */ + private static final String MILLISECONDS = "ms"; // $NON_NLS-1$ + + /** A properties file indicator for none. * */ + private static final String NONE = "none"; // $NON_NLS-1$ + + /** A properties file indicator for the first of a series. * */ + private static final String FIRST = "first"; // $NON_NLS-1$ + + /** A properties file indicator for all of a series. * */ + private static final String ALL = "all"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating which assertion results should be + * saved. + **************************************************************************/ + private static final String ASSERTION_RESULTS_FAILURE_MESSAGE_PROP = + "jmeter.save.saveservice.assertion_results_failure_message"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating which assertion results should be + * saved. + **************************************************************************/ + private static final String ASSERTION_RESULTS_PROP = "jmeter.save.saveservice.assertion_results"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating which delimiter should be used when + * saving in a delimited values format. + **************************************************************************/ + private static final String DEFAULT_DELIMITER_PROP = "jmeter.save.saveservice.default_delimiter"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating which format should be used when + * saving the results, e.g., xml or csv. + **************************************************************************/ + private static final String OUTPUT_FORMAT_PROP = "jmeter.save.saveservice.output_format"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether field names should be printed + * to a delimited file. + **************************************************************************/ + private static final String PRINT_FIELD_NAMES_PROP = "jmeter.save.saveservice.print_field_names"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the data type should be + * saved. + **************************************************************************/ + private static final String SAVE_DATA_TYPE_PROP = "jmeter.save.saveservice.data_type"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the label should be saved. + **************************************************************************/ + private static final String SAVE_LABEL_PROP = "jmeter.save.saveservice.label"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the response code should be + * saved. + **************************************************************************/ + private static final String SAVE_RESPONSE_CODE_PROP = "jmeter.save.saveservice.response_code"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the response data should be + * saved. + **************************************************************************/ + private static final String SAVE_RESPONSE_DATA_PROP = "jmeter.save.saveservice.response_data"; // $NON_NLS-1$ + + private static final String SAVE_RESPONSE_DATA_ON_ERROR_PROP = "jmeter.save.saveservice.response_data.on_error"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the response message should + * be saved. + **************************************************************************/ + private static final String SAVE_RESPONSE_MESSAGE_PROP = "jmeter.save.saveservice.response_message"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the success indicator should + * be saved. + **************************************************************************/ + private static final String SAVE_SUCCESSFUL_PROP = "jmeter.save.saveservice.successful"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the thread name should be + * saved. + **************************************************************************/ + private static final String SAVE_THREAD_NAME_PROP = "jmeter.save.saveservice.thread_name"; // $NON_NLS-1$ + + // Save bytes read + private static final String SAVE_BYTES_PROP = "jmeter.save.saveservice.bytes"; // $NON_NLS-1$ + + // Save URL + private static final String SAVE_URL_PROP = "jmeter.save.saveservice.url"; // $NON_NLS-1$ + + // Save fileName for ResultSaver + private static final String SAVE_FILENAME_PROP = "jmeter.save.saveservice.filename"; // $NON_NLS-1$ + + // Save hostname for ResultSaver + private static final String SAVE_HOSTNAME_PROP = "jmeter.save.saveservice.hostname"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property indicating whether the time should be saved. + **************************************************************************/ + private static final String SAVE_TIME_PROP = "jmeter.save.saveservice.time"; // $NON_NLS-1$ + + /*************************************************************************** + * The name of the property giving the format of the time stamp + **************************************************************************/ + private static final String TIME_STAMP_FORMAT_PROP = "jmeter.save.saveservice.timestamp_format"; // $NON_NLS-1$ + + private static final String SUBRESULTS_PROP = "jmeter.save.saveservice.subresults"; // $NON_NLS-1$ + private static final String ASSERTIONS_PROP = "jmeter.save.saveservice.assertions"; // $NON_NLS-1$ + private static final String LATENCY_PROP = "jmeter.save.saveservice.latency"; // $NON_NLS-1$ + private static final String SAMPLERDATA_PROP = "jmeter.save.saveservice.samplerData"; // $NON_NLS-1$ + private static final String RESPONSEHEADERS_PROP = "jmeter.save.saveservice.responseHeaders"; // $NON_NLS-1$ + private static final String REQUESTHEADERS_PROP = "jmeter.save.saveservice.requestHeaders"; // $NON_NLS-1$ + private static final String ENCODING_PROP = "jmeter.save.saveservice.encoding"; // $NON_NLS-1$ + + + // optional processing instruction for line 2; e.g. + // + private static final String XML_PI = "jmeter.save.saveservice.xml_pi"; // $NON_NLS-1$ + + private static final String SAVE_THREAD_COUNTS = "jmeter.save.saveservice.thread_counts"; // $NON_NLS-1$ + + private static final String SAVE_SAMPLE_COUNT = "jmeter.save.saveservice.sample_count"; // $NON_NLS-1$ + + private static final String SAVE_IDLE_TIME = "jmeter.save.saveservice.idle_time"; // $NON_NLS-1$ + // N.B. Remember to update the equals and hashCode methods when adding new variables. + + // Initialise values from properties + private boolean time = _time, latency = _latency, timestamp = _timestamp, success = _success, label = _label, + code = _code, message = _message, threadName = _threadName, dataType = _dataType, encoding = _encoding, + assertions = _assertions, subresults = _subresults, responseData = _responseData, + samplerData = _samplerData, xml = _xml, fieldNames = _fieldNames, responseHeaders = _responseHeaders, + requestHeaders = _requestHeaders, responseDataOnError = _responseDataOnError; + + private boolean saveAssertionResultsFailureMessage = _saveAssertionResultsFailureMessage; + + private boolean url = _url, bytes = _bytes , fileName = _fileName; + + private boolean hostname = _hostname; + + private boolean threadCounts = _threadCounts; + + private boolean sampleCount = _sampleCount; + + private boolean idleTime = _idleTime; + + // Does not appear to be used (yet) + private int assertionsResultsToSave = _assertionsResultsToSave; + + + // Don't save this, as it is derived from the time format + private boolean printMilliseconds = _printMilliseconds; + + /** A formatter for the time stamp. */ + private transient DateFormat formatter = _formatter; + /* Make transient as we don't want to save the SimpleDataFormat class + * Also, there's currently no way to change the value via the GUI, so changing it + * later means editting the JMX, or recreating the Listener. + */ + + // Defaults from properties: + private static final boolean _time, _timestamp, _success, _label, _code, _message, _threadName, _xml, + _responseData, _dataType, _encoding, _assertions, _latency, _subresults, _samplerData, _fieldNames, + _responseHeaders, _requestHeaders; + + private static final boolean _responseDataOnError; + + private static final boolean _saveAssertionResultsFailureMessage; + + private static final String _timeStampFormat; + + private static final int _assertionsResultsToSave; + + // TODO turn into method? + public static final int SAVE_NO_ASSERTIONS = 0; + + public static final int SAVE_FIRST_ASSERTION = SAVE_NO_ASSERTIONS + 1; + + public static final int SAVE_ALL_ASSERTIONS = SAVE_FIRST_ASSERTION + 1; + + private static final boolean _printMilliseconds; + + private static final boolean _bytes; + + private static final boolean _url; + + private static final boolean _fileName; + + private static final boolean _hostname; + + private static final boolean _threadCounts; + + private static final boolean _sampleCount; + + private static final DateFormat _formatter; + + /** + * The string used to separate fields when stored to disk, for example, the + * comma for CSV files. + */ + private static final String _delimiter; + + private static final boolean _idleTime; + + private static final String DEFAULT_DELIMITER = ","; // $NON_NLS-1$ + + /** + * Read in the properties having to do with saving from a properties file. + */ + static { + Properties props = JMeterUtils.getJMeterProperties(); + + _subresults = TRUE.equalsIgnoreCase(props.getProperty(SUBRESULTS_PROP, TRUE)); + _assertions = TRUE.equalsIgnoreCase(props.getProperty(ASSERTIONS_PROP, TRUE)); + _latency = TRUE.equalsIgnoreCase(props.getProperty(LATENCY_PROP, TRUE)); + _samplerData = TRUE.equalsIgnoreCase(props.getProperty(SAMPLERDATA_PROP, FALSE)); + _responseHeaders = TRUE.equalsIgnoreCase(props.getProperty(RESPONSEHEADERS_PROP, FALSE)); + _requestHeaders = TRUE.equalsIgnoreCase(props.getProperty(REQUESTHEADERS_PROP, FALSE)); + _encoding = TRUE.equalsIgnoreCase(props.getProperty(ENCODING_PROP, FALSE)); + + String dlm = props.getProperty(DEFAULT_DELIMITER_PROP, DEFAULT_DELIMITER); + if (dlm.equals("\\t")) {// Make it easier to enter a tab (can use \ but that is awkward) + dlm="\t"; + } + + if (dlm.length() != 1){ + throw new JMeterError("Delimiter '"+dlm+"' must be of length 1."); + } + char ch = dlm.charAt(0); + + if (CharUtils.isAsciiAlphanumeric(ch) || ch == CSVSaveService.QUOTING_CHAR){ + throw new JMeterError("Delimiter '"+ch+"' must not be alphanumeric or "+CSVSaveService.QUOTING_CHAR+"."); + } + + if (ch != '\t' && !CharUtils.isAsciiPrintable(ch)){ + throw new JMeterError("Delimiter (code "+(int)ch+") must be printable."); + } + + _delimiter = dlm; + + _fieldNames = TRUE.equalsIgnoreCase(props.getProperty(PRINT_FIELD_NAMES_PROP, FALSE)); + + _dataType = TRUE.equalsIgnoreCase(props.getProperty(SAVE_DATA_TYPE_PROP, TRUE)); + + _label = TRUE.equalsIgnoreCase(props.getProperty(SAVE_LABEL_PROP, TRUE)); + + _code = TRUE.equalsIgnoreCase(props.getProperty(SAVE_RESPONSE_CODE_PROP, TRUE)); + + _responseData = TRUE.equalsIgnoreCase(props.getProperty(SAVE_RESPONSE_DATA_PROP, FALSE)); + + _responseDataOnError = TRUE.equalsIgnoreCase(props.getProperty(SAVE_RESPONSE_DATA_ON_ERROR_PROP, FALSE)); + + _message = TRUE.equalsIgnoreCase(props.getProperty(SAVE_RESPONSE_MESSAGE_PROP, TRUE)); + + _success = TRUE.equalsIgnoreCase(props.getProperty(SAVE_SUCCESSFUL_PROP, TRUE)); + + _threadName = TRUE.equalsIgnoreCase(props.getProperty(SAVE_THREAD_NAME_PROP, TRUE)); + + _bytes = TRUE.equalsIgnoreCase(props.getProperty(SAVE_BYTES_PROP, TRUE)); + + _url = TRUE.equalsIgnoreCase(props.getProperty(SAVE_URL_PROP, FALSE)); + + _fileName = TRUE.equalsIgnoreCase(props.getProperty(SAVE_FILENAME_PROP, FALSE)); + + _hostname = TRUE.equalsIgnoreCase(props.getProperty(SAVE_HOSTNAME_PROP, FALSE)); + + _time = TRUE.equalsIgnoreCase(props.getProperty(SAVE_TIME_PROP, TRUE)); + + _timeStampFormat = props.getProperty(TIME_STAMP_FORMAT_PROP, MILLISECONDS); + + _printMilliseconds = MILLISECONDS.equalsIgnoreCase(_timeStampFormat); + + // Prepare for a pretty date + if (!_printMilliseconds && !NONE.equalsIgnoreCase(_timeStampFormat) && (_timeStampFormat != null)) { + _formatter = new SimpleDateFormat(_timeStampFormat); + } else { + _formatter = null; + } + + _timestamp = !NONE.equalsIgnoreCase(_timeStampFormat);// reversed compare allows for null + + _saveAssertionResultsFailureMessage = TRUE.equalsIgnoreCase(props.getProperty( + ASSERTION_RESULTS_FAILURE_MESSAGE_PROP, FALSE)); + + String whichAssertionResults = props.getProperty(ASSERTION_RESULTS_PROP, NONE); + if (NONE.equals(whichAssertionResults)) { + _assertionsResultsToSave = SAVE_NO_ASSERTIONS; + } else if (FIRST.equals(whichAssertionResults)) { + _assertionsResultsToSave = SAVE_FIRST_ASSERTION; + } else if (ALL.equals(whichAssertionResults)) { + _assertionsResultsToSave = SAVE_ALL_ASSERTIONS; + } else { + _assertionsResultsToSave = 0; + } + + String howToSave = props.getProperty(OUTPUT_FORMAT_PROP, XML); + + if (XML.equals(howToSave)) { + _xml = true; + } else { + _xml = false; + } + + _threadCounts=TRUE.equalsIgnoreCase(props.getProperty(SAVE_THREAD_COUNTS, FALSE)); + + _sampleCount=TRUE.equalsIgnoreCase(props.getProperty(SAVE_SAMPLE_COUNT, FALSE)); + + _idleTime=TRUE.equalsIgnoreCase(props.getProperty(SAVE_IDLE_TIME, FALSE)); + } + + // Don't save this, as not settable via GUI + private String delimiter = _delimiter; + + // Don't save this - only needed for processing CSV headers currently + private transient int varCount = 0; + + private static final SampleSaveConfiguration _static = new SampleSaveConfiguration(); + + public int getVarCount() { // Only for use by CSVSaveService + return varCount; + } + + public void setVarCount(int varCount) { // Only for use by CSVSaveService + this.varCount = varCount; + } + + // Give access to initial configuration + public static SampleSaveConfiguration staticConfig() { + return _static; + } + + public SampleSaveConfiguration() { + } + + /** + * Alternate constructor for use by OldSaveService + * + * @param value initial setting for boolean fields used in Config dialogue + */ + public SampleSaveConfiguration(boolean value) { + assertions = value; + bytes = value; + code = value; + dataType = value; + encoding = value; + fieldNames = value; + fileName = value; + hostname = value; + label = value; + latency = value; + message = value; + printMilliseconds = _printMilliseconds;//is derived from properties only + requestHeaders = value; + responseData = value; + responseDataOnError = value; + responseHeaders = value; + samplerData = value; + saveAssertionResultsFailureMessage = value; + subresults = value; + success = value; + threadCounts = value; + sampleCount = value; + threadName = value; + time = value; + timestamp = value; + url = value; + xml = value; + } + + private Object readResolve(){ + formatter = _formatter; + return this; + } + + @Override + public Object clone() { + try { + SampleSaveConfiguration clone = (SampleSaveConfiguration)super.clone(); + if(this.formatter != null) { + clone.formatter = (SimpleDateFormat)this.formatter.clone(); + } + return clone; + } + catch(CloneNotSupportedException e) { + throw new RuntimeException("Should not happen",e); + } + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if((obj == null) || (obj.getClass() != this.getClass())) { + return false; + } + // We know we are comparing to another SampleSaveConfiguration + SampleSaveConfiguration s = (SampleSaveConfiguration)obj; + boolean primitiveValues = s.time == time && + s.latency == latency && + s.timestamp == timestamp && + s.success == success && + s.label == label && + s.code == code && + s.message == message && + s.threadName == threadName && + s.dataType == dataType && + s.encoding == encoding && + s.assertions == assertions && + s.subresults == subresults && + s.responseData == responseData && + s.samplerData == samplerData && + s.xml == xml && + s.fieldNames == fieldNames && + s.responseHeaders == responseHeaders && + s.requestHeaders == requestHeaders && + s.assertionsResultsToSave == assertionsResultsToSave && + s.saveAssertionResultsFailureMessage == saveAssertionResultsFailureMessage && + s.printMilliseconds == printMilliseconds && + s.responseDataOnError == responseDataOnError && + s.url == url && + s.bytes == bytes && + s.fileName == fileName && + s.hostname == hostname && + s.sampleCount == sampleCount && + s.idleTime == idleTime && + s.threadCounts == threadCounts; + + boolean stringValues = false; + if(primitiveValues) { + stringValues = s.delimiter == delimiter || (delimiter != null && delimiter.equals(s.delimiter)); + } + boolean complexValues = false; + if(primitiveValues && stringValues) { + complexValues = s.formatter == formatter || (formatter != null && formatter.equals(s.formatter)); + } + + return primitiveValues && stringValues && complexValues; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + (time ? 1 : 0); + hash = 31 * hash + (latency ? 1 : 0); + hash = 31 * hash + (timestamp ? 1 : 0); + hash = 31 * hash + (success ? 1 : 0); + hash = 31 * hash + (label ? 1 : 0); + hash = 31 * hash + (code ? 1 : 0); + hash = 31 * hash + (message ? 1 : 0); + hash = 31 * hash + (threadName ? 1 : 0); + hash = 31 * hash + (dataType ? 1 : 0); + hash = 31 * hash + (encoding ? 1 : 0); + hash = 31 * hash + (assertions ? 1 : 0); + hash = 31 * hash + (subresults ? 1 : 0); + hash = 31 * hash + (responseData ? 1 : 0); + hash = 31 * hash + (samplerData ? 1 : 0); + hash = 31 * hash + (xml ? 1 : 0); + hash = 31 * hash + (fieldNames ? 1 : 0); + hash = 31 * hash + (responseHeaders ? 1 : 0); + hash = 31 * hash + (requestHeaders ? 1 : 0); + hash = 31 * hash + assertionsResultsToSave; + hash = 31 * hash + (saveAssertionResultsFailureMessage ? 1 : 0); + hash = 31 * hash + (printMilliseconds ? 1 : 0); + hash = 31 * hash + (responseDataOnError ? 1 : 0); + hash = 31 * hash + (url ? 1 : 0); + hash = 31 * hash + (bytes ? 1 : 0); + hash = 31 * hash + (fileName ? 1 : 0); + hash = 31 * hash + (hostname ? 1 : 0); + hash = 31 * hash + (threadCounts ? 1 : 0); + hash = 31 * hash + (delimiter != null ? delimiter.hashCode() : 0); + hash = 31 * hash + (formatter != null ? formatter.hashCode() : 0); + hash = 31 * hash + (sampleCount ? 1 : 0); + hash = 31 * hash + (idleTime ? 1 : 0); + + return hash; + } + + ///////////////////// Start of standard save/set access methods ///////////////////// + + public boolean saveResponseHeaders() { + return responseHeaders; + } + + public void setResponseHeaders(boolean r) { + responseHeaders = r; + } + + public boolean saveRequestHeaders() { + return requestHeaders; + } + + public void setRequestHeaders(boolean r) { + requestHeaders = r; + } + + public boolean saveAssertions() { + return assertions; + } + + public void setAssertions(boolean assertions) { + this.assertions = assertions; + } + + public boolean saveCode() { + return code; + } + + public void setCode(boolean code) { + this.code = code; + } + + public boolean saveDataType() { + return dataType; + } + + public void setDataType(boolean dataType) { + this.dataType = dataType; + } + + public boolean saveEncoding() { + return encoding; + } + + public void setEncoding(boolean encoding) { + this.encoding = encoding; + } + + public boolean saveLabel() { + return label; + } + + public void setLabel(boolean label) { + this.label = label; + } + + public boolean saveLatency() { + return latency; + } + + public void setLatency(boolean latency) { + this.latency = latency; + } + + public boolean saveMessage() { + return message; + } + + public void setMessage(boolean message) { + this.message = message; + } + + public boolean saveResponseData(SampleResult res) { + return responseData || TestPlan.getFunctionalMode() || (responseDataOnError && !res.isSuccessful()); + } + + public boolean saveResponseData() + { + return responseData; + } + + public void setResponseData(boolean responseData) { + this.responseData = responseData; + } + + public boolean saveSamplerData(SampleResult res) { + return samplerData || TestPlan.getFunctionalMode() // as per 2.0 branch + || (responseDataOnError && !res.isSuccessful()); + } + + public boolean saveSamplerData() + { + return samplerData; + } + + public void setSamplerData(boolean samplerData) { + this.samplerData = samplerData; + } + + public boolean saveSubresults() { + return subresults; + } + + public void setSubresults(boolean subresults) { + this.subresults = subresults; + } + + public boolean saveSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean saveThreadName() { + return threadName; + } + + public void setThreadName(boolean threadName) { + this.threadName = threadName; + } + + public boolean saveTime() { + return time; + } + + public void setTime(boolean time) { + this.time = time; + } + + public boolean saveTimestamp() { + return timestamp; + } + + public void setTimestamp(boolean timestamp) { + this.timestamp = timestamp; + } + + public boolean saveAsXml() { + return xml; + } + + public void setAsXml(boolean xml) { + this.xml = xml; + } + + public boolean saveFieldNames() { + return fieldNames; + } + + public void setFieldNames(boolean printFieldNames) { + this.fieldNames = printFieldNames; + } + + public boolean saveUrl() { + return url; + } + + public void setUrl(boolean save) { + this.url = save; + } + + public boolean saveBytes() { + return bytes; + } + + public void setBytes(boolean save) { + this.bytes = save; + } + + public boolean saveFileName() { + return fileName; + } + + public void setFileName(boolean save) { + this.fileName = save; + } + + public boolean saveAssertionResultsFailureMessage() { + return saveAssertionResultsFailureMessage; + } + + public void setAssertionResultsFailureMessage(boolean b) { + saveAssertionResultsFailureMessage = b; + } + + public boolean saveThreadCounts() { + return threadCounts; + } + + public void setThreadCounts(boolean save) { + this.threadCounts = save; + } + + public boolean saveSampleCount() { + return sampleCount; + } + + public void setSampleCount(boolean save) { + this.sampleCount = save; + } + + ///////////////// End of standard field accessors ///////////////////// + + /** + * Only intended for use by OldSaveService (and test cases) + */ + public void setFormatter(DateFormat fmt){ + printMilliseconds = (fmt == null); // maintain relationship + formatter = fmt; + } + + public boolean printMilliseconds() { + return printMilliseconds; + } + + public DateFormat formatter() { + return formatter; + } + + public int assertionsResultsToSave() { + return assertionsResultsToSave; + } + + public String getDelimiter() { + return delimiter; + } + + public String getXmlPi() { + return JMeterUtils.getJMeterProperties().getProperty(XML_PI, ""); // Defaults to empty; + } + + // Used by old Save service + public void setDelimiter(String delim) { + delimiter=delim; + } + + // Used by SampleSaveConfigurationConverter.unmarshall() + public void setDefaultDelimiter() { + delimiter=_delimiter; + } + + // Used by SampleSaveConfigurationConverter.unmarshall() + public void setDefaultTimeStampFormat() { + printMilliseconds=_printMilliseconds; + formatter=_formatter; + } + + public boolean saveHostname(){ + return hostname; + } + + public void setHostname(boolean save){ + hostname = save; + } + + public boolean saveIdleTime() { + return idleTime; + } + + public void setIdleTime(boolean save) { + idleTime = save; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/SampleSender.java b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleSender.java new file mode 100644 index 0000000..410bb32 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleSender.java @@ -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. + * + */ + +package org.apache.jmeter.samplers; + +/** + * This interface is used to define the methods that need to be intercepted + * by the SampleSender wrapper classes processed by the RemoteListenerWrapper. + */ +public interface SampleSender { + /** + * The test ended (probably not used; client-server mode needs a host) + */ + public void testEnded(); + + /** + * The test ended. + * + * This will be called from the engine thread. + * + * @param host + * the host that the test ended on. + */ + public void testEnded(String host); + + /** + * A sample occurred. + * + * This method will be called from the sampler thread. + * + * @param e + * a Sample Event + */ + public void sampleOccurred(SampleEvent e); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/SampleSenderFactory.java b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleSenderFactory.java new file mode 100644 index 0000000..5bd3674 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/SampleSenderFactory.java @@ -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. + * + */ + +package org.apache.jmeter.samplers; + +import java.lang.reflect.Constructor; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class SampleSenderFactory { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String MODE_STANDARD = "Standard"; // $NON-NLS-1$ + + private static final String MODE_HOLD = "Hold"; // $NON-NLS-1$ + + private static final String MODE_BATCH = "Batch"; // $NON-NLS-1$ + + private static final String MODE_STATISTICAL = "Statistical"; // $NON-NLS-1$ + + private static final String MODE_STRIPPED = "Stripped"; // $NON-NLS-1$ + + private static final String MODE_STRIPPED_BATCH = "StrippedBatch"; // $NON-NLS-1$ + + private static final String MODE_ASYNCH = "Asynch"; // $NON-NLS-1$ + + private static final String MODE_DISKSTORE = "DiskStore"; // $NON-NLS-1$ + + /** + * Checks for the Jmeter property mode and returns the required class. + * + * @param listener + * @return the appropriate class. Standard Jmeter functionality, + * hold_samples until end of test or batch samples. + */ + static SampleSender getInstance(RemoteSampleListener listener) { + // Support original property name + final boolean holdSamples = JMeterUtils.getPropDefault("hold_samples", false); // $NON-NLS-1$ + + // Extended property name + final String type = JMeterUtils.getPropDefault("mode", MODE_STANDARD); // $NON-NLS-1$ + + if (holdSamples || type.equalsIgnoreCase(MODE_HOLD)) { + HoldSampleSender h = new HoldSampleSender(listener); + return h; + } else if (type.equalsIgnoreCase(MODE_BATCH)) { + BatchSampleSender b = new BatchSampleSender(listener); + return b; + } else if (type.equalsIgnoreCase(MODE_STATISTICAL)) { + StatisticalSampleSender s = new StatisticalSampleSender(listener); + return s; + } else if (type.equalsIgnoreCase(MODE_STANDARD)) { + StandardSampleSender s = new StandardSampleSender(listener); + return s; + } else if(type.equalsIgnoreCase(MODE_STRIPPED_BATCH)) { + return new DataStrippingSampleSender(new BatchSampleSender(listener)); + } else if(type.equalsIgnoreCase(MODE_STRIPPED)){ + return new DataStrippingSampleSender(listener); + } else if(type.equalsIgnoreCase(MODE_ASYNCH)){ + return new AsynchSampleSender(listener); + } else if(type.equalsIgnoreCase(MODE_DISKSTORE)){ + return new DiskStoreSampleSender(listener); + } else { + // should be a user provided class name + SampleSender s = null; + try { + Class clazz = Class.forName(type); + Constructor cons = clazz.getConstructor(new Class[] {RemoteSampleListener.class}); + s = (SampleSender) cons.newInstance(new Object [] {listener}); + } catch (Exception e) { + // houston we have a problem !! + log.error("Unable to create a sample sender from class "+type); + throw new IllegalArgumentException(e.getMessage(), e); + } + + return s; + } + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/Sampler.java b/ApacheJmeter/src/org/apache/jmeter/samplers/Sampler.java new file mode 100644 index 0000000..e21546c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/Sampler.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import org.apache.jmeter.testelement.TestElement; + +/** + * Classes which are able to generate information about an entry should + * implement this interface. + * + * @version $Revision: 1310745 $ + */ +public interface Sampler extends java.io.Serializable, TestElement { + /** + * Obtains statistics about the given Entry, and packages the information + * into a SampleResult. + */ + public SampleResult sample(Entry e); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/StandardSampleSender.java b/ApacheJmeter/src/org/apache/jmeter/samplers/StandardSampleSender.java new file mode 100644 index 0000000..81c2fc5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/StandardSampleSender.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import org.apache.log.Logger; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; + +import java.rmi.RemoteException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +/** + * Default behaviour for remote testing. + */ + +public class StandardSampleSender extends AbstractSampleSender implements Serializable { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final RemoteSampleListener listener; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public StandardSampleSender(){ + this.listener = null; + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + + StandardSampleSender(RemoteSampleListener listener) { + this.listener = listener; + log.info("Using StandardSampleSender for this test run"); + } + + public void testEnded(String host) { + log.info("Test Ended on " + host); + try { + listener.testEnded(host); + } catch (RemoteException ex) { + log.warn("testEnded(host)"+ex); + } + } + + public void sampleOccurred(SampleEvent e) { + try { + listener.sampleOccurred(e); + } catch (RemoteException err) { + if (err.getCause() instanceof java.net.ConnectException){ + throw new JMeterError("Could not return sample",err); + } + log.error("sampleOccurred", err); + } + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * @throws ObjectStreamException + */ + private Object readResolve() throws ObjectStreamException{ + log.info("Using StandardSampleSender for this test run"); + return this; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/StatisticalSampleResult.java b/ApacheJmeter/src/org/apache/jmeter/samplers/StatisticalSampleResult.java new file mode 100644 index 0000000..1b24e89 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/StatisticalSampleResult.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import java.io.Serializable; + +/** + * Aggregates sample results for use by the Statistical remote batch mode. + * Samples are aggregated by the key defined by getKey(). + * TODO: merge error count into parent class? + */ +public class StatisticalSampleResult extends SampleResult implements + Serializable { + + private static final long serialVersionUID = 240L; + + private int errorCount; + + // Need to maintain our own elapsed timer to ensure more accurate aggregation + private long elapsed; + + public StatisticalSampleResult(){// May be called by XStream + } + + /** + * Allow OldSaveService to generate a suitable result when sample/error counts have been saved. + * + * @deprecated Needs to be replaced when multiple sample results are sorted out + * + * @param stamp + * @param elapsed + */ + @Deprecated + public StatisticalSampleResult(long stamp, long elapsed) { + super(stamp, elapsed); + } + + /** + * Create a statistical sample result from an ordinary sample result. + * + * @param res the sample result + * @param keyOnThreadName true if key includes threadName, false if threadGroup + */ + public StatisticalSampleResult(SampleResult res, boolean keyOnThreadName) { + // Copy data that is shared between samples (i.e. the key items): + setSampleLabel(res.getSampleLabel()); + + if (keyOnThreadName) { + setThreadName(res.getThreadName()); + } + + setSuccessful(true); // Assume result is OK + setSampleCount(0); // because we add the sample count in later + elapsed = 0; + } + + public void add(SampleResult res) { + // Add Sample Counter + setSampleCount(getSampleCount() + res.getSampleCount()); + + setBytes(getBytes() + res.getBytes()); + + // Add Error Counter + if (!res.isSuccessful()) { + errorCount++; + this.setSuccessful(false); + } + + // Set start/end times + if (getStartTime()==0){ // Bug 40954 - ensure start time gets started! + this.setStartTime(res.getStartTime()); + } else { + this.setStartTime(Math.min(getStartTime(), res.getStartTime())); + } + this.setEndTime(Math.max(getEndTime(), res.getEndTime())); + + setLatency(getLatency()+ res.getLatency()); + + elapsed += res.getTime(); + } + + @Override + public long getTime() { + return elapsed; + } + + @Override + public long getTimeStamp() { + return getEndTime(); + } + + @Override + public int getErrorCount() {// Overrides SampleResult + return errorCount; + } + + @Override + public void setErrorCount(int e) {// for reading CSV files + errorCount = e; + } + + /** + * Generates the key to be used for aggregating samples as follows:
+ * sampleLabel "-" [threadName|threadGroup] + *

+ * N.B. the key should agree with the fixed items that are saved in the sample. + * + * @param event sample event whose key is to be calculated + * @param keyOnThreadName true if key should use thread name, otherwise use thread group + * @return the key to use for aggregating samples + */ + public static String getKey(SampleEvent event, boolean keyOnThreadName) { + StringBuilder sb = new StringBuilder(80); + sb.append(event.getResult().getSampleLabel()); + if (keyOnThreadName){ + sb.append('-').append(event.getResult().getThreadName()); + } else { + sb.append('-').append(event.getThreadGroup()); + } + return sb.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/StatisticalSampleSender.java b/ApacheJmeter/src/org/apache/jmeter/samplers/StatisticalSampleSender.java new file mode 100644 index 0000000..5cf18c4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/StatisticalSampleSender.java @@ -0,0 +1,223 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implements batch reporting for remote testing. + * + */ +public class StatisticalSampleSender extends AbstractSampleSender implements Serializable { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int DEFAULT_NUM_SAMPLE_THRESHOLD = 100; + + private static final long DEFAULT_TIME_THRESHOLD = 60000L; + + // Static fields are set by the server when the class is constructed + + private static final int NUM_SAMPLES_THRESHOLD = JMeterUtils.getPropDefault( + "num_sample_threshold", DEFAULT_NUM_SAMPLE_THRESHOLD); + + private static final long TIME_THRESHOLD_MS = JMeterUtils.getPropDefault("time_threshold", + DEFAULT_TIME_THRESHOLD); + + // should the samples be aggregated on thread name or thread group (default) ? + private static boolean KEY_ON_THREADNAME = JMeterUtils.getPropDefault("key_on_threadname", false); + + // Instance fields are constructed by the client when the instance is create in the test plan + // and the field values are then transferred to the server copy by RMI serialisation/deserialisation + + private final int clientConfiguredNumSamplesThreshold = JMeterUtils.getPropDefault( + "num_sample_threshold", DEFAULT_NUM_SAMPLE_THRESHOLD); + + private final long clientConfiguredTimeThresholdMs = JMeterUtils.getPropDefault("time_threshold", + DEFAULT_TIME_THRESHOLD); + + // should the samples be aggregated on thread name or thread group (default) ? + private final boolean clientConfiguredKeyOnThreadName = JMeterUtils.getPropDefault("key_on_threadname", false); + + private final RemoteSampleListener listener; + + private final List sampleStore = new ArrayList(); + + //@GuardedBy("sampleStore") TODO perhaps use ConcurrentHashMap ? + private final Map sampleTable = new HashMap(); + + // Settings; readResolve sets these from the server/client values as appropriate + // TODO would be nice to make these final; not 100% sure volatile is needed as not changed after creation + private transient volatile int numSamplesThreshold; + + private transient volatile long timeThresholdMs; + + private transient volatile boolean keyOnThreadName; + + + // variables maintained by server code + // @GuardedBy("sampleStore") + private transient int sampleCount; // maintain separate count of samples for speed + + private transient long batchSendTime = -1; // @GuardedBy("sampleStore") + + /** + * @deprecated only for use by test code + */ + @Deprecated + public StatisticalSampleSender(){ + this(null); + log.warn("Constructor only intended for use in testing"); + } + + /** + * Constructor, only called by client code. + * + * @param listener that the List of sample events will be sent to. + */ + StatisticalSampleSender(RemoteSampleListener listener) { + this.listener = listener; + if (isClientConfigured()) { + log.info("Using StatisticalSampleSender (client settings) for this run." + + " Thresholds: num=" + clientConfiguredNumSamplesThreshold + + ", time=" + clientConfiguredTimeThresholdMs + + ". Key uses ThreadName: " + clientConfiguredKeyOnThreadName); + } else { + log.info("Using StatisticalSampleSender (server settings) for this run."); + } + } + + /** + * Checks if any sample events are still present in the sampleStore and + * sends them to the listener. Informs the listener of the testended. + * + * @param host the hostname that the test has ended on. + */ + public void testEnded(String host) { + log.info("Test Ended on " + host); + try { + if (sampleStore.size() != 0) { + sendBatch(); + } + listener.testEnded(host); + } catch (RemoteException err) { + log.warn("testEnded(hostname)", err); + } + } + + /** + * Stores sample events untill either a time or sample threshold is + * breached. Both thresholds are reset if one fires. If only one threshold + * is set it becomes the only value checked against. When a threhold is + * breached the list of sample events is sent to a listener where the event + * are fired locally. + * + * @param e a Sample Event + */ + public void sampleOccurred(SampleEvent e) { + synchronized (sampleStore) { + // Locate the statistical sample colector + String key = StatisticalSampleResult.getKey(e, keyOnThreadName); + StatisticalSampleResult statResult = sampleTable.get(key); + if (statResult == null) { + statResult = new StatisticalSampleResult(e.getResult(), keyOnThreadName); + // store the new statistical result collector + sampleTable.put(key, statResult); + // add a new wrapper samplevent + sampleStore + .add(new SampleEvent(statResult, e.getThreadGroup())); + } + statResult.add(e.getResult()); + sampleCount++; + boolean sendNow = false; + if (numSamplesThreshold != -1) { + if (sampleCount >= numSamplesThreshold) { + sendNow = true; + } + } + + long now = 0; + if (timeThresholdMs != -1) { + now = System.currentTimeMillis(); + // Checking for and creating initial timestamp to check against + if (batchSendTime == -1) { + this.batchSendTime = now + timeThresholdMs; + } + if (batchSendTime < now) { + sendNow = true; + } + } + if (sendNow) { + try { + if (log.isDebugEnabled()) { + log.debug("Firing sample"); + } + sendBatch(); + if (timeThresholdMs != -1) { + this.batchSendTime = now + timeThresholdMs; + } + } catch (RemoteException err) { + log.warn("sampleOccurred", err); + } + } + } // synchronized(sampleStore) + } + + private void sendBatch() throws RemoteException { + if (sampleStore.size() > 0) { + listener.processBatch(sampleStore); + sampleStore.clear(); + sampleTable.clear(); + sampleCount = 0; + } + } + + /** + * Processed by the RMI server code; acts as testStarted(). + * @throws ObjectStreamException + */ + private Object readResolve() throws ObjectStreamException{ + if (isClientConfigured()) { + numSamplesThreshold = clientConfiguredNumSamplesThreshold; + timeThresholdMs = clientConfiguredTimeThresholdMs; + keyOnThreadName = clientConfiguredKeyOnThreadName; + } else { + numSamplesThreshold = NUM_SAMPLES_THRESHOLD; + timeThresholdMs = TIME_THRESHOLD_MS; + keyOnThreadName = KEY_ON_THREADNAME; + } + log.info("Using StatisticalSampleSender for this run." + + (isClientConfigured() ? " Client config: " : " Server config: ") + + " Thresholds: num=" + numSamplesThreshold + + ", time=" + timeThresholdMs + + ". Key uses ThreadName: " + keyOnThreadName); + return this; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/samplers/gui/AbstractSamplerGui.java b/ApacheJmeter/src/org/apache/jmeter/samplers/gui/AbstractSamplerGui.java new file mode 100644 index 0000000..b7b8777 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/samplers/gui/AbstractSamplerGui.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which manage samplers. + * + */ +public abstract class AbstractSamplerGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most sampler + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultSamplerMenu(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#SAMPLERS}, which is + * appropriate for most sampler components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.SAMPLERS }); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/CSVSaveService.java b/ApacheJmeter/src/org/apache/jmeter/save/CSVSaveService.java new file mode 100644 index 0000000..8a734fd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/CSVSaveService.java @@ -0,0 +1,1116 @@ +/* + * 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.save; + +import java.io.BufferedReader; +import java.io.CharArrayWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.StringReader; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.swing.table.DefaultTableModel; + +import org.apache.commons.collections.map.LinkedMap; +import org.apache.commons.lang.CharUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.samplers.StatisticalSampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Visualizer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.jorphan.util.JMeterError; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * This class provides a means for saving/reading test results as CSV files. + */ +// For unit tests, @see TestCSVSaveService +public final class CSVSaveService { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // --------------------------------------------------------------------- + // XML RESULT FILE CONSTANTS AND FIELD NAME CONSTANTS + // --------------------------------------------------------------------- + + private static final String DATA_TYPE = "dataType"; // $NON-NLS-1$ + private static final String FAILURE_MESSAGE = "failureMessage"; // $NON-NLS-1$ + private static final String LABEL = "label"; // $NON-NLS-1$ + private static final String RESPONSE_CODE = "responseCode"; // $NON-NLS-1$ + private static final String RESPONSE_MESSAGE = "responseMessage"; // $NON-NLS-1$ + private static final String SUCCESSFUL = "success"; // $NON-NLS-1$ + private static final String THREAD_NAME = "threadName"; // $NON-NLS-1$ + private static final String TIME_STAMP = "timeStamp"; // $NON-NLS-1$ + + // --------------------------------------------------------------------- + // ADDITIONAL CSV RESULT FILE CONSTANTS AND FIELD NAME CONSTANTS + // --------------------------------------------------------------------- + + private static final String CSV_ELAPSED = "elapsed"; // $NON-NLS-1$ + private static final String CSV_BYTES = "bytes"; // $NON-NLS-1$ + private static final String CSV_THREAD_COUNT1 = "grpThreads"; // $NON-NLS-1$ + private static final String CSV_THREAD_COUNT2 = "allThreads"; // $NON-NLS-1$ + private static final String CSV_SAMPLE_COUNT = "SampleCount"; // $NON-NLS-1$ + private static final String CSV_ERROR_COUNT = "ErrorCount"; // $NON-NLS-1$ + private static final String CSV_URL = "URL"; // $NON-NLS-1$ + private static final String CSV_FILENAME = "Filename"; // $NON-NLS-1$ + private static final String CSV_LATENCY = "Latency"; // $NON-NLS-1$ + private static final String CSV_ENCODING = "Encoding"; // $NON-NLS-1$ + private static final String CSV_HOSTNAME = "Hostname"; // $NON-NLS-1$ + private static final String CSV_IDLETIME = "IdleTime"; // $NON-NLS-1$ + + // Used to enclose variable name labels, to distinguish from any of the + // above labels + private static final String VARIABLE_NAME_QUOTE_CHAR = "\""; // $NON-NLS-1$ + + // Initial config from properties + static private final SampleSaveConfiguration _saveConfig = SampleSaveConfiguration + .staticConfig(); + + // Date format to try if the time format does not parse as milliseconds + // (this is the suggested value in jmeter.properties) + private static final String DEFAULT_DATE_FORMAT_STRING = "MM/dd/yy HH:mm:ss"; // $NON-NLS-1$ + + private static final String LINE_SEP = System.getProperty("line.separator"); // $NON-NLS-1$ + + /** + * Private constructor to prevent instantiation. + */ + private CSVSaveService() { + } + + /** + * Read Samples from a file; handles quoted strings. + * + * @param filename + * input file + * @param visualizer + * where to send the results + * @param resultCollector + * the parent collector + * @throws IOException + */ + public static void processSamples(String filename, Visualizer visualizer, + ResultCollector resultCollector) throws IOException { + BufferedReader dataReader = null; + final boolean errorsOnly = resultCollector.isErrorLogging(); + final boolean successOnly = resultCollector.isSuccessOnlyLogging(); + try { + dataReader = new BufferedReader(new FileReader(filename)); // TODO Charset ? + dataReader.mark(400);// Enough to read the header column names + // Get the first line, and see if it is the header + String line = dataReader.readLine(); + if (line == null) { + throw new IOException(filename + ": unable to read header line"); + } + long lineNumber = 1; + SampleSaveConfiguration saveConfig = CSVSaveService + .getSampleSaveConfiguration(line, filename); + if (saveConfig == null) {// not a valid header + log.info(filename + + " does not appear to have a valid header. Using default configuration."); + saveConfig = (SampleSaveConfiguration) resultCollector + .getSaveConfig().clone(); // may change the format later + dataReader.reset(); // restart from beginning + lineNumber = 0; + } + String[] parts; + final char delim = saveConfig.getDelimiter().charAt(0); + // TODO: does it matter that an empty line will terminate the loop? + // CSV output files should never contain empty lines, so probably + // not + // If so, then need to check whether the reader is at EOF + while ((parts = csvReadFile(dataReader, delim)).length != 0) { + lineNumber++; + SampleEvent event = CSVSaveService + .makeResultFromDelimitedString(parts, saveConfig, + lineNumber); + if (event != null) { + final SampleResult result = event.getResult(); + if (ResultCollector.isSampleWanted(result.isSuccessful(), + errorsOnly, successOnly)) { + visualizer.add(result); + } + } + } + } finally { + JOrphanUtils.closeQuietly(dataReader); + } + } + + /** + * Make a SampleResult given a set of tokens + * + * @param parts + * tokens parsed from the input + * @param saveConfig + * the save configuration (may be updated) + * @param lineNumber + * @return the sample result + * + * @throws JMeterError + */ + private static SampleEvent makeResultFromDelimitedString( + final String[] parts, final SampleSaveConfiguration saveConfig, // may + // be + // updated + final long lineNumber) { + + SampleResult result = null; + String hostname = "";// $NON-NLS-1$ + long timeStamp = 0; + long elapsed = 0; + String text = null; + String field = null; // Save the name for error reporting + int i = 0; + final DateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat( + DEFAULT_DATE_FORMAT_STRING); + try { + if (saveConfig.saveTimestamp()) { + field = TIME_STAMP; + text = parts[i++]; + if (saveConfig.printMilliseconds()) { + try { + timeStamp = Long.parseLong(text); + } catch (NumberFormatException e) {// see if this works + log.warn(e.toString()); + // method is only ever called from one thread at a time + // so it's OK to use a static DateFormat + Date stamp = DEFAULT_DATE_FORMAT.parse(text); + timeStamp = stamp.getTime(); + log.warn("Setting date format to: " + + DEFAULT_DATE_FORMAT_STRING); + saveConfig.setFormatter(DEFAULT_DATE_FORMAT); + } + } else if (saveConfig.formatter() != null) { + Date stamp = saveConfig.formatter().parse(text); + timeStamp = stamp.getTime(); + } else { // can this happen? + final String msg = "Unknown timestamp format"; + log.warn(msg); + throw new JMeterError(msg); + } + } + + if (saveConfig.saveTime()) { + field = CSV_ELAPSED; + text = parts[i++]; + elapsed = Long.parseLong(text); + } + + if (saveConfig.saveSampleCount()) { + result = new StatisticalSampleResult(timeStamp, elapsed); + } else { + result = new SampleResult(timeStamp, elapsed); + } + + if (saveConfig.saveLabel()) { + field = LABEL; + text = parts[i++]; + result.setSampleLabel(text); + } + if (saveConfig.saveCode()) { + field = RESPONSE_CODE; + text = parts[i++]; + result.setResponseCode(text); + } + + if (saveConfig.saveMessage()) { + field = RESPONSE_MESSAGE; + text = parts[i++]; + result.setResponseMessage(text); + } + + if (saveConfig.saveThreadName()) { + field = THREAD_NAME; + text = parts[i++]; + result.setThreadName(text); + } + + if (saveConfig.saveDataType()) { + field = DATA_TYPE; + text = parts[i++]; + result.setDataType(text); + } + + if (saveConfig.saveSuccess()) { + field = SUCCESSFUL; + text = parts[i++]; + result.setSuccessful(Boolean.valueOf(text).booleanValue()); + } + + if (saveConfig.saveAssertionResultsFailureMessage()) { + i++; + // TODO - should this be restored? + } + + if (saveConfig.saveBytes()) { + field = CSV_BYTES; + text = parts[i++]; + result.setBytes(Integer.parseInt(text)); + } + + if (saveConfig.saveThreadCounts()) { + field = CSV_THREAD_COUNT1; + text = parts[i++]; + result.setGroupThreads(Integer.parseInt(text)); + + field = CSV_THREAD_COUNT2; + text = parts[i++]; + result.setAllThreads(Integer.parseInt(text)); + } + + if (saveConfig.saveUrl()) { + i++; + // TODO: should this be restored? + } + + if (saveConfig.saveFileName()) { + field = CSV_FILENAME; + text = parts[i++]; + result.setResultFileName(text); + } + if (saveConfig.saveLatency()) { + field = CSV_LATENCY; + text = parts[i++]; + result.setLatency(Long.parseLong(text)); + } + + if (saveConfig.saveEncoding()) { + field = CSV_ENCODING; + text = parts[i++]; + result.setEncodingAndType(text); + } + + if (saveConfig.saveSampleCount()) { + field = CSV_SAMPLE_COUNT; + text = parts[i++]; + result.setSampleCount(Integer.parseInt(text)); + field = CSV_ERROR_COUNT; + text = parts[i++]; + result.setErrorCount(Integer.parseInt(text)); + } + + if (saveConfig.saveHostname()) { + field = CSV_HOSTNAME; + hostname = parts[i++]; + } + + if (saveConfig.saveIdleTime()) { + field = CSV_IDLETIME; + text = parts[i++]; + result.setIdleTime(Long.parseLong(text)); + } + + if (i + saveConfig.getVarCount() < parts.length) { + log.warn("Line: " + lineNumber + ". Found " + parts.length + + " fields, expected " + i + + ". Extra fields have been ignored."); + } + + } catch (NumberFormatException e) { + log.warn("Error parsing field '" + field + "' at line " + + lineNumber + " " + e); + throw new JMeterError(e); + } catch (ParseException e) { + log.warn("Error parsing field '" + field + "' at line " + + lineNumber + " " + e); + throw new JMeterError(e); + } catch (ArrayIndexOutOfBoundsException e) { + log.warn("Insufficient columns to parse field '" + field + + "' at line " + lineNumber); + throw new JMeterError(e); + } + return new SampleEvent(result, "", hostname); + } + + /** + * Generates the field names for the output file + * + * @return the field names as a string + */ + public static String printableFieldNamesToString() { + return printableFieldNamesToString(_saveConfig); + } + + /** + * Generates the field names for the output file + * + * @return the field names as a string + */ + public static String printableFieldNamesToString( + SampleSaveConfiguration saveConfig) { + StringBuilder text = new StringBuilder(); + String delim = saveConfig.getDelimiter(); + + if (saveConfig.saveTimestamp()) { + text.append(TIME_STAMP); + text.append(delim); + } + + if (saveConfig.saveTime()) { + text.append(CSV_ELAPSED); + text.append(delim); + } + + if (saveConfig.saveLabel()) { + text.append(LABEL); + text.append(delim); + } + + if (saveConfig.saveCode()) { + text.append(RESPONSE_CODE); + text.append(delim); + } + + if (saveConfig.saveMessage()) { + text.append(RESPONSE_MESSAGE); + text.append(delim); + } + + if (saveConfig.saveThreadName()) { + text.append(THREAD_NAME); + text.append(delim); + } + + if (saveConfig.saveDataType()) { + text.append(DATA_TYPE); + text.append(delim); + } + + if (saveConfig.saveSuccess()) { + text.append(SUCCESSFUL); + text.append(delim); + } + + if (saveConfig.saveAssertionResultsFailureMessage()) { + text.append(FAILURE_MESSAGE); + text.append(delim); + } + + if (saveConfig.saveBytes()) { + text.append(CSV_BYTES); + text.append(delim); + } + + if (saveConfig.saveThreadCounts()) { + text.append(CSV_THREAD_COUNT1); + text.append(delim); + text.append(CSV_THREAD_COUNT2); + text.append(delim); + } + + if (saveConfig.saveUrl()) { + text.append(CSV_URL); + text.append(delim); + } + + if (saveConfig.saveFileName()) { + text.append(CSV_FILENAME); + text.append(delim); + } + + if (saveConfig.saveLatency()) { + text.append(CSV_LATENCY); + text.append(delim); + } + + if (saveConfig.saveEncoding()) { + text.append(CSV_ENCODING); + text.append(delim); + } + + if (saveConfig.saveSampleCount()) { + text.append(CSV_SAMPLE_COUNT); + text.append(delim); + text.append(CSV_ERROR_COUNT); + text.append(delim); + } + + if (saveConfig.saveHostname()) { + text.append(CSV_HOSTNAME); + text.append(delim); + } + + if (saveConfig.saveIdleTime()) { + text.append(CSV_IDLETIME); + text.append(delim); + } + + for (int i = 0; i < SampleEvent.getVarCount(); i++) { + text.append(VARIABLE_NAME_QUOTE_CHAR); + text.append(SampleEvent.getVarName(i)); + text.append(VARIABLE_NAME_QUOTE_CHAR); + text.append(delim); + } + + String resultString = null; + int size = text.length(); + int delSize = delim.length(); + + // Strip off the trailing delimiter + if (size >= delSize) { + resultString = text.substring(0, size - delSize); + } else { + resultString = text.toString(); + } + return resultString; + } + + // Map header names to set() methods + private static final LinkedMap headerLabelMethods = new LinkedMap(); + + // These entries must be in the same order as columns are saved/restored. + + static { + headerLabelMethods.put(TIME_STAMP, new Functor("setTimestamp")); + headerLabelMethods.put(CSV_ELAPSED, new Functor("setTime")); + headerLabelMethods.put(LABEL, new Functor("setLabel")); + headerLabelMethods.put(RESPONSE_CODE, new Functor("setCode")); + headerLabelMethods.put(RESPONSE_MESSAGE, new Functor("setMessage")); + headerLabelMethods.put(THREAD_NAME, new Functor("setThreadName")); + headerLabelMethods.put(DATA_TYPE, new Functor("setDataType")); + headerLabelMethods.put(SUCCESSFUL, new Functor("setSuccess")); + headerLabelMethods.put(FAILURE_MESSAGE, new Functor( + "setAssertionResultsFailureMessage")); + headerLabelMethods.put(CSV_BYTES, new Functor("setBytes")); + // Both these are needed in the list even though they set the same + // variable + headerLabelMethods.put(CSV_THREAD_COUNT1, + new Functor("setThreadCounts")); + headerLabelMethods.put(CSV_THREAD_COUNT2, + new Functor("setThreadCounts")); + headerLabelMethods.put(CSV_URL, new Functor("setUrl")); + headerLabelMethods.put(CSV_FILENAME, new Functor("setFileName")); + headerLabelMethods.put(CSV_LATENCY, new Functor("setLatency")); + headerLabelMethods.put(CSV_ENCODING, new Functor("setEncoding")); + // Both these are needed in the list even though they set the same + // variable + headerLabelMethods.put(CSV_SAMPLE_COUNT, new Functor("setSampleCount")); + headerLabelMethods.put(CSV_ERROR_COUNT, new Functor("setSampleCount")); + headerLabelMethods.put(CSV_HOSTNAME, new Functor("setHostname")); + headerLabelMethods.put(CSV_IDLETIME, new Functor("setIdleTime")); + } + + /** + * Parse a CSV header line + * + * @param headerLine + * from CSV file + * @param filename + * name of file (for log message only) + * @return config corresponding to the header items found or null if not a + * header line + */ + public static SampleSaveConfiguration getSampleSaveConfiguration( + String headerLine, String filename) { + String[] parts = splitHeader(headerLine, _saveConfig.getDelimiter()); // Try + // default + // delimiter + + String delim = null; + + if (parts == null) { + Perl5Matcher matcher = JMeterUtils.getMatcher(); + PatternMatcherInput input = new PatternMatcherInput(headerLine); + Pattern pattern = JMeterUtils.getPatternCache() + // This assumes the header names are all single words with no spaces + // word followed by 0 or more repeats of (non-word char + word) + // where the non-word char (\2) is the same + // e.g. abc|def|ghi but not abd|def~ghi + .getPattern("\\w+((\\W)\\w+)?(\\2\\w+)*(\\2\"\\w+\")*", // $NON-NLS-1$ + // last entries may be quoted strings + Perl5Compiler.READ_ONLY_MASK); + if (matcher.matches(input, pattern)) { + delim = matcher.getMatch().group(2); + parts = splitHeader(headerLine, delim);// now validate the + // result + } + } + + if (parts == null) { + return null; // failed to recognise the header + } + + // We know the column names all exist, so create the config + SampleSaveConfiguration saveConfig = new SampleSaveConfiguration(false); + + int varCount = 0; + for (int i = 0; i < parts.length; i++) { + String label = parts[i]; + if (isVariableName(label)) { + varCount++; + } else { + Functor set = (Functor) headerLabelMethods.get(label); + set.invoke(saveConfig, new Boolean[] { Boolean.TRUE }); + } + } + + if (delim != null) { + log.warn("Default delimiter '" + _saveConfig.getDelimiter() + + "' did not work; using alternate '" + delim + + "' for reading " + filename); + saveConfig.setDelimiter(delim); + } + + saveConfig.setVarCount(varCount); + + return saveConfig; + } + + private static String[] splitHeader(String headerLine, String delim) { + String parts[] = headerLine.split("\\Q" + delim);// $NON-NLS-1$ + int previous = -1; + // Check if the line is a header + for (int i = 0; i < parts.length; i++) { + final String label = parts[i]; + // Check for Quoted variable names + if (isVariableName(label)) { + previous = Integer.MAX_VALUE; // they are always last + continue; + } + int current = headerLabelMethods.indexOf(label); + if (current == -1) { + return null; // unknown column name + } + if (current <= previous) { + log.warn("Column header number " + (i + 1) + " name " + label + + " is out of order."); + return null; // out of order + } + previous = current; + } + return parts; + } + + /** + * Check if the label is a variable name, i.e. is it enclosed in + * double-quotes? + * + * @param label + * column name from CSV file + * @return if the label is enclosed in double-quotes + */ + private static boolean isVariableName(final String label) { + return label.length() > 2 && label.startsWith(VARIABLE_NAME_QUOTE_CHAR) + && label.endsWith(VARIABLE_NAME_QUOTE_CHAR); + } + + /** + * Method will save aggregate statistics as CSV. For now I put it here. Not + * sure if it should go in the newer SaveService instead of here. if we ever + * decide to get rid of this class, we'll need to move this method to the + * new save service. + * + * @param data + * List of data rows + * @param writer + * output file + * @throws IOException + */ + public static void saveCSVStats(List data, FileWriter writer) + throws IOException { + saveCSVStats(data, writer, null); + } + + /** + * Method will save aggregate statistics as CSV. For now I put it here. Not + * sure if it should go in the newer SaveService instead of here. if we ever + * decide to get rid of this class, we'll need to move this method to the + * new save service. + * + * @param data + * List of data rows + * @param writer + * output file + * @param headers + * header names (if non-null) + * @throws IOException + */ + public static void saveCSVStats(List data, FileWriter writer, + String headers[]) throws IOException { + final char DELIM = ','; + final char SPECIALS[] = new char[] { DELIM, QUOTING_CHAR }; + if (headers != null) { + for (int i = 0; i < headers.length; i++) { + if (i > 0) { + writer.write(DELIM); + } + writer.write(quoteDelimiters(headers[i], SPECIALS)); + } + writer.write(LINE_SEP); + } + for (int idx = 0; idx < data.size(); idx++) { + List row = (List) data.get(idx); + for (int idy = 0; idy < row.size(); idy++) { + if (idy > 0) { + writer.write(DELIM); + } + Object item = row.get(idy); + writer.write(quoteDelimiters(String.valueOf(item), SPECIALS)); + } + writer.write(LINE_SEP); + } + } + + /** + * Method saves aggregate statistics (with header names) as CSV from a table + * model. Same as {@link #saveCSVStats(List, FileWriter, String[])} except + * that there is no need to create a List containing the data. + * + * @param model + * table model containing the data + * @param writer + * output file + * @throws IOException + */ + public static void saveCSVStats(DefaultTableModel model, FileWriter writer) + throws IOException { + saveCSVStats(model, writer, true); + } + + /** + * Method saves aggregate statistics as CSV from a table model. Same as + * {@link #saveCSVStats(List, FileWriter, String[])} except that there is + * no need to create a List containing the data. + * + * @param model + * table model containing the data + * @param writer + * output file + * @param saveHeaders + * whether or not to save headers + * @throws IOException + */ + public static void saveCSVStats(DefaultTableModel model, FileWriter writer, + boolean saveHeaders) throws IOException { + final char DELIM = ','; + final char SPECIALS[] = new char[] { DELIM, QUOTING_CHAR }; + final int columns = model.getColumnCount(); + final int rows = model.getRowCount(); + if (saveHeaders) { + for (int i = 0; i < columns; i++) { + if (i > 0) { + writer.write(DELIM); + } + writer.write(quoteDelimiters(model.getColumnName(i), SPECIALS)); + } + writer.write(LINE_SEP); + } + for (int row = 0; row < rows; row++) { + for (int column = 0; column < columns; column++) { + if (column > 0) { + writer.write(DELIM); + } + Object item = model.getValueAt(row, column); + writer.write(quoteDelimiters(String.valueOf(item), SPECIALS)); + } + writer.write(LINE_SEP); + } + } + + /** + * Convert a result into a string, where the fields of the result are + * separated by the default delimiter. + * + * @param event + * the sample event to be converted + * @return the separated value representation of the result + */ + public static String resultToDelimitedString(SampleEvent event) { + return resultToDelimitedString(event, event.getResult().getSaveConfig() + .getDelimiter()); + } + + /** + * Convert a result into a string, where the fields of the result are + * separated by a specified String. + * + * @param event + * the sample event to be converted + * @param delimiter + * the separation string + * @return the separated value representation of the result + */ + public static String resultToDelimitedString(SampleEvent event, + final String delimiter) { + + /* + * Class to handle generating the delimited string. - adds the delimiter + * if not the first call - quotes any strings that require it + */ + final class StringQuoter { + final StringBuilder sb = new StringBuilder(); + private final char[] specials; + private boolean addDelim; + + public StringQuoter(char delim) { + specials = new char[] { delim, QUOTING_CHAR, CharUtils.CR, + CharUtils.LF }; + addDelim = false; // Don't add delimiter first time round + } + + private void addDelim() { + if (addDelim) { + sb.append(specials[0]); + } else { + addDelim = true; + } + } + + // These methods handle parameters that could contain delimiters or + // quotes: + public void append(String s) { + addDelim(); + // if (s == null) return; + sb.append(quoteDelimiters(s, specials)); + } + + public void append(Object obj) { + append(String.valueOf(obj)); + } + + // These methods handle parameters that cannot contain delimiters or + // quotes + public void append(int i) { + addDelim(); + sb.append(i); + } + + public void append(long l) { + addDelim(); + sb.append(l); + } + + public void append(boolean b) { + addDelim(); + sb.append(b); + } + + @Override + public String toString() { + return sb.toString(); + } + } + + StringQuoter text = new StringQuoter(delimiter.charAt(0)); + + SampleResult sample = event.getResult(); + SampleSaveConfiguration saveConfig = sample.getSaveConfig(); + + if (saveConfig.saveTimestamp()) { + if (saveConfig.printMilliseconds()) { + text.append(sample.getTimeStamp()); + } else if (saveConfig.formatter() != null) { + String stamp = saveConfig.formatter().format( + new Date(sample.getTimeStamp())); + text.append(stamp); + } + } + + if (saveConfig.saveTime()) { + text.append(sample.getTime()); + } + + if (saveConfig.saveLabel()) { + text.append(sample.getSampleLabel()); + } + + if (saveConfig.saveCode()) { + text.append(sample.getResponseCode()); + } + + if (saveConfig.saveMessage()) { + text.append(sample.getResponseMessage()); + } + + if (saveConfig.saveThreadName()) { + text.append(sample.getThreadName()); + } + + if (saveConfig.saveDataType()) { + text.append(sample.getDataType()); + } + + if (saveConfig.saveSuccess()) { + text.append(sample.isSuccessful()); + } + + if (saveConfig.saveAssertionResultsFailureMessage()) { + String message = null; + AssertionResult[] results = sample.getAssertionResults(); + + if (results != null) { + // Find the first non-null message + for (int i = 0; i < results.length; i++) { + message = results[i].getFailureMessage(); + if (message != null) { + break; + } + } + } + + if (message != null) { + text.append(message); + } else { + text.append(""); // Need to append something so delimiter is + // added + } + } + + if (saveConfig.saveBytes()) { + text.append(sample.getBytes()); + } + + if (saveConfig.saveThreadCounts()) { + text.append(sample.getGroupThreads()); + text.append(sample.getAllThreads()); + } + if (saveConfig.saveUrl()) { + text.append(sample.getURL()); + } + + if (saveConfig.saveFileName()) { + text.append(sample.getResultFileName()); + } + + if (saveConfig.saveLatency()) { + text.append(sample.getLatency()); + } + + if (saveConfig.saveEncoding()) { + text.append(sample.getDataEncodingWithDefault()); + } + + if (saveConfig.saveSampleCount()) {// Need both sample and error count + // to be any use + text.append(sample.getSampleCount()); + text.append(sample.getErrorCount()); + } + + if (saveConfig.saveHostname()) { + text.append(event.getHostname()); + } + + for (int i = 0; i < SampleEvent.getVarCount(); i++) { + text.append(event.getVarValue(i)); + } + + return text.toString(); + } + + // =================================== CSV quote/unquote handling + // ============================== + + /* + * Private versions of what might eventually be part of Commons-CSV or + * Commons-Lang/Io... + */ + + /* + *

Returns a String value for a character-delimited column + * value enclosed in the quote character, if required.

+ * + *

If the value contains a special character, then the String value is + * returned enclosed in the quote character.

+ * + *

Any quote characters in the value are doubled up.

+ * + *

If the value does not contain any special characters, then the String + * value is returned unchanged.

+ * + *

N.B. The list of special characters includes the quote character. + *

+ * + * @param input the input column String, may be null (without enclosing + * delimiters) + * + * @param specialChars special characters; second one must be the quote + * character + * + * @return the input String, enclosed in quote characters if the value + * contains a special character, null for null string input + */ + private static String quoteDelimiters(String input, char[] specialChars) { + if (StringUtils.containsNone(input, specialChars)) { + return input; + } + StringBuilder buffer = new StringBuilder(input.length() + 10); + final char quote = specialChars[1]; + buffer.append(quote); + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (c == quote) { + buffer.append(quote); // double the quote char + } + buffer.append(c); + } + buffer.append(quote); + return buffer.toString(); + } + + // State of the parser + private static final int INITIAL = 0, PLAIN = 1, QUOTED = 2, + EMBEDDEDQUOTE = 3; + public static final char QUOTING_CHAR = '"'; + + /** + * Reads from file and splits input into strings according to the delimiter, + * taking note of quoted strings. + *

+ * Handles DOS (CRLF), Unix (LF), and Mac (CR) line-endings equally. + *

+ * N.B. a blank line is returned as a zero length array, whereas "" is + * returned as an empty string. This is inconsistent. + * + * @param infile + * input file - must support mark(1) + * @param delim + * delimiter (e.g. comma) + * @return array of strings + * @throws IOException + * also for unexpected quote characters + */ + public static String[] csvReadFile(BufferedReader infile, char delim) + throws IOException { + int ch; + int state = INITIAL; + List list = new ArrayList(); + CharArrayWriter baos = new CharArrayWriter(200); + boolean push = false; + while (-1 != (ch = infile.read())) { + push = false; + switch (state) { + case INITIAL: + if (ch == QUOTING_CHAR) { + state = QUOTED; + } else if (isDelimOrEOL(delim, ch)) { + push = true; + } else { + baos.write(ch); + state = PLAIN; + } + break; + case PLAIN: + if (ch == QUOTING_CHAR) { + baos.write(ch); + throw new IOException( + "Cannot have quote-char in plain field:[" + + baos.toString() + "]"); + } else if (isDelimOrEOL(delim, ch)) { + push = true; + state = INITIAL; + } else { + baos.write(ch); + } + break; + case QUOTED: + if (ch == QUOTING_CHAR) { + state = EMBEDDEDQUOTE; + } else { + baos.write(ch); + } + break; + case EMBEDDEDQUOTE: + if (ch == QUOTING_CHAR) { + baos.write(QUOTING_CHAR); // doubled quote => quote + state = QUOTED; + } else if (isDelimOrEOL(delim, ch)) { + push = true; + state = INITIAL; + } else { + baos.write(QUOTING_CHAR); + throw new IOException( + "Cannot have single quote-char in quoted field:[" + + baos.toString() + "]"); + } + break; + } // switch(state) + if (push) { + if (ch == '\r') {// Remove following \n if present + infile.mark(1); + if (infile.read() != '\n') { + infile.reset(); // did not find \n, put the character + // back + } + } + String s = baos.toString(); + list.add(s); + baos.reset(); + } + if ((ch == '\n' || ch == '\r') && state != QUOTED) { + break; + } + } // while not EOF + if (ch == -1) {// EOF (or end of string) so collect any remaining data + if (state == QUOTED) { + throw new IOException(state + + " Missing trailing quote-char in quoted field:[\"" + + baos.toString() + "]"); + } + // Do we have some data, or a trailing empty field? + if (baos.size() > 0 // we have some data + || push // we've started a field + || state == EMBEDDEDQUOTE // Just seen "" + ) { + list.add(baos.toString()); + } + } + return list.toArray(new String[list.size()]); + } + + private static boolean isDelimOrEOL(char delim, int ch) { + return ch == delim || ch == '\n' || ch == '\r'; + } + + /** + * Reads from String and splits into strings according to the delimiter, + * taking note of quoted strings. + * + * Handles DOS (CRLF), Unix (LF), and Mac (CR) line-endings equally. + * + * @param line + * input line + * @param delim + * delimiter (e.g. comma) + * @return array of strings + * @throws IOException + * also for unexpected quote characters + */ + public static String[] csvSplitString(String line, char delim) + throws IOException { + return csvReadFile(new BufferedReader(new StringReader(line)), delim); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/ListenerResultWrapper.java b/ApacheJmeter/src/org/apache/jmeter/save/ListenerResultWrapper.java new file mode 100644 index 0000000..106e124 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/ListenerResultWrapper.java @@ -0,0 +1,80 @@ +/* + * 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.save; + +import java.util.Collection; + +import org.apache.jmeter.samplers.SampleResult; + +/** + * ListenerResultWrapper is for calculated results generated by listeners like + * aggregate listener and monitor listener. + */ +public class ListenerResultWrapper { + private String version = ""; + + private Collection calculatedResults; + + private long testStartTime; + + /** + * @return Returns the sampleResults. + */ + public Collection getSampleResults() { + return calculatedResults; + } + + /** + * @param results + * The sampleResults to set. + */ + public void setSampleResults(Collection results) { + this.calculatedResults = results; + } + + /** + * @return Returns the testStartTime. + */ + public long getTestStartTime() { + return testStartTime; + } + + /** + * @param testStartTime + * The testStartTime to set. + */ + public void setTestStartTime(long testStartTime) { + this.testStartTime = testStartTime; + } + + /** + * @return Returns the version. + */ + public String getVersion() { + return version; + } + + /** + * @param version + * The version to set. + */ + public void setVersion(String version) { + this.version = version; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/OldSaveService.java b/ApacheJmeter/src/org/apache/jmeter/save/OldSaveService.java new file mode 100644 index 0000000..89ca833 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/OldSaveService.java @@ -0,0 +1,530 @@ +/* + * 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.save; + +//import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +//import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Collection; +//import java.util.Date; +//import java.util.Iterator; +//import java.util.LinkedList; +//import java.util.List; +import java.util.Map; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +//import org.apache.avalon.framework.configuration.DefaultConfiguration; +import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; +//import org.apache.avalon.framework.configuration.DefaultConfigurationSerializer; +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.samplers.SampleResult; +//import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.MapProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.NameUpdater; +import org.apache.jmeter.visualizers.Visualizer; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.xml.sax.SAXException; + +/** + * This class restores the original Avalon XML format (not used by default). + * + * This may be removed in a future release. + */ +public final class OldSaveService { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // --------------------------------------------------------------------- + // XML RESULT FILE CONSTANTS AND FIELD NAME CONSTANTS + // --------------------------------------------------------------------- + + // Shared with TestElementSaver + static final String PRESERVE = "preserve"; // $NON-NLS-1$ + static final String XML_SPACE = "xml:space"; // $NON-NLS-1$ + + private static final String ASSERTION_RESULT_TAG_NAME = "assertionResult"; // $NON-NLS-1$ + private static final String BINARY = "binary"; // $NON-NLS-1$ + private static final String DATA_TYPE = "dataType"; // $NON-NLS-1$ + private static final String ERROR = "error"; // $NON-NLS-1$ + private static final String FAILURE = "failure"; // $NON-NLS-1$ + private static final String FAILURE_MESSAGE = "failureMessage"; // $NON-NLS-1$ + private static final String LABEL = "label"; // $NON-NLS-1$ + private static final String RESPONSE_CODE = "responseCode"; // $NON-NLS-1$ + private static final String RESPONSE_MESSAGE = "responseMessage"; // $NON-NLS-1$ + private static final String SAMPLE_RESULT_TAG_NAME = "sampleResult"; // $NON-NLS-1$ + private static final String SUCCESSFUL = "success"; // $NON-NLS-1$ + private static final String THREAD_NAME = "threadName"; // $NON-NLS-1$ + private static final String TIME = "time"; // $NON-NLS-1$ + private static final String TIME_STAMP = "timeStamp"; // $NON-NLS-1$ + + private static final DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); + + /** + * Private constructor to prevent instantiation. + */ + private OldSaveService() { + } + + +// public static void saveSubTree(HashTree subTree, OutputStream writer) throws IOException { +// Configuration config = getConfigsFromTree(subTree).get(0); +// DefaultConfigurationSerializer saver = new DefaultConfigurationSerializer(); +// +// saver.setIndent(true); +// try { +// saver.serialize(writer, config); +// } catch (SAXException e) { +// throw new IOException("SAX implementation problem"); +// } catch (ConfigurationException e) { +// throw new IOException("Problem using Avalon Configuration tools"); +// } +// } + + /** + * Read sampleResult from Avalon XML file. + * + * @param config Avalon configuration + * @return sample result + */ + // Probably no point in converting this to return a SampleEvent + private static SampleResult getSampleResult(Configuration config) { + SampleResult result = new SampleResult(config.getAttributeAsLong(TIME_STAMP, 0L), config.getAttributeAsLong( + TIME, 0L)); + + result.setThreadName(config.getAttribute(THREAD_NAME, "")); // $NON-NLS-1$ + result.setDataType(config.getAttribute(DATA_TYPE, "")); + result.setResponseCode(config.getAttribute(RESPONSE_CODE, "")); // $NON-NLS-1$ + result.setResponseMessage(config.getAttribute(RESPONSE_MESSAGE, "")); // $NON-NLS-1$ + result.setSuccessful(config.getAttributeAsBoolean(SUCCESSFUL, false)); + result.setSampleLabel(config.getAttribute(LABEL, "")); // $NON-NLS-1$ + result.setResponseData(getBinaryData(config.getChild(BINARY))); + Configuration[] subResults = config.getChildren(SAMPLE_RESULT_TAG_NAME); + + for (int i = 0; i < subResults.length; i++) { + result.storeSubResult(getSampleResult(subResults[i])); + } + Configuration[] assResults = config.getChildren(ASSERTION_RESULT_TAG_NAME); + + for (int i = 0; i < assResults.length; i++) { + result.addAssertionResult(getAssertionResult(assResults[i])); + } + + Configuration[] samplerData = config.getChildren("property"); // $NON-NLS-1$ + for (int i = 0; i < samplerData.length; i++) { + result.setSamplerData(samplerData[i].getValue("")); // $NON-NLS-1$ + } + return result; + } + +// private static List getConfigsFromTree(HashTree subTree) { +// Iterator iter = subTree.list().iterator(); +// List configs = new LinkedList(); +// +// while (iter.hasNext()) { +// TestElement item = iter.next(); +// DefaultConfiguration config = new DefaultConfiguration("node", "node"); // $NON-NLS-1$ // $NON-NLS-2$ +// +// config.addChild(getConfigForTestElement(null, item)); +// List configList = getConfigsFromTree(subTree.getTree(item)); +// Iterator iter2 = configList.iterator(); +// +// while (iter2.hasNext()) { +// config.addChild(iter2.next()); +// } +// configs.add(config); +// } +// return configs; +// } + +// private static Configuration getConfiguration(byte[] bin) { +// DefaultConfiguration config = new DefaultConfiguration(BINARY, "JMeter Save Service"); // $NON-NLS-1$ +// +// try { +// config.setValue(new String(bin, "UTF-8")); // $NON-NLS-1$ +// } catch (UnsupportedEncodingException e) { +// log.error("", e); // $NON-NLS-1$ +// } +// return config; +// } + + private static byte[] getBinaryData(Configuration config) { + if (config == null) { + return new byte[0]; + } + try { + return config.getValue("").getBytes("UTF-8"); // $NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + return new byte[0]; + } + } + + private static AssertionResult getAssertionResult(Configuration config) { + AssertionResult result = new AssertionResult(""); //TODO provide proper name? + result.setError(config.getAttributeAsBoolean(ERROR, false)); + result.setFailure(config.getAttributeAsBoolean(FAILURE, false)); + result.setFailureMessage(config.getAttribute(FAILURE_MESSAGE, "")); + return result; + } + +// private static Configuration getConfiguration(AssertionResult assResult) { +// DefaultConfiguration config = new DefaultConfiguration(ASSERTION_RESULT_TAG_NAME, "JMeter Save Service"); +// +// config.setAttribute(FAILURE_MESSAGE, assResult.getFailureMessage()); +// config.setAttribute(ERROR, "" + assResult.isError()); +// config.setAttribute(FAILURE, "" + assResult.isFailure()); +// return config; +// } + +// /** +// * This method determines the content of the result data that will be +// * stored for the Avalon XML format. +// * +// * @param result +// * the object containing all of the data that has been collected. +// * @param saveConfig +// * the configuration giving the data items to be saved. +// * N.B. It is rather out of date, as many fields are not saved. +// * However it is probably not worth updating, as no-one should be using the format. +// */ +// public static Configuration getConfiguration(SampleResult result, SampleSaveConfiguration saveConfig) { +// DefaultConfiguration config = new DefaultConfiguration(SAMPLE_RESULT_TAG_NAME, "JMeter Save Service"); // $NON-NLS-1$ +// +// if (saveConfig.saveTime()) { +// config.setAttribute(TIME, String.valueOf(result.getTime())); +// } +// if (saveConfig.saveLabel()) { +// config.setAttribute(LABEL, result.getSampleLabel()); +// } +// if (saveConfig.saveCode()) { +// config.setAttribute(RESPONSE_CODE, result.getResponseCode()); +// } +// if (saveConfig.saveMessage()) { +// config.setAttribute(RESPONSE_MESSAGE, result.getResponseMessage()); +// } +// if (saveConfig.saveThreadName()) { +// config.setAttribute(THREAD_NAME, result.getThreadName()); +// } +// if (saveConfig.saveDataType()) { +// config.setAttribute(DATA_TYPE, result.getDataType()); +// } +// +// if (saveConfig.printMilliseconds()) { +// config.setAttribute(TIME_STAMP, String.valueOf(result.getTimeStamp())); +// } else if (saveConfig.formatter() != null) { +// String stamp = saveConfig.formatter().format(new Date(result.getTimeStamp())); +// +// config.setAttribute(TIME_STAMP, stamp); +// } +// +// if (saveConfig.saveSuccess()) { +// config.setAttribute(SUCCESSFUL, Boolean.toString(result.isSuccessful())); +// } +// +// SampleResult[] subResults = result.getSubResults(); +// +// if (subResults != null) { +// for (int i = 0; i < subResults.length; i++) { +// config.addChild(getConfiguration(subResults[i], saveConfig)); +// } +// } +// +// AssertionResult[] assResults = result.getAssertionResults(); +// +// if (saveConfig.saveSamplerData(result)) { +// config.addChild(createConfigForString("samplerData", result.getSamplerData())); // $NON-NLS-1$ +// } +// if (saveConfig.saveAssertions() && assResults != null) { +// for (int i = 0; i < assResults.length; i++) { +// config.addChild(getConfiguration(assResults[i])); +// } +// } +// if (saveConfig.saveResponseData(result)) { +// config.addChild(getConfiguration(result.getResponseData())); +// } +// return config; +// } + +// private static Configuration getConfigForTestElement(String named, TestElement item) { +// TestElementSaver saver = new TestElementSaver(named); +// item.traverse(saver); +// Configuration config = saver.getConfiguration(); +// /* +// * DefaultConfiguration config = new DefaultConfiguration("testelement", +// * "testelement"); +// * +// * if (named != null) { config.setAttribute("name", named); } if +// * (item.getProperty(TestElement.TEST_CLASS) != null) { +// * config.setAttribute("class", (String) +// * item.getProperty(TestElement.TEST_CLASS)); } else { +// * config.setAttribute("class", item.getClass().getName()); } Iterator +// * iter = item.getPropertyNames().iterator(); +// * +// * while (iter.hasNext()) { String name = (String) iter.next(); Object +// * value = item.getProperty(name); +// * +// * if (value instanceof TestElement) { +// * config.addChild(getConfigForTestElement(name, (TestElement) value)); } +// * else if (value instanceof Collection) { +// * config.addChild(createConfigForCollection(name, (Collection) value)); } +// * else if (value != null) { config.addChild(createConfigForString(name, +// * value.toString())); } } +// */ +// return config; +// } + + +// private static Configuration createConfigForString(String name, String value) { +// if (value == null) { +// value = ""; +// } +// DefaultConfiguration config = new DefaultConfiguration("property", "property"); +// +// config.setAttribute("name", name); +// config.setValue(value); +// config.setAttribute(XML_SPACE, PRESERVE); +// return config; +// } + + // Called by SaveService.loadTree(InputStream reader) if XStream loading fails + public synchronized static HashTree loadSubTree(InputStream in) throws IOException { + try { + Configuration config = builder.build(in); + HashTree loadedTree = generateNode(config); + + return loadedTree; + } catch (ConfigurationException e) { + String message = "Problem loading using Avalon Configuration tools"; + log.error(message, e); + throw new IOException(message); + } catch (SAXException e) { + String message = "Problem with SAX implementation"; + log.error(message, e); + throw new IOException(message); + } + } + + private static TestElement createTestElement(Configuration config) throws ConfigurationException, + ClassNotFoundException, IllegalAccessException, InstantiationException { + TestElement element = null; + + String testClass = config.getAttribute("class"); // $NON-NLS-1$ + + String gui_class=""; // $NON-NLS-1$ + Configuration[] children = config.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i].getName().equals("property")) { // $NON-NLS-1$ + if (children[i].getAttribute("name").equals(TestElement.GUI_CLASS)){ // $NON-NLS-1$ + gui_class=children[i].getValue(); + } + } + } + + String newClass = NameUpdater.getCurrentTestName(testClass,gui_class); + + element = (TestElement) Class.forName(newClass).newInstance(); + + for (int i = 0; i < children.length; i++) { + if (children[i].getName().equals("property")) { // $NON-NLS-1$ + try { + JMeterProperty prop = createProperty(children[i], newClass); + if (prop!=null) { + element.setProperty(prop); + } + } catch (Exception ex) { + log.error("Problem loading property", ex); + element.setProperty(children[i].getAttribute("name"), ""); // $NON-NLS-1$ // $NON-NLS-2$ + } + } else if (children[i].getName().equals("testelement")) { // $NON-NLS-1$ + element.setProperty(new TestElementProperty(children[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createTestElement(children[i]))); + } else if (children[i].getName().equals("collection")) { // $NON-NLS-1$ + element.setProperty(new CollectionProperty(children[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createCollection(children[i], newClass))); + } else if (children[i].getName().equals("map")) { // $NON-NLS-1$ + element.setProperty(new MapProperty(children[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createMap(children[i],newClass))); + } + } + return element; + } + + private static Collection createCollection(Configuration config, String testClass) throws ConfigurationException, + ClassNotFoundException, IllegalAccessException, InstantiationException { + @SuppressWarnings("unchecked") // OK + Collection coll = (Collection) Class.forName(config.getAttribute("class")).newInstance(); // $NON-NLS-1$ + Configuration[] items = config.getChildren(); + + for (int i = 0; i < items.length; i++) { + if (items[i].getName().equals("property")) { // $NON-NLS-1$ + JMeterProperty prop = createProperty(items[i], testClass); + if (prop!=null) { + coll.add(prop); + } + } else if (items[i].getName().equals("testelement")) { // $NON-NLS-1$ + coll.add(new TestElementProperty(items[i].getAttribute("name", ""), createTestElement(items[i]))); // $NON-NLS-1$ // $NON-NLS-2$ + } else if (items[i].getName().equals("collection")) { // $NON-NLS-1$ + coll.add(new CollectionProperty(items[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createCollection(items[i], testClass))); + } else if (items[i].getName().equals("string")) { // $NON-NLS-1$ + JMeterProperty prop = createProperty(items[i], testClass); + if (prop!=null) { + coll.add(prop); + } + } else if (items[i].getName().equals("map")) { // $NON-NLS-1$ + coll.add(new MapProperty(items[i].getAttribute("name", ""), createMap(items[i], testClass))); // $NON-NLS-1$ // $NON-NLS-2$ + } + } + return coll; + } + + private static JMeterProperty createProperty(Configuration config, String testClass) throws IllegalAccessException, + ClassNotFoundException, InstantiationException { + String value = config.getValue(""); // $NON-NLS-1$ + String name = config.getAttribute("name", value); // $NON-NLS-1$ + String oname = name; + String type = config.getAttribute("propType", StringProperty.class.getName()); // $NON-NLS-1$ + + // Do upgrade translation: + name = NameUpdater.getCurrentName(name, testClass); + if (TestElement.GUI_CLASS.equals(name)) { + value = NameUpdater.getCurrentName(value); + } else if (TestElement.TEST_CLASS.equals(name)) { + value=testClass; // must always agree + } else { + value = NameUpdater.getCurrentName(value, name, testClass); + } + + // Delete any properties whose name converts to the empty string + if (oname.length() != 0 && name.length()==0) { + return null; + } + + // Create the property: + JMeterProperty prop = (JMeterProperty) Class.forName(type).newInstance(); + prop.setName(name); + prop.setObjectValue(value); + + return prop; + } + + private static Map createMap(Configuration config, String testClass) throws ConfigurationException, + ClassNotFoundException, IllegalAccessException, InstantiationException { + @SuppressWarnings("unchecked") // OK + Map map = (Map) Class.forName(config.getAttribute("class")).newInstance(); + Configuration[] items = config.getChildren(); + + for (int i = 0; i < items.length; i++) { + if (items[i].getName().equals("property")) { // $NON-NLS-1$ + JMeterProperty prop = createProperty(items[i], testClass); + if (prop!=null) { + map.put(prop.getName(), prop); + } + } else if (items[i].getName().equals("testelement")) { // $NON-NLS-1$ + map.put(items[i].getAttribute("name", ""), new TestElementProperty(items[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createTestElement(items[i]))); + } else if (items[i].getName().equals("collection")) { // $NON-NLS-1$ + map.put(items[i].getAttribute("name"), // $NON-NLS-1$ + new CollectionProperty(items[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createCollection(items[i], testClass))); + } else if (items[i].getName().equals("map")) { // $NON-NLS-1$ + map.put(items[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + new MapProperty(items[i].getAttribute("name", ""), // $NON-NLS-1$ // $NON-NLS-2$ + createMap(items[i], testClass))); + } + } + return map; + } + + private static HashTree generateNode(Configuration config) { + TestElement element = null; + + try { + element = createTestElement(config.getChild("testelement")); // $NON-NLS-1$ + } catch (Exception e) { + log.error("Problem loading part of file", e); + return null; + } + HashTree subTree = new ListedHashTree(element); + Configuration[] subNodes = config.getChildren("node"); // $NON-NLS-1$ + + for (int i = 0; i < subNodes.length; i++) { + HashTree t = generateNode(subNodes[i]); + + if (t != null) { + subTree.add(element, t); + } + } + return subTree; + } + + // Called by ResultCollector#loadExistingFile() if XStream loading fails + public static void processSamples(String filename, Visualizer visualizer, ResultCollector rc) + throws SAXException, IOException, ConfigurationException + { + DefaultConfigurationBuilder cfgbuilder = new DefaultConfigurationBuilder(); + Configuration savedSamples = cfgbuilder.buildFromFile(filename); + Configuration[] samples = savedSamples.getChildren(); + final boolean errorsOnly = rc.isErrorLogging(); + final boolean successOnly = rc.isSuccessOnlyLogging(); + for (int i = 0; i < samples.length; i++) { + SampleResult result = OldSaveService.getSampleResult(samples[i]); + if (ResultCollector.isSampleWanted(result.isSuccessful(), errorsOnly, successOnly)) { + visualizer.add(result); + } + } + } + + // Called by ResultCollector#recordResult() +// public static String getSerializedSampleResult( +// SampleResult result, DefaultConfigurationSerializer slzr, SampleSaveConfiguration cfg) +// throws SAXException, IOException, +// ConfigurationException { +// ByteArrayOutputStream tempOut = new ByteArrayOutputStream(); +// +// slzr.serialize(tempOut, OldSaveService.getConfiguration(result, cfg)); +// String serVer = tempOut.toString(); +// String lineSep=System.getProperty("line.separator"); // $NON-NLS-1$ +// /* +// * Remove the prefix. +// * When using the x-jars (xakan etc) or Java 1.4, the serialised output has a +// * newline after the prefix. However, when using Java 1.5 without the x-jars, the output +// * has no newline at all. +// */ +// int index = serVer.indexOf(lineSep); // Is there a new-line? +// if (index > -1) {// Yes, assume it follows the prefix +// return serVer.substring(index); +// } +// if (serVer.startsWith("");// must exist // $NON-NLS-1$ +// return lineSep + serVer.substring(index+2);// +2 for ?> +// } +// return serVer; +// } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/SaveGraphicsService.java b/ApacheJmeter/src/org/apache/jmeter/save/SaveGraphicsService.java new file mode 100644 index 0000000..ad8784e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/SaveGraphicsService.java @@ -0,0 +1,191 @@ +/* + * 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.save; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import javax.swing.JComponent; + +import org.apache.xmlgraphics.image.codec.png.PNGEncodeParam; +import org.apache.xmlgraphics.image.codec.png.PNGImageEncoder; +import org.apache.xmlgraphics.image.codec.tiff.TIFFEncodeParam; +import org.apache.xmlgraphics.image.codec.tiff.TIFFImageEncoder; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * Class is responsible for taking a component and saving it as a JPEG, PNG or + * TIFF. The class is very simple. Thanks to Batik and the developers who worked + * so hard on it. + */ +public class SaveGraphicsService { + + public static final int PNG = 0; + + public static final int TIFF = 1; + + public static final String PNG_EXTENSION = ".png"; //$NON-NLS-1$ + + public static final String TIFF_EXTENSION = ".tif"; //$NON-NLS-1$ + + public static final String JPEG_EXTENSION = ".jpg"; //$NON-NLS-1$ + + /** + * + */ + public SaveGraphicsService() { + super(); + } + +/* + * This is not currently used by JMeter code. + * As it uses Sun-specific code (the only such in JMeter), it has been commented out for now. + */ +// /** +// * If someone wants to save a JPEG, use this method. There is a limitation +// * though. It uses gray scale instead of color due to artifacts with color +// * encoding. For some reason, it does not translate pure red and orange +// * correctly. To make the text readable, gray scale is used. +// * +// * @param filename +// * @param component +// */ +// public void saveUsingJPEGEncoder(String filename, JComponent component) { +// Dimension size = component.getSize(); +// // We use Gray scale, since color produces poor quality +// // this is an unfortunate result of the default codec +// // implementation. +// BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_USHORT_GRAY); +// Graphics2D grp = image.createGraphics(); +// component.paint(grp); +// +// File outfile = new File(filename + JPEG_EXTENSION); +// FileOutputStream fos = createFile(outfile); +// JPEGEncodeParam param = JPEGCodec.getDefaultJPEGEncodeParam(image); +// Float q = new Float(1.0); +// param.setQuality(q.floatValue(), true); +// JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(fos, param); +// +// try { +// encoder.encode(image); +// } catch (Exception e) { +// log.warn(e.toString()); +// } finally { +// JOrphanUtils.closeQuietly(fos); +// } +// } + + /** + * Method will save the JComponent as an image. The formats are PNG, and + * TIFF. + * + * @param filename + * @param type + * @param component + */ + public void saveJComponent(String filename, int type, JComponent component) { + Dimension size = component.getSize(); + BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_BYTE_INDEXED); + Graphics2D grp = image.createGraphics(); + component.paint(grp); + + if (type == PNG) { + filename += PNG_EXTENSION; + this.savePNGWithBatik(filename, image); + } else if (type == TIFF) { + filename = filename + TIFF_EXTENSION; + this.saveTIFFWithBatik(filename, image); + } + } + + /** + * Use Batik to save a PNG of the graph + * + * @param filename + * @param image + */ + public void savePNGWithBatik(String filename, BufferedImage image) { + File outfile = new File(filename); + OutputStream fos = createFile(outfile); + if (fos == null) { + return; + } + PNGEncodeParam param = PNGEncodeParam.getDefaultEncodeParam(image); + PNGImageEncoder encoder = new PNGImageEncoder(fos, param); + try { + encoder.encode(image); + } catch (IOException e) { + JMeterUtils.reportErrorToUser("PNGImageEncoder reported: "+e.getMessage(), "Problem creating image file"); + } finally { + JOrphanUtils.closeQuietly(fos); + } + } + + /** + * Use Batik to save a TIFF file of the graph + * + * @param filename + * @param image + */ + public void saveTIFFWithBatik(String filename, BufferedImage image) { + File outfile = new File(filename); + OutputStream fos = createFile(outfile); + if (fos == null) { + return; + } + TIFFEncodeParam param = new TIFFEncodeParam(); + TIFFImageEncoder encoder = new TIFFImageEncoder(fos, param); + try { + encoder.encode(image); + } catch (IOException e) { + JMeterUtils.reportErrorToUser("TIFFImageEncoder reported: "+e.getMessage(), "Problem creating image file"); + // Yuck: TIFFImageEncoder uses Error to report runtime problems + } catch (Error e) { + JMeterUtils.reportErrorToUser("TIFFImageEncoder reported: "+e.getMessage(), "Problem creating image file"); + if (e.getClass() != Error.class){// rethrow other errors + throw e; + } + } finally { + JOrphanUtils.closeQuietly(fos); + } + } + + /** + * Create a new file for the graphics. Since the method creates a new file, + * we shouldn't get a FNFE. + * + * @param filename + * @return output stream created from the filename + */ + private FileOutputStream createFile(File filename) { + try { + return new FileOutputStream(filename); + } catch (FileNotFoundException e) { + JMeterUtils.reportErrorToUser("Could not create file: "+e.getMessage(), "Problem creating image file"); + return null; + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/SaveService.java b/ApacheJmeter/src/org/apache/jmeter/save/SaveService.java new file mode 100644 index 0000000..c8f9648 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/SaveService.java @@ -0,0 +1,614 @@ +/* + * 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.save; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.jmeter.reporters.ResultCollectorHelper; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.NameUpdater; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.DataHolder; +import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; +import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; +import com.thoughtworks.xstream.io.xml.XppDriver; +import com.thoughtworks.xstream.mapper.CannotResolveClassException; +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.mapper.MapperWrapper; + +/** + * Handles setting up XStream serialisation. + * The class reads alias definitions from saveservice.properties. + * + */ +public class SaveService { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Names of DataHolder entries for JTL processing + public static final String SAMPLE_EVENT_OBJECT = "SampleEvent"; // $NON-NLS-1$ + public static final String RESULTCOLLECTOR_HELPER_OBJECT = "ResultCollectorHelper"; // $NON-NLS-1$ + + // Names of DataHolder entries for JMX processing + public static final String TEST_CLASS_NAME = "TestClassName"; // $NON-NLS-1$ + + private static final class XStreamWrapper extends XStream { + private XStreamWrapper(ReflectionProvider reflectionProvider) { + super(reflectionProvider); + } + + // Override wrapMapper in order to insert the Wrapper in the chain + @Override + protected MapperWrapper wrapMapper(MapperWrapper next) { + // Provide our own aliasing using strings rather than classes + return new MapperWrapper(next){ + // Translate alias to classname and then delegate to wrapped class + @Override + public Class realClass(String alias) { + String fullName = aliasToClass(alias); + if (fullName != null) { + fullName = NameUpdater.getCurrentName(fullName); + } + return super.realClass(fullName == null ? alias : fullName); + } + // Translate to alias and then delegate to wrapped class + @Override + public String serializedClass(@SuppressWarnings("rawtypes") // superclass does not use types + Class type) { + if (type == null) { + return super.serializedClass(null); // was type, but that caused FindBugs warning + } + String alias = classToAlias(type.getName()); + return alias == null ? super.serializedClass(type) : alias ; + } + }; + } + } + + private static final XStream JMXSAVER = new XStreamWrapper(new PureJavaReflectionProvider()); + private static final XStream JTLSAVER = new XStreamWrapper(new PureJavaReflectionProvider()); + static { + JTLSAVER.setMode(XStream.NO_REFERENCES); // This is needed to stop XStream keeping copies of each class + } + + // The XML header, with placeholder for encoding, since that is controlled by property + private static final String XML_HEADER = "\"?>"; // $NON-NLS-1$ + + // Default file name + private static final String SAVESERVICE_PROPERTIES_FILE = "/bin/saveservice.properties"; // $NON-NLS-1$ + + // Property name used to define file name + private static final String SAVESERVICE_PROPERTIES = "saveservice_properties"; // $NON-NLS-1$ + + // Define file format property names + private static final String FILE_FORMAT = "file_format"; // $NON-NLS-1$ + private static final String FILE_FORMAT_TESTPLAN = "file_format.testplan"; // $NON-NLS-1$ + private static final String FILE_FORMAT_TESTLOG = "file_format.testlog"; // $NON-NLS-1$ + + // Define file format versions + private static final String VERSION_2_2 = "2.2"; // $NON-NLS-1$ + + // Default to overall format, and then to version 2.2 + public static final String TESTPLAN_FORMAT + = JMeterUtils.getPropDefault(FILE_FORMAT_TESTPLAN + , JMeterUtils.getPropDefault(FILE_FORMAT, VERSION_2_2)); + + public static final String TESTLOG_FORMAT + = JMeterUtils.getPropDefault(FILE_FORMAT_TESTLOG + , JMeterUtils.getPropDefault(FILE_FORMAT, VERSION_2_2)); + + private static boolean validateFormat(String format){ + if ("2.2".equals(format)) return true; + if ("2.1".equals(format)) return true; + return false; + } + + static{ + if (!validateFormat(TESTPLAN_FORMAT)){ + log.error("Invalid test plan format: "+TESTPLAN_FORMAT); + } + if (!validateFormat(TESTLOG_FORMAT)){ + log.error("Invalid test log format: "+TESTLOG_FORMAT); + } + } + + /** New XStream format - more compressed class names */ + public static final boolean IS_TESTPLAN_FORMAT_22 + = VERSION_2_2.equals(TESTPLAN_FORMAT); + + // Holds the mappings from the saveservice properties file + // Key: alias Entry: full class name + // There may be multiple aliases which map to the same class + private static final Properties aliasToClass = new Properties(); + + // Holds the reverse mappings + // Key: full class name Entry: primary alias + private static final Properties classToAlias = new Properties(); + + // Version information for test plan header + // This is written to JMX files by ScriptWrapperConverter + // Also to JTL files by ResultCollector + private static final String VERSION = "1.2"; // $NON-NLS-1$ + + // This is written to JMX files by ScriptWrapperConverter + private static String propertiesVersion = "";// read from properties file; written to JMX files + private static final String PROPVERSION = "2.3";// Expected version $NON-NLS-1$ + + // Internal information only + private static String fileVersion = ""; // read from saveservice.properties file// $NON-NLS-1$ + private static final String FILEVERSION = "1332608"; // Expected value $NON-NLS-1$ + private static String fileEncoding = ""; // read from properties file// $NON-NLS-1$ + + static { + log.info("Testplan (JMX) version: "+TESTPLAN_FORMAT+". Testlog (JTL) version: "+TESTLOG_FORMAT); + initProps(); + checkVersions(); + } + + // Helper method to simplify alias creation from properties + private static void makeAlias(String aliasList, String clazz) { + String aliases[]=aliasList.split(","); // Can have multiple aliases for same target classname + String alias=aliases[0]; + for (String a : aliases){ + Object old = aliasToClass.setProperty(a,clazz); + if (old != null){ + log.error("Duplicate class detected for "+alias+": "+clazz+" & "+old); + } + } + Object oldval=classToAlias.setProperty(clazz,alias); + if (oldval != null) { + log.error("Duplicate alias detected for "+clazz+": "+alias+" & "+oldval); + } + } + + public static Properties loadProperties() throws IOException{ + Properties nameMap = new Properties(); + FileInputStream fis = null; + try { + fis = new FileInputStream(JMeterUtils.getJMeterHome() + + JMeterUtils.getPropDefault(SAVESERVICE_PROPERTIES, SAVESERVICE_PROPERTIES_FILE)); + nameMap.load(fis); + } finally { + JOrphanUtils.closeQuietly(fis); + } + return nameMap; + } + private static void initProps() { + // Load the alias properties + try { + Properties nameMap = loadProperties(); + // now create the aliases + for (Map.Entry me : nameMap.entrySet()) { + String key = (String) me.getKey(); + String val = (String) me.getValue(); + if (!key.startsWith("_")) { // $NON-NLS-1$ + makeAlias(key, val); + } else { + // process special keys + if (key.equalsIgnoreCase("_version")) { // $NON-NLS-1$ + propertiesVersion = val; + log.info("Using SaveService properties version " + propertiesVersion); + } else if (key.equalsIgnoreCase("_file_version")) { // $NON-NLS-1$ + fileVersion = extractVersion(val); + log.info("Using SaveService properties file version " + fileVersion); + } else if (key.equalsIgnoreCase("_file_encoding")) { // $NON-NLS-1$ + fileEncoding = val; + log.info("Using SaveService properties file encoding " + fileEncoding); + } else { + key = key.substring(1);// Remove the leading "_" + try { + final String trimmedValue = val.trim(); + if (trimmedValue.equals("collection") // $NON-NLS-1$ + || trimmedValue.equals("mapping")) { // $NON-NLS-1$ + registerConverter(key, JMXSAVER, true); + registerConverter(key, JTLSAVER, true); + } else { + registerConverter(key, JMXSAVER, false); + registerConverter(key, JTLSAVER, false); + } + } catch (IllegalAccessException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (InstantiationException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (ClassNotFoundException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (IllegalArgumentException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (SecurityException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (InvocationTargetException e1) { + log.warn("Can't register a converter: " + key, e1); + } catch (NoSuchMethodException e1) { + log.warn("Can't register a converter: " + key, e1); + } + } + } + } + } catch (IOException e) { + log.fatalError("Bad saveservice properties file", e); + throw new JMeterError("JMeter requires the saveservice properties file to continue"); + } + } + + /** + * Register converter. + * @param key + * @param jmxsaver + * @param useMapper + * + * @throws InstantiationException + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws NoSuchMethodException + * @throws ClassNotFoundException + */ + private static void registerConverter(String key, XStream jmxsaver, boolean useMapper) + throws InstantiationException, IllegalAccessException, + InvocationTargetException, NoSuchMethodException, + ClassNotFoundException { + if (useMapper){ + jmxsaver.registerConverter((Converter) Class.forName(key).getConstructor( + new Class[] { Mapper.class }).newInstance( + new Object[] { jmxsaver.getMapper() })); + } else { + jmxsaver.registerConverter((Converter) Class.forName(key).newInstance()); + } + } + + // For converters to use + public static String aliasToClass(String s){ + String r = aliasToClass.getProperty(s); + return r == null ? s : r; + } + + // For converters to use + public static String classToAlias(String s){ + String r = classToAlias.getProperty(s); + return r == null ? s : r; + } + + // Called by Save function + public static void saveTree(HashTree tree, OutputStream out) throws IOException { + // Get the OutputWriter to use + OutputStreamWriter outputStreamWriter = getOutputStreamWriter(out); + writeXmlHeader(outputStreamWriter); + // Use deprecated method, to avoid duplicating code + ScriptWrapper wrapper = new ScriptWrapper(); + wrapper.testPlan = tree; + JMXSAVER.toXML(wrapper, outputStreamWriter); + outputStreamWriter.write('\n');// Ensure terminated properly + outputStreamWriter.close(); + } + + // Used by Test code + public static void saveElement(Object el, OutputStream out) throws IOException { + // Get the OutputWriter to use + OutputStreamWriter outputStreamWriter = getOutputStreamWriter(out); + writeXmlHeader(outputStreamWriter); + // Use deprecated method, to avoid duplicating code + JMXSAVER.toXML(el, outputStreamWriter); + outputStreamWriter.close(); + } + + // Used by Test code + public static Object loadElement(InputStream in) throws IOException { + // Get the InputReader to use + InputStreamReader inputStreamReader = getInputStreamReader(in); + // Use deprecated method, to avoid duplicating code + Object element = JMXSAVER.fromXML(inputStreamReader); + inputStreamReader.close(); + return element; + } + + /** + * Save a sampleResult to an XML output file using XStream. + * + * @param evt sampleResult wrapped in a sampleEvent + * @param writer output stream which must be created using {@link #getFileEncoding(String)} + */ + // Used by ResultCollector.sampleOccurred(SampleEvent event) + public synchronized static void saveSampleResult(SampleEvent evt, Writer writer) throws IOException { + DataHolder dh = JTLSAVER.newDataHolder(); + dh.put(SAMPLE_EVENT_OBJECT, evt); + // This is effectively the same as saver.toXML(Object, Writer) except we get to provide the DataHolder + // Don't know why there is no method for this in the XStream class + JTLSAVER.marshal(evt.getResult(), new XppDriver().createWriter(writer), dh); + writer.write('\n'); + } + + /** + * @param elem test element + * @param writer output stream which must be created using {@link #getFileEncoding(String)} + */ + // Used by ResultCollector#recordStats() + public synchronized static void saveTestElement(TestElement elem, Writer writer) throws IOException { + JMXSAVER.toXML(elem, writer); // TODO should this be JTLSAVER? Only seems to be called by MonitorHealthVisualzer + writer.write('\n'); + } + + private static boolean versionsOK = true; + + // Extract version digits from String of the form #Revision: n.mm # + // (where # is actually $ above) + private static final String REVPFX = "$Revision: "; + private static final String REVSFX = " $"; // $NON-NLS-1$ + + private static String extractVersion(String rev) { + if (rev.length() > REVPFX.length() + REVSFX.length()) { + return rev.substring(REVPFX.length(), rev.length() - REVSFX.length()); + } + return rev; + } + +// private static void checkVersion(Class clazz, String expected) { +// +// String actual = "*NONE*"; // $NON-NLS-1$ +// try { +// actual = (String) clazz.getMethod("getVersion", null).invoke(null, null); +// actual = extractVersion(actual); +// } catch (Exception ignored) { +// // Not needed +// } +// if (0 != actual.compareTo(expected)) { +// versionsOK = false; +// log.warn("Version mismatch: expected '" + expected + "' found '" + actual + "' in " + clazz.getName()); +// } +// } + + // Routines for TestSaveService + static boolean checkPropertyVersion(){ + return SaveService.PROPVERSION.equals(SaveService.propertiesVersion); + } + + static boolean checkFileVersion(){ + return SaveService.FILEVERSION.equals(SaveService.fileVersion); + } + + // Allow test code to check for spurious class references + static List checkClasses(){ + final ClassLoader classLoader = SaveService.class.getClassLoader(); + List missingClasses = new ArrayList(); + //boolean OK = true; + for (Object clazz : classToAlias.keySet()) { + String name = (String) clazz; + if (!NameUpdater.isMapped(name)) {// don't bother checking class is present if it is to be updated + try { + Class.forName(name, false, classLoader); + } catch (ClassNotFoundException e) { + log.error("Unexpected entry in saveservice.properties; class does not exist and is not upgraded: "+name); + missingClasses.add(name); + } + } + } + return missingClasses; + } + + static boolean checkVersions() { + versionsOK = true; + // Disable converter version checks as they are more of a nuisance than helpful +// checkVersion(BooleanPropertyConverter.class, "493779"); // $NON-NLS-1$ +// checkVersion(HashTreeConverter.class, "514283"); // $NON-NLS-1$ +// checkVersion(IntegerPropertyConverter.class, "493779"); // $NON-NLS-1$ +// checkVersion(LongPropertyConverter.class, "493779"); // $NON-NLS-1$ +// checkVersion(MultiPropertyConverter.class, "514283"); // $NON-NLS-1$ +// checkVersion(SampleResultConverter.class, "571992"); // $NON-NLS-1$ +// +// // Not built until later, so need to use this method: +// try { +// checkVersion( +// Class.forName("org.apache.jmeter.protocol.http.util.HTTPResultConverter"), // $NON-NLS-1$ +// "514283"); // $NON-NLS-1$ +// } catch (ClassNotFoundException e) { +// versionsOK = false; +// log.warn(e.getLocalizedMessage()); +// } +// checkVersion(StringPropertyConverter.class, "493779"); // $NON-NLS-1$ +// checkVersion(TestElementConverter.class, "549987"); // $NON-NLS-1$ +// checkVersion(TestElementPropertyConverter.class, "549987"); // $NON-NLS-1$ +// checkVersion(ScriptWrapperConverter.class, "514283"); // $NON-NLS-1$ +// checkVersion(TestResultWrapperConverter.class, "514283"); // $NON-NLS-1$ +// checkVersion(SampleSaveConfigurationConverter.class,"549936"); // $NON-NLS-1$ + + if (!PROPVERSION.equalsIgnoreCase(propertiesVersion)) { + log.warn("Bad _version - expected " + PROPVERSION + ", found " + propertiesVersion + "."); + } +// if (!FILEVERSION.equalsIgnoreCase(fileVersion)) { +// log.warn("Bad _file_version - expected " + FILEVERSION + ", found " + fileVersion +"."); +// } + if (versionsOK) { + log.info("All converter versions present and correct"); + } + return versionsOK; + } + + /** + * Read results from JTL file. + * + * @param reader of the file + * @param resultCollectorHelper helper class to enable TestResultWrapperConverter to deliver the samples + * @throws Exception + */ + public static void loadTestResults(InputStream reader, ResultCollectorHelper resultCollectorHelper) throws Exception { + // Get the InputReader to use + InputStreamReader inputStreamReader = getInputStreamReader(reader); + DataHolder dh = JTLSAVER.newDataHolder(); + dh.put(RESULTCOLLECTOR_HELPER_OBJECT, resultCollectorHelper); // Allow TestResultWrapper to feed back the samples + // This is effectively the same as saver.fromXML(InputStream) except we get to provide the DataHolder + // Don't know why there is no method for this in the XStream class + JTLSAVER.unmarshal(new XppDriver().createReader(reader), null, dh); + inputStreamReader.close(); + } + + /** + * Load a Test tree (JMX file) + * @param reader on the JMX file + * @return the loaded tree + * @throws Exception if there is a problem reading the file or processing it + */ + public static HashTree loadTree(InputStream reader) throws Exception { + if (!reader.markSupported()) { + reader = new BufferedInputStream(reader); + } + reader.mark(Integer.MAX_VALUE); + ScriptWrapper wrapper = null; + try { + // Get the InputReader to use + InputStreamReader inputStreamReader = getInputStreamReader(reader); + wrapper = (ScriptWrapper) JMXSAVER.fromXML(inputStreamReader); + inputStreamReader.close(); + if (wrapper == null){ + log.error("Problem loading XML: see above."); + return null; + } + return wrapper.testPlan; + } catch (CannotResolveClassException e) { + if (e.getMessage().startsWith("node")) { + log.info("Problem loading XML, trying Avalon format"); + reader.reset(); + return OldSaveService.loadSubTree(reader); + } + log.warn("Problem loading XML, cannot determine class for element: " + e.getLocalizedMessage()); + return null; + } catch (NoClassDefFoundError e) { + log.error("Missing class "+e); + return null; + } catch (ConversionException e) { + log.error("Conversion error "+e); + return null; + } + } + + private static InputStreamReader getInputStreamReader(InputStream inStream) { + // Check if we have a encoding to use from properties + Charset charset = getFileEncodingCharset(); + if(charset != null) { + return new InputStreamReader(inStream, charset); + } + else { + // We use the default character set encoding of the JRE + return new InputStreamReader(inStream); + } + } + + private static OutputStreamWriter getOutputStreamWriter(OutputStream outStream) { + // Check if we have a encoding to use from properties + Charset charset = getFileEncodingCharset(); + if(charset != null) { + return new OutputStreamWriter(outStream, charset); + } + else { + // We use the default character set encoding of the JRE + return new OutputStreamWriter(outStream); + } + } + + /** + * Returns the file Encoding specified in saveservice.properties or the default + * @param dflt value to return if file encoding was not provided + * + * @return file encoding or default + */ + // Used by ResultCollector when creating output files + public static String getFileEncoding(String dflt){ + if(fileEncoding != null && fileEncoding.length() > 0) { + return fileEncoding; + } + else { + return dflt; + } + } + + private static Charset getFileEncodingCharset() { + // Check if we have a encoding to use from properties + if(fileEncoding != null && fileEncoding.length() > 0) { + return Charset.forName(fileEncoding); + } + else { + // We use the default character set encoding of the JRE + return null; + } + } + + private static void writeXmlHeader(OutputStreamWriter writer) throws IOException { + // Write XML header if we have the charset to use for encoding + Charset charset = getFileEncodingCharset(); + if(charset != null) { + // We do not use getEncoding method of Writer, since that returns + // the historical name + String header = XML_HEADER.replaceAll("", charset.name()); + writer.write(header); + writer.write('\n'); + } + } + +// Normal output +// ---- Debugging information ---- +// required-type : org.apache.jorphan.collections.ListedHashTree +// cause-message : WebServiceSampler : WebServiceSampler +// class : org.apache.jmeter.save.ScriptWrapper +// message : WebServiceSampler : WebServiceSampler +// line number : 929 +// path : /jmeterTestPlan/hashTree/hashTree/hashTree[4]/hashTree[5]/WebServiceSampler +// cause-exception : com.thoughtworks.xstream.alias.CannotResolveClassException +// ------------------------------- + + /** + * Simplify getMessage() output from XStream ConversionException + * @param ce - ConversionException to analyse + * @return string with details of error + */ + public static String CEtoString(ConversionException ce){ + String msg = + "XStream ConversionException at line: " + ce.get("line number") + + "\n" + ce.get("message") + + "\nPerhaps a missing jar? See log file."; + return msg; + } + + public static String getPropertiesVersion() { + return propertiesVersion; + } + + public static String getVERSION() { + return VERSION; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/save/ScriptWrapper.java b/ApacheJmeter/src/org/apache/jmeter/save/ScriptWrapper.java new file mode 100644 index 0000000..6d26553 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/ScriptWrapper.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.jmeter.save; + +import org.apache.jorphan.collections.HashTree; + +class ScriptWrapper { + // Used by ScriptWrapperConverter + String version = ""; + + HashTree testPlan; +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/ScriptWrapperConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/ScriptWrapperConverter.java new file mode 100644 index 0000000..eba013e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/ScriptWrapperConverter.java @@ -0,0 +1,121 @@ +/* + * 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.save; + +import org.apache.jmeter.save.converters.ConversionHelp; +import org.apache.jorphan.collections.HashTree; + +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * Handles XStream conversion of Test Scripts + * + */ +public class ScriptWrapperConverter implements Converter { + + private static final String ATT_PROPERTIES = "properties"; // $NON-NLS-1$ + private static final String ATT_VERSION = "version"; // $NON-NLS-1$ + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 1206717 $"; // $NON-NLS-1$ + } + + private final Mapper classMapper; + + public ScriptWrapperConverter(Mapper classMapper) { + this.classMapper = classMapper; + } + + /** + * {@inheritDoc} + */ + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass is not typed + return arg0.equals(ScriptWrapper.class); + } + + /** + * {@inheritDoc} + */ + public void marshal(Object arg0, HierarchicalStreamWriter writer, MarshallingContext context) { + ScriptWrapper wrap = (ScriptWrapper) arg0; + String version = SaveService.getVERSION(); + ConversionHelp.setOutVersion(version);// Ensure output follows version + writer.addAttribute(ATT_VERSION, version); + writer.addAttribute(ATT_PROPERTIES, SaveService.getPropertiesVersion()); + writer.startNode(classMapper.serializedClass(wrap.testPlan.getClass())); + context.convertAnother(wrap.testPlan); + writer.endNode(); + } + + /** + * {@inheritDoc} + */ + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + ScriptWrapper wrap = new ScriptWrapper(); + wrap.version = reader.getAttribute(ATT_VERSION); + ConversionHelp.setInVersion(wrap.version);// Make sure decoding + // follows input file + reader.moveDown(); + // Catch errors and rethrow as ConversionException so we get location details + try { + wrap.testPlan = (HashTree) context.convertAnother(wrap, getNextType(reader)); + } catch (NoClassDefFoundError e) { + throw createConversionException(e); + } catch (Exception e) { + throw createConversionException(e); + } + return wrap; + } + + private ConversionException createConversionException(Throwable e) { + final ConversionException conversionException = new ConversionException(e); + StackTraceElement[] ste = e.getStackTrace(); + if (ste!=null){ + for(StackTraceElement top : ste){ + String className=top.getClassName(); + if (className.startsWith("org.apache.jmeter.")){ + conversionException.add("first-jmeter-class", top.toString()); + break; + } + } + } + return conversionException; + } + + protected Class getNextType(HierarchicalStreamReader reader) { + String classAttribute = reader.getAttribute(ConversionHelp.ATT_CLASS); + Class type; + if (classAttribute == null) { + type = classMapper.realClass(reader.getNodeName()); + } else { + type = classMapper.realClass(classAttribute); + } + return type; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/TestResultWrapper.java b/ApacheJmeter/src/org/apache/jmeter/save/TestResultWrapper.java new file mode 100644 index 0000000..45adf3e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/TestResultWrapper.java @@ -0,0 +1,79 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 6, 2004 + */ +package org.apache.jmeter.save; + +import java.util.Collection; + +import org.apache.jmeter.samplers.SampleResult; + +public class TestResultWrapper { + private String version = ""; + + private Collection sampleResults; + + private long testStartTime; + + /** + * @return Returns the sampleResults. + */ + public Collection getSampleResults() { + return sampleResults; + } + + /** + * @param sampleResults + * The sampleResults to set. + */ + public void setSampleResults(Collection sampleResults) { + this.sampleResults = sampleResults; + } + + /** + * @return Returns the testStartTime. + */ + public long getTestStartTime() { + return testStartTime; + } + + /** + * @param testStartTime + * The testStartTime to set. + */ + public void setTestStartTime(long testStartTime) { + this.testStartTime = testStartTime; + } + + /** + * @return Returns the version. + */ + public String getVersion() { + return version; + } + + /** + * @param version + * The version to set. + */ + public void setVersion(String version) { + this.version = version; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/BooleanPropertyConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/BooleanPropertyConverter.java new file mode 100644 index 0000000..0c1ca1c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/BooleanPropertyConverter.java @@ -0,0 +1,61 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.testelement.property.BooleanProperty; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class BooleanPropertyConverter implements Converter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 1232550 $"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) {// superclass does not use types + return arg0.equals(BooleanProperty.class); + } + + /** {@inheritDoc} */ + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext arg2) { + BooleanProperty prop = (BooleanProperty) obj; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + writer.setValue(prop.getStringValue()); + + } + + /** {@inheritDoc} */ + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final String name = ConversionHelp.getPropertyName(reader, context); + if (name == null) { + return null; + } + BooleanProperty prop = new BooleanProperty(name, Boolean.valueOf(reader.getValue()).booleanValue()); + return prop; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/ConversionHelp.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/ConversionHelp.java new file mode 100644 index 0000000..9c47d5f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/ConversionHelp.java @@ -0,0 +1,288 @@ +/* + * 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. + * + */ + +/* + * Created on Jul 27, 2004 + */ +package org.apache.jmeter.save.converters; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.NameUpdater; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * Utility conversion routines for use with XStream + * + */ +public class ConversionHelp { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String CHAR_SET = "UTF-8"; //$NON-NLS-1$ + + // Attributes for TestElement and TestElementProperty + // Must all be unique + public static final String ATT_CLASS = "class"; //$NON-NLS-1$ + // Also used by PropertyConverter classes + public static final String ATT_NAME = "name"; // $NON-NLS-1$ + public static final String ATT_ELEMENT_TYPE = "elementType"; // $NON-NLS-1$ + + private static final String ATT_TE_ENABLED = "enabled"; //$NON-NLS-1$ + private static final String ATT_TE_TESTCLASS = "testclass"; //$NON-NLS-1$ + static final String ATT_TE_GUICLASS = "guiclass"; //$NON-NLS-1$ + private static final String ATT_TE_NAME = "testname"; //$NON-NLS-1$ + + + /* + * These must be set before reading/writing the XML. Rather a hack, but + * saves changing all the method calls to include an extra variable. + * + * AFAIK the variables should only be accessed from one thread, so no need to synchronize. + */ + private static String inVersion; + + private static String outVersion = "1.1"; // Default for writing//$NON-NLS-1$ + + public static void setInVersion(String v) { + inVersion = v; + } + + public static void setOutVersion(String v) { + outVersion = v; + } + + /** + * Encode a string (if necessary) for output to a JTL file. + * Strings are only encoded if the output version is 1.0, + * but nulls are always converted to the empty string. + * + * @param p string to encode + * @return encoded string (will never be null) + */ + public static String encode(String p) { + if (p == null) {// Nulls cannot be written using PrettyPrintWriter - they cause an NPE + return ""; // $NON-NLS-1$ + } + // Only encode strings if outVersion = 1.0 + if (!"1.0".equals(outVersion)) {//$NON-NLS-1$ + return p; + } + try { + String p1 = URLEncoder.encode(p, CHAR_SET); + return p1; + } catch (UnsupportedEncodingException e) { + log.warn("System doesn't support " + CHAR_SET, e); + return p; + } + } + + public static String decode(String p) { + if (!"1.0".equals(inVersion)) {//$NON-NLS-1$ + return p; + } + // Only decode strings if inVersion = 1.0 + if (p == null) { + return null; + } + try { + return URLDecoder.decode(p, CHAR_SET); + } catch (UnsupportedEncodingException e) { + log.warn("System doesn't support " + CHAR_SET, e); + return p; + } + } + + public static String cdata(byte[] chars, String encoding) throws UnsupportedEncodingException { + StringBuilder buf = new StringBuilder(""); + return buf.toString(); + } + + // Names of properties that are handled specially + private static final Map propertyToAttribute=new HashMap(); + + private static void mapentry(String prop, String att){ + propertyToAttribute.put(prop,att); + } + + static{ + mapentry(TestElement.NAME,ATT_TE_NAME); + mapentry(TestElement.GUI_CLASS,ATT_TE_GUICLASS);//$NON-NLS-1$ + mapentry(TestElement.TEST_CLASS,ATT_TE_TESTCLASS);//$NON-NLS-1$ + mapentry(TestElement.ENABLED,ATT_TE_ENABLED); + } + + private static void saveClass(TestElement el, HierarchicalStreamWriter writer, String prop){ + String clazz=el.getPropertyAsString(prop); + if (clazz.length()>0) { + writer.addAttribute(propertyToAttribute.get(prop),SaveService.classToAlias(clazz)); + } + } + + private static void restoreClass(TestElement el, HierarchicalStreamReader reader, String prop) { + String att=propertyToAttribute.get(prop); + String alias=reader.getAttribute(att); + if (alias!=null){ + alias=SaveService.aliasToClass(alias); + if (TestElement.GUI_CLASS.equals(prop)) { // mainly for TestElementConverter + alias = NameUpdater.getCurrentName(alias); + } + el.setProperty(prop,alias); + } + } + + private static void saveItem(TestElement el, HierarchicalStreamWriter writer, String prop, + boolean encode){ + String item=el.getPropertyAsString(prop); + if (item.length() > 0) { + if (encode) { + item=ConversionHelp.encode(item); + } + writer.addAttribute(propertyToAttribute.get(prop),item); + } + } + + private static void restoreItem(TestElement el, HierarchicalStreamReader reader, String prop, + boolean decode) { + String att=propertyToAttribute.get(prop); + String value=reader.getAttribute(att); + if (value!=null){ + if (decode) { + value=ConversionHelp.decode(value); + } + el.setProperty(prop,value); + } + } + + public static boolean isSpecialProperty(String name) { + return propertyToAttribute.containsKey(name); + } + + /** + * Get the property name, updating it if necessary using {@link NameUpdater}. + * @param reader where to read the name attribute + * @param context the unmarshalling context + * + * @return the property name, may be null if the property has been deleted. + * @see #getUpgradePropertyName(String, UnmarshallingContext) + */ + public static String getPropertyName(HierarchicalStreamReader reader, UnmarshallingContext context) { + String name = ConversionHelp.decode(reader.getAttribute(ATT_NAME)); + return getUpgradePropertyName(name, context); + + } + + /** + * Get the property value, updating it if necessary using {@link NameUpdater}. + * + * Do not use for GUI_CLASS or TEST_CLASS. + * + * @param reader where to read the value + * @param context the unmarshalling context + * + * @return the property value, updated if necessary. + * @see #getUpgradePropertyValue(String, String, UnmarshallingContext) + */ + public static String getPropertyValue(HierarchicalStreamReader reader, UnmarshallingContext context, String name) { + String value = ConversionHelp.decode(reader.getValue()); + return getUpgradePropertyValue(name, value, context); + + } + + /** + * Update a property name using {@link NameUpdater}. + * @param name the original property name + * @param context the unmarshalling context + * + * @return the property name, may be null if the property has been deleted. + */ + public static String getUpgradePropertyName(String name, UnmarshallingContext context) { + String testClass = (String) context.get(SaveService.TEST_CLASS_NAME); + final String newName = NameUpdater.getCurrentName(name, testClass); + // Delete any properties whose name converts to the empty string + if (name.length() != 0 && newName.length()==0) { + return null; + } + return newName; + } + + /** + * Update a property value using {@link NameUpdater#getCurrentName(String, String, String)}. + * + * Do not use for GUI_CLASS or TEST_CLASS. + * + * @param name the original property name + * @param value the original property value + * @param context the unmarshalling context + * + * @return the property value, updated if necessary + */ + public static String getUpgradePropertyValue(String name, String value, UnmarshallingContext context) { + String testClass = (String) context.get(SaveService.TEST_CLASS_NAME); + return NameUpdater.getCurrentName(value, name, testClass); + } + + + /** + * Save the special properties: + *

    + *
  • TestElement.GUI_CLASS
  • + *
  • TestElement.TEST_CLASS
  • + *
  • TestElement.NAME
  • + *
  • TestElement.ENABLED
  • + *
+ * @param testElement + * @param writer + */ + public static void saveSpecialProperties(TestElement testElement, HierarchicalStreamWriter writer) { + saveClass(testElement,writer,TestElement.GUI_CLASS); + saveClass(testElement,writer,TestElement.TEST_CLASS); + saveItem(testElement,writer,TestElement.NAME,true); + saveItem(testElement,writer,TestElement.ENABLED,false); + } + + /** + * Restore the special properties: + *
    + *
  • TestElement.GUI_CLASS
  • + *
  • TestElement.TEST_CLASS
  • + *
  • TestElement.NAME
  • + *
  • TestElement.ENABLED
  • + *
+ * @param testElement + * @param reader + */ + public static void restoreSpecialProperties(TestElement testElement, HierarchicalStreamReader reader) { + restoreClass(testElement,reader,TestElement.GUI_CLASS); + restoreClass(testElement,reader,TestElement.TEST_CLASS); + restoreItem(testElement,reader,TestElement.NAME,true); + restoreItem(testElement,reader,TestElement.ENABLED,false); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/HashTreeConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/HashTreeConverter.java new file mode 100644 index 0000000..3117522 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/HashTreeConverter.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.save.converters; + +import org.apache.jorphan.collections.HashTree; + +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class HashTreeConverter extends AbstractCollectionConverter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 1188228 $"; //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return HashTree.class.isAssignableFrom(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object arg0, HierarchicalStreamWriter writer, MarshallingContext context) { + HashTree tree = (HashTree) arg0; + for (Object item : tree.list()) { + writeItem(item, context, writer); + writeItem(tree.getTree(item), context, writer); + } + + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + boolean isKey = true; + Object current = null; + HashTree tree = (HashTree) createCollection(context.getRequiredType()); + while (reader.hasMoreChildren()) { + reader.moveDown(); + Object item = readItem(reader, context, tree); + if (isKey) { + tree.add(item); + current = item; + isKey = false; + } else { + tree.set(current, (HashTree) item); + isKey = true; + } + reader.moveUp(); + } + return tree; + } + + public HashTreeConverter(Mapper arg0) { + super(arg0); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/IntegerPropertyConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/IntegerPropertyConverter.java new file mode 100644 index 0000000..224282f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/IntegerPropertyConverter.java @@ -0,0 +1,60 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.testelement.property.IntegerProperty; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class IntegerPropertyConverter implements Converter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 1232550 $"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return arg0.equals(IntegerProperty.class); + } + + /** {@inheritDoc} */ + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext arg2) { + IntegerProperty prop = (IntegerProperty) obj; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + writer.setValue(prop.getStringValue()); + } + + /** {@inheritDoc} */ + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final String name = ConversionHelp.getPropertyName(reader, context); + if (name == null) { + return null; + } + IntegerProperty prop = new IntegerProperty(name, Integer.parseInt(reader.getValue())); + return prop; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/LongPropertyConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/LongPropertyConverter.java new file mode 100644 index 0000000..6ef2a13 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/LongPropertyConverter.java @@ -0,0 +1,60 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.testelement.property.LongProperty; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class LongPropertyConverter implements Converter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 1232550 $"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return arg0.equals(LongProperty.class); + } + + /** {@inheritDoc} */ + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext arg2) { + LongProperty prop = (LongProperty) obj; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + writer.setValue(prop.getStringValue()); + } + + /** {@inheritDoc} */ + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final String name = ConversionHelp.getPropertyName(reader, context); + if (name == null) { + return null; + } + LongProperty prop = new LongProperty(name, Long.parseLong(reader.getValue())); + return prop; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/MultiPropertyConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/MultiPropertyConverter.java new file mode 100644 index 0000000..104a6f4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/MultiPropertyConverter.java @@ -0,0 +1,84 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.MapProperty; +import org.apache.jmeter.testelement.property.MultiProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; + +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class MultiPropertyConverter extends AbstractCollectionConverter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 1232554 $"; //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return arg0.equals(CollectionProperty.class) || arg0.equals(MapProperty.class); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object arg0, HierarchicalStreamWriter writer, MarshallingContext context) { + MultiProperty prop = (MultiProperty) arg0; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + PropertyIterator iter = prop.iterator(); + while (iter.hasNext()) { + writeItem(iter.next(), context, writer); + } + + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + MultiProperty prop = (MultiProperty) createCollection(context.getRequiredType()); + prop.setName(ConversionHelp.decode(reader.getAttribute(ConversionHelp.ATT_NAME))); + while (reader.hasMoreChildren()) { + reader.moveDown(); + JMeterProperty subProp = (JMeterProperty) readItem(reader, context, prop); + if (subProp != null) { // could be null if it has been deleted via NameUpdater + prop.addProperty(subProp); + } + reader.moveUp(); + } + return prop; + } + + /** + * @param arg0 + */ + public MultiPropertyConverter(Mapper arg0) { + super(arg0); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/SampleEventConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/SampleEventConverter.java new file mode 100644 index 0000000..89d7e41 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/SampleEventConverter.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.save.converters; + +import org.apache.jmeter.samplers.SampleEvent; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * XStream Converter for the SampleResult class + */ +public class SampleEventConverter implements Converter { + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 959055 $"; //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return SampleEvent.class.equals(arg0); + } + + /** {@inheritDoc} */ + // TODO save hostname; save sample type (plain or http) + public void marshal(Object source, HierarchicalStreamWriter writer, + MarshallingContext context) { + SampleEvent evt = (SampleEvent) source; + Object res = evt.getResult(); + context.convertAnother(res); + } + + /** {@inheritDoc} */ + // TODO does not work yet; need to determine the sample type + public Object unmarshal(HierarchicalStreamReader reader, + UnmarshallingContext context) { + SampleEvent evt = new SampleEvent(); + return evt; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/SampleResultConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/SampleResultConverter.java new file mode 100644 index 0000000..8070ead --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/SampleResultConverter.java @@ -0,0 +1,422 @@ +/* + * 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.save.converters; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.save.SaveService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.Converter; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * XStream Converter for the SampleResult class + */ +public class SampleResultConverter extends AbstractCollectionConverter { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String JAVA_LANG_STRING = "java.lang.String"; //$NON-NLS-1$ + private static final String ATT_CLASS = "class"; //$NON-NLS-1$ + + // Element tags. Must be unique. Keep sorted. + protected static final String TAG_COOKIES = "cookies"; //$NON-NLS-1$ + protected static final String TAG_METHOD = "method"; //$NON-NLS-1$ + protected static final String TAG_QUERY_STRING = "queryString"; //$NON-NLS-1$ + protected static final String TAG_REDIRECT_LOCATION = "redirectLocation"; //$NON-NLS-1$ + protected static final String TAG_REQUEST_HEADER = "requestHeader"; //$NON-NLS-1$ + + //NOT USED protected static final String TAG_URL = "requestUrl"; //$NON-NLS-1$ + + protected static final String TAG_RESPONSE_DATA = "responseData"; //$NON-NLS-1$ + protected static final String TAG_RESPONSE_HEADER = "responseHeader"; //$NON-NLS-1$ + protected static final String TAG_SAMPLER_DATA = "samplerData"; //$NON-NLS-1$ + protected static final String TAG_RESPONSE_FILE = "responseFile"; //$NON-NLS-1$ + + // samplerData attributes. Must be unique. Keep sorted by string value. + // Ensure the Listener documentation is updated when new attributes are added + private static final String ATT_BYTES = "by"; //$NON-NLS-1$ + private static final String ATT_DATA_ENCODING = "de"; //$NON-NLS-1$ + private static final String ATT_DATA_TYPE = "dt"; //$NON-NLS-1$ + private static final String ATT_ERROR_COUNT = "ec"; //$NON-NLS-1$ + private static final String ATT_HOSTNAME = "hn"; //$NON-NLS-1$ + private static final String ATT_LABEL = "lb"; //$NON-NLS-1$ + private static final String ATT_LATENCY = "lt"; //$NON-NLS-1$ + + private static final String ATT_ALL_THRDS = "na"; //$NON-NLS-1$ + private static final String ATT_GRP_THRDS = "ng"; //$NON-NLS-1$ + + // N.B. Originally the response code was saved with the code "rs" + // but retrieved with the code "rc". Changed to always use "rc", but + // allow for "rs" when restoring values. + private static final String ATT_RESPONSE_CODE = "rc"; //$NON-NLS-1$ + private static final String ATT_RESPONSE_MESSAGE = "rm"; //$NON-NLS-1$ + private static final String ATT_RESPONSE_CODE_OLD = "rs"; //$NON-NLS-1$ + + private static final String ATT_SUCCESS = "s"; //$NON-NLS-1$ + private static final String ATT_SAMPLE_COUNT = "sc"; //$NON-NLS-1$ + private static final String ATT_TIME = "t"; //$NON-NLS-1$ + private static final String ATT_IDLETIME = "it"; //$NON-NLS-1$ + private static final String ATT_THREADNAME = "tn"; //$NON-NLS-1$ + private static final String ATT_TIME_STAMP = "ts"; //$NON-NLS-1$ + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 959055 $"; //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return SampleResult.class.equals(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) { + SampleResult res = (SampleResult) obj; + SampleSaveConfiguration save = res.getSaveConfig(); + setAttributes(writer, context, res, save); + saveAssertions(writer, context, res, save); + saveSubResults(writer, context, res, save); + saveResponseHeaders(writer, context, res, save); + saveRequestHeaders(writer, context, res, save); + saveResponseData(writer, context, res, save); + saveSamplerData(writer, context, res, save); + } + + /** + * @param writer + * @param res + * @param save + */ + protected void saveSamplerData(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveSamplerData(res)) { + writeString(writer, TAG_SAMPLER_DATA, res.getSamplerData()); + } + if (save.saveUrl()) { + final URL url = res.getURL(); + if (url != null) { + writeItem(url, context, writer); + } + } + } + + /** + * @param writer + * @param res + * @param save + */ + protected void saveResponseData(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveResponseData(res)) { + writer.startNode(TAG_RESPONSE_DATA); + writer.addAttribute(ATT_CLASS, JAVA_LANG_STRING); + try { + if (SampleResult.TEXT.equals(res.getDataType())){ + writer.setValue(new String(res.getResponseData(), res.getDataEncodingWithDefault())); + } + // Otherwise don't save anything - no point + } catch (UnsupportedEncodingException e) { + writer.setValue("Unsupported encoding in response data, can't record."); + } + writer.endNode(); + } + if (save.saveFileName()){ + writer.startNode(TAG_RESPONSE_FILE); + writer.addAttribute(ATT_CLASS, JAVA_LANG_STRING); + writer.setValue(res.getResultFileName()); + writer.endNode(); + } + } + + /** + * @param writer + * @param res + * @param save + */ + protected void saveRequestHeaders(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveRequestHeaders()) { + writeString(writer, TAG_REQUEST_HEADER, res.getRequestHeaders()); + } + } + + /** + * @param writer + * @param res + * @param save + */ + protected void saveResponseHeaders(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveResponseHeaders()) { + writeString(writer, TAG_RESPONSE_HEADER, res.getResponseHeaders()); + } + } + + /** + * @param writer + * @param context + * @param res + * @param save + */ + protected void saveSubResults(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveSubresults()) { + SampleResult[] subResults = res.getSubResults(); + for (int i = 0; i < subResults.length; i++) { + subResults[i].setSaveConfig(save); + writeItem(subResults[i], context, writer); + } + } + } + + /** + * @param writer + * @param context + * @param res + * @param save + */ + protected void saveAssertions(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveAssertions()) { + AssertionResult[] assertionResults = res.getAssertionResults(); + for (int i = 0; i < assertionResults.length; i++) { + writeItem(assertionResults[i], context, writer); + } + } + } + + /** + * @param writer + * @param res + * @param save + */ + protected void setAttributes(HierarchicalStreamWriter writer, MarshallingContext context, SampleResult res, + SampleSaveConfiguration save) { + if (save.saveTime()) { + writer.addAttribute(ATT_TIME, Long.toString(res.getTime())); + } + if (save.saveIdleTime()) { + writer.addAttribute(ATT_IDLETIME, Long.toString(res.getIdleTime())); + } + if (save.saveLatency()) { + writer.addAttribute(ATT_LATENCY, Long.toString(res.getLatency())); + } + if (save.saveTimestamp()) { + writer.addAttribute(ATT_TIME_STAMP, Long.toString(res.getTimeStamp())); + } + if (save.saveSuccess()) { + writer.addAttribute(ATT_SUCCESS, Boolean.toString(res.isSuccessful())); + } + if (save.saveLabel()) { + writer.addAttribute(ATT_LABEL, ConversionHelp.encode(res.getSampleLabel())); + } + if (save.saveCode()) { + writer.addAttribute(ATT_RESPONSE_CODE, ConversionHelp.encode(res.getResponseCode())); + } + if (save.saveMessage()) { + writer.addAttribute(ATT_RESPONSE_MESSAGE, ConversionHelp.encode(res.getResponseMessage())); + } + if (save.saveThreadName()) { + writer.addAttribute(ATT_THREADNAME, ConversionHelp.encode(res.getThreadName())); + } + if (save.saveDataType()) { + writer.addAttribute(ATT_DATA_TYPE, ConversionHelp.encode(res.getDataType())); + } + if (save.saveEncoding()) { + writer.addAttribute(ATT_DATA_ENCODING, ConversionHelp.encode(res.getDataEncodingNoDefault())); + } + if (save.saveBytes()) { + writer.addAttribute(ATT_BYTES, String.valueOf(res.getBytes())); + } + if (save.saveSampleCount()){ + writer.addAttribute(ATT_SAMPLE_COUNT, String.valueOf(res.getSampleCount())); + writer.addAttribute(ATT_ERROR_COUNT, String.valueOf(res.getErrorCount())); + } + if (save.saveThreadCounts()){ + writer.addAttribute(ATT_GRP_THRDS, String.valueOf(res.getGroupThreads())); + writer.addAttribute(ATT_ALL_THRDS, String.valueOf(res.getAllThreads())); + } + SampleEvent event = (SampleEvent) context.get(SaveService.SAMPLE_EVENT_OBJECT); + if (event != null) { + if (save.saveHostname()){ + writer.addAttribute(ATT_HOSTNAME, event.getHostname()); + } + for (int i = 0; i < SampleEvent.getVarCount(); i++){ + writer.addAttribute(SampleEvent.getVarName(i), ConversionHelp.encode(event.getVarValue(i))); + } + } + } + + /** + * @param writer + * @param tag + * @param value + */ + protected void writeString(HierarchicalStreamWriter writer, String tag, String value) { + if (value != null) { + writer.startNode(tag); + writer.addAttribute(ATT_CLASS, JAVA_LANG_STRING); + writer.setValue(value); + writer.endNode(); + } + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + SampleResult res = (SampleResult) createCollection(context.getRequiredType()); + retrieveAttributes(reader, context, res); + while (reader.hasMoreChildren()) { + reader.moveDown(); + Object subItem = readItem(reader, context, res); + retrieveItem(reader, context, res, subItem); + reader.moveUp(); + } + + // If we have a file, but no data, then read the file + String resultFileName = res.getResultFileName(); + if (resultFileName.length()>0 + && res.getResponseData().length == 0) { + readFile(resultFileName,res); + } + return res; + } + + /** + * + * @param reader + * @param context + * @param res + * @return true if the item was processed (for HTTPResultConverter) + */ + protected boolean retrieveItem(HierarchicalStreamReader reader, UnmarshallingContext context, SampleResult res, + Object subItem) { + String nodeName = reader.getNodeName(); + if (subItem instanceof AssertionResult) { + res.addAssertionResult((AssertionResult) subItem); + } else if (subItem instanceof SampleResult) { + res.storeSubResult((SampleResult) subItem); + } else if (nodeName.equals(TAG_RESPONSE_HEADER)) { + res.setResponseHeaders((String) subItem); + } else if (nodeName.equals(TAG_REQUEST_HEADER)) { + res.setRequestHeaders((String) subItem); + } else if (nodeName.equals(TAG_RESPONSE_DATA)) { + final String responseData = (String) subItem; + if (responseData.length() > 0) { + final String dataEncoding = res.getDataEncodingWithDefault(); + try { + res.setResponseData(responseData.getBytes(dataEncoding)); + } catch (UnsupportedEncodingException e) { + res.setResponseData(("Can't support the char set: " + dataEncoding), null); + res.setDataType(SampleResult.TEXT); + } + } + } else if (nodeName.equals(TAG_SAMPLER_DATA)) { + res.setSamplerData((String) subItem); + } else if (nodeName.equals(TAG_RESPONSE_FILE)) { + res.setResultFileName((String) subItem); + // Don't try restoring the URL TODO: wy not? + } else { + return false; + } + return true; + } + + /** + * @param reader + * @param res + */ + protected void retrieveAttributes(HierarchicalStreamReader reader, UnmarshallingContext context, SampleResult res) { + res.setSampleLabel(ConversionHelp.decode(reader.getAttribute(ATT_LABEL))); + res.setDataEncoding(ConversionHelp.decode(reader.getAttribute(ATT_DATA_ENCODING))); + res.setDataType(ConversionHelp.decode(reader.getAttribute(ATT_DATA_TYPE))); + String oldrc=reader.getAttribute(ATT_RESPONSE_CODE_OLD); + if (oldrc!=null) { + res.setResponseCode(ConversionHelp.decode(oldrc)); + } else { + res.setResponseCode(ConversionHelp.decode(reader.getAttribute(ATT_RESPONSE_CODE))); + } + res.setResponseMessage(ConversionHelp.decode(reader.getAttribute(ATT_RESPONSE_MESSAGE))); + res.setSuccessful(Converter.getBoolean(reader.getAttribute(ATT_SUCCESS), true)); + res.setThreadName(ConversionHelp.decode(reader.getAttribute(ATT_THREADNAME))); + res.setStampAndTime(Converter.getLong(reader.getAttribute(ATT_TIME_STAMP)), + Converter.getLong(reader.getAttribute(ATT_TIME))); + res.setIdleTime(Converter.getLong(reader.getAttribute(ATT_IDLETIME))); + res.setLatency(Converter.getLong(reader.getAttribute(ATT_LATENCY))); + res.setBytes(Converter.getInt(reader.getAttribute(ATT_BYTES))); + res.setSampleCount(Converter.getInt(reader.getAttribute(ATT_SAMPLE_COUNT),1)); // default is 1 + res.setErrorCount(Converter.getInt(reader.getAttribute(ATT_ERROR_COUNT),0)); // default is 0 + res.setGroupThreads(Converter.getInt(reader.getAttribute(ATT_GRP_THRDS))); + res.setAllThreads(Converter.getInt(reader.getAttribute(ATT_ALL_THRDS))); + } + + protected void readFile(String resultFileName, SampleResult res) { + File in = null; + FileInputStream fis = null; + try { + in = new File(resultFileName); + fis = new FileInputStream(in); + ByteArrayOutputStream outstream = new ByteArrayOutputStream(res.getBytes()); + byte[] buffer = new byte[4096]; + int len; + while ((len = fis.read(buffer)) > 0) { + outstream.write(buffer, 0, len); + } + outstream.close(); + res.setResponseData(outstream.toByteArray()); + } catch (FileNotFoundException e) { + log.warn(e.getLocalizedMessage()); + } catch (IOException e) { + log.warn(e.getLocalizedMessage()); + } finally { + IOUtils.closeQuietly(fis); + } + } + + + /** + * @param arg0 + */ + public SampleResultConverter(Mapper arg0) { + super(arg0); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/SampleSaveConfigurationConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/SampleSaveConfigurationConverter.java new file mode 100644 index 0000000..aa10081 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/SampleSaveConfigurationConverter.java @@ -0,0 +1,168 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.samplers.SampleSaveConfiguration; + +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; +import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; +import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; +import com.thoughtworks.xstream.core.JVM; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.mapper.MapperWrapper; + +/* + * Allow new fields to be added to the SampleSaveConfiguration without + * changing the output JMX file unless it is necessary. + * + * TODO work out how to make shouldSerializeMember() conditionally return true. + */ +public class SampleSaveConfigurationConverter extends ReflectionConverter { + + private static final ReflectionProvider rp; + + static { + ReflectionProvider tmp; + try { + tmp = new JVM().bestReflectionProvider(); + } catch (NullPointerException e) {// Bug in above method + tmp = new PureJavaReflectionProvider(); + } + rp = tmp; + } + + private static final String TRUE = "true"; // $NON-NLS-1$ + + // N.B. These must agree with the new member names in SampleSaveConfiguration + private static final String NODE_FILENAME = "fileName"; // $NON-NLS-1$ + private static final String NODE_HOSTNAME = "hostname"; // $NON-NLS-1$ + private static final String NODE_URL = "url"; // $NON-NLS-1$ + private static final String NODE_BYTES = "bytes"; // $NON-NLS-1$ + private static final String NODE_THREAD_COUNT = "threadCounts"; // $NON-NLS-1$ + private static final String NODE_SAMPLE_COUNT = "sampleCount"; // $NON-NLS-1$ + private static final String NODE_IDLE_TIME = "idleTime"; // $NON-NLS-1$ + + // Additional member names which are currently not written out + private static final String NODE_DELIMITER = "delimiter"; // $NON-NLS-1$ + private static final String NODE_PRINTMS = "printMilliseconds"; // $NON-NLS-1$ + + + static class MyWrapper extends MapperWrapper{ + + public MyWrapper(Mapper wrapped) { + super(wrapped); + } + + /** {@inheritDoc} */ + @Override + public boolean shouldSerializeMember( + @SuppressWarnings("rawtypes") // superclass does not use types + Class definedIn, + String fieldName) { + if (SampleSaveConfiguration.class != definedIn) { return true; } + // These are new fields; not saved unless true + if (fieldName.equals(NODE_BYTES)) { return false; } + if (fieldName.equals(NODE_URL)) { return false; } + if (fieldName.equals(NODE_FILENAME)) { return false; } + if (fieldName.equals(NODE_HOSTNAME)) { return false; } + if (fieldName.equals(NODE_THREAD_COUNT)) { return false; } + if (fieldName.equals(NODE_SAMPLE_COUNT)) { return false; } + if (fieldName.equals(NODE_IDLE_TIME)) { return false; } + + // These fields are not currently saved or restored + if (fieldName.equals(NODE_DELIMITER)) { return false; } + if (fieldName.equals(NODE_PRINTMS)) { return false; } + return true; + } + } + + public SampleSaveConfigurationConverter(Mapper arg0) { + super(new MyWrapper(arg0),rp); + } + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 959055 $"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { + return arg0.equals(SampleSaveConfiguration.class); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) { + super.marshal(obj, writer, context); // Save most things + + SampleSaveConfiguration prop = (SampleSaveConfiguration) obj; + + // Save the new fields - but only if they are not the default + createNode(writer,prop.saveBytes(),NODE_BYTES); + createNode(writer,prop.saveUrl(),NODE_URL); + createNode(writer,prop.saveFileName(),NODE_FILENAME); + createNode(writer,prop.saveHostname(),NODE_HOSTNAME); + createNode(writer,prop.saveThreadCounts(),NODE_THREAD_COUNT); + createNode(writer,prop.saveSampleCount(),NODE_SAMPLE_COUNT); + createNode(writer,prop.saveIdleTime(),NODE_IDLE_TIME); + } + + // Helper method to simplify marshall routine + private void createNode(HierarchicalStreamWriter writer, boolean save, String node) { + if (!save) { + return; + } + writer.startNode(node); + writer.setValue(TRUE); + writer.endNode(); + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final Class thisClass = SampleSaveConfiguration.class; + final Class requiredType = context.getRequiredType(); + if (requiredType != thisClass) { + throw new IllegalArgumentException("Unexpected class: "+requiredType.getName()); + } + SampleSaveConfiguration result = new SampleSaveConfiguration(); + result.setBytes(false); // Maintain backward compatibility (bytes was not in the JMX file) + while (reader.hasMoreChildren()) { + reader.moveDown(); + String nn = reader.getNodeName(); + if (!"formatter".equals(nn)){// Skip formatter (if present) bug 42674 $NON-NLS-1$ + String fieldName = mapper.realMember(thisClass, nn); + java.lang.reflect.Field field = reflectionProvider.getField(thisClass,fieldName); + Class type = field.getType(); + Object value = unmarshallField(context, result, type, field); + reflectionProvider.writeField(result, nn, value, thisClass); + } + reader.moveUp(); + } + return result; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/StringPropertyConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/StringPropertyConverter.java new file mode 100644 index 0000000..188ef8f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/StringPropertyConverter.java @@ -0,0 +1,61 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.testelement.property.StringProperty; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class StringPropertyConverter implements Converter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 1232550 $"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return StringProperty.class.equals(arg0); + } + + /** {@inheritDoc} */ + public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext arg2) { + StringProperty prop = (StringProperty) obj; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + writer.setValue(ConversionHelp.encode(prop.getStringValue())); + } + + /** {@inheritDoc} */ + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + final String name = ConversionHelp.getPropertyName(reader, context); + if (name == null) { + return null; + } + final String value = ConversionHelp.getPropertyValue(reader, context, name); + StringProperty prop = new StringProperty(name, value); + return prop; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/TestElementConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/TestElementConverter.java new file mode 100644 index 0000000..a6956b1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/TestElementConverter.java @@ -0,0 +1,129 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.util.NameUpdater; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class TestElementConverter extends AbstractCollectionConverter { + private static final Logger log = LoggingManager.getLoggerForClass(); + + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 1232554 $"; //$NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return TestElement.class.isAssignableFrom(arg0); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object arg0, HierarchicalStreamWriter writer, MarshallingContext context) { + TestElement el = (TestElement) arg0; + if (SaveService.IS_TESTPLAN_FORMAT_22){ + ConversionHelp.saveSpecialProperties(el,writer); + } + PropertyIterator iter = el.propertyIterator(); + while (iter.hasNext()) { + JMeterProperty jmp=iter.next(); + // Skip special properties if required + if (!SaveService.IS_TESTPLAN_FORMAT_22 || !ConversionHelp.isSpecialProperty(jmp.getName())) { + // Don't save empty comments - except for the TestPlan (to maintain compatibility) + if (!( + TestElement.COMMENTS.equals(jmp.getName()) + && jmp.getStringValue().length()==0 + && !el.getClass().equals(TestPlan.class) + )) + { + writeItem(jmp, context, writer); + } + } + } + } + + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + String classAttribute = reader.getAttribute(ConversionHelp.ATT_CLASS); + Class type; + if (classAttribute == null) { + type = mapper().realClass(reader.getNodeName()); + } else { + type = mapper().realClass(classAttribute); + } + // Update the test class name if necessary (Bug 52466) + String inputName = type.getName(); + String targetName = inputName; + String guiClassName = SaveService.aliasToClass(reader.getAttribute(ConversionHelp.ATT_TE_GUICLASS)); + targetName = NameUpdater.getCurrentTestName(inputName, guiClassName); + if (!targetName.equals(inputName)) { // remap the class name + type = mapper().realClass(targetName); + } + context.put(SaveService.TEST_CLASS_NAME, targetName); // needed by property converters (Bug 52466) + try { + TestElement el = (TestElement) type.newInstance(); + // No need to check version, just process the attributes if present + ConversionHelp.restoreSpecialProperties(el, reader); + // Slight hack - we need to ensure the TestClass is not reset by the previous call + el.setProperty(TestElement.TEST_CLASS, targetName); + while (reader.hasMoreChildren()) { + reader.moveDown(); + JMeterProperty prop = (JMeterProperty) readItem(reader, context, el); + if (prop != null) { // could be null if it has been deleted via NameUpdater + el.setProperty(prop); + } + reader.moveUp(); + } + return el; + } catch (InstantiationException e) { + log.error("TestElement not instantiable: " + type, e); + return null; + } catch (IllegalAccessException e) { + log.error("TestElement not instantiable: " + type, e); + return null; + } + } + + /** + * @param arg0 + */ + public TestElementConverter(Mapper arg0) { + super(arg0); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/TestElementPropertyConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/TestElementPropertyConverter.java new file mode 100644 index 0000000..eb79842 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/TestElementPropertyConverter.java @@ -0,0 +1,144 @@ +/* + * 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.save.converters; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class TestElementPropertyConverter extends AbstractCollectionConverter { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String HEADER_CLASSNAME + = "org.apache.jmeter.protocol.http.control.Header"; // $NON-NLS-1$ + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 1232555 $"; // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return arg0.equals(TestElementProperty.class); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object arg0, HierarchicalStreamWriter writer, MarshallingContext context) { + TestElementProperty prop = (TestElementProperty) arg0; + writer.addAttribute(ConversionHelp.ATT_NAME, ConversionHelp.encode(prop.getName())); + Class clazz = prop.getObjectValue().getClass(); + writer.addAttribute(ConversionHelp.ATT_ELEMENT_TYPE, + SaveService.IS_TESTPLAN_FORMAT_22 ? mapper().serializedClass(clazz) : clazz.getName()); + if (SaveService.IS_TESTPLAN_FORMAT_22){ + TestElement te = (TestElement)prop.getObjectValue(); + ConversionHelp.saveSpecialProperties(te,writer); + } + PropertyIterator iter = prop.iterator(); + while (iter.hasNext()) { + JMeterProperty jmp=iter.next(); + // Skip special properties if required + if (!SaveService.IS_TESTPLAN_FORMAT_22 || !ConversionHelp.isSpecialProperty(jmp.getName())) + { + // Don't save empty comments + if (!(TestElement.COMMENTS.equals(jmp.getName()) + && jmp.getStringValue().length()==0)) + { + writeItem(jmp, context, writer); + } + } + } + //TODO clazz is probably always the same as testclass + } + + /* + * TODO - convert to work more like upgrade.properties/NameUpdater.java + * + * Special processing is carried out for the Header Class The String + * property TestElement.name is converted to Header.name for example: + * Mozilla%2F4.0+%28compatible%3B+MSIE+5.5%3B+Windows+98%29 + * User-Agent + * becomes Mozilla%2F4.0+%28compatible%3B+MSIE+5.5%3B+Windows+98%29 + * User-Agent + */ + /** {@inheritDoc} */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + try { + TestElementProperty prop = (TestElementProperty) createCollection(context.getRequiredType()); + prop.setName(ConversionHelp.decode(reader.getAttribute(ConversionHelp.ATT_NAME))); + String element = reader.getAttribute(ConversionHelp.ATT_ELEMENT_TYPE); + boolean isHeader = HEADER_CLASSNAME.equals(element); + prop.setObjectValue(mapper().realClass(element).newInstance());// Always decode + TestElement te = (TestElement)prop.getObjectValue(); + // No need to check version, just process the attributes if present + ConversionHelp.restoreSpecialProperties(te, reader); + while (reader.hasMoreChildren()) { + reader.moveDown(); + JMeterProperty subProp = (JMeterProperty) readItem(reader, context, prop); + if (subProp != null) { // could be null if it has been deleted via NameUpdater + if (isHeader) { + String name = subProp.getName(); + if (TestElement.NAME.equals(name)) { + subProp.setName("Header.name");// $NON-NLS-1$ + // Must be same as Header.HNAME - but that is built + // later + } + } + prop.addProperty(subProp); + } + reader.moveUp(); + } + return prop; + } catch (InstantiationException e) { + log.error("Couldn't unmarshall TestElementProperty", e); + return new TestElementProperty("ERROR", new ConfigTestElement());// $NON-NLS-1$ + } catch (IllegalAccessException e) { + log.error("Couldn't unmarshall TestElementProperty", e); + return new TestElementProperty("ERROR", new ConfigTestElement());// $NON-NLS-1$ + } + } + + /** + * @param arg0 + */ + public TestElementPropertyConverter(Mapper arg0) { + super(arg0); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/save/converters/TestResultWrapperConverter.java b/ApacheJmeter/src/org/apache/jmeter/save/converters/TestResultWrapperConverter.java new file mode 100644 index 0000000..95662ef --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/save/converters/TestResultWrapperConverter.java @@ -0,0 +1,107 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 7, 2004 + */ +package org.apache.jmeter.save.converters; + +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.jmeter.reporters.ResultCollectorHelper; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.save.TestResultWrapper; + +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.mapper.Mapper; + +/** + * XStream Class to convert TestResultWrapper + * + */ +public class TestResultWrapperConverter extends AbstractCollectionConverter { + + /** + * Returns the converter version; used to check for possible + * incompatibilities + */ + public static String getVersion() { + return "$Revision: 959055 $"; //$NON-NLS-1$ + } + + /** + * @param arg0 + */ + public TestResultWrapperConverter(Mapper arg0) { + super(arg0); + } + + /** {@inheritDoc} */ + @Override + public boolean canConvert(@SuppressWarnings("rawtypes") Class arg0) { // superclass does not use types + return arg0.equals(TestResultWrapper.class); + } + + /** {@inheritDoc} */ + @Override + public void marshal(Object arg0, HierarchicalStreamWriter arg1, MarshallingContext arg2) { + // Not used, as the element is generated by the + // ResultCollector class + } + + /** + * Read test results from JTL files and pass them to the visualiser directly. + * If the ResultCollector helper object is defined, then pass the samples to that + * rather than adding them to the test result wrapper. + * + * @return the test result wrapper (may be empty) + * + * @see com.thoughtworks.xstream.converters.Converter#unmarshal(com.thoughtworks.xstream.io.HierarchicalStreamReader, + * com.thoughtworks.xstream.converters.UnmarshallingContext) + */ + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + TestResultWrapper results = new TestResultWrapper(); + Collection samples = new ArrayList(); + String ver = reader.getAttribute("version"); //$NON-NLS-1$ + if (ver == null || ver.length() == 0) { + ver = "1.0"; //$NON-NLS-1$ + } + results.setVersion(ver); + ConversionHelp.setInVersion(ver);// Make sure decoding follows input file + final ResultCollectorHelper resultCollectorHelper = (ResultCollectorHelper) context.get(SaveService.RESULTCOLLECTOR_HELPER_OBJECT); + while (reader.hasMoreChildren()) { + reader.moveDown(); + SampleResult sample = (SampleResult) readItem(reader, context, results); + if (resultCollectorHelper != null) { + resultCollectorHelper.add(sample); + } else { + samples.add(sample); + } + reader.moveUp(); + } + results.setSampleResults(samples); + return results; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/services/FileServer.java b/ApacheJmeter/src/org/apache/jmeter/services/FileServer.java new file mode 100644 index 0000000..ad80589 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/services/FileServer.java @@ -0,0 +1,478 @@ +/* + * 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. + * + */ + +/* + * Created on Oct 19, 2004 + */ +package org.apache.jmeter.services; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import org.apache.commons.collections.ArrayStack; +import org.apache.jmeter.gui.JMeterFileFilter; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * + * The point of this class is to provide thread-safe access to files, and to + * provide some simplifying assumptions about where to find files and how to + * name them. For instance, putting supporting files in the same directory as + * the saved test plan file allows users to refer to the file with just it's + * name - this FileServer class will find the file without a problem. + * Eventually, I want all in-test file access to be done through here, with the + * goal of packaging up entire test plans as a directory structure that can be + * sent via rmi to remote servers (currently, one must make sure the remote + * server has all support files in a relative-same location) and to package up + * test plans to execute on unknown boxes that only have Java installed. + */ +public class FileServer { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * The default base used for resolving relative files, i.e.
+ * {@code System.getProperty("user.dir")} + */ + private static final String DEFAULT_BASE = System.getProperty("user.dir");// $NON-NLS-1$ + + /** Default base prefix: {@value} */ + private static final String BASE_PREFIX_DEFAULT = "~/"; // $NON-NLS-1$ + + private static final String BASE_PREFIX = + JMeterUtils.getPropDefault("jmeter.save.saveservice.base_prefix", // $NON-NLS-1$ + BASE_PREFIX_DEFAULT); + + //@GuardedBy("this") + private File base; + + //@GuardedBy("this") NOTE this also guards against possible window in checkForOpenFiles() + private final Map files = new HashMap(); + + private static final FileServer server = new FileServer(); + + private final Random random = new Random(); + + private String scriptName; + + // Cannot be instantiated + private FileServer() { + base = new File(DEFAULT_BASE); + log.info("Default base='"+DEFAULT_BASE+"'"); + } + + /** + * @return the singleton instance of the server. + */ + public static FileServer getFileServer() { + return server; + } + + /** + * Resets the current base to {@link #DEFAULT_BASE}. + */ + public synchronized void resetBase() { + base = new File(DEFAULT_BASE); + log.info("Reset base to'"+base+"'"); + } + + /** + * Sets the current base directory for relative file names from the provided path. + * If the path does not refer to an existing directory, then its parent is used. + * Normally the provided path is a file, so using the parent directory is appropriate. + * + * @param basedir the path to set, or {@code null} if the GUI is being cleared + * @throws IllegalStateException if files are still open + */ + public synchronized void setBasedir(String basedir) { + checkForOpenFiles(); + if (basedir != null) { + base = new File(basedir); + if (!base.isDirectory()) { + base = base.getParentFile(); + } + log.info("Set new base='"+base+"'"); + } + } + + /** + * Sets the current base directory for relative file names from the provided script file. + * The parameter is assumed to be the path to a JMX file, so the base directory is derived + * from its parent. + * + * @param scriptPath the path of the script file; must be not be {@code null} + * @throws IllegalStateException if files are still open + * @throws IllegalArgumentException if scriptPath parameter is null + */ + public synchronized void setBaseForScript(File scriptPath) { + if (scriptPath == null){ + throw new IllegalArgumentException("scriptPath must not be null"); + } + setScriptName(scriptPath.getName()); + // getParentFile() may not work on relative paths + setBase(scriptPath.getAbsoluteFile().getParentFile()); + } + + /** + * Sets the current base directory for relative file names. + * + * @param jmxBase the path of the script file base directory, cannot be null + * @throws IllegalStateException if files are still open + * @throws IllegalArgumentException if {@code basepath} is null + */ + public synchronized void setBase(File jmxBase) { + if (jmxBase == null) { + throw new IllegalArgumentException("jmxBase must not be null"); + } + checkForOpenFiles(); + base = jmxBase; + log.info("Set new base='"+base+"'"); + } + + /** + * Check if there are entries in use. + *

+ * + * @throws IllegalStateException if there are any entries still in use + */ + private void checkForOpenFiles() throws IllegalStateException { + if (filesOpen()) { // checks for entries in use + throw new IllegalStateException("Files are still open, cannot change base directory"); + } + files.clear(); // tidy up any unused entries + } + + public synchronized String getBaseDir() { + return base.getAbsolutePath(); + } + + public static String getDefaultBase(){ + return DEFAULT_BASE; + } + + /** + * Calculates the relative path from {@link #DEFAULT_BASE} to the current base, + * which must be the same as or a child of the default. + * + * @return the relative path, or {@code "."} if the path cannot be determined + */ + public synchronized File getBaseDirRelative() { + // Must first convert to absolute path names to ensure parents are available + File parent = new File(DEFAULT_BASE).getAbsoluteFile(); + File f = base.getAbsoluteFile(); + ArrayStack l = new ArrayStack(); + while (f != null) { + if (f.equals(parent)){ + if (l.isEmpty()){ + break; + } + File rel = new File((String) l.pop()); + while(!l.isEmpty()) { + rel = new File(rel, (String) l.pop()); + } + return rel; + } + l.push(f.getName()); + f = f.getParentFile(); + } + return new File("."); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + */ + public synchronized void reserveFile(String filename) { + reserveFile(filename,null); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + * @param charsetName - the character set encoding to use for the file (may be null) + */ + public synchronized void reserveFile(String filename, String charsetName) { + reserveFile(filename, charsetName, filename, false); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + * @param charsetName - the character set encoding to use for the file (may be null) + * @param alias - the name to be used to access the object (must not be null) + */ + public synchronized void reserveFile(String filename, String charsetName, String alias) { + reserveFile(filename, charsetName, alias, false); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + * @param charsetName - the character set encoding to use for the file (may be null) + * @param alias - the name to be used to access the object (must not be null) + * @param hasHeader true if the file has a header line describing the contents + * @return the header line; may be null + */ + public synchronized String reserveFile(String filename, String charsetName, String alias, boolean hasHeader) { + if (filename == null){ + throw new IllegalArgumentException("Filename must not be null"); + } + if (alias == null){ + throw new IllegalArgumentException("Alias must not be null"); + } + FileEntry fileEntry = files.get(alias); + if (fileEntry == null) { + File f = new File(filename); + fileEntry = + new FileEntry(f.isAbsolute() ? f : new File(base, filename),null,charsetName); + if (filename.equals(alias)){ + log.info("Stored: "+filename); + } else { + log.info("Stored: "+filename+" Alias: "+alias); + } + files.put(alias, fileEntry); + if (hasHeader){ + try { + fileEntry.headerLine=readLine(alias, false); + } catch (IOException e) { + throw new IllegalArgumentException("Could not read file header line",e); + } + } + } + return fileEntry.headerLine; + } + + /** + * Get the next line of the named file, recycle by default. + * + * @param filename + * @return String containing the next line in the file + * @throws IOException + */ + public String readLine(String filename) throws IOException { + return readLine(filename, true); + } + + /** + * Get the next line of the named file, first line is name to false + * + * @param filename + * @param recycle - should file be restarted at EOF? + * @return String containing the next line in the file (null if EOF reached and not recycle) + * @throws IOException + */ + public String readLine(String filename, boolean recycle) throws IOException { + return readLine(filename, recycle, false); + } + /** + * Get the next line of the named file. + * + * @param filename + * @param recycle - should file be restarted at EOF? + * @param firstLineIsNames - 1st line is fields names + * @return String containing the next line in the file (null if EOF reached and not recycle) + * @throws IOException + */ + public synchronized String readLine(String filename, boolean recycle, + boolean firstLineIsNames) throws IOException { + FileEntry fileEntry = files.get(filename); + if (fileEntry != null) { + if (fileEntry.inputOutputObject == null) { + fileEntry.inputOutputObject = createBufferedReader(fileEntry, filename); + } else if (!(fileEntry.inputOutputObject instanceof Reader)) { + throw new IOException("File " + filename + " already in use"); + } + BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject; + String line = reader.readLine(); + if (line == null && recycle) { + reader.close(); + reader = createBufferedReader(fileEntry, filename); + fileEntry.inputOutputObject = reader; + if (firstLineIsNames) { + // read first line and forget + reader.readLine(); + } + line = reader.readLine(); + } + if (log.isDebugEnabled()) { log.debug("Read:"+line); } + return line; + } + throw new IOException("File never reserved: "+filename); + } + + private BufferedReader createBufferedReader(FileEntry fileEntry, String filename) throws IOException { + FileInputStream fis = new FileInputStream(fileEntry.file); + InputStreamReader isr = null; + // If file encoding is specified, read using that encoding, otherwise use default platform encoding + String charsetName = fileEntry.charSetEncoding; + if(charsetName != null && charsetName.trim().length() > 0) { + isr = new InputStreamReader(fis, charsetName); + } else { + isr = new InputStreamReader(fis); + } + return new BufferedReader(isr); + } + + public synchronized void write(String filename, String value) throws IOException { + FileEntry fileEntry = files.get(filename); + if (fileEntry != null) { + if (fileEntry.inputOutputObject == null) { + fileEntry.inputOutputObject = createBufferedWriter(fileEntry, filename); + } else if (!(fileEntry.inputOutputObject instanceof Writer)) { + throw new IOException("File " + filename + " already in use"); + } + BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject; + if (log.isDebugEnabled()) { log.debug("Write:"+value); } + writer.write(value); + } else { + throw new IOException("File never reserved: "+filename); + } + } + + private BufferedWriter createBufferedWriter(FileEntry fileEntry, String filename) throws IOException { + FileOutputStream fos = new FileOutputStream(fileEntry.file); + OutputStreamWriter osw = null; + // If file encoding is specified, write using that encoding, otherwise use default platform encoding + String charsetName = fileEntry.charSetEncoding; + if(charsetName != null && charsetName.trim().length() > 0) { + osw = new OutputStreamWriter(fos, charsetName); + } else { + osw = new OutputStreamWriter(fos); + } + return new BufferedWriter(osw); + } + + public synchronized void closeFiles() throws IOException { + for (Map.Entry me : files.entrySet()) { + closeFile(me.getKey(),me.getValue() ); + } + files.clear(); + } + + /** + * @param name + * @throws IOException + */ + public synchronized void closeFile(String name) throws IOException { + FileEntry fileEntry = files.get(name); + closeFile(name, fileEntry); + } + + private void closeFile(String name, FileEntry fileEntry) throws IOException { + if (fileEntry != null && fileEntry.inputOutputObject != null) { + log.info("Close: "+name); + fileEntry.inputOutputObject.close(); + fileEntry.inputOutputObject = null; + } + } + + boolean filesOpen() { // package access for test code only + for (FileEntry fileEntry : files.values()) { + if (fileEntry.inputOutputObject != null) { + return true; + } + } + return false; + } + + /** + * Method will get a random file in a base directory + * TODO hey, not sure this method belongs here. FileServer is for threadsafe + * File access relative to current test's base directory. + * + * @param basedir + * @return a random File from the basedir that matches one of the extensions + */ + public File getRandomFile(String basedir, String[] extensions) { + File input = null; + if (basedir != null) { + File src = new File(basedir); + if (src.isDirectory() && src.list() != null) { + File[] lfiles = src.listFiles(new JMeterFileFilter(extensions)); + int count = lfiles.length; + input = lfiles[random.nextInt(count)]; + } + } + return input; + } + + private static class FileEntry{ + private String headerLine; + private final File file; + private Closeable inputOutputObject; + private final String charSetEncoding; + FileEntry(File f, Closeable o, String e){ + file=f; + inputOutputObject=o; + charSetEncoding=e; + } + } + + /** + * Resolve a file name that may be relative to the base directory. + * If the name begins with the value of the JMeter property + * "jmeter.save.saveservice.base_prefix" + * - default "~/" - then the name is assumed to be relative to the basename. + * + * @param relativeName + * @return the updated file + */ + public static String resolveBaseRelativeName(String relativeName) { + if (relativeName.startsWith(BASE_PREFIX)){ + String newName = relativeName.substring(BASE_PREFIX.length()); + return new File(getFileServer().getBaseDir(),newName).getAbsolutePath(); + } + return relativeName; + } + + /** + * @return JMX Script name + */ + public String getScriptName() { + return scriptName; + } + + /** + * @param scriptName Script name + */ + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/swing/HtmlPane.java b/ApacheJmeter/src/org/apache/jmeter/swing/HtmlPane.java new file mode 100644 index 0000000..cbea928 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/swing/HtmlPane.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.jmeter.swing; + +import javax.swing.JTextPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements an HTML Pane with local hyperlinking enabled. + */ +public class HtmlPane extends JTextPane { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public HtmlPane() { + this.addHyperlinkListener(new HyperlinkListener() { + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + String ref = e.getURL().getRef(); + if (ref != null && ref.length() > 0) { + log.debug("reference to scroll to = " + ref); + scrollToReference(ref); + } + } + } + }); + } + + @Override + public void scrollToReference(String reference) { + super.scrollToReference(reference); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/BeanInfoSupport.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/BeanInfoSupport.java new file mode 100644 index 0000000..670afc6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/BeanInfoSupport.java @@ -0,0 +1,264 @@ +/* + * 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.testbeans; + +import java.awt.Image; +import java.beans.BeanDescriptor; +import java.beans.BeanInfo; +import java.beans.EventSetDescriptor; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.MethodDescriptor; +import java.beans.PropertyDescriptor; +import java.beans.SimpleBeanInfo; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer; +import org.apache.jmeter.testbeans.gui.TypeEditor; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Support class for test bean beanInfo objects. It will help using the + * introspector to get most of the information, to then modify it at will. + *

+ * To use, subclass it, create a subclass with a parameter-less constructor + * that: + *

    + *
  1. Calls super(beanClass) + *
  2. Modifies the property descriptors, bean descriptor, etc. at will. + *
+ *

+ * Even before any such modifications, a resource bundle named xxxResources + * (where xxx is the fully qualified bean class name) will be obtained if + * available and used to localize the following: + *

    + *
  • Bean's display name -- from property displayName. + *
  • Properties' display names -- from properties propertyName.displayName. + *
  • Properties' short descriptions -- from properties propertyName.shortDescription. + *
+ *

+ * The resource bundle will be stored as the bean descriptor's "resourceBundle" + * attribute, so that it can be used for further localization. TestBeanGUI, for + * example, uses it to obtain the group's display names from properties groupName.displayName. + * + * @version $Revision: 1206442 $ + */ +public abstract class BeanInfoSupport extends SimpleBeanInfo { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Some known attribute names, just for convenience: + public static final String TAGS = GenericTestBeanCustomizer.TAGS; + + public static final String NOT_UNDEFINED = GenericTestBeanCustomizer.NOT_UNDEFINED; + + public static final String NOT_EXPRESSION = GenericTestBeanCustomizer.NOT_EXPRESSION; + + public static final String NOT_OTHER = GenericTestBeanCustomizer.NOT_OTHER; + + public static final String MULTILINE = "multiline"; + + public static final String DEFAULT = GenericTestBeanCustomizer.DEFAULT; + + public static final String RESOURCE_BUNDLE = GenericTestBeanCustomizer.RESOURCE_BUNDLE; + + /** The BeanInfo for our class as obtained by the introspector. */ + private final BeanInfo rootBeanInfo; + + /** The descriptor for our class */ + private final BeanDescriptor beanDescriptor; + + /** The icons for this bean. */ + private final Image[] icons = new Image[5]; + + /** The class for which we're providing the bean info. */ + private final Class beanClass; + + /** + * Construct a BeanInfo for the given class. + */ + protected BeanInfoSupport(Class beanClass) { + this.beanClass= beanClass; + + try { + rootBeanInfo = Introspector.getBeanInfo(beanClass, Introspector.IGNORE_IMMEDIATE_BEANINFO); + } catch (IntrospectionException e) { + throw new Error("Can't introspect "+beanClass, e); // Programming error: bail out. + } + + // N.B. JVMs other than Sun may return different instances each time + // so we cache the value here (and avoid having to fetch it every time) + beanDescriptor = rootBeanInfo.getBeanDescriptor(); + + try { + ResourceBundle resourceBundle = ResourceBundle.getBundle( + beanClass.getName() + "Resources", // $NON-NLS-1$ + JMeterUtils.getLocale()); + + // Store the resource bundle as an attribute of the BeanDescriptor: + getBeanDescriptor().setValue(RESOURCE_BUNDLE, resourceBundle); + // Localize the bean name + try { + getBeanDescriptor().setDisplayName(resourceBundle.getString("displayName")); // $NON-NLS-1$ + } catch (MissingResourceException e) { + log.debug("Localized display name not available for bean " + beanClass); + } + // Localize the property names and descriptions: + PropertyDescriptor[] properties = getPropertyDescriptors(); + for (int i = 0; i < properties.length; i++) { + String name = properties[i].getName(); + try { + properties[i].setDisplayName(resourceBundle.getString(name + ".displayName")); // $NON-NLS-1$ + } catch (MissingResourceException e) { + log.debug("Localized display name not available for property " + name + " in " + beanClass); + } + + try { + properties[i].setShortDescription(resourceBundle.getString(name + ".shortDescription")); + } catch (MissingResourceException e) { + log.debug("Localized short description not available for property " + name + " in " + beanClass); + } + } + } catch (MissingResourceException e) { + log.warn("Localized strings not available for bean " + beanClass, e); + } catch (Exception e) { + log.warn("Something bad happened when loading bean info for bean " + beanClass, e); + } + } + + /** + * Get the property descriptor for the property of the given name. + * + * @param name + * property name + * @return descriptor for a property of that name, or null if there's none + */ + protected PropertyDescriptor property(String name) { + for (PropertyDescriptor propdesc : getPropertyDescriptors()) { + if (propdesc.getName().equals(name)) { + return propdesc; + } + } + log.error("Cannot find property: " + name + " in class " + beanClass); + return null; + } + + /** + * Get the property descriptor for the property of the given name. + * + * @param name + * property name + * @return descriptor for a property of that name, or null if there's none + */ + protected PropertyDescriptor property(String name, TypeEditor editor) { + PropertyDescriptor property = property(name); + if (property != null) { + property.setValue(GenericTestBeanCustomizer.GUITYPE, editor); + } + return property; + } + + /** + * Set the bean's 16x16 colour icon. + * + * @param resourceName + * A pathname relative to the directory holding the class file of + * the current class. + */ + protected void setIcon(String resourceName) { + icons[ICON_COLOR_16x16] = loadImage(resourceName); + } + + /** Number of groups created so far by createPropertyGroup. */ + private int numCreatedGroups = 0; + + /** + * Utility method to group and order properties. + *

+ * It will assing the given group name to each of the named properties, and + * set their order attribute so that they are shown in the given order. + *

+ * The created groups will get order 1, 2, 3,... in the order in which they + * are created. + * + * @param group + * name of the group + * @param names + * property names in the desired order + */ + protected void createPropertyGroup(String group, String[] names) { + for (int i = 0; i < names.length; i++) { // i is used below + log.debug("Getting property for: " + names[i]); + PropertyDescriptor p = property(names[i]); + p.setValue(GenericTestBeanCustomizer.GROUP, group); + p.setValue(GenericTestBeanCustomizer.ORDER, Integer.valueOf(i)); + } + numCreatedGroups++; + getBeanDescriptor().setValue(GenericTestBeanCustomizer.ORDER(group), Integer.valueOf(numCreatedGroups)); + } + + /** {@inheritDoc} */ + @Override + public BeanInfo[] getAdditionalBeanInfo() { + return rootBeanInfo.getAdditionalBeanInfo(); + } + + /** {@inheritDoc} */ + @Override + public BeanDescriptor getBeanDescriptor() { + return beanDescriptor; + } + + /** {@inheritDoc} */ + @Override + public int getDefaultEventIndex() { + return rootBeanInfo.getDefaultEventIndex(); + } + + /** {@inheritDoc} */ + @Override + public int getDefaultPropertyIndex() { + return rootBeanInfo.getDefaultPropertyIndex(); + } + + /** {@inheritDoc} */ + @Override + public EventSetDescriptor[] getEventSetDescriptors() { + return rootBeanInfo.getEventSetDescriptors(); + } + + /** {@inheritDoc} */ + @Override + public Image getIcon(int iconKind) { + return icons[iconKind]; + } + + /** {@inheritDoc} */ + @Override + public MethodDescriptor[] getMethodDescriptors() { + return rootBeanInfo.getMethodDescriptors(); + } + + /** {@inheritDoc} */ + @Override + public PropertyDescriptor[] getPropertyDescriptors() { + return rootBeanInfo.getPropertyDescriptors(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/TestBean.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/TestBean.java new file mode 100644 index 0000000..edc6969 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/TestBean.java @@ -0,0 +1,30 @@ +/* + * 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. + * + */ + +/* + * Created on May 21, 2004 + */ +package org.apache.jmeter.testbeans; + +/** + * Marker interface to tell JMeter to make a Test Bean Gui for the class. + * + */ +public interface TestBean { + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/TestBeanBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/TestBeanBeanInfo.java new file mode 100644 index 0000000..4ec503a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/TestBeanBeanInfo.java @@ -0,0 +1,96 @@ +/* + * 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.testbeans; + +import java.awt.Image; +import java.beans.BeanDescriptor; +import java.beans.BeanInfo; +import java.beans.EventSetDescriptor; +import java.beans.MethodDescriptor; +import java.beans.PropertyDescriptor; + +/** + * This is the BeanInfo object for the TestBean class. It acts as a "stopper" + * for the introspector: we don't want it to look at properties defined at this + * or higher classes. + *

+ * Note this is really needed since using Introspector.getBeanInfo with a stop + * class is not an option because: + *

    + *
  1. The API does not define a 3-parameter getBeanInfo in which you can use a + * stop class AND flags. [Why? I guess this is a bug in the spec.] + *
  2. java.beans.Introspector is buggy and, opposite to what's stated in the + * Javadocs, only results of getBeanInfo(Class) are actually cached. + *
+ * + * @version $Revision: 908219 $ + */ +public class TestBeanBeanInfo implements BeanInfo { + + public BeanInfo[] getAdditionalBeanInfo() { + return new BeanInfo[0]; + } + + /** + * {@inheritDoc} + */ + public BeanDescriptor getBeanDescriptor() { + return null; + } + + /** + * {@inheritDoc} + */ + public int getDefaultEventIndex() { + return 0; + } + + /** + * {@inheritDoc} + */ + public int getDefaultPropertyIndex() { + return 0; + } + + /** + * {@inheritDoc} + */ + public EventSetDescriptor[] getEventSetDescriptors() { + return new EventSetDescriptor[0]; + } + + /** + * {@inheritDoc} + */ + public Image getIcon(int iconKind) { + return null; + } + + /** + * {@inheritDoc} + */ + public MethodDescriptor[] getMethodDescriptors() { + return new MethodDescriptor[0]; + } + + /** + * {@inheritDoc} + */ + public PropertyDescriptor[] getPropertyDescriptors() { + return new PropertyDescriptor[0]; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/TestBeanHelper.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/TestBeanHelper.java new file mode 100644 index 0000000..a02d1a1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/TestBeanHelper.java @@ -0,0 +1,192 @@ +/* + * 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.testbeans; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.LinkedList; + +import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer; +import org.apache.jmeter.testbeans.gui.TableEditor; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.MultiProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.Converter; +import org.apache.log.Logger; + +/** + * This is an experimental class. An attempt to address the complexity of + * writing new JMeter components. + *

+ * TestBean currently extends AbstractTestElement to support + * backward-compatibility, but the property-value-map may later on be separated + * from the test beans themselves. To ensure this will be doable with minimum + * damage, all inherited methods are deprecated. + * + */ +public class TestBeanHelper { + protected static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * Prepare the bean for work by populating the bean's properties from the + * property value map. + *

+ * + * @deprecated to limit it's usage in expectation of moving it elsewhere. + */ + @Deprecated + public static void prepare(TestElement el) { + if (!(el instanceof TestBean)) { + return; + } + try { + BeanInfo beanInfo = Introspector.getBeanInfo(el.getClass()); + PropertyDescriptor[] desc = beanInfo.getPropertyDescriptors(); + Object[] param = new Object[1]; + + if (log.isDebugEnabled()) { + log.debug("Preparing " + el.getClass()); + } + + for (int x = 0; x < desc.length; x++) { + // Obtain a value of the appropriate type for this property. + JMeterProperty jprop = el.getProperty(desc[x].getName()); + Class type = desc[x].getPropertyType(); + Object value = unwrapProperty(desc[x], jprop, type); + + if (log.isDebugEnabled()) { + log.debug("Setting " + jprop.getName() + "=" + value); + } + + // Set the bean's property to the value we just obtained: + if (value != null || !type.isPrimitive()) + // We can't assign null to primitive types. + { + param[0] = value; + Method writeMethod = desc[x].getWriteMethod(); + if (writeMethod!=null) { + invokeOrBailOut(el, writeMethod, param); + } + } + } + } catch (IntrospectionException e) { + log.error("Couldn't set properties for " + el.getClass().getName(), e); + } + } + + /** + * @param desc + * @param x + * @param jprop + * @param type + * @return + */ + private static Object unwrapProperty(PropertyDescriptor desc, JMeterProperty jprop, Class type) { + Object value; + if(jprop instanceof TestElementProperty) + { + TestElement te = ((TestElementProperty)jprop).getElement(); + if(te instanceof TestBean) + { + prepare(te); + } + value = te; + } + else if(jprop instanceof MultiProperty) + { + value = unwrapCollection((MultiProperty)jprop,(String)desc.getValue(TableEditor.CLASSNAME)); + } + // value was not provided, and this is allowed + else if (jprop instanceof NullProperty && + // use negative condition so missing (null) value is treated as FALSE + ! Boolean.TRUE.equals(desc.getValue(GenericTestBeanCustomizer.NOT_UNDEFINED))) + { + value=null; + } + else value = Converter.convert(jprop.getStringValue(), type); + return value; + } + + private static Object unwrapCollection(MultiProperty prop,String type) + { + if(prop instanceof CollectionProperty) + { + Collection values = new LinkedList(); + PropertyIterator iter = prop.iterator(); + while(iter.hasNext()) + { + try + { + values.add(unwrapProperty(null,iter.next(),Class.forName(type))); + } + catch(Exception e) + { + log.error("Couldn't convert object: " + prop.getObjectValue() + " to " + type,e); + } + } + return values; + } + return null; + } + + /** + * Utility method that invokes a method and does the error handling around + * the invocation. + * + * @param method + * @param params + * @return the result of the method invocation. + */ + private static Object invokeOrBailOut(Object invokee, Method method, Object[] params) { + try { + return method.invoke(invokee, params); + } catch (IllegalArgumentException e) { + throw new Error(createMessage(invokee, method, params), e); + } catch (IllegalAccessException e) { + throw new Error(createMessage(invokee, method, params), e); + } catch (InvocationTargetException e) { + throw new Error(createMessage(invokee, method, params), e); + } + } + + private static String createMessage(Object invokee, Method method, Object[] params){ + StringBuilder sb = new StringBuilder(); + sb.append("This should never happen. Tried to invoke:\n"); + sb.append(invokee.getClass().getName()); + sb.append("#"); + sb.append(method.getName()); + sb.append("("); + for(Object o : params) { + sb.append(o.getClass().getSimpleName()); + sb.append(' '); + sb.append(o); + sb.append(' '); + } + sb.append(")"); + return sb.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/BooleanPropertyEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/BooleanPropertyEditor.java new file mode 100644 index 0000000..2db43e3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/BooleanPropertyEditor.java @@ -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. + * + */ + +package org.apache.jmeter.testbeans.gui; + +import java.beans.PropertyEditorSupport; + +/** + * Property Editor which handles Boolean properties. + */ +public class BooleanPropertyEditor extends PropertyEditorSupport { + + private static final String[] TAGS = {"True", "False"}; // $NON-NLS-1$ + + @Override + public void setAsText(String text) { + this.setValue(text); + } + + @Override + public void setValue(Object value){ + if (value instanceof String) { + super.setValue(Boolean.valueOf((String) value)); + } else if (value == null || value instanceof Boolean) { + super.setValue(value); // not sure if null is passed in but no harm in setting it + } else { + throw new java.lang.IllegalArgumentException("Unexpected type: "+value.getClass().getName()); + } + } + + @Override + public String[] getTags() { + return TAGS; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/ComboStringEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/ComboStringEditor.java new file mode 100644 index 0000000..2cbb7d5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/ComboStringEditor.java @@ -0,0 +1,301 @@ +/* + * 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.testbeans.gui; + +import java.awt.Component; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.beans.PropertyEditorSupport; +import java.util.Arrays; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.text.JTextComponent; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class implements a property editor for possibly null String properties + * that supports custom editing (i.e.: provides a GUI component) based on a + * combo box. + *

+ * The provided GUI is a combo box with: + *

    + *
  • An option for "undefined" (corresponding to the null value), unless the + * noUndefined property is set. + *
  • An option for each value in the tags property. + *
  • The possibility to write your own value, unless the noEdit + * property is set. + *
+ * + */ +class ComboStringEditor extends PropertyEditorSupport implements ItemListener { + + /** + * The list of options to be offered by this editor. + */ + private String[] tags = new String[0]; + + /** + * True iif the editor should not accept (nor produce) a null value. + */ + private boolean noUndefined = false; + + /** + * True iif the editor should not accept (nor produce) any non-null values + * different from the provided tags. + */ + private boolean noEdit = false; + + /** + * The edited property's default value. + */ + private String initialEditValue; + + private JComboBox combo; + + private DefaultComboBoxModel model; + + private boolean startingEdit = false; + + /* + * True iif we're currently processing an event triggered by the user + * selecting the "Edit" option. Used to prevent reverting the combo to + * non-editable during processing of secondary events. + */ + + // TODO - do these behave properly during language change? Probably not. + + // Needs to be visible to test cases + static final Object UNDEFINED = new UniqueObject(JMeterUtils.getResString("property_undefined")); //$NON-NLS-1$ + + private static final Object EDIT = new UniqueObject(JMeterUtils.getResString("property_edit")); //$NON-NLS-1$ + + ComboStringEditor() { + // Create the combo box we will use to edit this property: + + model = new DefaultComboBoxModel(); + model.addElement(UNDEFINED); + model.addElement(EDIT); + + combo = new JComboBox(model); + combo.addItemListener(this); + combo.setEditable(false); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean supportsCustomEditor() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Component getCustomEditor() { + return combo; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getValue() { + return getAsText(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getAsText() { + Object value = combo.getSelectedItem(); + + if (value == UNDEFINED) { + return null; + } + return (String) value; + } + + /** + * {@inheritDoc} + */ + @Override + public void setValue(Object value) { + setAsText((String) value); + } + + /** + * {@inheritDoc} + */ + @Override + public void setAsText(String value) { + combo.setEditable(true); + + if (value == null) { + combo.setSelectedItem(UNDEFINED); + } else { + combo.setSelectedItem(value); + } + + if (!startingEdit && combo.getSelectedIndex() >= 0) { + combo.setEditable(false); + } + } + + /** + * {@inheritDoc} + */ + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + if (e.getItem() == EDIT) { + startingEdit = true; + startEditing(); + startingEdit = false; + } else { + if (!startingEdit && combo.getSelectedIndex() >= 0) { + combo.setEditable(false); + } + + firePropertyChange(); + } + } + } + + private void startEditing() { + JTextComponent textField = (JTextComponent) combo.getEditor().getEditorComponent(); + + combo.setEditable(true); + + textField.requestFocus(); + + String text = initialEditValue; + if (initialEditValue == null) { + text = ""; // will revert to last valid value if invalid + } + + combo.setSelectedItem(text); + + int i = text.indexOf("${}"); + if (i != -1) { + textField.setCaretPosition(i + 2); + } else { + textField.selectAll(); + } + } + + public String getInitialEditValue() { + return initialEditValue; + } + + public boolean getNoEdit() { + return noEdit; + } + + public boolean getNoUndefined() { + return noUndefined; + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getTags() { + return tags; + } + + /** + * @param object + */ + public void setInitialEditValue(String object) { + initialEditValue = object; + } + + /** + * @param b + */ + public void setNoEdit(boolean b) { + if (noEdit == b) { + return; + } + noEdit = b; + + if (noEdit) { + model.removeElement(EDIT); + } else { + model.addElement(EDIT); + } + } + + /** + * @param b + */ + public void setNoUndefined(boolean b) { + if (noUndefined == b) { + return; + } + noUndefined = b; + + if (noUndefined) { + model.removeElement(UNDEFINED); + } else { + model.insertElementAt(UNDEFINED, 0); + } + } + + /** + * @param strings + */ + public void setTags(String[] strings) { + if (Arrays.equals(tags,strings)) { + return; + } + + for (int i = 0; i < tags.length; i++) { + model.removeElement(tags[i]); + } + + tags = strings == null ? new String[0] : strings; + + int b = noUndefined ? 0 : 1; // base index for tags + for (int i = 0; i < tags.length; i++) { + model.insertElementAt(tags[i], b + i); + } + } + + /** + * This is a funny hack: if you use a plain String, entering the text of the + * string in the editor will make the combo revert to that option -- which + * actually amounts to making that string 'reserved'. I preferred to avoid + * this by using a different type having a controlled .toString(). + */ + private static class UniqueObject { + private String s; + + UniqueObject(String s) { + this.s = s; + } + + @Override + public String toString() { + return s; + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/FieldStringEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/FieldStringEditor.java new file mode 100644 index 0000000..8c867ac --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/FieldStringEditor.java @@ -0,0 +1,132 @@ +/* + * 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.testbeans.gui; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyEditorSupport; + +import javax.swing.JTextField; + +//import org.apache.jorphan.logging.LoggingManager; +//import org.apache.log.Logger; + +/** + * This class implements a property editor for non-null String properties that + * supports custom editing (i.e.: provides a GUI component) based on a text + * field. + *

+ * The provided GUI is a simple text field. + * + */ +class FieldStringEditor extends PropertyEditorSupport implements ActionListener, FocusListener { +// private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * This will hold the text editing component, either a plain JTextField (in + * cases where the combo box would not have other options than 'Edit'), or + * the text editing component in the combo box. + */ + private JTextField textField; + + /** + * Value on which we started the editing. Used to avoid firing + * PropertyChanged events when there's not been such change. + */ + private String initialValue = ""; + + protected FieldStringEditor() { + super(); + + textField = new JTextField(); + textField.addActionListener(this); + textField.addFocusListener(this); + } + + @Override + public String getAsText() { + return textField.getText(); + } + + @Override + public void setAsText(String value) { + initialValue = value; + textField.setText(value); + } + + @Override + public Object getValue() { + return getAsText(); + } + + @Override + public void setValue(Object value) { + if (value instanceof String) { + setAsText((String) value); + } else { + throw new IllegalArgumentException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Component getCustomEditor() { + return textField; + } + + // TODO should this implement supportsCustomEditor() ? + + /** + * {@inheritDoc} + */ + @Override + public void firePropertyChange() { + String newValue = getAsText(); + + if (initialValue.equals(newValue)) { + return; + } + initialValue = newValue; + + super.firePropertyChange(); + } + + /** + * {@inheritDoc} + */ + public void actionPerformed(ActionEvent e) { + firePropertyChange(); + } + + /** + * {@inheritDoc} + */ + public void focusGained(FocusEvent e) { + } + + /** + * {@inheritDoc} + */ + public void focusLost(FocusEvent e) { + firePropertyChange(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/FileEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/FileEditor.java new file mode 100644 index 0000000..b4eb3e1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/FileEditor.java @@ -0,0 +1,222 @@ +/* + * 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.testbeans.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.IntrospectionException; +import java.beans.PropertyChangeListener; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; +import java.beans.PropertyEditorSupport; +import java.io.File; + +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JPanel; + +import org.apache.jmeter.gui.util.FileDialoger; + +/** + * A property editor for File properties. + *

+ * Note that it never gives out File objects, but always Strings. This is + * because JMeter is now too dumb to handle File objects (there's no + * FileProperty). + * + */ +public class FileEditor implements PropertyEditor, ActionListener { + + /** + * The editor's panel. + */ + private final JPanel panel; + + /** + * The editor handling the text field inside: + */ + private final PropertyEditor editor; + + /** + * @throws IntrospectionException + * @deprecated Only for use by test cases + */ + @Deprecated + public FileEditor() throws IntrospectionException { + this(new PropertyDescriptor("dummy", null, null)); + } + + public FileEditor(PropertyDescriptor descriptor) { + if (descriptor == null) { + throw new NullPointerException("Descriptor must not be null"); + } + + // Create a button to trigger the file chooser: + JButton button = new JButton("Browse..."); + button.addActionListener(this); + + // Get a WrapperEditor to provide the field or combo -- we'll delegate + // most methods to it: + boolean notNull = GenericTestBeanCustomizer.notNull(descriptor); + boolean notExpression = GenericTestBeanCustomizer.notExpression(descriptor); + boolean notOther = GenericTestBeanCustomizer.notOther(descriptor); + Object defaultValue = descriptor.getValue(GenericTestBeanCustomizer.DEFAULT); + ComboStringEditor cse = new ComboStringEditor(); + cse.setNoUndefined(notNull); + cse.setNoEdit(notExpression && notOther); + editor = new WrapperEditor(this, new SimpleFileEditor(), cse, + !notNull, // acceptsNull + !notExpression, // acceptsExpressions + !notOther, // acceptsOther + defaultValue); // default + + // Create a panel containing the combo and the button: + panel = new JPanel(new BorderLayout(5, 0)); + panel.add(editor.getCustomEditor(), BorderLayout.CENTER); + panel.add(button, BorderLayout.EAST); + } + + /** + * {@inheritDoc} + */ + public void actionPerformed(ActionEvent e) { + JFileChooser chooser = FileDialoger.promptToOpenFile(); + + if (chooser == null){ + return; + } + + setValue(chooser.getSelectedFile().getPath()); + } + + /** + * @param listener + */ + public void addPropertyChangeListener(PropertyChangeListener listener) { + editor.addPropertyChangeListener(listener); + } + + /** + * @return the text + */ + public String getAsText() { + return editor.getAsText(); + } + + /** + * @return custom editor panel + */ + public Component getCustomEditor() { + return panel; + } + + /** + * @return the Java initialisation string + */ + public String getJavaInitializationString() { + return editor.getJavaInitializationString(); + } + + /** + * @return the editor tags + */ + public String[] getTags() { + return editor.getTags(); + } + + /** + * @return the value + */ + public Object getValue() { + return editor.getValue(); + } + + /** + * @return true if the editor is paintable + */ + public boolean isPaintable() { + return editor.isPaintable(); + } + + /** + * @param gfx + * @param box + */ + public void paintValue(Graphics gfx, Rectangle box) { + editor.paintValue(gfx, box); + } + + /** + * @param listener + */ + public void removePropertyChangeListener(PropertyChangeListener listener) { + editor.removePropertyChangeListener(listener); + } + + /** + * @param text + * @throws java.lang.IllegalArgumentException + */ + public void setAsText(String text) throws IllegalArgumentException { + editor.setAsText(text); + } + + /** + * @param value + */ + public void setValue(Object value) { + editor.setValue(value); + } + + /** + * @return true if supports a custom editor + */ + public boolean supportsCustomEditor() { + return editor.supportsCustomEditor(); + } + + private static class SimpleFileEditor extends PropertyEditorSupport { + + @Override + public String getAsText() { + Object value = super.getValue(); + if (value instanceof File) { + return ((File) value).getPath(); + } + return (String) value; // assume it's string + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + super.setValue(new File(text)); + } + + @Override + public Object getValue() { + return super.getValue(); + } + + @Override + public void setValue(Object file) { + super.setValue(file); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java new file mode 100644 index 0000000..9dba09e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java @@ -0,0 +1,747 @@ +/* + * 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.testbeans.gui; + +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.beans.BeanInfo; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; +import java.beans.PropertyEditorManager; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The GenericTestBeanCustomizer is designed to provide developers with a + * mechanism to quickly implement GUIs for new components. + *

+ * It allows editing each of the public exposed properties of the edited type 'a + * la JavaBeans': as far as the types of those properties have an associated + * editor, there's no GUI development required. + *

+ * This class understands the following PropertyDescriptor attributes: + *

+ *
group: String
+ *
Group under which the property should be shown in the GUI. The string is + * also used as a group title (but see comment on resourceBundle below). The + * default group is "".
+ *
order: Integer
+ *
Order in which the property will be shown in its group. A smaller + * integer means higher up in the GUI. The default order is 0. Properties of + * equal order are sorted alphabetically.
+ *
tags: String[]
+ *
List of values to be offered for the property in addition to those + * offered by its property editor.
+ *
notUndefined: Boolean
+ *
If true, the property should not be left undefined. A default + * attribute must be provided if this is set.
+ *
notExpression: Boolean
+ *
If true, the property content should always be constant: JMeter + * 'expressions' (strings using ${var}, etc...) can't be used. + *
notOther: Boolean
+ *
If true, the property content must always be one of the tags values or + * null. + *
default: Object
+ *
Initial value for the property's GUI. Must be provided and be non-null + * if notUndefined is set. Must be one of the provided tags (or null) if + * notOther is set. + *
+ *

+ * The following BeanDescriptor attributes are also understood: + *

+ *
group.group.order: Integer
+ *
where group is a group name used in a group + * attribute in one or more PropertyDescriptors. Defines the order in which the + * group will be shown in the GUI. A smaller integer means higher up in the GUI. + * The default order is 0. Groups of equal order are sorted alphabetically.
+ *
resourceBundle: ResourceBundle
+ *
A resource bundle to be used for GUI localization: group display names + * will be obtained from property "group.displayName" if + * available (where group is the group name). + *
+ */ +public class GenericTestBeanCustomizer extends JPanel implements SharedCustomizer { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // should be quicker to find the editors if they are registered. + static { + PropertyEditorManager.registerEditor(Long.class, LongPropertyEditor.class); + PropertyEditorManager.registerEditor(Integer.class, IntegerPropertyEditor.class); + PropertyEditorManager.registerEditor(Boolean.class, BooleanPropertyEditor.class); + } + + public static final String GROUP = "group"; //$NON-NLS-1$ + + public static final String ORDER = "order"; //$NON-NLS-1$ + + /** + * Array of permissible values. + *

+ * Must be provided if: + *

    + *
  • {@link #NOT_OTHER} is TRUE, and
  • + *
  • {@link PropertyEditor#getTags()} is null
  • + *
+ */ + public static final String TAGS = "tags"; //$NON-NLS-1$ + + /** + * Whether the field must be defined (i.e. is required); + * Boolean, defaults to FALSE + */ + public static final String NOT_UNDEFINED = "notUndefined"; //$NON-NLS-1$ + + /** Whether the field disallows JMeter expressions; Boolean, default FALSE */ + public static final String NOT_EXPRESSION = "notExpression"; //$NON-NLS-1$ + + /** Whether the field disallows constant values different from the provided tags; Boolean, default FALSE */ + public static final String NOT_OTHER = "notOther"; //$NON-NLS-1$ + + /** Default value, must be provided if {@link #NOT_UNDEFINED} is TRUE */ + public static final String DEFAULT = "default"; //$NON-NLS-1$ + + public static final String RESOURCE_BUNDLE = "resourceBundle"; //$NON-NLS-1$ + + /** Property editor override; must be an enum of type {@link TypeEditor} */ + public static final String GUITYPE = "guiType"; // $NON-NLS-$ + + public static final String ORDER(String group) { + return "group." + group + ".order"; + } + + public static final String DEFAULT_GROUP = ""; + + @SuppressWarnings("unused") // TODO - use or remove + private int scrollerCount = 0; + + /** + * BeanInfo object for the class of the objects being edited. + */ + private transient BeanInfo beanInfo; + + /** + * Property descriptors from the beanInfo. + */ + private transient PropertyDescriptor[] descriptors; + + /** + * Property editors -- or null if the property can't be edited. Unused if + * customizerClass==null. + */ + private transient PropertyEditor[] editors; + + /** + * Message format for property field labels: + */ + private MessageFormat propertyFieldLabelMessage; + + /** + * Message format for property tooltips: + */ + private MessageFormat propertyToolTipMessage; + + /** + * The Map we're currently customizing. Set by setObject(). + */ + private Map propertyMap; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public GenericTestBeanCustomizer(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + /** + * Create a customizer for a given test bean type. + * + * @param testBeanClass + * a subclass of TestBean + * @see org.apache.jmeter.testbeans.TestBean + */ + GenericTestBeanCustomizer(BeanInfo beanInfo) { + super(); + + this.beanInfo = beanInfo; + + // Get and sort the property descriptors: + descriptors = beanInfo.getPropertyDescriptors(); + Arrays.sort(descriptors, new PropertyComparator(beanInfo)); + + // Obtain the propertyEditors: + editors = new PropertyEditor[descriptors.length]; + for (int i = 0; i < descriptors.length; i++) { // Index is also used for accessing editors array + PropertyDescriptor descriptor = descriptors[i]; + String name = descriptor.getName(); + + // Don't get editors for hidden or non-read-write properties: + if (descriptor.isHidden() || (descriptor.isExpert() && !JMeterUtils.isExpertMode()) + || descriptor.getReadMethod() == null || descriptor.getWriteMethod() == null) { + log.debug("Skipping editor for property " + name); + editors[i] = null; + continue; + } + + PropertyEditor propertyEditor; + Object guiType = descriptor.getValue(GUITYPE); + if (guiType instanceof TypeEditor) { + propertyEditor = ((TypeEditor) guiType).getInstance(descriptor); + } else { + Class editorClass = descriptor.getPropertyEditorClass(); + if (log.isDebugEnabled()) { + log.debug("Property " + name + " has editor class " + editorClass); + } + + if (editorClass != null) { + try { + propertyEditor = (PropertyEditor) editorClass.newInstance(); + } catch (InstantiationException e) { + log.error("Can't create property editor.", e); + throw new Error(e.toString()); + } catch (IllegalAccessException e) { + log.error("Can't create property editor.", e); + throw new Error(e.toString()); + } + } else { + Class c = descriptor.getPropertyType(); + propertyEditor = PropertyEditorManager.findEditor(c); + } + } + + if (propertyEditor == null) { + log.warn("No editor for property: " + name + + " type: " + descriptor.getPropertyType() + + " in bean: " + beanInfo.getBeanDescriptor().getDisplayName() + ); + editors[i] = null; + continue; + } + + if (log.isDebugEnabled()) { + log.debug("Property " + name + " has property editor " + propertyEditor); + } + + validateAttributes(descriptor, propertyEditor); + + if (!propertyEditor.supportsCustomEditor()) { + propertyEditor = createWrapperEditor(propertyEditor, descriptor); + + if (log.isDebugEnabled()) { + log.debug("Editor for property " + name + " is wrapped in " + propertyEditor); + } + } + if(propertyEditor instanceof TestBeanPropertyEditor) + { + ((TestBeanPropertyEditor)propertyEditor).setDescriptor(descriptor); + } + if (propertyEditor.getCustomEditor() instanceof JScrollPane) { + scrollerCount++; + } + + editors[i] = propertyEditor; + + // Initialize the editor with the provided default value or null: + setEditorValue(i, descriptor.getValue(DEFAULT)); + + } + + // Obtain message formats: + propertyFieldLabelMessage = new MessageFormat(JMeterUtils.getResString("property_as_field_label")); //$NON-NLS-1$ + propertyToolTipMessage = new MessageFormat(JMeterUtils.getResString("property_tool_tip")); //$NON-NLS-1$ + + // Initialize the GUI: + init(); + } + + /** + * Validate the descriptor attributes. + * + * @param pd the descriptor + * @param pe the propertyEditor + */ + private static void validateAttributes(PropertyDescriptor pd, PropertyEditor pe) { + if (notNull(pd) && pd.getValue(DEFAULT) == null) { + log.warn(getDetails(pd) + " requires a value but does not provide a default."); + } + if (notOther(pd) && pd.getValue(TAGS) == null && pe.getTags() == null) { + log.warn(getDetails(pd) + " does not have tags but other values are not allowed."); + } + if (!notNull(pd)) { + Class propertyType = pd.getPropertyType(); + if (propertyType.isPrimitive()) { + log.warn(getDetails(pd) + " allows null but is a primitive type"); + } + } + if (!pd.attributeNames().hasMoreElements()) { + log.warn(getDetails(pd) + " does not appear to have been configured"); + } + } + + /** + * Identify the property from the descriptor. + * + * @param pd + * @return + */ + private static String getDetails(PropertyDescriptor pd) { + StringBuilder sb = new StringBuilder(); + sb.append(pd.getReadMethod().getDeclaringClass().getName()); + sb.append('#'); + sb.append(pd.getName()); + return sb.toString(); + } + + /** + * Find the default typeEditor and a suitable guiEditor for the given + * property descriptor, and combine them in a WrapperEditor. + * + * @param typeEditor + * @param descriptor + * @return + */ + private WrapperEditor createWrapperEditor(PropertyEditor typeEditor, PropertyDescriptor descriptor) { + String[] editorTags = typeEditor.getTags(); + String[] additionalTags = (String[]) descriptor.getValue(TAGS); + String[] tags = null; + if (editorTags == null) { + tags = additionalTags; + } else if (additionalTags == null) { + tags = editorTags; + } else { + tags = new String[editorTags.length + additionalTags.length]; + int j = 0; + for (String editorTag : editorTags) { + tags[j++] = editorTag; + } + for (String additionalTag : additionalTags) { + tags[j++] = additionalTag; + } + } + + boolean notNull = notNull(descriptor); + boolean notExpression = notExpression(descriptor); + boolean notOther = notOther(descriptor); + + PropertyEditor guiEditor; + if (notNull && tags == null) { + guiEditor = new FieldStringEditor(); + } else { + ComboStringEditor e = new ComboStringEditor(); + e.setNoUndefined(notNull); + e.setNoEdit(notExpression && notOther); + e.setTags(tags); + + guiEditor = e; + } + + WrapperEditor wrapper = new WrapperEditor(typeEditor, guiEditor, + !notNull, // acceptsNull + !notExpression, // acceptsExpressions + !notOther, // acceptsOther + descriptor.getValue(DEFAULT)); + + return wrapper; + } + + /** + * Returns true if the property disallows constant values different from the provided tags. + * + * @param descriptor the property descriptor + * @return true if the attribute {@link #NOT_OTHER} is defined and equal to Boolean.TRUE; + * otherwise the default is false + */ + static boolean notOther(PropertyDescriptor descriptor) { + boolean notOther = Boolean.TRUE.equals(descriptor.getValue(NOT_OTHER)); + return notOther; + } + + /** + * Returns true if the property does not allow JMeter expressions. + * + * @param descriptor the property descriptor + * @return true if the attribute {@link #NOT_EXPRESSION} is defined and equal to Boolean.TRUE; + * otherwise the default is false + */ + static boolean notExpression(PropertyDescriptor descriptor) { + boolean notExpression = Boolean.TRUE.equals(descriptor.getValue(NOT_EXPRESSION)); + return notExpression; + } + + /** + * Returns true if the property must be defined (i.e. is required); + * + * @param descriptor the property descriptor + * @return true if the attribute {@link #NOT_UNDEFINED} is defined and equal to Boolean.TRUE; + * otherwise the default is false + */ + static boolean notNull(PropertyDescriptor descriptor) { + boolean notNull = Boolean.TRUE.equals(descriptor.getValue(NOT_UNDEFINED)); + return notNull; + } + + /** + * Set the value of the i-th property, properly reporting a possible + * failure. + * + * @param i + * the index of the property in the descriptors and editors + * arrays + * @param value + * the value to be stored in the editor + * + * @throws IllegalArgumentException + * if the editor refuses the value + */ + private void setEditorValue(int i, Object value) throws IllegalArgumentException { + editors[i].setValue(value); + } + + + /** + * {@inheritDoc} + * @param map must be an instance of Map<String, Object> + */ + @SuppressWarnings("unchecked") + public void setObject(Object map) { + propertyMap = (Map) map; + + if (propertyMap.size() == 0) { + // Uninitialized -- set it to the defaults: + for (PropertyDescriptor descriptor : descriptors) { + Object value = descriptor.getValue(DEFAULT); + String name = descriptor.getName(); + if (value != null) { + propertyMap.put(name, value); + log.debug("Set " + name + "= " + value); + } + firePropertyChange(name, null, value); + } + } + + // Now set the editors to the element's values: + for (int i = 0; i < editors.length; i++) { + if (editors[i] == null) { + continue; + } + try { + setEditorValue(i, propertyMap.get(descriptors[i].getName())); + } catch (IllegalArgumentException e) { + // I guess this can happen as a result of a bad + // file read? In this case, it would be better to replace the + // incorrect value with anything valid, e.g. the default value + // for the property. + // But for the time being, I just prefer to be aware of any + // problems occuring here, most likely programming errors, + // so I'll bail out. + // (MS Note) Can't bail out - newly create elements have blank + // values and must get the defaults. + // Also, when loading previous versions of jmeter test scripts, + // some values + // may not be right, and should get default values - MS + // TODO: review this and possibly change to: + setEditorValue(i, descriptors[i].getValue(DEFAULT)); + } + } + } + +// /** +// * Find the index of the property of the given name. +// * +// * @param name +// * the name of the property +// * @return the index of that property in the descriptors array, or -1 if +// * there's no property of this name. +// */ +// private int descriptorIndex(String name) // NOTUSED +// { +// for (int i = 0; i < descriptors.length; i++) { +// if (descriptors[i].getName().equals(name)) { +// return i; +// } +// } +// return -1; +// } + + /** + * Initialize the GUI. + */ + private void init() { + setLayout(new GridBagLayout()); + + GridBagConstraints cl = new GridBagConstraints(); // for labels + cl.gridx = 0; + cl.anchor = GridBagConstraints.EAST; + cl.insets = new Insets(0, 1, 0, 1); + + GridBagConstraints ce = new GridBagConstraints(); // for editors + ce.fill = GridBagConstraints.BOTH; + ce.gridx = 1; + ce.weightx = 1.0; + ce.insets = new Insets(0, 1, 0, 1); + + GridBagConstraints cp = new GridBagConstraints(); // for panels + cp.fill = GridBagConstraints.BOTH; + cp.gridx = 1; + cp.gridy = GridBagConstraints.RELATIVE; + cp.gridwidth = 2; + cp.weightx = 1.0; + + JPanel currentPanel = this; + String currentGroup = DEFAULT_GROUP; + int y = 0; + + for (int i = 0; i < editors.length; i++) { + if (editors[i] == null) { + continue; + } + + if (log.isDebugEnabled()) { + log.debug("Laying property " + descriptors[i].getName()); + } + + String g = group(descriptors[i]); + if (!currentGroup.equals(g)) { + if (currentPanel != this) { + add(currentPanel, cp); + } + currentGroup = g; + currentPanel = new JPanel(new GridBagLayout()); + currentPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + groupDisplayName(g))); + cp.weighty = 0.0; + y = 0; + } + + Component customEditor = editors[i].getCustomEditor(); + + boolean multiLineEditor = false; + if (customEditor.getPreferredSize().height > 50 || customEditor instanceof JScrollPane) { + // TODO: the above works in the current situation, but it's + // just a hack. How to get each editor to report whether it + // wants to grow bigger? Whether the property label should + // be at the left or at the top of the editor? ...? + multiLineEditor = true; + } + + JLabel label = createLabel(descriptors[i]); + label.setLabelFor(customEditor); + + cl.gridy = y; + cl.gridwidth = multiLineEditor ? 2 : 1; + cl.anchor = multiLineEditor ? GridBagConstraints.CENTER : GridBagConstraints.EAST; + currentPanel.add(label, cl); + + ce.gridx = multiLineEditor ? 0 : 1; + ce.gridy = multiLineEditor ? ++y : y; + ce.gridwidth = multiLineEditor ? 2 : 1; + ce.weighty = multiLineEditor ? 1.0 : 0.0; + + cp.weighty += ce.weighty; + + currentPanel.add(customEditor, ce); + + y++; + } + if (currentPanel != this) { + add(currentPanel, cp); + } + + // Add a 0-sized invisible component that will take all the vertical + // space that nobody wants: + cp.weighty = 0.0001; + add(Box.createHorizontalStrut(0), cp); + } + + private JLabel createLabel(PropertyDescriptor desc) { + String text = desc.getDisplayName(); + if (!"".equals(text)) { + text = propertyFieldLabelMessage.format(new Object[] { desc.getDisplayName() }); + } + // if the displayName is the empty string, leave it like that. + JLabel label = new JLabel(text); + label.setHorizontalAlignment(JLabel.TRAILING); + text = propertyToolTipMessage.format(new Object[] { desc.getName(), desc.getShortDescription() }); + label.setToolTipText(text); + + return label; + } + + /** + * Obtain a property descriptor's group. + * + * @param descriptor + * @return the group String. + */ + private static String group(PropertyDescriptor d) { + String group = (String) d.getValue(GROUP); + if (group == null){ + group = DEFAULT_GROUP; + } + return group; + } + + /** + * Obtain a group's display name + */ + private String groupDisplayName(String group) { + try { + ResourceBundle b = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(RESOURCE_BUNDLE); + if (b == null) { + return group; + } + return b.getString(group + ".displayName"); + } catch (MissingResourceException e) { + return group; + } + } + + /** + * Comparator used to sort properties for presentation in the GUI. + */ + private static class PropertyComparator implements Comparator, Serializable { + private static final long serialVersionUID = 240L; + + private final BeanInfo beanInfo; + public PropertyComparator(BeanInfo beanInfo) { + this.beanInfo = beanInfo; + } + + public int compare(PropertyDescriptor d1, PropertyDescriptor d2) { + int result; + + String g1 = group(d1), g2 = group(d2); + Integer go1 = groupOrder(g1), go2 = groupOrder(g2); + + result = go1.compareTo(go2); + if (result != 0) { + return result; + } + + result = g1.compareTo(g2); + if (result != 0) { + return result; + } + + Integer po1 = propertyOrder(d1), po2 = propertyOrder(d2); + result = po1.compareTo(po2); + if (result != 0) { + return result; + } + + return d1.getName().compareTo(d2.getName()); + } + + /** + * Obtain a group's order. + * + * @param group + * group name + * @return the group's order (zero by default) + */ + private Integer groupOrder(String group) { + Integer order = (Integer) beanInfo.getBeanDescriptor().getValue(ORDER(group)); + if (order == null) { + order = Integer.valueOf(0); + } + return order; + } + + /** + * Obtain a property's order. + * + * @param d + * @return the property's order attribute (zero by default) + */ + private Integer propertyOrder(PropertyDescriptor d) { + Integer order = (Integer) d.getValue(ORDER); + if (order == null) { + order = Integer.valueOf(0); + } + return order; + } + } + + /** + * Save values from the GUI fields into the property map + */ + void saveGuiFields() { + for (int i = 0; i < editors.length; i++) { + PropertyEditor propertyEditor=editors[i]; // might be null (e.g. in testing) + if (propertyEditor != null) { + Object value = propertyEditor.getValue(); + String name = descriptors[i].getName(); + if (value == null) { + propertyMap.remove(name); + if (log.isDebugEnabled()) { + log.debug("Unset " + name); + } + } else { + propertyMap.put(name, value); + if (log.isDebugEnabled()) { + log.debug("Set " + name + "= " + value); + } + } + } + } + } + + void clearGuiFields() { + for (int i = 0; i < editors.length; i++) { + PropertyEditor propertyEditor=editors[i]; // might be null (e.g. in testing) + if (propertyEditor != null) { + try { + if (propertyEditor instanceof WrapperEditor){ + WrapperEditor we = (WrapperEditor) propertyEditor; + String tags[]=we.getTags(); + if (tags != null && tags.length > 0) { + we.setAsText(tags[0]); + } else { + we.resetValue(); + } + } else if (propertyEditor instanceof ComboStringEditor) { + ComboStringEditor cse = (ComboStringEditor) propertyEditor; + cse.setAsText(cse.getInitialEditValue()); + } else { + propertyEditor.setAsText(""); + } + } catch (IllegalArgumentException ex){ + log.error("Failed to set field "+descriptors[i].getName(),ex); + } + } + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/IntegerPropertyEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/IntegerPropertyEditor.java new file mode 100644 index 0000000..c40f241 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/IntegerPropertyEditor.java @@ -0,0 +1,44 @@ +/* + * 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.testbeans.gui; + +import java.beans.PropertyEditorSupport; + +/** + * Property Editor which handles Integer properties. + * Uses {@link Integer#decode(String)} so supports hex and octal input. + */ +public class IntegerPropertyEditor extends PropertyEditorSupport { + + @Override + public void setAsText(String text) { + this.setValue(text); + } + + @Override + public void setValue(Object value){ + if (value instanceof String) { + super.setValue(Integer.decode((String) value)); // handles hex as well + } else if (value == null || value instanceof Integer) { + super.setValue(value); // not sure if null is passed in but no harm in setting it + } else { + throw new java.lang.IllegalArgumentException("Unexpected type: "+value.getClass().getName()); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/LongPropertyEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/LongPropertyEditor.java new file mode 100644 index 0000000..6cf0b14 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/LongPropertyEditor.java @@ -0,0 +1,44 @@ +/* + * 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.testbeans.gui; + +import java.beans.PropertyEditorSupport; + +/** + * Property Editor which handles Long properties. + * Uses {@link Long#decode(String)} so supports hex and octal input. + */ +public class LongPropertyEditor extends PropertyEditorSupport { + + @Override + public void setAsText(String text) { + this.setValue(text); + } + + @Override + public void setValue(Object value){ + if (value instanceof String) { + super.setValue(Long.decode((String) value)); // handles hex as well + } else if (value == null || value instanceof Long) { + super.setValue(value); // not sure if null is passed in but no harm in setting it + } else { + throw new java.lang.IllegalArgumentException("Unexpected type: "+value.getClass().getName()); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/PasswordEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/PasswordEditor.java new file mode 100644 index 0000000..7ca8064 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/PasswordEditor.java @@ -0,0 +1,128 @@ +/* + * 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.testbeans.gui; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyEditorSupport; + +import javax.swing.JPasswordField; + +/** + * This class implements a property editor for non-null String properties that + * supports custom editing (i.e.: provides a GUI component) based on a text + * field. + *

+ * The provided GUI is a simple password field. + * + */ +public class PasswordEditor extends PropertyEditorSupport implements ActionListener, FocusListener { + + private JPasswordField textField; + + /** + * Value on which we started the editing. Used to avoid firing + * PropertyChanged events when there's not been such change. + */ + private String initialValue = ""; + + protected PasswordEditor() { + super(); + + textField = new JPasswordField(); + textField.addActionListener(this); + textField.addFocusListener(this); + } + + @Override + public String getAsText() { + return new String(textField.getPassword()); + } + + @Override + public void setAsText(String value) { + initialValue = value; + textField.setText(value); + } + + @Override + public Object getValue() { + return getAsText(); + } + + @Override + public void setValue(Object value) { + if (value instanceof String) { + setAsText((String) value); + } else { + throw new IllegalArgumentException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Component getCustomEditor() { + return textField; + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + /** + * Avoid needlessly firing PropertyChanged events. + *

+ * {@inheritDoc} + */ + @Override + public void firePropertyChange() { + String newValue = getAsText(); + + if (initialValue.equals(newValue)) { + return; + } + initialValue = newValue; + + super.firePropertyChange(); + } + + /** + * {@inheritDoc} + */ + public void actionPerformed(ActionEvent e) { + firePropertyChange(); + } + + /** + * {@inheritDoc} + */ + public void focusGained(FocusEvent e) { + } + + /** + * {@inheritDoc} + */ + public void focusLost(FocusEvent e) { + firePropertyChange(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/SharedCustomizer.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/SharedCustomizer.java new file mode 100644 index 0000000..9090b6c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/SharedCustomizer.java @@ -0,0 +1,30 @@ +/* + * 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.testbeans.gui; + +import java.beans.Customizer; + +/** + * Tagging interface to mark a customizer class as shareable among elements of + * the same type. + *

+ * The interface is equivalent to Customizer -- the only difference is that + * setElement can be called multiple times to change the element it works on. + * + */ +public interface SharedCustomizer extends Customizer { +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TableEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TableEditor.java new file mode 100644 index 0000000..5962e37 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TableEditor.java @@ -0,0 +1,304 @@ +/* + * 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.testbeans.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditorSupport; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import javax.swing.CellEditor; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; + +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.log.Logger; + +/** + * Table editor for TestBean GUI properties. + * Currently only works for: + * - property type Collection, where there is a single header entry + */ +public class TableEditor extends PropertyEditorSupport implements FocusListener,TestBeanPropertyEditor,TableModelListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * attribute name for class name of a table row; + * value must be java.lang.String, or a class which supports set and get/is methods for the property name. + */ + public static final String CLASSNAME = "tableObject.classname"; // $NON-NLS-1$ + + /** + * attribute name for table headers, value must be a String array. + * If {@link #CLASSNAME} is java.lang.String, there must be only a single entry. + */ + public static final String HEADERS = "table.headers"; // $NON-NLS-1$ + + /** attribute name for property names within the {@link #CLASSNAME}, value must be String array */ + public static final String OBJECT_PROPERTIES = "tableObject.properties"; // $NON-NLS-1$ + + private JTable table; + private ObjectTableModel model; + private Class clazz; + private PropertyDescriptor descriptor; + private final JButton addButton,removeButton,clearButton; + + public TableEditor() { + addButton = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ + addButton.addActionListener(new AddListener()); + removeButton = new JButton(JMeterUtils.getResString("remove")); // $NON-NLS-1$ + removeButton.addActionListener(new RemoveListener()); + clearButton = new JButton(JMeterUtils.getResString("clear")); // $NON-NLS-1$ + clearButton.addActionListener(new ClearListener()); + } + + @Override + public String getAsText() { + return null; + } + + @Override + public Component getCustomEditor() { + JComponent pane = makePanel(); + pane.doLayout(); + pane.validate(); + return pane; + } + + private JComponent makePanel() + { + JPanel p = new JPanel(new BorderLayout()); + JScrollPane scroller = new JScrollPane(table); + scroller.setPreferredSize(scroller.getMinimumSize()); + p.add(scroller,BorderLayout.CENTER); + JPanel south = new JPanel(); + south.add(addButton); + south.add(removeButton); + south.add(clearButton); + p.add(south,BorderLayout.SOUTH); + return p; + } + + @Override + public Object getValue() { + return model.getObjectList(); + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + //not interested in this method. + } + + @Override + public void setValue(Object value) { + if(value != null) + { + model.setRows(convertCollection((Collection)value)); + } + else model.clearData(); + this.firePropertyChange(); + } + + private Collection convertCollection(Collection values) + { + List l = new LinkedList(); + for(Object obj : values) + { + if(obj instanceof TestElementProperty) + { + l.add(((TestElementProperty)obj).getElement()); + } + else + { + l.add(obj); + } + } + return l; + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + /** + * For the table editor, the CLASSNAME attribute must simply be the name of the class of object it will hold + * where each row holds one object. + */ + public void setDescriptor(PropertyDescriptor descriptor) { + this.descriptor = descriptor; + String value = (String)descriptor.getValue(CLASSNAME); + if (value == null) { + throw new RuntimeException("The Table Editor requires the CLASSNAME atttribute be set - the name of the object to represent a row"); + } + try { + clazz = Class.forName(value); + initializeModel(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not find the CLASSNAME class "+ value, e); + } + } + + void initializeModel() + { + Object hdrs = descriptor.getValue(HEADERS); + if (!(hdrs instanceof String[])){ + throw new RuntimeException("attribute HEADERS must be a String array"); + } + if(clazz == String.class) + { + model = new ObjectTableModel((String[])hdrs,new Functor[0],new Functor[0],new Class[]{String.class}); + } + else + { + Object value = descriptor.getValue(OBJECT_PROPERTIES); + if (!(value instanceof String[])) { + throw new RuntimeException("attribute OBJECT_PROPERTIES must be a String array"); + } + String[] props = (String[])value; + Functor[] writers = new Functor[props.length]; + Functor[] readers = new Functor[props.length]; + Class[] editors = new Class[props.length]; + int count = 0; + for(String propName : props) + { + propName = propName.substring(0,1).toUpperCase(Locale.ENGLISH) + propName.substring(1); + writers[count] = createWriter(clazz,propName); + readers[count] = createReader(clazz,propName); + editors[count] = getArgForWriter(clazz,propName); + count++; + } + model = new ObjectTableModel((String[])hdrs,readers,writers,editors); + } + model.addTableModelListener(this); + table = new JTable(model); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + table.addFocusListener(this); + } + + Functor createWriter(Class c,String propName) + { + String setter = "set" + propName; // $NON-NLS-1$ + return new Functor(setter); + } + + Functor createReader(Class c,String propName) + { + String getter = "get" + propName; // $NON-NLS-1$ + try + { + c.getMethod(getter,new Class[0]); + return new Functor(getter); + } + catch(Exception e) { return new Functor("is" + propName); } + } + + Class getArgForWriter(Class c,String propName) + { + String setter = "set" + propName; // $NON-NLS-1$ + for(Method m : c.getMethods()) + { + if(m.getName().equals(setter)) + { + return m.getParameterTypes()[0]; + } + } + return null; + } + + public void tableChanged(TableModelEvent e) { + this.firePropertyChange(); + } + + public void focusGained(FocusEvent e) { + + } + + public void focusLost(FocusEvent e) { + final int editingRow = table.getEditingRow(); + final int editingColumn = table.getEditingColumn(); + CellEditor ce = null; + if (editingRow != -1 && editingColumn != -1){ + ce = table.getCellEditor(editingRow,editingColumn); + } + Component editor = table.getEditorComponent(); + if(ce != null && (editor == null || editor != e.getOppositeComponent())) + { + ce.stopCellEditing(); + } + else if(editor != null) + { + editor.addFocusListener(this); + } + this.firePropertyChange(); + } + + private class AddListener implements ActionListener + { + public void actionPerformed(ActionEvent e) + { + try + { + model.addRow(clazz.newInstance()); + }catch(Exception err) + { + log.error("The class type given to TableEditor was not instantiable. ",err); + } + } + } + + private class RemoveListener implements ActionListener + { + public void actionPerformed(ActionEvent e) + { + int row = table.getSelectedRow(); + if (row >= 0) { + model.removeRow(row); + } + } + } + + private class ClearListener implements ActionListener + { + public void actionPerformed(ActionEvent e) + { + model.clearData(); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TestBeanGUI.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TestBeanGUI.java new file mode 100644 index 0000000..9174b42 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TestBeanGUI.java @@ -0,0 +1,516 @@ +/* + * 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.testbeans.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.beans.BeanDescriptor; +import java.beans.BeanInfo; +import java.beans.Customizer; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditorManager; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.swing.JPopupMenu; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.jmeter.assertions.Assertion; +import org.apache.jmeter.assertions.gui.AbstractAssertionGui; +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.gui.AbstractControllerGui; +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.JMeterGUIComponent; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.processor.gui.AbstractPostProcessorGui; +import org.apache.jmeter.processor.gui.AbstractPreProcessorGui; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.samplers.gui.AbstractSamplerGui; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.AbstractProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.timers.Timer; +import org.apache.jmeter.timers.gui.AbstractTimerGui; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.LocaleChangeEvent; +import org.apache.jmeter.util.LocaleChangeListener; +import org.apache.jmeter.visualizers.Visualizer; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * JMeter GUI element editing for TestBean elements. + *

+ * The actual GUI is always a bean customizer: if the bean descriptor provides + * one, it will be used; otherwise, a GenericTestBeanCustomizer will be created + * for this purpose. + *

+ * Those customizers deviate from the standards only in that, instead of a bean, + * they will receive a Map in the setObject call. This will be a property name + * to value Map. The customizer is also in charge of initializing empty Maps + * with sensible initial values. + *

+ * If the provided Customizer class implements the SharedCustomizer interface, + * the same instance of the customizer will be reused for all beans of the type: + * setObject(map) can then be called multiple times. Otherwise, one separate + * instance will be used for each element. For efficiency reasons, most + * customizers should implement SharedCustomizer. + * + */ +public class TestBeanGUI extends AbstractJMeterGuiComponent implements JMeterGUIComponent, LocaleChangeListener{ + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Class testBeanClass; + + private transient BeanInfo beanInfo; + + private final Class customizerClass; + + /** + * The single customizer if the customizer class implements + * SharedCustomizer, null otherwise. + */ + private Customizer customizer = null; + + /** + * TestElement to Customizer map if customizer is null. This is necessary to + * avoid the cost of creating a new customizer on each edit. The cache size + * needs to be limited, though, to avoid memory issues when editing very + * large test plans. + */ + @SuppressWarnings("unchecked") + private final Map customizers = new LRUMap(20); + + /** + * Index of the customizer in the JPanel's child component list: + */ + private int customizerIndexInPanel; + + /** + * The property name to value map that the active customizer edits: + */ + private final Map propertyMap = new HashMap(); + + /** + * Whether the GUI components have been created. + */ + private boolean initialized = false; + + static { + List paths = new LinkedList(); + paths.add("org.apache.jmeter.testbeans.gui");// $NON-NLS-1$ + paths.addAll(Arrays.asList(PropertyEditorManager.getEditorSearchPath())); + String s = JMeterUtils.getPropDefault("propertyEditorSearchPath", null);// $NON-NLS-1$ + if (s != null) { + paths.addAll(Arrays.asList(JOrphanUtils.split(s, ",", "")));// $NON-NLS-1$ // $NON-NLS-2$ + } + PropertyEditorManager.setEditorSearchPath(paths.toArray(new String[paths.size()])); + } + + /** + * @deprecated Dummy for JUnit test purposes only + */ + @Deprecated + public TestBeanGUI() { + log.warn("Constructor only for use in testing");// $NON-NLS-1$ + testBeanClass = null; + customizerClass = null; + beanInfo = null; + } + + public TestBeanGUI(Class testBeanClass) { + super(); + log.debug("testing class: " + testBeanClass.getName()); + // A quick verification, just in case: + if (!TestBean.class.isAssignableFrom(testBeanClass)) { + Error e = new Error(); + log.error("This should never happen!", e); + throw e; // Programming error: bail out. + } + + this.testBeanClass = testBeanClass; + + // Get the beanInfo: + try { + beanInfo = Introspector.getBeanInfo(testBeanClass); + } catch (IntrospectionException e) { + log.error("Can't get beanInfo for " + testBeanClass.getName(), e); + throw new Error(e.toString()); // Programming error. Don't + // continue. + } + + customizerClass = beanInfo.getBeanDescriptor().getCustomizerClass(); + + // Creation of the customizer and GUI initialization is delayed until + // the + // first + // configure call. We don't need all that just to find out the static + // label, menu + // categories, etc! + initialized = false; + JMeterUtils.addLocaleChangeListener(this); + } + + private Customizer createCustomizer() { + try { + return (Customizer) customizerClass.newInstance(); + } catch (InstantiationException e) { + log.error("Could not instantiate customizer of class " + customizerClass, e); + throw new Error(e.toString()); + } catch (IllegalAccessException e) { + log.error("Could not instantiate customizer of class " + customizerClass, e); + throw new Error(e.toString()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getStaticLabel() { + if (beanInfo == null){ + return "null";// $NON-NLS-1$ + } + return beanInfo.getBeanDescriptor().getDisplayName(); + } + + /** + * {@inheritDoc} + */ + public TestElement createTestElement() { + try { + TestElement element = (TestElement) testBeanClass.newInstance(); + // In other GUI component, clearGUI resets the value to defaults one as there is one GUI per Element + // With TestBeanGUI as it's shared, its default values are only known here, we must call setValues with + // element (as it holds default values) + // otherwise we will get values as computed by customizer reset and not default ones + if(initialized) { + setValues(element); + } + // configure(element); + // super.clear(); // set name, enabled. + modifyTestElement(element); // put the default values back into the + // new element + return element; + } catch (InstantiationException e) { + log.error("Can't create test element", e); + throw new Error(e.toString()); // Programming error. Don't + // continue. + } catch (IllegalAccessException e) { + log.error("Can't create test element", e); + throw new Error(e.toString()); // Programming error. Don't + // continue. + } + } + + /** + * {@inheritDoc} + */ + public void modifyTestElement(TestElement element) { + // Fetch data from screen fields + if (customizer instanceof GenericTestBeanCustomizer) { + GenericTestBeanCustomizer gtbc = (GenericTestBeanCustomizer) customizer; + gtbc.saveGuiFields(); + } + configureTestElement(element); + + // Copy all property values from the map into the element: + for (PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) { + String name = desc.getName(); + Object value = propertyMap.get(name); + log.debug("Modify " + name + " to " + value); + if (value == null) { + if (GenericTestBeanCustomizer.notNull(desc)) { // cannot be null + setPropertyInElement(element, name, desc.getValue(GenericTestBeanCustomizer.DEFAULT)); + } else { + element.removeProperty(name); + } + } else { + setPropertyInElement(element, name, value); + } + } + } + + /** + * @param element + * @param name + */ + private void setPropertyInElement(TestElement element, String name, Object value) { + JMeterProperty jprop = AbstractProperty.createProperty(value); + jprop.setName(name); + element.setProperty(jprop); + } + + /** + * {@inheritDoc} + */ + public JPopupMenu createPopupMenu() { + if (Timer.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultTimerMenu(); + } + else if(Sampler.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultSamplerMenu(); + } + else if(ConfigElement.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultConfigElementMenu(); + } + else if(Assertion.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultAssertionMenu(); + } + else if(PostProcessor.class.isAssignableFrom(testBeanClass) || + PreProcessor.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultExtractorMenu(); + } + else if(Visualizer.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultVisualizerMenu(); + } + else if(Controller.class.isAssignableFrom(testBeanClass)) + { + return MenuFactory.getDefaultControllerMenu(); + } + else { + log.warn("Cannot determine PopupMenu for "+testBeanClass.getName()); + return MenuFactory.getDefaultMenu(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(TestElement element) { + if (!initialized){ + init(); + } + clearGui(); + + super.configure(element); + + setValues(element); + + initialized = true; + } + + /** + * Get values from element to fill propertyMap and setup customizer + * @param element TestElement + */ + private void setValues(TestElement element) { + // Copy all property values into the map: + for (PropertyIterator jprops = element.propertyIterator(); jprops.hasNext();) { + JMeterProperty jprop = jprops.next(); + propertyMap.put(jprop.getName(), jprop.getObjectValue()); + } + + if (customizer != null) { + customizer.setObject(propertyMap); + } else { + if (initialized){ + remove(customizerIndexInPanel); + } + Customizer c = customizers.get(element); + if (c == null) { + c = createCustomizer(); + c.setObject(propertyMap); + customizers.put(element, c); + } + add((Component) c, BorderLayout.CENTER); + } + } + + /** {@inheritDoc} */ + public Collection getMenuCategories() { + List menuCategories = new LinkedList(); + BeanDescriptor bd = beanInfo.getBeanDescriptor(); + + // We don't want to show expert beans in the menus unless we're + // in expert mode: + if (bd.isExpert() && !JMeterUtils.isExpertMode()) { + return null; + } + + int matches = setupGuiClasses(menuCategories); + if (matches == 0) { + log.error("Could not assign GUI class to " + testBeanClass.getName()); + } else if (matches > 1) {// may be impossible, but no harm in + // checking ... + log.error("More than 1 GUI class found for " + testBeanClass.getName()); + } + return menuCategories; + } + + /** + * Setup GUI class + * @return number of matches + */ + public int setupGuiClasses() { + return setupGuiClasses(new ArrayList()); + } + + /** + * Setup GUI class + * @param menuCategories List menu categories + * @return number of matches + */ + private int setupGuiClasses(List menuCategories ) { + int matches = 0;// How many classes can we assign from? + // TODO: there must be a nicer way... + BeanDescriptor bd = beanInfo.getBeanDescriptor(); + if (Assertion.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.ASSERTIONS); + bd.setValue(TestElement.GUI_CLASS, AbstractAssertionGui.class.getName()); + matches++; + } + if (ConfigElement.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.CONFIG_ELEMENTS); + bd.setValue(TestElement.GUI_CLASS, AbstractConfigGui.class.getName()); + matches++; + } + if (Controller.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.CONTROLLERS); + bd.setValue(TestElement.GUI_CLASS, AbstractControllerGui.class.getName()); + matches++; + } + if (Visualizer.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.LISTENERS); + bd.setValue(TestElement.GUI_CLASS, AbstractVisualizer.class.getName()); + matches++; + } + if (PostProcessor.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.POST_PROCESSORS); + bd.setValue(TestElement.GUI_CLASS, AbstractPostProcessorGui.class.getName()); + matches++; + } + if (PreProcessor.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.PRE_PROCESSORS); + bd.setValue(TestElement.GUI_CLASS, AbstractPreProcessorGui.class.getName()); + matches++; + } + if (Sampler.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.SAMPLERS); + bd.setValue(TestElement.GUI_CLASS, AbstractSamplerGui.class.getName()); + matches++; + } + if (Timer.class.isAssignableFrom(testBeanClass)) { + menuCategories.add(MenuFactory.TIMERS); + bd.setValue(TestElement.GUI_CLASS, AbstractTimerGui.class.getName()); + matches++; + } + return matches; + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + customizerIndexInPanel = getComponentCount(); + + if (customizerClass == null) { + customizer = new GenericTestBeanCustomizer(beanInfo); + } else if (SharedCustomizer.class.isAssignableFrom(customizerClass)) { + customizer = createCustomizer(); + } + + if (customizer != null){ + add((Component) customizer, BorderLayout.CENTER); + } + } + + /** + * {@inheritDoc} + */ + public String getLabelResource() { + // @see getStaticLabel + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + super.clearGui(); + if (customizer instanceof GenericTestBeanCustomizer) { + GenericTestBeanCustomizer gtbc = (GenericTestBeanCustomizer) customizer; + gtbc.clearGuiFields(); + } + propertyMap.clear(); + } + + public boolean isHidden() { + return beanInfo.getBeanDescriptor().isHidden(); + } + + public boolean isExpert() { + return beanInfo.getBeanDescriptor().isExpert(); + } + + /** + * Handle Locale Change by reloading BeanInfo + * @param event {@link LocaleChangeEvent} + */ + public void localeChanged(LocaleChangeEvent event) { + try { + beanInfo = Introspector.getBeanInfo(testBeanClass); + setupGuiClasses(); + } catch (IntrospectionException e) { + log.error("Can't get beanInfo for " + testBeanClass.getName(), e); + JMeterUtils.reportErrorToUser("Can't get beanInfo for " + testBeanClass.getName()); + } + } + + /** + * {@inheritDoc}} + * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#getDocAnchor() + */ + @Override + public String getDocAnchor() { + ResourceBundle resourceBundle = ResourceBundle.getBundle( + testBeanClass.getName() + "Resources", // $NON-NLS-1$ + new Locale("","")); + + String name = resourceBundle.getString("displayName"); + return name.replace(' ', '_'); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TestBeanPropertyEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TestBeanPropertyEditor.java new file mode 100644 index 0000000..2b73d72 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TestBeanPropertyEditor.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.jmeter.testbeans.gui; + +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; + +public interface TestBeanPropertyEditor extends PropertyEditor { + + public void setDescriptor(PropertyDescriptor descriptor); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TextAreaEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TextAreaEditor.java new file mode 100644 index 0000000..a385e1d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TextAreaEditor.java @@ -0,0 +1,112 @@ +/* + * 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. + * + */ + +/* + * Created on May 21, 2004 + */ +package org.apache.jmeter.testbeans.gui; + +import java.awt.Component; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyEditorSupport; + +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +public class TextAreaEditor extends PropertyEditorSupport implements FocusListener { + + private JTextArea textUI; + + private JScrollPane scroller; + + /** {@inheritDoc} */ + public void focusGained(FocusEvent e) { + } + + /** {@inheritDoc} */ + public void focusLost(FocusEvent e) { + firePropertyChange(); + } + + private final void init() {// called from ctor, so must not be overridable + textUI = new JTextArea(); + textUI.addFocusListener(this); + textUI.setWrapStyleWord(true); + textUI.setLineWrap(true); + scroller = new JScrollPane(textUI, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + } + + /** + * + */ + public TextAreaEditor() { + super(); + init(); + } + + /** + * @param source + */ + public TextAreaEditor(Object source) { + super(source); + init(); + setValue(source); + } + + /** {@inheritDoc} */ + @Override + public String getAsText() { + return textUI.getText(); + } + + /** {@inheritDoc} */ + @Override + public Component getCustomEditor() { + return scroller; + } + + /** {@inheritDoc} */ + @Override + public void setAsText(String text) throws IllegalArgumentException { + textUI.setText(text); + } + + /** {@inheritDoc} */ + @Override + public void setValue(Object value) { + if (value != null) { + textUI.setText(value.toString()); + } else { + textUI.setText(""); + } + } + + /** {@inheritDoc} */ + @Override + public Object getValue() { + return textUI.getText(); + } + + /** {@inheritDoc} */ + @Override + public boolean supportsCustomEditor() { + return true; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TypeEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TypeEditor.java new file mode 100644 index 0000000..9221184 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/TypeEditor.java @@ -0,0 +1,35 @@ +/* + * 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.testbeans.gui; + +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; + +/** + * Allow direct specification of property editors. + */ +public enum TypeEditor { + FileEditor {@Override PropertyEditor getInstance(PropertyDescriptor descriptor) { return new FileEditor(descriptor); }}, + PasswordEditor {@Override PropertyEditor getInstance(PropertyDescriptor descriptor) { return new PasswordEditor(); }}, + TableEditor {@Override PropertyEditor getInstance(PropertyDescriptor descriptor) { return new TableEditor(); }}, + TextAreaEditor {@Override PropertyEditor getInstance(PropertyDescriptor descriptor) { return new TextAreaEditor(); }}, + ; + // Some editors may need the descriptor + abstract PropertyEditor getInstance(PropertyDescriptor descriptor); +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/WrapperEditor.java b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/WrapperEditor.java new file mode 100644 index 0000000..b77bd8f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testbeans/gui/WrapperEditor.java @@ -0,0 +1,447 @@ +/* + * 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.testbeans.gui; + +import java.awt.Component; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyEditor; +import java.beans.PropertyEditorSupport; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This is an implementation of a full-fledged property editor, providing both + * object-text transformation and an editor GUI (a custom editor component), + * from two simpler property editors providing only one of these functionalities + * each, namely: + *

+ *
typeEditor + *
+ *
Provides suitable object-to-string and string-to-object transformation + * for the property's type. That is: it's a simple editor that only need to + * support the set/getAsText and set/getValue methods.
+ *
guiEditor
+ *
Provides a suitable GUI for the property, but works on [possibly null] + * String values. That is: it supportsCustomEditor, but get/setAsText and + * get/setValue are indentical.
+ *
+ *

+ * The resulting editor provides optional support for null values (you can + * choose whether null is to be a valid property value). It also + * provides optional support for JMeter 'expressions' (you can choose whether + * they make valid property values). + * + */ +class WrapperEditor extends PropertyEditorSupport implements PropertyChangeListener { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * The type's property editor. + */ + private final PropertyEditor typeEditor; + + /** + * The gui property editor + */ + private final PropertyEditor guiEditor; + + /** + * Whether to allow null as a property value. + */ + private final boolean acceptsNull; + + /** + * Whether to allow JMeter 'expressions' as property values. + */ + private final boolean acceptsExpressions; + + /** + * Whether to allow any constant values different from the provided tags. + */ + private final boolean acceptsOther; + + /** Default value to be used to (re-)initialiase the field */ + private final Object defaultValue; + + /** + * Keep track of the last valid value in the editor, so that we can revert + * to it if the user enters an invalid value. + */ + private String lastValidValue = null; + + /** + * Constructor for use when a PropertyEditor is delegating to us. + */ + WrapperEditor(Object source, PropertyEditor typeEditor, PropertyEditor guiEditor, boolean acceptsNull, + boolean acceptsExpressions, boolean acceptsOther, Object defaultValue) { + super(); + if (source != null) { + super.setSource(source); + } + this.typeEditor = typeEditor; + this.guiEditor = guiEditor; + this.acceptsNull = acceptsNull; + this.acceptsExpressions = acceptsExpressions; + this.acceptsOther = acceptsOther; + this.defaultValue = defaultValue; + initialize(); + } + + /** + * Constructor for use for regular instantiation and by subclasses. + */ + WrapperEditor(PropertyEditor typeEditor, PropertyEditor guiEditor, boolean acceptsNull, boolean acceptsExpressions, + boolean acceptsOther, Object defaultValue) { + this(null, typeEditor, guiEditor, acceptsNull, acceptsExpressions, acceptsOther, defaultValue); + } + + final void resetValue(){ + setValue(defaultValue); + lastValidValue = getAsText(); + } + + private void initialize() { + + resetValue(); + + if (guiEditor instanceof ComboStringEditor) { + String[] tags = ((ComboStringEditor) guiEditor).getTags(); + + // Provide an initial edit value if necessary -- this is an + // heuristic that tries to provide the most convenient + // initial edit value: + + String v; + if (!acceptsOther) { + v = "${}"; //$NON-NLS-1$ + } else if (isValidValue("")) { //$NON-NLS-1$ + v = ""; //$NON-NLS-1$ + } else if (acceptsExpressions) { + v = "${}"; //$NON-NLS-1$ + } else if (tags != null && tags.length > 0) { + v = tags[0]; + } else { + v = getAsText(); + } + + ((ComboStringEditor) guiEditor).setInitialEditValue(v); + } + + guiEditor.addPropertyChangeListener(this); + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + @Override + public Component getCustomEditor() { + return guiEditor.getCustomEditor(); + } + + @Override + public String[] getTags() { + return guiEditor.getTags(); + } + + /** + * Determine wheter a string is one of the known tags. + * + * @param text + * @return true iif text equals one of the getTags() + */ + private boolean isATag(String text) { + String[] tags = getTags(); + if (tags == null) { + return false; + } + for (int i = 0; i < tags.length; i++) { + if (tags[i].equals(text)) { + return true; + } + } + return false; + } + + /** + * Determine whether a string is a valid value for the property. + * + * @param text + * the value to be checked + * @return true iif text is a valid value + */ + private boolean isValidValue(String text) { + if (text == null) { + return acceptsNull; + } + + if (acceptsExpressions && isExpression(text)) { + return true; + } + + // Not an expression (isn't or can't be), not null. + + // The known tags are assumed to be valid: + if (isATag(text)) { + return true; + } + // Was not a tag, so if we can't accept other values... + if (!acceptsOther) { + return false; + } + + // Delegate the final check to the typeEditor: + try { + typeEditor.setAsText(text); + } catch (IllegalArgumentException e1) { + // setAsText failed: not valid + return false; + } + // setAsText succeeded: valid + return true; + } + + /** + * This method is used to do some low-cost defensive programming: it is + * called when a condition that the program logic should prevent from + * happening occurs. I hope this will help early detection of logical bugs + * in property value handling. + * + * @throws Error + * always throws an error. + */ + private final void shouldNeverHappen(String msg) throws Error { + throw new Error(msg); // Programming error: bail out. + } + + /** + * Same as shouldNeverHappen(), but provide a source exception. + * + * @param e + * the exception that helped identify the problem + * @throws Error + * always throws one. + */ + private final void shouldNeverHappen(Exception e) throws Error { + throw new Error(e.toString()); // Programming error: bail out. + } + + /** + * Check if a string is a valid JMeter 'expression'. + *

+ * The current implementation is very basic: it just accepts any string + * containing "${" as a valid expression. TODO: improve, but keep returning + * true for "${}". + */ + private final boolean isExpression(String text) { + return text.indexOf("${") != -1;//$NON-NLS-1$ + } + + /** + * Same as isExpression(String). + * + * @param text + * @return true iif text is a String and isExpression(text). + */ + private final boolean isExpression(Object text) { + return text instanceof String && isExpression((String) text); + } + + /** + * @see java.beans.PropertyEditor#getValue() + * @see org.apache.jmeter.testelement.property.JMeterProperty + */ + @Override + public Object getValue() { + String text = (String) guiEditor.getValue(); + + Object value; + + if (text == null) { + if (!acceptsNull) { + shouldNeverHappen("Text is null but null is not allowed"); + } + value = null; + } else { + if (acceptsExpressions && isExpression(text)) { + value = text; + } else { + // not an expression (isn't or can't be), not null. + + // a check, just in case: + if (!acceptsOther && !isATag(text)) { + shouldNeverHappen("Text is not a tag but other entries are not allowed"); + } + + try { + // Bug 44314 Number field does not seem to accept "" + try { + typeEditor.setAsText(text); + } catch (NumberFormatException e) { + if (text.length()==0){ + text="0";//$NON-NLS-1$ + typeEditor.setAsText(text); + } else { + shouldNeverHappen(e); + } + } + } catch (IllegalArgumentException e) { + shouldNeverHappen(e); + } + value = typeEditor.getValue(); + } + } + + if (log.isDebugEnabled()) { + log.debug("->" + (value != null ? value.getClass().getName() : "NULL") + ":" + value); + } + return value; + } + + @Override + public final void setValue(Object value) { /// final because called from ctor + String text; + + if (log.isDebugEnabled()) { + log.debug("<-" + (value != null ? value.getClass().getName() : "NULL") + ":" + value); + } + + if (value == null) { + if (!acceptsNull) { + throw new IllegalArgumentException("Null is not allowed"); + } + text = null; + } else if (acceptsExpressions && isExpression(value)) { + text = (String) value; + } else { + // Not an expression (isn't or can't be), not null. + typeEditor.setValue(value); // may throw IllegalArgumentExc. + text = fixGetAsTextBug(typeEditor.getAsText()); + + if (!acceptsOther && !isATag(text)) { + throw new IllegalArgumentException("Value not allowed: "+text); + } + } + + guiEditor.setValue(text); + } + + /* + * Fix bug in JVMs that return true/false rather than True/False + * from the type editor getAsText() method + */ + private String fixGetAsTextBug(String asText) { + if (asText == null){ + return asText; + } + if (asText.equals("true")){ + log.debug("true=>True");// so we can detect it + return "True"; + } + if (asText.equals("false")){ + log.debug("false=>False");// so we can detect it + return "False"; + } + return asText; + } + + @Override + public String getAsText() { + String text = fixGetAsTextBug(guiEditor.getAsText()); + + if (text == null) { + if (!acceptsNull) { + shouldNeverHappen("Text is null, but null is not allowed"); + } + } else if (!acceptsExpressions || !isExpression(text)) { + // not an expression (can't be or isn't), not null. + try { + typeEditor.setAsText(text); // ensure value is propagated to editor + } catch (IllegalArgumentException e) { + shouldNeverHappen(e); + } + text = fixGetAsTextBug(typeEditor.getAsText()); + + // a check, just in case: + if (!acceptsOther && !isATag(text)) { + shouldNeverHappen("Text is not a tag, but other values are not allowed"); + } + } + + if (log.isDebugEnabled()) { + log.debug("->\"" + text + "\""); + } + return text; + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + if (log.isDebugEnabled()) { + log.debug(text == null ? "<-null" : "<-\"" + text + "\""); + } + + String value; + + if (text == null) { + if (!acceptsNull) { + throw new IllegalArgumentException("Null parameter not allowed"); + } + value = null; + } else { + if (acceptsExpressions && isExpression(text)) { + value = text; + } else { + // Some editors do tiny transformations (e.g. "true" to + // "True",...): + typeEditor.setAsText(text); // may throw IllegalArgumentException + value = typeEditor.getAsText(); + + if (!acceptsOther && !isATag(text)) { + throw new IllegalArgumentException("Value not allowed: "+text); + } + } + } + + guiEditor.setValue(value); + } + + public void propertyChange(PropertyChangeEvent event) { + String text = fixGetAsTextBug(guiEditor.getAsText()); + if (isValidValue(text)) { + lastValidValue = text; + firePropertyChange(); + } else { + if (GuiPackage.getInstance() == null){ + log.warn("Invalid value: "+text+" "+typeEditor); + } else { + JOptionPane.showMessageDialog(guiEditor.getCustomEditor().getParent(), + JMeterUtils.getResString("property_editor.value_is_invalid_message"),//$NON-NLS-1$ + JMeterUtils.getResString("property_editor.value_is_invalid_title"), //$NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + } + // Revert to the previous value: + guiEditor.setAsText(lastValidValue); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractChart.java b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractChart.java new file mode 100644 index 0000000..5d03152 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractChart.java @@ -0,0 +1,242 @@ +/* + * 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.testelement; + +import java.awt.image.BufferedImage; +import java.util.List; + +import javax.swing.JComponent; + +import org.apache.jmeter.report.DataSet; +import org.apache.jmeter.report.ReportChart; +import org.apache.jmeter.visualizers.SamplingStatCalculator; +import org.apache.jorphan.util.JOrphanUtils; + +/** + * The general idea of the chart graphs information for a table. + * A chart can only be generated from a specific table, though more + * than one chart can be generated from a single table. + * + */ +public abstract class AbstractChart extends AbstractTestElement implements ReportChart { + + private static final long serialVersionUID = 240L; + + public static final String REPORT_CHART_X_AXIS = "ReportChart.chart.x.axis"; + public static final String REPORT_CHART_Y_AXIS = "ReportChart.chart.y.axis"; + public static final String REPORT_CHART_X_LABEL = "ReportChart.chart.x.label"; + public static final String REPORT_CHART_Y_LABEL = "ReportChart.chart.y.label"; + public static final String REPORT_CHART_TITLE = "ReportChart.chart.title"; + public static final String REPORT_CHART_CAPTION = "ReportChart.chart.caption"; + public static final String REPORT_CHART_WIDTH = "ReportChart.chart.width"; + public static final String REPORT_CHART_HEIGHT = "ReportChart.chart.height"; + + public static final int DEFAULT_WIDTH = 350; + public static final int DEFAULT_HEIGHT = 350; + + public static final String X_DATA_FILENAME_LABEL = "Filename"; + public static final String X_DATA_DATE_LABEL = "Date"; + public static final String[] X_LABELS = { X_DATA_FILENAME_LABEL, X_DATA_DATE_LABEL }; + protected BufferedImage image = null; + + public AbstractChart() { + super(); + } + + public String getXAxis() { + return getPropertyAsString(REPORT_CHART_X_AXIS); + } + + public String getFormattedXAxis() { + String text = getXAxis(); + if (text.indexOf('.') > -1) { + text = text.substring(text.indexOf('.') + 1); + text = JOrphanUtils.replaceAllChars(text,'_'," "); + } + return text; + } + public void setXAxis(String field) { + setProperty(REPORT_CHART_X_AXIS,field); + } + + public String getYAxis() { + return getPropertyAsString(REPORT_CHART_Y_AXIS); + } + + public void setYAxis(String scale) { + setProperty(REPORT_CHART_Y_AXIS,scale); + } + + public String getXLabel() { + return getPropertyAsString(REPORT_CHART_X_LABEL); + } + + /** + * The X data labels should be either the filename, date or some + * other series of values + * @param label + */ + public void setXLabel(String label) { + setProperty(REPORT_CHART_X_LABEL,label); + } + + public String getYLabel() { + return getPropertyAsString(REPORT_CHART_Y_LABEL); + } + + public void setYLabel(String label) { + setProperty(REPORT_CHART_Y_LABEL,label); + } + + /** + * The title is a the name for the chart. A page link will + * be generated using the title. The title will also be + * used for a page index. + * @return chart title + */ + public String getTitle() { + return getPropertyAsString(REPORT_CHART_TITLE); + } + + /** + * The title is a the name for the chart. A page link will + * be generated using the title. The title will also be + * used for a page index. + * @param title + */ + public void setTitle(String title) { + setProperty(REPORT_CHART_TITLE,title); + } + + /** + * The caption is a description for the chart explaining + * what the chart means. + * @return caption + */ + public String getCaption() { + return getPropertyAsString(REPORT_CHART_CAPTION); + } + + /** + * The caption is a description for the chart explaining + * what the chart means. + * @param caption + */ + public void setCaption(String caption) { + setProperty(REPORT_CHART_CAPTION,caption); + } + + /** + * if no width is set, the default is returned + * @return width + */ + public int getWidth() { + int w = getPropertyAsInt(REPORT_CHART_WIDTH); + if (w <= 0) { + return DEFAULT_WIDTH; + } else { + return w; + } + } + + /** + * set the width of the graph + * @param width + */ + public void setWidth(String width) { + setProperty(REPORT_CHART_WIDTH,String.valueOf(width)); + } + + /** + * if the height is not set, the default is returned + * @return height + */ + public int getHeight() { + int h = getPropertyAsInt(REPORT_CHART_HEIGHT); + if (h <= 0) { + return DEFAULT_HEIGHT; + } else { + return h; + } + } + + /** + * set the height of the graph + * @param height + */ + public void setHeight(String height) { + setProperty(REPORT_CHART_HEIGHT,String.valueOf(height)); + } + + /** + * Subclasses will need to implement the method by doing the following: + * 1. get the x and y axis + * 2. filter the table data + * 3. pass the data to the chart library + * 4. return the generated chart + */ + public abstract JComponent renderChart(List data); + + /** + * this makes it easy to get the bufferedImage + * @return image + */ + public BufferedImage getBufferedImage() { + return this.image; + } + + /** + * in case an user wants set the bufferdImage + * @param img + */ + public void setBufferedImage(BufferedImage img) { + this.image = img; + } + + /** + * convienance method for getting the selected value. Rather than use + * Method.invoke(Object,Object[]), it's simpler to just check which + * column is selected and call the method directly. + * @param stat + * @return value + */ + public double getValue(SamplingStatCalculator stat) { + if (this.getXAxis().equals(AbstractTable.REPORT_TABLE_50_PERCENT)) { + return stat.getPercentPoint(.50).doubleValue(); + } else if (this.getXAxis().equals(AbstractTable.REPORT_TABLE_90_PERCENT)){ + return stat.getPercentPoint(.90).doubleValue(); + } else if (this.getXAxis().equals(AbstractTable.REPORT_TABLE_ERROR_RATE)) { + return stat.getErrorPercentage(); + } else if (this.getXAxis().equals(AbstractTable.REPORT_TABLE_MAX)) { + return stat.getMax().doubleValue(); + } else if (this.getXAxis().equals(AbstractTable.REPORT_TABLE_MEAN)) { + return stat.getMean(); + } else if (this.getXAxis().equals(AbstractTable.REPORT_TABLE_MEDIAN)) { + return stat.getMedian().doubleValue(); + } else if (this.getXAxis().equals(AbstractTable.REPORT_TABLE_MIN)) { + return stat.getMin().doubleValue(); + } else if (this.getXAxis().equals(AbstractTable.REPORT_TABLE_RESPONSE_RATE)) { + return stat.getRate(); + } else if (this.getXAxis().equals(AbstractTable.REPORT_TABLE_TRANSFER_RATE)) { + // return the pagesize divided by 1024 to get kilobytes + return stat.getKBPerSecond(); + } else { + return Double.NaN; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractScopedAssertion.java b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractScopedAssertion.java new file mode 100644 index 0000000..51a45cf --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractScopedAssertion.java @@ -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. + * + */ + +package org.apache.jmeter.testelement; + +/** + *

+ * Super-class for all Assertions that can be applied to main sample, sub-samples or both. + * Test elements merely need to extend this class to support scoping. + *

+ * + *

+ * Their corresponding GUI classes need to add the AssertionScopePanel to the GUI + * using the AbstractAssertionGui methods: + *

    + *
  • createScopePanel()
  • + *
  • saveScopeSettings()
  • + *
  • showScopeSettings()
  • + *
+ *

+ */ +public abstract class AbstractScopedAssertion extends AbstractScopedTestElement { + + private static final long serialVersionUID = 240L; + + //+ JMX attributes - do not change + private static final String SCOPE = "Assertion.scope"; + //- JMX + + @Override + protected String getScopeName() { + return SCOPE; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractScopedTestElement.java b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractScopedTestElement.java new file mode 100644 index 0000000..358eef6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractScopedTestElement.java @@ -0,0 +1,169 @@ +/* + * 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.testelement; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.jmeter.samplers.SampleResult; + +/** + *

+ * Super-class for TestElements that can be applied to main sample, sub-samples or both. + * [Assertions use a different class because they use a different value for the {@link #getScopeName()} constant] + *

+ * + *

+ * Their corresponding GUI classes need to add the ScopePanel to the GUI + * using the AbstractXXXGui methods: + *

    + *
  • createScopePanel()
  • + *
  • saveScopeSettings()
  • + *
  • showScopeSettings()
  • + *
+ *

+ */ +public abstract class AbstractScopedTestElement extends AbstractTestElement { + + private static final long serialVersionUID = 240L; + + //+ JMX attributes - do not change + private static final String SCOPE = "Sample.scope"; // $NON-NLS-1$ + private static final String SCOPE_PARENT = "parent"; // $NON-NLS-1$ + private static final String SCOPE_CHILDREN = "children"; // $NON-NLS-1$ + private static final String SCOPE_ALL = "all"; // $NON-NLS-1$ + private static final String SCOPE_VARIABLE = "variable"; // $NON-NLS-1$ + private static final String SCOPE_VARIABLE_NAME = "Scope.variable"; // $NON-NLS-1$ + //- JMX + + + protected String getScopeName() { + return SCOPE; + } + + /** + * Get the scope setting + * @return the scope, default parent + */ + public String fetchScope() { + return getPropertyAsString(getScopeName(), SCOPE_PARENT); + } + + /** + * Is the assertion to be applied to the main (parent) sample? + * + * @param scope + * @return if the assertion is to be applied to the parent sample. + */ + public boolean isScopeParent(String scope) { + return scope.equals(SCOPE_PARENT); + } + + /** + * Is the assertion to be applied to the sub-samples (children)? + * + * @param scope + * @return if the assertion is to be applied to the children. + */ + public boolean isScopeChildren(String scope) { + return scope.equals(SCOPE_CHILDREN); + } + + /** + * Is the assertion to be applied to the all samples? + * + * @param scope + * @return if the assertion is to be applied to the all samples. + */ + public boolean isScopeAll(String scope) { + return scope.equals(SCOPE_ALL); + } + + /** + * Is the assertion to be applied to the all samples? + * + * @param scope + * @return if the assertion is to be applied to the all samples. + */ + public boolean isScopeVariable(String scope) { + return scope.equals(SCOPE_VARIABLE); + } + + /** + * Is the assertion to be applied to the all samples? + * + * @return if the assertion is to be applied to the all samples. + */ + protected boolean isScopeVariable() { + return isScopeVariable(fetchScope()); + } + + public String getVariableName(){ + return getPropertyAsString(SCOPE_VARIABLE_NAME, ""); + } + + public void setScopeParent() { + removeProperty(getScopeName()); + } + + public void setScopeChildren() { + setProperty(getScopeName(), SCOPE_CHILDREN); + } + + public void setScopeAll() { + setProperty(getScopeName(), SCOPE_ALL); + } + + public void setScopeVariable(String variableName) { + setProperty(getScopeName(), SCOPE_VARIABLE); + setProperty(SCOPE_VARIABLE_NAME, variableName); + } + + /** + * Generate a list of qualifying sample results, + * depending on the scope. + * + * @param result current sample + * @return list containing the current sample and/or its child samples + */ + protected List getSampleList(SampleResult result) { + List sampleList = new ArrayList(); + + String scope = fetchScope(); + if (isScopeParent(scope) || isScopeAll(scope)) { + sampleList.add(result); + } + if (isScopeChildren(scope) || isScopeAll(scope)) { + for (SampleResult subResult : result.getSubResults()) { + sampleList.add(subResult); + } + } + return sampleList; + } + + /** + * {@inheritDoc}} + */ + @Override + public List getSearchableTokens() throws Exception { + List result = super.getSearchableTokens(); + result.add(getVariableName()); + return result; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTable.java b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTable.java new file mode 100644 index 0000000..0386d1f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTable.java @@ -0,0 +1,150 @@ +/* + * 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.testelement; + +import java.util.List; + +import org.apache.jmeter.report.ReportTable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * AbstractTable is the base Element for different kinds of report tables. + * + */ +public abstract class AbstractTable extends AbstractTestElement + implements ReportTable +{ + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String REPORT_TABLE_MEAN = "ReportTable.Mean"; + public static final String REPORT_TABLE_MEDIAN = "ReportTable.Median"; + public static final String REPORT_TABLE_MAX = "ReportTable.Max"; + public static final String REPORT_TABLE_MIN = "ReportTable.Min"; + public static final String REPORT_TABLE_RESPONSE_RATE = "ReportTable.Response_rate"; + public static final String REPORT_TABLE_TRANSFER_RATE = "ReportTable.Transfer_rate"; + public static final String REPORT_TABLE_50_PERCENT = "ReportTable.50_percent"; + public static final String REPORT_TABLE_90_PERCENT = "ReportTable.90_percent"; + public static final String REPORT_TABLE_ERROR_RATE = "ReportTable.Error.rate"; + public static final String[] items = { + REPORT_TABLE_MEAN, REPORT_TABLE_MEDIAN, REPORT_TABLE_MAX, REPORT_TABLE_MIN, + REPORT_TABLE_RESPONSE_RATE, REPORT_TABLE_TRANSFER_RATE, REPORT_TABLE_50_PERCENT, + REPORT_TABLE_90_PERCENT, REPORT_TABLE_ERROR_RATE }; + + public static final String REPORT_TABLE_TOTAL = "ReportTable.total"; + public static final String REPORT_TABLE_URL = "ReportTable.url"; + + public static final String[] xitems = { REPORT_TABLE_TOTAL, + REPORT_TABLE_URL }; + + public AbstractTable() { + super(); + } + + public boolean getMean() { + return getPropertyAsBoolean(REPORT_TABLE_MEAN); + } + + public void setMean(String set) { + setProperty(REPORT_TABLE_MEAN,set); + } + + public boolean getMedian() { + return getPropertyAsBoolean(REPORT_TABLE_MEDIAN); + } + + public void setMedian(String set) { + setProperty(REPORT_TABLE_MEDIAN,set); + } + + public boolean getMax() { + return getPropertyAsBoolean(REPORT_TABLE_MAX); + } + + public void setMax(String set) { + setProperty(REPORT_TABLE_MAX,set); + } + + public boolean getMin() { + return getPropertyAsBoolean(REPORT_TABLE_MIN); + } + + public void setMin(String set) { + setProperty(REPORT_TABLE_MIN,set); + } + + public boolean getResponseRate() { + return getPropertyAsBoolean(REPORT_TABLE_RESPONSE_RATE); + } + + public void setResponseRate(String set) { + setProperty(REPORT_TABLE_RESPONSE_RATE,set); + } + + public boolean getTransferRate() { + return getPropertyAsBoolean(REPORT_TABLE_TRANSFER_RATE); + } + + public void setTransferRate(String set) { + setProperty(REPORT_TABLE_TRANSFER_RATE,set); + } + + public boolean get50Percent() { + return getPropertyAsBoolean(REPORT_TABLE_50_PERCENT); + } + + public void set50Percent(String set) { + setProperty(REPORT_TABLE_50_PERCENT,set); + } + + public boolean get90Percent() { + return getPropertyAsBoolean(REPORT_TABLE_90_PERCENT); + } + + public void set90Percent(String set) { + setProperty(REPORT_TABLE_90_PERCENT,set); + } + + public boolean getErrorRate() { + return getPropertyAsBoolean(REPORT_TABLE_ERROR_RATE); + } + + public void setErrorRate(String set) { + setProperty(REPORT_TABLE_ERROR_RATE,set); + } + + @Override + public void addTestElement(TestElement el) { + if (el != null) { + super.addTestElement(el); + log.info("TestElement: " + el.getClass().getName()); + } + } + + /** + * method isn't implemented and is left abstract. Subclasses + * need to filter the data in the list and return statistics. + * The statistics should be like the aggregate listener. + */ + @SuppressWarnings("rawtypes") // TODO fix this when there is a real implementation + public abstract String[][] getTableData(List data); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTestElement.java b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTestElement.java new file mode 100644 index 0000000..23ed35d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTestElement.java @@ -0,0 +1,586 @@ +/* + * 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.testelement; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.jmeter.gui.Searchable; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.MapProperty; +import org.apache.jmeter.testelement.property.MultiProperty; +import org.apache.jmeter.testelement.property.NullProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.testelement.property.PropertyIteratorImpl; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + */ +public abstract class AbstractTestElement implements TestElement, Serializable, Searchable { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Map propMap = + Collections.synchronizedMap(new LinkedHashMap()); + + /** + * Holds properties added when isRunningVersion is true + */ + private transient Set temporaryProperties; + + private transient boolean runningVersion = false; + + // Thread-specific variables saved here to save recalculation + private transient JMeterContext threadContext = null; + + private transient String threadName = null; + + @Override + public Object clone() { + try { + TestElement clonedElement = this.getClass().newInstance(); + + PropertyIterator iter = propertyIterator(); + while (iter.hasNext()) { + clonedElement.setProperty(iter.next().clone()); + } + clonedElement.setRunningVersion(runningVersion); + return clonedElement; + } catch (InstantiationException e) { + throw new AssertionError(e); // clone should never return null + } catch (IllegalAccessException e) { + throw new AssertionError(e); // clone should never return null + } + } + + /** + * {@inheritDoc} + */ + public void clear() { + propMap.clear(); + } + + /** + * {@inheritDoc} + *

+ * Default implementation - does nothing + */ + public void clearTestElementChildren(){ + // NOOP + } + + /** + * {@inheritDoc} + */ + public void removeProperty(String key) { + propMap.remove(key); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (o instanceof AbstractTestElement) { + return ((AbstractTestElement) o).propMap.equals(propMap); + } else { + return false; + } + } + + // TODO temporary hack to avoid unnecessary bug reports for subclasses + + /** + * {@inheritDoc} + */ + @Override + public int hashCode(){ + return System.identityHashCode(this); + } + + /* + * URGENT: TODO - sort out equals and hashCode() - at present equal + * instances can/will have different hashcodes - problem is, when a proper + * hashcode is used, tests stop working, e.g. listener data disappears when + * switching views... This presumably means that instances currently + * regarded as equal, aren't really equal. + * + * @see java.lang.Object#hashCode() + */ + // This would be sensible, but does not work: + // public int hashCode() + // { + // return propMap.hashCode(); + // } + + /** + * {@inheritDoc} + */ + public void addTestElement(TestElement el) { + mergeIn(el); + } + + public void setName(String name) { + setProperty(TestElement.NAME, name); + } + + public String getName() { + return getPropertyAsString(TestElement.NAME); + } + + public void setComment(String comment){ + setProperty(new StringProperty(TestElement.COMMENTS, comment)); + } + + public String getComment(){ + return getProperty(TestElement.COMMENTS).getStringValue(); + } + + /** + * Get the named property. If it doesn't exist, a new NullProperty object is + * created with the same name and returned. + */ + public JMeterProperty getProperty(String key) { + JMeterProperty prop = propMap.get(key); + if (prop == null) { + prop = new NullProperty(key); + } + return prop; + } + + public void traverse(TestElementTraverser traverser) { + PropertyIterator iter = propertyIterator(); + traverser.startTestElement(this); + while (iter.hasNext()) { + traverseProperty(traverser, iter.next()); + } + traverser.endTestElement(this); + } + + protected void traverseProperty(TestElementTraverser traverser, JMeterProperty value) { + traverser.startProperty(value); + if (value instanceof TestElementProperty) { + ((TestElement) value.getObjectValue()).traverse(traverser); + } else if (value instanceof CollectionProperty) { + traverseCollection((CollectionProperty) value, traverser); + } else if (value instanceof MapProperty) { + traverseMap((MapProperty) value, traverser); + } + traverser.endProperty(value); + } + + protected void traverseMap(MapProperty map, TestElementTraverser traverser) { + PropertyIterator iter = map.valueIterator(); + while (iter.hasNext()) { + traverseProperty(traverser, iter.next()); + } + } + + protected void traverseCollection(CollectionProperty col, TestElementTraverser traverser) { + PropertyIterator iter = col.iterator(); + while (iter.hasNext()) { + traverseProperty(traverser, iter.next()); + } + } + + public int getPropertyAsInt(String key) { + return getProperty(key).getIntValue(); + } + + public int getPropertyAsInt(String key, int defaultValue) { + JMeterProperty jmp = getProperty(key); + return jmp instanceof NullProperty ? defaultValue : jmp.getIntValue(); + } + + public boolean getPropertyAsBoolean(String key) { + return getProperty(key).getBooleanValue(); + } + + public boolean getPropertyAsBoolean(String key, boolean defaultVal) { + JMeterProperty jmp = getProperty(key); + return jmp instanceof NullProperty ? defaultVal : jmp.getBooleanValue(); + } + + public float getPropertyAsFloat(String key) { + return getProperty(key).getFloatValue(); + } + + public long getPropertyAsLong(String key) { + return getProperty(key).getLongValue(); + } + + public long getPropertyAsLong(String key, long defaultValue) { + JMeterProperty jmp = getProperty(key); + return jmp instanceof NullProperty ? defaultValue : jmp.getLongValue(); + } + + public double getPropertyAsDouble(String key) { + return getProperty(key).getDoubleValue(); + } + + public String getPropertyAsString(String key) { + return getProperty(key).getStringValue(); + } + + public String getPropertyAsString(String key, String defaultValue) { + JMeterProperty jmp = getProperty(key); + return jmp instanceof NullProperty ? defaultValue : jmp.getStringValue(); + } + + /** + * Add property to test element + * @param property {@link JMeterProperty} to add to current Test Element + * @param clone clone property + */ + protected void addProperty(JMeterProperty property, boolean clone) { + JMeterProperty propertyToPut = property; + if(clone) { + propertyToPut = property.clone(); + } + if (isRunningVersion()) { + setTemporary(propertyToPut); + } else { + clearTemporary(property); + } + JMeterProperty prop = getProperty(property.getName()); + + if (prop instanceof NullProperty || (prop instanceof StringProperty && prop.getStringValue().equals(""))) { + propMap.put(property.getName(), propertyToPut); + } else { + prop.mergeIn(propertyToPut); + } + } + + /** + * Add property to test element without cloning it + * @param property {@link JMeterProperty} + */ + protected void addProperty(JMeterProperty property) { + addProperty(property, false); + } + + /** + * Remove property from temporaryProperties + * @param property {@link JMeterProperty} + */ + protected void clearTemporary(JMeterProperty property) { + if (temporaryProperties != null) { + temporaryProperties.remove(property); + } + } + + /** + * Log the properties of the test element + * + * @see TestElement#setProperty(JMeterProperty) + */ + protected void logProperties() { + if (log.isDebugEnabled()) { + PropertyIterator iter = propertyIterator(); + while (iter.hasNext()) { + JMeterProperty prop = iter.next(); + log.debug("Property " + prop.getName() + " is temp? " + isTemporary(prop) + " and is a " + + prop.getObjectValue()); + } + } + } + + public void setProperty(JMeterProperty property) { + if (isRunningVersion()) { + if (getProperty(property.getName()) instanceof NullProperty) { + addProperty(property); + } else { + getProperty(property.getName()).setObjectValue(property.getObjectValue()); + } + } else { + propMap.put(property.getName(), property); + } + } + + public void setProperty(String name, String value) { + setProperty(new StringProperty(name, value)); + } + + /** + * Create a String property - but only if it is not the default. + * This is intended for use when adding new properties to JMeter + * so that JMX files are not expanded unnecessarily. + * + * N.B. - must agree with the default applied when reading the property. + * + * @param name property name + * @param value current value + * @param dflt default + */ + public void setProperty(String name, String value, String dflt) { + if (dflt.equals(value)) { + removeProperty(name); + } else { + setProperty(new StringProperty(name, value)); + } + } + + public void setProperty(String name, boolean value) { + setProperty(new BooleanProperty(name, value)); + } + + /** + * Create a boolean property - but only if it is not the default. + * This is intended for use when adding new properties to JMeter + * so that JMX files are not expanded unnecessarily. + * + * N.B. - must agree with the default applied when reading the property. + * + * @param name property name + * @param value current value + * @param dflt default + */ + public void setProperty(String name, boolean value, boolean dflt) { + if (value == dflt) { + removeProperty(name); + } else { + setProperty(new BooleanProperty(name, value)); + } + } + + public void setProperty(String name, int value) { + setProperty(new IntegerProperty(name, value)); + } + + /** + * Create a boolean property - but only if it is not the default. + * This is intended for use when adding new properties to JMeter + * so that JMX files are not expanded unnecessarily. + * + * N.B. - must agree with the default applied when reading the property. + * + * @param name property name + * @param value current value + * @param dflt default + */ + public void setProperty(String name, int value, int dflt) { + if (value == dflt) { + removeProperty(name); + } else { + setProperty(new IntegerProperty(name, value)); + } + } + + public PropertyIterator propertyIterator() { + return new PropertyIteratorImpl(propMap.values()); + } + + /** + * Add to this the properties of element (by reference) + * @param element {@link TestElement} + */ + protected void mergeIn(TestElement element) { + PropertyIterator iter = element.propertyIterator(); + while (iter.hasNext()) { + JMeterProperty prop = iter.next(); + addProperty(prop, false); + } + } + + /** + * Returns the runningVersion. + */ + public boolean isRunningVersion() { + return runningVersion; + } + + /** + * Sets the runningVersion. + * + * @param runningVersion + * the runningVersion to set + */ + public void setRunningVersion(boolean runningVersion) { + this.runningVersion = runningVersion; + PropertyIterator iter = propertyIterator(); + while (iter.hasNext()) { + iter.next().setRunningVersion(runningVersion); + } + } + + /** + * {@inheritDoc} + */ + public void recoverRunningVersion() { + Iterator> iter = propMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + JMeterProperty prop = entry.getValue(); + if (isTemporary(prop)) { + iter.remove(); + clearTemporary(prop); + } else { + prop.recoverRunningVersion(this); + } + } + emptyTemporary(); + } + + /** + * Clears temporaryProperties + */ + protected void emptyTemporary() { + if (temporaryProperties != null) { + temporaryProperties.clear(); + } + } + + /** + * {@inheritDoc} + */ + public boolean isTemporary(JMeterProperty property) { + if (temporaryProperties == null) { + return false; + } else { + return temporaryProperties.contains(property); + } + } + + /** + * {@inheritDoc} + */ + public void setTemporary(JMeterProperty property) { + if (temporaryProperties == null) { + temporaryProperties = new LinkedHashSet(); + } + temporaryProperties.add(property); + if (property instanceof MultiProperty) { + PropertyIterator iter = ((MultiProperty) property).iterator(); + while (iter.hasNext()) { + setTemporary(iter.next()); + } + } + } + + /** + * @return Returns the threadContext. + */ + public JMeterContext getThreadContext() { + if (threadContext == null) { + /* + * Only samplers have the thread context set up by JMeterThread at + * present, so suppress the warning for now + */ + // log.warn("ThreadContext was not set up - should only happen in + // JUnit testing..." + // ,new Throwable("Debug")); + threadContext = JMeterContextService.getContext(); + } + return threadContext; + } + + /** + * @param inthreadContext + * The threadContext to set. + */ + public void setThreadContext(JMeterContext inthreadContext) { + if (threadContext != null) { + if (inthreadContext != threadContext) { + throw new RuntimeException("Attempting to reset the thread context"); + } + } + this.threadContext = inthreadContext; + } + + /** + * @return Returns the threadName. + */ + public String getThreadName() { + return threadName; + } + + /** + * @param inthreadName + * The threadName to set. + */ + public void setThreadName(String inthreadName) { + if (threadName != null) { + if (!threadName.equals(inthreadName)) { + throw new RuntimeException("Attempting to reset the thread name"); + } + } + this.threadName = inthreadName; + } + + public AbstractTestElement() { + super(); + } + + /** + * {@inheritDoc} + */ + // Default implementation + public boolean canRemove() { + return true; + } + + /** + * {@inheritDoc} + */ + // Moved from JMeter class + public boolean isEnabled() { + return getProperty(TestElement.ENABLED) instanceof NullProperty || getPropertyAsBoolean(TestElement.ENABLED); + } + + /** + * {@inheritDoc}} + */ + public List getSearchableTokens() throws Exception { + List result = new ArrayList(2); + result.add(getComment()); + result.add(getName()); + return result; + } + + /** + * Add to result the values of propertyNames + * @param result List values of propertyNames + * @param propertyNames Set properties to extract + */ + protected final void addPropertiesValues(List result, Set propertyNames) { + PropertyIterator iterator = propertyIterator(); + while(iterator.hasNext()) { + JMeterProperty jMeterProperty = iterator.next(); + if(propertyNames.contains(jMeterProperty.getName())) { + result.add(jMeterProperty.getStringValue()); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTestElementBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTestElementBeanInfo.java new file mode 100644 index 0000000..764c64b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/AbstractTestElementBeanInfo.java @@ -0,0 +1,96 @@ +/* + * 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.testelement; + +import java.awt.Image; +import java.beans.BeanDescriptor; +import java.beans.BeanInfo; +import java.beans.EventSetDescriptor; +import java.beans.MethodDescriptor; +import java.beans.PropertyDescriptor; + +/** + * This is the BeanInfo object for the TestBean class. It acts as a "stopper" + * for the introspector: we don't want it to look at properties defined at this + * or higher classes. + *

+ * Note this is really needed since using Introspector.getBeanInfo with a stop + * class is not an option because: + *

    + *
  1. The API does not define a 3-parameter getBeanInfo in which you can use a + * stop class AND flags. [Why? I guess this is a bug in the spec.] + *
  2. java.beans.Introspector is buggy and, opposite to what's stated in the + * Javadocs, only results of getBeanInfo(Class) are actually cached. + *
+ * + * @version $Revision: 908219 $ + */ +public class AbstractTestElementBeanInfo implements BeanInfo { + + public BeanInfo[] getAdditionalBeanInfo() { + return new BeanInfo[0]; + } + + /** + * {@inheritDoc} + */ + public BeanDescriptor getBeanDescriptor() { + return null; + } + + /** + * {@inheritDoc} + */ + public int getDefaultEventIndex() { + return 0; + } + + /** + * {@inheritDoc} + */ + public int getDefaultPropertyIndex() { + return 0; + } + + /** + * {@inheritDoc} + */ + public EventSetDescriptor[] getEventSetDescriptors() { + return new EventSetDescriptor[0]; + } + + /** + * {@inheritDoc} + */ + public Image getIcon(int iconKind) { + return null; + } + + /** + * {@inheritDoc} + */ + public MethodDescriptor[] getMethodDescriptors() { + return new MethodDescriptor[0]; + } + + /** + * {@inheritDoc} + */ + public PropertyDescriptor[] getPropertyDescriptors() { + return new PropertyDescriptor[0]; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/BarChart.java b/ApacheJmeter/src/org/apache/jmeter/testelement/BarChart.java new file mode 100644 index 0000000..b6b09f7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/BarChart.java @@ -0,0 +1,111 @@ +/* + * 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.testelement; + +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.swing.JComponent; + +import org.apache.jmeter.report.DataSet; +import org.apache.jmeter.visualizers.AxisGraph; +import org.apache.jmeter.visualizers.SamplingStatCalculator; + +/** + * The class is reponsible for returning + * + */ +public class BarChart extends AbstractChart { + + private static final long serialVersionUID = 240L; + + public static final String REPORT_BAR_CHART_URL = "ReportChart.bar.chart.url"; + + public BarChart() { + super(); + } + + public String getURL() { + return getPropertyAsString(REPORT_BAR_CHART_URL); + } + + public void setURL(String url) { + setProperty(REPORT_BAR_CHART_URL,url); + } + + /** + * Convert the data from SamplingStatCalculator to double array of array + * @param data + * @return data values + */ + public double[][] convertToDouble(List data) { + double[][] dataset = new double[1][data.size()]; + //Iterator itr = data.iterator(); + for (int idx=0; idx < data.size(); idx++) { + SamplingStatCalculator stat = data.get(idx); + dataset[0][idx] = getValue(stat); + } + return dataset; + } + + @Override + public JComponent renderChart(List data) { + ArrayList dset = new ArrayList(); + ArrayList xlabels = new ArrayList(); + Iterator itr = data.iterator(); + while (itr.hasNext()) { + DataSet item = itr.next(); + SamplingStatCalculator ss = item.getStatistics(this.getURL()); + if (ss != null) { + // we add the entry + dset.add(ss); + if ( getXLabel().equals(X_DATA_FILENAME_LABEL) ) { + xlabels.add(item.getDataSourceName()); + } else { + xlabels.add(item.getMonthDayYearDate()); + } + } + } + double[][] dbset = convertToDouble(dset); + return renderGraphics(dbset, xlabels.toArray(new String[xlabels.size()])); + } + + public JComponent renderGraphics(double[][] data, String[] xAxisLabels) { + AxisGraph panel = new AxisGraph(); + panel.setTitle(this.getTitle()); + panel.setData(data); + panel.setXAxisLabels(xAxisLabels); + panel.setYAxisLabels(this.getYLabel()); + panel.setXAxisTitle(this.getFormattedXAxis()); + panel.setYAxisTitle(this.getYAxis()); + // we should make this configurable eventually + int width = getWidth(); + int height = getHeight(); + panel.setPreferredSize(new Dimension(width,height)); + panel.setSize(new Dimension(width,height)); + panel.setWidth(width); + panel.setHeight(width); + setBufferedImage(new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB)); + panel.paintComponent(this.getBufferedImage().createGraphics()); + return panel; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/JTLData.java b/ApacheJmeter/src/org/apache/jmeter/testelement/JTLData.java new file mode 100644 index 0000000..ed4341b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/JTLData.java @@ -0,0 +1,218 @@ +/* + * 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.testelement; + +import java.io.File; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.apache.jmeter.report.DataSet; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.visualizers.SamplingStatCalculator; + +/** + * + * The purpose of TableData is to contain the results of a single .jtl file. + * It is equivalent to what the AggregateListener table. A HashMap is used + * to store the data. The URL is the key and the value is SamplingStatCalculator + */ +public class JTLData implements Serializable, DataSet { + + private static final long serialVersionUID = 240L; + + private final HashMap data = new HashMap(); + private String jtl_file = null; + private long startTimestamp = 0; + private long endTimestamp = 0; + private transient File inputFile = null; + + /** + * + */ + public JTLData() { + super(); + } + + /** + * Return a Set of the URLs + * @return set of URLs + */ + public Set getURLs() { + return this.data.keySet(); + } + + /** + * Return a Set of the values + * @return values + */ + public Set getStats() { + return (Set) this.data.values(); + } + + /** + * The purpose of the method is to make it convienant to pass a list + * of the URLs and return a list of the SamplingStatCalculators. If + * no URLs match, the list is empty. + * TODO - this method seems to be wrong - it does not agree with the Javadoc + * The SamplingStatCalculators will be returned in the same sequence + * as the url list. + * @param urls + * @return array list of non-null entries (may be empty) + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) // Method is broken anyway + public List getStats(List urls) { + ArrayList items = new ArrayList(); + Iterator itr = urls.iterator(); + if (itr.hasNext()) { + SamplingStatCalculator row = (SamplingStatCalculator)itr.next(); + if (row != null) { + items.add(row); + } + } + return items; + } + + public void setDataSource(String absolutePath) { + this.jtl_file = absolutePath; + } + + public String getDataSource() { + return this.jtl_file; + } + + public String getDataSourceName() { + if (inputFile == null) { + inputFile = new File(getDataSource()); + } + return inputFile.getName().substring(0,inputFile.getName().length() - 4); + } + + public void setStartTimestamp(long stamp) { + this.startTimestamp = stamp; + } + + public long getStartTimestamp() { + return this.startTimestamp; + } + + public void setEndTimestamp(long stamp) { + this.endTimestamp = stamp; + } + + public long getEndTimestamp() { + return this.endTimestamp; + } + + /** + * The date we use for the result is the start timestamp. The + * reasoning is that a test may run for a long time, but it + * is most likely scheduled to run using CRON on unix or + * scheduled task in windows. + * @return start time + */ + public Date getDate() { + return new Date(this.startTimestamp); + } + + public String getMonthDayDate() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(this.startTimestamp); + return String.valueOf(cal.get(Calendar.MONTH)) + " - " + + String.valueOf(cal.get(Calendar.DAY_OF_MONTH)); + } + + public String getMonthDayYearDate() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(this.startTimestamp); + return String.valueOf(cal.get(Calendar.MONTH)) + " - " + + String.valueOf(cal.get(Calendar.DAY_OF_MONTH)) + " - " + + String.valueOf(cal.get(Calendar.YEAR)); + } + + /** + * The method will SamplingStatCalculator for the given URL. If the URL + * doesn't exist, the method returns null. + * @param url + * @return data for this URL + */ + public SamplingStatCalculator getStatistics(String url) { + if (this.data.containsKey(url)) { + return this.data.get(url); + } else { + return null; + } + } + + /** + * The implementation loads a single .jtl file and cleans up the + * ResultCollector. + */ + public void loadData() { + if (this.getDataSource() != null) { + ResultCollector rc = new ResultCollector(); + rc.setFilename(this.getDataSource()); + rc.setListener(this); + rc.loadExistingFile(); + // we clean up the ResultCollector to make sure there's + // no slow leaks + rc.clear(); + rc.setListener(null); + } + } + + /** + * the implementation will set the start timestamp if the HashMap + * is empty. otherwise it will set the end timestamp using the + * end time + */ + public void add(SampleResult sample) { + if (data.size() == 0) { + this.startTimestamp = sample.getStartTime(); + } else { + this.endTimestamp = sample.getEndTime(); + } + // now add the samples to the HashMap + String url = sample.getSampleLabel(); + if (url == null) { + url = sample.getURL().toString(); + } + SamplingStatCalculator row = data.get(url); + if (row == null) { + row = new SamplingStatCalculator(url); + // just like the aggregate listener, we use the sample label to represent + // a row. in this case, we use it as a key. + this.data.put(url,row); + } + row.addSample(sample); + } + + /** + * By default, the method always returns true. Subclasses can over + * ride the implementation. + */ + public boolean isStats() { + return true; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/LineChart.java b/ApacheJmeter/src/org/apache/jmeter/testelement/LineChart.java new file mode 100644 index 0000000..54dd265 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/LineChart.java @@ -0,0 +1,155 @@ +/* + * 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.testelement; + +import java.awt.BasicStroke; +import java.awt.Dimension; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.swing.JComponent; + +import org.apache.jmeter.report.DataSet; +import org.apache.jmeter.visualizers.LineGraph; +import org.apache.jmeter.visualizers.SamplingStatCalculator; +import org.jCharts.properties.PointChartProperties; + +public class LineChart extends AbstractChart { + + private static final long serialVersionUID = 240L; + + private static final String URL_DELIM = ","; //$NON-NLS-1$ + private static final String REPORT_CHART_URLS = "ReportChart.chart.urls"; //$NON-NLS-1$ + private static final Shape[] SHAPE_ARRAY = {PointChartProperties.SHAPE_CIRCLE, + PointChartProperties.SHAPE_DIAMOND,PointChartProperties.SHAPE_SQUARE, + PointChartProperties.SHAPE_TRIANGLE}; + + private int shape_counter = 0; + + public LineChart() { + super(); + } + + public String getURLs() { + return getPropertyAsString(REPORT_CHART_URLS); + } + + public void setURLs(String urls) { + setProperty(REPORT_CHART_URLS,urls); + } + + private double[][] convertToDouble(List data) { + String[] urls = this.getURLs().split(URL_DELIM); + double[][] dataset = new double[urls.length][data.size()]; + for (int idx=0; idx < urls.length; idx++) { + for (int idz=0; idz < data.size(); idz++) { + DataSet dset = data.get(idz); + SamplingStatCalculator ss = dset.getStatistics(urls[idx]); + dataset[idx][idz] = getValue(ss); + } + } + return dataset; + } + + @Override + public JComponent renderChart(List dataset) { + ArrayList dset = new ArrayList(); + ArrayList xlabels = new ArrayList(); + Iterator itr = dataset.iterator(); + while (itr.hasNext()) { + DataSet item = itr.next(); + if (item != null) { + // we add the entry + dset.add(item); + if ( getXLabel().equals(X_DATA_FILENAME_LABEL) ) { + xlabels.add(item.getDataSourceName()); + } else { + xlabels.add(item.getMonthDayYearDate()); + } + } + } + double[][] dbset = convertToDouble(dset); + return renderGraphics(dbset, xlabels.toArray(new String[xlabels.size()])); + } + + public JComponent renderGraphics(double[][] data, String[] xAxisLabels) { + LineGraph panel = new LineGraph(); + panel.setTitle(this.getTitle()); + panel.setData(data); + panel.setXAxisLabels(xAxisLabels); + panel.setYAxisLabels(this.getURLs().split(URL_DELIM)); + panel.setXAxisTitle(this.getFormattedXAxis()); + panel.setYAxisTitle(this.getYAxis()); + // we should make this configurable eventually + int _width = getWidth(); + int _height = getHeight(); + panel.setPreferredSize(new Dimension(_width,_height)); + panel.setSize(new Dimension(_width,_height)); + panel.setWidth(_width); + panel.setHeight(_width); + setBufferedImage(new BufferedImage(_width,_height,BufferedImage.TYPE_INT_RGB)); + panel.paintComponent(this.getBufferedImage().createGraphics()); + return panel; + } + + /** + * Since we only have 4 shapes, the method will start with the + * first shape and keep cycling through the shapes in order. + * @param count + * @return array of shapes + */ + public Shape[] createShapes(int count) { + Shape[] shapes = new Shape[count]; + for (int idx=0; idx < count; idx++) { + shapes[idx] = nextShape(); + } + return shapes; + } + + /** + * Return the next shape + * @return the shape + */ + public Shape nextShape() { + if (shape_counter >= (SHAPE_ARRAY.length - 1)) { + shape_counter = 0; + } + return SHAPE_ARRAY[shape_counter]; + } + + /** + * + * @param count + * @return array of strokes + */ + public Stroke[] createStrokes(int count) { + Stroke[] str = new Stroke[count]; + for (int idx=0; idx < count; idx++) { + str[idx] = nextStroke(); + } + return str; + } + + public Stroke nextStroke() { + return new BasicStroke(1.5f); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/OnErrorTestElement.java b/ApacheJmeter/src/org/apache/jmeter/testelement/OnErrorTestElement.java new file mode 100644 index 0000000..b93ae8c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/OnErrorTestElement.java @@ -0,0 +1,73 @@ +/* + * 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. + * + */ + +/* + * Created on Dec 9, 2003 + * + */ +package org.apache.jmeter.testelement; + +import org.apache.jmeter.testelement.property.IntegerProperty; + +/** + * @version $Revision: 905027 $ + */ +public abstract class OnErrorTestElement extends AbstractTestElement { + private static final long serialVersionUID = 240L; + + /* Action to be taken when a Sampler error occurs */ + public final static int ON_ERROR_CONTINUE = 0; + + public final static int ON_ERROR_STOPTHREAD = 1; + + public final static int ON_ERROR_STOPTEST = 2; + + public final static int ON_ERROR_STOPTEST_NOW = 3; + + /* Property name */ + public final static String ON_ERROR_ACTION = "OnError.action"; + + protected OnErrorTestElement() { + super(); + } + + public void setErrorAction(int value) { + setProperty(new IntegerProperty(ON_ERROR_ACTION, value)); + } + + public int getErrorAction() { + int value = getPropertyAsInt(ON_ERROR_ACTION); + return value; + } + + public boolean isContinue() { + return getErrorAction() == ON_ERROR_CONTINUE; + } + + public boolean isStopThread() { + return getErrorAction() == ON_ERROR_STOPTHREAD; + } + + public boolean isStopTest() { + return getErrorAction() == ON_ERROR_STOPTEST; + } + + public boolean isStopTestNow() { + return getErrorAction() == ON_ERROR_STOPTEST_NOW; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/ReportPage.java b/ApacheJmeter/src/org/apache/jmeter/testelement/ReportPage.java new file mode 100644 index 0000000..fae014c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/ReportPage.java @@ -0,0 +1,100 @@ +/* + * 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.testelement; + +import java.io.Serializable; +import org.apache.jmeter.testelement.AbstractTestElement; +//import org.apache.jorphan.logging.LoggingManager; +//import org.apache.log.Logger; + +/** + * ReportPage + * + */ +public class ReportPage extends AbstractTestElement implements Serializable { + private static final long serialVersionUID = 240L; + +// private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String REPORT_PAGE_TITLE = "ReportPage.title"; + public static final String REPORT_PAGE_INDEX = "ReportPage.index"; + public static final String REPORT_PAGE_CSS = "ReportPage.css"; + public static final String REPORT_PAGE_HEADER = "ReportPage.header"; + public static final String REPORT_PAGE_FOOTER = "ReportPage.footer"; + public static final String REPORT_PAGE_INTRO = "ReportPage.intro"; + + /** + * No-arg constructor. + */ + public ReportPage() { + } + + public static ReportPage createReportPage(String name) { + ReportPage page = new ReportPage(); + return page; + } + + public String getTitle() { + return getPropertyAsString(REPORT_PAGE_TITLE); + } + + public void setTitle(String title) { + setProperty(REPORT_PAGE_TITLE,title); + } + + public boolean getIndex() { + return getPropertyAsBoolean(REPORT_PAGE_INDEX); + } + + public void setIndex(String makeIndex) { + setProperty(REPORT_PAGE_INDEX,makeIndex); + } + + public String getCSS() { + return getPropertyAsString(REPORT_PAGE_CSS); + } + + public void setCSS(String css) { + setProperty(REPORT_PAGE_CSS,css); + } + + public String getHeaderURL() { + return getPropertyAsString(REPORT_PAGE_HEADER); + } + + public void setHeaderURL(String url) { + setProperty(REPORT_PAGE_HEADER,url); + } + + public String getFooterURL() { + return getPropertyAsString(REPORT_PAGE_FOOTER); + } + + public void setFooterURL(String url) { + setProperty(REPORT_PAGE_FOOTER,url); + } + + public String getIntroduction() { + return getPropertyAsString(REPORT_PAGE_INTRO); + } + + public void setIntroduction(String intro) { + setProperty(REPORT_PAGE_INTRO,intro); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/ReportPlan.java b/ApacheJmeter/src/org/apache/jmeter/testelement/ReportPlan.java new file mode 100644 index 0000000..f847116 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/ReportPlan.java @@ -0,0 +1,225 @@ +/* + * 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.testelement; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.ConfigElement; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.property.CollectionProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class ReportPlan extends AbstractTestElement implements Serializable, TestListener { + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String REPORT_PAGE = "ReportPlan.report_page"; + + public static final String USER_DEFINED_VARIABLES = "ReportPlan.user_defined_variables"; + + public static final String REPORT_COMMENTS = "ReportPlan.comments"; + + public static final String BASEDIR = "ReportPlan.basedir"; + + private transient List reportPages = new LinkedList(); + + private transient List configs = new LinkedList(); + + private static final List itemsCanAdd = new LinkedList(); + + //private static ReportPlan plan; + + // There's only 1 test plan, so can cache the mode here + private static volatile boolean functionalMode = false; + + static { + itemsCanAdd.add(JMeterUtils.getResString("report_page")); + } + + public ReportPlan() { + this(JMeterUtils.getResString("report_plan")); + } + + public ReportPlan(String name) { + setName(name); + setProperty(new CollectionProperty(REPORT_PAGE, reportPages)); + } + + public void setUserDefinedVariables(Arguments vars) { + setProperty(new TestElementProperty(USER_DEFINED_VARIABLES, vars)); + } + + public String getBasedir() { + return getPropertyAsString(BASEDIR); + } + + public void setBasedir(String b) { + setProperty(BASEDIR, b); + } + + public Map getUserDefinedVariables() { + Arguments args = getVariables(); + return args.getArgumentsAsMap(); + } + + private Arguments getVariables() { + Arguments args = (Arguments) getProperty(USER_DEFINED_VARIABLES).getObjectValue(); + if (args == null) { + args = new Arguments(); + setUserDefinedVariables(args); + } + return args; + } + + /** + * Gets the static copy of the functional mode + * + * @return mode + */ + public static boolean getFunctionalMode() { + return functionalMode; + } + + public void addParameter(String name, String value) { + getVariables().addArgument(name, value); + } + + // FIXME Wrong code that create different constructor for static field depending on caller +// public static ReportPlan createReportPlan(String name) { +// if (plan == null) { +// if (name == null) { +// plan = new ReportPlan(); +// } else { +// plan = new ReportPlan(name); +// } +// plan.setProperty(new StringProperty(TestElement.GUI_CLASS, "org.apache.jmeter.control.gui.ReportGui")); +// } +// return plan; +// } + + @Override + public void addTestElement(TestElement tg) { + super.addTestElement(tg); + if (tg instanceof AbstractThreadGroup && !isRunningVersion()) { + addReportPage((AbstractThreadGroup) tg); + } + } + + public void addJMeterComponent(TestElement child) { + if (child instanceof AbstractThreadGroup) { + addReportPage((AbstractThreadGroup) child); + } + } + + /** + * Gets the ThreadGroups attribute of the TestPlan object. + * + * @return the ThreadGroups value + */ + public Collection getReportPages() { + return reportPages; + } + + /** + * Adds a feature to the ConfigElement attribute of the TestPlan object. + * + * @param c + * the feature to be added to the ConfigElement attribute + */ + public void addConfigElement(ConfigElement c) { + configs.add(c); + } + + /** + * Adds a feature to the AbstractThreadGroup attribute of the TestPlan object. + * + * @param group + * the feature to be added to the AbstractThreadGroup attribute + */ + public void addReportPage(AbstractThreadGroup group) { + reportPages.add(group); + } + + /* + * (non-Javadoc) + * + * @see org.apache.jmeter.testelement.TestListener#testEnded() + */ + public void testEnded() { + try { + FileServer.getFileServer().closeFiles(); + } catch (IOException e) { + log.error("Problem closing files at end of test", e); + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.jmeter.testelement.TestListener#testEnded(java.lang.String) + */ + public void testEnded(String host) { + testEnded(); + + } + + /* + * (non-Javadoc) + * + * @see org.apache.jmeter.testelement.TestListener#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent) + */ + public void testIterationStart(LoopIterationEvent event) { + } + + /* + * (non-Javadoc) + * + * @see org.apache.jmeter.testelement.TestListener#testStarted() + */ + public void testStarted() { + if (getBasedir() != null && getBasedir().length() > 0) { + try { + FileServer.getFileServer().setBasedir(FileServer.getFileServer().getBaseDir() + getBasedir()); + } catch (IllegalStateException e) { + log.error("Failed to set file server base dir with " + getBasedir(), e); + } + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.jmeter.testelement.TestListener#testStarted(java.lang.String) + */ + public void testStarted(String host) { + testStarted(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/Table.java b/ApacheJmeter/src/org/apache/jmeter/testelement/Table.java new file mode 100644 index 0000000..8b61424 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/Table.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.jmeter.testelement; + +import java.util.List; + +public class Table extends AbstractTable { + + private static final long serialVersionUID = 240L; + + public Table() { + super(); + } + + /** + * for now the method isn't implemented. I still need to decide how + * it should be implemented. + */ + @SuppressWarnings("rawtypes") // TODO fix this when there is a real implementation + @Override + public String[][] getTableData(List data) { + return new String[0][0]; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/TestCloneable.java b/ApacheJmeter/src/org/apache/jmeter/testelement/TestCloneable.java new file mode 100644 index 0000000..15cd362 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/TestCloneable.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Created on May 28, 2004 + */ +package org.apache.jmeter.testelement; + +public interface TestCloneable extends Cloneable { + public Object clone(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/TestElement.java b/ApacheJmeter/src/org/apache/jmeter/testelement/TestElement.java new file mode 100644 index 0000000..4329224 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/TestElement.java @@ -0,0 +1,203 @@ +/* + * 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.testelement; + +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.PropertyIterator; +import org.apache.jmeter.threads.JMeterContext; + +public interface TestElement extends Cloneable { + public final static String NAME = "TestElement.name"; //$NON-NLS-1$ + + public final static String GUI_CLASS = "TestElement.gui_class"; //$NON-NLS-1$ + + public final static String ENABLED = "TestElement.enabled"; //$NON-NLS-1$ + + public final static String TEST_CLASS = "TestElement.test_class"; //$NON-NLS-1$ + + // Needed by AbstractTestElement. + // Also TestElementConverter and TestElementPropertyConverter for handling empty comments + public final static String COMMENTS = "TestPlan.comments"; //$NON-NLS-1$ + // N.B. Comments originally only applied to Test Plans, hence the name - which can now not be easily changed + + public void addTestElement(TestElement child); + + /** + * This method should clear any test element properties that are merged + * by {@link #addTestElement(TestElement)}. + */ + public void clearTestElementChildren(); + + public void setProperty(String key, String value); + + public void setProperty(String key, String value, String dflt); + + public void setProperty(String key, boolean value); + + public void setProperty(String key, boolean value, boolean dflt); + + public void setProperty(String key, int value); + + public void setProperty(String key, int value, int dflt); + + /** + * Check if ENABLED property is present and true ; defaults to true + * + * @return true if element is enabled + */ + public boolean isEnabled(); + + /** + * Returns true or false whether the element is the running version. + */ + public boolean isRunningVersion(); + + /** + * Test whether a given property is only a temporary resident of the + * TestElement + * + * @param property + * @return boolean + */ + public boolean isTemporary(JMeterProperty property); + + /** + * Indicate that the given property should be only a temporary property in + * the TestElement + * + * @param property + * void + */ + public void setTemporary(JMeterProperty property); + + /** + * Return a property as a boolean value. + */ + public boolean getPropertyAsBoolean(String key); + + public boolean getPropertyAsBoolean(String key, boolean defaultValue); + + public long getPropertyAsLong(String key); + + public long getPropertyAsLong(String key, long defaultValue); + + public int getPropertyAsInt(String key); + + public int getPropertyAsInt(String key, int defaultValue); + + public float getPropertyAsFloat(String key); + + public double getPropertyAsDouble(String key); + + /** + * Make the test element the running version, or make it no longer the + * running version. This tells the test element that it's current state must + * be retrievable by a call to recoverRunningVersion(). It is kind of like + * making the TestElement Read- Only, but not as strict. Changes can be made + * and the element can be modified, but the state of the element at the time + * of the call to setRunningVersion() must be recoverable. + */ + public void setRunningVersion(boolean run); + + /** + * Tells the test element to return to the state it was in when + * setRunningVersion(true) was called. + */ + public void recoverRunningVersion(); + + /** + * Clear the TestElement of all data. + */ + public void clear(); + // TODO - yet another ambiguous name - does it need changing? + // See also: Clearable, JMeterGUIComponent + + public String getPropertyAsString(String key); + + public String getPropertyAsString(String key, String defaultValue); + + /** + * Sets and overwrites a property in the TestElement. This call will be + * ignored if the TestElement is currently a "running version". + */ + public void setProperty(JMeterProperty property); + + /** + * Given the name of the property, returns the appropriate property from + * JMeter. If it is null, a NullProperty object will be returned. + */ + public JMeterProperty getProperty(String propName); + + /** + * Get a Property Iterator for the TestElements properties. + * + * @return PropertyIterator + */ + public PropertyIterator propertyIterator(); + + public void removeProperty(String key); + + // lifecycle methods + + public Object clone(); + + /** + * Convenient way to traverse a test element. + */ + public void traverse(TestElementTraverser traverser); + + /** + * @return Returns the threadContext. + */ + public JMeterContext getThreadContext(); + + /** + * @param threadContext + * The threadContext to set. + */ + public void setThreadContext(JMeterContext threadContext); + + /** + * @return Returns the threadName. + */ + public String getThreadName(); + + /** + * @param threadName + * The threadName to set. + */ + public void setThreadName(String threadName); + + /** + * Called by Remove to determine if it is safe to remove the element. The + * element can either clean itself up, and return true, or the element can + * return false. + * + * @return true if safe to remove the element + */ + public boolean canRemove(); + + public String getName(); + + public void setName(String name); + + public String getComment(); + + public void setComment(String comment); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/TestElementTraverser.java b/ApacheJmeter/src/org/apache/jmeter/testelement/TestElementTraverser.java new file mode 100644 index 0000000..2d7136e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/TestElementTraverser.java @@ -0,0 +1,61 @@ +/* + * 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.testelement; + +import org.apache.jmeter.testelement.property.JMeterProperty; + +/** + * For traversing Test Elements, which contain property that can be other test + * elements, strings, collections, maps, objects + * + * @version $Revision: 674365 $ + */ +public interface TestElementTraverser { + + /** + * Notification that a new test element is about to be traversed. + * + * @param el + */ + public void startTestElement(TestElement el); + + /** + * Notification that the test element is now done. + * + * @param el + */ + public void endTestElement(TestElement el); + + /** + * Notification that a property is starting. This could be a test element + * property or a Map property - depends on the context. + * + * @param key + */ + public void startProperty(JMeterProperty key); + + /** + * Notification that a property is ending. Again, this could be a test + * element or a Map property, dependig on the context. + * + * @param key + */ + public void endProperty(JMeterProperty key); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/TestListener.java b/ApacheJmeter/src/org/apache/jmeter/testelement/TestListener.java new file mode 100644 index 0000000..abf24c5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/TestListener.java @@ -0,0 +1,115 @@ +/* + * 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.testelement; + +import org.apache.jmeter.engine.event.LoopIterationEvent; + +/** + * TestListener interface is used for methods that are called at different + * stages of each test. + * + */ +public interface TestListener { + /** + *

+ * Called just before the start of the test from the main engine thread. + * + * This is before the test elements are cloned. + * + * Note that not all the test + * variables will have been set up at this point. + *

+ * + *

+ * + * N.B. testStarted() and testEnded() are called from different threads. + * + *

+ * @see org.apache.jmeter.engine.StandardJMeterEngine#run() + * + */ + public void testStarted(); + + /** + *

+ * Called just before the start of the test from the main engine thread. + * + * This is before the test elements are cloned. + * + * Note that not all the test + * variables will have been set up at this point. + *

+ * + *

+ * + * N.B. testStarted() and testEnded() are called from different threads. + * + *

+ * @see org.apache.jmeter.engine.StandardJMeterEngine#run() + * @param host name of host + */ + public void testStarted(String host); + + /** + *

+ * Called once for all threads after the end of a test. + * + * This will use the same element instances as at the start of the test. + *

+ * + *

+ * + * N.B. testStarted() and testEnded() are called from different threads. + * + *

+ * @see org.apache.jmeter.engine.StandardJMeterEngine#stopTest() + * + */ + public void testEnded(); + + /** + *

+ * Called once for all threads after the end of a test. + * + * This will use the same element instances as at the start of the test. + *

+ * + *

+ * + * N.B. testStarted() and testEnded() are called from different threads. + * + *

+ * @see org.apache.jmeter.engine.StandardJMeterEngine#stopTest() + * @param host name of host + * + */ + + public void testEnded(String host); + + /** + * Each time through a Thread Group's test script, an iteration event is + * fired for each thread. + * + * This will be after the test elements have been cloned, so in general + * the instance will not be the same as the ones the start/end methods call. + * + * @param event + */ + public void testIterationStart(LoopIterationEvent event); +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/TestPlan.java b/ApacheJmeter/src/org/apache/jmeter/testelement/TestPlan.java new file mode 100644 index 0000000..2e6be75 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/TestPlan.java @@ -0,0 +1,325 @@ +/* + * 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.testelement; + +import java.io.IOException; +import java.io.Serializable; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.NewDriver; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public class TestPlan extends AbstractTestElement implements Serializable, TestListener { + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + // does not appear to be needed +// private final static String THREAD_GROUPS = "TestPlan.thread_groups"; //$NON-NLS-1$ + + private final static String FUNCTIONAL_MODE = "TestPlan.functional_mode"; //$NON-NLS-1$ + + private final static String USER_DEFINED_VARIABLES = "TestPlan.user_defined_variables"; //$NON-NLS-1$ + + private final static String SERIALIZE_THREADGROUPS = "TestPlan.serialize_threadgroups"; //$NON-NLS-1$ + + private final static String CLASSPATHS = "TestPlan.user_define_classpath"; //$NON-NLS-1$ + private static final String CLASSPATH_SEPARATOR = ","; //$NON-NLS-1$ + + private final static String BASEDIR = "basedir"; + + private transient List threadGroups = new LinkedList(); + + // Does not appear to be needed +// private transient List configs = new LinkedList(); + +// // Does not appear to be needed +// private static List itemsCanAdd = new LinkedList(); + + // Does not appear to be needed +// private static TestPlan plan; + + // There's only 1 test plan, so can cache the mode here + private static volatile boolean functionalMode = false; + + static { + // WARNING! This String value must be identical to the String value + // returned in org.apache.jmeter.threads.AbstractThreadGroup.getClassLabel() + // method. If it's not you will not be able to add a Thread Group + // element to a Test Plan. + + // Does not appear to be needed +// itemsCanAdd.add(JMeterUtils.getResString("threadgroup")); //$NON-NLS-1$ + } + + public TestPlan() { + // this("Test Plan"); + // setFunctionalMode(false); + // setSerialized(false); + } + + public TestPlan(String name) { + setName(name); + // setFunctionalMode(false); + // setSerialized(false); + + // Does not appear to be needed +// setProperty(new CollectionProperty(THREAD_GROUPS, threadGroups)); + } + + // create transient item + private Object readResolve(){ + threadGroups = new LinkedList(); + return this; + } + + public void prepareForPreCompile() + { + getVariables().setRunningVersion(true); + } + + /** + * Fetches the functional mode property + * + * @return functional mode + */ + public boolean isFunctionalMode() { + return getPropertyAsBoolean(FUNCTIONAL_MODE); + } + + public void setUserDefinedVariables(Arguments vars) { + setProperty(new TestElementProperty(USER_DEFINED_VARIABLES, vars)); + } + + public JMeterProperty getUserDefinedVariablesAsProperty() { + return getProperty(USER_DEFINED_VARIABLES); + } + + public String getBasedir() { + return getPropertyAsString(BASEDIR); + } + + // Does not appear to be used yet + public void setBasedir(String b) { + setProperty(BASEDIR, b); + } + + public Arguments getArguments() { + return getVariables(); + } + + public Map getUserDefinedVariables() { + Arguments args = getVariables(); + return args.getArgumentsAsMap(); + } + + private Arguments getVariables() { + Arguments args = (Arguments) getProperty(USER_DEFINED_VARIABLES).getObjectValue(); + if (args == null) { + args = new Arguments(); + setUserDefinedVariables(args); + } + return args; + } + + public void setFunctionalMode(boolean funcMode) { + setProperty(new BooleanProperty(FUNCTIONAL_MODE, funcMode)); + functionalMode = funcMode; + } + + /** + * Gets the static copy of the functional mode + * + * @return mode + */ + public static boolean getFunctionalMode() { + return functionalMode; + } + + public void setSerialized(boolean serializeTGs) { + setProperty(new BooleanProperty(SERIALIZE_THREADGROUPS, serializeTGs)); + } + + /** + * Set the classpath for the test plan + * @param text + */ + public void setTestPlanClasspath(String text) { + setProperty(CLASSPATHS,text); + } + + public void setTestPlanClasspathArray(String[] text) { + StringBuilder cat = new StringBuilder(); + for (int idx=0; idx < text.length; idx++) { + if (idx > 0) { + cat.append(CLASSPATH_SEPARATOR); + } + cat.append(text[idx]); + } + this.setTestPlanClasspath(cat.toString()); + } + + public String[] getTestPlanClasspathArray() { + return JOrphanUtils.split(this.getTestPlanClasspath(),CLASSPATH_SEPARATOR); + } + + /** + * Returns the classpath + * @return classpath + */ + public String getTestPlanClasspath() { + return getPropertyAsString(CLASSPATHS); + } + + /** + * Fetch the serialize threadgroups property + * + * @return serialized setting + */ + public boolean isSerialized() { + return getPropertyAsBoolean(SERIALIZE_THREADGROUPS); + } + + public void addParameter(String name, String value) { + getVariables().addArgument(name, value); + } + + // Does not appear to be needed +// public static TestPlan createTestPlan(String name) { +// if (plan == null) { +// if (name == null) { +// plan = new TestPlan(); +// } else { +// plan = new TestPlan(name); +// } +// plan.setProperty(new StringProperty(TestElement.GUI_CLASS, +// "org.apache.jmeter.control.gui.TestPlanGui")); //$NON-NLS-1$ +// } +// return plan; +// } + + @Override + public void addTestElement(TestElement tg) { + super.addTestElement(tg); + if (tg instanceof AbstractThreadGroup && !isRunningVersion()) { + addThreadGroup((AbstractThreadGroup) tg); + } + } + +// // Does not appear to be needed +// public void addJMeterComponent(TestElement child) { +// if (child instanceof AbstractThreadGroup) { +// addThreadGroup((AbstractThreadGroup) child); +// } +// } + +// /** +// * Gets the ThreadGroups attribute of the TestPlan object. +// * +// * @return the ThreadGroups value +// */ +// // Does not appear to be needed +// public Collection getThreadGroups() { +// return threadGroups; +// } + +// /** +// * Adds a feature to the ConfigElement attribute of the TestPlan object. +// * +// * @param c +// * the feature to be added to the ConfigElement attribute +// */ +// // Does not appear to be needed +// public void addConfigElement(ConfigElement c) { +// configs.add(c); +// } + + /** + * Adds a feature to the AbstractThreadGroup attribute of the TestPlan object. + * + * @param group + * the feature to be added to the AbstractThreadGroup attribute + */ + public void addThreadGroup(AbstractThreadGroup group) { + threadGroups.add(group); + } + + /** + * {@inheritDoc} + */ + public void testEnded() { + try { + FileServer.getFileServer().closeFiles(); + } catch (IOException e) { + log.error("Problem closing files at end of test", e); + } + } + + /** + * {@inheritDoc} + */ + public void testEnded(String host) { + testEnded(); + + } + + /** + * {@inheritDoc} + */ + public void testIterationStart(LoopIterationEvent event) { + } + + /** + * {@inheritDoc} + */ + public void testStarted() { + if (getBasedir() != null && getBasedir().length() > 0) { + try { + FileServer.getFileServer().setBasedir(FileServer.getFileServer().getBaseDir() + getBasedir()); + } catch (IllegalStateException e) { + log.error("Failed to set file server base dir with " + getBasedir(), e); + } + } + // we set the classpath + String[] paths = this.getTestPlanClasspathArray(); + for (int idx=0; idx < paths.length; idx++) { + NewDriver.addURL(paths[idx]); + log.info("add " + paths[idx] + " to classpath"); + } + } + + /** + * {@inheritDoc} + */ + public void testStarted(String host) { + testStarted(); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/ThreadListener.java b/ApacheJmeter/src/org/apache/jmeter/testelement/ThreadListener.java new file mode 100644 index 0000000..ca509c3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/ThreadListener.java @@ -0,0 +1,46 @@ +/* + * 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.testelement; + +import org.apache.jmeter.engine.event.LoopIterationListener; + +/** + * Allow threads to perform startup and closedown if necessary + * + */ +public interface ThreadListener { + /** + * Called for each thread before starting sampling. + * WARNING: this is called before any Config test elements are processed, + * so any properties they define will not have been merged in yet. + * + * @see org.apache.jmeter.threads.JMeterThread#threadStarted() + * + */ + public void threadStarted(); + + /** + * Called for each thread after all samples have been processed. + * + * @see org.apache.jmeter.threads.JMeterThread#threadFinished(LoopIterationListener) + * + */ + public void threadFinished(); + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/VariablesCollection.java b/ApacheJmeter/src/org/apache/jmeter/testelement/VariablesCollection.java new file mode 100644 index 0000000..3762b19 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/VariablesCollection.java @@ -0,0 +1,44 @@ +/* + * 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.testelement; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.threads.JMeterVariables; + +/** + * @version $Revision: 915405 $ + */ +public class VariablesCollection implements Serializable { + + private static final long serialVersionUID = 240L; + + private Map varMap = new HashMap(); + + public void addJMeterVariables(JMeterVariables jmVars) { + varMap.put(Thread.currentThread().getName(), jmVars); + } + + public JMeterVariables getVariables() { + return varMap.get(Thread.currentThread().getName()); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/WorkBench.java b/ApacheJmeter/src/org/apache/jmeter/testelement/WorkBench.java new file mode 100644 index 0000000..ed36b67 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/WorkBench.java @@ -0,0 +1,34 @@ +/* + * 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.testelement; + +public class WorkBench extends AbstractTestElement { + + private static final long serialVersionUID = 240L; + + /** + * Constructor for the WorkBench object. + */ + public WorkBench(String name, boolean isRootNode) { + setName(name); + } + + public WorkBench() { + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/AbstractProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/AbstractProperty.java new file mode 100644 index 0000000..e9a26da --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/AbstractProperty.java @@ -0,0 +1,404 @@ +/* + * 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.testelement.property; + +import java.util.Collection; +import java.util.Map; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public abstract class AbstractProperty implements JMeterProperty { + private static final long serialVersionUID = 240L; + + //TODO consider using private logs for each derived class + protected static final Logger log = LoggingManager.getLoggerForClass(); + + private String name; + + private transient boolean runningVersion = false; + + // private static StringProperty defaultProperty = new StringProperty(); + + public AbstractProperty(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null"); + } + this.name = name; + } + + public AbstractProperty() { + this(""); + } + + protected boolean isEqualType(JMeterProperty prop) { + if (this.getClass().equals(prop.getClass())) { + return true; + } else { + return false; + } + } + + /** {@inheritDoc} */ + public boolean isRunningVersion() { + return runningVersion; + } + + /** {@inheritDoc} */ + public String getName() { + return name; + } + + /** {@inheritDoc} */ + public void setName(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null"); + } + this.name = name; + } + + /** {@inheritDoc} */ + public void setRunningVersion(boolean runningVersion) { + this.runningVersion = runningVersion; + } + + protected PropertyIterator getIterator(Collection values) { + return new PropertyIteratorImpl(values); + } + + /** {@inheritDoc} */ + @Override + public AbstractProperty clone() { + try { + AbstractProperty prop = (AbstractProperty) super.clone(); + prop.name = name; + prop.runningVersion = runningVersion; + return prop; + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); // clone should never return null + } + } + + /** + * Returns 0 if string is invalid or null. + * + * @see JMeterProperty#getIntValue() + */ + public int getIntValue() { + String val = getStringValue(); + if (val == null) { + return 0; + } + try { + return Integer.parseInt(val); + } catch (NumberFormatException e) { + return 0; + } + } + + /** + * Returns 0 if string is invalid or null. + * + * @see JMeterProperty#getLongValue() + */ + public long getLongValue() { + String val = getStringValue(); + if (val == null) { + return 0; + } + try { + return Long.parseLong(val); + } catch (NumberFormatException e) { + return 0; + } + } + + /** + * Returns 0 if string is invalid or null. + * + * @see JMeterProperty#getDoubleValue() + */ + public double getDoubleValue() { + String val = getStringValue(); + if (val == null) { + return 0; + } + try { + return Double.parseDouble(val); + } catch (NumberFormatException e) { + log.error("Tried to parse a non-number string to an integer", e); + return 0; + } + } + + /** + * Returns 0 if string is invalid or null. + * + * @see JMeterProperty#getFloatValue() + */ + public float getFloatValue() { + String val = getStringValue(); + if (val == null) { + return 0; + } + try { + return Float.parseFloat(val); + } catch (NumberFormatException e) { + log.error("Tried to parse a non-number string to an integer", e); + return 0; + } + } + + /** + * Returns false if string is invalid or null. + * + * @see JMeterProperty#getBooleanValue() + */ + public boolean getBooleanValue() { + String val = getStringValue(); + if (val == null) { + return false; + } + return Boolean.valueOf(val).booleanValue(); + } + + /** + * Determines if the two objects are equal by comparing names and values + * + * @return true if names are equal and values are equal (or both null) + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof JMeterProperty)) { + return false; + } + if (this == o) { + return true; + } + JMeterProperty jpo = (JMeterProperty) o; + if (!name.equals(jpo.getName())) { + return false; + } + Object o1 = getObjectValue(); + Object o2 = jpo.getObjectValue(); + return o1 == null ? o2 == null : o1.equals(o2); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int result = 17; + result = result * 37 + name.hashCode();// name cannot be null + Object o = getObjectValue(); + result = result * 37 + (o == null ? 0 : o.hashCode()); + return result; + } + + /** + * Compares two JMeterProperty object values. N.B. Does not compare names + * + * @param arg0 + * JMeterProperty to compare against + * @return 0 if equal values or both values null; -1 otherwise + * @see Comparable#compareTo(Object) + */ + public int compareTo(JMeterProperty arg0) { + // We don't expect the string values to ever be null. But (as in + // bug 19499) sometimes they are. So have null compare less than + // any other value. Log a warning so we can try to find the root + // cause of the null value. + String val = getStringValue(); + String val2 = arg0.getStringValue(); + if (val == null) { + log.warn("Warning: Unexpected null value for property: " + name); + + if (val2 == null) { + // Two null values -- return equal + return 0; + } else { + return -1; + } + } + return val.compareTo(val2); + } + + /** + * Get the property type for this property. Used to convert raw values into + * JMeterProperties. + */ + protected Class getPropertyType() { + return getClass(); + } + + protected JMeterProperty getBlankProperty() { + try { + JMeterProperty prop = getPropertyType().newInstance(); + if (prop instanceof NullProperty) { + return new StringProperty(); + } + return prop; + } catch (Exception e) { + return new StringProperty(); + } + } + + protected static JMeterProperty getBlankProperty(Object item) { + if (item == null) { + return new NullProperty(); + } + if (item instanceof String) { + return new StringProperty("", item.toString()); + } else if (item instanceof Boolean) { + return new BooleanProperty("", ((Boolean) item).booleanValue()); + } else if (item instanceof Float) { + return new FloatProperty("", ((Float) item).floatValue()); + } else if (item instanceof Double) { + return new DoubleProperty("", ((Double) item).doubleValue()); + } else if (item instanceof Integer) { + return new IntegerProperty("", ((Integer) item).intValue()); + } else if (item instanceof Long) { + return new LongProperty("", ((Long) item).longValue()); + } else if (item instanceof Long) { + return new LongProperty("", ((Long) item).longValue()); + } else { + return new StringProperty("", item.toString()); + } + } + + /** + * Convert a collection of objects into JMeterProperty objects. + * + * @param coll Collection of any type of object + * @return Collection of JMeterProperty objects + */ + protected Collection normalizeList(Collection coll) { + if (coll.isEmpty()) { + @SuppressWarnings("unchecked") // empty collection + Collection okColl = (Collection) coll; + return okColl; + } + try { + @SuppressWarnings("unchecked") // empty collection + Collection newColl = coll.getClass().newInstance(); + for (Object item : coll) { + newColl.add(convertObject(item)); + } + return newColl; + } catch (Exception e) {// should not happen + log.error("Cannot create copy of "+coll.getClass().getName(),e); + return null; + } + } + + /** + * Given a Map, it converts the Map into a collection of JMeterProperty + * objects, appropriate for a MapProperty object. + */ + protected Map normalizeMap(Map coll) { + if (coll.isEmpty()) { + @SuppressWarnings("unchecked") // empty collection ok to cast + Map emptyColl = (Map) coll; + return emptyColl; + } + try { + @SuppressWarnings("unchecked") // empty collection + Map newColl = coll.getClass().newInstance(); + for (Map.Entry entry : ((Map)coll).entrySet()) { + Object key = entry.getKey(); + Object prop = entry.getValue(); + String item=null; + if (key instanceof String) { + item = (String) key; + } else { + if (key != null) { + log.error("Expected key type String, found: "+key.getClass().getName()); + item = key.toString(); + } + } + newColl.put(item, convertObject(prop)); + } + return newColl; + } catch (Exception e) {// should not happen + log.error("Cannot create copy of "+coll.getClass().getName(),e); + return null; + } + } + + public static JMeterProperty createProperty(Object item) { + JMeterProperty prop = makeProperty(item); + if (prop == null) { + prop = getBlankProperty(item); + } + return prop; + } + + /** + * Create a JMeterProperty from an object. + * The object can be one of: + *
    + *
  • JMeterProperty - returned unchanged
  • + *
  • TestElement => TestElementProperty with the same name
  • + *
  • Map|Collection => Map|CollectionProperty with the name = item.hashCode
  • + *
+ * @param item object to be turned into a propery + * @return the JMeterProperty + */ + protected static JMeterProperty makeProperty(Object item) { + if (item instanceof JMeterProperty) { + return (JMeterProperty) item; + } + if (item instanceof TestElement) { + return new TestElementProperty(((TestElement) item).getName(), + (TestElement) item); + } + if (item instanceof Collection) { + return new CollectionProperty("" + item.hashCode(), (Collection) item); + } + if (item instanceof Map) { + return new MapProperty("" + item.hashCode(), (Map) item); + } + return null; + } + + protected JMeterProperty convertObject(Object item) { + JMeterProperty prop = makeProperty(item); + if (prop == null) { + prop = getBlankProperty(); + prop.setName("" + item.hashCode()); + prop.setObjectValue(item); + } + return prop; + } + + /** + * Provides the string representation of the property. + * + * @return the string value + */ + @Override + public String toString() { + // N.B. Other classes rely on this returning just the string. + return getStringValue(); + } + + /** {@inheritDoc} */ + public void mergeIn(JMeterProperty prop) { + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/BooleanProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/BooleanProperty.java new file mode 100644 index 0000000..77805df --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/BooleanProperty.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision: 1188141 $ + */ +public class BooleanProperty extends AbstractProperty { + private static final long serialVersionUID = 233L; + + private boolean value; + + private transient boolean savedValue; + + public BooleanProperty(String name, boolean v) { + super(name); + value = v; + } + + public BooleanProperty() { + super(); + } + + public void setObjectValue(Object v) { + if (v instanceof Boolean) { + value = ((Boolean) v).booleanValue(); + } else { + value = Boolean.valueOf(v.toString()).booleanValue(); + } + } + + /** + * {@inheritDoc} + */ + public String getStringValue() { + return Boolean.toString(value); + } + + /** + * {@inheritDoc} + */ + public Object getObjectValue() { + return Boolean.valueOf(value); + } + + @Override + public BooleanProperty clone() { + BooleanProperty prop = (BooleanProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean getBooleanValue() { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + savedValue = value; + super.setRunningVersion(runningVersion); + } + + /** + * {@inheritDoc} + */ + public void recoverRunningVersion(TestElement owner) { + value = savedValue; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/CollectionProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/CollectionProperty.java new file mode 100644 index 0000000..389246a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/CollectionProperty.java @@ -0,0 +1,215 @@ +/* + * 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.testelement.property; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.jmeter.testelement.TestElement; + +public class CollectionProperty extends MultiProperty { + + private static final long serialVersionUID = 221L; // Remember to change this when the class changes ... + + private Collection value; + + private transient Collection savedValue; + + public CollectionProperty(String name, Collection value) { + super(name); + this.value = normalizeList(value); + } + + public CollectionProperty() { + super(); + value = new ArrayList(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof CollectionProperty) { + if (value != null) { + return value.equals(((JMeterProperty) o).getObjectValue()); + } + } + return false; + } + + @Override + public int hashCode() { + return (value == null ? 0 : value.hashCode()); + } + + public void remove(String prop) { + PropertyIterator iter = iterator(); + while (iter.hasNext()) { + if (iter.next().getName().equals(prop)) { + iter.remove(); + } + } + } + + public void set(int index, String prop) { + if (value instanceof List) { + ((List) value).set(index, new StringProperty(prop, prop)); + } + } + + public void set(int index, JMeterProperty prop) { + if (value instanceof List) { + ((List) value).set(index, prop); + } + } + + public JMeterProperty get(int row) { + if (value instanceof List) { + return ((List) value).get(row); + } + return null; + } + + public void remove(int index) { + if (value instanceof List) { + ((List) value).remove(index); + } + } + + /** + * {@inheritDoc} + */ + public void setObjectValue(Object v) { + if (v instanceof Collection) { + setCollection((Collection) v); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public PropertyIterator iterator() { + return getIterator(value); + } + + /** + * {@inheritDoc} + */ + public String getStringValue() { + return value.toString(); + } + + /** + * {@inheritDoc} + */ + public Object getObjectValue() { + return value; + } + + public int size() { + return value.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public CollectionProperty clone() { + CollectionProperty prop = (CollectionProperty) super.clone(); + prop.value = cloneCollection(); + return prop; + } + + private Collection cloneCollection() { + try { + @SuppressWarnings("unchecked") // value is of type Collection + Collection newCol = value.getClass().newInstance(); + PropertyIterator iter = iterator(); + while (iter.hasNext()) { + newCol.add(iter.next().clone()); + } + return newCol; + } catch (Exception e) { + log.error("Couldn't clone collection", e); + return value; + } + } + + public void setCollection(Collection coll) { + value = normalizeList(coll); + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + value.clear(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addProperty(JMeterProperty prop) { + value.add(prop); + } + + public void addItem(Object item) { + addProperty(convertObject(item)); + } + + /** + * Figures out what kind of properties this collection is holding and + * returns the class type. + * + * @see AbstractProperty#getPropertyType() + */ + @Override + protected Class getPropertyType() { + if (value != null && value.size() > 0) { + return value.iterator().next().getClass(); + } + return NullProperty.class; + } + + /** + * {@inheritDoc} + */ + public void recoverRunningVersion(TestElement owner) { + if (savedValue != null) { + value = savedValue; + } + recoverRunningVersionOfSubElements(owner); + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean running) { + super.setRunningVersion(running); + if (running) { + savedValue = value; + } else { + savedValue = null; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/DoubleProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/DoubleProperty.java new file mode 100644 index 0000000..567da75 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/DoubleProperty.java @@ -0,0 +1,140 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision: 1226919 $ + */ +public class DoubleProperty extends NumberProperty { + private static final long serialVersionUID = 240L; + + private double value; + + private double savedValue; + + public DoubleProperty(String name, double value) { + super(name); + this.value = value; + } + + public DoubleProperty() { + } + + public void setValue(float value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(Number n) { + value = n.doubleValue(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(String n) throws NumberFormatException { + value = Double.parseDouble(n); + } + + /** + * {@inheritDoc} + */ + public String getStringValue() { + return Double.toString(value); + } + + /** + * {@inheritDoc} + */ + public Object getObjectValue() { + return Double.valueOf(value); + } + + /** + * {@inheritDoc} + */ + @Override + public DoubleProperty clone() { + DoubleProperty prop = (DoubleProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean getBooleanValue() { + return value > 0 ? true : false; + } + + /** + * {@inheritDoc} + */ + @Override + public double getDoubleValue() { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public float getFloatValue() { + return (float) value; + } + + /** + * {@inheritDoc} + */ + @Override + public int getIntValue() { + return (int) value; + } + + /** + * @see JMeterProperty#getLongValue() + */ + @Override + public long getLongValue() { + return (long) value; + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + savedValue = value; + super.setRunningVersion(runningVersion); + } + + /** + * {@inheritDoc} + */ + public void recoverRunningVersion(TestElement owner) { + value = savedValue; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/FloatProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/FloatProperty.java new file mode 100644 index 0000000..e01d301 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/FloatProperty.java @@ -0,0 +1,140 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision: 1226919 $ + */ +public class FloatProperty extends NumberProperty { + private static final long serialVersionUID = 240L; + + private float value; + + private float savedValue; + + public FloatProperty(String name, float value) { + super(name); + this.value = value; + } + + public FloatProperty() { + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + savedValue = value; + super.setRunningVersion(runningVersion); + } + + /** + * {@inheritDoc} + */ + public void recoverRunningVersion(TestElement owner) { + value = savedValue; + } + + public void setValue(float value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(Number n) { + value = n.floatValue(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(String n) throws NumberFormatException { + value = Float.parseFloat(n); + } + + /** + * {@inheritDoc} + */ + public String getStringValue() { + return Float.toString(value); + } + + /** + * {@inheritDoc} + */ + public Object getObjectValue() { + return Float.valueOf(value); + } + + /** + * {@inheritDoc} + */ + @Override + public FloatProperty clone() { + FloatProperty prop = (FloatProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean getBooleanValue() { + return value > 0 ? true : false; + } + + /** + * {@inheritDoc} + */ + @Override + public double getDoubleValue() { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public float getFloatValue() { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public int getIntValue() { + return (int) value; + } + + /** + * {@inheritDoc} + */ + @Override + public long getLongValue() { + return (long) value; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/FunctionProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/FunctionProperty.java new file mode 100644 index 0000000..7d65e33 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/FunctionProperty.java @@ -0,0 +1,127 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; + +/** + * Class that implements the Function property + */ +public class FunctionProperty extends AbstractProperty { + private static final long serialVersionUID = 233L; + + private transient CompoundVariable function; + + private int testIteration = -1; + + private String cacheValue; + + public FunctionProperty(String name, CompoundVariable func) { + super(name); + function = func; + } + + public FunctionProperty() { + super(); + } + + public void setObjectValue(Object v) { + if (v instanceof CompoundVariable && !isRunningVersion()) { + function = (CompoundVariable) v; + } else { + cacheValue = v.toString(); + } + } + + @Override + public boolean equals(Object o) { + if (o instanceof FunctionProperty) { + if (function != null) { + return function.equals(((JMeterProperty) o).getObjectValue()); + } + } + return false; + } + + @Override + public int hashCode(){ + int hash = super.hashCode(); + if (function != null) { + hash = hash*37 + function.hashCode(); + } + return hash; + } + + /** + * Executes the function (and caches the value for the duration of the test + * iteration) if the property is a running version. Otherwise, the raw + * string representation of the function is provided. + * + * @see JMeterProperty#getStringValue() + */ + public String getStringValue() { + JMeterContext ctx = JMeterContextService.getContext();// Expensive, so + // do + // once + if (!isRunningVersion() /*|| !ctx.isSamplingStarted()*/) { + log.debug("Not running version, return raw function string"); + return function.getRawParameters(); + } + if(!ctx.isSamplingStarted()) { + return function.execute(); + } + log.debug("Running version, executing function"); + int iter = ctx.getVariables() != null ? ctx.getVariables().getIteration() : -1; + if (iter < testIteration) { + testIteration = -1; + } + if (iter > testIteration || cacheValue == null) { + testIteration = iter; + cacheValue = function.execute(); + } + return cacheValue; + + } + + /** + * @see JMeterProperty#getObjectValue() + */ + public Object getObjectValue() { + return function; + } + + @Override + public FunctionProperty clone() { + FunctionProperty prop = (FunctionProperty) super.clone(); + prop.cacheValue = cacheValue; + prop.testIteration = testIteration; + prop.function = function; + return prop; + } + + /** + * @see JMeterProperty#recoverRunningVersion(TestElement) + */ + public void recoverRunningVersion(TestElement owner) { + cacheValue = null; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/IntegerProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/IntegerProperty.java new file mode 100644 index 0000000..f143b6a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/IntegerProperty.java @@ -0,0 +1,145 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision: 1188141 $ + */ +public class IntegerProperty extends NumberProperty { + private static final long serialVersionUID = 240L; + + private int value; + + private int savedValue; + + public IntegerProperty(String name, int value) { + super(name); + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + savedValue = value; + super.setRunningVersion(runningVersion); + } + + /** + * {@inheritDoc} + */ + public void recoverRunningVersion(TestElement owner) { + value = savedValue; + } + + public IntegerProperty(String name) { + super(name); + } + + public IntegerProperty() { + super(); + } + + public void setValue(int value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(Number n) { + value = n.intValue(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(String n) throws NumberFormatException { + value = Integer.parseInt(n); + } + + /** + * @see JMeterProperty#getStringValue() + */ + public String getStringValue() { + return Integer.toString(value); + } + + /** + * @see JMeterProperty#getObjectValue() + */ + public Object getObjectValue() { + return Integer.valueOf(value); + } + + /** + * {@inheritDoc} + */ + @Override + public IntegerProperty clone() { + IntegerProperty prop = (IntegerProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * @see JMeterProperty#getBooleanValue() + */ + @Override + public boolean getBooleanValue() { + return getIntValue() > 0 ? true : false; + } + + /** + * @see JMeterProperty#getDoubleValue() + */ + @Override + public double getDoubleValue() { + return value; + } + + /** + * @see JMeterProperty#getFloatValue() + */ + @Override + public float getFloatValue() { + return value; + } + + /** + * @see JMeterProperty#getIntValue() + */ + @Override + public int getIntValue() { + return value; + } + + /** + * @see JMeterProperty#getLongValue() + */ + @Override + public long getLongValue() { + return value; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/JMeterProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/JMeterProperty.java new file mode 100644 index 0000000..6f3df3c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/JMeterProperty.java @@ -0,0 +1,94 @@ +/* + * 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.testelement.property; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.TestElement; + +public interface JMeterProperty extends Serializable, Cloneable, Comparable { + /** + * Returns whether the property is a running version. + * + * @return boolean + */ + public boolean isRunningVersion(); + + /** + * The name of the property. Typically this should match the name that keys + * the property's location in the test elements Map. + * + * @return String + */ + public String getName(); + + /** + * Set the property name. + * + * @param name + */ + public void setName(String name); + + /** + * Make the property a running version or turn it off as the running + * version. A property that is made a running version will preserve the + * current state in such a way that it is retrievable by a future call to + * 'recoverRunningVersion()'. Additionally, a property that is a running + * version will resolve all functions prior to returning it's property + * value. A non-running version property will return functions as their + * uncompiled string representation. + * + * @param runningVersion + */ + public void setRunningVersion(boolean runningVersion); + + /** + * Tell the property to revert to the state at the time + * setRunningVersion(true) was called. + */ + public void recoverRunningVersion(TestElement owner); + + /** + * Take the given property object and merge it's value with the current + * property object. For most property types, this will simply be ignored. + * But for collection properties and test element properties, more complex + * behavior is required. + * + * @param prop + */ + public void mergeIn(JMeterProperty prop); + + public int getIntValue(); + + public long getLongValue(); + + public double getDoubleValue(); + + public float getFloatValue(); + + public boolean getBooleanValue(); + + public String getStringValue(); + + public Object getObjectValue(); + + public void setObjectValue(Object value); + + public JMeterProperty clone(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/LongProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/LongProperty.java new file mode 100644 index 0000000..d5461ec --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/LongProperty.java @@ -0,0 +1,141 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision: 1188141 $ + */ +public class LongProperty extends NumberProperty { + private static final long serialVersionUID = 240L; + + private long value; + + private long savedValue; + + public LongProperty(String name, long value) { + super(name); + this.value = value; + } + + public LongProperty() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + savedValue = value; + super.setRunningVersion(runningVersion); + } + + /** + * {@inheritDoc} + */ + public void recoverRunningVersion(TestElement owner) { + value = savedValue; + } + + public void setValue(int value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(Number n) { + value = n.longValue(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setNumberValue(String n) throws NumberFormatException { + value = Long.parseLong(n); + } + + /** + * @see JMeterProperty#getStringValue() + */ + public String getStringValue() { + return Long.toString(value); + } + + /** + * @see JMeterProperty#getObjectValue() + */ + public Object getObjectValue() { + return Long.valueOf(value); + } + + /** + * {@inheritDoc} + */ + @Override + public LongProperty clone() { + LongProperty prop = (LongProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * @see JMeterProperty#getBooleanValue() + */ + @Override + public boolean getBooleanValue() { + return getLongValue() > 0 ? true : false; + } + + /** + * @see JMeterProperty#getDoubleValue() + */ + @Override + public double getDoubleValue() { + return value; + } + + /** + * @see JMeterProperty#getFloatValue() + */ + @Override + public float getFloatValue() { + return value; + } + + /** + * @see JMeterProperty#getIntValue() + */ + @Override + public int getIntValue() { + return (int) value; + } + + /** + * @see JMeterProperty#getLongValue() + */ + @Override + public long getLongValue() { + return value; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/MapProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/MapProperty.java new file mode 100644 index 0000000..d6961a4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/MapProperty.java @@ -0,0 +1,173 @@ +/* + * 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.testelement.property; + +import java.util.Map; + +import org.apache.jmeter.testelement.TestElement; + +public class MapProperty extends MultiProperty { + + private static final long serialVersionUID = 221L; // Remember to change this when the class changes ... + + private Map value; + + private transient Map savedValue = null; + + public MapProperty(String name, Map value) { + super(name); + log.info("map = " + value); + this.value = normalizeMap(value); + log.info("normalized map = " + this.value); + } + + public MapProperty() { + super(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + if (o instanceof MapProperty) { + if (value != null) { + return value.equals(((JMeterProperty) o).getObjectValue()); + } + } + return false; + } + + @Override + public int hashCode(){ + int hash = super.hashCode(); + if (value != null) { + hash = hash*37 + value.hashCode(); + } + return hash; + } + + /** {@inheritDoc} */ + public void setObjectValue(Object v) { + if (v instanceof Map) { + setMap((Map) v); + } + } + + /** {@inheritDoc} */ + @Override + public void addProperty(JMeterProperty prop) { + addProperty(prop.getName(), prop); + } + + public JMeterProperty get(String key) { + return value.get(key); + } + + /** + * Figures out what kind of properties this collection is holding and + * returns the class type. + * + * @see AbstractProperty#getPropertyType() + */ + @Override + protected Class getPropertyType() { + if (value.size() > 0) { + return valueIterator().next().getClass(); + } + return NullProperty.class; + } + + /** {@inheritDoc} */ + public String getStringValue() { + return value.toString(); + } + + /** {@inheritDoc} */ + public Object getObjectValue() { + return value; + } + + /** {@inheritDoc} */ + @Override + public MapProperty clone() { + MapProperty prop = (MapProperty) super.clone(); + prop.value = cloneMap(); + return prop; + } + + private Map cloneMap() { + try { + @SuppressWarnings("unchecked") // value is the correct class + Map newCol = value.getClass().newInstance(); + PropertyIterator iter = valueIterator(); + while (iter.hasNext()) { + JMeterProperty item = iter.next(); + newCol.put(item.getName(), item.clone()); + } + return newCol; + } catch (Exception e) { + log.error("Couldn't clone map", e); + return value; + } + } + + public PropertyIterator valueIterator() { + return getIterator(value.values()); + } + + public void addProperty(String name, JMeterProperty prop) { + if (!value.containsKey(name)) { + value.put(name, prop); + } + } + + public void setMap(Map newMap) { + value = normalizeMap(newMap); + } + + /** {@inheritDoc} */ + public void recoverRunningVersion(TestElement owner) { + if (savedValue != null) { + value = savedValue; + } + recoverRunningVersionOfSubElements(owner); + } + + /** {@inheritDoc} */ + @Override + public void clear() { + value.clear(); + } + + /** {@inheritDoc} */ + @Override + public PropertyIterator iterator() { + return valueIterator(); + } + + /** {@inheritDoc} */ + @Override + public void setRunningVersion(boolean running) { + super.setRunningVersion(running); + if (running) { + savedValue = value; + } else { + savedValue = null; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/MultiProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/MultiProperty.java new file mode 100644 index 0000000..3a2e02f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/MultiProperty.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * For JMeterProperties that hold multiple properties within, provides a simple + * interface for retrieving a property iterator for the sub values. + * + * @version $Revision: 905027 $ + */ +public abstract class MultiProperty extends AbstractProperty { + private static final long serialVersionUID = 240L; + + public MultiProperty() { + super(); + } + + public MultiProperty(String name) { + super(name); + } + + /** + * Get the property iterator to iterate through the sub-values of this + * JMeterProperty. + * + * @return an iterator for the sub-values of this property + */ + public abstract PropertyIterator iterator(); + + /** + * Add a property to the collection. + */ + public abstract void addProperty(JMeterProperty prop); + + /** + * Clear away all values in the property. + */ + public abstract void clear(); + + @Override + public void setRunningVersion(boolean running) { + super.setRunningVersion(running); + PropertyIterator iter = iterator(); + while (iter.hasNext()) { + iter.next().setRunningVersion(running); + } + } + + protected void recoverRunningVersionOfSubElements(TestElement owner) { + PropertyIterator iter = iterator(); + while (iter.hasNext()) { + JMeterProperty prop = iter.next(); + if (owner.isTemporary(prop)) { + iter.remove(); + } else { + prop.recoverRunningVersion(owner); + } + } + } + + @Override + public void mergeIn(JMeterProperty prop) { + if (prop.getObjectValue() == getObjectValue()) { + return; + } + log.debug("merging in " + prop.getClass()); + if (prop instanceof MultiProperty) { + PropertyIterator iter = ((MultiProperty) prop).iterator(); + while (iter.hasNext()) { + JMeterProperty item = iter.next(); + addProperty(item); + } + } else { + addProperty(prop); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/NullProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/NullProperty.java new file mode 100644 index 0000000..ffe4b4f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/NullProperty.java @@ -0,0 +1,135 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * A null property. + * + */ +public final class NullProperty extends AbstractProperty { + private static final long serialVersionUID = 240L; + + private JMeterProperty tempValue; // TODO - why does null property have a value? + + public NullProperty(String name) { + super(name); + } + + public NullProperty() { + super(); + } + + /** + * @see JMeterProperty#getStringValue() + */ + public String getStringValue() { + if (tempValue != null) { + return tempValue.getStringValue(); + } + return ""; + } + + public void setObjectValue(Object v) { + } + + /** + * @see JMeterProperty#getObjectValue() + */ + public Object getObjectValue() { + return null; + } + + /** + * @see JMeterProperty#isRunningVersion() + */ + @Override + public boolean isRunningVersion() { + return false; + } + + /** + * see JMeterProperty#isTemporary(TestElement) + */ + public boolean isTemporary(TestElement owner) { + return true; + } + + /** + * @see JMeterProperty#mergeIn(JMeterProperty) + */ + @Override + public void mergeIn(JMeterProperty prop) { + tempValue = prop; + } + + @Override + public final NullProperty clone() { + return this; + } + + /** + * @see JMeterProperty#getBooleanValue() + */ + @Override + public boolean getBooleanValue() { + return false; + } + + /** + * @see JMeterProperty#getDoubleValue() + */ + @Override + public double getDoubleValue() { + return 0; + } + + /** + * @see JMeterProperty#getFloatValue() + */ + @Override + public float getFloatValue() { + return 0; + } + + /** + * @see JMeterProperty#getIntValue() + */ + @Override + public int getIntValue() { + return 0; + } + + /** + * @see JMeterProperty#getLongValue() + */ + @Override + public long getLongValue() { + return 0; + } + + /** + * @see JMeterProperty#recoverRunningVersion(TestElement) + */ + public void recoverRunningVersion(TestElement owner) { + tempValue = null; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/NumberProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/NumberProperty.java new file mode 100644 index 0000000..3617a43 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/NumberProperty.java @@ -0,0 +1,71 @@ +/* + * 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. + * + */ + +/* + * Created on May 5, 2003 + */ +package org.apache.jmeter.testelement.property; + +public abstract class NumberProperty extends AbstractProperty { + private static final long serialVersionUID = 240L; + + public NumberProperty() { + super(); + } + + public NumberProperty(String name) { + super(name); + } + + /** + * Set the value of the property with a Number object. + */ + protected abstract void setNumberValue(Number n); + + /** + * Set the value of the property with a String object. + */ + protected abstract void setNumberValue(String n) throws NumberFormatException; + + public void setObjectValue(Object v) { + if (v instanceof Number) { + setNumberValue((Number) v); + } else { + try { + setNumberValue(v.toString()); + } catch (RuntimeException e) { + } + } + } + + /** + * @see Comparable#compareTo(Object) + */ + @Override + public int compareTo(JMeterProperty arg0) { + double compareValue = getDoubleValue() - arg0.getDoubleValue(); + + if (compareValue < 0) { + return -1; + } else if (compareValue == 0) { + return 0; + } else { + return 1; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/ObjectProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/ObjectProperty.java new file mode 100644 index 0000000..cee6629 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/ObjectProperty.java @@ -0,0 +1,106 @@ +/* + * 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. + * + */ + +/* + * Created on Sep 16, 2004 + */ +package org.apache.jmeter.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +public class ObjectProperty extends AbstractProperty { + private static final long serialVersionUID = 1; + + private Object value; + + private Object savedValue; + + /** + * {@inheritDoc} + */ + public void recoverRunningVersion(TestElement owner) { + if (savedValue != null) { + value = savedValue; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + super.setRunningVersion(runningVersion); + if (runningVersion) { + savedValue = value; + } else { + savedValue = null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public ObjectProperty clone() { + ObjectProperty p = (ObjectProperty) super.clone(); + p.value = value; + return p; + } + + /** + * + */ + public ObjectProperty() { + super(); + // TODO Auto-generated constructor stub + } + + /** + * @param name + */ + public ObjectProperty(String name) { + super(name); + } + + public ObjectProperty(String name, Object p) { + super(name); + value = p; + } + + /** + * {@inheritDoc} + */ + public String getStringValue() { + return value.toString(); + } + + /** + * {@inheritDoc} + */ + public Object getObjectValue() { + return value; + } + + /** + * {@inheritDoc} + */ + public void setObjectValue(Object value) { + this.value = value; + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/PropertyIterator.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/PropertyIterator.java new file mode 100644 index 0000000..b84553f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/PropertyIterator.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.jmeter.testelement.property; + +public interface PropertyIterator { + public boolean hasNext(); + + public JMeterProperty next(); + + public void remove(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/PropertyIteratorImpl.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/PropertyIteratorImpl.java new file mode 100644 index 0000000..d6c286f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/PropertyIteratorImpl.java @@ -0,0 +1,47 @@ +/* + * 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.testelement.property; + +import java.util.Collection; +import java.util.Iterator; + +public class PropertyIteratorImpl implements PropertyIterator { + + private final Iterator iter; + + public PropertyIteratorImpl(Collection value) { + iter = value.iterator(); + } + + /** {@inheritDoc} */ + public boolean hasNext() { + return iter.hasNext(); + } + + /** {@inheritDoc} */ + public JMeterProperty next() { + return iter.next(); + } + + /** {@inheritDoc} */ + public void remove() { + iter.remove(); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/StringProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/StringProperty.java new file mode 100644 index 0000000..cdfbd74 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/StringProperty.java @@ -0,0 +1,104 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +/** + * @version $Revision: 1188141 $ + */ +public class StringProperty extends AbstractProperty { + private static final long serialVersionUID = 233L; + + private String value; + + private transient String savedValue; + + public StringProperty(String name, String value) { + super(name); + this.value = value; + } + + public StringProperty() { + super(); + } + + /** + * @see JMeterProperty#setRunningVersion(boolean) + */ + @Override + public void setRunningVersion(boolean runningVersion) { + super.setRunningVersion(runningVersion); + if (runningVersion) { + savedValue = value; + } else { + savedValue = null; + } + } + + /** + * {@inheritDoc} + */ + public void setObjectValue(Object v) { + value = v.toString(); + } + + /** + * @see JMeterProperty#getStringValue() + */ + public String getStringValue() { + return value; + } + + /** + * @see JMeterProperty#getObjectValue() + */ + public Object getObjectValue() { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public StringProperty clone() { + StringProperty prop = (StringProperty) super.clone(); + prop.value = value; + return prop; + } + + /** + * Sets the value. + * + * @param value + * The value to set + */ + public void setValue(String value) { + this.value = value; + } + + /** + * {@inheritDoc} + */ + public void recoverRunningVersion(TestElement owner) { + if (savedValue != null) { + value = savedValue; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/testelement/property/TestElementProperty.java b/ApacheJmeter/src/org/apache/jmeter/testelement/property/TestElementProperty.java new file mode 100644 index 0000000..7e45055 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/testelement/property/TestElementProperty.java @@ -0,0 +1,162 @@ +/* + * 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.testelement.property; + +import org.apache.jmeter.testelement.TestElement; + +public class TestElementProperty extends MultiProperty { + private static final long serialVersionUID = 233L; + + private TestElement value; + + private transient TestElement savedValue = null; + + public TestElementProperty(String name, TestElement value) { + super(name); + this.value = value; + } + + public TestElementProperty() { + super(); + } + + /** + * Determines if two test elements are equal. + * + * @return true if the value is not null and equals the other Objects value; + * false otherwise (even if both values are null) + */ + @Override + public boolean equals(Object o) { + if (o instanceof TestElementProperty) { + if (this == o) { + return true; + } + if (value != null) { + return value.equals(((JMeterProperty) o).getObjectValue()); + } + } + return false; + } + + @Override + public int hashCode() { + return value == null ? 0 : value.hashCode(); + } + + /** + * {@inheritDoc} + */ + public String getStringValue() { + return value.toString(); + } + + /** + * {@inheritDoc} + */ + public void setObjectValue(Object v) { + if (v instanceof TestElement) { + value = (TestElement) v; + } + } + + /** + * {@inheritDoc} + */ + public Object getObjectValue() { + return value; + } + + public TestElement getElement() { + return value; + } + + public void setElement(TestElement el) { + value = el; + } + + /** + * {@inheritDoc} + */ + @Override + public TestElementProperty clone() { + TestElementProperty prop = (TestElementProperty) super.clone(); + prop.value = (TestElement) value.clone(); + return prop; + } + + /** + * {@inheritDoc} + */ + @Override + public void mergeIn(JMeterProperty prop) { + if (isEqualType(prop)) { + value.addTestElement((TestElement) prop.getObjectValue()); + } + } + + /** + * {@inheritDoc} + */ + public void recoverRunningVersion(TestElement owner) { + if (savedValue != null) { + value = savedValue; + } + value.recoverRunningVersion(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setRunningVersion(boolean runningVersion) { + super.setRunningVersion(runningVersion); + value.setRunningVersion(runningVersion); + if (runningVersion) { + savedValue = value; + } else { + savedValue = null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addProperty(JMeterProperty prop) { + value.setProperty(prop); + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + value.clear(); + + } + + /** + * {@inheritDoc} + */ + @Override + public PropertyIterator iterator() { + return value.propertyIterator(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/AbstractThreadGroup.java b/ApacheJmeter/src/org/apache/jmeter/threads/AbstractThreadGroup.java new file mode 100644 index 0000000..27b1276 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/AbstractThreadGroup.java @@ -0,0 +1,220 @@ +/* + * 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.threads; + +import java.io.Serializable; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.LoopController; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.TestElementProperty; + +/** + * ThreadGroup holds the settings for a JMeter thread group. + * + * This class is intended to be ThreadSafe. + */ +public abstract class AbstractThreadGroup extends AbstractTestElement implements Serializable, Controller { + + private static final long serialVersionUID = 240L; + + /** Action to be taken when a Sampler error occurs */ + public final static String ON_SAMPLE_ERROR = "ThreadGroup.on_sample_error"; // int + + /** Continue, i.e. ignore sampler errors */ + public final static String ON_SAMPLE_ERROR_CONTINUE = "continue"; + + /** Start next loop for current thread if sampler error occurs */ + public final static String ON_SAMPLE_ERROR_START_NEXT_LOOP = "startnextloop"; + + /** Stop current thread if sampler error occurs */ + public final static String ON_SAMPLE_ERROR_STOPTHREAD = "stopthread"; + + /** Stop test (all threads) if sampler error occurs */ + public final static String ON_SAMPLE_ERROR_STOPTEST = "stoptest"; + + /** Stop test NOW (all threads) if sampler error occurs */ + public final static String ON_SAMPLE_ERROR_STOPTEST_NOW = "stoptestnow"; + + /** Number of threads in the thread group */ + public final static String NUM_THREADS = "ThreadGroup.num_threads"; + + public final static String MAIN_CONTROLLER = "ThreadGroup.main_controller"; + + // @GuardedBy("this") + private int numberOfThreads = 0; // Number of active threads in this group + + /** {@inheritDoc} */ + public boolean isDone() { + return getSamplerController().isDone(); + } + + /** {@inheritDoc} */ + public Sampler next() { + return getSamplerController().next(); + } + + /** + * Get the sampler controller. + * + * @return the sampler controller. + */ + public Controller getSamplerController() { + Controller c = (Controller) getProperty(MAIN_CONTROLLER).getObjectValue(); + return c; + } + + /** + * Set the sampler controller. + * + * @param c + * the sampler controller. + */ + public void setSamplerController(LoopController c) { + c.setContinueForever(false); + setProperty(new TestElementProperty(MAIN_CONTROLLER, c)); + } + + /** + * Add a test element. + * + * @param child + * the test element to add. + */ + @Override + public void addTestElement(TestElement child) { + getSamplerController().addTestElement(child); + } + + /** {@inheritDoc} */ + public void addIterationListener(LoopIterationListener lis) { + getSamplerController().addIterationListener(lis); + } + + /** {@inheritDoc} */ + public void removeIterationListener(LoopIterationListener iterationListener) { + getSamplerController().removeIterationListener(iterationListener); + } + + /** {@inheritDoc} */ + public void initialize() { + Controller c = getSamplerController(); + JMeterProperty property = c.getProperty(TestElement.NAME); + property.setObjectValue(getName()); // Copy our name into that of the controller + property.setRunningVersion(property.isRunningVersion());// otherwise name reverts + c.initialize(); + } + + /** + * Start next iteration after an error + */ + public void startNextLoop() { + ((LoopController) getSamplerController()).startNextLoop(); + } + + /** + * NOOP + */ + public void triggerEndOfLoop() { + // NOOP + } + + /** + * Set the total number of threads to start + * + * @param numThreads + * the number of threads. + */ + public void setNumThreads(int numThreads) { + setProperty(new IntegerProperty(NUM_THREADS, numThreads)); + } + + /** + * Increment the number of active threads + */ + synchronized void incrNumberOfThreads() { + numberOfThreads++; + } + + /** + * Decrement the number of active threads + */ + synchronized void decrNumberOfThreads() { + numberOfThreads--; + } + + /** + * Get the number of active threads + */ + public synchronized int getNumberOfThreads() { + return numberOfThreads; + } + + /** + * Get the number of threads. + * + * @return the number of threads. + */ + public int getNumThreads() { + return this.getPropertyAsInt(AbstractThreadGroup.NUM_THREADS); + } + + /** + * Check if a sampler error should cause thread to start next loop. + * + * @return true if thread should start next loop + */ + public boolean getOnErrorStartNextLoop() { + return getPropertyAsString(ThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_START_NEXT_LOOP); + } + + /** + * Check if a sampler error should cause thread to stop. + * + * @return true if thread should stop + */ + public boolean getOnErrorStopThread() { + return getPropertyAsString(ThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_STOPTHREAD); + } + + /** + * Check if a sampler error should cause test to stop. + * + * @return true if test (all threads) should stop + */ + public boolean getOnErrorStopTest() { + return getPropertyAsString(ThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_STOPTEST); + } + + /** + * Check if a sampler error should cause test to stop now. + * + * @return true if test (all threads) should stop immediately + */ + public boolean getOnErrorStopTestNow() { + return getPropertyAsString(ThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_STOPTEST_NOW); + } + + public abstract void scheduleThread(JMeterThread thread); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/FindTestElementsUpToRootTraverser.java b/ApacheJmeter/src/org/apache/jmeter/threads/FindTestElementsUpToRootTraverser.java new file mode 100644 index 0000000..5d03879 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/FindTestElementsUpToRootTraverser.java @@ -0,0 +1,100 @@ +/* + * 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.threads; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * HashTreeTraverser implementation that stores in a Stack all + * the Test Elements on the path to a particular node. + */ +public class FindTestElementsUpToRootTraverser implements HashTreeTraverser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final LinkedList stack = new LinkedList(); + + /** + * Node to find in TestTree + */ + private Object nodeToFind; + /** + * Once we find the node in the Tree we stop recording nodes + */ + private boolean stopRecording = false; + + /** + * @param nodeToFind Node to find + */ + public FindTestElementsUpToRootTraverser(Object nodeToFind) { + this.nodeToFind = nodeToFind; + } + + /** {@inheritDoc} */ + public void addNode(Object node, HashTree subTree) { + if(stopRecording) { + return; + } + if(node == nodeToFind) { + this.stopRecording = true; + } + stack.addLast((TestElement) node); + } + + /** {@inheritDoc} */ + public void subtractNode() { + if(stopRecording) { + return; + } + if(log.isDebugEnabled()) { + log.debug("Subtracting node, stack size = " + stack.size()); + } + stack.removeLast(); + } + + /** {@inheritDoc} */ + public void processPath() { + //NOOP + } + + /** + * Returns all controllers that where in Tree down to nodeToFind in reverse order (from leaf to root) + * @return List + */ + public List getControllersToRoot() { + List result = new ArrayList(stack.size()); + LinkedList stackLocalCopy = new LinkedList(stack); + while(stackLocalCopy.size()>0) { + TestElement te = stackLocalCopy.getLast(); + if(te instanceof Controller) { + result.add((Controller)te); + } + stackLocalCopy.removeLast(); + } + return result; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/JMeterContext.java b/ApacheJmeter/src/org/apache/jmeter/threads/JMeterContext.java new file mode 100644 index 0000000..d21c6a0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/JMeterContext.java @@ -0,0 +1,208 @@ +/* + * 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.threads; + +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; + +/** + * Holds context for a thread. + * Generated by JMeterContextService. + * + * The class is not thread-safe - it is only intended for use within a single thread. + */ +public class JMeterContext { + private JMeterVariables variables; + + private SampleResult previousResult; + + private Sampler currentSampler; + + private Sampler previousSampler; + + private boolean samplingStarted; + + private StandardJMeterEngine engine; + + private JMeterThread thread; + + private AbstractThreadGroup threadGroup; + + private int threadNum; + + private boolean isReinitSubControllers = false; + + private boolean restartNextLoop = false; + + JMeterContext() { + clear0(); + } + + public void clear() { + clear0(); + } + + private void clear0() { + variables = null; + previousResult = null; + currentSampler = null; + previousSampler = null; + samplingStarted = false; + threadNum = 0; + thread = null; + isReinitSubControllers = false; + } + + /** + * Gives access to the JMeter variables for the current thread. + * + * @return a pointer to the JMeter variables. + */ + public JMeterVariables getVariables() { + return variables; + } + + public void setVariables(JMeterVariables vars) { + this.variables = vars; + } + + public SampleResult getPreviousResult() { + return previousResult; + } + + public void setPreviousResult(SampleResult result) { + this.previousResult = result; + } + + public Sampler getCurrentSampler() { + return currentSampler; + } + + public void setCurrentSampler(Sampler sampler) { + this.previousSampler = currentSampler; + this.currentSampler = sampler; + } + + /** + * Returns the previousSampler. + * + * @return Sampler + */ + public Sampler getPreviousSampler() { + return previousSampler; + } + + /** + * Returns the threadNum. + * + * @return int + */ + public int getThreadNum() { + return threadNum; + } + + /** + * Sets the threadNum. + * + * @param threadNum + * the threadNum to set + */ + public void setThreadNum(int threadNum) { + this.threadNum = threadNum; + } + + public JMeterThread getThread() { + return this.thread; + } + + public void setThread(JMeterThread thread) { + this.thread = thread; + } + + public AbstractThreadGroup getThreadGroup() { + return this.threadGroup; + } + + public void setThreadGroup(AbstractThreadGroup threadgrp) { + this.threadGroup = threadgrp; + } + + public StandardJMeterEngine getEngine() { + return engine; + } + + public void setEngine(StandardJMeterEngine engine) { + this.engine = engine; + } + + public boolean isSamplingStarted() { + return samplingStarted; + } + + public void setSamplingStarted(boolean b) { + samplingStarted = b; + } + + /** + * Reset flag indicating listeners should not be notified since reinit of sub + * controllers is being done. See bug 50032 + */ + public void unsetIsReinitializingSubControllers() { + if (isReinitSubControllers) { + isReinitSubControllers = false; + } + } + + /** + * Set flag indicating listeners should not be notified since reinit of sub + * controllers is being done. See bug 50032 + * @return true if it is the first one to set + */ + public boolean setIsReinitializingSubControllers() { + if (!isReinitSubControllers) { + isReinitSubControllers = true; + return true; + } + return false; + } + + /** + * @return true if within reinit of Sub Controllers + */ + public boolean isReinitializingSubControllers() { + return isReinitSubControllers; + } + + /** + * if set to true a restart of the loop will occurs + * @param restartNextLoop + */ + public void setRestartNextLoop(boolean restartNextLoop) { + this.restartNextLoop = restartNextLoop; + } + + /** + * a restart of the loop was required ? + * @return the restartNextLoop + */ + public boolean isRestartNextLoop() { + return restartNextLoop; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/JMeterContextService.java b/ApacheJmeter/src/org/apache/jmeter/threads/JMeterContextService.java new file mode 100644 index 0000000..439a59b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/JMeterContextService.java @@ -0,0 +1,146 @@ +/* + * 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.threads; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Provides context service for JMeter threads. + * Keeps track of active and total thread counts. + */ +public final class JMeterContextService { + private static final ThreadLocal threadContext = new ThreadLocal() { + @Override + public JMeterContext initialValue() { + return new JMeterContext(); + } + }; + + //@GuardedGy("this") + private static long testStart = 0; + + //@GuardedGy("this") + private static int numberOfActiveThreads = 0; + + //@GuardedGy("this") + private static int totalThreads = 0; + + /** + * Private constructor to prevent instantiation. + */ + private JMeterContextService() { + } + + /** + * Gives access to the current thread context. + * + * @return the current thread Context + */ + public static JMeterContext getContext() { + return threadContext.get(); + } + + /** + * Allows the thread Context to be completely cleared. + *
+ * Invokes {@link ThreadLocal#remove()}. + */ + static void removeContext(){ // Currently only used by JMeterThread + threadContext.remove(); + } + + /** + * Replace Thread Context by the parameter. + * Currently only used by {@link org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.ASyncSample HTTPSamplerBase.ASyncSample} + * @param context {@link JMeterContext} + */ + public static void replaceContext(JMeterContext context) { + threadContext.remove(); + threadContext.set(context); + } + /** + * Method is called by the JMeterEngine class when a test run is started. + * Zeroes numberOfActiveThreads. + * Saves current time in a field and in the JMeter property "TESTSTART.MS" + */ + public static synchronized void startTest() { + if (testStart == 0) { + numberOfActiveThreads = 0; + testStart = System.currentTimeMillis(); + JMeterUtils.setProperty("TESTSTART.MS",Long.toString(testStart));// $NON-NLS-1$ + } + } + + /** + * Increment number of active threads. + */ + static synchronized void incrNumberOfThreads() { + numberOfActiveThreads++; + } + + /** + * Decrement number of active threads. + */ + static synchronized void decrNumberOfThreads() { + numberOfActiveThreads--; + } + + /** + * Get the number of currently active threads + * @return active thread count + */ + public static synchronized int getNumberOfThreads() { + return numberOfActiveThreads; + } + + /** + * Called by MainFrame#testEnded(). + * Clears start time field. + */ + public static synchronized void endTest() { + testStart = 0; + } + + public static synchronized long getTestStartTime() {// NOT USED + return testStart; + } + + /** + * Get the total number of threads (>= active) + * @return total thread count + */ + public static synchronized int getTotalThreads() { + return totalThreads; + } + + /** + * Update the total number of threads + * @param thisGroup number of threads in this thread group + */ + public static synchronized void addTotalThreads(int thisGroup) { + totalThreads += thisGroup; + } + + /** + * Set total threads to zero + */ + public static synchronized void clearTotalThreads() { + totalThreads = 0; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/JMeterThread.java b/ApacheJmeter/src/org/apache/jmeter/threads/JMeterThread.java new file mode 100644 index 0000000..dcb0c72 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/JMeterThread.java @@ -0,0 +1,901 @@ +/* + * 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.threads; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.jmeter.assertions.Assertion; +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.TransactionSampler; +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.samplers.Interruptible; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBeanHelper; +import org.apache.jmeter.testelement.AbstractScopedAssertion; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.timers.Timer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.collections.SearchByClass; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterStopTestException; +import org.apache.jorphan.util.JMeterStopTestNowException; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.log.Logger; + +/** + * The JMeter interface to the sampling process, allowing JMeter to see the + * timing, add listeners for sampling events and to stop the sampling process. + * + */ +public class JMeterThread implements Runnable, Interruptible { + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final String PACKAGE_OBJECT = "JMeterThread.pack"; // $NON-NLS-1$ + + public static final String LAST_SAMPLE_OK = "JMeterThread.last_sample_ok"; // $NON-NLS-1$ + + private static final String TRUE = Boolean.toString(true); // i.e. "true" + + /** How often to check for shutdown during ramp-up, default 1000ms */ + private static final int RAMPUP_GRANULARITY = + JMeterUtils.getPropDefault("jmeterthread.rampup.granularity", 1000); // $NON-NLS-1$ + + private final Controller controller; + + private final HashTree testTree; + + private final TestCompiler compiler; + + private final JMeterThreadMonitor monitor; + + private final JMeterVariables threadVars; + + private final Collection testListeners; + + private final ListenerNotifier notifier; + + /* + * The following variables are set by StandardJMeterEngine. + * This is done before start() is called, so the values will be published to the thread safely + * TODO - consider passing them to the constructor, so that they can be made final + * (to avoid adding lots of parameters, perhaps have a parameter wrapper object. + */ + private String threadName; + + private int initialDelay = 0; + + private int threadNum = 0; + + private long startTime = 0; + + private long endTime = 0; + + private boolean scheduler = false; + // based on this scheduler is enabled or disabled + + // Gives access to parent thread threadGroup + private AbstractThreadGroup threadGroup; + + private StandardJMeterEngine engine = null; // For access to stop methods. + + /* + * The following variables may be set/read from multiple threads. + */ + private volatile boolean running; // may be set from a different thread + + private volatile boolean onErrorStopTest; + + private volatile boolean onErrorStopTestNow; + + private volatile boolean onErrorStopThread; + + private volatile boolean onErrorStartNextLoop; + + private volatile Sampler currentSampler; + + private final ReentrantLock interruptLock = new ReentrantLock(); // ensure that interrupt cannot overlap with shutdown + + public JMeterThread(HashTree test, JMeterThreadMonitor monitor, ListenerNotifier note) { + this.monitor = monitor; + threadVars = new JMeterVariables(); + testTree = test; + compiler = new TestCompiler(testTree, threadVars); + controller = (Controller) testTree.getArray()[0]; + SearchByClass threadListenerSearcher = new SearchByClass(TestListener.class); + test.traverse(threadListenerSearcher); + testListeners = threadListenerSearcher.getSearchResults(); + notifier = note; + running = true; + } + + public void setInitialContext(JMeterContext context) { + threadVars.putAll(context.getVariables()); + } + + /** + * Enable the scheduler for this JMeterThread. + */ + public void setScheduled(boolean sche) { + this.scheduler = sche; + } + + /** + * Set the StartTime for this Thread. + * + * @param stime the StartTime value. + */ + public void setStartTime(long stime) { + startTime = stime; + } + + /** + * Get the start time value. + * + * @return the start time value. + */ + public long getStartTime() { + return startTime; + } + + /** + * Set the EndTime for this Thread. + * + * @param etime + * the EndTime value. + */ + public void setEndTime(long etime) { + endTime = etime; + } + + /** + * Get the end time value. + * + * @return the end time value. + */ + public long getEndTime() { + return endTime; + } + + /** + * Check the scheduled time is completed. + * + */ + private void stopScheduler() { + long delay = System.currentTimeMillis() - endTime; + if ((delay >= 0)) { + running = false; + } + } + + /** + * Wait until the scheduled start time if necessary + * + */ + private void startScheduler() { + long delay = (startTime - System.currentTimeMillis()); + if (delay > 0) { + long start = System.currentTimeMillis(); + long end = start + delay; + long now=0; + long pause = RAMPUP_GRANULARITY; + while(running && (now = System.currentTimeMillis()) < end) { + long togo = end - now; + if (togo < pause) { + pause = togo; + } + try { + Thread.sleep(pause); // delay between checks + } catch (InterruptedException e) { + if (running) { // Don't bother reporting stop test interruptions + log.warn("startScheduler delay for "+threadName+" was interrupted. Waited "+(now - start)+" milli-seconds out of "+delay); + } + break; + } + } + } + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + /* + * See below for reason for this change. Just in case this causes problems, + * allow the change to be backed out + */ + private static final boolean startEarlier = + JMeterUtils.getPropDefault("jmeterthread.startearlier", true); // $NON-NLS-1$ + + private static final boolean reversePostProcessors = + JMeterUtils.getPropDefault("jmeterthread.reversePostProcessors",false); // $NON-NLS-1$ + + static { + if (startEarlier) { + log.info("jmeterthread.startearlier=true (see jmeter.properties)"); + } else { + log.info("jmeterthread.startearlier=false (see jmeter.properties)"); + } + if (reversePostProcessors) { + log.info("Running PostProcessors in reverse order"); + } else { + log.info("Running PostProcessors in forward order"); + } + } + + public void run() { + // threadContext is not thread-safe, so keep within thread + JMeterContext threadContext = JMeterContextService.getContext(); + LoopIterationListener iterationListener=null; + + try { + iterationListener = initRun(threadContext); + while (running) { + Sampler firstSampler = controller.next(); + Sampler sam = firstSampler; + while (running && sam != null) { + process_sampler(sam, null, threadContext); + if(onErrorStartNextLoop || threadContext.isRestartNextLoop()) { + if(threadContext.isRestartNextLoop()) { + triggerEndOfLoopOnParentControllers(sam, threadContext); + sam = null; + threadContext.getVariables().put(LAST_SAMPLE_OK, TRUE); + threadContext.setRestartNextLoop(false); + } else { + boolean lastSampleFailed = !TRUE.equals(threadContext.getVariables().get(LAST_SAMPLE_OK)); + if(lastSampleFailed) { + if(log.isDebugEnabled()) { + log.debug("StartNextLoop option is on, Last sample failed, starting next loop"); + } + triggerEndOfLoopOnParentControllers(sam, threadContext); + sam = null; + threadContext.getVariables().put(LAST_SAMPLE_OK, TRUE); + } else { + sam = controller.next(); + } + } + } + else { + sam = controller.next(); + } + } + if (controller.isDone()) { + running = false; + } + } + } + // Might be found by contoller.next() + catch (JMeterStopTestException e) { + log.info("Stopping Test: " + e.toString()); + stopTest(); + } + catch (JMeterStopTestNowException e) { + log.info("Stopping Test Now: " + e.toString()); + stopTestNow(); + } catch (JMeterStopThreadException e) { + log.info("Stop Thread seen: " + e.toString()); + } catch (Exception e) { + log.error("Test failed!", e); + } catch (ThreadDeath e) { + throw e; // Must not ignore this one + } catch (Error e) {// Make sure errors are output to the log file + log.error("Test failed!", e); + } finally { + currentSampler = null; // prevent any further interrupts + try { + interruptLock.lock(); // make sure current interrupt is finished, prevent another starting yet + threadContext.clear(); + log.info("Thread finished: " + threadName); + threadFinished(iterationListener); + monitor.threadFinished(this); // Tell the engine we are done + JMeterContextService.removeContext(); // Remove the ThreadLocal entry + } + finally { + interruptLock.unlock(); // Allow any pending interrupt to complete (OK because currentSampler == null) + } + } + } + + /** + * Trigger end of loop on parent controllers up to Thread Group + * @param sam Sampler Base sampler + * @param threadContext + */ + private void triggerEndOfLoopOnParentControllers(Sampler sam, JMeterContext threadContext) { + // Find parent controllers of current sampler + FindTestElementsUpToRootTraverser pathToRootTraverser=null; + TransactionSampler transactionSampler = null; + if(sam instanceof TransactionSampler) { + transactionSampler = (TransactionSampler) sam; + pathToRootTraverser = new FindTestElementsUpToRootTraverser((transactionSampler).getTransactionController()); + } else { + pathToRootTraverser = new FindTestElementsUpToRootTraverser(sam); + } + testTree.traverse(pathToRootTraverser); + List controllersToReinit = pathToRootTraverser.getControllersToRoot(); + + // Trigger end of loop condition on all parent controllers of current sampler + for (Iterator iterator = controllersToReinit + .iterator(); iterator.hasNext();) { + Controller parentController = iterator.next(); + if(parentController instanceof ThreadGroup) { + ThreadGroup tg = (ThreadGroup) parentController; + tg.startNextLoop(); + } else { + parentController.triggerEndOfLoop(); + } + } + if(transactionSampler!=null) { + process_sampler(transactionSampler, null, threadContext); + } + } + + /** + * Process the current sampler, handling transaction samplers. + * + * @param current sampler + * @param parent sampler + * @param threadContext + * @return SampleResult if a transaction was processed + */ + @SuppressWarnings("deprecation") // OK to call TestBeanHelper.prepare() + private SampleResult process_sampler(Sampler current, Sampler parent, JMeterContext threadContext) { + SampleResult transactionResult = null; + try { + // Check if we are running a transaction + TransactionSampler transactionSampler = null; + if(current instanceof TransactionSampler) { + transactionSampler = (TransactionSampler) current; + } + // Find the package for the transaction + SamplePackage transactionPack = null; + if(transactionSampler != null) { + transactionPack = compiler.configureTransactionSampler(transactionSampler); + + // Check if the transaction is done + if(transactionSampler.isTransactionDone()) { + // Get the transaction sample result + transactionResult = transactionSampler.getTransactionResult(); + transactionResult.setThreadName(threadName); + transactionResult.setGroupThreads(threadGroup.getNumberOfThreads()); + transactionResult.setAllThreads(JMeterContextService.getNumberOfThreads()); + + // Check assertions for the transaction sample + checkAssertions(transactionPack.getAssertions(), transactionResult, threadContext); + // Notify listeners with the transaction sample result + if (!(parent instanceof TransactionSampler)){ + notifyListeners(transactionPack.getSampleListeners(), transactionResult); + } + compiler.done(transactionPack); + // Transaction is done, we do not have a sampler to sample + current = null; + } + else { + Sampler prev = current; + // It is the sub sampler of the transaction that will be sampled + current = transactionSampler.getSubSampler(); + if (current instanceof TransactionSampler){ + SampleResult res = process_sampler(current, prev, threadContext);// recursive call + threadContext.setCurrentSampler(prev); + current=null; + if (res!=null){ + transactionSampler.addSubSamplerResult(res); + } + } + } + } + + // Check if we have a sampler to sample + if(current != null) { + threadContext.setCurrentSampler(current); + // Get the sampler ready to sample + SamplePackage pack = compiler.configureSampler(current); + runPreProcessors(pack.getPreProcessors()); + + // Hack: save the package for any transaction controllers + threadVars.putObject(PACKAGE_OBJECT, pack); + + delay(pack.getTimers()); + Sampler sampler = pack.getSampler(); + sampler.setThreadContext(threadContext); + // TODO should this set the thread names for all the subsamples? + // might be more efficient than fetching the name elsewehere + sampler.setThreadName(threadName); + TestBeanHelper.prepare(sampler); + + // Perform the actual sample + currentSampler = sampler; + SampleResult result = sampler.sample(null); + currentSampler = null; + // TODO: remove this useless Entry parameter + + // If we got any results, then perform processing on the result + if (result != null) { + result.setGroupThreads(threadGroup.getNumberOfThreads()); + result.setAllThreads(JMeterContextService.getNumberOfThreads()); + result.setThreadName(threadName); + threadContext.setPreviousResult(result); + runPostProcessors(pack.getPostProcessors()); + checkAssertions(pack.getAssertions(), result, threadContext); + // Do not send subsamples to listeners which receive the transaction sample + List sampleListeners = getSampleListeners(pack, transactionPack, transactionSampler); + notifyListeners(sampleListeners, result); + compiler.done(pack); + // Add the result as subsample of transaction if we are in a transaction + if(transactionSampler != null) { + transactionSampler.addSubSamplerResult(result); + } + + // Check if thread or test should be stopped + if (result.isStopThread() || (!result.isSuccessful() && onErrorStopThread)) { + stopThread(); + } + if (result.isStopTest() || (!result.isSuccessful() && onErrorStopTest)) { + stopTest(); + } + if (result.isStopTestNow() || (!result.isSuccessful() && onErrorStopTestNow)) { + stopTestNow(); + } + } else { + compiler.done(pack); // Finish up + } + } + if (scheduler) { + // checks the scheduler to stop the iteration + stopScheduler(); + } + } catch (JMeterStopTestException e) { + log.info("Stopping Test: " + e.toString()); + stopTest(); + } catch (JMeterStopThreadException e) { + log.info("Stopping Thread: " + e.toString()); + stopThread(); + } catch (Exception e) { + if (current != null) { + log.error("Error while processing sampler '"+current.getName()+"' :", e); + } else { + log.error("", e); + } + } + return transactionResult; + } + + /** + * Get the SampleListeners for the sampler. Listeners who receive transaction sample + * will not be in this list. + * + * @param samplePack + * @param transactionPack + * @param transactionSampler + * @return the listeners who should receive the sample result + */ + private List getSampleListeners(SamplePackage samplePack, SamplePackage transactionPack, TransactionSampler transactionSampler) { + List sampleListeners = samplePack.getSampleListeners(); + // Do not send subsamples to listeners which receive the transaction sample + if(transactionSampler != null) { + ArrayList onlySubSamplerListeners = new ArrayList(); + List transListeners = transactionPack.getSampleListeners(); + for(SampleListener listener : sampleListeners) { + // Check if this instance is present in transaction listener list + boolean found = false; + for(SampleListener trans : transListeners) { + // Check for the same instance + if(trans == listener) { + found = true; + break; + } + } + if(!found) { + onlySubSamplerListeners.add(listener); + } + } + sampleListeners = onlySubSamplerListeners; + } + return sampleListeners; + } + + /** + * @param threadContext + * @return + * + */ + private IterationListener initRun(JMeterContext threadContext) { + threadContext.setVariables(threadVars); + threadContext.setThreadNum(getThreadNum()); + threadContext.getVariables().put(LAST_SAMPLE_OK, TRUE); + threadContext.setThread(this); + threadContext.setThreadGroup(threadGroup); + threadContext.setEngine(engine); + testTree.traverse(compiler); + // listeners = controller.getListeners(); + if (scheduler) { + // set the scheduler to start + startScheduler(); + } + rampUpDelay(); // TODO - how to handle thread stopped here + log.info("Thread started: " + Thread.currentThread().getName()); + /* + * Setting SamplingStarted before the contollers are initialised allows + * them to access the running values of functions and variables (however + * it does not seem to help with the listeners) + */ + if (startEarlier) { + threadContext.setSamplingStarted(true); + } + controller.initialize(); + IterationListener iterationListener = new IterationListener(); + controller.addIterationListener(iterationListener); + if (!startEarlier) { + threadContext.setSamplingStarted(true); + } + threadStarted(); + return iterationListener; + } + + private void threadStarted() { + JMeterContextService.incrNumberOfThreads(); + threadGroup.incrNumberOfThreads(); + GuiPackage gp =GuiPackage.getInstance(); + if (gp != null) {// check there is a GUI + gp.getMainFrame().updateCounts(); + } + ThreadListenerTraverser startup = new ThreadListenerTraverser(true); + testTree.traverse(startup); // call ThreadListener.threadStarted() + } + + private void threadFinished(LoopIterationListener iterationListener) { + ThreadListenerTraverser shut = new ThreadListenerTraverser(false); + testTree.traverse(shut); // call ThreadListener.threadFinished() + JMeterContextService.decrNumberOfThreads(); + threadGroup.decrNumberOfThreads(); + GuiPackage gp = GuiPackage.getInstance(); + if (gp != null){// check there is a GUI + gp.getMainFrame().updateCounts(); + } + if (iterationListener != null) { // probably not possible, but check anyway + controller.removeIterationListener(iterationListener); + } + } + + private static class ThreadListenerTraverser implements HashTreeTraverser { + private boolean isStart = false; + + private ThreadListenerTraverser(boolean start) { + isStart = start; + } + + public void addNode(Object node, HashTree subTree) { + if (node instanceof ThreadListener) { + ThreadListener tl = (ThreadListener) node; + if (isStart) { + tl.threadStarted(); + } else { + tl.threadFinished(); + } + } + } + + public void subtractNode() { + } + + public void processPath() { + } + } + + public String getThreadName() { + return threadName; + } + + public void stop() { // Called by StandardJMeterEngine, TestAction and AccessLogSampler + running = false; + log.info("Stopping: " + threadName); + } + + /** {@inheritDoc} */ + public boolean interrupt(){ + try { + interruptLock.lock(); + Sampler samp = currentSampler; // fetch once; must be done under lock + if (samp instanceof Interruptible){ // (also protects against null) + log.warn("Interrupting: " + threadName + " sampler: " +samp.getName()); + try { + boolean found = ((Interruptible)samp).interrupt(); + if (!found) { + log.warn("No operation pending"); + } + return found; + } catch (Exception e) { + log.warn("Caught Exception interrupting sampler: "+e.toString()); + } + } else if (samp != null){ + log.warn("Sampler is not Interruptible: "+samp.getName()); + } + } finally { + interruptLock.unlock(); + } + return false; + } + + private void stopTest() { + running = false; + log.info("Stop Test detected by thread: " + threadName); + if (engine != null) { + engine.askThreadsToStop(); + } + } + + private void stopTestNow() { + running = false; + log.info("Stop Test Now detected by thread: " + threadName); + if (engine != null) { + engine.stopTest(); + } + } + + private void stopThread() { + running = false; + log.info("Stop Thread detected by thread: " + threadName); + } + + @SuppressWarnings("deprecation") // OK to call TestBeanHelper.prepare() + private void checkAssertions(List assertions, SampleResult parent, JMeterContext threadContext) { + for (Assertion assertion : assertions) { + TestBeanHelper.prepare((TestElement) assertion); + if (assertion instanceof AbstractScopedAssertion){ + AbstractScopedAssertion scopedAssertion = (AbstractScopedAssertion) assertion; + String scope = scopedAssertion.fetchScope(); + if (scopedAssertion.isScopeParent(scope) || scopedAssertion.isScopeAll(scope) || scopedAssertion.isScopeVariable(scope)){ + processAssertion(parent, assertion); + } + if (scopedAssertion.isScopeChildren(scope) || scopedAssertion.isScopeAll(scope)){ + SampleResult children[] = parent.getSubResults(); + boolean childError = false; + for (int i=0;i extractors) { + ListIterator iter; + if (reversePostProcessors) {// Original (rather odd) behaviour + iter = extractors.listIterator(extractors.size());// start at the end + while (iter.hasPrevious()) { + PostProcessor ex = iter.previous(); + TestBeanHelper.prepare((TestElement) ex); + ex.process(); + } + } else { + for (PostProcessor ex : extractors) { + TestBeanHelper.prepare((TestElement) ex); + ex.process(); + } + } + } + + @SuppressWarnings("deprecation") // OK to call TestBeanHelper.prepare() + private void runPreProcessors(List preProcessors) { + for (PreProcessor ex : preProcessors) { + if (log.isDebugEnabled()) { + log.debug("Running preprocessor: " + ((AbstractTestElement) ex).getName()); + } + TestBeanHelper.prepare((TestElement) ex); + ex.process(); + } + } + + @SuppressWarnings("deprecation") // OK to call TestBeanHelper.prepare() + private void delay(List timers) { + long sum = 0; + for (Timer timer : timers) { + TestBeanHelper.prepare((TestElement) timer); + sum += timer.delay(); + } + if (sum > 0) { + try { + Thread.sleep(sum); + } catch (InterruptedException e) { + log.warn("The delay timer was interrupted - probably did not wait as long as intended."); + } + } + } + + void notifyTestListeners() { + threadVars.incIteration(); + for (TestListener listener : testListeners) { + if (listener instanceof TestElement) { + listener.testIterationStart(new LoopIterationEvent(controller, threadVars.getIteration())); + ((TestElement) listener).recoverRunningVersion(); + } else { + listener.testIterationStart(new LoopIterationEvent(controller, threadVars.getIteration())); + } + } + } + + private void notifyListeners(List listeners, SampleResult result) { + SampleEvent event = new SampleEvent(result, threadGroup.getName(), threadVars); + notifier.notifyListeners(event, listeners); + + } + + public void setInitialDelay(int delay) { + initialDelay = delay; + } + + /** + * Initial delay if ramp-up period is active for this threadGroup. + */ + private void rampUpDelay() { + if (initialDelay > 0) { + long start = System.currentTimeMillis(); + long end = start + initialDelay; + long now=0; + long pause = RAMPUP_GRANULARITY; + while(running && (now = System.currentTimeMillis()) < end) { + long togo = end - now; + if (togo < pause) { + pause = togo; + } + try { + Thread.sleep(pause); // delay between checks + } catch (InterruptedException e) { + if (running) { // Don't bother reporting stop test interruptions + log.warn("RampUp delay for "+threadName+" was interrupted. Waited "+(now - start)+" milli-seconds out of "+initialDelay); + } + break; + } + } + } + } + + /** + * Returns the threadNum. + */ + public int getThreadNum() { + return threadNum; + } + + /** + * Sets the threadNum. + * + * @param threadNum + * the threadNum to set + */ + public void setThreadNum(int threadNum) { + this.threadNum = threadNum; + } + + private class IterationListener implements LoopIterationListener { + /** + * {@inheritDoc} + */ + public void iterationStart(LoopIterationEvent iterEvent) { + notifyTestListeners(); + } + } + + /** + * Save the engine instance for access to the stop methods + * + * @param engine + */ + public void setEngine(StandardJMeterEngine engine) { + this.engine = engine; + } + + /** + * Should Test stop on sampler error? + * + * @param b - + * true or false + */ + public void setOnErrorStopTest(boolean b) { + onErrorStopTest = b; + } + + /** + * Should Test stop abruptly on sampler error? + * + * @param b - + * true or false + */ + public void setOnErrorStopTestNow(boolean b) { + onErrorStopTestNow = b; + } + + /** + * Should Thread stop on Sampler error? + * + * @param b - + * true or false + */ + public void setOnErrorStopThread(boolean b) { + onErrorStopThread = b; + } + + /** + * Should Thread start next loop on Sampler error? + * + * @param b - + * true or false + */ + public void setOnErrorStartNextLoop(boolean b) { + onErrorStartNextLoop = b; + } + + public void setThreadGroup(AbstractThreadGroup group) { + this.threadGroup = group; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/JMeterThreadMonitor.java b/ApacheJmeter/src/org/apache/jmeter/threads/JMeterThreadMonitor.java new file mode 100644 index 0000000..257633f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/JMeterThreadMonitor.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.threads; + +/** + * @version $Revision: 674365 $ + */ +public interface JMeterThreadMonitor { + public void threadFinished(JMeterThread thread); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/JMeterVariables.java b/ApacheJmeter/src/org/apache/jmeter/threads/JMeterVariables.java new file mode 100644 index 0000000..a8cacb7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/JMeterVariables.java @@ -0,0 +1,151 @@ +/* + * 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.threads; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * Class which defines JMeter variables. + * These are similar to properties, but they are local to a single thread. + */ +public class JMeterVariables { + private final Map variables = new HashMap(); + + private int iteration = 0; + + // Property names to preload into JMeter variables: + private static final String [] PRE_LOAD = { + "START.MS", // $NON-NLS-1$ + "START.YMD", // $NON-NLS-1$ + "START.HMS", //$NON-NLS-1$ + "TESTSTART.MS", // $NON-NLS-1$ + }; + + public JMeterVariables() { + preloadVariables(); + } + + private void preloadVariables(){ + for (int i = 0; i < PRE_LOAD.length; i++){ + String property=PRE_LOAD[i]; + String value=JMeterUtils.getProperty(property); + if (value != null){ + variables.put(property,value); + } + } + } + + public String getThreadName() { + return Thread.currentThread().getName(); + } + + public int getIteration() { + return iteration; + } + + public void incIteration() { + iteration++; + } + + // Does not appear to be used + public void initialize() { + variables.clear(); + preloadVariables(); + } + + /** + * Remove a variable. + * + * @param key the variable name to remove + * + * @return the variable value, or {@code null} if there was no such variable + */ + public Object remove(String key) { + return variables.remove(key); + } + + /** + * Creates or updates a variable with a String value. + * + * @param key the variable name + * @param value the variable value + */ + public void put(String key, String value) { + variables.put(key, value); + } + + /** + * Creates or updates a variable with a value that does not have to be a String. + * + * @param key the variable name + * @param value the variable value + */ + public void putObject(String key, Object value) { + variables.put(key, value); + } + + public void putAll(Map vars) { + variables.putAll(vars); + } + + public void putAll(JMeterVariables vars) { + putAll(vars.variables); + } + + /** + * Gets the value of a variable, coerced to a String. + * + * @param key the name of the variable + * @return the value of the variable, or {@code null} if it does not exist + */ + public String get(String key) { + return (String) variables.get(key); + } + + /** + * Gets the value of a variable (not converted to String). + * + * @param key the name of the variable + * @return the value of the variable, or {@code null} if it does not exist + */ + public Object getObject(String key) { + return variables.get(key); + } + + /** + * Gets a read-only Iterator over the variables. + * + * @return the iterator + */ + public Iterator> getIterator(){ + return Collections.unmodifiableMap(variables).entrySet().iterator() ; + } + + // Used by DebugSampler + public Set> entrySet(){ + return Collections.unmodifiableMap(variables).entrySet(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/ListenerNotifier.java b/ApacheJmeter/src/org/apache/jmeter/threads/ListenerNotifier.java new file mode 100644 index 0000000..7008194 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/ListenerNotifier.java @@ -0,0 +1,271 @@ +/* + * 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. + * + */ + +///////////////////////////////////////// +//////// +//////// This code is mostly unused at present +//////// it seems that only notifyListeners() +//////// is used. +//////// +//////// However, it does look useful. +//////// And it may one day be used... +//////// +///////////////////////////////////////// + +package org.apache.jmeter.threads; + +import java.util.List; + +//import org.apache.commons.collections.Buffer; +//import org.apache.commons.collections.BufferUtils; +//import org.apache.commons.collections.buffer.UnboundedFifoBuffer; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.testbeans.TestBeanHelper; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +///** +// * The ListenerNotifier thread is responsible for performing +// * asynchronous notifications that a sample has occurred. Each time a sample +// * occurs, the addLast method should be called to add the sample +// * and its list of listeners to the notification queue. This thread will then +// * notify those listeners asynchronously at some future time. +// *

+// * In the current implementation, the notifications will be made in batches, +// * with 2 seconds between the beginning of successive batches. If the notifier +// * thread starts to get behind, the priority of the thread will be increased in +// * an attempt to help it to keep up. +// * +// * @see org.apache.jmeter.samplers.SampleListener +// * +// */ +/** + * Processes sample events. + * The current implementation processes events in the calling thread + * using {@link #notifyListeners(SampleEvent, List)} + * The other code is not used currently, so is commented out. + */ +public class ListenerNotifier { + private static final Logger log = LoggingManager.getLoggerForClass(); + + + /** + * Notify a list of listeners that a sample has occurred. + * + * @param res + * the sample event that has occurred. Must be non-null. + * @param listeners + * a list of the listeners which should be notified. This list + * must not be null and must contain only SampleListener + * elements. + */ + @SuppressWarnings("deprecation") // TestBeanHelper.prepare() is OK + public void notifyListeners(SampleEvent res, List listeners) { + for (SampleListener sampleListener : listeners) { + try { + TestBeanHelper.prepare((TestElement) sampleListener); + sampleListener.sampleOccurred(res); + } catch (RuntimeException e) { + log.error("Detected problem in Listener: ", e); + log.info("Continuing to process further listeners"); + } + } + } + +// /** +// * The number of milliseconds between batches of notifications. +// */ +// private static final int SLEEP_TIME = 2000; +// +// /** +// * Indicates whether or not this thread should remain running. The thread +// * will continue running after this field is set to false until the next +// * batch of notifications has been completed and the notification queue is +// * empty. +// */ +// private boolean running = true; +// +// /** +// * Indicates whether or not this thread has stopped. No further +// * notifications will be performed. +// */ +// private boolean isStopped = true; +// +// /** +// * The queue containing the notifications to be performed. Each notification +// * consists of a pair of entries in this queue. The first is the +// * {@link org.apache.jmeter.samplers.SampleEvent SampleEvent} representing +// * the sample. The second is a List of +// * {@link org.apache.jmeter.samplers.SampleListener SampleListener}s which +// * should be notified. +// */ +// private Buffer listenerEvents = BufferUtils.synchronizedBuffer(new UnboundedFifoBuffer()); +// +// /** +// * Stops the ListenerNotifier thread. The thread will continue processing +// * any events remaining in the notification queue before it actually stops, +// * but this method will return immediately. +// */ +// public void stop() { +// running = false; +// } +// +// /** +// * Indicates whether or not the thread has stopped. This will not return +// * true until the stop method has been called and any +// * remaining notifications in the queue have been completed. +// * +// * @return true if the ListenerNotifier has completely stopped, false +// * otherwise +// */ +// public boolean isStopped() { +// return isStopped; +// } +// +// /** +// * Process the events in the notification queue until the thread has been +// * told to stop and the notification queue is empty. +// *

+// * In the current implementation, this method will iterate continually until +// * the thread is told to stop. In each iteration it will process any +// * notifications that are in the queue at the beginning of the iteration, +// * and then will sleep until it is time to start the next batch. As long as +// * the thread is keeping up, each batch should start 2 seconds after the +// * beginning of the last batch. This exact behavior is subject to change. +// */ +// public void run() { +// boolean isMaximumPriority = false; +// int normalCount = 0; +// +// while (running) { +// long startTime = System.currentTimeMillis(); +// processNotifications(); +// long sleep = SLEEP_TIME - (System.currentTimeMillis() - startTime); +// +// // If the thread has been told to stop then we shouldn't sleep +// if (!running) { +// break; +// } +// +// if (sleep < 0) { +// isMaximumPriority = true; +// normalCount = 0; +// if (log.isInfoEnabled()) { +// log.info("ListenerNotifier exceeded maximum " + "notification time by " + (-sleep) + "ms"); +// } +// boostPriority(); +// } else { +// normalCount++; +// +// // If there have been three consecutive iterations since the +// // last iteration which took too long to execute, return the +// // thread to normal priority. +// if (isMaximumPriority && normalCount >= 3) { +// isMaximumPriority = false; +// unboostPriority(); +// } +// +// if (log.isDebugEnabled()) { +// log.debug("ListenerNotifier sleeping for " + sleep + "ms"); +// } +// +// try { +// Thread.sleep(sleep); +// } catch (InterruptedException e) { +// } +// } +// } +// +// // Make sure that all pending notifications are processed before +// // actually ending the thread. +// processNotifications(); +// isStopped = true; +// } +// +// /** +// * Process all of the pending notifications. Only the samples which are in +// * the queue when this method is called will be processed. Any samples added +// * between the time when this method is called and when it exits are saved +// * for the next batch. +// */ +// private void processNotifications() { +// int listenerEventsSize = listenerEvents.size(); +// if (log.isDebugEnabled()) { +// log.debug("ListenerNotifier: processing " + listenerEventsSize + " events"); +// } +// +// while (listenerEventsSize > 0) { +// // Since this is a FIFO and this is the only place we remove +// // from it (only from a single thread) we don't have to remove +// // these two items in one atomic operation. Each individual +// // remove is atomic (because we use a synchronized buffer), +// // which is necessary since the buffer can be accessed from +// // other threads (to add things to the buffer). +// SampleEvent res = (SampleEvent) listenerEvents.remove(); +// List listeners = (List) listenerEvents.remove(); +// +// notifyListeners(res, listeners); +// +// listenerEventsSize -= 2; +// } +// } +// +// /** +// * Boost the priority of the current thread to maximum priority. If the +// * thread is already at maximum priority then this will have no effect. +// */ +// private void boostPriority() { +// if (Thread.currentThread().getPriority() != Thread.MAX_PRIORITY) { +// log.info("ListenerNotifier: Boosting thread priority to maximum."); +// Thread.currentThread().setPriority(Thread.MAX_PRIORITY); +// } +// } +// +// /** +// * Return the priority of the current thread to normal. If the thread is +// * already at normal priority then this will have no effect. +// */ +// private void unboostPriority() { +// if (Thread.currentThread().getPriority() != Thread.NORM_PRIORITY) { +// log.info("ListenerNotifier: Returning thread priority to normal."); +// Thread.currentThread().setPriority(Thread.NORM_PRIORITY); +// } +// } +// +// /** +// * Add a new sample event to the notification queue. The notification will +// * be performed asynchronously and this method will return immediately. +// * +// * @param item +// * the sample event that has occurred. Must be non-null. +// * @param listeners +// * a list of the listeners which should be notified. This list +// * must not be null and must contain only SampleListener +// * elements. +// */ +// public void addLast(SampleEvent item, List listeners) { +// // Must use explicit synchronization here so that the item and +// // listeners are added together atomically +// synchronized (listenerEvents) { +// listenerEvents.add(item); +// listenerEvents.add(listeners); +// } +// } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/PostThreadGroup.java b/ApacheJmeter/src/org/apache/jmeter/threads/PostThreadGroup.java new file mode 100644 index 0000000..bcb20c8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/PostThreadGroup.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.jmeter.threads; + + +/** + * PostThreadGroup is a special type of ThreadGroup that can be used for + * performing actions at the end of a test for cleanup and such. + */ +public class PostThreadGroup extends ThreadGroup { + private static final long serialVersionUID = 240L; +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/SamplePackage.java b/ApacheJmeter/src/org/apache/jmeter/threads/SamplePackage.java new file mode 100644 index 0000000..0b321fc --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/SamplePackage.java @@ -0,0 +1,230 @@ +/* + * 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.threads; + +import java.util.List; + +import org.apache.jmeter.assertions.Assertion; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.timers.Timer; + +/** + * Packages methods related to sample handling.
+ * A SamplePackage contains all elements associated to a Sampler: + *

    + *
  • SampleListener(s)
  • + *
  • Timer(s)
  • + *
  • Assertion(s)
  • + *
  • PreProcessor(s)
  • + *
  • PostProcessor(s)
  • + *
  • ConfigTestElement(s)
  • + *
  • Controller(s)
  • + *
+ */ +public class SamplePackage { + + private final List sampleListeners; + + private final List timers; + + private final List assertions; + + private final List postProcessors; + + private final List preProcessors; + + private final List configs; + + private final List controllers; + + private Sampler sampler; + + public SamplePackage( + List configs, + List listeners, + List timers, + List assertions, + List postProcessors, + List preProcessors, + List controllers) { + this.configs = configs; + this.sampleListeners = listeners; + this.timers = timers; + this.assertions = assertions; + this.postProcessors = postProcessors; + this.preProcessors = preProcessors; + this.controllers = controllers; + } + + /** + * Make the SamplePackage the running version, or make it no longer the + * running version. This tells to each element of the SamplePackage that it's current state must + * be retrievable by a call to recoverRunningVersion(). + * @param running boolean + * @see TestElement#setRunningVersion(boolean) + */ + public void setRunningVersion(boolean running) { + setRunningVersion(configs, running); + setRunningVersion(sampleListeners, running); + setRunningVersion(assertions, running); + setRunningVersion(timers, running); + setRunningVersion(postProcessors, running); + setRunningVersion(preProcessors, running); + setRunningVersion(controllers, running); + sampler.setRunningVersion(running); + } + + private void setRunningVersion(List list, boolean running) { + @SuppressWarnings("unchecked") // all implementations extend TestElement + List telist = (List)list; + for (TestElement te : telist) { + te.setRunningVersion(running); + } + } + + private void recoverRunningVersion(List list) { + @SuppressWarnings("unchecked") // All implementations extend TestElement + List telist = (List)list; + for (TestElement te : telist) { + te.recoverRunningVersion(); + } + } + + /** + * Recover each member of SamplePackage to the state before the call of setRunningVersion(true) + * @see TestElement#recoverRunningVersion() + */ + public void recoverRunningVersion() { + recoverRunningVersion(configs); + recoverRunningVersion(sampleListeners); + recoverRunningVersion(assertions); + recoverRunningVersion(timers); + recoverRunningVersion(postProcessors); + recoverRunningVersion(preProcessors); + recoverRunningVersion(controllers); + sampler.recoverRunningVersion(); + } + + /** + * @return List + */ + public List getSampleListeners() { + return sampleListeners; + } + + /** + * Add Sample Listener + * @param listener {@link SampleListener} + */ + public void addSampleListener(SampleListener listener) { + sampleListeners.add(listener); + } + + /** + * @return List + */ + public List getTimers() { + return timers; + } + + + /** + * Add Post processor + * @param ex {@link PostProcessor} + */ + public void addPostProcessor(PostProcessor ex) { + postProcessors.add(ex); + } + + /** + * Add Pre processor + * @param pre {@link PreProcessor} + */ + public void addPreProcessor(PreProcessor pre) { + preProcessors.add(pre); + } + + /** + * Add Timer + * @param timer {@link Timer} + */ + public void addTimer(Timer timer) { + timers.add(timer); + } + + /** + * Add Assertion + * @param asser {@link Assertion} + */ + public void addAssertion(Assertion asser) { + assertions.add(asser); + } + + /** + * @return List + */ + public List getAssertions() { + return assertions; + } + + /** + * @return List + */ + public List getPostProcessors() { + return postProcessors; + } + + /** + * @return {@link Sampler} + */ + public Sampler getSampler() { + return sampler; + } + + /** + * @param s {@link Sampler} + */ + public void setSampler(Sampler s) { + sampler = s; + } + + /** + * Returns the preProcessors. + * @return List + */ + public List getPreProcessors() { + return preProcessors; + } + + /** + * Returns the configs. + * + * @return List + */ + public List getConfigs() { + return configs; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/SetupThreadGroup.java b/ApacheJmeter/src/org/apache/jmeter/threads/SetupThreadGroup.java new file mode 100644 index 0000000..330d1c1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/SetupThreadGroup.java @@ -0,0 +1,33 @@ +/* + * 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.threads; + + +/** + * SetupThreadGroup.java is a special type of ThreadGroup that can be used for + * setting up of a test before the bulk of the test executes later. + * + */ +public class SetupThreadGroup extends ThreadGroup { + private static final long serialVersionUID = 240L; + + public SetupThreadGroup() { + super(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/TestCompiler.java b/ApacheJmeter/src/org/apache/jmeter/threads/TestCompiler.java new file mode 100644 index 0000000..935899b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/TestCompiler.java @@ -0,0 +1,303 @@ +/* + * 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.threads; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import org.apache.jmeter.assertions.Assertion; +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.control.Controller; +import org.apache.jmeter.control.TransactionController; +import org.apache.jmeter.control.TransactionSampler; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.engine.util.ConfigMergabilityIndicator; +import org.apache.jmeter.engine.util.NoConfigMerge; +import org.apache.jmeter.processor.PostProcessor; +import org.apache.jmeter.processor.PreProcessor; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testbeans.TestBeanHelper; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.timers.Timer; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * HashTreeTraverser implementation that traverses the Test Tree to build: + *
    + *
  • A map with key Sampler and as value the associated SamplePackage
  • + *
  • A map with key TransactionController and as value the associated SamplePackage
  • + *
+ */ +public class TestCompiler implements HashTreeTraverser { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final LinkedList stack = new LinkedList(); + + private final Map samplerConfigMap = new HashMap(); + + private final Map transactionControllerConfigMap = + new HashMap(); + + private final HashTree testTree; + + /* + * This set keeps track of which ObjectPairs have been seen. + * Its purpose is not entirely clear (please document if you know!) but it is needed,.. + */ + private static final Set pairing = new HashSet(); + + //List loopIterListeners = new ArrayList(); + + public TestCompiler(HashTree testTree, JMeterVariables vars) { + this.testTree = testTree; + } + + /** + * Clears the pairing Set Called by StandardJmeterEngine at the start of a + * test run. + */ + public static void initialize() { + // synch is probably not needed as only called before run starts + synchronized (pairing) { + pairing.clear(); + } + } + + /** + * Configures sampler from SamplePackage extracted from Test plan and returns it + * @param sampler {@link Sampler} + * @return {@link SamplePackage} + */ + public SamplePackage configureSampler(Sampler sampler) { + SamplePackage pack = samplerConfigMap.get(sampler); + pack.setSampler(sampler); + configureWithConfigElements(sampler, pack.getConfigs()); + return pack; + } + + /** + * Configures Transaction Sampler from SamplePackage extracted from Test plan and returns it + * @param transactionSampler {@link TransactionSampler} + * @return {@link SamplePackage} + */ + public SamplePackage configureTransactionSampler(TransactionSampler transactionSampler) { + TransactionController controller = transactionSampler.getTransactionController(); + SamplePackage pack = transactionControllerConfigMap.get(controller); + pack.setSampler(transactionSampler); + return pack; + } + + /** + * Reset pack to it's initial state + * @param pack + */ + public void done(SamplePackage pack) { + pack.recoverRunningVersion(); + } + + /** {@inheritDoc} */ + public void addNode(Object node, HashTree subTree) { + stack.addLast((TestElement) node); + } + + /** {@inheritDoc} */ + public void subtractNode() { + log.debug("Subtracting node, stack size = " + stack.size()); + TestElement child = stack.getLast(); + trackIterationListeners(stack); + if (child instanceof Sampler) { + saveSamplerConfigs((Sampler) child); + } + else if(child instanceof TransactionController) { + saveTransactionControllerConfigs((TransactionController) child); + } + stack.removeLast(); + if (stack.size() > 0) { + ObjectPair pair = new ObjectPair(child, stack.getLast()); + synchronized (pairing) {// Called from multiple threads + if (!pairing.contains(pair)) { + pair.addTestElements(); + pairing.add(pair); + } + } + } + } + + @SuppressWarnings("deprecation") // TestBeanHelper.prepare() is OK + private void trackIterationListeners(LinkedList p_stack) { + TestElement child = p_stack.getLast(); + if (child instanceof LoopIterationListener) { + ListIterator iter = p_stack.listIterator(p_stack.size()); + while (iter.hasPrevious()) { + TestElement item = iter.previous(); + if (item == child) { + continue; + } + if (item instanceof Controller) { + TestBeanHelper.prepare(child); + ((Controller) item).addIterationListener((LoopIterationListener) child); + break; + } + } + } + } + + /** {@inheritDoc} */ + public void processPath() { + } + + private void saveSamplerConfigs(Sampler sam) { + List configs = new LinkedList(); + List controllers = new LinkedList(); + List listeners = new LinkedList(); + List timers = new LinkedList(); + List assertions = new LinkedList(); + LinkedList posts = new LinkedList(); + LinkedList pres = new LinkedList(); + for (int i = stack.size(); i > 0; i--) { + addDirectParentControllers(controllers, stack.get(i - 1)); + List tempPre = new LinkedList (); + List tempPost = new LinkedList(); + for (Object item : testTree.list(stack.subList(0, i))) { + if ((item instanceof ConfigTestElement)) { + configs.add((ConfigTestElement) item); + } + if (item instanceof SampleListener) { + listeners.add((SampleListener) item); + } + if (item instanceof Timer) { + timers.add((Timer) item); + } + if (item instanceof Assertion) { + assertions.add((Assertion) item); + } + if (item instanceof PostProcessor) { + tempPost.add((PostProcessor) item); + } + if (item instanceof PreProcessor) { + tempPre.add((PreProcessor) item); + } + } + pres.addAll(0, tempPre); + posts.addAll(0, tempPost); + } + + SamplePackage pack = new SamplePackage(configs, listeners, timers, assertions, + posts, pres, controllers); + pack.setSampler(sam); + pack.setRunningVersion(true); + samplerConfigMap.put(sam, pack); + } + + private void saveTransactionControllerConfigs(TransactionController tc) { + List configs = new LinkedList(); + List controllers = new LinkedList(); + List listeners = new LinkedList(); + List timers = new LinkedList(); + List assertions = new LinkedList(); + LinkedList posts = new LinkedList(); + LinkedList pres = new LinkedList(); + for (int i = stack.size(); i > 0; i--) { + addDirectParentControllers(controllers, stack.get(i - 1)); + for (Object item : testTree.list(stack.subList(0, i))) { + if (item instanceof SampleListener) { + listeners.add((SampleListener) item); + } + if (item instanceof Assertion) { + assertions.add((Assertion) item); + } + } + } + + SamplePackage pack = new SamplePackage(configs, listeners, timers, assertions, + posts, pres, controllers); + pack.setSampler(new TransactionSampler(tc, tc.getName())); + pack.setRunningVersion(true); + transactionControllerConfigMap.put(tc, pack); + } + + /** + * @param controllers + * @param i + */ + private void addDirectParentControllers(List controllers, TestElement maybeController) { + if (maybeController instanceof Controller) { + log.debug("adding controller: " + maybeController + " to sampler config"); + controllers.add((Controller) maybeController); + } + } + + private static class ObjectPair + { + private final TestElement child; + private final TestElement parent; + + public ObjectPair(TestElement one, TestElement two) { + this.child = one; + this.parent = two; + } + + public void addTestElements() { + if (parent instanceof Controller && (child instanceof Sampler || child instanceof Controller)) { + parent.addTestElement(child); + } + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return child.hashCode() + parent.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + if (o instanceof ObjectPair) { + return child == ((ObjectPair) o).child && parent == ((ObjectPair) o).parent; + } + return false; + } + } + + private void configureWithConfigElements(Sampler sam, List configs) { + sam.clearTestElementChildren(); + for (ConfigTestElement config : configs) { + if (!(config instanceof NoConfigMerge)) + { + if(sam instanceof ConfigMergabilityIndicator) { + if(((ConfigMergabilityIndicator)sam).applies(config)) { + sam.addTestElement(config); + } + } else { + // Backward compatibility + sam.addTestElement(config); + } + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/ThreadGroup.java b/ApacheJmeter/src/org/apache/jmeter/threads/ThreadGroup.java new file mode 100644 index 0000000..001aa97 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/ThreadGroup.java @@ -0,0 +1,213 @@ +/* + * 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.threads; + +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.IntegerProperty; +import org.apache.jmeter.testelement.property.LongProperty; + +/** + * ThreadGroup holds the settings for a JMeter thread group. + * + * This class is intended to be ThreadSafe. + */ +public class ThreadGroup extends AbstractThreadGroup { + private static final long serialVersionUID = 240L; + + /** Ramp-up time */ + public final static String RAMP_TIME = "ThreadGroup.ramp_time"; + + /** Whether scheduler is being used */ + public final static String SCHEDULER = "ThreadGroup.scheduler"; + + /** Scheduler absolute start time */ + public final static String START_TIME = "ThreadGroup.start_time"; + + /** Scheduler absolute end time */ + public final static String END_TIME = "ThreadGroup.end_time"; + + /** Scheduler duration, overrides end time */ + public final static String DURATION = "ThreadGroup.duration"; + + /** Scheduler start delay, overrides start time */ + public final static String DELAY = "ThreadGroup.delay"; + + /** + * No-arg constructor. + */ + public ThreadGroup() { + } + + + /** + * Set whether scheduler is being used + * + * @param Scheduler true is scheduler is to be used + */ + public void setScheduler(boolean Scheduler) { + setProperty(new BooleanProperty(SCHEDULER, Scheduler)); + } + + /** + * Get whether scheduler is being used + * + * @return true if scheduler is being used + */ + public boolean getScheduler() { + return getPropertyAsBoolean(SCHEDULER); + } + + /** + * Set the absolute StartTime value. + * + * @param stime - + * the StartTime value. + */ + public void setStartTime(long stime) { + setProperty(new LongProperty(START_TIME, stime)); + } + + /** + * Get the absolute start time value. + * + * @return the start time value. + */ + public long getStartTime() { + return getPropertyAsLong(START_TIME); + } + + /** + * Get the desired duration of the thread group test run + * + * @return the duration (in secs) + */ + public long getDuration() { + return getPropertyAsLong(DURATION); + } + + /** + * Set the desired duration of the thread group test run + * + * @param duration + * in seconds + */ + public void setDuration(long duration) { + setProperty(new LongProperty(DURATION, duration)); + } + + /** + * Get the startup delay + * + * @return the delay (in secs) + */ + public long getDelay() { + return getPropertyAsLong(DELAY); + } + + /** + * Set the startup delay + * + * @param delay + * in seconds + */ + public void setDelay(long delay) { + setProperty(new LongProperty(DELAY, delay)); + } + + /** + * Set the EndTime value. + * + * @param etime - + * the EndTime value. + */ + public void setEndTime(long etime) { + setProperty(new LongProperty(END_TIME, etime)); + } + + /** + * Get the end time value. + * + * @return the end time value. + */ + public long getEndTime() { + return getPropertyAsLong(END_TIME); + } + + /** + * Set the ramp-up value. + * + * @param rampUp + * the ramp-up value. + */ + public void setRampUp(int rampUp) { + setProperty(new IntegerProperty(RAMP_TIME, rampUp)); + } + + /** + * Get the ramp-up value. + * + * @return the ramp-up value. + */ + public int getRampUp() { + return getPropertyAsInt(ThreadGroup.RAMP_TIME); + } + + @Override + public void scheduleThread(JMeterThread thread) + { + int rampUp = getRampUp(); + float perThreadDelay = ((float) (rampUp * 1000) / (float) getNumThreads()); + thread.setInitialDelay((int) (perThreadDelay * thread.getThreadNum())); + + scheduleThread(thread, this); + } + + /** + * This will schedule the time for the JMeterThread. + * + * @param thread + * @param group + */ + private void scheduleThread(JMeterThread thread, ThreadGroup group) { + // if true the Scheduler is enabled + if (group.getScheduler()) { + long now = System.currentTimeMillis(); + // set the start time for the Thread + if (group.getDelay() > 0) {// Duration is in seconds + thread.setStartTime(group.getDelay() * 1000 + now); + } else { + long start = group.getStartTime(); + if (start < now) { + start = now; // Force a sensible start time + } + thread.setStartTime(start); + } + + // set the endtime for the Thread + if (group.getDuration() > 0) {// Duration is in seconds + thread.setEndTime(group.getDuration() * 1000 + (thread.getStartTime())); + } else { + thread.setEndTime(group.getEndTime()); + } + + // Enables the scheduler + thread.setScheduled(true); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java b/ApacheJmeter/src/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java new file mode 100644 index 0000000..b773d44 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java @@ -0,0 +1,182 @@ +/* + * 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.threads.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.util.Arrays; +import java.util.Collection; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JPanel; + +import javax.swing.JPopupMenu; +import javax.swing.JRadioButton; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jmeter.util.JMeterUtils; + +public abstract class AbstractThreadGroupGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + + // Sampler error action buttons + private JRadioButton continueBox; + + private JRadioButton startNextLoop; + + private JRadioButton stopThrdBox; + + private JRadioButton stopTestBox; + + private JRadioButton stopTestNowBox; + + public AbstractThreadGroupGui(){ + super(); + init(); + initGui(); + } + + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.THREADS }); + } + + public JPopupMenu createPopupMenu() { + JPopupMenu pop = new JPopupMenu(); + pop.add(MenuFactory.makeMenus(new String[] { + MenuFactory.CONTROLLERS, + MenuFactory.CONFIG_ELEMENTS, + MenuFactory.TIMERS, + MenuFactory.PRE_PROCESSORS, + MenuFactory.SAMPLERS, + MenuFactory.POST_PROCESSORS, + MenuFactory.ASSERTIONS, + MenuFactory.LISTENERS, + }, + JMeterUtils.getResString("add"), // $NON-NLS-1$ + ActionNames.ADD)); + MenuFactory.addEditMenu(pop, true); + MenuFactory.addFileMenu(pop); + return pop; + } + + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + + @Override + public void clearGui(){ + super.clearGui(); + initGui(); + } + + private void init() { + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + + Box box = Box.createVerticalBox(); + box.add(makeTitlePanel()); + box.add(createOnErrorPanel()); + add(box, BorderLayout.NORTH); + } + + private void initGui() { + continueBox.setSelected(true); + } + + private JPanel createOnErrorPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("sampler_on_error_action"))); // $NON-NLS-1$ + + ButtonGroup group = new ButtonGroup(); + + continueBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_continue")); // $NON-NLS-1$ + group.add(continueBox); + panel.add(continueBox); + + startNextLoop = new JRadioButton(JMeterUtils.getResString("sampler_on_error_start_next_loop")); // $NON-NLS-1$ + group.add(startNextLoop); + panel.add(startNextLoop); + + stopThrdBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_thread")); // $NON-NLS-1$ + group.add(stopThrdBox); + panel.add(stopThrdBox); + + stopTestBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test")); // $NON-NLS-1$ + group.add(stopTestBox); + panel.add(stopTestBox); + + stopTestNowBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test_now")); // $NON-NLS-1$ + group.add(stopTestNowBox); + panel.add(stopTestNowBox); + + return panel; + } + + private void setSampleErrorBoxes(AbstractThreadGroup te) { + if (te.getOnErrorStopTest()) { + stopTestBox.setSelected(true); + } else if (te.getOnErrorStopTestNow()) { + stopTestNowBox.setSelected(true); + } else if (te.getOnErrorStopThread()) { + stopThrdBox.setSelected(true); + } else if (te.getOnErrorStartNextLoop()) { + startNextLoop.setSelected(true); + } else { + continueBox.setSelected(true); + } + } + + private String onSampleError() { + if (stopTestBox.isSelected()) { + return AbstractThreadGroup.ON_SAMPLE_ERROR_STOPTEST; + } + if (stopTestNowBox.isSelected()) { + return AbstractThreadGroup.ON_SAMPLE_ERROR_STOPTEST_NOW; + } + if (stopThrdBox.isSelected()) { + return AbstractThreadGroup.ON_SAMPLE_ERROR_STOPTHREAD; + } + if (startNextLoop.isSelected()) { + return AbstractThreadGroup.ON_SAMPLE_ERROR_START_NEXT_LOOP; + } + + // Defaults to continue + return AbstractThreadGroup.ON_SAMPLE_ERROR_CONTINUE; + } + + @Override + public void configure(TestElement tg) { + super.configure(tg); + setSampleErrorBoxes((AbstractThreadGroup) tg); + } + + @Override + protected void configureTestElement(TestElement tg) { + super.configureTestElement(tg); + tg.setProperty(new StringProperty(AbstractThreadGroup.ON_SAMPLE_ERROR, onSampleError())); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/gui/PostThreadGroupGui.java b/ApacheJmeter/src/org/apache/jmeter/threads/gui/PostThreadGroupGui.java new file mode 100644 index 0000000..4d79fdb --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/gui/PostThreadGroupGui.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.threads.gui; + +import java.awt.event.ItemListener; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.threads.PostThreadGroup; + +public class PostThreadGroupGui extends ThreadGroupGui implements ItemListener { + private static final long serialVersionUID = 240L; + + @Override + public String getLabelResource() { + return "post_thread_group_title"; // $NON-NLS-1$ + + } + + @Override + public TestElement createTestElement() { + PostThreadGroup tg = new PostThreadGroup(); + modifyTestElement(tg); + return tg; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/gui/SetupThreadGroupGui.java b/ApacheJmeter/src/org/apache/jmeter/threads/gui/SetupThreadGroupGui.java new file mode 100644 index 0000000..d444732 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/gui/SetupThreadGroupGui.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.threads.gui; + +import java.awt.event.ItemListener; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.threads.SetupThreadGroup; + +public class SetupThreadGroupGui extends ThreadGroupGui implements ItemListener { + private static final long serialVersionUID = 240L; + + @Override + public String getLabelResource() { + return "setup_thread_group_title"; // $NON-NLS-1$ + + } + + @Override + public TestElement createTestElement() { + SetupThreadGroup tg = new SetupThreadGroup(); + modifyTestElement(tg); + return tg; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/threads/gui/ThreadGroupGui.java b/ApacheJmeter/src/org/apache/jmeter/threads/gui/ThreadGroupGui.java new file mode 100644 index 0000000..1f90f6d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/threads/gui/ThreadGroupGui.java @@ -0,0 +1,277 @@ +/* + * 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.threads.gui; + +import java.awt.BorderLayout; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.Date; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.control.LoopController; +import org.apache.jmeter.control.gui.LoopControlPanel; +import org.apache.jmeter.gui.util.JDateField; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.LongProperty; +import org.apache.jmeter.threads.ThreadGroup; +import org.apache.jmeter.util.JMeterUtils; + +public class ThreadGroupGui extends AbstractThreadGroupGui implements ItemListener { + private static final long serialVersionUID = 240L; + + private LoopControlPanel loopPanel; + + private VerticalPanel mainPanel; + + private final static String THREAD_NAME = "Thread Field"; + + private final static String RAMP_NAME = "Ramp Up Field"; + + private JTextField threadInput; + + private JTextField rampInput; + + private JDateField start; + + private JDateField end; + + private JCheckBox scheduler; + + private JTextField duration; + + private JTextField delay; // Relative start-up time + + public ThreadGroupGui() { + super(); + init(); + initGui(); + } + + public TestElement createTestElement() { + ThreadGroup tg = new ThreadGroup(); + modifyTestElement(tg); + return tg; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement tg) { + super.configureTestElement(tg); + if (tg instanceof ThreadGroup) { + ((ThreadGroup) tg).setSamplerController((LoopController) loopPanel.createTestElement()); + } + + tg.setProperty(ThreadGroup.NUM_THREADS, threadInput.getText()); + tg.setProperty(ThreadGroup.RAMP_TIME, rampInput.getText()); + tg.setProperty(new LongProperty(ThreadGroup.START_TIME, start.getDate().getTime())); + tg.setProperty(new LongProperty(ThreadGroup.END_TIME, end.getDate().getTime())); + tg.setProperty(new BooleanProperty(ThreadGroup.SCHEDULER, scheduler.isSelected())); + tg.setProperty(ThreadGroup.DURATION, duration.getText()); + tg.setProperty(ThreadGroup.DELAY, delay.getText()); + } + + @Override + public void configure(TestElement tg) { + super.configure(tg); + threadInput.setText(tg.getPropertyAsString(ThreadGroup.NUM_THREADS)); + rampInput.setText(tg.getPropertyAsString(ThreadGroup.RAMP_TIME)); + loopPanel.configure((TestElement) tg.getProperty(ThreadGroup.MAIN_CONTROLLER).getObjectValue()); + scheduler.setSelected(tg.getPropertyAsBoolean(ThreadGroup.SCHEDULER)); + + if (scheduler.isSelected()) { + mainPanel.setVisible(true); + } else { + mainPanel.setVisible(false); + } + + // Check if the property exists + String s = tg.getPropertyAsString(ThreadGroup.START_TIME); + if (s.length() == 0) {// Must be an old test plan + start.setDate(new Date()); + end.setDate(new Date()); + } else { + start.setDate(new Date(tg.getPropertyAsLong(ThreadGroup.START_TIME))); + end.setDate(new Date(tg.getPropertyAsLong(ThreadGroup.END_TIME))); + } + duration.setText(tg.getPropertyAsString(ThreadGroup.DURATION)); + delay.setText(tg.getPropertyAsString(ThreadGroup.DELAY)); + } + + public void itemStateChanged(ItemEvent ie) { + if (ie.getItem().equals(scheduler)) { + if (scheduler.isSelected()) { + mainPanel.setVisible(true); + } else { + mainPanel.setVisible(false); + } + } + } + + private JPanel createControllerPanel() { + loopPanel = new LoopControlPanel(false); + LoopController looper = (LoopController) loopPanel.createTestElement(); + looper.setLoops(1); + loopPanel.configure(looper); + return loopPanel; + } + + /** + * Create a panel containing the StartTime field and corresponding label. + * + * @return a GUI panel containing the StartTime field + */ + private JPanel createStartTimePanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("starttime")); //$NON-NLS-1$ + panel.add(label, BorderLayout.WEST); + start = new JDateField(); + panel.add(start, BorderLayout.CENTER); + return panel; + } + + /** + * Create a panel containing the EndTime field and corresponding label. + * + * @return a GUI panel containing the EndTime field + */ + private JPanel createEndTimePanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("endtime")); // $NON-NLS-1$ + panel.add(label, BorderLayout.WEST); + + end = new JDateField(); + panel.add(end, BorderLayout.CENTER); + return panel; + } + + /** + * Create a panel containing the Duration field and corresponding label. + * + * @return a GUI panel containing the Duration field + */ + private JPanel createDurationPanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("duration")); // $NON-NLS-1$ + panel.add(label, BorderLayout.WEST); + duration = new JTextField(); + panel.add(duration, BorderLayout.CENTER); + return panel; + } + + /** + * Create a panel containing the Duration field and corresponding label. + * + * @return a GUI panel containing the Duration field + */ + private JPanel createDelayPanel() { + JPanel panel = new JPanel(new BorderLayout(5, 0)); + JLabel label = new JLabel(JMeterUtils.getResString("delay")); // $NON-NLS-1$ + panel.add(label, BorderLayout.WEST); + delay = new JTextField(); + panel.add(delay, BorderLayout.CENTER); + return panel; + } + + public String getLabelResource() { + return "threadgroup"; // $NON-NLS-1$ + } + + @Override + public void clearGui(){ + super.clearGui(); + initGui(); + } + + // Initialise the gui field values + private void initGui(){ + threadInput.setText("1"); // $NON-NLS-1$ + rampInput.setText("1"); // $NON-NLS-1$ + loopPanel.clearGui(); + scheduler.setSelected(false); + Date today = new Date(); + end.setDate(today); + start.setDate(today); + delay.setText(""); // $NON-NLS-1$ + duration.setText(""); // $NON-NLS-1$ + } + + private void init() { + // THREAD PROPERTIES + VerticalPanel threadPropsPanel = new VerticalPanel(); + threadPropsPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("thread_properties"))); // $NON-NLS-1$ + + // NUMBER OF THREADS + JPanel threadPanel = new JPanel(new BorderLayout(5, 0)); + + JLabel threadLabel = new JLabel(JMeterUtils.getResString("number_of_threads")); // $NON-NLS-1$ + threadPanel.add(threadLabel, BorderLayout.WEST); + + threadInput = new JTextField(5); + threadInput.setName(THREAD_NAME); + threadLabel.setLabelFor(threadInput); + threadPanel.add(threadInput, BorderLayout.CENTER); + + threadPropsPanel.add(threadPanel); + + // RAMP-UP + JPanel rampPanel = new JPanel(new BorderLayout(5, 0)); + JLabel rampLabel = new JLabel(JMeterUtils.getResString("ramp_up")); // $NON-NLS-1$ + rampPanel.add(rampLabel, BorderLayout.WEST); + + rampInput = new JTextField(5); + rampInput.setName(RAMP_NAME); + rampLabel.setLabelFor(rampInput); + rampPanel.add(rampInput, BorderLayout.CENTER); + + threadPropsPanel.add(rampPanel); + + // LOOP COUNT + threadPropsPanel.add(createControllerPanel()); + + // mainPanel.add(threadPropsPanel, BorderLayout.NORTH); + // add(mainPanel, BorderLayout.CENTER); + + scheduler = new JCheckBox(JMeterUtils.getResString("scheduler")); // $NON-NLS-1$ + scheduler.addItemListener(this); + threadPropsPanel.add(scheduler); + mainPanel = new VerticalPanel(); + mainPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("scheduler_configuration"))); // $NON-NLS-1$ + mainPanel.add(createStartTimePanel()); + mainPanel.add(createEndTimePanel()); + mainPanel.add(createDurationPanel()); + mainPanel.add(createDelayPanel()); + mainPanel.setVisible(false); + VerticalPanel intgrationPanel = new VerticalPanel(); + intgrationPanel.add(threadPropsPanel); + intgrationPanel.add(mainPanel); + add(intgrationPanel, BorderLayout.CENTER); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/BSFTimer.java b/ApacheJmeter/src/org/apache/jmeter/timers/BSFTimer.java new file mode 100644 index 0000000..080aa79 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/BSFTimer.java @@ -0,0 +1,56 @@ +/* + * 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.timers; + +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class BSFTimer extends BSFTestElement implements Cloneable, Timer, TestBean { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + /** {@inheritDoc} */ + public long delay() { + long delay = 0; + BSFManager mgr = null; + try { + mgr = getManager(); + Object o = evalFileOrScript(mgr); + if (o == null) { + log.warn("Script did not return a value"); + return 0; + } + delay = Long.valueOf(o.toString()).longValue(); + } catch (NumberFormatException e) { + log.warn("Problem in BSF script "+e); + } catch (BSFException e) { + log.warn("Problem in BSF script "+e); + } finally { + if(mgr != null) { + mgr.terminate(); + } + } + return delay; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/BSFTimerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/timers/BSFTimerBeanInfo.java new file mode 100644 index 0000000..b7c8a27 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/BSFTimerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.timers; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +public class BSFTimerBeanInfo extends BSFBeanInfoSupport { + + public BSFTimerBeanInfo() { + super(BSFTimer.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/BeanShellTimer.java b/ApacheJmeter/src/org/apache/jmeter/timers/BeanShellTimer.java new file mode 100644 index 0000000..6d3d81a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/BeanShellTimer.java @@ -0,0 +1,64 @@ +/* + * 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.timers; + +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +public class BeanShellTimer extends BeanShellTestElement implements Cloneable, Timer, TestBean { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + // can be specified in jmeter.properties + private static final String INIT_FILE = "beanshell.timer.init"; //$NON-NLS-1$ + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + /** + * {@inheritDoc} + */ + public long delay() { + String ret="0"; + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + log.error("BeanShell not found"); + return 0; + } + try { + Object o = processFileOrScript(bshInterpreter); + if (o != null) { ret=o.toString(); } + } catch (JMeterException e) { + log.warn("Problem in BeanShell script "+e); + } + try { + return Long.decode(ret).longValue(); + } catch (NumberFormatException e){ + log.warn(e.getLocalizedMessage()); + return 0; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/BeanShellTimerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/timers/BeanShellTimerBeanInfo.java new file mode 100644 index 0000000..ee6c690 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/BeanShellTimerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.timers; + +import org.apache.jmeter.util.BeanShellBeanInfoSupport; + +public class BeanShellTimerBeanInfo extends BeanShellBeanInfoSupport { + + public BeanShellTimerBeanInfo() { + super(BeanShellTimer.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/ConstantThroughputTimer.java b/ApacheJmeter/src/org/apache/jmeter/timers/ConstantThroughputTimer.java new file mode 100644 index 0000000..41321f4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/ConstantThroughputTimer.java @@ -0,0 +1,260 @@ +/* + * 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.timers; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.AbstractThreadGroup; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This class implements a constant throughput timer. A Constant Throughtput + * Timer paces the samplers under its influence so that the total number of + * samples per unit of time approaches a given constant as much as possible. + * + * There are two different ways of pacing the requests: + * - delay each thread according to when it last ran + * - delay each thread according to when any thread last ran + */ +public class ConstantThroughputTimer extends AbstractTestElement implements Timer, TestListener, TestBean { + private static final long serialVersionUID = 3; + + private static class ThroughputInfo{ + final Object MUTEX = new Object(); + long lastScheduledTime = 0; + } + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final double MILLISEC_PER_MIN = 60000.0; + + /** + * Target time for the start of the next request. The delay provided by the + * timer will be calculated so that the next request happens at this time. + */ + private long previousTime = 0; + + private String calcMode; // String representing the mode + // (Locale-specific) + + private int modeInt; // mode as an integer + + /** + * Desired throughput, in samples per minute. + */ + private double throughput; + + //For calculating throughput across all threads + private final static ThroughputInfo allThreadsInfo = new ThroughputInfo(); + + //For holding the ThrougputInfo objects for all ThreadGroups. Keyed by AbstractThreadGroup objects + private final static ConcurrentMap threadGroupsInfoMap = + new ConcurrentHashMap(); + + + /** + * Constructor for a non-configured ConstantThroughputTimer. + */ + public ConstantThroughputTimer() { + } + + /** + * Sets the desired throughput. + * + * @param throughput + * Desired sampling rate, in samples per minute. + */ + public void setThroughput(double throughput) { + this.throughput = throughput; + } + + /** + * Gets the configured desired throughput. + * + * @return the rate at which samples should occur, in samples per minute. + */ + public double getThroughput() { + return throughput; + } + + public String getCalcMode() { + return calcMode; + } + + // Needed by test code + int getCalcModeInt() { + return modeInt; + } + + public void setCalcMode(String mode) { + this.calcMode = mode; + // TODO find better way to get modeInt + this.modeInt = ConstantThroughputTimerBeanInfo.getCalcModeAsInt(calcMode); + } + + /** + * Retrieve the delay to use during test execution. + * + * @see org.apache.jmeter.timers.Timer#delay() + */ + public long delay() { + long currentTime = System.currentTimeMillis(); + + /* + * If previous time is zero, then target will be in the past. + * This is what we want, so first sample is run without a delay. + */ + long currentTarget = previousTime + calculateDelay(); + if (currentTime > currentTarget) { + // We're behind schedule -- try to catch up: + previousTime = currentTime; + return 0; + } + previousTime = currentTarget; + return currentTarget - currentTime; + } + + /** + * @param currentTime + * @return new Target time + */ + // TODO - is this used? (apart from test code) + protected long calculateCurrentTarget(long currentTime) { + return currentTime + calculateDelay(); + } + + // Calculate the delay based on the mode + private long calculateDelay() { + long delay = 0; + // N.B. we fetch the throughput each time, as it may vary during a test + double msPerRequest = (MILLISEC_PER_MIN / getThroughput()); + switch (modeInt) { + case 1: // Total number of threads + delay = (long) (JMeterContextService.getNumberOfThreads() * msPerRequest); + break; + + case 2: // Active threads in this group + delay = (long) (JMeterContextService.getContext().getThreadGroup().getNumberOfThreads() * msPerRequest); + break; + + case 3: // All threads - alternate calculation + delay = calculateSharedDelay(allThreadsInfo,(long) msPerRequest); + break; + + case 4: //All threads in this group - alternate calculation + final org.apache.jmeter.threads.AbstractThreadGroup group = + JMeterContextService.getContext().getThreadGroup(); + ThroughputInfo groupInfo = threadGroupsInfoMap.get(group); + if (groupInfo == null) { + groupInfo = new ThroughputInfo(); + ThroughputInfo previous = threadGroupsInfoMap.putIfAbsent(group, groupInfo); + if (previous != null) { // We did not replace the entry + groupInfo = previous; // so use the existing one + } + } + delay = calculateSharedDelay(groupInfo,(long) msPerRequest); + break; + + default: // e.g. 0 + delay = (long) msPerRequest; // i.e. * 1 + break; + } + return delay; + } + + private long calculateSharedDelay(ThroughputInfo info, long milliSecPerRequest) { + final long now = System.currentTimeMillis(); + final long calculatedDelay; + + //Synchronize on the info object's MUTEX to ensure + //Multiple threads don't update the scheduled time simultaneously + synchronized (info.MUTEX) { + final long nextRequstTime = info.lastScheduledTime + milliSecPerRequest; + info.lastScheduledTime = Math.max(now, nextRequstTime); + calculatedDelay = info.lastScheduledTime - now; + } + + return Math.max(calculatedDelay, 0); + } + + private synchronized void reset() { + allThreadsInfo.lastScheduledTime = 0; + threadGroupsInfoMap.clear(); + previousTime = 0; + } + + /** + * Provide a description of this timer class. + * + * TODO: Is this ever used? I can't remember where. Remove if it isn't -- + * TODO: or obtain text from bean's displayName or shortDescription. + * + * @return the description of this timer class. + */ + @Override + public String toString() { + return JMeterUtils.getResString("constant_throughput_timer_memo"); //$NON-NLS-1$ + } + + /** + * Get the timer ready to compute delays for a new test. + *

+ * {@inheritDoc} + */ + public void testStarted() + { + log.debug("Test started - reset throughput calculation."); + reset(); + } + + /** + * {@inheritDoc} + */ + public void testEnded() { + //NOOP + } + + /** + * {@inheritDoc} + */ + public void testStarted(String host) { + testStarted(); + } + + /** + * {@inheritDoc} + */ + public void testEnded(String host) { + //NOOP + } + + /** + * {@inheritDoc} + */ + public void testIterationStart(LoopIterationEvent event) { + //NOOP + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/ConstantThroughputTimerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/timers/ConstantThroughputTimerBeanInfo.java new file mode 100644 index 0000000..a5e5ed4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/ConstantThroughputTimerBeanInfo.java @@ -0,0 +1,68 @@ +/* + * 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.timers; + +import java.beans.PropertyDescriptor; +import java.util.ResourceBundle; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +/** + * BeanInfo for the ConstantThroughputTimer. + * + */ +public class ConstantThroughputTimerBeanInfo extends BeanInfoSupport { + private static final String[] tags = new String[5]; + + public ConstantThroughputTimerBeanInfo() { + super(ConstantThroughputTimer.class); + + ResourceBundle rb = (ResourceBundle) getBeanDescriptor().getValue(RESOURCE_BUNDLE); +// These must agree with the Timer resources + tags[0] = rb.getString("calcMode.1"); //$NON-NLS-1$ + tags[1] = rb.getString("calcMode.2"); //$NON-NLS-1$ + tags[2] = rb.getString("calcMode.3"); //$NON-NLS-1$ + tags[3] = rb.getString("calcMode.4"); //$NON-NLS-1$ + tags[4] = rb.getString("calcMode.5"); //$NON-NLS-1$ + createPropertyGroup("delay", //$NON-NLS-1$ + new String[] { "throughput", //$NON-NLS-1$ + "calcMode" }); //$NON-NLS-1$ + + PropertyDescriptor p = property("throughput"); //$NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Double.valueOf(0.0)); + + p = property("calcMode"); //$NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, tags[0]); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(TAGS, tags); + } + + // TODO need to find better way to do this + public static int getCalcModeAsInt(String mode) { + for (int i = 0; i < tags.length; i++) { + if (tags[i].equals(mode)) { + return i; + } + } + return -1; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/ConstantTimer.java b/ApacheJmeter/src/org/apache/jmeter/timers/ConstantTimer.java new file mode 100644 index 0000000..1ef9ffc --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/ConstantTimer.java @@ -0,0 +1,109 @@ +/* + * 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.timers; + +import java.io.Serializable; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class implements a constant timer with its own panel and fields for + * value update and user interaction. + * + */ +public class ConstantTimer extends AbstractTestElement implements Timer, Serializable, LoopIterationListener { + + private static final long serialVersionUID = 240L; + + public final static String DELAY = "ConstantTimer.delay"; //$NON-NLS-1$ + + private long delay = 0; + + /** + * No-arg constructor. + */ + public ConstantTimer() { + } + + /** + * Set the delay for this timer. + * + */ + public void setDelay(String delay) { + setProperty(DELAY, delay); + } + + /** + * Set the range (not used for this timer). + * + */ + public void setRange(double range) { + // NOOP + } + + /** + * Get the delay value for display. + * + * @return the delay value for display. + */ + public String getDelay() { + return getPropertyAsString(DELAY); + } + + /** + * Retrieve the range (not used for this timer). + * + * @return the range (always zero for this timer). + */ + public double getRange() { + return 0; + } + + /** + * Retrieve the delay to use during test execution. + * + * @return the delay. + */ + public long delay() { + return delay; + } + + /** + * Provide a description of this timer class. + * + * @return the description of this timer class. + */ + @Override + public String toString() { + return JMeterUtils.getResString("constant_timer_memo"); //$NON-NLS-1$ + } + + /** + * Gain access to any variables that have been defined. + * + * @see LoopIterationListener#iterationStart(LoopIterationEvent) + */ + public void iterationStart(LoopIterationEvent event) { + delay = getPropertyAsLong(DELAY); + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/GaussianRandomTimer.java b/ApacheJmeter/src/org/apache/jmeter/timers/GaussianRandomTimer.java new file mode 100644 index 0000000..c867d54 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/GaussianRandomTimer.java @@ -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.timers; + +import java.io.Serializable; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class implements those methods needed by RandomTimer to be instantiable + * and implements a random delay with an average value and a gaussian + * distributed variation. + * + */ +public class GaussianRandomTimer extends RandomTimer implements Serializable { + private static final long serialVersionUID = 240L; + + @Override + public long delay() { + return (long) Math.abs((this.random.nextGaussian() * getRange()) + super.delay()); + } + + @Override + public String toString() { + return JMeterUtils.getResString("gaussian_timer_memo"); //$NON-NLS-1$ + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/JSR223Timer.java b/ApacheJmeter/src/org/apache/jmeter/timers/JSR223Timer.java new file mode 100644 index 0000000..d8b353c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/JSR223Timer.java @@ -0,0 +1,59 @@ +/* + * 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.timers; + +import java.io.IOException; + +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223Timer extends JSR223TestElement implements Cloneable, Timer, TestBean { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + /** {@inheritDoc} */ + public long delay() { + long delay = 0; + try { + ScriptEngineManager mgr = getManager(); + if (mgr == null) { + return 0; + } + Object o = processFileOrScript(mgr); + if (o == null) { + log.warn("Script did not return a value"); + return 0; + } + delay = Long.valueOf(o.toString()).longValue(); + } catch (NumberFormatException e) { + log.warn("Problem in JSR223 script "+e); + } catch (IOException e) { + log.warn("Problem in JSR223 script "+e); + } catch (ScriptException e) { + log.warn("Problem in JSR223 script "+e); + } + return delay; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/JSR223TimerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/timers/JSR223TimerBeanInfo.java new file mode 100644 index 0000000..824979b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/JSR223TimerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.timers; + +import org.apache.jmeter.util.JSR223BeanInfoSupport; + +public class JSR223TimerBeanInfo extends JSR223BeanInfoSupport { + + public JSR223TimerBeanInfo() { + super(JSR223Timer.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/PoissonRandomTimer.java b/ApacheJmeter/src/org/apache/jmeter/timers/PoissonRandomTimer.java new file mode 100644 index 0000000..7d34710 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/PoissonRandomTimer.java @@ -0,0 +1,390 @@ +/* + * 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.timers; + +import java.io.Serializable; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class implements those methods needed by RandomTimer to be instantiable + * and implements a random delay with an average value and a Poisson + * distributed variation. + * + */ +public class PoissonRandomTimer extends RandomTimer implements Serializable { + /** + * + */ + private static final long serialVersionUID = 3514708226113231004L; + /** + * + */ + private static final double[] LOG_FACTORIAL = + { + 0.000000000000000, + 0.000000000000000, + 0.693147180559945, + 1.791759469228055, + 3.178053830347946, + 4.787491742782046, + 6.579251212010101, + 8.525161361065415, + 10.604602902745251, + 12.801827480081469, + 15.104412573075516, + 17.502307845873887, + 19.987214495661885, + 22.552163853123421, + 25.191221182738683, + 27.899271383840894, + 30.671860106080675, + 33.505073450136891, + 36.395445208033053, + 39.339884187199495, + 42.335616460753485, + 45.380138898476908, + 48.471181351835227, + 51.606675567764377, + 54.784729398112319, + 58.003605222980518, + 61.261701761002001, + 64.557538627006323, + 67.889743137181526, + 71.257038967168000, + 74.658236348830158, + 78.092223553315307, + 81.557959456115029, + 85.054467017581516, + 88.580827542197682, + 92.136175603687079, + 95.719694542143202, + 99.330612454787428, + 102.968198614513810, + 106.631760260643450, + 110.320639714757390, + 114.034211781461690, + 117.771881399745060, + 121.533081515438640, + 125.317271149356880, + 129.123933639127240, + 132.952575035616290, + 136.802722637326350, + 140.673923648234250, + 144.565743946344900, + 148.477766951773020, + 152.409592584497350, + 156.360836303078800, + 160.331128216630930, + 164.320112263195170, + 168.327445448427650, + 172.352797139162820, + 176.395848406997370, + 180.456291417543780, + 184.533828861449510, + 188.628173423671600, + 192.739047287844900, + 196.866181672889980, + 201.009316399281570, + 205.168199482641200, + 209.342586752536820, + 213.532241494563270, + 217.736934113954250, + 221.956441819130360, + 226.190548323727570, + 230.439043565776930, + 234.701723442818260, + 238.978389561834350, + 243.268849002982730, + 247.572914096186910, + 251.890402209723190, + 256.221135550009480, + 260.564940971863220, + 264.921649798552780, + 269.291097651019810, + 273.673124285693690, + 278.067573440366120, + 282.474292687630400, + 286.893133295426990, + 291.323950094270290, + 295.766601350760600, + 300.220948647014100, + 304.686856765668720, + 309.164193580146900, + 313.652829949878990, + 318.152639620209300, + 322.663499126726210, + 327.185287703775200, + 331.717887196928470, + 336.261181979198450, + 340.815058870798960, + 345.379407062266860, + 349.954118040770250, + 354.539085519440790, + 359.134205369575340, + 363.739375555563470, + 368.354496072404690, + 372.979468885689020, + 377.614197873918670, + 382.258588773060010, + 386.912549123217560, + 391.575988217329610, + 396.248817051791490, + 400.930948278915760, + 405.622296161144900, + 410.322776526937280, + 415.032306728249580, + 419.750805599544780, + 424.478193418257090, + 429.214391866651570, + 433.959323995014870, + 438.712914186121170, + 443.475088120918940, + 448.245772745384610, + 453.024896238496130, + 457.812387981278110, + 462.608178526874890, + 467.412199571608080, + 472.224383926980520, + 477.044665492585580, + 481.872979229887900, + 486.709261136839360, + 491.553448223298010, + 496.405478487217580, + 501.265290891579240, + 506.132825342034830, + 511.008022665236070, + 515.890824587822520, + 520.781173716044240, + 525.679013515995050, + 530.584288294433580, + 535.496943180169520, + 540.416924105997740, + 545.344177791154950, + 550.278651724285620, + 555.220294146894960, + 560.169054037273100, + 565.124881094874350, + 570.087725725134190, + 575.057539024710200, + 580.034272767130800, + 585.017879388839220, + 590.008311975617860, + 595.005524249382010, + 600.009470555327430, + 605.020105849423770, + 610.037385686238740, + 615.061266207084940, + 620.091704128477430, + 625.128656730891070, + 630.172081847810200, + 635.221937855059760, + 640.278183660408100, + 645.340778693435030, + 650.409682895655240, + 655.484856710889060, + 660.566261075873510, + 665.653857411105950, + 670.747607611912710, + 675.847474039736880, + 680.953419513637530, + 686.065407301994010, + 691.183401114410800, + 696.307365093814040, + 701.437263808737160, + 706.573062245787470, + 711.714725802289990, + 716.862220279103440, + 722.015511873601330, + 727.174567172815840, + 732.339353146739310, + 737.509837141777440, + 742.685986874351220, + 747.867770424643370, + 753.055156230484160, + 758.248113081374300, + 763.446610112640200, + 768.650616799717000, + 773.860102952558460, + 779.075038710167410, + 784.295394535245690, + 789.521141208958970, + 794.752249825813460, + 799.988691788643450, + 805.230438803703120, + 810.477462875863580, + 815.729736303910160, + 820.987231675937890, + 826.249921864842800, + 831.517780023906310, + 836.790779582469900, + 842.068894241700490, + 847.352097970438420, + 852.640365001133090, + 857.933669825857460, + 863.231987192405430, + 868.535292100464630, + 873.843559797865740, + 879.156765776907600, + 884.474885770751830, + 889.797895749890240, + 895.125771918679900, + 900.458490711945270, + 905.796028791646340, + 911.138363043611210, + 916.485470574328820, + 921.837328707804890, + 927.193914982476710, + 932.555207148186240, + 937.921183163208070, + 943.291821191335660, + 948.667099599019820, + 954.046996952560450, + 959.431492015349480, + 964.820563745165940, + 970.214191291518320, + 975.612353993036210, + 981.015031374908400, + 986.422203146368590, + 991.833849198223450, + 997.249949600427840, + 1002.670484599700300, + 1008.095434617181700, + 1013.524780246136200, + 1018.958502249690200, + 1024.396581558613400, + 1029.838999269135500, + 1035.285736640801600, + 1040.736775094367400, + 1046.192096209724900, + 1051.651681723869200, + 1057.115513528895000, + 1062.583573670030100, + 1068.055844343701400, + 1073.532307895632800, + 1079.012946818975000, + 1084.497743752465600, + 1089.986681478622400, + 1095.479742921962700, + 1100.976911147256000, + 1106.478169357800900, + 1111.983500893733000, + 1117.492889230361000, + 1123.006317976526100, + 1128.523770872990800, + 1134.045231790853000, + 1139.570684729984800, + 1145.100113817496100, + 1150.633503306223700, + 1156.170837573242400, + }; + + + /** + * {@inheritDoc} + */ + @Override + public long delay() { + return Math.abs(randomPoisson((int)Math.round(getRange())) + super.delay()); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return JMeterUtils.getResString("poisson_timer_memo"); //$NON-NLS-1$ + } + + /** + * Generate Poisson random based using + * @param lambda Lambda in Poisson + * @return random + */ + private static int randomPoisson(int lambda) { + if(lambda <= 30) { + return poissonRandomNumberLowEq30(lambda); + } else { + return poissonRandomNumberSup30(lambda); + } + } + /** + * see http://en.wikipedia.org/wiki/Poisson_distribution + * @param lambda Lambda in Poisson + * @return random + */ + private static final int poissonRandomNumberLowEq30(int lambda) { + double L = Math.exp(-lambda); + int k = 0; + double p = 1; + do { + k = k + 1; + double u = Math.random(); + p = p * u; + } while (p > L); + return k - 1; + } + + /** + * http://www.johndcook.com/blog/2010/06/14/generating-poisson-random-values/ + * @param lambda Lambda in Poisson + * @return random + */ + private static final int poissonRandomNumberSup30(int lambda) { + double c = 0.767 - 3.36/lambda; + double beta = Math.PI/Math.sqrt(3.0*lambda); + double alpha = beta*lambda; + double k = Math.log(c) - lambda - Math.log(beta); + while(true) { + double u = Math.random(); + double x = (alpha - Math.log((1.0 - u)/u))/beta; + int n = (int)Math.floor(x + 0.5); + if (n < 0){ + continue; + } + double v = Math.random(); + double y = alpha - beta*x; + double lhs = y + Math.log(v/Math.pow((1.0 + Math.exp(y)),2)); + double rhs = k + n*Math.log(lambda) -logFactorial(n); + if (lhs <= rhs) { + return n; + } + } + } + + /** + * Compute log factorial + * http://www.johndcook.com/blog/2010/08/16/how-to-compute-log-factorial/ + * @param n Number for which we want log(n!) + * @return Log factorial + */ + private static final double logFactorial(int n) + { + if (n < 0) { + throw new IllegalArgumentException(); + } + else if (n > 254) { + double x = n + 1; + return (x - 0.5)*Math.log(x) - x + 0.5*Math.log(2*Math.PI) + 1.0/(12.0*x); + } + else { + return LOG_FACTORIAL[n]; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/RandomTimer.java b/ApacheJmeter/src/org/apache/jmeter/timers/RandomTimer.java new file mode 100644 index 0000000..4fd9d06 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/RandomTimer.java @@ -0,0 +1,69 @@ +/* + * 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.timers; + +import java.io.Serializable; +import java.util.Random; + +import org.apache.jmeter.testelement.property.DoubleProperty; +import org.apache.jmeter.testelement.property.StringProperty; + +/** + * This class implements a random timer with its own panel and fields for value + * update and user interaction. Since this class does not define the delay() + * method, is abstract and must be extended to provide full functionality. + * + */ +public abstract class RandomTimer extends ConstantTimer implements Timer, Serializable { + private static final long serialVersionUID = 240L; + + public final static String RANGE = "RandomTimer.range"; + + protected final Random random; + + /** + * No-arg constructor. + */ + public RandomTimer() { + this.random = new Random(); + } + + /** + * Set the range value. + */ + @Override + public void setRange(double range) { + setProperty(new DoubleProperty(RANGE, range)); + } + + public void setRange(String range) { + setProperty(new StringProperty(RANGE, range)); + } + + /** + * Get the range value. + * + * @return double + */ + @Override + public double getRange() { + return this.getPropertyAsDouble(RANGE); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/SyncTimer.java b/ApacheJmeter/src/org/apache/jmeter/timers/SyncTimer.java new file mode 100644 index 0000000..bb8771c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/SyncTimer.java @@ -0,0 +1,220 @@ +/* + * 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.timers; + +import java.io.Serializable; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.threads.JMeterContextService; + +/** + * The purpose of the SyncTimer is to block threads until X number of threads + * have been blocked, and then they are all released at once. A SyncTimer can + * thus create large instant loads at various points of the test plan. + * + */ +public class SyncTimer extends AbstractTestElement implements Timer, Serializable, TestBean, TestListener, ThreadListener { + + /** + * Wrapper to {@link CyclicBarrier} to allow lazy init of CyclicBarrier when SyncTimer is configured with 0 + */ + private static final class BarrierWrapper implements Cloneable { + + private CyclicBarrier barrier; + + /** + * + */ + public BarrierWrapper() { + this.barrier = null; + } + + /** + * @param parties Number of parties + */ + public BarrierWrapper(int parties) { + this.barrier = new CyclicBarrier(parties); + } + + /** + * Synchronized is required to ensure CyclicBarrier is initialized only once per Thread Group + * @param parties Number of parties + */ + public synchronized void setup(int parties) { + if(this.barrier== null) { + this.barrier = new CyclicBarrier(parties); + } + } + + /** + * @see CyclicBarrier#await() + * @return int + * @throws InterruptedException + * @throws BrokenBarrierException + * @see java.util.concurrent.CyclicBarrier#await() + */ + public int await() throws InterruptedException, BrokenBarrierException { + return barrier.await(); + } + + /** + * @see java.util.concurrent.CyclicBarrier#reset() + */ + public void reset() { + barrier.reset(); + } + + /** + * @see java.lang.Object#clone() + */ + @Override + protected Object clone() { + BarrierWrapper barrierWrapper= null; + try { + barrierWrapper = (BarrierWrapper) super.clone(); + barrierWrapper.barrier = this.barrier; + } catch (CloneNotSupportedException e) { + //Cannot happen + } + return barrierWrapper; + } + } + + private static final long serialVersionUID = 2; + + private transient BarrierWrapper barrier; + + private int groupSize; + + // Ensure transient object is created by the server + private Object readResolve(){ + createBarrier(); + return this; + } + + /** + * @return Returns the numThreads. + */ + public int getGroupSize() { + return groupSize; + } + + /** + * @param numThreads + * The numThreads to set. + */ + public void setGroupSize(int numThreads) { + this.groupSize = numThreads; + } + + /** + * {@inheritDoc} + */ + public long delay() { + if(getGroupSize()>=0) { + int arrival = 0; + try { + arrival = this.barrier.await(); + } catch (InterruptedException e) { + return 0; + } catch (BrokenBarrierException e) { + return 0; + } finally { + if(arrival == 0) { + barrier.reset(); + } + } + } + return 0; + } + + /** + * We have to control the cloning process because we need some cross-thread + * communication if our synctimers are to be able to determine when to block + * and when to release. + */ + @Override + public Object clone() { + SyncTimer newTimer = (SyncTimer) super.clone(); + newTimer.barrier = barrier; + return newTimer; + } + + /** + * {@inheritDoc} + */ + public void testEnded() { + this.testEnded(null); + } + + /** + * Reset timerCounter + */ + public void testEnded(String host) { + createBarrier(); + } + + /** + * {@inheritDoc} + */ + public void testStarted() { + testStarted(null); + } + + /** + * Reset timerCounter + */ + public void testStarted(String host) { + createBarrier(); + } + + public void testIterationStart(LoopIterationEvent event) { + // NOOP + } + + /** + * + */ + private void createBarrier() { + if(getGroupSize() == 0) { + // Lazy init + this.barrier = new BarrierWrapper(); + } else { + this.barrier = new BarrierWrapper(getGroupSize()); + } + } + + public void threadStarted() { + if(getGroupSize() == 0) { + int numThreadsInGroup = JMeterContextService.getContext().getThreadGroup().getNumThreads(); + // Unique Barrier creation ensured by synchronized setup + this.barrier.setup(numThreadsInGroup); + } + } + + public void threadFinished() { + // NOOP + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/SyncTimerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/timers/SyncTimerBeanInfo.java new file mode 100644 index 0000000..72ffa7e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/SyncTimerBeanInfo.java @@ -0,0 +1,37 @@ +/* + * 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.timers; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; + +public class SyncTimerBeanInfo extends BeanInfoSupport { + + public SyncTimerBeanInfo() { + super(SyncTimer.class); + + createPropertyGroup("grouping", new String[] { "groupSize" }); + + PropertyDescriptor p = property("groupSize"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Integer.valueOf(0)); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/Timer.java b/ApacheJmeter/src/org/apache/jmeter/timers/Timer.java new file mode 100644 index 0000000..afb3d40 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/Timer.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.jmeter.timers; + +import java.io.Serializable; + +/** + * This interface defines those methods that must be implemented by timer + * plugins. + * + */ +public interface Timer extends Serializable { + /** + * This method is called after a sampling process is done to know how much + * time the sampling thread has to wait until sampling again. + * + * @return the computed delay value. + */ + public long delay(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/UniformRandomTimer.java b/ApacheJmeter/src/org/apache/jmeter/timers/UniformRandomTimer.java new file mode 100644 index 0000000..4fbcfe6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/UniformRandomTimer.java @@ -0,0 +1,44 @@ +/* + * 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.timers; + +import java.io.Serializable; + +import org.apache.jmeter.util.JMeterUtils; + +/** + * This class implements those methods needed by RandomTimer to be instantiable + * and implements a random delay with an average value and a uniformly + * distributed variation. + * + */ +public class UniformRandomTimer extends RandomTimer implements Serializable { + private static final long serialVersionUID = 240L; + + @Override + public long delay() { + return (long) Math.abs((this.random.nextDouble() * getRange()) + super.delay()); + } + + @Override + public String toString() { + return JMeterUtils.getResString("uniform_timer_memo"); //$NON-NLS-1$ + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/gui/AbstractRandomTimerGui.java b/ApacheJmeter/src/org/apache/jmeter/timers/gui/AbstractRandomTimerGui.java new file mode 100644 index 0000000..c9c529f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/gui/AbstractRandomTimerGui.java @@ -0,0 +1,192 @@ +/* + * 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.timers.gui; + +import java.awt.Dimension; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.timers.ConstantTimer; +import org.apache.jmeter.timers.RandomTimer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * Abstract Random timer GUI. + * + */ +public abstract class AbstractRandomTimerGui extends AbstractTimerGui { + + /** + * + */ + private static final long serialVersionUID = -322164502276145504L; + + private static final String DELAY_FIELD = "Delay Field"; + + private static final String RANGE_FIELD = "Range Field"; + + private JTextField delayField; + + private JTextField rangeField; + + /** + * No-arg constructor. + */ + public AbstractRandomTimerGui() { + init(); + } + + /** + * Handle an error. + * + * @param e + * the Exception that was thrown. + * @param thrower + * the JComponent that threw the Exception. + */ + public static void error(Exception e, JComponent thrower) { + JOptionPane.showMessageDialog(thrower, e, "Error", JOptionPane.ERROR_MESSAGE); + } + + /** + * Create the test element underlying this GUI component. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + RandomTimer timer = createRandomTimer(); + modifyTestElement(timer); + return timer; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement timer) { + this.configureTestElement(timer); + ((RandomTimer) timer).setDelay(delayField.getText()); + ((RandomTimer) timer).setRange(rangeField.getText()); + } + + /** + * Configure this GUI component from the underlying TestElement. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) + */ + @Override + public void configure(TestElement el) { + super.configure(el); + delayField.setText(el.getPropertyAsString(ConstantTimer.DELAY)); + rangeField.setText(el.getPropertyAsString(RandomTimer.RANGE)); + } + + + /** + * Initialize this component. + */ + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + setBorder(makeBorder()); + + add(makeTitlePanel()); + + JPanel threadDelayPropsPanel = new JPanel(); + threadDelayPropsPanel.setLayout(new VerticalLayout(5, VerticalLayout.LEFT)); + threadDelayPropsPanel.setBorder(BorderFactory.createTitledBorder( + JMeterUtils.getResString("thread_delay_properties")));//$NON-NLS-1$ + + // DELAY DEVIATION + Box delayDevPanel = Box.createHorizontalBox(); + delayDevPanel.add(new JLabel(getTimerRangeLabelKey()));//$NON-NLS-1$ + delayDevPanel.add(Box.createHorizontalStrut(5)); + + rangeField = new JTextField(6); + rangeField.setText(getDefaultRange()); + rangeField.setName(RANGE_FIELD); + delayDevPanel.add(rangeField); + + threadDelayPropsPanel.add(delayDevPanel); + + // AVG DELAY + Box avgDelayPanel = Box.createHorizontalBox(); + avgDelayPanel.add(new JLabel(getTimerDelayLabelKey()));//$NON-NLS-1$ + avgDelayPanel.add(Box.createHorizontalStrut(5)); + + delayField = new JTextField(6); + delayField.setText(getDefaultDelay()); + delayField.setName(DELAY_FIELD); + avgDelayPanel.add(delayField); + + threadDelayPropsPanel.add(avgDelayPanel); + threadDelayPropsPanel.setMaximumSize(new Dimension(threadDelayPropsPanel.getMaximumSize().width, + threadDelayPropsPanel.getPreferredSize().height)); + add(threadDelayPropsPanel); + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + rangeField.setText(getDefaultRange()); + delayField.setText(getDefaultDelay()); + super.clearGui(); + } + + /** + * {@inheritDoc} + */ + abstract public String getLabelResource(); + + /** + * Create implementation of RandomTimer + * @return {@link RandomTimer} + */ + protected abstract RandomTimer createRandomTimer(); + + /** + * @return String timer delay label key + */ + abstract protected String getTimerDelayLabelKey(); + + /** + * @return String timer range label key + */ + abstract protected String getTimerRangeLabelKey(); + + /** + * @return String default delay value + */ + abstract protected String getDefaultDelay(); + + /** + * @return String default range value + */ + abstract protected String getDefaultRange(); +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/gui/AbstractTimerGui.java b/ApacheJmeter/src/org/apache/jmeter/timers/gui/AbstractTimerGui.java new file mode 100644 index 0000000..c7d8d8b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/gui/AbstractTimerGui.java @@ -0,0 +1,64 @@ +/* + * 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.timers.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * This is the base class for JMeter GUI components which manage timers. + * + * @version $Revision: 905027 $ + */ +public abstract class AbstractTimerGui extends AbstractJMeterGuiComponent { + private static final long serialVersionUID = 240L; + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most timer + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultTimerMenu(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#TIMERS}, which is + * appropriate for most timer components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.TIMERS }); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/gui/ConstantTimerGui.java b/ApacheJmeter/src/org/apache/jmeter/timers/gui/ConstantTimerGui.java new file mode 100644 index 0000000..0f0e350 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/gui/ConstantTimerGui.java @@ -0,0 +1,131 @@ +/* + * 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.timers.gui; + +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JTextField; + +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.timers.ConstantTimer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * The GUI for ConstantTimer. + * + */ +public class ConstantTimerGui extends AbstractTimerGui { + private static final long serialVersionUID = 240L; + + /** + * The default value for the delay. + */ + private static final String DEFAULT_DELAY = "300"; + + private static final String DELAY_FIELD = "Delay Field"; + + private JTextField delayField; + + /** + * No-arg constructor. + */ + public ConstantTimerGui() { + init(); + } + + /** + * Handle an error. + * + * @param e + * the Exception that was thrown. + * @param thrower + * the JComponent that threw the Exception. + */ + public static void error(Exception e, JComponent thrower) { + JOptionPane.showMessageDialog(thrower, e, "Error", JOptionPane.ERROR_MESSAGE); + } + + public String getLabelResource() { + return "constant_timer_title"; // $NON-NLS-1$ + } + + /** + * Create the test element underlying this GUI component. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() + */ + public TestElement createTestElement() { + ConstantTimer timer = new ConstantTimer(); + modifyTestElement(timer); + return timer; + } + + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + public void modifyTestElement(TestElement timer) { + this.configureTestElement(timer); + ((ConstantTimer) timer).setDelay(delayField.getText()); + } + + /** + * Configure this GUI component from the underlying TestElement. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) + */ + @Override + public void configure(TestElement el) { + super.configure(el); + delayField.setText(((ConstantTimer) el).getDelay()); + } + + /** + * Initialize this component. + */ + private void init() { + setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP)); + + setBorder(makeBorder()); + add(makeTitlePanel()); + + Box delayPanel = Box.createHorizontalBox(); + JLabel delayLabel = new JLabel(JMeterUtils.getResString("constant_timer_delay"));//$NON-NLS-1$ + delayPanel.add(delayLabel); + + delayField = new JTextField(6); + delayField.setText(DEFAULT_DELAY); + delayField.setName(DELAY_FIELD); + delayPanel.add(delayField); + add(delayPanel); + } + + /** + * {@inheritDoc} + */ + @Override + public void clearGui() { + delayField.setText(DEFAULT_DELAY); + super.clearGui(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/gui/GaussianRandomTimerGui.java b/ApacheJmeter/src/org/apache/jmeter/timers/gui/GaussianRandomTimerGui.java new file mode 100644 index 0000000..5252c7b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/gui/GaussianRandomTimerGui.java @@ -0,0 +1,91 @@ +/* + * 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.timers.gui; + +import org.apache.jmeter.timers.GaussianRandomTimer; +import org.apache.jmeter.timers.RandomTimer; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Implementation of a gaussian random timer. + */ +public class GaussianRandomTimerGui extends AbstractRandomTimerGui { + + private static final long serialVersionUID = 240L; + + private static final String DEFAULT_DELAY = "300"; // $NON-NLS-1$ + + private static final String DEFAULT_RANGE = "100.0"; // $NON-NLS-1$ + + + /** + * No-arg constructor. + */ + public GaussianRandomTimerGui() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getLabelResource() { + return "gaussian_timer_title";//$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + protected RandomTimer createRandomTimer() { + return new GaussianRandomTimer(); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getTimerDelayLabelKey() { + return JMeterUtils.getResString("gaussian_timer_delay"); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getTimerRangeLabelKey() { + return JMeterUtils.getResString("gaussian_timer_range"); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getDefaultDelay() { + return DEFAULT_DELAY; + } + + /** + * {@inheritDoc} + */ + @Override + protected String getDefaultRange() { + return DEFAULT_RANGE; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/gui/PoissonRandomTimerGui.java b/ApacheJmeter/src/org/apache/jmeter/timers/gui/PoissonRandomTimerGui.java new file mode 100644 index 0000000..3bf11d2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/gui/PoissonRandomTimerGui.java @@ -0,0 +1,69 @@ +/* + * 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.timers.gui; + +import org.apache.jmeter.timers.PoissonRandomTimer; +import org.apache.jmeter.timers.RandomTimer; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Implementation of a Poisson random timer. + */ +public class PoissonRandomTimerGui extends AbstractRandomTimerGui { + + private static final long serialVersionUID = -3218002787832805275L; + + private static final String DEFAULT_DELAY = "300"; // $NON-NLS-1$ + + private static final String DEFAULT_RANGE = "100"; // $NON-NLS-1$ + + public PoissonRandomTimerGui() { + super(); + } + + @Override + public String getLabelResource() { + return "poisson_timer_title";//$NON-NLS-1$ + } + + @Override + protected RandomTimer createRandomTimer() { + return new PoissonRandomTimer(); + } + + @Override + protected String getTimerDelayLabelKey() { + return JMeterUtils.getResString("poisson_timer_delay"); + } + + @Override + protected String getTimerRangeLabelKey() { + return JMeterUtils.getResString("poisson_timer_range"); + } + + @Override + protected String getDefaultDelay() { + return DEFAULT_DELAY; + } + + @Override + protected String getDefaultRange() { + return DEFAULT_RANGE; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/timers/gui/UniformRandomTimerGui.java b/ApacheJmeter/src/org/apache/jmeter/timers/gui/UniformRandomTimerGui.java new file mode 100644 index 0000000..31e9fee --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/timers/gui/UniformRandomTimerGui.java @@ -0,0 +1,92 @@ +/* + * 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.timers.gui; + +import org.apache.jmeter.timers.RandomTimer; +import org.apache.jmeter.timers.UniformRandomTimer; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Implementation of a uniform random timer. + * + */ +public class UniformRandomTimerGui extends AbstractRandomTimerGui { + + private static final long serialVersionUID = 240L; + + private static final String DEFAULT_DELAY = "0"; // $NON-NLS-1$ + + private static final String DEFAULT_RANGE = "100.0";// $NON-NLS-1$ + + /** + * No-arg constructor. + */ + public UniformRandomTimerGui() { + super(); + } + + + /** + * {@inheritDoc} + */ + @Override + public String getLabelResource() { + return "uniform_timer_title";//$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + protected RandomTimer createRandomTimer() { + return new UniformRandomTimer(); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getTimerDelayLabelKey() { + return JMeterUtils.getResString("uniform_timer_delay"); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getTimerRangeLabelKey() { + return JMeterUtils.getResString("uniform_timer_range"); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getDefaultDelay() { + return DEFAULT_DELAY; + } + + /** + * {@inheritDoc} + */ + @Override + protected String getDefaultRange() { + return DEFAULT_RANGE; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/BSFBeanInfoSupport.java b/ApacheJmeter/src/org/apache/jmeter/util/BSFBeanInfoSupport.java new file mode 100644 index 0000000..5a4e46e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/BSFBeanInfoSupport.java @@ -0,0 +1,46 @@ +/* + * 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; + +import java.util.Arrays; +import java.util.Properties; + +/** + * Parent class to handle common GUI design for BSF test elements + */ +public abstract class BSFBeanInfoSupport extends ScriptingBeanInfoSupport { + + private final static String[] LANGUAGE_TAGS; + + static { + Properties languages = JMeterUtils.loadProperties("org/apache/bsf/Languages.properties"); // $NON-NLS-1$ + LANGUAGE_TAGS = new String[languages.size() + 1]; + int i = 0; + for (Object language : languages.keySet()) { + LANGUAGE_TAGS[i++] = language.toString(); + } + LANGUAGE_TAGS[i] = "jexl"; // $NON-NLS-1$ + Arrays.sort(LANGUAGE_TAGS); + } + + protected BSFBeanInfoSupport(Class beanClass) { + super(beanClass, LANGUAGE_TAGS); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/BSFJavaScriptEngine.java b/ApacheJmeter/src/org/apache/jmeter/util/BSFJavaScriptEngine.java new file mode 100644 index 0000000..1f6a11b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/BSFJavaScriptEngine.java @@ -0,0 +1,241 @@ +/* + * 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; + +import java.util.Iterator; +import java.util.Vector; + +import org.apache.bsf.BSFDeclaredBean; +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.bsf.util.BSFEngineImpl; +import org.apache.bsf.util.BSFFunctions; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.EvaluatorException; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.ImporterTopLevel; +import org.mozilla.javascript.JavaScriptException; +import org.mozilla.javascript.NativeJavaObject; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.WrappedException; +import org.mozilla.javascript.Wrapper; + +/** + * This is the interface to Netscape's Rhino (JavaScript) from the + * Bean Scripting Framework. + *

+ * The original version of this code was first written by Adam Peller + * for use in LotusXSL. Sanjiva took his code and adapted it for BSF. + * + * Modified for JMeter to fix bug BSF-22. + */ +public class BSFJavaScriptEngine extends BSFEngineImpl { + /** + * The global script object, where all embedded functions are defined, + * as well as the standard ECMA "core" objects. + */ + private Scriptable global; + + /** + * Return an object from an extension. + * @param object Object on which to make the call (ignored). + * @param method The name of the method to call. + * @param args an array of arguments to be + * passed to the extension, which may be either + * Vectors of Nodes, or Strings. + */ + public Object call(Object object, String method, Object[] args) + throws BSFException { + + Object retval = null; + Context cx; + + try { + cx = Context.enter(); + + // REMIND: convert arg list Vectors here? + + Object fun = global.get(method, global); + // NOTE: Source and line arguments are nonsense in a call(). + // Any way to make these arguments *sensible? + if (fun == Scriptable.NOT_FOUND) + throw new EvaluatorException("function " + method + + " not found.", "none", 0); + + cx.setOptimizationLevel(-1); + cx.setGeneratingDebug(false); + cx.setGeneratingSource(false); + cx.setOptimizationLevel(0); + cx.setDebugger(null, null); + + retval = + ((Function) fun).call(cx, global, global, args); + +// ScriptRuntime.call(cx, fun, global, args, global); + + if (retval instanceof Wrapper) + retval = ((Wrapper) retval).unwrap(); + } + catch (Throwable t) { + handleError(t); + } + finally { + Context.exit(); + } + return retval; + } + + @Override + public void declareBean(BSFDeclaredBean bean) throws BSFException { + if ((bean.bean instanceof Number) || + (bean.bean == null) || + (bean.bean instanceof String) || + (bean.bean instanceof Boolean)) { + global.put(bean.name, global, bean.bean); + } + else { + // Must wrap non-scriptable objects before presenting to Rhino + Scriptable wrapped = Context.toObject(bean.bean, global); + global.put(bean.name, global, wrapped); + } + } + + /** + * This is used by an application to evaluate a string containing + * some expression. + */ + public Object eval(String source, int lineNo, int columnNo, Object oscript) + throws BSFException { + + String scriptText = oscript.toString(); + Object retval = null; + Context cx; + + try { + cx = Context.enter(); + + cx.setOptimizationLevel(-1); + cx.setGeneratingDebug(false); + cx.setGeneratingSource(false); + cx.setOptimizationLevel(0); + cx.setDebugger(null, null); + + retval = cx.evaluateString(global, scriptText, + source, lineNo, + null); + + if (retval instanceof NativeJavaObject) + retval = ((NativeJavaObject) retval).unwrap(); + + } + catch (Throwable t) { // includes JavaScriptException, rethrows Errors + handleError(t); + } + finally { + Context.exit(); + } + return retval; + } + + private void handleError(Throwable t) throws BSFException { + if (t instanceof WrappedException) + t = ((WrappedException) t).getWrappedException(); + + String message = null; + Throwable target = t; + + if (t instanceof JavaScriptException) { + message = t.getLocalizedMessage(); + + // Is it an exception wrapped in a JavaScriptException? + Object value = ((JavaScriptException) t).getValue(); + if (value instanceof Throwable) { + // likely a wrapped exception from a LiveConnect call. + // Display its stack trace as a diagnostic + target = (Throwable) value; + } + } + else if (t instanceof EvaluatorException || + t instanceof SecurityException) { + message = t.getLocalizedMessage(); + } + else if (t instanceof RuntimeException) { + message = "Internal Error: " + t.toString(); + } + else if (t instanceof StackOverflowError) { + message = "Stack Overflow"; + } + + if (message == null) + message = t.toString(); + + if (t instanceof Error && !(t instanceof StackOverflowError)) { + // Re-throw Errors because we're supposed to let the JVM see it + // Don't re-throw StackOverflows, because we know we've + // corrected the situation by aborting the loop and + // a long stacktrace would end up on the user's console + throw (Error) t; + } + else { + throw new BSFException(BSFException.REASON_OTHER_ERROR, + "JavaScript Error: " + message, + target); + } + } + + /** + * Initialize the engine. + * Put the manager into the context-manager + * map hashtable too. + */ + @Override + public void initialize(BSFManager mgr, String lang, + @SuppressWarnings("rawtypes") // superclass does not support types + Vector declaredBeans) + throws BSFException { + + super.initialize(mgr, lang, declaredBeans); + + // Initialize context and global scope object + try { + Context cx = Context.enter(); + global = new ImporterTopLevel(cx); + Scriptable bsf = Context.toObject(new BSFFunctions(mgr, this), global); + global.put("bsf", global, bsf); + + for( + @SuppressWarnings("unchecked") + Iterator it = declaredBeans.iterator(); + it.hasNext();) { + declareBean(it.next()); + } + } + catch (Throwable t) { + handleError(t); + } + finally { + Context.exit(); + } + } + + @Override + public void undeclareBean(BSFDeclaredBean bean) throws BSFException { + global.delete(bean.name); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/BSFTestElement.java b/ApacheJmeter/src/org/apache/jmeter/util/BSFTestElement.java new file mode 100644 index 0000000..9d4a803 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/BSFTestElement.java @@ -0,0 +1,206 @@ +/* + * 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; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.Properties; + +import org.apache.bsf.BSFEngine; +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.commons.io.FileUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public abstract class BSFTestElement extends AbstractTestElement + implements Serializable, Cloneable +{ + private static final long serialVersionUID = 233L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + static { + BSFManager.registerScriptingEngine("jexl", //$NON-NLS-1$ + "org.apache.commons.jexl.bsf.JexlEngine", //$NON-NLS-1$ + new String[]{"jexl"}); //$NON-NLS-1$ + log.info("Registering JMeter version of JavaScript engine as work-round for BSF-22"); + BSFManager.registerScriptingEngine("javascript", //$NON-NLS-1$ + "org.apache.jmeter.util.BSFJavaScriptEngine", //$NON-NLS-1$ + new String[]{"js"}); //$NON-NLS-1$ + } + + //++ For TestBean implementations only + private String parameters; // passed to file or script + + private String filename; // file to source (overrides script) + + private String script; // script (if file not provided) + + private String scriptLanguage; // BSF language to use + //-- For TestBean implementations only + + public BSFTestElement() { + super(); + init(); + } + + private void init() { + parameters=""; // ensure variables are not null + filename=""; + script=""; + scriptLanguage=""; + } + + protected Object readResolve() { + init(); + return this; + } + + @Override + public Object clone() { + BSFTestElement o = (BSFTestElement) super.clone(); + o.init(); + return o; + } + + protected BSFManager getManager() throws BSFException { + BSFManager mgr = new BSFManager(); + initManager(mgr); + return mgr; + } + + protected void initManager(BSFManager mgr) throws BSFException{ + final String label = getName(); + final String fileName = getFilename(); + final String scriptParameters = getParameters(); + // Use actual class name for log + final Logger logger = LoggingManager.getLoggerForShortName(getClass().getName()); + mgr.declareBean("log", logger, Logger.class); // $NON-NLS-1$ + mgr.declareBean("Label",label, String.class); // $NON-NLS-1$ + mgr.declareBean("FileName",fileName, String.class); // $NON-NLS-1$ + mgr.declareBean("Parameters", scriptParameters, String.class); // $NON-NLS-1$ + String [] args=JOrphanUtils.split(scriptParameters, " ");//$NON-NLS-1$ + mgr.declareBean("args",args,args.getClass());//$NON-NLS-1$ + // Add variables for access to context and variables + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + Properties props = JMeterUtils.getJMeterProperties(); + + mgr.declareBean("ctx", jmctx, jmctx.getClass()); // $NON-NLS-1$ + mgr.declareBean("vars", vars, vars.getClass()); // $NON-NLS-1$ + mgr.declareBean("props", props, props.getClass()); // $NON-NLS-1$ + // For use in debugging: + mgr.declareBean("OUT", System.out, PrintStream.class); // $NON-NLS-1$ + + // Most subclasses will need these: + Sampler sampler = jmctx.getCurrentSampler(); + mgr.declareBean("sampler", sampler, Sampler.class); + SampleResult prev = jmctx.getPreviousResult(); + mgr.declareBean("prev", prev, SampleResult.class); + } + + protected void processFileOrScript(BSFManager mgr) throws BSFException{ + BSFEngine bsfEngine = mgr.loadScriptingEngine(getScriptLanguage()); + final String scriptFile = getFilename(); + if (scriptFile.length() == 0) { + bsfEngine.exec("[script]",0,0,getScript()); + } else {// we have a file, read and process it + try { + String script=FileUtils.readFileToString(new File(scriptFile)); + bsfEngine.exec(scriptFile,0,0,script); + } catch (IOException e) { + log.warn(e.getLocalizedMessage()); + throw new BSFException(BSFException.REASON_IO_ERROR,"Problem reading script file",e); + } + } + } + + protected Object evalFileOrScript(BSFManager mgr) throws BSFException{ + BSFEngine bsfEngine = mgr.loadScriptingEngine(getScriptLanguage()); + final String scriptFile = getFilename(); + if (scriptFile.length() == 0) { + return bsfEngine.eval("[script]",0,0,getScript()); + } else {// we have a file, read and process it + try { + String script=FileUtils.readFileToString(new File(scriptFile)); + return bsfEngine.eval(scriptFile,0,0,script); + } catch (IOException e) { + log.warn(e.getLocalizedMessage()); + throw new BSFException(BSFException.REASON_IO_ERROR,"Problem reading script file",e); + } + } + } + + /** + * Return the script (TestBean version). + * Must be overridden for subclasses that don't implement TestBean + * otherwise the clone() method won't work. + * + * @return the script to execute + */ + public String getScript(){ + return script; + } + + /** + * Set the script (TestBean version). + * Must be overridden for subclasses that don't implement TestBean + * otherwise the clone() method won't work. + * + * @param s the script to execute (may be blank) + */ + public void setScript(String s){ + script=s; + } + + public String getParameters() { + return parameters; + } + + public void setParameters(String s) { + parameters = s; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String s) { + filename = s; + } + + public String getScriptLanguage() { + return scriptLanguage; + } + + public void setScriptLanguage(String s) { + scriptLanguage = s; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/BeanShellBeanInfoSupport.java b/ApacheJmeter/src/org/apache/jmeter/util/BeanShellBeanInfoSupport.java new file mode 100644 index 0000000..d721597 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/BeanShellBeanInfoSupport.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.util; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.TextAreaEditor; + +/** + * Parent class to handle common GUI design + */ +public abstract class BeanShellBeanInfoSupport extends BeanInfoSupport { + + protected BeanShellBeanInfoSupport(Class beanClass) { + super(beanClass); + PropertyDescriptor p; + + p = property("resetInterpreter"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.FALSE); + p.setValue(NOT_EXPRESSION, Boolean.TRUE); + p.setValue(NOT_OTHER, Boolean.TRUE); + + createPropertyGroup("resetGroup", new String[] { "resetInterpreter" }); + + p = property("parameters"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + createPropertyGroup("parameterGroup", new String[] { "parameters" }); + + p = property("filename"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + createPropertyGroup("filenameGroup", new String[] { "filename" }); + + p = property("script"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + p.setPropertyEditorClass(TextAreaEditor.class); + + createPropertyGroup("scripting", new String[] { "script" }); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/BeanShellClient.java b/ApacheJmeter/src/org/apache/jmeter/util/BeanShellClient.java new file mode 100644 index 0000000..c681df8 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/BeanShellClient.java @@ -0,0 +1,115 @@ +/* + * 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; + +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; + + +/** + * Implements a client that can talk to the JMeter BeanShell server. + */ +public class BeanShellClient { + + private static final int MINARGS = 3; + + public static void main(String [] args) throws Exception{ + if (args.length < MINARGS){ + System.out.println("Please provide "+MINARGS+" or more arguments:"); + System.out.println("serverhost serverport filename [arg1 arg2 ...]"); + System.out.println("e.g. "); + System.out.println("localhost 9000 extras/remote.bsh apple blake 7"); + return; + } + String host=args[0]; + String portString = args[1]; + String file=args[2]; + + int port=Integer.parseInt(portString)+1;// convert to telnet port + + System.out.println("Connecting to BSH server on "+host+":"+portString); + + Socket sock = new Socket(host,port); + InputStream is = sock.getInputStream(); + + OutputStream os = sock.getOutputStream(); + + InputStreamReader fis = new FileReader(file); + + new SockRead(is).start(); + + sendLine("bsh.prompt=\"\";",os);// Prompt is unnecessary + + sendLine("String [] args={",os); + for (int i=MINARGS; i -1) { + char c = (char) x; + System.out.print(c); + } + } catch (IOException e) { + } finally { + System.out.println("... disconnected from server."); + try { + is.close(); + } catch (IOException e) { + } + } + + } + + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/BeanShellInterpreter.java b/ApacheJmeter/src/org/apache/jmeter/util/BeanShellInterpreter.java new file mode 100644 index 0000000..f6a93d0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/BeanShellInterpreter.java @@ -0,0 +1,225 @@ +/* + * 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; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +/** + * BeanShell setup function - encapsulates all the access to the BeanShell + * Interpreter in a single class. + * + * The class uses dynamic class loading to access BeanShell, which means that + * all the source files can be built without needing access to the bsh jar. + * + * If the beanshell jar is not present at run-time, an error will be logged + * + */ + +public class BeanShellInterpreter { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final Method bshGet; + + private static final Method bshSet; + + private static final Method bshEval; + + private static final Method bshSource; + + private static final Class bshClass; + + private static final String BSH_INTERPRETER = "bsh.Interpreter"; //$NON-NLS-1$ + + static { + // Temporary copies, so can set the final ones + Method get = null, eval = null, set = null, source = null; + Class clazz = null; + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + try { + clazz = loader.loadClass(BSH_INTERPRETER); + Class string = String.class; + Class object = Object.class; + + get = clazz.getMethod("get", //$NON-NLS-1$ + new Class[] { string }); + eval = clazz.getMethod("eval", //$NON-NLS-1$ + new Class[] { string }); + set = clazz.getMethod("set", //$NON-NLS-1$ + new Class[] { string, object }); + source = clazz.getMethod("source", //$NON-NLS-1$ + new Class[] { string }); + } catch (ClassNotFoundException e) { + log.error("Beanshell Interpreter not found"); + } catch (SecurityException e) { + log.error("Beanshell Interpreter not found", e); + } catch (NoSuchMethodException e) { + log.error("Beanshell Interpreter not found", e); + } finally { + bshEval = eval; + bshGet = get; + bshSet = set; + bshSource = source; + bshClass = clazz; + } + } + + // This class is not serialised + private Object bshInstance = null; // The interpreter instance for this class + + private final String initFile; // Script file to initialize the Interpreter with + + private final Logger logger; // Logger to use during initialization and script run + + public BeanShellInterpreter() throws ClassNotFoundException { + initFile = null; + logger = null; + init(); + } + + /** + * + * @param init initialisation file + * @param _log logger to pass to interpreter + */ + public BeanShellInterpreter(String init, Logger _log) throws ClassNotFoundException { + initFile = init; + logger = _log; + init(); + } + + // Called from ctor, so must be private (or final, but it does not seem useful elsewhere) + private void init() throws ClassNotFoundException { + if (bshClass == null) { + throw new ClassNotFoundException(BSH_INTERPRETER); + } + try { + bshInstance = bshClass.newInstance(); + } catch (InstantiationException e) { + log.error("Can't instantiate BeanShell", e); + throw new ClassNotFoundException("Can't instantiate BeanShell", e); + } catch (IllegalAccessException e) { + log.error("Can't instantiate BeanShell", e); + throw new ClassNotFoundException("Can't instantiate BeanShell", e); + } + if (logger != null) {// Do this before starting the script + try { + set("log", logger);//$NON-NLS-1$ + } catch (JMeterException e) { + log.warn("Can't set logger variable", e); + } + } + if (initFile != null && initFile.length() > 0) { + String fileToUse=initFile; + // Check file so we can distinguish file error from script error + File in = new File(fileToUse); + if (!in.exists()){// Cannot find the file locally, so try the bin directory + fileToUse=JMeterUtils.getJMeterHome() + +File.separator+"bin" // $NON-NLS-1$ + +File.separator+initFile; + in = new File(fileToUse); + if (!in.exists()) { + log.warn("Cannot find init file: "+initFile); + } + } + if (!in.canRead()) { + log.warn("Cannot read init file: "+fileToUse); + } + try { + source(fileToUse); + } catch (JMeterException e) { + log.warn("Cannot source init file: "+fileToUse,e); + } + } + } + + /** + * Resets the BeanShell interpreter. + * + * @throws ClassNotFoundException if interpreter cannot be instantiated + */ + public void reset() throws ClassNotFoundException { + init(); + } + + private Object bshInvoke(Method m, Object[] o, boolean shouldLog) throws JMeterException { + Object r = null; + final String errorString = "Error invoking bsh method: "; + try { + r = m.invoke(bshInstance, o); + } catch (IllegalArgumentException e) { // Programming error + final String message = errorString + m.getName(); + log.error(message); + throw new JMeterError(message, e); + } catch (IllegalAccessException e) { // Also programming error + final String message = errorString + m.getName(); + log.error(message); + throw new JMeterError(message, e); + } catch (InvocationTargetException e) { // Can occur at run-time + // could be caused by the bsh Exceptions: + // EvalError, ParseException or TargetError + String message = errorString + m.getName(); + Throwable cause = e.getCause(); + if (cause != null) { + message += "\t" + cause.getLocalizedMessage(); + } + + if (shouldLog) { + log.error(message); + } + throw new JMeterException(message, e); + } + return r; + } + + public Object eval(String s) throws JMeterException { + return bshInvoke(bshEval, new Object[] { s }, true); + } + + public Object evalNoLog(String s) throws JMeterException { + return bshInvoke(bshEval, new Object[] { s }, false); + } + + public Object set(String s, Object o) throws JMeterException { + return bshInvoke(bshSet, new Object[] { s, o }, true); + } + + public Object set(String s, boolean b) throws JMeterException { + return bshInvoke(bshSet, new Object[] { s, Boolean.valueOf(b) }, true); + } + + public Object source(String s) throws JMeterException { + return bshInvoke(bshSource, new Object[] { s }, true); + } + + public Object get(String s) throws JMeterException { + return bshInvoke(bshGet, new Object[] { s }, true); + } + + // For use by Unit Tests + public static boolean isInterpreterPresent(){ + return bshClass != null; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/BeanShellServer.java b/ApacheJmeter/src/org/apache/jmeter/util/BeanShellServer.java new file mode 100644 index 0000000..c5135b4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/BeanShellServer.java @@ -0,0 +1,103 @@ +/* + * 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; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements a BeanShell server to allow access to JMeter variables and + * methods. + * + * To enable, define the JMeter property: beanshell.server.port (see + * JMeter.java) beanshell.server.file (optional, startup file) + * + */ +public class BeanShellServer implements Runnable { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final int serverport; + + private final String serverfile; + + /** + * + */ + public BeanShellServer(int port, String file) { + super(); + serverfile = file;// can be the empty string + serverport = port; + } + + // For use by the server script + static String getprop(String s) { + return JMeterUtils.getPropDefault(s, s); + } + + // For use by the server script + static void setprop(String s, String v) { + JMeterUtils.getJMeterProperties().setProperty(s, v); + } + + public void run() { + + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + + try { + Class Interpreter = loader.loadClass("bsh.Interpreter");//$NON-NLS-1$ + Object instance = Interpreter.newInstance(); + Class string = String.class; + Class object = Object.class; + + Method eval = Interpreter.getMethod("eval", new Class[] { string });//$NON-NLS-1$ + Method setObj = Interpreter.getMethod("set", new Class[] { string, object });//$NON-NLS-1$ + Method setInt = Interpreter.getMethod("set", new Class[] { string, int.class });//$NON-NLS-1$ + Method source = Interpreter.getMethod("source", new Class[] { string });//$NON-NLS-1$ + + setObj.invoke(instance, new Object[] { "t", this });//$NON-NLS-1$ + setInt.invoke(instance, new Object[] { "portnum", Integer.valueOf(serverport) });//$NON-NLS-1$ + + if (serverfile.length() > 0) { + try { + source.invoke(instance, new Object[] { serverfile }); + } catch (InvocationTargetException e1) { + log.warn("Could not source " + serverfile); + Throwable t= e1.getCause(); + if (t != null) { + log.warn(t.toString()); + if(t instanceof Error) { + throw (Error)t; + } + } + } + } + eval.invoke(instance, new Object[] { "setAccessibility(true);" });//$NON-NLS-1$ + eval.invoke(instance, new Object[] { "server(portnum);" });//$NON-NLS-1$ + + } catch (ClassNotFoundException e) { + log.error("Beanshell Interpreter not found"); + } catch (Exception e) { + log.error("Problem starting BeanShell server ", e); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/BeanShellTestElement.java b/ApacheJmeter/src/org/apache/jmeter/util/BeanShellTestElement.java new file mode 100644 index 0000000..82b57f3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/BeanShellTestElement.java @@ -0,0 +1,291 @@ +/* + * 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; + +import java.io.Serializable; +import java.util.List; + +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestListener; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public abstract class BeanShellTestElement extends AbstractTestElement + implements Serializable, Cloneable, ThreadListener, TestListener +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + //++ For TestBean implementations only + private String parameters; // passed to file or script + + private String filename; // file to source (overrides script) + + private String script; // script (if file not provided) + + private boolean resetInterpreter = false; + //-- For TestBean implementations only + + + private transient BeanShellInterpreter bshInterpreter = null; + + private transient boolean hasInitFile = false; + + public BeanShellTestElement() { + super(); + init(); + } + + protected abstract String getInitFileProperty(); + + /** + * Get the interpreter and set up standard script variables. + *

+ * Sets the following script variables: + *

    + *
  • ctx
  • + *
  • Label
  • + *
  • prev
  • + *
  • props
  • + *
  • vars
  • + *
+ * @return the interpreter + */ + protected BeanShellInterpreter getBeanShellInterpreter() { + if (isResetInterpreter()) { + try { + bshInterpreter.reset(); + } catch (ClassNotFoundException e) { + log.error("Cannot reset BeanShell: "+e.toString()); + } + } + + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = jmctx.getVariables(); + + try { + bshInterpreter.set("ctx", jmctx);//$NON-NLS-1$ + bshInterpreter.set("Label", getName()); //$NON-NLS-1$ + bshInterpreter.set("prev", jmctx.getPreviousResult());//$NON-NLS-1$ + bshInterpreter.set("props", JMeterUtils.getJMeterProperties()); + bshInterpreter.set("vars", vars);//$NON-NLS-1$ + } catch (JMeterException e) { + log.warn("Problem setting one or more BeanShell variables "+e); + } + return bshInterpreter; + } + + private void init() { + parameters=""; // ensure variables are not null + filename=""; + script=""; + try { + String initFileName = JMeterUtils.getProperty(getInitFileProperty()); + hasInitFile = initFileName != null; + bshInterpreter = new BeanShellInterpreter(initFileName, log); + } catch (ClassNotFoundException e) { + log.error("Cannot find BeanShell: "+e.toString()); + } + } + + protected Object readResolve() { + init(); + return this; + } + + @Override + public Object clone() { + BeanShellTestElement o = (BeanShellTestElement) super.clone(); + o.init(); + return o; + } + + /** + * Process the file or script from the test element. + *

+ * Sets the following script variables: + *

    + *
  • FileName
  • + *
  • Parameters
  • + *
  • bsh.args
  • + *
+ * @param bsh the interpreter, not {@code null} + * @return the result of the script, may be {@code null} + * + * @throws JMeterException + */ + protected Object processFileOrScript(BeanShellInterpreter bsh) throws JMeterException{ + String fileName = getFilename(); + String params = getParameters(); + + bsh.set("FileName", fileName);//$NON-NLS-1$ + // Set params as a single line + bsh.set("Parameters", params); // $NON-NLS-1$ + // and set as an array + bsh.set("bsh.args",//$NON-NLS-1$ + JOrphanUtils.split(params, " "));//$NON-NLS-1$ + + if (fileName.length() == 0) { + return bsh.eval(getScript()); + } + return bsh.source(fileName); + } + + /** + * Return the script (TestBean version). + * Must be overridden for subclasses that don't implement TestBean + * otherwise the clone() method won't work. + * + * @return the script to execute + */ + public String getScript(){ + return script; + } + + /** + * Set the script (TestBean version). + * Must be overridden for subclasses that don't implement TestBean + * otherwise the clone() method won't work. + * + * @param s the script to execute (may be blank) + */ + public void setScript(String s){ + script=s; + } + + public void threadStarted() { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.evalNoLog("threadStarted()"); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + public void threadFinished() { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.evalNoLog("threadFinished()"); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + public void testEnded() { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.evalNoLog("testEnded()"); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + public void testEnded(String host) { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.eval((new StringBuilder("testEnded(")) // $NON-NLS-1$ + .append(host) + .append(")") // $NON-NLS-1$ + .toString()); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + public void testIterationStart(LoopIterationEvent event) { + // Not implemented + } + + public void testStarted() { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.evalNoLog("testStarted()"); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + public void testStarted(String host) { + if (bshInterpreter == null || !hasInitFile) { + return; + } + try { + bshInterpreter.eval((new StringBuilder("testStarted(")) // $NON-NLS-1$ + .append(host) + .append(")") // $NON-NLS-1$ + .toString()); // $NON-NLS-1$ + } catch (JMeterException ignored) { + log.debug(getClass().getName() + " : " + ignored.getLocalizedMessage()); // $NON-NLS-1$ + } + } + + // Overridden by non-TestBean implementations to return the property value instead + public String getParameters() { + return parameters; + } + + public void setParameters(String s) { + parameters = s; + } + + // Overridden by non-TestBean implementations to return the property value instead + public String getFilename() { + return filename; + } + + public void setFilename(String s) { + filename = s; + } + + public boolean isResetInterpreter() { + return resetInterpreter; + } + + public void setResetInterpreter(boolean b) { + resetInterpreter = b; + } + + /** + * {@inheritDoc}} + */ + @Override + public List getSearchableTokens() throws Exception { + List result = super.getSearchableTokens(); + result.add(getScript()); + return result; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/CPSPauser.java b/ApacheJmeter/src/org/apache/jmeter/util/CPSPauser.java new file mode 100644 index 0000000..f275162 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/CPSPauser.java @@ -0,0 +1,58 @@ +/* + * 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; + +/** + * + * Generate appropriate pauses for a given CPS (characters per second) + */ +public class CPSPauser{ + private final int CPS; // Characters per second to emulate + + // Conversions for milli and nano seconds + private static final int MS_PER_SEC = 1000; + private static final int NS_PER_SEC = 1000000000; + private static final int NS_PER_MS = NS_PER_SEC/MS_PER_SEC; + + /** + * Create a pauser with the appropriate speed settings. + * + * @param cps CPS to emulate + */ + public CPSPauser(int cps){ + if (cps <=0) { + throw new IllegalArgumentException("Speed (cps) <= 0"); + } + CPS=cps; + } + + /** + * Pause for an appropriate time according to the number of bytes being transferred. + * + * @param bytes number of bytes being transferred + */ + public void pause(int bytes){ + long sleepMS = (bytes*MS_PER_SEC)/CPS; + int sleepNS = ((bytes*MS_PER_SEC)/CPS) % NS_PER_MS; + try { + Thread.sleep(sleepMS,sleepNS); + } catch (InterruptedException ignored) { + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/util/Calculator.java b/ApacheJmeter/src/org/apache/jmeter/util/Calculator.java new file mode 100644 index 0000000..9cf2873 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/Calculator.java @@ -0,0 +1,235 @@ +/* + * 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; + +import org.apache.jmeter.samplers.SampleResult; + +/** + * Class to calculate various items that don't require all previous results to be saved: + * - mean = average + * - standard deviation + * - minimum + * - maximum + */ +public class Calculator { + + private double sum = 0; + + private double sumOfSquares = 0; + + private double mean = 0; + + private double deviation = 0; + + private int count = 0; + + private long bytes = 0; + + private long maximum = Long.MIN_VALUE; + + private long minimum = Long.MAX_VALUE; + + private int errors = 0; + + private final String label; + + public Calculator() { + this(""); + } + + public Calculator(String label) { + this.label = label; + } + + public void clear() { + maximum = Long.MIN_VALUE; + minimum = Long.MAX_VALUE; + sum = 0; + sumOfSquares = 0; + mean = 0; + deviation = 0; + count = 0; + } + + /** + * Add the value for a single sample. + * + * @param newValue + * + * @see #addValue(long, int) + * @deprecated Use {@link #addSample(SampleResult)} instead + */ + @Deprecated + public void addValue(long newValue) { + addValue(newValue, 1); + } + + /** + * Add the value for (possibly multiple) samples. + * Updates the count, sum, min, max, sumOfSqaures, mean and deviation. + * + * @param newValue the total value for all the samples. + * @param sampleCount number of samples included in the value + */ + private void addValue(long newValue, int sampleCount) { + count += sampleCount; + double currentVal = newValue; + sum += currentVal; + if (sampleCount > 1){ + minimum=Math.min(newValue/sampleCount, minimum); + maximum=Math.max(newValue/sampleCount, maximum); + // For n values in an aggregate sample the average value = (val/n) + // So need to add n * (val/n) * (val/n) = val * val / n + sumOfSquares += (currentVal * currentVal) / (sampleCount); + } else { // no point dividing by 1 + minimum=Math.min(newValue, minimum); + maximum=Math.max(newValue, maximum); + sumOfSquares += currentVal * currentVal; + } + // Calculate each time, as likely to be called for each add + mean = sum / count; + deviation = Math.sqrt((sumOfSquares / count) - (mean * mean)); + } + + + public void addBytes(long newValue) { + bytes += newValue; + } + + private long startTime = 0; + private long elapsedTime = 0; + + /** + * Add details for a sample result, which may consist of multiple samples. + * Updates the number of bytes read, error count, startTime and elapsedTime + * @param res the sample result; might represent multiple values + * @see #addValue(long, int) + */ + public void addSample(SampleResult res) { + addBytes(res.getBytes()); + addValue(res.getTime(),res.getSampleCount()); + errors+=res.getErrorCount(); // account for multiple samples + if (startTime == 0){ // not yet intialised + startTime=res.getStartTime(); + } else { + startTime = Math.min(startTime, res.getStartTime()); + } + elapsedTime = Math.max(elapsedTime, res.getEndTime()-startTime); + } + + + public long getTotalBytes() { + return bytes; + } + + + public double getMean() { + return mean; + } + + public Number getMeanAsNumber() { + return Long.valueOf((long) mean); + } + + public double getStandardDeviation() { + return deviation; + } + + public long getMin() { + return minimum; + } + + public long getMax() { + return maximum; + } + + public int getCount() { + return count; + } + + public String getLabel() { + return label; + } + + /** + * Returns the raw double value of the percentage of samples with errors + * that were recorded. (Between 0.0 and 1.0) + * + * @return the raw double value of the percentage of samples with errors + * that were recorded. + */ + public double getErrorPercentage() { + double rval = 0.0; + + if (count == 0) { + return (rval); + } + rval = (double) errors / (double) count; + return (rval); + } + + /** + * Returns the throughput associated to this sampler in requests per second. + * May be slightly skewed because it takes the timestamps of the first and + * last samples as the total time passed, and the test may actually have + * started before that start time and ended after that end time. + */ + public double getRate() { + if (elapsedTime == 0) { + return 0.0; + } + + return ((double) count / (double) elapsedTime ) * 1000; + } + + /** + * calculates the average page size, which means divide the bytes by number + * of samples. + * + * @return average page size in bytes + */ + public double getAvgPageBytes() { + if (count > 0 && bytes > 0) { + return (double) bytes / count; + } + return 0.0; + } + + /** + * Throughput in bytes / second + * + * @return throughput in bytes/second + */ + public double getBytesPerSecond() { + if (elapsedTime > 0) { + return bytes / ((double) elapsedTime / 1000); // 1000 = millisecs/sec + } + return 0.0; + } + + /** + * Throughput in kilobytes / second + * + * @return Throughput in kilobytes / second + */ + public double getKBPerSecond() { + return getBytesPerSecond() / 1024; // 1024=bytes per kb + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/util/ColorHelper.java b/ApacheJmeter/src/org/apache/jmeter/util/ColorHelper.java new file mode 100644 index 0000000..444f814 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/ColorHelper.java @@ -0,0 +1,64 @@ +/* + * 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; + +import java.awt.Color; + +/** + * This class contains the static utility methods to manipulate colors. + * + * @version $Revision: 674365 $ + */ +public final class ColorHelper { + /** + * Private constructor to prevent instantiation. + */ + private ColorHelper() { + } + + /** + * Given the Color, get the red, green and blue components. + * Increment the lowest of the components by the indicated increment value. + * If all the components are the same value increment in the order of red, + * green and blue. + * + * @param inc + * value to increment the color components + * @return the color after change + */ + public static Color changeColorCyclicIncrement(Color col, int inc) { + int red = col.getRed(); + int green = col.getGreen(); + int blue = col.getBlue(); + int temp1 = Math.min(red, green); + int temp2 = Math.min(temp1, blue); + // now temp2 has the lowest of the three components + if (red == temp2) { + red += inc; + red %= 256; + } else if (green == temp2) { + green += inc; + green %= 256; + } else if (blue == temp2) { + blue += inc; + blue %= 256; + } + return new Color(red, green, blue); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/CustomX509TrustManager.java b/ApacheJmeter/src/org/apache/jmeter/util/CustomX509TrustManager.java new file mode 100644 index 0000000..8cf649b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/CustomX509TrustManager.java @@ -0,0 +1,100 @@ +/* + * 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; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509TrustManager; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Custom TrustManager ignores all certificate errors + * + * TODO: implement conditional checking and logging + * + * (Derived from AuthSSLX509TrustManager in HttpClient contrib directory) + */ + +public class CustomX509TrustManager implements X509TrustManager +{ + private final X509TrustManager defaultTrustManager; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public CustomX509TrustManager(final X509TrustManager defaultTrustManager) { + super(); + if (defaultTrustManager == null) { + throw new IllegalArgumentException("Trust manager may not be null"); + } + this.defaultTrustManager = defaultTrustManager; + } + + /** + * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],String) + */ + public void checkClientTrusted(X509Certificate[] certificates,String authType) throws CertificateException { + if (certificates != null && log.isDebugEnabled()) { + for (int c = 0; c < certificates.length; c++) { + X509Certificate cert = certificates[c]; + log.debug(" Client certificate " + (c + 1) + ":"); + log.debug(" Subject DN: " + cert.getSubjectDN()); + log.debug(" Signature Algorithm: " + cert.getSigAlgName()); + log.debug(" Valid from: " + cert.getNotBefore() ); + log.debug(" Valid until: " + cert.getNotAfter()); + log.debug(" Issuer: " + cert.getIssuerDN()); + } + } +// try { +// defaultTrustManager.checkClientTrusted(certificates,authType); +// } catch (CertificateException e){ +// log.warn("Ignoring failed Client trust check: "+e.getMessage()); +// } + } + + /** + * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],String) + */ + public void checkServerTrusted(X509Certificate[] certificates,String authType) throws CertificateException { + if (certificates != null && log.isDebugEnabled()) { + for (int c = 0; c < certificates.length; c++) { + X509Certificate cert = certificates[c]; + log.debug(" Server certificate " + (c + 1) + ":"); + log.debug(" Subject DN: " + cert.getSubjectDN()); + log.debug(" Signature Algorithm: " + cert.getSigAlgName()); + log.debug(" Valid from: " + cert.getNotBefore() ); + log.debug(" Valid until: " + cert.getNotAfter()); + log.debug(" Issuer: " + cert.getIssuerDN()); + } + } +// try{ +// defaultTrustManager.checkServerTrusted(certificates,authType); +// } catch (CertificateException e){ +// log.warn("Ignoring failed Server trust check: "+e.getMessage()); +// } + } + + /** + * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() + */ + public X509Certificate[] getAcceptedIssuers() { + return this.defaultTrustManager.getAcceptedIssuers(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java b/ApacheJmeter/src/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java new file mode 100644 index 0000000..76587bb --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java @@ -0,0 +1,266 @@ +/* + * 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; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.security.GeneralSecurityException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.params.HttpConnectionParams; +import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Derived from EasySSLProtocolFactory + * + * Used by JsseSSLManager to set up the Commons HttpClient and Java https socket handling + */ + +public class HttpSSLProtocolSocketFactory + extends SSLSocketFactory // for java sockets + implements SecureProtocolSocketFactory { // for Commons Httpclient sockets + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final JsseSSLManager sslManager; + + private final int CPS; // Characters per second to emulate + + public HttpSSLProtocolSocketFactory(JsseSSLManager sslManager) { + this(sslManager, 0); + } + + public HttpSSLProtocolSocketFactory(JsseSSLManager sslManager, int cps) { + super(); + this.sslManager = sslManager; + CPS=cps; + } + + private static final String protocolList = + JMeterUtils.getPropDefault("https.socket.protocols", ""); // $NON-NLS-1$ $NON-NLS-2$ + + static { + if (protocolList.length()>0){ + log.info("Using protocol list: "+protocolList); + } + } + + private static final String[] protocols = protocolList.split(" "); // $NON-NLS-1$ + + private void setSocket(Socket socket){ + if (!(socket instanceof SSLSocket)) { + throw new IllegalArgumentException("Expected SSLSocket"); + } + SSLSocket sock = (SSLSocket) socket; + if (protocolList.length() > 0) { + try { + sock.setEnabledProtocols(protocols); + } catch (IllegalArgumentException e) { + log.warn("Could not set protocol list: " + protocolList + "."); + log.warn("Valid protocols are: " + join(sock.getSupportedProtocols())); + } + } + } + + private String join(String[] strings) { + StringBuilder sb = new StringBuilder(); + for (int i=0;i0) { + sb.append(" "); + } + sb.append(strings[i]); + } + return sb.toString(); + } + + private SSLSocketFactory getSSLSocketFactory() throws IOException { + try { + SSLContext sslContext = this.sslManager.getContext(); + return sslContext.getSocketFactory(); + } catch (GeneralSecurityException ex) { + throw new IOException(ex.getMessage()); + } + } + + /* + * Wraps the socket in a slow SSL socket if necessary + */ + private Socket wrapSocket(Socket sock){ + if (CPS>0) { + return new SlowSSLSocket((SSLSocket) sock, CPS); + } + return sock; + } + + /** + * Attempts to get a new socket connection to the given host within the given time limit. + * + * @param host the host name/IP + * @param port the port on the host + * @param localAddress the local host name/IP to bind the socket to + * @param localPort the port on the local machine + * @param params {@link HttpConnectionParams Http connection parameters} + * + * @return Socket a new socket + * + * @throws IOException if an I/O error occurs while creating the socket + * @throws UnknownHostException if the IP address of the host cannot be + * determined + */ + public Socket createSocket( + final String host, + final int port, + final InetAddress localAddress, + final int localPort, + final HttpConnectionParams params + ) throws IOException, UnknownHostException, ConnectTimeoutException { + if (params == null) { + throw new IllegalArgumentException("Parameters may not be null"); + } + int timeout = params.getConnectionTimeout(); + + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket socket; + if (timeout == 0) { + socket = sslfac.createSocket(host, port, localAddress, localPort); + } else { + socket = sslfac.createSocket(); + SocketAddress localaddr = new InetSocketAddress(localAddress, localPort); + SocketAddress remoteaddr = new InetSocketAddress(host, port); + socket.bind(localaddr); + socket.connect(remoteaddr, timeout); + } + setSocket(socket); + return wrapSocket(socket); + } + + /** + * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int) + */ + @Override + public Socket createSocket(String host, int port) + throws IOException, UnknownHostException { + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock = sslfac.createSocket( + host, + port + ); + setSocket(sock); + return wrapSocket(sock); + } + + /** + * @see javax.net.SocketFactory#createSocket() + */ + @Override + public Socket createSocket() throws IOException, UnknownHostException { + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock = sslfac.createSocket(); + setSocket(sock); + return wrapSocket(sock); + } + + /** + * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean) + */ + @Override + public Socket createSocket( + Socket socket, + String host, + int port, + boolean autoClose) + throws IOException, UnknownHostException { + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock = sslfac.createSocket( + socket, + host, + port, + autoClose + ); + setSocket(sock); + return wrapSocket(sock); + } + + /** + * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int) + */ + @Override + public Socket createSocket( + String host, + int port, + InetAddress clientHost, + int clientPort) + throws IOException, UnknownHostException { + + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock = sslfac.createSocket( + host, + port, + clientHost, + clientPort + ); + setSocket(sock); + return wrapSocket(sock); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock=sslfac.createSocket(host,port); + setSocket(sock); + return wrapSocket(sock); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + SSLSocketFactory sslfac = getSSLSocketFactory(); + Socket sock=sslfac.createSocket(address, port, localAddress, localPort); + setSocket(sock); + return wrapSocket(sock); + } + + @Override + public String[] getDefaultCipherSuites() { + try { + SSLSocketFactory sslfac = getSSLSocketFactory(); + return sslfac.getDefaultCipherSuites(); + } catch (IOException ex) { + return new String[] {}; + } + } + + @Override + public String[] getSupportedCipherSuites() { + try { + SSLSocketFactory sslfac = getSSLSocketFactory(); + return sslfac.getSupportedCipherSuites(); + } catch (IOException ex) { + return new String[] {}; + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/JMeterUtils.java b/ApacheJmeter/src/org/apache/jmeter/util/JMeterUtils.java new file mode 100644 index 0000000..c4dea68 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/JMeterUtils.java @@ -0,0 +1,1298 @@ +/* + * 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; + +import java.awt.Dimension; +import java.awt.HeadlessException; +import java.awt.event.ActionListener; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.net.InetAddress; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.Properties; +import java.util.Random; +import java.util.ResourceBundle; +import java.util.Vector; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.jorphan.test.UnitTestManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.apache.oro.text.MalformedCachePatternException; +import org.apache.oro.text.PatternCacheLRU; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.xml.sax.XMLReader; + +/** + * This class contains the static utility methods used by JMeter. + * + */ +public class JMeterUtils implements UnitTestManager { + private static final Logger log = LoggingManager.getLoggerForClass(); + + // Note: cannot use a static variable here, because that would be processed before the JMeter properties + // have been defined (Bug 52783) + private static class LazyPatternCacheHolder { + public static final PatternCacheLRU INSTANCE = new PatternCacheLRU( + getPropDefault("oro.patterncache.size",1000), // $NON-NLS-1$ + new Perl5Compiler()); + } + + private static final String EXPERT_MODE_PROPERTY = "jmeter.expertMode"; // $NON-NLS-1$ + + private static final String ENGLISH_LANGUAGE = Locale.ENGLISH.getLanguage(); + + private static volatile Properties appProperties; + + private static final Vector localeChangeListeners = new Vector(); + + private static volatile Locale locale; + + private static volatile ResourceBundle resources; + + // What host am I running on? + + //@GuardedBy("this") + private static String localHostIP = null; + //@GuardedBy("this") + private static String localHostName = null; + //@GuardedBy("this") + private static String localHostFullName = null; + + private static volatile boolean ignoreResorces = false; // Special flag for use in debugging resources + + private static final ThreadLocal localMatcher = new ThreadLocal() { + @Override + protected Perl5Matcher initialValue() { + return new Perl5Matcher(); + } + }; + + // Provide Random numbers to whomever wants one + private static final Random rand = new Random(); + + /** + * Gets Perl5Matcher for this thread. + */ + public static Perl5Matcher getMatcher() { + return localMatcher.get(); + } + + /** + * This method is used by the init method to load the property file that may + * even reside in the user space, or in the classpath under + * org.apache.jmeter.jmeter.properties. + * + * The method also initialises logging and sets up the default Locale + * + * TODO - perhaps remove? + * [still used + * + * @param file + * the file to load + * @return the Properties from the file + * @see #getJMeterProperties() + * @see #loadJMeterProperties(String) + * @see #initLogging() + * @see #initLocale() + */ + public static Properties getProperties(String file) { + loadJMeterProperties(file); + initLogging(); + initLocale(); + return appProperties; + } + + /** + * Initialise JMeter logging + */ + public static void initLogging() { + LoggingManager.initializeLogging(appProperties); + } + + /** + * Initialise the JMeter Locale + */ + public static void initLocale() { + String loc = appProperties.getProperty("language"); // $NON-NLS-1$ + if (loc != null) { + String []parts = JOrphanUtils.split(loc,"_");// $NON-NLS-1$ + if (parts.length==2) { + setLocale(new Locale(parts[0], parts[1])); + } else { + setLocale(new Locale(loc, "")); // $NON-NLS-1$ + } + + } else { + setLocale(Locale.getDefault()); + } + } + + + /** + * Load the JMeter properties file; if not found, then + * default to "org/apache/jmeter/jmeter.properties" from the classpath + * + * c.f. loadProperties + * + */ + public static void loadJMeterProperties(String file) { + Properties p = new Properties(System.getProperties()); + InputStream is = null; + try { + File f = new File(file); + is = new FileInputStream(f); + p.load(is); + } catch (IOException e) { + try { + is = + ClassLoader.getSystemResourceAsStream("org/apache/jmeter/jmeter.properties"); // $NON-NLS-1$ + if (is == null) { + throw new RuntimeException("Could not read JMeter properties file"); + } + p.load(is); + } catch (IOException ex) { + // JMeter.fail("Could not read internal resource. " + + // "Archive is broken."); + } + } finally { + JOrphanUtils.closeQuietly(is); + } + appProperties = p; + } + + /** + * This method loads a property file that may reside in the user space, or + * in the classpath + * + * @param file + * the file to load + * @return the Properties from the file, may be null (e.g. file not found) + */ + public static Properties loadProperties(String file) { + return loadProperties(file, null); + } + + /** + * This method loads a property file that may reside in the user space, or + * in the classpath + * + * @param file + * the file to load + * @param defaultProps a set of default properties + * @return the Properties from the file; if it could not be processed, the defaultProps are returned. + */ + public static Properties loadProperties(String file, Properties defaultProps) { + Properties p = new Properties(defaultProps); + InputStream is = null; + try { + File f = new File(file); + is = new FileInputStream(f); + p.load(is); + } catch (IOException e) { + try { + final URL resource = JMeterUtils.class.getClassLoader().getResource(file); + if (resource == null) { + log.warn("Cannot find " + file); + return defaultProps; + } + is = resource.openStream(); + if (is == null) { + log.warn("Cannot open " + file); + return defaultProps; + } + p.load(is); + } catch (IOException ex) { + log.warn("Error reading " + file + " " + ex.toString()); + return defaultProps; + } + } finally { + JOrphanUtils.closeQuietly(is); + } + return p; + } + + public static PatternCacheLRU getPatternCache() { + return LazyPatternCacheHolder.INSTANCE; + } + + /** + * Get a compiled expression from the pattern cache (READ_ONLY). + * + * @param expression + * @return compiled pattern + * + * @throws MalformedCachePatternException (Runtime) + * This should be caught for expressions that may vary (e.g. user input) + * + */ + public static Pattern getPattern(String expression) throws MalformedCachePatternException { + return getPattern(expression, Perl5Compiler.READ_ONLY_MASK); + } + + /** + * Get a compiled expression from the pattern cache. + * + * @param expression RE + * @param options e.g. READ_ONLY_MASK + * @return compiled pattern + * + * @throws MalformedCachePatternException (Runtime) + * This should be caught for expressions that may vary (e.g. user input) + * + */ + public static Pattern getPattern(String expression, int options) throws MalformedCachePatternException { + return LazyPatternCacheHolder.INSTANCE.getPattern(expression, options); + } + + public void initializeProperties(String file) { + System.out.println("Initializing Properties: " + file); + getProperties(file); + } + + /** + * Convenience method for + * {@link ClassFinder#findClassesThatExtend(String[], Class[], boolean)} + * with the option to include inner classes in the search set to false + * and the path list is derived from JMeterUtils.getSearchPaths(). + * + * @param superClass - single class to search for + * @return List of Strings containing discovered class names. + */ + public static List findClassesThatExtend(Class superClass) + throws IOException { + return ClassFinder.findClassesThatExtend(getSearchPaths(), new Class[]{superClass}, false); + } + + /** + * Generate a list of paths to search. + * The output array always starts with + * JMETER_HOME/lib/ext + * and is followed by any paths obtained from the "search_paths" JMeter property. + * + * @return array of path strings + */ + public static String[] getSearchPaths() { + String p = JMeterUtils.getPropDefault("search_paths", null); // $NON-NLS-1$ + String[] result = new String[1]; + + if (p != null) { + String[] paths = p.split(";"); // $NON-NLS-1$ + result = new String[paths.length + 1]; + for (int i = 1; i < result.length; i++) { + result[i] = paths[i - 1]; + } + } + result[0] = getJMeterHome() + "/lib/ext"; // $NON-NLS-1$ + return result; + } + + /** + * Provide random numbers + * + * @param r - + * the upper bound (exclusive) + */ + public static int getRandomInt(int r) { + return rand.nextInt(r); + } + + /** + * Changes the current locale: re-reads resource strings and notifies + * listeners. + * + * @param loc - + * new locale + */ + public static void setLocale(Locale loc) { + log.info("Setting Locale to " + loc.toString()); + /* + * See bug 29920. getBundle() defaults to the property file for the + * default Locale before it defaults to the base property file, so we + * need to change the default Locale to ensure the base property file is + * found. + */ + Locale def = null; + boolean isDefault = false; // Are we the default language? + if (loc.getLanguage().equals(ENGLISH_LANGUAGE)) { + isDefault = true; + def = Locale.getDefault(); + // Don't change locale from en_GB to en + if (!def.getLanguage().equals(ENGLISH_LANGUAGE)) { + Locale.setDefault(Locale.ENGLISH); + } else { + def = null; // no need to reset Locale + } + } + if (loc.toString().equals("ignoreResources")){ // $NON-NLS-1$ + log.warn("Resource bundles will be ignored"); + ignoreResorces = true; + // Keep existing settings + } else { + ignoreResorces = false; + ResourceBundle resBund = ResourceBundle.getBundle("org.apache.jmeter.resources.messages", loc); // $NON-NLS-1$ + resources = resBund; + locale = loc; + final Locale resBundLocale = resBund.getLocale(); + if (isDefault || resBundLocale.equals(loc)) {// language change worked + // Check if we at least found the correct language: + } else if (resBundLocale.getLanguage().equals(loc.getLanguage())) { + log.info("Could not find resources for '"+loc.toString()+"', using '"+resBundLocale.toString()+"'"); + } else { + log.error("Could not find resources for '"+loc.toString()+"'"); + } + } + notifyLocaleChangeListeners(); + /* + * Reset Locale if necessary so other locales are properly handled + */ + if (def != null) { + Locale.setDefault(def); + } + } + + /** + * Gets the current locale. + * + * @return current locale + */ + public static Locale getLocale() { + return locale; + } + + public static void addLocaleChangeListener(LocaleChangeListener listener) { + localeChangeListeners.add(listener); + } + + public static void removeLocaleChangeListener(LocaleChangeListener listener) { + localeChangeListeners.remove(listener); + } + + /** + * Notify all listeners interested in locale changes. + * + */ + private static void notifyLocaleChangeListeners() { + LocaleChangeEvent event = new LocaleChangeEvent(JMeterUtils.class, locale); + @SuppressWarnings("unchecked") // clone will produce correct type + // TODO but why do we need to clone the list? + Vector listeners = (Vector) localeChangeListeners.clone(); + for (LocaleChangeListener listener : listeners) { + listener.localeChanged(event); + } + } + + /** + * Gets the resource string for this key. + * + * If the resource is not found, a warning is logged + * + * @param key + * the key in the resource file + * @return the resource string if the key is found; otherwise, return + * "[res_key="+key+"]" + */ + public static String getResString(String key) { + return getResStringDefault(key, RES_KEY_PFX + key + "]"); // $NON-NLS-1$ + } + + /** + * Gets the resource string for this key in Locale. + * + * If the resource is not found, a warning is logged + * + * @param key + * the key in the resource file + * @param forcedLocale Force a particular locale + * @return the resource string if the key is found; otherwise, return + * "[res_key="+key+"]" + * @since 2.7 + */ + public static String getResString(String key, Locale forcedLocale) { + return getResStringDefault(key, RES_KEY_PFX + key + "]", // $NON-NLS-1$ + forcedLocale); + } + + public static final String RES_KEY_PFX = "[res_key="; // $NON-NLS-1$ + + /** + * Gets the resource string for this key. + * + * If the resource is not found, a warning is logged + * + * @param key + * the key in the resource file + * @param defaultValue - + * the default value + * + * @return the resource string if the key is found; otherwise, return the + * default + * @deprecated Only intended for use in development; use + * getResString(String) normally + */ + @Deprecated + public static String getResString(String key, String defaultValue) { + return getResStringDefault(key, defaultValue); + } + + /* + * Helper method to do the actual work of fetching resources; allows + * getResString(S,S) to be deprecated without affecting getResString(S); + */ + private static String getResStringDefault(String key, String defaultValue) { + return getResStringDefault(key, defaultValue, null); + } + /* + * Helper method to do the actual work of fetching resources; allows + * getResString(S,S) to be deprecated without affecting getResString(S); + */ + private static String getResStringDefault(String key, String defaultValue, Locale forcedLocale) { + if (key == null) { + return null; + } + // Resource keys cannot contain spaces, and are forced to lower case + String resKey = key.replace(' ', '_'); // $NON-NLS-1$ // $NON-NLS-2$ + resKey = resKey.toLowerCase(java.util.Locale.ENGLISH); + String resString = null; + try { + ResourceBundle bundle = resources; + if(forcedLocale != null) { + bundle = ResourceBundle.getBundle("org.apache.jmeter.resources.messages", forcedLocale); // $NON-NLS-1$ + } + resString = bundle.getString(resKey); + if (ignoreResorces ){ // Special mode for debugging resource handling + return "["+key+"]"; + } + } catch (MissingResourceException mre) { + if (ignoreResorces ){ // Special mode for debugging resource handling + return "[?"+key+"?]"; + } + log.warn("ERROR! Resource string not found: [" + resKey + "]", mre); + resString = defaultValue; + } + return resString; + } + + /** + * To get I18N label from properties file + * + * @param key + * in messages.properties + * @return I18N label without (if exists) last colon ':' and spaces + */ + public static String getParsedLabel(String key) { + String value = JMeterUtils.getResString(key); + return value.replaceFirst("(?m)\\s*?:\\s*$", ""); // $NON-NLS-1$ $NON-NLS-2$ + } + + /** + * Get the locale name as a resource. + * Does not log an error if the resource does not exist. + * This is needed to support additional locales, as they won't be in existing messages files. + * + * @param locale name + * @return the locale display name as defined in the current Locale or the original string if not present + */ + public static String getLocaleString(String locale){ + // All keys in messages.properties are lowercase (historical reasons?) + String resKey = locale.toLowerCase(java.util.Locale.ENGLISH); + try { + return resources.getString(resKey); + } catch (MissingResourceException e) { + } + return locale; + } + /** + * This gets the currently defined appProperties. It can only be called + * after the {@link #getProperties(String)} or {@link #loadJMeterProperties(String)} + * method has been called. + * + * @return The JMeterProperties value, + * may be null if {@link #loadJMeterProperties(String)} has not been called + * @see #getProperties(String) + * @see #loadJMeterProperties(String) + */ + public static Properties getJMeterProperties() { + return appProperties; + } + + /** + * This looks for the requested image in the classpath under + * org.apache.jmeter.images. + * + * @param name + * Description of Parameter + * @return The Image value + */ + public static ImageIcon getImage(String name) { + try { + return new ImageIcon(JMeterUtils.class.getClassLoader().getResource( + "org/apache/jmeter/images/" + name.trim())); // $NON-NLS-1$ + } catch (NullPointerException e) { + log.warn("no icon for " + name); + return null; + } catch (NoClassDefFoundError e) {// Can be returned by headless hosts + log.info("no icon for " + name + " " + e.getMessage()); + return null; + } catch (InternalError e) {// Can be returned by headless hosts + log.info("no icon for " + name + " " + e.getMessage()); + return null; + } + } + + /** + * This looks for the requested image in the classpath under + * org.apache.jmeter.images. , and also sets the description + * of the image, which is useful if the icon is going to be placed + * on the clipboard. + * + * @param name + * the name of the image + * @param description + * the description of the image + * @return The Image value + */ + public static ImageIcon getImage(String name, String description) { + ImageIcon icon = getImage(name); + if(icon != null) { + icon.setDescription(description); + } + return icon; + } + + public static String getResourceFileAsText(String name) { + BufferedReader fileReader = null; + try { + String lineEnd = System.getProperty("line.separator"); // $NON-NLS-1$ + fileReader = new BufferedReader(new InputStreamReader(JMeterUtils.class.getClassLoader() + .getResourceAsStream(name))); + StringBuilder text = new StringBuilder(); + String line = "NOTNULL"; // $NON-NLS-1$ + while (line != null) { + line = fileReader.readLine(); + if (line != null) { + text.append(line); + text.append(lineEnd); + } + } + // Done by finally block: fileReader.close(); + return text.toString(); + } catch (NullPointerException e) // Cannot find file + { + return ""; // $NON-NLS-1$ + } catch (IOException e) { + return ""; // $NON-NLS-1$ + } finally { + IOUtils.closeQuietly(fileReader); + } + } + + /** + * Creates the vector of Timers plugins. + * + * @param properties + * Description of Parameter + * @return The Timers value + */ + public static Vector getTimers(Properties properties) { + return instantiate(getVector(properties, "timer."), // $NON-NLS-1$ + "org.apache.jmeter.timers.Timer"); // $NON-NLS-1$ + } + + /** + * Creates the vector of visualizer plugins. + * + * @param properties + * Description of Parameter + * @return The Visualizers value + */ + public static Vector getVisualizers(Properties properties) { + return instantiate(getVector(properties, "visualizer."), // $NON-NLS-1$ + "org.apache.jmeter.visualizers.Visualizer"); // $NON-NLS-1$ + } + + /** + * Creates a vector of SampleController plugins. + * + * @param properties + * The properties with information about the samplers + * @return The Controllers value + */ + // TODO - does not appear to be called directly + public static Vector getControllers(Properties properties) { + String name = "controller."; // $NON-NLS-1$ + Vector v = new Vector(); + Enumeration names = properties.keys(); + while (names.hasMoreElements()) { + String prop = (String) names.nextElement(); + if (prop.startsWith(name)) { + Object o = instantiate(properties.getProperty(prop), + "org.apache.jmeter.control.SamplerController"); // $NON-NLS-1$ + v.addElement(o); + } + } + return v; + } + + /** + * Create a string of class names for a particular SamplerController + * + * @param properties + * The properties with info about the samples. + * @param name + * The name of the sampler controller. + * @return The TestSamples value + */ + public static String[] getTestSamples(Properties properties, String name) { + Vector vector = getVector(properties, name + ".testsample"); // $NON-NLS-1$ + return vector.toArray(new String[vector.size()]); + } + + /** + * Create an instance of an org.xml.sax.Parser based on the default props. + * + * @return The XMLParser value + */ + // TODO only called by UserParameterXMLParser.getXMLParameters which is a deprecated class + public static XMLReader getXMLParser() { + XMLReader reader = null; + final String parserName = getPropDefault("xml.parser", // $NON-NLS-1$ + "org.apache.xerces.parsers.SAXParser"); // $NON-NLS-1$ + try { + reader = (XMLReader) instantiate(parserName, + "org.xml.sax.XMLReader"); // $NON-NLS-1$ + // reader = xmlFactory.newSAXParser().getXMLReader(); + } catch (Exception e) { + reader = (XMLReader) instantiate(parserName, // $NON-NLS-1$ + "org.xml.sax.XMLReader"); // $NON-NLS-1$ + } + return reader; + } + + /** + * Creates the vector of alias strings. + * + * @param properties + * @return The Alias value + */ + public static Hashtable getAlias(Properties properties) { + return getHashtable(properties, "alias."); // $NON-NLS-1$ + } + + /** + * Creates a vector of strings for all the properties that start with a + * common prefix. + * + * @param properties + * Description of Parameter + * @param name + * Description of Parameter + * @return The Vector value + */ + public static Vector getVector(Properties properties, String name) { + Vector v = new Vector(); + Enumeration names = properties.keys(); + while (names.hasMoreElements()) { + String prop = (String) names.nextElement(); + if (prop.startsWith(name)) { + v.addElement(properties.getProperty(prop)); + } + } + return v; + } + + /** + * Creates a table of strings for all the properties that start with a + * common prefix. + * + * @param properties input to search + * @param prefix to match against properties + * @return a Hashtable where the keys are the original keys with the prefix removed + */ + public static Hashtable getHashtable(Properties properties, String prefix) { + Hashtable t = new Hashtable(); + Enumeration names = properties.keys(); + final int length = prefix.length(); + while (names.hasMoreElements()) { + String prop = (String) names.nextElement(); + if (prop.startsWith(prefix)) { + t.put(prop.substring(length), properties.getProperty(prop)); + } + } + return t; + } + + /** + * Get a int value with default if not present. + * + * @param propName + * the name of the property. + * @param defaultVal + * the default value. + * @return The PropDefault value + */ + public static int getPropDefault(String propName, int defaultVal) { + int ans; + try { + ans = (Integer.valueOf(appProperties.getProperty(propName, Integer.toString(defaultVal)).trim())) + .intValue(); + } catch (Exception e) { + ans = defaultVal; + } + return ans; + } + + /** + * Get a boolean value with default if not present. + * + * @param propName + * the name of the property. + * @param defaultVal + * the default value. + * @return The PropDefault value + */ + public static boolean getPropDefault(String propName, boolean defaultVal) { + boolean ans; + try { + String strVal = appProperties.getProperty(propName, Boolean.toString(defaultVal)).trim(); + if (strVal.equalsIgnoreCase("true") || strVal.equalsIgnoreCase("t")) { // $NON-NLS-1$ // $NON-NLS-2$ + ans = true; + } else if (strVal.equalsIgnoreCase("false") || strVal.equalsIgnoreCase("f")) { // $NON-NLS-1$ // $NON-NLS-2$ + ans = false; + } else { + ans = ((Integer.valueOf(strVal)).intValue() == 1); + } + } catch (Exception e) { + ans = defaultVal; + } + return ans; + } + + /** + * Get a long value with default if not present. + * + * @param propName + * the name of the property. + * @param defaultVal + * the default value. + * @return The PropDefault value + */ + public static long getPropDefault(String propName, long defaultVal) { + long ans; + try { + ans = (Long.valueOf(appProperties.getProperty(propName, Long.toString(defaultVal)).trim())).longValue(); + } catch (Exception e) { + ans = defaultVal; + } + return ans; + } + + /** + * Get a String value with default if not present. + * + * @param propName + * the name of the property. + * @param defaultVal + * the default value. + * @return The PropDefault value + */ + public static String getPropDefault(String propName, String defaultVal) { + String ans = defaultVal; + try { + String value = appProperties.getProperty(propName, defaultVal); + if(value != null) { + ans = value.trim(); + } + } catch (Exception e) { + ans = defaultVal; + } + return ans; + } + + /** + * Get the value of a JMeter property. + * + * @param propName + * the name of the property. + * @return the value of the JMeter property, or null if not defined + */ + public static String getProperty(String propName) { + String ans = null; + try { + ans = appProperties.getProperty(propName); + } catch (Exception e) { + ans = null; + } + return ans; + } + + /** + * Set a String value + * + * @param propName + * the name of the property. + * @param propValue + * the value of the property + * @return the previous value of the property + */ + public static Object setProperty(String propName, String propValue) { + return appProperties.setProperty(propName, propValue); + } + + /** + * Sets the selection of the JComboBox to the Object 'name' from the list in + * namVec. + * NOTUSED? + */ + public static void selJComboBoxItem(Properties properties, JComboBox combo, Vector namVec, String name) { + int idx = namVec.indexOf(name); + combo.setSelectedIndex(idx); + // Redisplay. + combo.updateUI(); + return; + } + + /** + * Instatiate an object and guarantee its class. + * + * @param className + * The name of the class to instantiate. + * @param impls + * The name of the class it must be an instance of + * @return an instance of the class, or null if instantiation failed or the class did not implement/extend as required + */ + // TODO probably not needed + public static Object instantiate(String className, String impls) { + if (className != null) { + className = className.trim(); + } + + if (impls != null) { + impls = impls.trim(); + } + + try { + Class c = Class.forName(impls); + try { + Class o = Class.forName(className); + Object res = o.newInstance(); + if (c.isInstance(res)) { + return res; + } + throw new IllegalArgumentException(className + " is not an instance of " + impls); + } catch (ClassNotFoundException e) { + log.error("Error loading class " + className + ": class is not found"); + } catch (IllegalAccessException e) { + log.error("Error loading class " + className + ": does not have access"); + } catch (InstantiationException e) { + log.error("Error loading class " + className + ": could not instantiate"); + } catch (NoClassDefFoundError e) { + log.error("Error loading class " + className + ": couldn't find class " + e.getMessage()); + } + } catch (ClassNotFoundException e) { + log.error("Error loading class " + impls + ": was not found."); + } + return null; + } + + /** + * Instantiate a vector of classes + * + * @param v + * Description of Parameter + * @param className + * Description of Parameter + * @return Description of the Returned Value + */ + public static Vector instantiate(Vector v, String className) { + Vector i = new Vector(); + try { + Class c = Class.forName(className); + Enumeration elements = v.elements(); + while (elements.hasMoreElements()) { + String name = elements.nextElement(); + try { + Object o = Class.forName(name).newInstance(); + if (c.isInstance(o)) { + i.addElement(o); + } + } catch (ClassNotFoundException e) { + log.error("Error loading class " + name + ": class is not found"); + } catch (IllegalAccessException e) { + log.error("Error loading class " + name + ": does not have access"); + } catch (InstantiationException e) { + log.error("Error loading class " + name + ": could not instantiate"); + } catch (NoClassDefFoundError e) { + log.error("Error loading class " + name + ": couldn't find class " + e.getMessage()); + } + } + } catch (ClassNotFoundException e) { + log.error("Error loading class " + className + ": class is not found"); + } + return i; + } + + /** + * Create a button with the netscape style + * + * @param name + * Description of Parameter + * @param listener + * Description of Parameter + * @return Description of the Returned Value + */ + public static JButton createButton(String name, ActionListener listener) { + JButton button = new JButton(getImage(name + ".on.gif")); // $NON-NLS-1$ + button.setDisabledIcon(getImage(name + ".off.gif")); // $NON-NLS-1$ + button.setRolloverIcon(getImage(name + ".over.gif")); // $NON-NLS-1$ + button.setPressedIcon(getImage(name + ".down.gif")); // $NON-NLS-1$ + button.setActionCommand(name); + button.addActionListener(listener); + button.setRolloverEnabled(true); + button.setFocusPainted(false); + button.setBorderPainted(false); + button.setOpaque(false); + button.setPreferredSize(new Dimension(24, 24)); + return button; + } + + /** + * Create a button with the netscape style + * + * @param name + * Description of Parameter + * @param listener + * Description of Parameter + * @return Description of the Returned Value + */ + public static JButton createSimpleButton(String name, ActionListener listener) { + JButton button = new JButton(getImage(name + ".gif")); // $NON-NLS-1$ + button.setActionCommand(name); + button.addActionListener(listener); + button.setFocusPainted(false); + button.setBorderPainted(false); + button.setOpaque(false); + button.setPreferredSize(new Dimension(25, 25)); + return button; + } + + + /** + * Report an error through a dialog box. + * Title defaults to "error_title" resource string + * @param errorMsg - the error message. + */ + public static void reportErrorToUser(String errorMsg) { + reportErrorToUser(errorMsg, JMeterUtils.getResString("error_title")); // $NON-NLS-1$ + } + + /** + * Report an error through a dialog box. + * + * @param errorMsg - the error message. + * @param titleMsg - title string + */ + public static void reportErrorToUser(String errorMsg, String titleMsg) { + if (errorMsg == null) { + errorMsg = "Unknown error - see log file"; + log.warn("Unknown error", new Throwable("errorMsg == null")); + } + GuiPackage instance = GuiPackage.getInstance(); + if (instance == null) { + System.out.println(errorMsg); + return; // Done + } + try { + JOptionPane.showMessageDialog(instance.getMainFrame(), + errorMsg, + titleMsg, + JOptionPane.ERROR_MESSAGE); + } catch (HeadlessException e) { + log.warn("reportErrorToUser(\"" + errorMsg + "\") caused", e); + } + } + + /** + * Finds a string in an array of strings and returns the + * + * @param array + * Array of strings. + * @param value + * String to compare to array values. + * @return Index of value in array, or -1 if not in array. + */ + //TODO - move to JOrphanUtils? + public static int findInArray(String[] array, String value) { + int count = -1; + int index = -1; + if (array != null && value != null) { + while (++count < array.length) { + if (array[count] != null && array[count].equals(value)) { + index = count; + break; + } + } + } + return index; + } + + /** + * Takes an array of strings and a tokenizer character, and returns a string + * of all the strings concatenated with the tokenizer string in between each + * one. + * + * @param splittee + * Array of Objects to be concatenated. + * @param splitChar + * Object to unsplit the strings with. + * @return Array of all the tokens. + */ + //TODO - move to JOrphanUtils? + public static String unsplit(Object[] splittee, Object splitChar) { + StringBuilder retVal = new StringBuilder(); + int count = -1; + while (++count < splittee.length) { + if (splittee[count] != null) { + retVal.append(splittee[count]); + } + if (count + 1 < splittee.length && splittee[count + 1] != null) { + retVal.append(splitChar); + } + } + return retVal.toString(); + } + + // End Method + + /** + * Takes an array of strings and a tokenizer character, and returns a string + * of all the strings concatenated with the tokenizer string in between each + * one. + * + * @param splittee + * Array of Objects to be concatenated. + * @param splitChar + * Object to unsplit the strings with. + * @param def + * Default value to replace null values in array. + * @return Array of all the tokens. + */ + //TODO - move to JOrphanUtils? + public static String unsplit(Object[] splittee, Object splitChar, String def) { + StringBuilder retVal = new StringBuilder(); + int count = -1; + while (++count < splittee.length) { + if (splittee[count] != null) { + retVal.append(splittee[count]); + } else { + retVal.append(def); + } + if (count + 1 < splittee.length) { + retVal.append(splitChar); + } + } + return retVal.toString(); + } + + /** + * Get the JMeter home directory - does not include the trailing separator. + * + * @return the home directory + */ + public static String getJMeterHome() { + return jmDir; + } + + /** + * Get the JMeter bin directory - does not include the trailing separator. + * + * @return the bin directory + */ + public static String getJMeterBinDir() { + return jmBin; + } + + public static void setJMeterHome(String home) { + jmDir = home; + jmBin = jmDir + File.separator + "bin"; // $NON-NLS-1$ + } + + // TODO needs to be synch? Probably not changed after threads have started + private static String jmDir; // JMeter Home directory (excludes trailing separator) + private static String jmBin; // JMeter bin directory (excludes trailing separator) + + + /** + * Gets the JMeter Version. + * + * @return the JMeter version string + */ + public static String getJMeterVersion() { + return JMeterVersion.getVERSION(); + } + + /** + * Gets the JMeter copyright. + * + * @return the JMeter copyright string + */ + public static String getJMeterCopyright() { + return JMeterVersion.getCopyRight(); + } + + /** + * Determine whether we are in 'expert' mode. Certain features may be hidden + * from user's view unless in expert mode. + * + * @return true iif we're in expert mode + */ + public static boolean isExpertMode() { + return JMeterUtils.getPropDefault(EXPERT_MODE_PROPERTY, false); + } + + /** + * Find a file in the current directory or in the JMeter bin directory. + * + * @param fileName + * @return File object + */ + public static File findFile(String fileName){ + File f =new File(fileName); + if (!f.exists()){ + f=new File(getJMeterBinDir(),fileName); + } + return f; + } + + /** + * Returns the cached result from calling + * InetAddress.getLocalHost().getHostAddress() + * + * @return String representation of local IP address + */ + public static synchronized String getLocalHostIP(){ + if (localHostIP == null) { + getLocalHostDetails(); + } + return localHostIP; + } + + /** + * Returns the cached result from calling + * InetAddress.getLocalHost().getHostName() + * + * @return local host name + */ + public static synchronized String getLocalHostName(){ + if (localHostName == null) { + getLocalHostDetails(); + } + return localHostName; + } + + /** + * Returns the cached result from calling + * InetAddress.getLocalHost().getCanonicalHostName() + * + * @return local host name in canonical form + */ + public static synchronized String getLocalHostFullName(){ + if (localHostFullName == null) { + getLocalHostDetails(); + } + return localHostFullName; + } + + private static void getLocalHostDetails(){ + InetAddress localHost=null; + try { + localHost = InetAddress.getLocalHost(); + } catch (UnknownHostException e1) { + log.error("Unable to get local host IP address."); + return; // TODO - perhaps this should be a fatal error? + } + localHostIP=localHost.getHostAddress(); + localHostName=localHost.getHostName(); + localHostFullName=localHost.getCanonicalHostName(); + } + + /** + * Split line into name/value pairs and remove colon ':' + * + * @param headers + * multi-line string headers + * @return a map name/value for each header + */ + public static LinkedHashMap parseHeaders(String headers) { + LinkedHashMap linkedHeaders = new LinkedHashMap(); + String[] list = headers.split("\n"); // $NON-NLS-1$ + for (String header : list) { + int colon = header.indexOf(':'); // $NON-NLS-1$ + if (colon <= 0) { + linkedHeaders.put(header, ""); // Empty value // $NON-NLS-1$ + } else { + linkedHeaders.put(header.substring(0, colon).trim(), header + .substring(colon + 1).trim()); + } + } + return linkedHeaders; + } + + /** + * Run the runnable in AWT Thread if current thread is not AWT thread + * otherwise runs call {@link SwingUtilities#invokeAndWait(Runnable)} + * @param runnable {@link Runnable} + */ + public static final void runSafe(Runnable runnable) { + if(SwingUtilities.isEventDispatchThread()) { + runnable.run(); + } else { + try { + SwingUtilities.invokeAndWait(runnable); + } catch (InterruptedException e) { + log.warn("Interrupted in thread "+Thread.currentThread().getName(), e); + } catch (InvocationTargetException e) { + throw new Error(e); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/JMeterVersion.java b/ApacheJmeter/src/org/apache/jmeter/util/JMeterVersion.java new file mode 100644 index 0000000..4e4328c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/JMeterVersion.java @@ -0,0 +1,106 @@ +/* + * 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. + * + */ + +/* + * Created on 02-Oct-2003 + * + * This class defines the JMeter version only (moved from JMeterUtils) + * + * Version changes no longer change the JMeterUtils source file + * - easier to spot when JMeterUtils really changes + * - much smaller to download when the version changes + * + */ +package org.apache.jmeter.util; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; + +/** + * Utility class to define the JMeter Version string + * + */ +public class JMeterVersion { + + /* + * + * The string is made private so the compiler can't propagate it into + * JMeterUtils. (Java compilers may make copies of final variables) + * + * This ensures that JMeterUtils always gets the correct + * version, even if JMeterUtils is not re-compiled during the build. + */ + private static final String VERSION = "2.7"; + + private static final String IMPLEMENTATION; + + // Same applies to copyright string + private static final String COPYRIGHT = "Copyright (c) 1998-2012 The Apache Software Foundation"; + + static { + String impl=null; + final Class myClass = JMeterVersion.class; + // This assumes that the JMV treats a class file as a resource (not all do). + URL resource = myClass.getResource("JMeterVersion.class"); + // For example: + // jar:file:/JMeter/lib/ext/ApacheJMeter_core.jar!/org/apache/jmeter/util/JMeterVersion.class + // or if using an IDE + // file:/workspaces/JMeter/build/core/org/apache/jmeter/util/JMeterVersion.class + + + try { + // Convert to URL for manifest + String url = resource.toString().replaceFirst("!/.+", "!/META-INF/MANIFEST.MF"); + resource=new URL(url); + InputStream inputStream = resource.openStream(); + if (inputStream != null) { + Properties props = new Properties(); + try { + props.load(inputStream); + impl = props.getProperty("Implementation-Version"); + } finally { + IOUtils.closeQuietly(inputStream); + } + } + } catch (IOException ioe) { + // Ignored + } + if (impl == null) { + IMPLEMENTATION = VERSION; // default to plain version + } else { + IMPLEMENTATION = impl; + } + } + + private JMeterVersion() // Not instantiable + { + super(); + } + + static final String getVERSION() { + return IMPLEMENTATION; + } + + public static String getCopyRight() { + return COPYRIGHT; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/JSR223BeanInfoSupport.java b/ApacheJmeter/src/org/apache/jmeter/util/JSR223BeanInfoSupport.java new file mode 100644 index 0000000..5b03805 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/JSR223BeanInfoSupport.java @@ -0,0 +1,55 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.script.ScriptEngineFactory; +import javax.script.ScriptEngineManager; + +/** + * Parent class to handle common GUI design for JSR223 test elements + */ +public abstract class JSR223BeanInfoSupport extends ScriptingBeanInfoSupport { + + private final static String[] LANGUAGE_TAGS; + + static { + List shortNames = new ArrayList(); + ScriptEngineManager sem = new ScriptEngineManager(); + @SuppressWarnings("unchecked") // can be dropped in Java 1.6 + final List engineFactories = sem.getEngineFactories(); + for(ScriptEngineFactory fact : engineFactories){ + @SuppressWarnings("unchecked") // can be dropped in Java 1.6 + List names = fact.getNames(); + for(String shorName : names) { + shortNames.add(shorName); + } + } + LANGUAGE_TAGS = shortNames.toArray(new String[shortNames.size()]); + Arrays.sort(LANGUAGE_TAGS); + } + + protected JSR223BeanInfoSupport(Class beanClass) { + super(beanClass, LANGUAGE_TAGS); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/JSR223TestElement.java b/ApacheJmeter/src/org/apache/jmeter/util/JSR223TestElement.java new file mode 100644 index 0000000..536c0b9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/JSR223TestElement.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Serializable; +import java.util.Properties; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import org.apache.commons.io.IOUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public abstract class JSR223TestElement extends AbstractTestElement + implements Serializable, Cloneable +{ + private static final long serialVersionUID = 233L; + + //++ For TestBean implementations only + private String parameters; // passed to file or script + + private String filename; // file to source (overrides script) + + private String script; // script (if file not provided) + + private String scriptLanguage; // JSR223 language to use + //-- For TestBean implementations only + + public JSR223TestElement() { + super(); + init(); + } + + private void init() { + parameters=""; // ensure variables are not null + filename=""; + script=""; + scriptLanguage=""; + } + + protected Object readResolve() { + init(); + return this; + } + + @Override + public Object clone() { + JSR223TestElement o = (JSR223TestElement) super.clone(); + o.init(); + return o; + } + + protected ScriptEngineManager getManager() { + ScriptEngineManager sem = new ScriptEngineManager(); + initManager(sem); + return sem; + } + + protected void initManager(ScriptEngineManager sem) { + final String label = getName(); + final String fileName = getFilename(); + final String scriptParameters = getParameters(); + // Use actual class name for log + final Logger logger = LoggingManager.getLoggerForShortName(getClass().getName()); + + sem.put("log", logger); + sem.put("Label", label); + sem.put("FileName", fileName); + sem.put("Parameters", scriptParameters); + String [] args=JOrphanUtils.split(scriptParameters, " ");//$NON-NLS-1$ + sem.put("args", args); + // Add variables for access to context and variables + JMeterContext jmctx = JMeterContextService.getContext(); + sem.put("ctx", jmctx); + JMeterVariables vars = jmctx.getVariables(); + sem.put("vars", vars); + Properties props = JMeterUtils.getJMeterProperties(); + sem.put("props", props); + // For use in debugging: + sem.put("OUT", System.out); + + // Most subclasses will need these: + Sampler sampler = jmctx.getCurrentSampler(); + sem.put("sampler", sampler); + SampleResult prev = jmctx.getPreviousResult(); + sem.put("prev", prev); + } + + + protected Object processFileOrScript(ScriptEngineManager sem) throws IOException, ScriptException { + + final String lang = getScriptLanguage(); + ScriptEngine scriptEngine = sem.getEngineByName(lang); + if (scriptEngine == null) { + throw new ScriptException("Cannot find engine named: "+lang); + } + + File scriptFile = new File(getFilename()); + if (scriptFile.exists()) { + BufferedReader fileReader = null; + try { + fileReader = new BufferedReader(new FileReader(scriptFile)); // TODO Charset ? + return scriptEngine.eval(fileReader); + } finally { + IOUtils.closeQuietly(fileReader); + } + } else { + return scriptEngine.eval(getScript()); + } + + } + + /** + * Return the script (TestBean version). + * Must be overridden for subclasses that don't implement TestBean + * otherwise the clone() method won't work. + * + * @return the script to execute + */ + public String getScript(){ + return script; + } + + /** + * Set the script (TestBean version). + * Must be overridden for subclasses that don't implement TestBean + * otherwise the clone() method won't work. + * + * @param s the script to execute (may be blank) + */ + public void setScript(String s){ + script=s; + } + + public String getParameters() { + return parameters; + } + + public void setParameters(String s) { + parameters = s; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String s) { + filename = s; + } + + public String getScriptLanguage() { + return scriptLanguage; + } + + public void setScriptLanguage(String s) { + scriptLanguage = s; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/JsseSSLManager.java b/ApacheJmeter/src/org/apache/jmeter/util/JsseSSLManager.java new file mode 100644 index 0000000..3fa51a1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/JsseSSLManager.java @@ -0,0 +1,397 @@ +/* + * 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; + +import java.net.HttpURLConnection; +import java.net.Socket; +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.jmeter.util.keystore.JmeterKeyStore; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * The SSLManager handles the KeyStore information for JMeter. Basically, it + * handles all the logic for loading and initializing all the JSSE parameters + * and selecting the alias to authenticate against if it is available. + * SSLManager will try to automatically select the client certificate for you, + * but if it can't make a decision, it will pop open a dialog asking you for + * more information. + * + * TODO: does not actually prompt + * + */ +public class JsseSSLManager extends SSLManager { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String HTTPS = "https"; // $NON-NLS-1$ + + // Temporary fix to allow default protocol to be changed + private static final String DEFAULT_SSL_PROTOCOL = + JMeterUtils.getPropDefault("https.default.protocol","TLS"); // $NON-NLS-1$ // $NON-NLS-2$ + + // Allow reversion to original shared session context + private static final boolean SHARED_SESSION_CONTEXT = + JMeterUtils.getPropDefault("https.sessioncontext.shared",false); // $NON-NLS-1$ + + private static final int cps; + + static { + log.info("Using default SSL protocol: "+DEFAULT_SSL_PROTOCOL); + log.info("SSL session context: "+(SHARED_SESSION_CONTEXT ? "shared" : "per-thread")); + cps = JMeterUtils.getPropDefault("httpclient.socket.https.cps", 0); // $NON-NLS-1$ + + if (cps > 0) { + log.info("Setting up HTTPS SlowProtocol, cps="+cps); + } + + } + + /** + * Cache the SecureRandom instance because it takes a long time to create + */ + private SecureRandom rand; + + private Provider pro = null; // TODO why not use the super class value? + + private SSLContext defaultContext; // If we are using a single session + private ThreadLocal threadlocal; // Otherwise + + /** + * Create the SSLContext, and wrap all the X509KeyManagers with + * our X509KeyManager so that we can choose our alias. + * + * @param provider + * Description of Parameter + */ + public JsseSSLManager(Provider provider) { + log.debug("ssl Provider = " + provider); + setProvider(provider); + if (null == this.rand) { // Surely this is always null in the constructor? + this.rand = new SecureRandom(); + } + try { + if (SHARED_SESSION_CONTEXT) { + log.debug("Creating shared context"); + this.defaultContext = createContext(); + } else { + this.threadlocal = new ThreadLocal(); + } + + /* + * Set up Java defaults. + * N.B. does not allow SlowSocket - fails with: + * java.lang.RuntimeException: Export restriction: this JSSE implementation is non-pluggable. + */ + + HttpsURLConnection.setDefaultSSLSocketFactory(new HttpSSLProtocolSocketFactory(this)); + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + + /* + * Also set up HttpClient defaults + */ + Protocol protocol = new Protocol( + JsseSSLManager.HTTPS, + (ProtocolSocketFactory) new HttpSSLProtocolSocketFactory(this, cps), + 443); + Protocol.registerProtocol(JsseSSLManager.HTTPS, protocol); + log.debug("SSL stuff all set"); + } catch (GeneralSecurityException ex) { + log.error("Could not set up SSLContext", ex); + } + log.debug("JsseSSLManager installed"); + } + + /** + * Sets the Context attribute of the JsseSSLManager object + * + * @param conn + * The new Context value + */ + @Override + public void setContext(HttpURLConnection conn) { + if (conn instanceof HttpsURLConnection) { +/* + * No point doing this on a per-connection basis, as there is currently no way to configure it. + * So we leave it to the defaults set up in the SSL Context + * + */ +// HttpsURLConnection secureConn = (HttpsURLConnection) conn; +// secureConn.setSSLSocketFactory(this.getContext().getSocketFactory()); + } else { + log.warn("Unexpected HttpURLConnection class: "+conn.getClass().getName()); + } + } + + /** + * Sets the Provider attribute of the JsseSSLManager object + * + * @param p + * The new Provider value + */ + @Override + protected final void setProvider(Provider p) { + super.setProvider(p); + if (null == this.pro) { + this.pro = p; + } + } + + /** + * Returns the SSLContext we are using. + * This is either a context per thread, + * or, for backwards compatibility, a single shared context. + * + * @return The Context value + */ + public SSLContext getContext() throws GeneralSecurityException { + if (SHARED_SESSION_CONTEXT) { + if (log.isDebugEnabled()){ + log.debug("Using shared SSL context for: "+Thread.currentThread().getName()); + } + return this.defaultContext; + } + + SSLContext sslContext = this.threadlocal.get(); + if (sslContext == null) { + if (log.isDebugEnabled()){ + log.debug("Creating threadLocal SSL context for: "+Thread.currentThread().getName()); + } + sslContext = createContext(); + this.threadlocal.set(sslContext); + } + if (log.isDebugEnabled()){ + log.debug("Using threadLocal SSL context for: "+Thread.currentThread().getName()); + } + return sslContext; + } + + /** + * Resets the SSLContext if using per-thread contexts. + * + */ + public void resetContext() { + if (!SHARED_SESSION_CONTEXT) { + log.debug("Clearing session context for current thread"); + this.threadlocal.set(null); + } + } + /* + * + * Creates new SSL context + * @return SSL context + * @throws GeneralSecurityException + */ + private SSLContext createContext() throws GeneralSecurityException { + SSLContext context; + if (pro != null) { + context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL, pro); // $NON-NLS-1$ + } else { + context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); // $NON-NLS-1$ + } + KeyManagerFactory managerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + JmeterKeyStore keys = this.getKeyStore(); + managerFactory.init(null, defaultpw == null ? new char[]{} : defaultpw.toCharArray()); + KeyManager[] managers = managerFactory.getKeyManagers(); + log.debug(keys.getClass().toString()); + + // Now wrap the default managers with our key manager + for (int i = 0; i < managers.length; i++) { + if (managers[i] instanceof X509KeyManager) { + X509KeyManager manager = (X509KeyManager) managers[i]; + managers[i] = new WrappedX509KeyManager(manager, keys); + } + } + + // Get the default trust managers + TrustManagerFactory tmfactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + tmfactory.init(this.getTrustStore()); + + // Wrap the defaults in our custom trust manager + TrustManager[] trustmanagers = tmfactory.getTrustManagers(); + for (int i = 0; i < trustmanagers.length; i++) { + if (trustmanagers[i] instanceof X509TrustManager) { + trustmanagers[i] = new CustomX509TrustManager( + (X509TrustManager)trustmanagers[i]); + } + } + context.init(managers, trustmanagers, this.rand); + if (log.isDebugEnabled()){ + String[] dCiphers = context.getSocketFactory().getDefaultCipherSuites(); + String[] sCiphers = context.getSocketFactory().getSupportedCipherSuites(); + int len = (dCiphers.length > sCiphers.length) ? dCiphers.length : sCiphers.length; + for (int i = 0; i < len; i++) { + if (i < dCiphers.length) { + log.debug("Default Cipher: " + dCiphers[i]); + } + if (i < sCiphers.length) { + log.debug("Supported Cipher: " + sCiphers[i]); + } + } + } + return context; + } + + /** + * This is the X509KeyManager we have defined for the sole purpose of + * selecting the proper key and certificate based on the keystore available. + * + */ + private static class WrappedX509KeyManager implements X509KeyManager { + + /** + * The parent X509KeyManager + */ + private final X509KeyManager manager; + + /** + * The KeyStore this KeyManager uses + */ + private final JmeterKeyStore store; + + /** + * Instantiate a new WrappedX509KeyManager. + * + * @param parent + * The parent X509KeyManager + * @param ks + * The KeyStore we derive our client certs and keys from + */ + public WrappedX509KeyManager(X509KeyManager parent, JmeterKeyStore ks) { + this.manager = parent; + this.store = ks; + } + + /** + * Compiles the list of all client aliases with a private key. + * + * @param keyType the key algorithm type name (RSA, DSA, etc.) + * @param issuers the CA certificates we are narrowing our selection on. + * + * @return the array of aliases; may be empty + */ + public String[] getClientAliases(String keyType, Principal[] issuers) { + log.debug("WrappedX509Manager: getClientAliases: "); + // implementation moved to JmeterKeystore as only that has the keyType info + return this.store.getClientAliases(keyType, issuers); + } + + /** + * Get the list of server aliases for the SSLServerSockets. This is not + * used in JMeter. + * + * @param keyType + * the type of private key the server expects (RSA, DSA, + * etc.) + * @param issuers + * the CA certificates we are narrowing our selection on. + * @return the ServerAliases value + */ + public String[] getServerAliases(String keyType, Principal[] issuers) { + log.debug("WrappedX509Manager: getServerAliases: "); + return this.manager.getServerAliases(keyType, issuers); + } + + /** + * Get the Certificate chain for a particular alias + * + * @param alias + * The client alias + * @return The CertificateChain value + */ + public X509Certificate[] getCertificateChain(String alias) { + log.debug("WrappedX509Manager: getCertificateChain(" + alias + ")"); + return this.store.getCertificateChain(alias); + } + + /** + * Get the Private Key for a particular alias + * + * @param alias + * The client alias + * @return The PrivateKey value + */ + public PrivateKey getPrivateKey(String alias) { + PrivateKey privateKey = this.store.getPrivateKey(alias); + log.debug("WrappedX509Manager: getPrivateKey: " + privateKey); + return privateKey; + } + + /** + * Select the Alias we will authenticate as if Client authentication is + * required by the server we are connecting to. We get the list of + * aliases, and if there is only one alias we automatically select it. + * If there are more than one alias that has a private key, we prompt + * the user to choose which alias using a combo box. Otherwise, we + * simply provide a text box, which may or may not work. The alias does + * have to match one in the keystore. + * + * TODO? - does not actually allow the user to choose an alias at present + * + * @param keyType the key algorithm type name(s), ordered with the most-preferred key type first. + * @param issuers the list of acceptable CA issuer subject names or null if it does not matter which issuers are used. + * @param socket the socket to be used for this connection. + * This parameter can be null, which indicates that implementations are free to select an alias applicable to any socket. + * + * @see javax.net.ssl.X509KeyManager#chooseClientAlias(String[], Principal[], Socket) + */ + public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { + log.debug("keyType: " + keyType[0]); + String alias = this.store.getAlias(); + if (alias == null || alias.length() == 0) { + log.debug("ClientAlias not found."); + } + return alias; + } + + /** + * Choose the server alias for the SSLServerSockets. This are not used + * in JMeter. + * + * @see javax.net.ssl.X509KeyManager#chooseServerAlias(String, Principal[], Socket) + */ + public String chooseServerAlias(String arg0, Principal[] arg1, Socket arg2) { + return this.manager.chooseServerAlias(arg0, arg1, arg2); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/LocaleChangeEvent.java b/ApacheJmeter/src/org/apache/jmeter/util/LocaleChangeEvent.java new file mode 100644 index 0000000..6b97753 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/LocaleChangeEvent.java @@ -0,0 +1,45 @@ +/* + * 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; + +import java.util.EventObject; +import java.util.Locale; + +/** + * @version $Revision: 905027 $ + */ +public class LocaleChangeEvent extends EventObject { + + private static final long serialVersionUID = 240L; + + private Locale locale; + + public LocaleChangeEvent(Object source) { + super(source); + } + + public LocaleChangeEvent(Object source, Locale locale) { + super(source); + this.locale = locale; + } + + public Locale getLocale() { + return locale; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/LocaleChangeListener.java b/ApacheJmeter/src/org/apache/jmeter/util/LocaleChangeListener.java new file mode 100644 index 0000000..b4111e2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/LocaleChangeListener.java @@ -0,0 +1,23 @@ +/* + * 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; + +public interface LocaleChangeListener { + public void localeChanged(LocaleChangeEvent event); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/NameUpdater.java b/ApacheJmeter/src/org/apache/jmeter/util/NameUpdater.java new file mode 100644 index 0000000..d0f0af6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/NameUpdater.java @@ -0,0 +1,190 @@ +/* + * 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. + * + */ + +/* + * Created on Jun 13, 2003 + */ +package org.apache.jmeter.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Properties; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +public final class NameUpdater { + private static final Properties nameMap; + // Read-only access after class has been initialised + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String NAME_UPDATER_PROPERTIES = + "META-INF/resources/org.apache.jmeter.nameupdater.properties"; // $NON-NLS-1$ + + static { + nameMap = new Properties(); + FileInputStream fis = null; + File f = new File(JMeterUtils.getJMeterHome(), + JMeterUtils.getPropDefault("upgrade_properties", // $NON-NLS-1$ + "/bin/upgrade.properties")); // $NON-NLS-1$ + try { + fis = new FileInputStream(f); + nameMap.load(fis); + } catch (FileNotFoundException e) { + log.error("Could not find upgrade file: ", e); + } catch (IOException e) { + log.error("Error processing upgrade file: "+f.getPath(), e); + } finally { + JOrphanUtils.closeQuietly(fis); + } + + //load additionnal name conversion rules from plugins + Enumeration enu = null; + + try { + enu = JMeterUtils.class.getClassLoader().getResources(NAME_UPDATER_PROPERTIES); + } catch (IOException e) { + log.error("Error in finding additional nameupdater.properties files: ", e); + } + + if(enu != null) { + while(enu.hasMoreElements()) { + URL ressourceUrl = enu.nextElement(); + log.info("Processing "+ressourceUrl.toString()); + Properties prop = new Properties(); + InputStream is = null; + try { + is = ressourceUrl.openStream(); + prop.load(is); + } catch (IOException e) { + log.error("Error processing upgrade file: " + ressourceUrl.getPath(), e); + } finally { + JOrphanUtils.closeQuietly(is); + } + + @SuppressWarnings("unchecked") // names are Strings + Enumeration propertyNames = (Enumeration) prop.propertyNames(); + while (propertyNames.hasMoreElements()) { + String key = propertyNames.nextElement(); + if (!nameMap.contains(key)) { + nameMap.put(key, prop.get(key)); + log.info("Added additional nameMap entry: " + key); + } else { + log.warn("Additional nameMap entry: '" + key + "' rejected as already defined."); + } + } + } + } + } + + /** + * Looks up the class name; if that does not exist in the map, + * then defaults to the input name. + * + * @param className the classname from the script file + * @return the class name to use, possibly updated. + */ + public static String getCurrentName(String className) { + if (nameMap.containsKey(className)) { + String newName = nameMap.getProperty(className); + log.info("Upgrading class " + className + " to " + newName); + return newName; + } + return className; + } + + /** + * Looks up test element / gui class combination; if that + * does not exist in the map, then defaults to getCurrentName(testClassName). + * + * @param testClassName - test element class name + * @param guiClassName - associated gui class name + * @return new test class name + */ + public static String getCurrentTestName(String testClassName, String guiClassName) { + String key = testClassName + "|" + guiClassName; + if (nameMap.containsKey(key)) { + String newName = nameMap.getProperty(key); + log.info("Upgrading " + key + " to " + newName); + return newName; + } + return getCurrentName(testClassName); + } + + /** + * Looks up class name / property name combination; if that + * does not exist in the map, then defaults to input property name. + * + * @param propertyName - property name to check + * @param className - class name containing the property + * @return possibly updated property name + */ + public static String getCurrentName(String propertyName, String className) { + String key = className + "/" + propertyName; + if (nameMap.containsKey(key)) { + String newName = nameMap.getProperty(key); + log.info("Upgrading property " + propertyName + " to " + newName); + return newName; + } + return propertyName; + } + + /** + * Looks up class name . property name / value combination; + * if that does not exist in the map, returns the original value. + * + * @param value the value to be checked + * @param propertyName the name of the property + * @param className the class containing the propery. + * @return the value, updated if necessary + */ + public static String getCurrentName(String value, String propertyName, String className) { + String key = className + "." + propertyName + "/" + value; + if (nameMap.containsKey(key)) { + String newValue = nameMap.getProperty(key); + log.info("Upgrading value " + value + " to " + newValue); + return newValue; + } + return value; + } + + /** + * Private constructor to prevent instantiation. + */ + private NameUpdater() { + } + + /** + * Check if a key is in the map; intended for use by + * {@link org.apache.jmeter.save.SaveService#checkClasses() SaveService#checkClasses()} + * only. + * + * @param key + * @return true if the key is in the map + */ + public static boolean isMapped(String key) { + return nameMap.containsKey(key); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/NamedObject.java b/ApacheJmeter/src/org/apache/jmeter/util/NamedObject.java new file mode 100644 index 0000000..99011ff --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/NamedObject.java @@ -0,0 +1,23 @@ +/* + * 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; + +public interface NamedObject { + public String getName(); +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/util/PropertiesBasedPrefixResolver.java b/ApacheJmeter/src/org/apache/jmeter/util/PropertiesBasedPrefixResolver.java new file mode 100644 index 0000000..d0938e9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/PropertiesBasedPrefixResolver.java @@ -0,0 +1,99 @@ +/* + * 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; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.lang.StringUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.apache.xml.utils.PrefixResolver; +import org.apache.xml.utils.PrefixResolverDefault; +import org.w3c.dom.Node; + +/** + * {@link PrefixResolver} implementation that loads prefix configuration from jmeter property xpath.namespace.config + */ +public class PropertiesBasedPrefixResolver extends PrefixResolverDefault { + private static final Logger logger = LoggingManager.getLoggerForClass(); + private static final String XPATH_NAMESPACE_CONFIG = "xpath.namespace.config"; + private static final Map NAMESPACE_MAP = new HashMap(); + static { + String pathToNamespaceConfig = JMeterUtils.getPropDefault(XPATH_NAMESPACE_CONFIG, ""); + if(!StringUtils.isEmpty(pathToNamespaceConfig)) { + Properties properties = new Properties(); + InputStream inputStream = null; + try { + File pathToNamespaceConfigFile = JMeterUtils.findFile(pathToNamespaceConfig); + if(!pathToNamespaceConfigFile.exists()) { + logger.error("Cannot find configured file:'"+ + pathToNamespaceConfig+"' in property:'"+XPATH_NAMESPACE_CONFIG+"', file does not exist"); + } else { + if(!pathToNamespaceConfigFile.canRead()) { + logger.error("Cannot read configured file:'"+ + pathToNamespaceConfig+"' in property:'"+XPATH_NAMESPACE_CONFIG+"'"); + } else { + inputStream = new BufferedInputStream(new FileInputStream(pathToNamespaceConfigFile)); + properties.load(inputStream); + properties.entrySet(); + for (Map.Entry entry : properties.entrySet()) { + NAMESPACE_MAP.put((String) entry.getKey(), (String) entry.getValue()); + } + logger.info("Read following XPath namespace configuration "+ + NAMESPACE_MAP); + } + } + } catch(IOException e) { + logger.error("Error loading namespaces from file:'"+ + pathToNamespaceConfig+"', message:"+e.getMessage(),e); + } finally { + JOrphanUtils.closeQuietly(inputStream); + } + } + } + /** + * @param xpathExpressionContext Node + */ + public PropertiesBasedPrefixResolver(Node xpathExpressionContext) { + super(xpathExpressionContext); + } + + /** + * Searches prefix in NAMESPACE_MAP, if it fails to find it defaults to parent implementation + * @param prefix Prefix + * @param namespaceContext Node + */ + @Override + public String getNamespaceForPrefix(String prefix, Node namespaceContext) { + String namespace = NAMESPACE_MAP.get(prefix); + if(namespace==null) { + return super.getNamespaceForPrefix(prefix, namespaceContext); + } else { + return namespace; + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/util/SSLManager.java b/ApacheJmeter/src/org/apache/jmeter/util/SSLManager.java new file mode 100644 index 0000000..e0d1bfb --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/SSLManager.java @@ -0,0 +1,292 @@ +/* + * 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; + +import java.io.File; +import java.io.FileInputStream; +import java.net.HttpURLConnection; +import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; +import java.util.Locale; + +import javax.swing.JOptionPane; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.keystore.JmeterKeyStore; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * The SSLManager handles the KeyStore information for JMeter. Basically, it + * handles all the logic for loading and initializing all the JSSE parameters + * and selecting the alias to authenticate against if it is available. + * SSLManager will try to automatically select the client certificate for you, + * but if it can't make a decision, it will pop open a dialog asking you for + * more information. + * + * TODO? - N.B. does not currently allow the selection of a client certificate. + * + */ +public abstract class SSLManager { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String SSL_TRUST_STORE = "javax.net.ssl.trustStore";// $NON-NLS-1$ + + private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; // $NON-NLS-1$ + + public static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; // $NON-NLS-1$ + + private static final String JAVAX_NET_SSL_KEY_STORE_TYPE = "javax.net.ssl.keyStoreType"; // $NON-NLS-1$ + + private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$ + + /** Singleton instance of the manager */ + //@GuardedBy("this") + private static SSLManager manager; + + private static final boolean isSSLSupported = true; + + /** Cache the KeyStore instance */ + private volatile JmeterKeyStore keyStore; + + /** Cache the TrustStore instance - null if no truststore name was provided */ + private KeyStore trustStore = null; + // Have we yet tried to load the truststore? + private volatile boolean truststore_loaded=false; + + /** Have the password available */ + protected String defaultpw = System.getProperty(KEY_STORE_PASSWORD); + + private int keystoreAliasStartIndex; + + private int keystoreAliasEndIndex; + + /** + * Resets the SSLManager so that we can create a new one with a new keystore + */ + public static synchronized void reset() { + SSLManager.manager = null; + } + + public abstract void setContext(HttpURLConnection conn); + + /** + * Default implementation of setting the Provider + */ + protected void setProvider(Provider provider) { + if (null != provider) { + Security.addProvider(provider); + } + } + + /** + * Opens and initializes the KeyStore. If the password for the KeyStore is + * not set, this method will prompt you to enter it. Unfortunately, there is + * no PasswordEntryField available from JOptionPane. + */ + protected JmeterKeyStore getKeyStore() { + if (null == this.keyStore) { + String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE,""); // empty if not provided + String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type + fileName.toLowerCase(Locale.UK).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name + log.info("JmeterKeyStore Location: " + fileName + " type " + fileType); + try { + this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex); + log.info("KeyStore created OK"); + } catch (Exception e) { + this.keyStore = null; + throw new RuntimeException("Could not create keystore: "+e.getMessage()); + } + FileInputStream fileInputStream = null; + try { + File initStore = new File(fileName); + + if (fileName.length() >0 && initStore.exists()) { + fileInputStream = new FileInputStream(initStore); + this.keyStore.load(fileInputStream, getPassword()); + if (log.isInfoEnabled()) { + log.info("Total of " + keyStore.getAliasCount() + " aliases loaded OK from keystore"); + } + } else { + log.warn("Keystore file not found, loading empty keystore"); + this.defaultpw = ""; // Ensure not null + this.keyStore.load(null, ""); + } + } catch (Exception e) { + log.error("Problem loading keystore: " +e.getMessage(), e); + } finally { + JOrphanUtils.closeQuietly(fileInputStream); + } + + log.debug("JmeterKeyStore type: " + this.keyStore.getClass().toString()); + } + + return this.keyStore; + } + + /* + * The password can be defined as a property; this dialogue is provided to allow it + * to be entered at run-time. + * + * However, this does not gain much, as the dialogue does not (yet) support hidden input ... + * + */ + private String getPassword() { + String password = this.defaultpw; + if (null == password) { + this.defaultpw = System.getProperty(KEY_STORE_PASSWORD); + + if (null == defaultpw) { + synchronized (this) { + this.defaultpw = JOptionPane.showInputDialog( + GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("ssl_pass_prompt"), // $NON-NLS-1$ + JMeterUtils.getResString("ssl_pass_title"), // $NON-NLS-1$ + JOptionPane.QUESTION_MESSAGE); + System.setProperty(KEY_STORE_PASSWORD, this.defaultpw); + } + } + + password = this.defaultpw; + System.setProperty(KEY_STORE_PASSWORD, password); + } + return password; + } + + /** + * Opens and initializes the TrustStore. + * + * There are 3 possibilities: + * - no truststore name provided, in which case the default Java truststore should be used + * - truststore name is provided, and loads OK + * - truststore name is provided, but is not found or does not load OK, in which case an empty + * truststore is created + * + * If the KeyStore object cannot be created, then this is currently treated the same + * as if no truststore name was provided. + * + * @return truststore + * - null: use Java truststore + * - otherwise, the truststore, which may be empty if the file could not be loaded. + * + */ + protected KeyStore getTrustStore() { + if (!truststore_loaded) { + + truststore_loaded=true;// we've tried ... + + String fileName = System.getProperty(SSL_TRUST_STORE); + if (fileName == null) { + return null; + } + log.info("TrustStore Location: " + fileName); + + try { + this.trustStore = KeyStore.getInstance("JKS"); + log.info("TrustStore created OK, Type: JKS"); + } catch (Exception e) { + this.trustStore = null; + throw new RuntimeException("Problem creating truststore: "+e.getMessage()); + } + + FileInputStream fileInputStream = null; + try { + File initStore = new File(fileName); + + if (initStore.exists()) { + fileInputStream = new FileInputStream(initStore); + this.trustStore.load(fileInputStream, null); + log.info("Truststore loaded OK from file"); + } else { + log.info("Truststore file not found, loading empty truststore"); + this.trustStore.load(null, null); + } + } catch (Exception e) { + throw new RuntimeException("Can't load TrustStore: " + e.toString()); + } finally { + JOrphanUtils.closeQuietly(fileInputStream); + } + } + + return this.trustStore; + } + + /** + * Protected Constructor to remove the possibility of directly instantiating + * this object. Create the SSLContext, and wrap all the X509KeyManagers with + * our X509KeyManager so that we can choose our alias. + */ + protected SSLManager() { + } + + /** + * Static accessor for the SSLManager object. The SSLManager is a singleton. + */ + public static final synchronized SSLManager getInstance() { + if (null == SSLManager.manager) { + SSLManager.manager = new JsseSSLManager(null); +// if (SSLManager.isSSLSupported) { +// String classname = null; +// classname = "org.apache.jmeter.util.JsseSSLManager"; // $NON-NLS-1$ +// +// try { +// Class clazz = Class.forName(classname); +// Constructor con = clazz.getConstructor(new Class[] { Provider.class }); +// SSLManager.manager = (SSLManager) con.newInstance(new Object[] { SSLManager.sslProvider }); +// } catch (Exception e) { +// log.error("Could not create SSLManager instance", e); // $NON-NLS-1$ +// SSLManager.isSSLSupported = false; +// return null; +// } +// } + } + + return SSLManager.manager; + } + + /** + * Test whether SSL is supported or not. + */ + public static final boolean isSSLSupported() { + return SSLManager.isSSLSupported; + } + + /** + * Configure Keystore + * @param preload + * @param startIndex + * @param endIndex + */ + public void configureKeystore(boolean preload, int startIndex, int endIndex) { + this.keystoreAliasStartIndex = startIndex; + this.keystoreAliasEndIndex = endIndex; + if(preload) { + keyStore = getKeyStore(); + } + } + + /** + * Destroy Keystore + */ + public void destroyKeystore() { + keyStore=null; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/ScopePanel.java b/ApacheJmeter/src/org/apache/jmeter/util/ScopePanel.java new file mode 100644 index 0000000..fbd4d5b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/ScopePanel.java @@ -0,0 +1,165 @@ +/* + * 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; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Scope panel so users can choose whether + * to apply the test element to the parent sample, the child samples or both. + * + */ +public class ScopePanel extends JPanel implements ActionListener { + + private static final long serialVersionUID = 240L; + + private final JRadioButton parentButton; + private final JRadioButton childButton; + private final JRadioButton allButton; + private final JRadioButton variableButton; + private final JTextField variableName; + + public ScopePanel(){ + this(false); + } + + public ScopePanel(boolean enableVariableButton) { + allButton = new JRadioButton(JMeterUtils.getResString("sample_scope_all")); //$NON-NLS-1$ + parentButton = new JRadioButton(JMeterUtils.getResString("sample_scope_parent")); //$NON-NLS-1$ + childButton = new JRadioButton(JMeterUtils.getResString("sample_scope_children")); //$NON-NLS-1$ + if (enableVariableButton) { + variableButton = new JRadioButton(JMeterUtils.getResString("sample_scope_variable")); //$NON-NLS-1$ + variableName = new JTextField(10); + } else { + variableButton = null; + variableName = null; + } + init(); + } + + /** + * Initialize the GUI components and layout. + */ + private void init() { + setLayout(new BorderLayout(5, 0)); + setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("sample_scope"))); //$NON-NLS-1$ + + parentButton.setSelected(true); + + JPanel buttonPanel = new HorizontalPanel(); + ButtonGroup group = new ButtonGroup(); + group.add(allButton); + group.add(parentButton); + group.add(childButton); + buttonPanel.add(parentButton); + buttonPanel.add(childButton); + buttonPanel.add(allButton); + if (variableButton != null){ + variableButton.addActionListener(this); + group.add(variableButton); + buttonPanel.add(variableButton); + buttonPanel.add(variableName); + } + add(buttonPanel); + } + + public void clearGui() { + parentButton.setSelected(true); + } + + public int getSelection(){ + if (parentButton.isSelected()){ + return 0; + } + return 1; + } + + public void setScopeAll() { + setScopeAll(false); + } + + public void setScopeAll(boolean enableVariableButton) { + allButton.setSelected(true); + if (enableVariableButton) { + variableName.setText(""); //$NON-NLS-1$ + } + } + + public void setScopeChildren() { + setScopeChildren(false); + } + + public void setScopeChildren(boolean enableVariableButton) { + childButton.setSelected(true); + if (enableVariableButton) { + variableName.setText(""); //$NON-NLS-1$ + } + } + + public void setScopeParent() { + setScopeParent(false); + } + + public void setScopeParent(boolean enableVariableButton) { + parentButton.setSelected(true); + if (enableVariableButton) { + variableName.setText(""); //$NON-NLS-1$ + } + } + + public void setScopeVariable(String value){ + variableButton.setSelected(true); + variableName.setText(value); + } + + public boolean isScopeParent() { + return parentButton.isSelected(); + } + + public boolean isScopeChildren() { + return childButton.isSelected(); + } + + public boolean isScopeAll() { + return allButton.isSelected(); + } + + public boolean isScopeVariable() { + return variableButton.isSelected(); + } + + public void actionPerformed(ActionEvent e) { + variableName.setEnabled(variableButton.isSelected()); + } + + public String getVariable() { + return variableName.getText(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/ScriptingBeanInfoSupport.java b/ApacheJmeter/src/org/apache/jmeter/util/ScriptingBeanInfoSupport.java new file mode 100644 index 0000000..04b252f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/ScriptingBeanInfoSupport.java @@ -0,0 +1,68 @@ +/* + * 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; + +import java.beans.PropertyDescriptor; + +import org.apache.jmeter.testbeans.BeanInfoSupport; +import org.apache.jmeter.testbeans.gui.FileEditor; +import org.apache.jmeter.testbeans.gui.TextAreaEditor; + +/** + * Parent class to define common GUI parameters for BSF and JSR223 test elements + */ +public abstract class ScriptingBeanInfoSupport extends BeanInfoSupport { + + protected ScriptingBeanInfoSupport(Class beanClass, String[] LANGUAGE_TAGS) { + super(beanClass); + PropertyDescriptor p; + + p = property("scriptLanguage"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + p.setValue(TAGS, LANGUAGE_TAGS); + + createPropertyGroup("scriptingLanguage", // $NON-NLS-1$ + new String[] { "scriptLanguage" }); // $NON-NLS-1$ + + p = property("parameters"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + + createPropertyGroup("parameterGroup", // $NON-NLS-1$ + new String[] { "parameters" }); // $NON-NLS-1$ + + p = property("filename"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + p.setPropertyEditorClass(FileEditor.class); + + createPropertyGroup("filenameGroup", // $NON-NLS-1$ + new String[] { "filename" }); // $NON-NLS-1$ + + p = property("script"); // $NON-NLS-1$ + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); // $NON-NLS-1$ + p.setPropertyEditorClass(TextAreaEditor.class); + + createPropertyGroup("scripting", // $NON-NLS-1$ + new String[] { "script" }); // $NON-NLS-1$ + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/ShutdownClient.java b/ApacheJmeter/src/org/apache/jmeter/util/ShutdownClient.java new file mode 100644 index 0000000..3ae407a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/ShutdownClient.java @@ -0,0 +1,49 @@ +/* + * 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; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; + +import org.apache.jmeter.JMeter; + + +/** + * Simple utility to send a shutdown message to a non-GUI instance of JMeter + */ +public class ShutdownClient { + public static void main(String[] args) throws IOException { + int port = JMeter.UDP_PORT_DEFAULT; + if (args.length > 1){ + port = Integer.parseInt(args[1]); + } else if (args.length == 0) { + throw new RuntimeException("Usage: command [port]"); + } + String command = args[0]; + System.out.println("Sending "+command+" request to port "+port); + DatagramSocket socket = new DatagramSocket(); + byte[] buf = command.getBytes("ASCII"); + InetAddress address = InetAddress.getByName("localhost"); + DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port); + socket.send(packet); + socket.close(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/SlowInputStream.java b/ApacheJmeter/src/org/apache/jmeter/util/SlowInputStream.java new file mode 100644 index 0000000..5e79f42 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/SlowInputStream.java @@ -0,0 +1,56 @@ +/* + * 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; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * InputStream wrapper to emulate a slow device, e.g. modem + * + */ +public class SlowInputStream extends FilterInputStream { + + private final CPSPauser pauser; + + /** + * Wraps the input stream to emulate a slow device + * @param in input stream + * @param cps characters per second to emulate + */ + public SlowInputStream(InputStream in, int cps) { + super(in); + pauser = new CPSPauser(cps); + } + + @Override + public int read() throws IOException { + pauser.pause(1); + return in.read(); + } + + // Also handles read(byte[]) + @Override + public int read(byte[] b, int off, int len) throws IOException { + pauser.pause(len); + return in.read(b, off, len); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/SlowOutputStream.java b/ApacheJmeter/src/org/apache/jmeter/util/SlowOutputStream.java new file mode 100644 index 0000000..614e92d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/SlowOutputStream.java @@ -0,0 +1,55 @@ +/* + * 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; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * OutputStream filter to emulate a slow device, e.g. modem + * + */ +public class SlowOutputStream extends FilterOutputStream { + + private final CPSPauser pauser; + + /** + * Create wrapped Output Stream toe emulate the requested CPS. + * @param out OutputStream + * @param cps characters per second + */ + public SlowOutputStream(OutputStream out, int cps) { + super(out); + pauser = new CPSPauser(cps); + } + + // Also handles write(byte[]) + @Override + public void write(byte[] b, int off, int len) throws IOException { + pauser.pause(len); + out.write(b, off, len); + } + + @Override + public void write(int b) throws IOException { + pauser.pause(1); + out.write(b); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/SlowSSLSocket.java b/ApacheJmeter/src/org/apache/jmeter/util/SlowSSLSocket.java new file mode 100644 index 0000000..a535445 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/SlowSSLSocket.java @@ -0,0 +1,354 @@ +/* + * 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; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SocketChannel; + +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +/** + * "Slow" SSLsocket implementation to emulate dial-up modems etc + * + * WARNING: the class relies on overriding all superclass methods in order to apply them to the input socket. + * Any missing methods will access the superclass socket, which will probably be in the wrong state. + * + */ +public class SlowSSLSocket extends SSLSocket { + + private final int CPS; // Characters per second to emulate + + private final SSLSocket sslSock; // Save the actual socket + + /** + * Wrap an SSLSocket with slow input and output streams + * @param sock SSLSocket to be wrapped + * @param cps characters per second to emulate + */ + public SlowSSLSocket(final SSLSocket sock, final int cps){ + if (cps <=0) { + throw new IllegalArgumentException("Speed (cps) <= 0"); + } + sslSock=sock; + CPS=cps; + } + + // Override so we can intercept the stream + @Override + public OutputStream getOutputStream() throws IOException { + return new SlowOutputStream(sslSock.getOutputStream(), CPS); + } + + // Override so we can intercept the stream + @Override + public InputStream getInputStream() throws IOException { + return new SlowInputStream(sslSock.getInputStream(), CPS); + } + + // Forward all the SSLSocket methods to the input socket + + @Override + public void addHandshakeCompletedListener(HandshakeCompletedListener arg0) { + sslSock.addHandshakeCompletedListener(arg0); + } + + @Override + public boolean getEnableSessionCreation() { + return sslSock.getEnableSessionCreation(); + } + + @Override + public String[] getEnabledCipherSuites() { + return sslSock.getEnabledCipherSuites(); + } + + @Override + public String[] getEnabledProtocols() { + return sslSock.getEnabledProtocols(); + } + + @Override + public boolean getNeedClientAuth() { + return sslSock.getNeedClientAuth(); + } + + @Override + public SSLSession getSession() { + return sslSock.getSession(); + } + + @Override + public String[] getSupportedCipherSuites() { + return sslSock.getSupportedCipherSuites(); + } + + @Override + public String[] getSupportedProtocols() { + return sslSock.getSupportedProtocols(); + } + + @Override + public boolean getUseClientMode() { + return sslSock.getUseClientMode(); + } + + @Override + public boolean getWantClientAuth() { + return sslSock.getWantClientAuth(); + } + + @Override + public void removeHandshakeCompletedListener(HandshakeCompletedListener arg0) { + sslSock.removeHandshakeCompletedListener(arg0); + } + + @Override + public void setEnableSessionCreation(boolean arg0) { + sslSock.setEnableSessionCreation(arg0); + } + + @Override + public void setEnabledCipherSuites(String[] arg0) { + sslSock.setEnabledCipherSuites(arg0); + } + + @Override + public void setEnabledProtocols(String[] arg0) { + sslSock.setEnabledProtocols(arg0); + } + + @Override + public void setNeedClientAuth(boolean arg0) { + sslSock.setNeedClientAuth(arg0); + } + + @Override + public void setUseClientMode(boolean arg0) { + sslSock.setUseClientMode(arg0); + } + + @Override + public void setWantClientAuth(boolean arg0) { + sslSock.setWantClientAuth(arg0); + } + + @Override + public void startHandshake() throws IOException { + sslSock.startHandshake(); + } + + // Also forward all the Socket methods. + + @Override + public void bind(SocketAddress bindpoint) throws IOException { + sslSock.bind(bindpoint); + } + + @Override + public synchronized void close() throws IOException { + sslSock.close(); + } + + @Override + public void connect(SocketAddress endpoint, int timeout) throws IOException { + sslSock.connect(endpoint, timeout); + } + + @Override + public void connect(SocketAddress endpoint) throws IOException { + sslSock.connect(endpoint); + } + + @Override + public SocketChannel getChannel() { + return sslSock.getChannel(); + } + + @Override + public InetAddress getInetAddress() { + return sslSock.getInetAddress(); + } + + @Override + public boolean getKeepAlive() throws SocketException { + return sslSock.getKeepAlive(); + } + + @Override + public InetAddress getLocalAddress() { + return sslSock.getLocalAddress(); + } + + @Override + public int getLocalPort() { + return sslSock.getLocalPort(); + } + + @Override + public SocketAddress getLocalSocketAddress() { + return sslSock.getLocalSocketAddress(); + } + + @Override + public boolean getOOBInline() throws SocketException { + return sslSock.getOOBInline(); + } + + @Override + public int getPort() { + return sslSock.getPort(); + } + + @Override + public synchronized int getReceiveBufferSize() throws SocketException { + return sslSock.getReceiveBufferSize(); + } + + @Override + public SocketAddress getRemoteSocketAddress() { + return sslSock.getRemoteSocketAddress(); + } + + @Override + public boolean getReuseAddress() throws SocketException { + return sslSock.getReuseAddress(); + } + + @Override + public synchronized int getSendBufferSize() throws SocketException { + return sslSock.getSendBufferSize(); + } + + @Override + public int getSoLinger() throws SocketException { + return sslSock.getSoLinger(); + } + + @Override + public synchronized int getSoTimeout() throws SocketException { + return sslSock.getSoTimeout(); + } + + @Override + public boolean getTcpNoDelay() throws SocketException { + return sslSock.getTcpNoDelay(); + } + + @Override + public int getTrafficClass() throws SocketException { + return sslSock.getTrafficClass(); + } + + @Override + public boolean isBound() { + return sslSock.isBound(); + } + + @Override + public boolean isClosed() { + return sslSock.isClosed(); + } + + @Override + public boolean isConnected() { + return sslSock.isConnected(); + } + + @Override + public boolean isInputShutdown() { + return sslSock.isInputShutdown(); + } + + @Override + public boolean isOutputShutdown() { + return sslSock.isOutputShutdown(); + } + + @Override + public void sendUrgentData(int data) throws IOException { + sslSock.sendUrgentData(data); + } + + @Override + public void setKeepAlive(boolean on) throws SocketException { + sslSock.setKeepAlive(on); + } + + @Override + public void setOOBInline(boolean on) throws SocketException { + sslSock.setOOBInline(on); + } + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException { + sslSock.setReceiveBufferSize(size); + } + + @Override + public void setReuseAddress(boolean on) throws SocketException { + sslSock.setReuseAddress(on); + } + + @Override + public synchronized void setSendBufferSize(int size) throws SocketException { + sslSock.setSendBufferSize(size); + } + + @Override + public void setSoLinger(boolean on, int linger) throws SocketException { + sslSock.setSoLinger(on, linger); + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + sslSock.setSoTimeout(timeout); + } + + @Override + public void setTcpNoDelay(boolean on) throws SocketException { + sslSock.setTcpNoDelay(on); + } + + @Override + public void setTrafficClass(int tc) throws SocketException { + sslSock.setTrafficClass(tc); + } + + @Override + public void shutdownInput() throws IOException { + sslSock.shutdownInput(); + } + + @Override + public void shutdownOutput() throws IOException { + sslSock.shutdownOutput(); + } + + @Override + public String toString() { + return sslSock.toString(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/util/SlowSocket.java b/ApacheJmeter/src/org/apache/jmeter/util/SlowSocket.java new file mode 100644 index 0000000..da32917 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/SlowSocket.java @@ -0,0 +1,110 @@ +/* + * 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; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.UnknownHostException; + +/** + * "Slow" (non-SSL) socket implementation to emulate dial-up modems etc + */ +public class SlowSocket extends Socket { + + private final int CPS; // Characters per second to emulate + + public SlowSocket(final int cps, String host, int port, InetAddress localAddress, int localPort, int timeout) throws IOException { + super(); + if (cps <=0) { + throw new IllegalArgumentException("Speed (cps) <= 0"); + } + CPS=cps; + // This sequence is borrowed from: + // org.apache.commons.httpclient.protocol.ReflectionSocketFactory.createSocket + SocketAddress localaddr = new InetSocketAddress(localAddress, localPort); + SocketAddress remoteaddr = new InetSocketAddress(host, port); + bind(localaddr); + connect(remoteaddr, timeout); + } + + /** + * + * @param cps characters per second + * @param host hostname + * @param port port + * @param localAddr local address + * @param localPort local port + * + * @throws IOException + * @throws IllegalArgumentException if cps <=0 + */ + public SlowSocket(int cps, String host, int port, InetAddress localAddr, int localPort) throws IOException { + super(host, port, localAddr, localPort); + if (cps <=0) { + throw new IllegalArgumentException("Speed (cps) <= 0"); + } + CPS=cps; + } + + /** + * + * @param cps characters per second + * @param host hostname + * @param port port + * + * @throws UnknownHostException + * @throws IOException + * @throws IllegalArgumentException if cps <=0 + */ + public SlowSocket(int cps, String host, int port) throws UnknownHostException, IOException { + super(host, port); + if (cps <=0) { + throw new IllegalArgumentException("Speed (cps) <= 0"); + } + CPS=cps; + } + + /** + * Added for use by SlowHC4SocketFactory. + * + * @param cps + */ + public SlowSocket(int cps) { + super(); + CPS = cps; + } + + // Override so we can intercept the stream + @Override + public OutputStream getOutputStream() throws IOException { + return new SlowOutputStream(super.getOutputStream(), CPS); + } + + // Override so we can intercept the stream + @Override + public InputStream getInputStream() throws IOException { + return new SlowInputStream(super.getInputStream(), CPS); + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/util/StringUtilities.java b/ApacheJmeter/src/org/apache/jmeter/util/StringUtilities.java new file mode 100644 index 0000000..c31cada --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/StringUtilities.java @@ -0,0 +1,53 @@ +/* + * 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; + +public final class StringUtilities { + + /** + * Private constructor to prevent instantiation. + */ + private StringUtilities() { + } + + /** + * Replace all patterns in a String + * + * @see String#replaceAll(String,String) + * - JDK1.4 only + * + * @param input - string to be transformed + * @param pattern - pattern to replace + * @param sub - replacement + * @return the updated string + */ + public static String substitute(final String input, final String pattern, final String sub) { + StringBuilder ret = new StringBuilder(input.length()); + int start = 0; + int index = -1; + final int length = pattern.length(); + while ((index = input.indexOf(pattern, start)) >= start) { + ret.append(input.substring(start, index)); + ret.append(sub); + start = index + length; + } + ret.append(input.substring(start)); + return ret.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/TidyException.java b/ApacheJmeter/src/org/apache/jmeter/util/TidyException.java new file mode 100644 index 0000000..4e69fc2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/TidyException.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * Class for reporting errors when running Tidy. + */ +public class TidyException extends Exception { + + private static final long serialVersionUID = 240L; + + public TidyException() { + this(0,0); + } + + public TidyException(int errors, int warnings){ + super("tidy: " + errors + " errors, " + warnings + " warnings"); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/util/XPathUtil.java b/ApacheJmeter/src/org/apache/jmeter/util/XPathUtil.java new file mode 100644 index 0000000..9b20477 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/XPathUtil.java @@ -0,0 +1,423 @@ +/* + * 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; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.xml.utils.PrefixResolver; +import org.apache.xpath.XPathAPI; +import org.apache.xpath.objects.XObject; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.tidy.Tidy; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * This class provides a few utility methods for dealing with XML/XPath. + */ +public class XPathUtil { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private XPathUtil() { + super(); + } + + //@GuardedBy("this") + private static DocumentBuilderFactory documentBuilderFactory; + + /** + * Returns a suitable document builder factory. + * Caches the factory in case the next caller wants the same options. + * + * @param validate should the parser validate documents? + * @param whitespace should the parser eliminate whitespace in element content? + * @param namespace should the parser be namespace aware? + * + * @return javax.xml.parsers.DocumentBuilderFactory + */ + private static synchronized DocumentBuilderFactory makeDocumentBuilderFactory(boolean validate, boolean whitespace, + boolean namespace) { + if (XPathUtil.documentBuilderFactory == null || documentBuilderFactory.isValidating() != validate + || documentBuilderFactory.isNamespaceAware() != namespace + || documentBuilderFactory.isIgnoringElementContentWhitespace() != whitespace) { + // configure the document builder factory + documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setValidating(validate); + documentBuilderFactory.setNamespaceAware(namespace); + documentBuilderFactory.setIgnoringElementContentWhitespace(whitespace); + } + return XPathUtil.documentBuilderFactory; + } + + /** + * Create a DocumentBuilder using the makeDocumentFactory func. + * + * @param validate should the parser validate documents? + * @param whitespace should the parser eliminate whitespace in element content? + * @param namespace should the parser be namespace aware? + * @param downloadDTDs if true, parser should attempt to resolve external entities + * @return document builder + * @throws ParserConfigurationException + */ + public static DocumentBuilder makeDocumentBuilder(boolean validate, boolean whitespace, boolean namespace, boolean downloadDTDs) + throws ParserConfigurationException { + DocumentBuilder builder = makeDocumentBuilderFactory(validate, whitespace, namespace).newDocumentBuilder(); + builder.setErrorHandler(new MyErrorHandler(validate, false)); + if (!downloadDTDs){ + EntityResolver er = new EntityResolver(){ + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + return new InputSource(new ByteArrayInputStream(new byte[]{})); + } + }; + builder.setEntityResolver(er); + } + return builder; + } + + /** + * Utility function to get new Document + * + * @param stream - Document Input stream + * @param validate - Validate Document (not Tidy) + * @param whitespace - Element Whitespace (not Tidy) + * @param namespace - Is Namespace aware. (not Tidy) + * @param tolerant - Is tolerant - i.e. use the Tidy parser + * @param quiet - set Tidy quiet + * @param showWarnings - set Tidy warnings + * @param report_errors - throw TidyException if Tidy detects an error + * @param isXml - is document already XML (Tidy only) + * @param downloadDTDs - if true, try to download external DTDs + * @return document + * @throws ParserConfigurationException + * @throws SAXException + * @throws IOException + * @throws TidyException + */ + public static Document makeDocument(InputStream stream, boolean validate, boolean whitespace, boolean namespace, + boolean tolerant, boolean quiet, boolean showWarnings, boolean report_errors, boolean isXml, boolean downloadDTDs) + throws ParserConfigurationException, SAXException, IOException, TidyException { + return makeDocument(stream, validate, whitespace, namespace, + tolerant, quiet, showWarnings, report_errors, isXml, downloadDTDs, null); + } + + /** + * Utility function to get new Document + * + * @param stream - Document Input stream + * @param validate - Validate Document (not Tidy) + * @param whitespace - Element Whitespace (not Tidy) + * @param namespace - Is Namespace aware. (not Tidy) + * @param tolerant - Is tolerant - i.e. use the Tidy parser + * @param quiet - set Tidy quiet + * @param showWarnings - set Tidy warnings + * @param report_errors - throw TidyException if Tidy detects an error + * @param isXml - is document already XML (Tidy only) + * @param downloadDTDs - if true, try to download external DTDs + * @param tidyOut OutputStream for Tidy pretty-printing + * @return document + * @throws ParserConfigurationException + * @throws SAXException + * @throws IOException + * @throws TidyException + */ + public static Document makeDocument(InputStream stream, boolean validate, boolean whitespace, boolean namespace, + boolean tolerant, boolean quiet, boolean showWarnings, boolean report_errors, boolean isXml, boolean downloadDTDs, + OutputStream tidyOut) + throws ParserConfigurationException, SAXException, IOException, TidyException { + Document doc; + if (tolerant) { + doc = tidyDoc(stream, quiet, showWarnings, report_errors, isXml, tidyOut); + } else { + doc = makeDocumentBuilder(validate, whitespace, namespace, downloadDTDs).parse(stream); + } + return doc; + } + + /** + * Create a document using Tidy + * + * @param stream - input + * @param quiet - set Tidy quiet? + * @param showWarnings - show Tidy warnings? + * @param report_errors - log errors and throw TidyException? + * @param isXML - treat document as XML? + * @param out OutputStream, null if no output required + * @return the document + * + * @throws TidyException if a ParseError is detected and report_errors is true + */ + private static Document tidyDoc(InputStream stream, boolean quiet, boolean showWarnings, boolean report_errors, + boolean isXML, OutputStream out) throws TidyException { + StringWriter sw = new StringWriter(); + Tidy tidy = makeTidyParser(quiet, showWarnings, isXML, sw); + Document doc = tidy.parseDOM(stream, out); + doc.normalize(); + if (tidy.getParseErrors() > 0) { + if (report_errors) { + log.error("TidyException: " + sw.toString()); + throw new TidyException(tidy.getParseErrors(),tidy.getParseWarnings()); + } + log.warn("Tidy errors: " + sw.toString()); + } + return doc; + } + + /** + * Create a Tidy parser with the specified settings. + * + * @param quiet - set the Tidy quiet flag? + * @param showWarnings - show Tidy warnings? + * @param isXml - treat the content as XML? + * @param stringWriter - if non-null, use this for Tidy errorOutput + * @return the Tidy parser + */ + public static Tidy makeTidyParser(boolean quiet, boolean showWarnings, boolean isXml, StringWriter stringWriter) { + Tidy tidy = new Tidy(); + tidy.setInputEncoding("UTF8"); + tidy.setOutputEncoding("UTF8"); + tidy.setQuiet(quiet); + tidy.setShowWarnings(showWarnings); + tidy.setMakeClean(true); + tidy.setXmlTags(isXml); + if (stringWriter != null) { + tidy.setErrout(new PrintWriter(stringWriter)); + } + return tidy; + } + + static class MyErrorHandler implements ErrorHandler { + private final boolean val, tol; + + private final String type; + + MyErrorHandler(boolean validate, boolean tolerate) { + val = validate; + tol = tolerate; + type = "Val=" + val + " Tol=" + tol; + } + + public void warning(SAXParseException ex) throws SAXException { + log.info("Type=" + type + " " + ex); + if (val && !tol){ + throw new SAXException(ex); + } + } + + public void error(SAXParseException ex) throws SAXException { + log.warn("Type=" + type + " " + ex); + if (val && !tol) { + throw new SAXException(ex); + } + } + + public void fatalError(SAXParseException ex) throws SAXException { + log.error("Type=" + type + " " + ex); + if (val && !tol) { + throw new SAXException(ex); + } + } + } + + /** + * Return value for node + * @param node Node + * @return String + */ + private static String getValueForNode(Node node) { + StringWriter sw = new StringWriter(); + try { + Transformer t = TransformerFactory.newInstance().newTransformer(); + t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + t.transform(new DOMSource(node), new StreamResult(sw)); + } catch (TransformerException e) { + sw.write(e.getMessageAndLocation()); + } + return sw.toString(); + } + + /** + * Extract NodeList using expression + * @param document {@link Document} + * @param xPathExpression XPath expression + * @return {@link NodeList} + * @throws TransformerException + */ + public static NodeList selectNodeList(Document document, String xPathExpression) throws TransformerException { + XObject xObject = XPathAPI.eval(document, xPathExpression, getPrefixResolver(document)); + return xObject.nodelist(); + } + + /** + * Put in matchStrings results of evaluation + * @param document XML document + * @param xPathQuery XPath Query + * @param matchStrings List that will be filled + * @param fragment return fragment + * @throws TransformerException + */ + public static void putValuesForXPathInList(Document document, + String xPathQuery, + List matchStrings, boolean fragment) throws TransformerException { + String val = null; + XObject xObject = XPathAPI.eval(document, xPathQuery, getPrefixResolver(document)); + final int objectType = xObject.getType(); + if (objectType == XObject.CLASS_NODESET) { + NodeList matches = xObject.nodelist(); + int length = matches.getLength(); + for (int i = 0 ; i < length; i++) { + Node match = matches.item(i); + if ( match instanceof Element){ + if (fragment){ + val = getValueForNode(match); + } else { + // elements have empty nodeValue, but we are usually interested in their content + final Node firstChild = match.getFirstChild(); + if (firstChild != null) { + val = firstChild.getNodeValue(); + } else { + val = match.getNodeValue(); // TODO is this correct? + } + } + } else { + val = match.getNodeValue(); + } + matchStrings.add(val); + } + } else if (objectType == XObject.CLASS_NULL + || objectType == XObject.CLASS_UNKNOWN + || objectType == XObject.CLASS_UNRESOLVEDVARIABLE) { + log.warn("Unexpected object type: "+xObject.getTypeString()+" returned for: "+xPathQuery); + } else { + val = xObject.toString(); + matchStrings.add(val); + } + } + + /** + * + * @param document XML Document + * @return {@link PrefixResolver} + */ + private static PrefixResolver getPrefixResolver(Document document) { + PropertiesBasedPrefixResolver propertiesBasedPrefixResolver = + new PropertiesBasedPrefixResolver(document.getDocumentElement()); + return propertiesBasedPrefixResolver; + } + + /** + * Validate xpathString is a valid XPath expression + * @param document XML Document + * @param xpathString XPATH String + * @throws TransformerException if expression fails to evaluate + */ + public static void validateXPath(Document document, String xpathString) throws TransformerException { + if (XPathAPI.eval(document, xpathString, getPrefixResolver(document)) == null) { + // We really should never get here + // because eval will throw an exception + // if xpath is invalid, but whatever, better + // safe + throw new IllegalArgumentException("xpath eval of '" + xpathString + "' was null"); + } + } + + /** + * Fills result + * @param result {@link AssertionResult} + * @param doc XML Document + * @param xPathExpression XPath expression + * @param isNegated + */ + public static void computeAssertionResult(AssertionResult result, + Document doc, + String xPathExpression, + boolean isNegated) { + try { + XObject xObject = XPathAPI.eval(doc, xPathExpression, getPrefixResolver(doc)); + switch (xObject.getType()) { + case XObject.CLASS_NODESET: + NodeList nodeList = xObject.nodelist(); + if (nodeList == null || nodeList.getLength() == 0) { + if (log.isDebugEnabled()) { + log.debug(new StringBuilder("nodeList null no match ").append(xPathExpression).toString()); + } + result.setFailure(!isNegated); + result.setFailureMessage("No Nodes Matched " + xPathExpression); + return; + } + if (log.isDebugEnabled()) { + log.debug("nodeList length " + nodeList.getLength()); + if (!isNegated) { + for (int i = 0; i < nodeList.getLength(); i++){ + log.debug(new StringBuilder("nodeList[").append(i).append("] ").append(nodeList.item(i)).toString()); + } + } + } + result.setFailure(isNegated); + if (isNegated) { + result.setFailureMessage("Specified XPath was found... Turn off negate if this is not desired"); + } + return; + case XObject.CLASS_BOOLEAN: + if (!xObject.bool()){ + result.setFailure(!isNegated); + result.setFailureMessage("No Nodes Matched " + xPathExpression); + } + return; + default: + result.setFailure(true); + result.setFailureMessage("Cannot understand: " + xPathExpression); + return; + } + } catch (TransformerException e) { + result.setError(true); + result.setFailureMessage( + new StringBuilder("TransformerException: ") + .append(e.getMessage()) + .append(" for:") + .append(xPathExpression) + .toString()); + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/util/keystore/JmeterKeyStore.java b/ApacheJmeter/src/org/apache/jmeter/util/keystore/JmeterKeyStore.java new file mode 100644 index 0000000..29be255 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/util/keystore/JmeterKeyStore.java @@ -0,0 +1,238 @@ +/* + * 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.keystore; + +import java.io.InputStream; +import java.security.KeyStore; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Enumeration; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Use this Keystore for JMeter specific KeyStores. + * + */ +public class JmeterKeyStore { + + private static final Logger LOG = LoggingManager.getLoggerForClass(); + + private final KeyStore store; + private final int startIndex; + private final int endIndex; + + private X509Certificate[][] certChains; + private PrivateKey[] keys; + private String[] names; + + //@GuardedBy("this") + private int last_user; + + private JmeterKeyStore(String type, int startIndex, int endIndex) throws Exception { + if (startIndex < 0 || endIndex < 0 || endIndex < startIndex) { + throw new IllegalArgumentException("Invalid index(es). Start="+startIndex+", end="+endIndex); + } + this.store = KeyStore.getInstance(type); + this.startIndex = startIndex; + this.endIndex = endIndex; + } + + /** + * Process the input stream + */ + public void load(InputStream is, String pword) throws Exception { + store.load(is, pword.toCharArray()); + + ArrayList v_names = new ArrayList(); + ArrayList v_keys = new ArrayList(); + ArrayList v_certChains = new ArrayList(); + + if (null != is){ // No point checking an empty keystore + PrivateKey _key = null; + int index = 0; + Enumeration aliases = store.aliases(); + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + if (store.isKeyEntry(alias)) { + if ((index >= startIndex && index <= endIndex)) { + _key = (PrivateKey) store.getKey(alias, pword.toCharArray()); + if (null == _key) { + throw new Exception("No key found for alias: " + alias); // Should not happen + } + Certificate[] chain = store.getCertificateChain(alias); + if (null == chain) { + throw new Exception("No certificate chain found for alias: " + alias); + } + v_names.add(alias); + v_keys.add(_key); + X509Certificate[] x509certs = new X509Certificate[chain.length]; + for (int i = 0; i < x509certs.length; i++) { + x509certs[i] = (X509Certificate)chain[i]; + } + v_certChains.add(x509certs); + } + } + index++; + } + + if (null == _key) { + throw new Exception("No key(s) found"); + } + if (index <= endIndex-startIndex) { + LOG.warn("Did not find all requested aliases. Start="+startIndex+", end="+endIndex+", found="+index); + } + } + + /* + * Note: if is == null, the arrays will be empty + */ + int v_size = v_names.size(); + + this.names = new String[v_size]; + this.names = v_names.toArray(names); + + this.keys = new PrivateKey[v_size]; + this.keys = v_keys.toArray(keys); + + this.certChains = new X509Certificate[v_size][]; + this.certChains = v_certChains.toArray(certChains); + } + + + /** + * Get the ordered certificate chain for a specific alias. + */ + public X509Certificate[] getCertificateChain(String alias) { + int entry = findAlias(alias); + if (entry >=0) { + return this.certChains[entry]; + } + return null; + } + + /** + * Get the next or only alias. + * @return the next or only alias. + */ + public String getAlias() { + int length = this.names.length; + if (length == 0) { // i.e. is == null + return null; + } + return this.names[getIndexAndIncrement(length)]; + } + + public int getAliasCount() { + return this.names.length; + } + + public String getAlias(int index) { + int length = this.names.length; + if (length == 0 && index == 0) { // i.e. is == null + return null; + } + if (index >= length || index < 0) { + throw new ArrayIndexOutOfBoundsException(index); + } + return this.names[index]; + } + + /** + * Return the private Key for a specific alias + */ + public PrivateKey getPrivateKey(String alias) { + int entry = findAlias(alias); + if (entry >=0) { + return this.keys[entry]; + } + return null; + } + + /** + * Create a keystore which returns a range of aliases (if available) + * @param type store type (e.g. JKS) + * @param startIndex first index (from 0) + * @param endIndex last index (to count -1) + * @return the keystore + * @throws Exception + */ + public static JmeterKeyStore getInstance(String type, int startIndex, int endIndex) throws Exception { + return new JmeterKeyStore(type, startIndex, endIndex); + } + + /** + * Create a keystore which returns the first alias only. + * @param type e.g. JKS + * @return the keystore + * @throws Exception + */ + public static JmeterKeyStore getInstance(String type) throws Exception { + return new JmeterKeyStore(type, 0, 0); + } + + private int findAlias(String alias) { + for(int i = 0; i < names.length; i++) { + if (alias.equals(names[i])){ + return i; + } + } + return -1; + } + + /** + * Gets current index and increment by rolling if index is equal to length + * @param length Number of keys to roll + */ + private int getIndexAndIncrement(int length) { + synchronized(this) { + int result = last_user++; + if (last_user >= length) { + last_user = 0; + } + return result; + } + } + + /** + * Compiles the list of all client aliases with a private key. + * TODO Currently, keyType and issuers are both ignored. + * + * @param keyType the key algorithm type name (RSA, DSA, etc.) + * @param issuers the CA certificates we are narrowing our selection on. + * + * @return the array of aliases; may be empty + */ + public String[] getClientAliases(String keyType, Principal[] issuers) { + int count = getAliasCount(); + String[] aliases = new String[count]; + for(int i = 0; i < aliases.length; i++) { +// if (keys[i].getAlgorithm().equals(keyType)){ +// +// } + aliases[i] = this.names[i]; + } + return aliases; + } + +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/AccumListener.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/AccumListener.java new file mode 100644 index 0000000..d3d3fd6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/AccumListener.java @@ -0,0 +1,24 @@ +/* + * 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.visualizers; + +public interface AccumListener { + + public void updateGui(RunningSample s); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/AssertionVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/AssertionVisualizer.java new file mode 100644 index 0000000..3bb80af --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/AssertionVisualizer.java @@ -0,0 +1,119 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Dimension; + +import javax.swing.Box; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +public class AssertionVisualizer extends AbstractVisualizer implements Clearable { + + private static final long serialVersionUID = 240L; + + private JTextArea textArea; + + public AssertionVisualizer() { + init(); + setName(getStaticLabel()); + } + + public String getLabelResource() { + return "assertion_visualizer_title"; // $NON-NLS-1$ + } + + public void add(SampleResult sample) { + final StringBuilder sb = new StringBuilder(100); + sb.append(sample.getSampleLabel()); + sb.append(getAssertionResult(sample)); + sb.append("\n"); // $NON-NLS-1$ + JMeterUtils.runSafe(new Runnable() { + public void run() { + synchronized (textArea) { + textArea.append(sb.toString()); + textArea.setCaretPosition(textArea.getText().length()); + } + } + }); + } + + public void clearData() { + textArea.setText(""); // $NON-NLS-1$ + } + + private String getAssertionResult(SampleResult res) { + if (res != null) { + StringBuilder display = new StringBuilder(); + AssertionResult assertionResults[] = res.getAssertionResults(); + for (int i = 0; i < assertionResults.length; i++) { + AssertionResult item = assertionResults[i]; + + if (item.isFailure() || item.isError()) { + display.append("\n\t"); // $NON-NLS-1$ + display.append(item.getName() != null ? item.getName() + " : " : "");// $NON-NLS-1$ + display.append(item.getFailureMessage()); + } + } + return display.toString(); + } + return ""; + } + + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + Border margin = new EmptyBorder(10, 10, 5, 10); + + this.setBorder(margin); + + // NAME + this.add(makeTitlePanel(), BorderLayout.NORTH); + + // TEXTAREA LABEL + JLabel textAreaLabel = + new JLabel(JMeterUtils.getResString("assertion_textarea_label")); // $NON-NLS-1$ + Box mainPanel = Box.createVerticalBox(); + mainPanel.add(textAreaLabel); + + // TEXTAREA + textArea = new JTextArea(); + textArea.setEditable(false); + textArea.setLineWrap(false); + JScrollPane areaScrollPane = new JScrollPane(textArea); + + areaScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + areaScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + + areaScrollPane.setPreferredSize(new Dimension(mainPanel.getWidth(),mainPanel.getHeight())); + mainPanel.add(areaScrollPane); + this.add(mainPanel, BorderLayout.CENTER); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/AxisGraph.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/AxisGraph.java new file mode 100644 index 0000000..6a2d85f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/AxisGraph.java @@ -0,0 +1,421 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.LayoutManager; +import java.awt.Paint; +import java.math.BigDecimal; + +import javax.swing.JPanel; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.jCharts.axisChart.AxisChart; +import org.jCharts.axisChart.customRenderers.axisValue.renderers.ValueLabelPosition; +import org.jCharts.axisChart.customRenderers.axisValue.renderers.ValueLabelRenderer; +import org.jCharts.chartData.AxisChartDataSet; +import org.jCharts.chartData.ChartDataException; +import org.jCharts.chartData.DataSeries; +import org.jCharts.properties.AxisProperties; +import org.jCharts.properties.ChartProperties; +import org.jCharts.properties.ClusteredBarChartProperties; +import org.jCharts.properties.DataAxisProperties; +import org.jCharts.properties.LabelAxisProperties; +import org.jCharts.properties.LegendProperties; +import org.jCharts.properties.PropertyException; +import org.jCharts.properties.util.ChartFont; +import org.jCharts.types.ChartType; + +/** + * + * Axis graph is used by StatGraphVisualizer, which generates bar graphs + * from the statistical data. + */ +public class AxisGraph extends JPanel { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String ELLIPSIS = "..."; //$NON-NLS-1$ + private static final int ELLIPSIS_LEN = ELLIPSIS.length(); + + protected double[][] data = null; + protected String title, xAxisTitle, yAxisTitle, yAxisLabel; + protected int maxLength; + protected String[] xAxisLabels; + protected int width, height; + + protected String[] legendLabels = { JMeterUtils.getResString("aggregate_graph_legend") }; + + protected int maxYAxisScale; + + protected Font titleFont; + + protected Font legendFont; + + protected Font valueFont = new Font("SansSerif", Font.PLAIN, 8); + + protected Color[] color = { Color.YELLOW }; + + protected Color foreColor = Color.BLACK; + + protected boolean outlinesBarFlag = false; + + protected boolean showGrouping = true; + + protected boolean valueOrientation = true; + + protected int legendPlacement = LegendProperties.BOTTOM; + + /** + * + */ + public AxisGraph() { + super(); + } + + /** + * @param layout + */ + public AxisGraph(LayoutManager layout) { + super(layout); + } + + /** + * @param layout + * @param isDoubleBuffered + */ + public AxisGraph(LayoutManager layout, boolean isDoubleBuffered) { + super(layout, isDoubleBuffered); + } + + public void setData(double[][] data) { + this.data = data; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setMaxLength(int maxLength) { + this.maxLength = maxLength; + } + + public void setXAxisTitle(String title) { + this.xAxisTitle = title; + } + + public void setYAxisTitle(String title) { + this.yAxisTitle = title; + } + + public void setXAxisLabels(String[] labels) { + this.xAxisLabels = labels; + } + + public void setYAxisLabels(String label) { + this.yAxisLabel = label; + } + + public void setLegendLabels(String[] labels) { + this.legendLabels = labels; + } + + public void setWidth(int w) { + this.width = w; + } + + public void setHeight(int h) { + this.height = h; + } + + /** + * @return the maxYAxisScale + */ + public int getMaxYAxisScale() { + return maxYAxisScale; + } + + /** + * @param maxYAxisScale the maxYAxisScale to set + */ + public void setMaxYAxisScale(int maxYAxisScale) { + this.maxYAxisScale = maxYAxisScale; + } + + /** + * @return the color + */ + public Color[] getColor() { + return color; + } + + /** + * @param color the color to set + */ + public void setColor(Color[] color) { + this.color = color; + } + + /** + * @return the foreColor + */ + public Color getForeColor() { + return foreColor; + } + + /** + * @param foreColor the foreColor to set + */ + public void setForeColor(Color foreColor) { + this.foreColor = foreColor; + } + + /** + * @return the titleFont + */ + public Font getTitleFont() { + return titleFont; + } + + /** + * @param titleFont the titleFont to set + */ + public void setTitleFont(Font titleFont) { + this.titleFont = titleFont; + } + + /** + * @return the legendFont + */ + public Font getLegendFont() { + return legendFont; + } + + /** + * @param legendFont the legendFont to set + */ + public void setLegendFont(Font legendFont) { + this.legendFont = legendFont; + } + + /** + * @return the valueFont + */ + public Font getValueFont() { + return valueFont; + } + + /** + * @param valueFont the valueFont to set + */ + public void setValueFont(Font valueFont) { + this.valueFont = valueFont; + } + + /** + * @return the legendPlacement + */ + public int getLegendPlacement() { + return legendPlacement; + } + + /** + * @param legendPlacement the legendPlacement to set + */ + public void setLegendPlacement(int legendPlacement) { + this.legendPlacement = legendPlacement; + } + + /** + * @return the outlinesBarFlag + */ + public boolean isOutlinesBarFlag() { + return outlinesBarFlag; + } + + /** + * @param outlinesBarFlag the outlinesBarFlag to set + */ + public void setOutlinesBarFlag(boolean outlinesBarFlag) { + this.outlinesBarFlag = outlinesBarFlag; + } + + /** + * @return the valueOrientation + */ + public boolean isValueOrientation() { + return valueOrientation; + } + + /** + * @param valueOrientation the valueOrientation to set + */ + public void setValueOrientation(boolean valueOrientation) { + this.valueOrientation = valueOrientation; + } + + /** + * @return the showGrouping + */ + public boolean isShowGrouping() { + return showGrouping; + } + + /** + * @param showGrouping the showGrouping to set + */ + public void setShowGrouping(boolean showGrouping) { + this.showGrouping = showGrouping; + } + + @Override + public void paintComponent(Graphics graphics) { + if (data != null && this.title != null && this.xAxisLabels != null && + this.xAxisTitle != null && this.yAxisLabel != null && + this.yAxisTitle != null) { + drawSample(this.title, this.maxLength, this.xAxisLabels, + this.xAxisTitle, this.yAxisTitle, this.legendLabels, + this.data, this.width, this.height, this.color, + this.legendFont, graphics); + } + } + + private double findMax(double _data[][]) { + double max = 0; + max = _data[0][0]; + for (int i = 0; i < _data.length; i++) { + for (int j = 0; j < _data[i].length; j++) { + if (_data[i][j] > max) { + max = _data[i][j]; + } + } + } + return max; + } + + private String squeeze (String input, int _maxLength){ + if (input.length()>_maxLength){ + String output=input.substring(0,_maxLength-ELLIPSIS_LEN)+ELLIPSIS; + return output; + } + return input; + } + + private void drawSample(String _title, int _maxLength, String[] _xAxisLabels, String _xAxisTitle, + String _yAxisTitle, String[] _legendLabels, double[][] _data, int _width, int _height, Color[] _color, Font font, Graphics g) { + double max = maxYAxisScale > 0 ? maxYAxisScale : findMax(_data); // define max scale y axis + try { + /** These controls are already done in StatGraphVisualizer + if (_width == 0) { + _width = 450; + } + if (_height == 0) { + _height = 250; + } + **/ + if (_maxLength < 3) { + _maxLength = 3; + } + // if the "Title of Graph" is empty, we can assume some default + if (_title.length() == 0 ) { + _title = JMeterUtils.getResString("aggregate_graph_title"); //$NON-NLS-1$ + } + // if the labels are too long, they'll be "squeezed" to make the chart viewable. + for (int i = 0; i < _xAxisLabels.length; i++) { + String label = _xAxisLabels[i]; + _xAxisLabels[i]=squeeze(label, _maxLength); + } + this.setPreferredSize(new Dimension(_width,_height)); + DataSeries dataSeries = new DataSeries( _xAxisLabels, null, _yAxisTitle, _title ); // replace _xAxisTitle to null (don't display x axis title) + + ClusteredBarChartProperties clusteredBarChartProperties= new ClusteredBarChartProperties(); + clusteredBarChartProperties.setShowOutlinesFlag(outlinesBarFlag); + ValueLabelRenderer valueLabelRenderer = new ValueLabelRenderer(false, false, showGrouping, 0); + valueLabelRenderer.setValueLabelPosition(ValueLabelPosition.AT_TOP); + + valueLabelRenderer.setValueChartFont(new ChartFont(valueFont, foreColor)); + valueLabelRenderer.useVerticalLabels(valueOrientation); + + clusteredBarChartProperties.addPostRenderEventListener(valueLabelRenderer); + + Paint[] paints = new Paint[_color.length]; + for (int i = 0; i < _color.length; i++) { + paints[i] = _color[i]; + } + + AxisChartDataSet axisChartDataSet = + new AxisChartDataSet( + _data, _legendLabels, paints, ChartType.BAR_CLUSTERED, clusteredBarChartProperties ); + dataSeries.addIAxisPlotDataSet( axisChartDataSet ); + + ChartProperties chartProperties= new ChartProperties(); + LabelAxisProperties xaxis = new LabelAxisProperties(); + DataAxisProperties yaxis = new DataAxisProperties(); + yaxis.setUseCommas(showGrouping); + + if (legendFont != null) { + yaxis.setAxisTitleChartFont(new ChartFont(legendFont, new Color(20))); + yaxis.setScaleChartFont(new ChartFont(legendFont, new Color(20))); + xaxis.setAxisTitleChartFont(new ChartFont(legendFont, new Color(20))); + xaxis.setScaleChartFont(new ChartFont(legendFont, new Color(20))); + } + if (titleFont != null) { + chartProperties.setTitleFont(new ChartFont(titleFont, new Color(0))); + } + + // Y Axis + try { + BigDecimal round = new BigDecimal(max / 1000d); + round = round.setScale(0, BigDecimal.ROUND_UP); + double topValue = round.doubleValue() * 1000; + yaxis.setUserDefinedScale(0, 500); + yaxis.setNumItems((int) (topValue / 500)+1); + yaxis.setShowGridLines(1); + } catch (PropertyException e) { + log.warn("",e); + } + + AxisProperties axisProperties= new AxisProperties(xaxis, yaxis); + axisProperties.setXAxisLabelsAreVertical(true); + LegendProperties legendProperties= new LegendProperties(); + legendProperties.setBorderStroke(null); + legendProperties.setPlacement(legendPlacement); + if (legendFont != null) { + legendProperties.setFont(legendFont); //new Font("SansSerif", Font.PLAIN, 10) + } + AxisChart axisChart = new AxisChart( + dataSeries, chartProperties, axisProperties, + legendProperties, _width, _height ); + axisChart.setGraphics2D((Graphics2D) g); + axisChart.render(); + } catch (ChartDataException e) { + log.warn("",e); + } catch (PropertyException e) { + log.warn("",e); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/BSFListener.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/BSFListener.java new file mode 100644 index 0000000..031f18e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/BSFListener.java @@ -0,0 +1,75 @@ +/* + * 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.visualizers; + +import org.apache.bsf.BSFException; +import org.apache.bsf.BSFManager; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BSFTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class BSFListener extends BSFTestElement + implements Cloneable, SampleListener, TestBean, Visualizer { +// N.B. Needs to implement Visualizer so that TestBeanGUI can find the correct GUI class + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 234L; + + public void sampleOccurred(SampleEvent event) { + BSFManager mgr =null; + try { + mgr = getManager(); + if (mgr == null) { + log.error("Problem creating BSF manager"); + return; + } + mgr.declareBean("sampleEvent", event, SampleEvent.class); + SampleResult result = event.getResult(); + mgr.declareBean("sampleResult", result, SampleResult.class); + processFileOrScript(mgr); + } catch (BSFException e) { + log.warn("Problem in BSF script "+e); + } finally { + if (mgr != null) { + mgr.terminate(); + } + } + } + + public void sampleStarted(SampleEvent e) { + // NOOP + } + + public void sampleStopped(SampleEvent e) { + // NOOP + } + + public void add(SampleResult sample) { + // NOOP + } + + public boolean isStats() { + return false; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/BSFListenerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/BSFListenerBeanInfo.java new file mode 100644 index 0000000..fbaad27 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/BSFListenerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.visualizers; + +import org.apache.jmeter.util.BSFBeanInfoSupport; + +public class BSFListenerBeanInfo extends BSFBeanInfoSupport { + + public BSFListenerBeanInfo() { + super(BSFListener.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/BarGraph.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/BarGraph.java new file mode 100644 index 0000000..8c854fb --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/BarGraph.java @@ -0,0 +1,90 @@ +/* + * 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.visualizers; + +import java.awt.Color; + +import javax.swing.JCheckBox; + +import org.apache.jmeter.util.JMeterUtils; + +public class BarGraph { + + private String label; + + private JCheckBox chkBox; + + private Color backColor; + + /** + * @param resString + * @param checked + * @param backColor + */ + public BarGraph(String resString, boolean checked, Color backColor) { + super(); + this.label = JMeterUtils.getResString(resString); + this.chkBox = new JCheckBox(this.label, checked); + this.backColor = backColor; + } + + /** + * @return the label + */ + public String getLabel() { + return label; + } + + /** + * @param label the label to set + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * @return the chkBox + */ + public JCheckBox getChkBox() { + return chkBox; + } + + /** + * @param chkBox the chkBox to set + */ + public void setChkBox(JCheckBox chkBox) { + this.chkBox = chkBox; + } + + /** + * @return the backColor + */ + public Color getBackColor() { + return backColor; + } + + /** + * @param backColor the backColor to set + */ + public void setBackColor(Color backColor) { + this.backColor = backColor; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/BeanShellListener.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/BeanShellListener.java new file mode 100644 index 0000000..79ac75e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/BeanShellListener.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.visualizers; + +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.BeanShellInterpreter; +import org.apache.jmeter.util.BeanShellTestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterException; +import org.apache.log.Logger; + +public class BeanShellListener extends BeanShellTestElement + implements Cloneable, SampleListener, TestBean, Visualizer, UnsharedComponent { + // N.B. Needs to implement Visualizer so that TestBeanGUI can find the correct GUI class + // TODO - remove UnsharedComponent ? Probably does not make sense for a TestBean. + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 4; + + // can be specified in jmeter.properties + private static final String INIT_FILE = "beanshell.listener.init"; //$NON-NLS-1$ + + @Override + protected String getInitFileProperty() { + return INIT_FILE; + } + + public void sampleOccurred(SampleEvent se) { + final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter(); + if (bshInterpreter == null) { + log.error("BeanShell not found"); + return; + } + + SampleResult samp=se.getResult(); + try { + bshInterpreter.set("sampleEvent", se);//$NON-NLS-1$ + bshInterpreter.set("sampleResult", samp);//$NON-NLS-1$ + processFileOrScript(bshInterpreter); + } catch (JMeterException e) { + log.warn("Problem in BeanShell script "+e); + } + } + + public void sampleStarted(SampleEvent e) { + // NOOP + } + + public void sampleStopped(SampleEvent e) { + // NOOP + } + + public void add(SampleResult sample) { + // NOOP + } + + public boolean isStats() { // Needed by Visualizer interface + return false; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/BeanShellListenerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/BeanShellListenerBeanInfo.java new file mode 100644 index 0000000..beb4caa --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/BeanShellListenerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.visualizers; + +import org.apache.jmeter.util.BeanShellBeanInfoSupport; + +public class BeanShellListenerBeanInfo extends BeanShellBeanInfoSupport { + + public BeanShellListenerBeanInfo() { + super(BeanShellListener.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/CachingStatCalculator.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/CachingStatCalculator.java new file mode 100644 index 0000000..a48df26 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/CachingStatCalculator.java @@ -0,0 +1,66 @@ +/* + * 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.visualizers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.jmeter.samplers.SampleResult; + +/** + * Provides storage of samples in addition to calculations + */ +public class CachingStatCalculator extends SamplingStatCalculator { + + private final List storedValues = Collections.synchronizedList(new ArrayList()); + + public CachingStatCalculator(String string) { + super(string); + } + + public List getSamples() { + return storedValues; + } + + public Sample getSample(int index) { + synchronized( storedValues ){ + if (index < storedValues.size()) { + return storedValues.get(index); + } + } + return null; + } + + @Override + public void clear() { + super.clear(); + storedValues.clear(); + } + /** + * Records a sample. + * + */ + @Override + public Sample addSample(SampleResult res) { + final Sample sample = super.addSample(res); + storedValues.add(sample); + return sample; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/ComparisonVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/ComparisonVisualizer.java new file mode 100644 index 0000000..194d886 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/ComparisonVisualizer.java @@ -0,0 +1,169 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridLayout; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextPane; +import javax.swing.JTree; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.assertions.CompareAssertionResult; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +public class ComparisonVisualizer extends AbstractVisualizer implements Clearable { + private static final long serialVersionUID = 240L; + + private JTree resultsTree; + + private DefaultTreeModel treeModel; + + private DefaultMutableTreeNode root; + + private JTextPane base, secondary; + + public ComparisonVisualizer() { + super(); + init(); + } + + public void add(final SampleResult sample) { + JMeterUtils.runSafe(new Runnable() { + public void run() { + DefaultMutableTreeNode currNode = new DefaultMutableTreeNode(sample); + treeModel.insertNodeInto(currNode, root, root.getChildCount()); + if (root.getChildCount() == 1) { + resultsTree.expandPath(new TreePath(root)); + } + } + }); + } + + public String getLabelResource() { + return "comparison_visualizer_title"; //$NON-NLS-1$ + } + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + split.add(getTreePanel()); + split.add(getSideBySidePanel()); + add(split, BorderLayout.CENTER); + } + + private JComponent getSideBySidePanel() { + JPanel main = new JPanel(new GridLayout(1, 2)); + JScrollPane base = new JScrollPane(getBaseTextPane()); + base.setPreferredSize(base.getMinimumSize()); + JScrollPane secondary = new JScrollPane(getSecondaryTextPane()); + secondary.setPreferredSize(secondary.getMinimumSize()); + main.add(base); + main.add(secondary); + main.setPreferredSize(main.getMinimumSize()); + return main; + } + + private JTextPane getBaseTextPane() { + base = new JTextPane(); + base.setEditable(false); + base.setBackground(getBackground()); + return base; + } + + private JTextPane getSecondaryTextPane() { + secondary = new JTextPane(); + secondary.setEditable(false); + return secondary; + } + + private JComponent getTreePanel() { + root = new DefaultMutableTreeNode("Root"); //$NON-NLS-1$ + treeModel = new DefaultTreeModel(root); + resultsTree = new JTree(treeModel); + resultsTree.setCellRenderer(new TreeNodeRenderer()); + resultsTree.setCellRenderer(new TreeNodeRenderer()); + resultsTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + resultsTree.addTreeSelectionListener(new Selector()); + resultsTree.setRootVisible(false); + resultsTree.setShowsRootHandles(true); + + JScrollPane treePane = new JScrollPane(resultsTree); + treePane.setPreferredSize(new Dimension(150, 50)); + JPanel panel = new JPanel(new GridLayout(1, 1)); + panel.add(treePane); + return panel; + } + + private class Selector implements TreeSelectionListener { + /** + * {@inheritDoc} + */ + public void valueChanged(TreeSelectionEvent e) { + try { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) resultsTree.getLastSelectedPathComponent(); + SampleResult sr = (SampleResult) node.getUserObject(); + AssertionResult[] results = sr.getAssertionResults(); + CompareAssertionResult result = null; + for (AssertionResult r : results) { + if (r instanceof CompareAssertionResult) { + result = (CompareAssertionResult) r; + break; + } + } + if (result == null) + result = new CompareAssertionResult(getName()); + base.setText(result.getBaseResult()); + secondary.setText(result.getSecondaryResult()); + } catch (Exception err) { + base.setText(JMeterUtils.getResString("comparison_invalid_node") + err); //$NON-NLS-1$ + secondary.setText(JMeterUtils.getResString("comparison_invalid_node") + err); //$NON-NLS-1$ + } + base.setCaretPosition(0); + secondary.setCaretPosition(0); + } + } + + public void clearData() { + while (root.getChildCount() > 0) { + // the child to be removed will always be 0 'cos as the nodes are + // removed the nth node will become (n-1)th + treeModel.removeNodeFromParent((DefaultMutableTreeNode) root.getChildAt(0)); + base.setText(""); //$NON-NLS-1$ + secondary.setText(""); //$NON-NLS-1$ + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/DistributionGraph.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/DistributionGraph.java new file mode 100644 index 0000000..5dc6e79 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/DistributionGraph.java @@ -0,0 +1,211 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JComponent; +import javax.swing.Scrollable; + +import org.apache.jmeter.samplers.Clearable; +// import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.math.NumberComparator; + +/** + * New graph for drawing distribution graph of the results. It is intended as a + * way to view the data after the stress has been performed. Although it can be + * used at runtime, it is not recommended, since it is rather intensive. The + * graph will draw a red line at 90% and an orange line at 50%. I like + * distribution graphs because they allow me to see how the data clumps. In + * general, the data will tend to clump in predictable ways when the application + * is well designed and implemented. Data that generates erratic graphs are + * generally not desirable. + * + */ +public class DistributionGraph extends JComponent implements Scrollable, Clearable { + + private static final long serialVersionUID = 240L; + + private SamplingStatCalculator model; + + private static final int xborder = 30; + + /** + * Constructor for the Graph object. + */ + public DistributionGraph() { + init(); + } + + /** + * Constructor for the Graph object. + */ + public DistributionGraph(SamplingStatCalculator model) { + this(); + setModel(model); + } + + private void init() {// called from ctor, so must not be overridable + repaint(); + } + + /** + * Gets the ScrollableTracksViewportWidth attribute of the Graph object. + * + * @return the ScrollableTracksViewportWidth value + */ + public boolean getScrollableTracksViewportWidth() { + return true; + } + + /** + * Gets the ScrollableTracksViewportHeight attribute of the Graph object. + * + * @return the ScrollableTracksViewportHeight value + */ + public boolean getScrollableTracksViewportHeight() { + return true; + } + + /** + * Sets the Model attribute of the Graph object. + */ + private void setModel(Object model) { + this.model = (SamplingStatCalculator) model; + repaint(); + } + + /** + * Gets the PreferredScrollableViewportSize attribute of the Graph object. + * + * @return the PreferredScrollableViewportSize value + */ + public Dimension getPreferredScrollableViewportSize() { + return this.getPreferredSize(); + } + + /** + * Gets the ScrollableUnitIncrement attribute of the Graph object. + * + * @return the ScrollableUnitIncrement value + */ + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return 5; + } + + /** + * Gets the ScrollableBlockIncrement attribute of the Graph object. + * + * @return the ScrollableBlockIncrement value + */ + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + return (int) (visibleRect.width * .9); + } + + /** + * Clears this graph. + */ + public void clearData() { + model.clear(); + } + + /** + * Method is responsible for calling drawSample and updating the graph. + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + final SamplingStatCalculator m = this.model; + synchronized (m) { + drawSample(m, g); + } + } + + private void drawSample(SamplingStatCalculator p_model, Graphics g) { + int width = getWidth(); + double height = getHeight() - 1.0; + + // first lets draw the grid + for (int y = 0; y < 4; y++) { + int q1 = (int) (height - (height * 0.25 * y)); + g.setColor(Color.lightGray); + g.drawLine(xborder, q1, width, q1); + g.setColor(Color.black); + g.drawString(String.valueOf((25 * y) + "%"), 0, q1); + } + g.setColor(Color.black); + // draw the X axis + g.drawLine(xborder, (int) height, width, (int) height); + // draw the Y axis + g.drawLine(xborder, 0, xborder, (int) height); + // the test plan has to have more than 200 samples + // for it to generate half way decent distribution + // graph. the larger the sample, the better the + // results. + if (p_model != null && p_model.getCount() > 50) { + // now draw the bar chart + Number ninety = p_model.getPercentPoint(0.90); + Number fifty = p_model.getPercentPoint(0.50); + + long total = p_model.getCount(); + Collection values = p_model.getDistribution().values(); + Number[][] objval = values.toArray(new Number[values.size()][]); + // we sort the objects + Arrays.sort(objval, new NumberComparator()); + int len = objval.length; + for (int count = 0; count < len; count++) { + // calculate the height + Number[] num = objval[count]; + double iper = (double) num[1].intValue() / (double) total; + double iheight = height * iper; + // if the height is less than one, we set it + // to one pixel + if (iheight < 1) { + iheight = 1.0; + } + int ix = (count * 4) + xborder + 5; + int dheight = (int) (height - iheight); + g.setColor(Color.blue); + g.drawLine(ix - 1, (int) height, ix - 1, dheight); + g.drawLine(ix, (int) height, ix, dheight); + g.setColor(Color.black); + // draw a red line for 90% point + if (num[0].longValue() == ninety.longValue()) { + g.setColor(Color.red); + g.drawLine(ix, (int) height, ix, 55); + g.drawLine(ix, 35, ix, 0); + g.drawString("90%", ix - 30, 20); + g.drawString(String.valueOf(num[0].longValue()), ix + 8, 20); + } + // draw an orange line for 50% point + if (num[0].longValue() == fifty.longValue()) { + g.setColor(Color.orange); + g.drawLine(ix, (int) height, ix, 30); + g.drawString("50%", ix - 30, 50); + g.drawString(String.valueOf(num[0].longValue()), ix + 8, 50); + } + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/DistributionGraphVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/DistributionGraphVisualizer.java new file mode 100644 index 0000000..8d2836a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/DistributionGraphVisualizer.java @@ -0,0 +1,236 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Image; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +/** + * This class implements the visualizer for displaying the distribution graph. + * Distribution graphs are useful for standard benchmarks and viewing the + * distribution of data points. Results tend to clump together. + * + * Created May 25, 2004 + */ +public class DistributionGraphVisualizer extends AbstractVisualizer implements ImageVisualizer, GraphListener, + Clearable { + private static final long serialVersionUID = 240L; + + private SamplingStatCalculator model; + + private JPanel graphPanel = null; + + private DistributionGraph graph; + + private JTextField noteField; + + private int delay = 10; + + private int counter = 0; + + /** + * Constructor for the GraphVisualizer object. + */ + public DistributionGraphVisualizer() { + model = new SamplingStatCalculator("Distribution"); + graph = new DistributionGraph(model); + graph.setBackground(Color.white); + init(); + } + + /** + * Gets the Image attribute of the GraphVisualizer object. + * + * @return the Image value + */ + public Image getImage() { + Image result = graph.createImage(graph.getWidth(), graph.getHeight()); + + graph.paintComponent(result.getGraphics()); + + return result; + } + + public synchronized void updateGui() { + if (graph.getWidth() < 10) { + graph.setPreferredSize(new Dimension(getWidth() - 40, getHeight() - 160)); + } + graphPanel.updateUI(); + graph.repaint(); + } + + public synchronized void updateGui(Sample s) { + // We have received one more sample + if (delay == counter) { + updateGui(); + counter = 0; + } else { + counter++; + } + } + + public void add(final SampleResult res) { + JMeterUtils.runSafe(new Runnable() { + public void run() { + // made currentSample volatile + model.addSample(res); + updateGui(model.getCurrentSample()); + } + }); + } + + public String getLabelResource() { + return "distribution_graph_title"; // $NON-NLS-1$ + } + + public synchronized void clearData() { + this.graph.clearData(); + model.clear(); + repaint(); + } + + @Override + public String toString() { + return "Show the samples in a distribution graph"; + } + + /** + * Initialize the GUI. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + Border margin = new EmptyBorder(10, 10, 5, 10); + + this.setBorder(margin); + + // Set up the graph with header, footer, Y axis and graph display + JPanel lgraphPanel = new JPanel(new BorderLayout()); + lgraphPanel.add(createGraphPanel(), BorderLayout.CENTER); + lgraphPanel.add(createGraphInfoPanel(), BorderLayout.SOUTH); + + // Add the main panel and the graph + this.add(makeTitlePanel(), BorderLayout.NORTH); + this.add(lgraphPanel, BorderLayout.CENTER); + } + + // Methods used in creating the GUI + + /** + * Creates a scroll pane containing the actual graph of the results. + * + * @return a scroll pane containing the graph + */ + private Component createGraphPanel() { + graphPanel = new JPanel(); + graphPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED, Color.lightGray, Color.darkGray)); + graphPanel.add(graph); + graphPanel.setBackground(Color.white); + return graphPanel; + } + + // /** + // * Creates one of the fields used to display the graph's current + // * values. + // * + // * @param color the color used to draw the value. By convention + // * this is the same color that is used to draw the + // * graph for this value and in the choose panel. + // * @param length the number of digits which the field should be + // * able to display + // * + // * @return a text field configured to display one of the + // * current graph values + // */ + // private JTextField createInfoField(Color color, int length) + // { + // JTextField field = new JTextField(length); + // field.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + // field.setEditable(false); + // field.setForeground(color); + // field.setBackground(getBackground()); + // + // // The text field should expand horizontally, but have + // // a fixed height + // field.setMaximumSize(new Dimension( + // field.getMaximumSize().width, + // field.getPreferredSize().height)); + // return field; + // } + + /** + * Creates a label for one of the fields used to display the graph's current + * values. Neither the label created by this method or the + * field passed as a parameter is added to the GUI here. + * + * @param labelResourceName + * the name of the label resource. This is used to look up the + * label text using {@link JMeterUtils#getResString(String)}. + * @param field + * the field this label is being created for. + */ + private JLabel createInfoLabel(String labelResourceName, JTextField field) { + JLabel label = new JLabel(JMeterUtils.getResString(labelResourceName)); + label.setForeground(field.getForeground()); + label.setLabelFor(field); + return label; + } + + /** + * Creates the information Panel at the bottom + * + * @return + */ + private Box createGraphInfoPanel() { + Box graphInfoPanel = Box.createHorizontalBox(); + this.noteField = new JTextField(); + graphInfoPanel.add(this.createInfoLabel("distribution_note1", this.noteField)); // $NON-NLS-1$ + return graphInfoPanel; + } + + /** + * Method implements Printable, which is suppose to return the correct + * internal component. The Action class can then print or save the graphics + * to a file. + */ + @Override + public JComponent getPrintableComponent() { + return this.graphPanel; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/Graph.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/Graph.java new file mode 100644 index 0000000..84fe28c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/Graph.java @@ -0,0 +1,255 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.util.Iterator; +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.Scrollable; +import javax.swing.SwingUtilities; + +import org.apache.jmeter.gui.util.JMeterColor; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Implements a simple graph for displaying performance results. + * + */ +public class Graph extends JComponent implements Scrollable, Clearable { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private boolean wantData = true; + + private boolean wantAverage = true; + + private boolean wantDeviation = true; + + private boolean wantThroughput = true; + + private boolean wantMedian = true; + + private CachingStatCalculator model; + + private static final int width = 2000; + + private long graphMax = 1; + + private double throughputMax = 1; + + /** + * Constructor for the Graph object. + */ + public Graph() { + this.setPreferredSize(new Dimension(width, 100)); + } + + /** + * Constructor for the Graph object. + */ + public Graph(CachingStatCalculator model) { + this(); + this.model = model; + } + + /** + * Gets the PreferredScrollableViewportSize attribute of the Graph object. + * + * @return the PreferredScrollableViewportSize value + */ + public Dimension getPreferredScrollableViewportSize() { + return this.getPreferredSize(); + // return new Dimension(width, 400); + } + + /** + * Gets the ScrollableUnitIncrement attribute of the Graph object. + * + * @return the ScrollableUnitIncrement value + */ + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return 5; + } + + /** + * Gets the ScrollableBlockIncrement attribute of the Graph object. + * + * @return the ScrollableBlockIncrement value + */ + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + return (int) (visibleRect.width * .9); + } + + /** + * Gets the ScrollableTracksViewportWidth attribute of the Graph object. + * + * @return the ScrollableTracksViewportWidth value + */ + public boolean getScrollableTracksViewportWidth() { + return false; + } + + /** + * Gets the ScrollableTracksViewportHeight attribute of the Graph object. + * + * @return the ScrollableTracksViewportHeight value + */ + public boolean getScrollableTracksViewportHeight() { + return true; + } + + /** + * Clears this graph. + */ + public void clearData() { + graphMax = 1; + throughputMax = 1; + } + + public void enableData(boolean value) { + this.wantData = value; + } + + public void enableAverage(boolean value) { + this.wantAverage = value; + } + + public void enableMedian(boolean value) { + this.wantMedian = value; + } + + public void enableDeviation(boolean value) { + this.wantDeviation = value; + } + + public void enableThroughput(boolean value) { + this.wantThroughput = value; + } + + public void updateGui(final Sample oneSample) { + long h = model.getPercentPoint((float) 0.90).longValue(); + boolean repaint = false; + if ((oneSample.getCount() % 20 == 0 || oneSample.getCount() < 20) && h > (graphMax * 1.2) || graphMax > (h * 1.2)) { + if (h >= 1) { + graphMax = h; + } else { + graphMax = 1; + } + repaint = true; + } + if (model.getMaxThroughput() > throughputMax) { + throughputMax = model.getMaxThroughput() * 1.3; + repaint = true; + } + if (repaint) { + repaint(); + return; + } + final long xPos = model.getCount(); + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Graphics g = getGraphics(); + + if (g != null) { + drawSample(xPos, oneSample, g); + } + } + }); + } + + /** {@inheritDoc}} */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + + List samples = model.getSamples(); + synchronized (samples ) { + Iterator e = samples.iterator(); + + for (int i = 0; e.hasNext(); i++) { + Sample s = e.next(); + + drawSample(i, s, g); + } + } + } + + private void drawSample(long x, Sample oneSample, Graphics g) { + // int width = getWidth(); + int height = getHeight(); + log.debug("Drawing a sample at " + x); + int adjustedWidth = (int)(x % width); // will always be within range of an int: as must be < width + if (wantData) { + int data = (int) (oneSample.getData() * height / graphMax); + + if (oneSample.isSuccess()) { + g.setColor(Color.black); + } else { + g.setColor(JMeterColor.YELLOW); + } + g.drawLine(adjustedWidth, height - data, adjustedWidth, height - data - 1); + if (log.isDebugEnabled()) { + log.debug("Drawing coords = " + adjustedWidth + "," + (height - data)); + } + } + + if (wantAverage) { + int average = (int) (oneSample.getAverage() * height / graphMax); + + g.setColor(Color.blue); + g.drawLine(adjustedWidth, height - average, adjustedWidth, (height - average - 1)); + } + + if (wantMedian) { + int median = (int) (oneSample.getMedian() * height / graphMax); + + g.setColor(JMeterColor.purple); + g.drawLine(adjustedWidth, height - median, adjustedWidth, (height - median - 1)); + } + + if (wantDeviation) { + int deviation = (int) (oneSample.getDeviation() * height / graphMax); + + g.setColor(Color.red); + g.drawLine(adjustedWidth, height - deviation, adjustedWidth, (height - deviation - 1)); + } + if (wantThroughput) { + int throughput = (int) (oneSample.getThroughput() * height / throughputMax); + + g.setColor(JMeterColor.dark_green); + g.drawLine(adjustedWidth, height - throughput, adjustedWidth, (height - throughput - 1)); + } + } + + /** + * @return Returns the graphMax. + */ + public long getGraphMax() { + return graphMax; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/GraphListener.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/GraphListener.java new file mode 100644 index 0000000..8a67d9e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/GraphListener.java @@ -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.visualizers; + +public interface GraphListener { + public void updateGui(Sample s); + + public void updateGui(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/GraphVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/GraphVisualizer.java new file mode 100644 index 0000000..5e40f66 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/GraphVisualizer.java @@ -0,0 +1,456 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Image; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.text.NumberFormat; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.gui.util.JMeterColor; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +/** + * This class implements a statistical analyser that calculates both the average + * and the standard deviation of the sampling process and outputs them as + * autoscaling plots. + * + * Created February 8, 2001 + * + */ +public class GraphVisualizer extends AbstractVisualizer implements ImageVisualizer, ItemListener, Clearable { + + private static final long serialVersionUID = 240L; + + private static final String ZERO = "0"; //$NON-NLS-1$ + + private NumberFormat nf = NumberFormat.getInstance(); // OK, because used in synchronised method + + private CachingStatCalculator model; + + private JTextField maxYField = null; + + private JTextField minYField = null; + + private JTextField noSamplesField = null; + + private String minute = JMeterUtils.getResString("minute"); // $NON-NLS-1$ + + private Graph graph; + + private JCheckBox data; + + private JCheckBox average; + + private JCheckBox deviation; + + private JCheckBox throughput; + + private JCheckBox median; + + private JTextField dataField; + + private JTextField averageField; + + private JTextField deviationField; + + private JTextField throughputField; + + private JTextField medianField; + + /** + * Constructor for the GraphVisualizer object. + */ + public GraphVisualizer() { + model = new CachingStatCalculator("Graph"); + graph = new Graph(model); + init(); + } + + /** + * Gets the Image attribute of the GraphVisualizer object. + * + * @return the Image value + */ + public Image getImage() { + Image result = graph.createImage(graph.getWidth(), graph.getHeight()); + + graph.paintComponent(result.getGraphics()); + + return result; + } + + public synchronized void updateGui(Sample s) { + // We have received one more sample + graph.updateGui(s); + noSamplesField.setText(Long.toString(s.getCount())); + dataField.setText(Long.toString(s.getData())); + averageField.setText(Long.toString(s.getAverage())); + deviationField.setText(Long.toString(s.getDeviation())); + throughputField.setText(nf.format(60 * s.getThroughput()) + "/" + minute); // $NON-NLS-1$ + medianField.setText(Long.toString(s.getMedian())); + updateYAxis(); + } + + public void add(final SampleResult res) { + JMeterUtils.runSafe(new Runnable() { + public void run() { + updateGui(model.addSample(res)); + } + }); + } + + public String getLabelResource() { + return "graph_results_title"; // $NON-NLS-1$ + } + + public void itemStateChanged(ItemEvent e) { + if (e.getItem() == data) { + this.graph.enableData(e.getStateChange() == ItemEvent.SELECTED); + } else if (e.getItem() == average) { + this.graph.enableAverage(e.getStateChange() == ItemEvent.SELECTED); + } else if (e.getItem() == deviation) { + this.graph.enableDeviation(e.getStateChange() == ItemEvent.SELECTED); + } else if (e.getItem() == throughput) { + this.graph.enableThroughput(e.getStateChange() == ItemEvent.SELECTED); + } else if (e.getItem() == median) { + this.graph.enableMedian(e.getStateChange() == ItemEvent.SELECTED); + } + this.graph.repaint(); + } + + public void clearData() { + graph.clearData(); + model.clear(); + dataField.setText(ZERO); + averageField.setText(ZERO); + deviationField.setText(ZERO); + throughputField.setText("0/" + minute); //$NON-NLS-1$ + medianField.setText(ZERO); + noSamplesField.setText(ZERO); + updateYAxis(); + repaint(); + } + + @Override + public String toString() { + return "Show the samples analysis as dot plots"; + } + + /** + * Update the max and min value of the Y axis. + */ + private void updateYAxis() { + maxYField.setText(Long.toString(graph.getGraphMax())); + minYField.setText(ZERO); + } + + /** + * Initialize the GUI. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + Border margin = new EmptyBorder(10, 10, 5, 10); + + this.setBorder(margin); + + // Set up the graph with header, footer, Y axis and graph display + JPanel graphPanel = new JPanel(new BorderLayout()); + graphPanel.add(createYAxis(), BorderLayout.WEST); + graphPanel.add(createChoosePanel(), BorderLayout.NORTH); + graphPanel.add(createGraphPanel(), BorderLayout.CENTER); + graphPanel.add(createGraphInfoPanel(), BorderLayout.SOUTH); + + // Add the main panel and the graph + this.add(makeTitlePanel(), BorderLayout.NORTH); + this.add(graphPanel, BorderLayout.CENTER); + } + + // Methods used in creating the GUI + + /** + * Creates the panel containing the graph's Y axis labels. + * + * @return the Y axis panel + */ + private JPanel createYAxis() { + JPanel graphYAxisPanel = new JPanel(); + + graphYAxisPanel.setLayout(new BorderLayout()); + + maxYField = createYAxisField(5); + minYField = createYAxisField(3); + + graphYAxisPanel.add(createYAxisPanel("graph_results_ms", maxYField), BorderLayout.NORTH); // $NON-NLS-1$ + graphYAxisPanel.add(createYAxisPanel("graph_results_ms", minYField), BorderLayout.SOUTH); // $NON-NLS-1$ + + return graphYAxisPanel; + } + + /** + * Creates a text field to be used for the value of a Y axis label. These + * fields hold the minimum and maximum values for the graph. The units are + * kept in a separate label outside of this field. + * + * @param length + * the number of characters which the field will use to calculate + * its preferred width. This should be set to the maximum number + * of digits that are expected to be necessary to hold the label + * value. + * + * @see #createYAxisPanel(String, JTextField) + * + * @return a text field configured to be used in the Y axis + */ + private JTextField createYAxisField(int length) { + JTextField field = new JTextField(length); + field.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); + field.setEditable(false); + field.setForeground(Color.black); + field.setBackground(getBackground()); + field.setHorizontalAlignment(JTextField.RIGHT); + return field; + } + + /** + * Creates a panel for an entire Y axis label. This includes the dynamic + * value as well as the unit label. + * + * @param labelResourceName + * the name of the label resource. This is used to look up the + * label text using {@link JMeterUtils#getResString(String)}. + * + * @return a panel containing both the dynamic and static parts of a Y axis + * label + */ + private JPanel createYAxisPanel(String labelResourceName, JTextField field) { + JPanel panel = new JPanel(new FlowLayout()); + JLabel label = new JLabel(JMeterUtils.getResString(labelResourceName)); + + panel.add(field); + panel.add(label); + return panel; + } + + /** + * Creates a panel which allows the user to choose which graphs to display. + * This panel consists of a check box for each type of graph (current + * sample, average, deviation, and throughput). + * + * @return a panel allowing the user to choose which graphs to display + */ + private JPanel createChoosePanel() { + JPanel chooseGraphsPanel = new JPanel(); + + chooseGraphsPanel.setLayout(new FlowLayout()); + JLabel selectGraphsLabel = new JLabel(JMeterUtils.getResString("graph_choose_graphs")); //$NON-NLS-1$ + data = createChooseCheckBox("graph_results_data", Color.black); // $NON-NLS-1$ + average = createChooseCheckBox("graph_results_average", Color.blue); // $NON-NLS-1$ + deviation = createChooseCheckBox("graph_results_deviation", Color.red); // $NON-NLS-1$ + throughput = createChooseCheckBox("graph_results_throughput", JMeterColor.dark_green); // $NON-NLS-1$ + median = createChooseCheckBox("graph_results_median", JMeterColor.purple); // $NON-NLS-1$ + + chooseGraphsPanel.add(selectGraphsLabel); + chooseGraphsPanel.add(data); + chooseGraphsPanel.add(average); + chooseGraphsPanel.add(median); + chooseGraphsPanel.add(deviation); + chooseGraphsPanel.add(throughput); + return chooseGraphsPanel; + } + + /** + * Creates a check box configured to be used to in the choose panel allowing + * the user to select whether or not a particular kind of graph data will be + * displayed. + * + * @param labelResourceName + * the name of the label resource. This is used to look up the + * label text using {@link JMeterUtils#getResString(String)}. + * @param color + * the color used for the checkbox text. By convention this is + * the same color that is used to draw the graph and for the + * corresponding info field. + * + * @return a checkbox allowing the user to select whether or not a kind of + * graph data will be displayed + */ + private JCheckBox createChooseCheckBox(String labelResourceName, Color color) { + JCheckBox checkBox = new JCheckBox(JMeterUtils.getResString(labelResourceName)); + checkBox.setSelected(true); + checkBox.addItemListener(this); + checkBox.setForeground(color); + return checkBox; + } + + /** + * Creates a scroll pane containing the actual graph of the results. + * + * @return a scroll pane containing the graph + */ + private Component createGraphPanel() { + JScrollPane graphScrollPanel = makeScrollPane(graph, JScrollPane.VERTICAL_SCROLLBAR_NEVER, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + graphScrollPanel.setViewportBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + graphScrollPanel.setPreferredSize(graphScrollPanel.getMinimumSize()); + + return graphScrollPanel; + } + + /** + * Creates a panel which numerically displays the current graph values. + * + * @return a panel showing the current graph values + */ + private Box createGraphInfoPanel() { + Box graphInfoPanel = Box.createHorizontalBox(); + + noSamplesField = createInfoField(Color.black, 6); + dataField = createInfoField(Color.black, 5); + averageField = createInfoField(Color.blue, 5); + deviationField = createInfoField(Color.red, 5); + throughputField = createInfoField(JMeterColor.dark_green, 15); + medianField = createInfoField(JMeterColor.purple, 5); + + graphInfoPanel.add(createInfoColumn(createInfoLabel("graph_results_no_samples", noSamplesField), // $NON-NLS-1$ + noSamplesField, createInfoLabel("graph_results_deviation", deviationField), deviationField)); // $NON-NLS-1$ + graphInfoPanel.add(Box.createHorizontalGlue()); + + graphInfoPanel.add(createInfoColumn(createInfoLabel("graph_results_latest_sample", dataField), dataField, // $NON-NLS-1$ + createInfoLabel("graph_results_throughput", throughputField), throughputField)); // $NON-NLS-1$ + graphInfoPanel.add(Box.createHorizontalGlue()); + + graphInfoPanel.add(createInfoColumn(createInfoLabel("graph_results_average", averageField), averageField, // $NON-NLS-1$ + createInfoLabel("graph_results_median", medianField), medianField)); // $NON-NLS-1$ + graphInfoPanel.add(Box.createHorizontalGlue()); + return graphInfoPanel; + } + + /** + * Creates one of the fields used to display the graph's current values. + * + * @param color + * the color used to draw the value. By convention this is the + * same color that is used to draw the graph for this value and + * in the choose panel. + * @param length + * the number of digits which the field should be able to display + * + * @return a text field configured to display one of the current graph + * values + */ + private JTextField createInfoField(Color color, int length) { + JTextField field = new JTextField(length); + field.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + field.setEditable(false); + field.setForeground(color); + field.setBackground(getBackground()); + + // The text field should expand horizontally, but have + // a fixed height + field.setMaximumSize(new Dimension(field.getMaximumSize().width, field.getPreferredSize().height)); + return field; + } + + /** + * Creates a label for one of the fields used to display the graph's current + * values. Neither the label created by this method or the + * field passed as a parameter is added to the GUI here. + * + * @param labelResourceName + * the name of the label resource. This is used to look up the + * label text using {@link JMeterUtils#getResString(String)}. + * @param field + * the field this label is being created for. + */ + private JLabel createInfoLabel(String labelResourceName, JTextField field) { + JLabel label = new JLabel(JMeterUtils.getResString(labelResourceName)); + label.setForeground(field.getForeground()); + label.setLabelFor(field); + return label; + } + + /** + * Creates a panel containing two pairs of labels and fields for displaying + * the current graph values. This method exists to help with laying out the + * fields in columns. If one or more components are null then these + * components will be represented by blank space. + * + * @param label1 + * the label for the first field. This label will be placed in + * the upper left section of the panel. If this parameter is + * null, this section of the panel will be left blank. + * @param field1 + * the field corresponding to the first label. This field will be + * placed in the upper right section of the panel. If this + * parameter is null, this section of the panel will be left + * blank. + * @param label2 + * the label for the second field. This label will be placed in + * the lower left section of the panel. If this parameter is + * null, this section of the panel will be left blank. + * @param field2 + * the field corresponding to the second label. This field will + * be placed in the lower right section of the panel. If this + * parameter is null, this section of the panel will be left + * blank. + */ + private Box createInfoColumn(JLabel label1, JTextField field1, JLabel label2, JTextField field2) { + // This column actually consists of a row with two sub-columns + // The first column contains the labels, and the second + // column contains the fields. + Box row = Box.createHorizontalBox(); + Box col = Box.createVerticalBox(); + col.add(label1 != null ? label1 : Box.createVerticalGlue()); + col.add(label2 != null ? label2 : Box.createVerticalGlue()); + row.add(col); + + row.add(Box.createHorizontalStrut(5)); + + col = Box.createVerticalBox(); + col.add(field1 != null ? field1 : Box.createVerticalGlue()); + col.add(field2 != null ? field2 : Box.createVerticalGlue()); + row.add(col); + + row.add(Box.createHorizontalStrut(5)); + + return row; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/ImageVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/ImageVisualizer.java new file mode 100644 index 0000000..c511384 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/ImageVisualizer.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.jmeter.visualizers; + +/** + * TODO - does not appear to be used + * + * @version $Revision: 674365 $ + */ +public interface ImageVisualizer { + public java.awt.Image getImage(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/JSR223Listener.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/JSR223Listener.java new file mode 100644 index 0000000..b5651c3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/JSR223Listener.java @@ -0,0 +1,71 @@ +/* + * 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.visualizers; + +import java.io.IOException; + +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.util.JSR223TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class JSR223Listener extends JSR223TestElement + implements Cloneable, SampleListener, TestBean, Visualizer { +// N.B. Needs to implement Visualizer so that TestBeanGUI can find the correct GUI class + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 234L; + + public void sampleOccurred(SampleEvent event) { + try { + ScriptEngineManager sem = getManager(); + if (sem == null) { return; } + sem.put("sampleEvent", event); + sem.put("sampleResult", event.getResult()); + processFileOrScript(sem); + } catch (ScriptException e) { + log.warn("Problem in JSR223 script "+e); + } catch (IOException e) { + log.warn("Problem in JSR223 script "+e); + } + } + + public void sampleStarted(SampleEvent e) { + // NOOP + } + + public void sampleStopped(SampleEvent e) { + // NOOP + } + + public void add(SampleResult sample) { + // NOOP + } + + public boolean isStats() { + return false; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/JSR223ListenerBeanInfo.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/JSR223ListenerBeanInfo.java new file mode 100644 index 0000000..806b7e4 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/JSR223ListenerBeanInfo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.jmeter.visualizers; + +import org.apache.jmeter.util.JSR223BeanInfoSupport; + +public class JSR223ListenerBeanInfo extends JSR223BeanInfoSupport { + + public JSR223ListenerBeanInfo() { + super(JSR223Listener.class); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/LineGraph.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/LineGraph.java new file mode 100644 index 0000000..a4d3c57 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/LineGraph.java @@ -0,0 +1,261 @@ +/* + * 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.visualizers; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.LayoutManager; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; + +import javax.swing.JPanel; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.jCharts.axisChart.AxisChart; +import org.jCharts.chartData.AxisChartDataSet; +import org.jCharts.chartData.DataSeries; +import org.jCharts.properties.AxisProperties; +import org.jCharts.properties.ChartProperties; +import org.jCharts.properties.DataAxisProperties; +import org.jCharts.properties.LegendProperties; +import org.jCharts.properties.LineChartProperties; +import org.jCharts.properties.PointChartProperties; +import org.jCharts.types.ChartType; + +/** + * + * Axis graph is used by StatGraphVisualizer, which generates bar graphs + * from the statistical data. + */ +public class LineGraph extends JPanel { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected double[][] data = null; + protected String title, xAxisTitle, yAxisTitle; + protected String[] xAxisLabels, yAxisLabel; + protected int width, height; + + private static final Shape[] SHAPE_ARRAY = {PointChartProperties.SHAPE_CIRCLE, + PointChartProperties.SHAPE_DIAMOND,PointChartProperties.SHAPE_SQUARE, + PointChartProperties.SHAPE_TRIANGLE}; + + /** + * 12 basic colors for line graphs. If we need more colors than this, + * we can add more. Though more than 12 lines per graph will look + * rather busy and be hard to read. + */ + private static final Paint[] PAINT_ARRAY = {Color.black, + Color.blue,Color.green,Color.magenta,Color.orange, + Color.red,Color.yellow,Color.darkGray,Color.gray,Color.lightGray, + Color.pink,Color.cyan}; + protected int shape_counter = 0; + protected int paint_counter = -1; + + /** + * + */ + public LineGraph() { + super(); + } + + /** + * @param layout + */ + public LineGraph(LayoutManager layout) { + super(layout); + } + + /** + * @param layout + * @param isDoubleBuffered + */ + public LineGraph(LayoutManager layout, boolean isDoubleBuffered) { + super(layout, isDoubleBuffered); + } + + public void setData(double[][] data) { + this.data = data; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setXAxisTitle(String title) { + this.xAxisTitle = title; + } + + public void setYAxisTitle(String title) { + this.yAxisTitle = title; + } + + public void setXAxisLabels(String[] labels) { + this.xAxisLabels = labels; + } + + public void setYAxisLabels(String[] label) { + this.yAxisLabel = label; + } + + public void setWidth(int w) { + this.width = w; + } + + public void setHeight(int h) { + this.height = h; + } + + @Override + public void paintComponent(Graphics g) { + // reset the paint counter + this.paint_counter = -1; + if (data != null && this.title != null && this.xAxisLabels != null && + this.xAxisTitle != null && this.yAxisLabel != null && + this.yAxisTitle != null) { + drawSample(this.title,this.xAxisLabels,this.xAxisTitle, + this.yAxisTitle,this.data,this.width,this.height,g); + } + } + + private void drawSample(String _title, String[] _xAxisLabels, String _xAxisTitle, + String _yAxisTitle, double[][] _data, int _width, int _height, Graphics g) { + try { + if (_width == 0) { + _width = 450; + } + if (_height == 0) { + _height = 250; + } + this.setPreferredSize(new Dimension(_width,_height)); + DataSeries dataSeries = new DataSeries( _xAxisLabels, _xAxisTitle, _yAxisTitle, _title ); + String[] legendLabels= yAxisLabel; + Paint[] paints = this.createPaint(_data.length); + Shape[] shapes = createShapes(_data.length); + Stroke[] lstrokes = createStrokes(_data.length); + LineChartProperties lineChartProperties= new LineChartProperties(lstrokes,shapes); + AxisChartDataSet axisChartDataSet= new AxisChartDataSet( _data, + legendLabels, + paints, + ChartType.LINE, + lineChartProperties ); + dataSeries.addIAxisPlotDataSet( axisChartDataSet ); + + ChartProperties chartProperties = new ChartProperties(); + AxisProperties axisProperties = new AxisProperties(); + // show the grid lines, to turn it off, set it to zero + axisProperties.getYAxisProperties().setShowGridLines(1); + axisProperties.setXAxisLabelsAreVertical(true); + // set the Y Axis to round + DataAxisProperties daxp = (DataAxisProperties)axisProperties.getYAxisProperties(); + daxp.setRoundToNearest(1); + LegendProperties legendProperties = new LegendProperties(); + AxisChart axisChart = new AxisChart( + dataSeries, chartProperties, axisProperties, + legendProperties, _width, _height ); + axisChart.setGraphics2D((Graphics2D) g); + axisChart.render(); + } catch (Exception e) { + log.error(e.getMessage()); + } + } + + /** + * Since we only have 4 shapes, the method will start with the + * first shape and keep cycling through the shapes in order. + * @param count + * @return the first n shapes + */ + public Shape[] createShapes(int count) { + Shape[] shapes = new Shape[count]; + for (int idx=0; idx < count; idx++) { + shapes[idx] = nextShape(); + } + return shapes; + } + + /** + * Return the next shape + * @return the next shape + */ + public Shape nextShape() { + this.shape_counter++; + if (shape_counter >= (SHAPE_ARRAY.length - 1)) { + shape_counter = 0; + } + return SHAPE_ARRAY[shape_counter]; + } + + /** + * + * @param count + * @return the first count strokes + */ + public Stroke[] createStrokes(int count) { + Stroke[] str = new Stroke[count]; + for (int idx=0; idx < count; idx++) { + str[idx] = nextStroke(); + } + return str; + } + + /** + * method always return a new BasicStroke with 1.0f weight + * @return a new BasicStroke with 1.0f weight + */ + public Stroke nextStroke() { + return new BasicStroke(1.0f); + } + + /** + * return an array of Paint with different colors. The current + * implementation will cycle through 12 colors if a line graph + * has more than 12 entries + * @param count + * @return an array of Paint with different colors + */ + public Paint[] createPaint(int count) { + Paint[] pts = new Paint[count]; + for (int idx=0; idx < count; idx++) { + pts[idx] = nextPaint(); + } + return pts; + } + + /** + * The method will return the next paint color in the PAINT_ARRAY. + * Rather than return a random color, we want it to always go through + * the same sequence. This way, the same charts will always use the + * same color and make it easier to compare side by side. + * @return the next paint color in the PAINT_ARRAY + */ + public Paint nextPaint() { + this.paint_counter++; + if (this.paint_counter == (PAINT_ARRAY.length - 1)) { + this.paint_counter = 0; + } + return PAINT_ARRAY[this.paint_counter]; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/MailerVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/MailerVisualizer.java new file mode 100644 index 0000000..91a9db6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/MailerVisualizer.java @@ -0,0 +1,449 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.mail.MessagingException; +import javax.mail.internet.AddressException; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.reporters.MailerModel; +import org.apache.jmeter.reporters.MailerResultCollector; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/* + * TODO : - Create a subpanel for other visualizers - connect to the properties. - + * Get the specific URL that is failing. - add a seperate interface to collect + * the thrown failure messages. - - suggestions ;-) + */ + +/** + * This class implements a visualizer that mails a message when an error occurs. + * + */ +public class MailerVisualizer extends AbstractVisualizer implements ActionListener, Clearable, ChangeListener { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private JButton testerButton; + + private JTextField addressField; + + private JTextField fromField; + + private JTextField smtpHostField; + + private JTextField smtpPortField; + + private JTextField failureSubjectField; + + private JTextField successSubjectField; + + private JTextField failureField; + + private JTextField failureLimitField; + + private JTextField successLimitField; + + private JTextField smtpLoginField; + + private JTextField smtpPasswordField; + + private JComboBox authTypeCombo; + + /** + * Constructs the MailerVisualizer and initializes its GUI. + */ + public MailerVisualizer() { + super(); + setModel(new MailerResultCollector()); + // initialize GUI. + initGui(); + } + + public JPanel getControlPanel() { + return this; + } + + /** + * Clears any stored sampling-informations. + */ + public synchronized void clearData() { + if (getModel() != null) { + MailerModel model = ((MailerResultCollector) getModel()).getMailerModel(); + model.clear(); + updateVisualizer(model); + } + } + + public void add(final SampleResult res) { + if (getModel() != null) { + JMeterUtils.runSafe(new Runnable() { + public void run() { + MailerModel model = ((MailerResultCollector) getModel()).getMailerModel(); + // method called by add is synchronized + model.add(res);//this is a different model from the one used by the result collector + updateVisualizer(model); + } + }); + } + } + + @Override + public String toString() { + return JMeterUtils.getResString("mailer_string"); // $NON-NLS-1$ + } + + /** + * Initializes the GUI. Lays out components and adds them to the container. + */ + private void initGui() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new VerticalPanel(); + Border margin = new EmptyBorder(5, 10, 5, 10); + this.setBorder(margin); + + mainPanel.add(makeTitlePanel()); + + JPanel attributePane = new VerticalPanel(); + attributePane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("mailer_title_settings"))); // $NON-NLS-1$ + + // Settings panes + attributePane.add(createMailingSettings()); + attributePane.add(createSmtpSettings()); + + // Test mail button + JPanel testerPanel = new JPanel(new BorderLayout()); + testerButton = new JButton(JMeterUtils.getResString("mailer_test_mail")); // $NON-NLS-1$ + testerButton.addActionListener(this); + testerButton.setEnabled(true); + testerPanel.add(testerButton, BorderLayout.EAST); + attributePane.add(testerPanel); + mainPanel.add(attributePane); + mainPanel.add(Box.createRigidArea(new Dimension(0,5))); + + // Failures count + JPanel mailerPanel = new JPanel(new BorderLayout()); + mailerPanel.add(new JLabel(JMeterUtils.getResString("mailer_failures")), BorderLayout.WEST); // $NON-NLS-1$ + failureField = new JTextField(6); + failureField.setEditable(false); + mailerPanel.add(failureField, BorderLayout.CENTER); + mainPanel.add(mailerPanel); + + this.add(mainPanel, BorderLayout.CENTER); + } + + private JPanel createMailingSettings() { + JPanel settingsPane = new JPanel(new BorderLayout()); + settingsPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("mailer_title_message"))); // $NON-NLS-1$ + + JPanel headerPane = new JPanel(new BorderLayout()); + headerPane.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); + JPanel fromPane = new JPanel(new BorderLayout()); + fromPane.add(new JLabel(JMeterUtils.getResString("mailer_from")), BorderLayout.WEST); // $NON-NLS-1$ + fromField = new JTextField(25); + fromField.setEditable(true); + fromPane.add(fromField, BorderLayout.CENTER); + fromPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + headerPane.add(fromPane, BorderLayout.WEST); + JPanel addressPane = new JPanel(new BorderLayout()); + addressPane.add(new JLabel(JMeterUtils.getResString("mailer_addressees")), BorderLayout.WEST); // $NON-NLS-1$ + addressField = new JTextField(10); + addressField.setEditable(true); + addressPane.add(addressField, BorderLayout.CENTER); + headerPane.add(addressPane, BorderLayout.CENTER); + + JPanel successPane = new JPanel(new BorderLayout()); + successPane.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); + JPanel succesSubjectPane = new JPanel(new BorderLayout()); + succesSubjectPane.add(new JLabel(JMeterUtils.getResString("mailer_success_subject")), BorderLayout.WEST); // $NON-NLS-1$ + successSubjectField = new JTextField(10); + successSubjectField.setEditable(true); + succesSubjectPane.add(successSubjectField, BorderLayout.CENTER); + succesSubjectPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + successPane.add(succesSubjectPane, BorderLayout.CENTER); + JPanel successLimitPane = new JPanel(new BorderLayout()); + successLimitPane.add(new JLabel(JMeterUtils.getResString("mailer_success_limit")), BorderLayout.WEST); // $NON-NLS-1$ + successLimitField = new JTextField("2", 5); // $NON-NLS-1$ + successLimitField.setEditable(true); + successLimitPane.add(successLimitField, BorderLayout.CENTER); + successPane.add(successLimitPane, BorderLayout.EAST); + + JPanel failurePane = new JPanel(new BorderLayout()); + failurePane.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); + JPanel failureSubjectPane = new JPanel(new BorderLayout()); + failureSubjectPane.add(new JLabel(JMeterUtils.getResString("mailer_failure_subject")), BorderLayout.WEST); // $NON-NLS-1$ + failureSubjectField = new JTextField(10); + failureSubjectField.setEditable(true); + failureSubjectPane.add(failureSubjectField, BorderLayout.CENTER); + failureSubjectPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + failurePane.add(failureSubjectPane, BorderLayout.CENTER); + JPanel failureLimitPane = new JPanel(new BorderLayout()); + failureLimitPane.add(new JLabel(JMeterUtils.getResString("mailer_failure_limit")), BorderLayout.WEST); // $NON-NLS-1$ + failureLimitField = new JTextField("2", 5); // $NON-NLS-1$ + failureLimitField.setEditable(true); + failureLimitPane.add(failureLimitField, BorderLayout.CENTER); + failurePane.add(failureLimitPane, BorderLayout.EAST); + + settingsPane.add(headerPane, BorderLayout.NORTH); + settingsPane.add(successPane, BorderLayout.CENTER); + settingsPane.add(failurePane, BorderLayout.SOUTH); + + return settingsPane; + } + + private JPanel createSmtpSettings() { + JPanel settingsPane = new JPanel(new BorderLayout()); + settingsPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("mailer_title_smtpserver"))); // $NON-NLS-1$ + + JPanel hostPane = new JPanel(new BorderLayout()); + hostPane.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); + JPanel smtpHostPane = new JPanel(new BorderLayout()); + smtpHostPane.add(new JLabel(JMeterUtils.getResString("mailer_host")), BorderLayout.WEST); // $NON-NLS-1$ + smtpHostField = new JTextField(10); + smtpHostField.setEditable(true); + smtpHostPane.add(smtpHostField, BorderLayout.CENTER); + smtpHostPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + hostPane.add(smtpHostPane, BorderLayout.CENTER); + JPanel smtpPortPane = new JPanel(new BorderLayout()); + smtpPortPane.add(new JLabel(JMeterUtils.getResString("mailer_port")), BorderLayout.WEST); // $NON-NLS-1$ + smtpPortField = new JTextField(10); + smtpPortField.setEditable(true); + smtpPortPane.add(smtpPortField, BorderLayout.CENTER); + hostPane.add(smtpPortPane, BorderLayout.EAST); + + JPanel authPane = new JPanel(new BorderLayout()); + hostPane.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); + JPanel smtpLoginPane = new JPanel(new BorderLayout()); + smtpLoginPane.add(new JLabel(JMeterUtils.getResString("mailer_login")), BorderLayout.WEST); // $NON-NLS-1$ + smtpLoginField = new JTextField(10); + smtpLoginField.setEditable(true); + smtpLoginPane.add(smtpLoginField, BorderLayout.CENTER); + smtpLoginPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + authPane.add(smtpLoginPane, BorderLayout.CENTER); + JPanel smtpPasswordPane = new JPanel(new BorderLayout()); + smtpPasswordPane.add(new JLabel(JMeterUtils.getResString("mailer_password")), BorderLayout.WEST); // $NON-NLS-1$ + smtpPasswordField = new JPasswordField(10); + smtpPasswordField.setEditable(true); + smtpPasswordPane.add(smtpPasswordField, BorderLayout.CENTER); + smtpPasswordPane.add(Box.createRigidArea(new Dimension(5,0)), BorderLayout.EAST); + authPane.add(smtpPasswordPane, BorderLayout.EAST); + + JPanel authTypePane = new JPanel(new BorderLayout()); + authTypePane.add(new JLabel(JMeterUtils.getResString("mailer_connection_security")), BorderLayout.WEST); + authTypeCombo = new JComboBox(new Object[] { + MailerModel.MailAuthType.NONE.toString(), + MailerModel.MailAuthType.SSL.toString(), + MailerModel.MailAuthType.TLS.toString()}); + authTypeCombo.setFont(new Font("SansSerif", Font.PLAIN, 10)); // $NON-NLS-1$ + authTypePane.add(authTypeCombo, BorderLayout.CENTER); + + JPanel credPane = new JPanel(new BorderLayout()); + credPane.add(authPane, BorderLayout.CENTER); + credPane.add(authTypePane, BorderLayout.EAST); + + settingsPane.add(hostPane, BorderLayout.NORTH); + settingsPane.add(credPane, BorderLayout.CENTER); + + return settingsPane; + } + + public String getLabelResource() { + return "mailer_visualizer_title"; //$NON-NLS-1$ + } + + /** + * Returns a String for the title of the attributes-panel as set up in the + * properties-file using the lookup-constant "mailer_attributes_panel". + * + * @return The title of the component. + */ + public String getAttributesTitle() { + return JMeterUtils.getResString("mailer_attributes_panel"); //$NON-NLS-1$ + } + + // //////////////////////////////////////////////////////////// + // + // Implementation of the ActionListener-Interface. + // + // //////////////////////////////////////////////////////////// + + /** + * Reacts on an ActionEvent (like pressing a button). + * + * @param e + * The ActionEvent with information about the event and its + * source. + */ + public void actionPerformed(ActionEvent e) { + if (e.getSource() == testerButton) { + ResultCollector testElement = getModel(); + modifyTestElement(testElement); + try { + MailerModel model = ((MailerResultCollector) testElement).getMailerModel(); + model.sendTestMail(); + displayMessage(JMeterUtils.getResString("mail_sent"), false); //$NON-NLS-1$ + } catch (AddressException ex) { + log.error("Invalid mail address ", ex); + displayMessage(JMeterUtils.getResString("invalid_mail_address") //$NON-NLS-1$ + + "\n" + ex.getMessage(), true); //$NON-NLS-1$ + } catch (MessagingException ex) { + log.error("Couldn't send mail...", ex); + displayMessage(JMeterUtils.getResString("invalid_mail") //$NON-NLS-1$ + + "\n" + ex.getMessage(), true); //$NON-NLS-1$ + } + } + } + + // //////////////////////////////////////////////////////////// + // + // Methods used to store and retrieve the MailerVisualizer. + // + // //////////////////////////////////////////////////////////// + + /** + * Restores MailerVisualizer. + */ + @Override + public void configure(TestElement el) { + super.configure(el); + updateVisualizer(((MailerResultCollector) el).getMailerModel()); + } + + /** + * Makes MailerVisualizer storable. + */ + @Override + public TestElement createTestElement() { + ResultCollector model = getModel(); + if (model == null) { + model = new MailerResultCollector(); + setModel(model); + } + modifyTestElement(model); + return model; + } + + /** + * {@inheritDoc} + */ + @Override + public void modifyTestElement(TestElement c) { + super.modifyTestElement(c); + MailerModel mailerModel = ((MailerResultCollector) c).getMailerModel(); + mailerModel.setFailureLimit(failureLimitField.getText()); + mailerModel.setFailureSubject(failureSubjectField.getText()); + mailerModel.setFromAddress(fromField.getText()); + mailerModel.setSmtpHost(smtpHostField.getText()); + mailerModel.setSmtpPort(smtpPortField.getText()); + mailerModel.setLogin(smtpLoginField.getText()); + mailerModel.setPassword(smtpPasswordField.getText()); + mailerModel.setMailAuthType( + authTypeCombo.getSelectedItem().toString()); + mailerModel.setSuccessLimit(successLimitField.getText()); + mailerModel.setSuccessSubject(successSubjectField.getText()); + mailerModel.setToAddress(addressField.getText()); + } + + // //////////////////////////////////////////////////////////// + // + // Methods to implement the ModelListener. + // + // //////////////////////////////////////////////////////////// + + /** + * Notifies this Visualizer about model-changes. Causes the Visualizer to + * query the model about its new state. + */ + private void updateVisualizer(MailerModel model) { + addressField.setText(model.getToAddress()); + fromField.setText(model.getFromAddress()); + smtpHostField.setText(model.getSmtpHost()); + smtpPortField.setText(model.getSmtpPort()); + smtpLoginField.setText(model.getLogin()); + smtpPasswordField.setText(model.getPassword()); + authTypeCombo.setSelectedItem(model.getMailAuthType().toString()); + successSubjectField.setText(model.getSuccessSubject()); + failureSubjectField.setText(model.getFailureSubject()); + failureLimitField.setText(String.valueOf(model.getFailureLimit())); + failureField.setText(String.valueOf(model.getFailureCount())); + successLimitField.setText(String.valueOf(model.getSuccessLimit())); + repaint(); + } + + /** + * Shows a message using a DialogBox. + */ + private void displayMessage(String message, boolean isError) { + int type = 0; + + if (isError) { + type = JOptionPane.ERROR_MESSAGE; + } else { + type = JOptionPane.INFORMATION_MESSAGE; + } + JOptionPane.showMessageDialog(null, message, isError ? + JMeterUtils.getResString("mailer_msg_title_error") : JMeterUtils.getResString("mailer_msg_title_information"), type); + } + + /** + * {@inheritDoc} + */ + @Override + public void stateChanged(ChangeEvent e) { + if (e.getSource() instanceof MailerModel) { + MailerModel testModel = (MailerModel) e.getSource(); + updateVisualizer(testModel); + } else { + super.stateChanged(e); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/ModelListener.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/ModelListener.java new file mode 100644 index 0000000..f69756a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/ModelListener.java @@ -0,0 +1,37 @@ +/* + * 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.visualizers; + +/** + * The Interface to be implemented by any class that wants to be notified by + * model which makes use of this callback-Interface. + * + */ +public interface ModelListener { + + /** + * Informs the Visualizer that the model has changed. + */ + public void updateVisualizer(); + + /** + * Informs the Visualizer that a message should be displayed. + */ + public void displayMessage(String messageString, boolean isError); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorAccumModel.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorAccumModel.java new file mode 100644 index 0000000..bda8ecf --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorAccumModel.java @@ -0,0 +1,240 @@ +/* + * 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.visualizers; + +import java.io.Serializable; +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import org.apache.jmeter.monitor.model.ObjectFactory; +import org.apache.jmeter.monitor.model.Status; +import org.apache.jmeter.monitor.util.Stats; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; + +public class MonitorAccumModel implements Clearable, Serializable { + + private static final long serialVersionUID = 240L; + + private final HashMap> serverListMap; + + /** + * we use this to set the current monitorModel so that we can save the stats + * to the resultcolllector. + */ + private MonitorModel current; + + private final List listeners; + + /** + * By default, we set the default to 800 + */ + private int defaultBufferSize = 800; + + // optional connector name prefix + private String connectorPrefix = null; + + /** + * + */ + public MonitorAccumModel() { + serverListMap = new HashMap>(); + listeners = new LinkedList(); + } + + public int getBufferSize() { + return defaultBufferSize; + } + + public void setBufferSize(int buffer) { + defaultBufferSize = buffer; + } + + public void setPrefix(String prefix) { + connectorPrefix = prefix; + } + + /** + * Added this method we that we can save the calculated stats. + * + * @return current sample + */ + public MonitorModel getLastSample() { + return this.current; + } + + /** + * Method will look up the server in the map. The MonitorModel will be added + * to an existing list, or a new one will be created. + * + * @param model + */ + public void addSample(MonitorModel model) { + this.current = model; + if (serverListMap.containsKey(model.getURL())) { + List newlist = updateArray(model, serverListMap.get(model.getURL())); + serverListMap.put(model.getURL(), newlist); + } else { + List samples = Collections.synchronizedList(new LinkedList()); + samples.add(model); + serverListMap.put(model.getURL(), samples); + } + } + + /** + * We want to keep only 240 entries for each server, so we handle the object + * array ourselves. + * + * @param model + */ + private List updateArray(MonitorModel model, List list) { + if (list.size() < defaultBufferSize) { + list.add(model); + } else { + list.add(model); + list.remove(0); + } + return list; + } + + /** + * Get all MonitorModels matching the URL. + * + * @param url + * @return list + */ + public List getAllSamples(String url) { + if (!serverListMap.containsKey(url)) { + return Collections.synchronizedList(new LinkedList()); + } else { + return serverListMap.get(url); + } + } + + /** + * Get the MonitorModel matching the url. + * + * @param url + * @return list + */ + public MonitorModel getSample(String url) { + if (serverListMap.containsKey(url)) { + return serverListMap.get(url).get(0); + } else { + return null; + } + } + + /** + * Method will try to parse the response data. If the request was a monitor + * request, but the response was incomplete, bad or the server refused the + * connection, we will set the server's health to "dead". If the request was + * not a monitor sample, the method will ignore it. + * + * @param sample + */ + public void addSample(SampleResult sample) { + URL surl = null; + if (sample instanceof HTTPSampleResult) { + surl = ((HTTPSampleResult) sample).getURL(); + // String rescontent = new String(sample.getResponseData()); + if (sample.isResponseCodeOK() && ((HTTPSampleResult) sample).isMonitor()) { + ObjectFactory of = ObjectFactory.getInstance(); + Status st = of.parseBytes(sample.getResponseData()); + st.setConnectorPrefix(connectorPrefix); + if (surl != null) {// surl can be null if read from a file + MonitorStats stat = new MonitorStats(Stats.calculateStatus(st), Stats.calculateLoad(st), 0, Stats + .calculateMemoryLoad(st), Stats.calculateThreadLoad(st), surl.getHost(), String.valueOf(surl + .getPort()), surl.getProtocol(), System.currentTimeMillis()); + MonitorModel mo = new MonitorModel(stat); + this.addSample(mo); + notifyListeners(mo); + } + // This part of code throws NullPointerException + // Don't think Monitor results can be loaded from files + // see https://issues.apache.org/bugzilla/show_bug.cgi?id=51810 +// else { +// noResponse(surl); +// } + } else if (((HTTPSampleResult) sample).isMonitor()) { + noResponse(surl); + } + } + } + + /** + * If there is no response from the server, we create a new MonitorStats + * object with the current timestamp and health "dead". + * + * @param url + */ + public void noResponse(URL url) { + notifyListeners(createNewMonitorModel(url)); + } + + /** + * Method will return a new MonitorModel object with the given URL. This is + * used when the server fails to respond fully, or is dead. + * + * @param url + * @return new MonitorModel + */ + public MonitorModel createNewMonitorModel(URL url) { + MonitorStats stat = new MonitorStats(Stats.DEAD, 0, 0, 0, 0, url.getHost(), String.valueOf(url.getPort()), url + .getProtocol(), System.currentTimeMillis()); + MonitorModel mo = new MonitorModel(stat); + return mo; + } + + /** + * Clears everything except the listener. Do not clear the listeners. If we + * clear listeners, subsequent "run" will not notify the gui of data + * changes. + */ + public void clearData() { + for (List modelList : this.serverListMap.values()) { + modelList.clear(); + } + this.serverListMap.clear(); + } + + /** + * notify the listeners with the MonitorModel object. + * + * @param model + */ + public void notifyListeners(MonitorModel model) { + for (int idx = 0; idx < listeners.size(); idx++) { + MonitorListener ml = listeners.get(idx); + ml.addSample(model); + } + } + + /** + * Add a listener. When samples are added, the class will notify the + * listener of the change. + * + * @param listener + */ + public void addListener(MonitorListener listener) { + listeners.add(listener); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorGraph.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorGraph.java new file mode 100644 index 0000000..5512287 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorGraph.java @@ -0,0 +1,204 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Graphics; +import java.util.Iterator; +import java.util.List; + +import javax.swing.JComponent; + +import org.apache.jmeter.samplers.Clearable; + +/** + * MonitorGraph will draw the performance history of a given server. It displays + * 4 lines: + *

+ */ +public class MonitorGraph extends JComponent implements MonitorGuiListener, Clearable { + + private static final long serialVersionUID = 240L; + + private final MonitorAccumModel model; + + private MonitorModel current; + + private boolean drawHealth = true; + + private boolean drawLoad = true; + + private boolean drawMemory = true; + + private boolean drawThread = true; + + private boolean drawYgrid = true; + + private boolean drawXgrid = true; + + /** + * Needed for Serialization tests. + * @deprecated Only for use in unit testing + */ + @Deprecated + public MonitorGraph() { + // log.warn("Only for use in unit testing"); + model = null; + } + + public MonitorGraph(MonitorAccumModel model) { + this.model = model; + repaint(); + } + + public void setHealth(boolean health) { + this.drawHealth = health; + } + + public void setLoad(boolean load) { + this.drawLoad = load; + } + + public void setMem(boolean mem) { + this.drawMemory = mem; + } + + public void setThread(boolean thread) { + this.drawThread = thread; + } + + /** + * The method will first check to see if the graph is visible. If it is, it + * will repaint the graph. + */ + public void updateGui(final MonitorModel model) { + if (this.isShowing()) { + this.current = model; + repaint(); + } + } + + /** + * painComponent is responsible for drawing the actual graph. This is + * because of how screen works. Tried to use clipping, but I don't + * understand it well enough to get the desired effect. + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + if (this.current != null) { + synchronized (model) { + List samples = model.getAllSamples(this.current.getURL()); + int size = samples.size(); + synchronized (samples) { + Iterator e; + if (size > getWidth()) { + e = samples.listIterator(size - getWidth()); + } else { + e = samples.iterator(); + } + MonitorModel last = null; + for (int i = 0; e.hasNext(); i++) { + MonitorModel s = e.next(); + if (last == null) { + last = s; + } + drawSample(i, s, g, last); + last = s; + } + } + } + } + } + + /** + * updateGui() will call repaint + */ + public void updateGui() { + repaint(); + } + + /** + * clear will repaint the graph + */ + public void clearData() { + paintComponent(getGraphics()); + this.repaint(); + } + + private void drawSample(int x, MonitorModel model, Graphics g, MonitorModel last) { + double width = getWidth(); + double height = getHeight() - 10.0; + int xaxis = (int) (width * (x / width)); + int lastx = (int) (width * ((x - 1) / width)); + + // draw grid only when x is 1. If we didn't + // the grid line would draw over the data + // lines making it look bad. + if (drawYgrid && x == 1) { + int q1 = (int) (height * 0.25); + int q2 = (int) (height * 0.50); + int q3 = (int) (height * 0.75); + g.setColor(Color.lightGray); + g.drawLine(0, q1, getWidth(), q1); + g.drawLine(0, q2, getWidth(), q2); + g.drawLine(0, q3, getWidth(), q3); + } + if (drawXgrid && x == 1) { + int x1 = (int) (width * 0.25); + int x2 = (int) (width * 0.50); + int x3 = (int) (width * 0.75); + g.drawLine(x1, 0, x1, getHeight()); + g.drawLine(x2, 0, x2, getHeight()); + g.drawLine(x3, 0, x3, getHeight()); + g.drawLine(getWidth(), 0, getWidth(), getHeight()); + } + + if (drawHealth) { + int hly = (int) (height - (height * (model.getHealth() / 3.0))); + int lasty = (int) (height - (height * (last.getHealth() / 3.0))); + + g.setColor(Color.green); + g.drawLine(lastx, lasty, xaxis, hly); + } + + if (drawLoad) { + int ldy = (int) (height - (height * (model.getLoad() / 100.0))); + int lastldy = (int) (height - (height * (last.getLoad() / 100.0))); + + g.setColor(Color.blue); + g.drawLine(lastx, lastldy, xaxis, ldy); + } + + if (drawMemory) { + int mmy = (int) (height - (height * (model.getMemload() / 100.0))); + int lastmmy = (int) (height - (height * (last.getMemload() / 100.0))); + + g.setColor(Color.orange); + g.drawLine(lastx, lastmmy, xaxis, mmy); + } + + if (drawThread) { + int thy = (int) (height - (height * (model.getThreadload() / 100.0))); + int lastthy = (int) (height - (height * (last.getThreadload() / 100.0))); + + g.setColor(Color.red); + g.drawLine(lastx, lastthy, xaxis, thy); + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorGuiListener.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorGuiListener.java new file mode 100644 index 0000000..0bff653 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorGuiListener.java @@ -0,0 +1,23 @@ +/* + * 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.visualizers; + +public interface MonitorGuiListener { + void updateGui(MonitorModel event); + + void updateGui(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorHealthPanel.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorHealthPanel.java new file mode 100644 index 0000000..50a3560 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorHealthPanel.java @@ -0,0 +1,144 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import java.util.HashMap; + +import javax.swing.JScrollPane; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.samplers.Clearable; + +/** + * The health panel is responsible for showing the health of the servers. It + * only uses the most current information to show the status. + */ +public class MonitorHealthPanel extends JPanel implements MonitorListener, Clearable { + private static final long serialVersionUID = 240L; + + private final HashMap serverPanelMap = new HashMap(); + + private JPanel servers = null; + + private final MonitorAccumModel model; + + private JScrollPane serverScrollPane = null; + + // NOTUSED Font plainText = new Font("plain", Font.PLAIN, 9); + // These must not be static, otherwise Language change does not work + private final String INFO_H = JMeterUtils.getResString("monitor_equation_healthy"); //$NON-NLS-1$ + + private final String INFO_A = JMeterUtils.getResString("monitor_equation_active"); //$NON-NLS-1$ + + private final String INFO_W = JMeterUtils.getResString("monitor_equation_warning"); //$NON-NLS-1$ + + private final String INFO_D = JMeterUtils.getResString("monitor_equation_dead"); //$NON-NLS-1$ + + private final String INFO_LOAD = JMeterUtils.getResString("monitor_equation_load"); //$NON-NLS-1$ + + /** + * + * @deprecated Only for use in unit testing + */ + @Deprecated + public MonitorHealthPanel() { + // log.warn("Only for use in unit testing"); + model = null; + } + + /** + * + */ + public MonitorHealthPanel(MonitorAccumModel model) { + this.model = model; + this.model.addListener(this); + init(); + } + + /** + * init is responsible for creating the necessary legends and information + * for the health panel. + */ + private void init() {// called from ctor, so must not be overridable + this.setLayout(new BorderLayout()); + ImageIcon legend = JMeterUtils.getImage("monitor-legend.gif"); // I18N: Contains fixed English text ... + JLabel label = new JLabel(legend); + label.setPreferredSize(new Dimension(550, 25)); + this.add(label, BorderLayout.NORTH); + + this.servers = new JPanel(); + this.servers.setLayout(new BoxLayout(servers, BoxLayout.Y_AXIS)); + this.servers.setAlignmentX(Component.LEFT_ALIGNMENT); + + serverScrollPane = new JScrollPane(this.servers); + serverScrollPane.setPreferredSize(new Dimension(300, 300)); + this.add(serverScrollPane, BorderLayout.CENTER); + + // the equations + String eqstring1 = " " + INFO_H + " | " + INFO_A; + String eqstring2 = " " + INFO_W + " | " + INFO_D; + String eqstring3 = " " + INFO_LOAD; + JLabel eqs = new JLabel(); + eqs.setLayout(new BorderLayout()); + eqs.setPreferredSize(new Dimension(500, 60)); + eqs.add(new JLabel(eqstring1), BorderLayout.NORTH); + eqs.add(new JLabel(eqstring2), BorderLayout.CENTER); + eqs.add(new JLabel(eqstring3), BorderLayout.SOUTH); + this.add(eqs, BorderLayout.SOUTH); + } + + /** + * + * @param model + */ + public void addSample(MonitorModel model) { + if (serverPanelMap.containsKey(model.getURL())) { + ServerPanel pane = null; + if (serverPanelMap.get(model.getURL()) != null) { + pane = serverPanelMap.get((model.getURL())); + } else { + pane = new ServerPanel(model); + serverPanelMap.put(model.getURL(), pane); + } + pane.updateGui(model); + } else { + ServerPanel newpane = new ServerPanel(model); + serverPanelMap.put(model.getURL(), newpane); + this.servers.add(newpane); + newpane.updateGui(model); + } + this.servers.updateUI(); + } + + /** + * clear will clear the hashmap, remove all ServerPanels from the servers + * pane, and update the ui. + */ + public void clearData() { + this.serverPanelMap.clear(); + this.servers.removeAll(); + this.servers.updateUI(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorHealthVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorHealthVisualizer.java new file mode 100644 index 0000000..c71a5d9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorHealthVisualizer.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jmeter.visualizers; + +import java.awt.BorderLayout; +import java.awt.Graphics; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.Image; + +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * For performance reasons, I am using tabs for the visualizers. Since a + * visualizer is heavy weight, I don not want to have two separate result + * collectors rather the same information. Instead, I would rather have the + * visualizer be the container for the data and simply pass the data to child + * JComponents. In the future, we may want to add email alerts as a third tab. + */ +public class MonitorHealthVisualizer extends AbstractVisualizer implements ImageVisualizer, ItemListener, + GraphListener, Clearable { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String CONNECTOR_PREFIX = "connector.prefix"; // $NON-NLS-1$ + private static final String CONNECTOR_PREFIX_DEFAULT = ""; // $NON-NLS-1$ + + private static final String BUFFER = "monitor.buffer.size"; // $NON-NLS-1$ + + private MonitorTabPane tabPane; + + private MonitorHealthPanel healthPane; + + private MonitorPerformancePanel perfPane; + + private MonitorAccumModel model; + + private MonitorGraph graph; + + private JLabeledTextField prefixField; + + /** + * Constructor for the GraphVisualizer object. + */ + public MonitorHealthVisualizer() { + this.isStats = true; + initModel(); + init(); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + prefixField.setText(el.getPropertyAsString(CONNECTOR_PREFIX, CONNECTOR_PREFIX_DEFAULT)); + model.setPrefix(prefixField.getText()); + } + + @Override + public void modifyTestElement(TestElement c) { + super.modifyTestElement(c); + c.setProperty(CONNECTOR_PREFIX,prefixField.getText(),CONNECTOR_PREFIX_DEFAULT); + model.setPrefix(prefixField.getText()); + } + + private void initModel() { + model = new MonitorAccumModel(); + graph = new MonitorGraph(model); + model.setBufferSize(JMeterUtils.getPropDefault(BUFFER, 800)); + } + + public String getLabelResource() { + return "monitor_health_title"; // $NON-NLS-1$ + } + + /** + * Because of the unique requirements of a monitor We have to handle the + * results differently than normal GUI components. A monitor should be able + * to run for a very long time without eating up all the memory. + */ + public void add(SampleResult res) { + model.addSample(res); + try { + collector.recordStats(this.model.getLastSample().cloneMonitorStats()); + } catch (Exception e) { + // for now just swallow the exception + log.debug("StatsModel was null", e); + } + } + + public Image getImage() { + Image result = graph.createImage(this.getWidth(), this.getHeight()); + Graphics image = result.getGraphics(); + graph.paintComponent(image); + return result; + } + + public void itemStateChanged(ItemEvent e) { + } + + public synchronized void updateGui() { + this.repaint(); + } + + public synchronized void updateGui(Sample s) { + this.repaint(); + } + + /** + * Initialize the GUI. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + Border margin = new EmptyBorder(10, 10, 5, 10); + this.setBorder(margin); + + // Add the main panel and the graph + this.add(this.makeTitlePanel(), BorderLayout.NORTH); + this.createTabs(); + prefixField = new JLabeledTextField(JMeterUtils.getResString("monitor_label_prefix")); // $NON-NLS-1$ + add(prefixField, BorderLayout.SOUTH); + } + + private void createTabs() { + tabPane = new MonitorTabPane(); + createHealthPane(tabPane); + createPerformancePane(tabPane); + this.add(tabPane, BorderLayout.CENTER); + } + + /** + * Create the JPanel + * + * @param pane + */ + private void createHealthPane(MonitorTabPane pane) { + healthPane = new MonitorHealthPanel(model); + pane.addTab(JMeterUtils.getResString("monitor_health_tab_title"), healthPane); // $NON-NLS-1$ + } + + /** + * Create the JSplitPane for the performance history + * + * @param pane + */ + private void createPerformancePane(MonitorTabPane pane) { + perfPane = new MonitorPerformancePanel(model, graph); + pane.addTab(JMeterUtils.getResString("monitor_performance_tab_title"), perfPane); // $NON-NLS-1$ + } + + /** + * Clears the MonitorAccumModel. + */ + public void clearData() { + this.model.clearData(); + this.healthPane.clearData(); + this.perfPane.clearData(); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorListener.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorListener.java new file mode 100644 index 0000000..a55418b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorListener.java @@ -0,0 +1,21 @@ +/* + * 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.visualizers; + +public interface MonitorListener { + public void addSample(MonitorModel model); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorModel.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorModel.java new file mode 100644 index 0000000..6625c53 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorModel.java @@ -0,0 +1,127 @@ +/* + * 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.visualizers; + +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.apache.jmeter.samplers.Clearable; + +public class MonitorModel implements Clearable, Serializable, Cloneable { + + private static final long serialVersionUID = 240L; + + private MonitorStats current = new MonitorStats(0, 0, 0, 0, 0, "", "", "", System.currentTimeMillis()); + + /** + * + */ + public MonitorModel() { + super(); + } + + public MonitorModel(MonitorStats stat) { + this.current = stat; + } + + public int getHealth() { + return this.current.getHealth(); + } + + public int getLoad() { + return this.current.getLoad(); + } + + public int getCpuload() { + return this.current.getCpuLoad(); + } + + public int getMemload() { + return this.current.getMemLoad(); + } + + public int getThreadload() { + return this.current.getThreadLoad(); + } + + public String getHost() { + return this.current.getHost(); + } + + public String getPort() { + return this.current.getPort(); + } + + public String getProtocol() { + return this.current.getProtocol(); + } + + public long getTimestamp() { + return this.current.getTimeStamp(); + } + + public String getURL() { + return this.current.getURL(); + } + + /** + * Method will return a formatted date using SimpleDateFormat. + * + * @return String + */ + public String getTimestampString() { + Date date = new Date(this.current.getTimeStamp()); + SimpleDateFormat ft = new SimpleDateFormat(); + return ft.format(date); + } + + /** + * Method is used by DefaultMutableTreeNode to get the label for the node. + */ + @Override + public String toString() { + return getURL(); + } + + /** + * clear will create a new MonitorStats object. + */ + public void clearData() { + current = new MonitorStats(0, 0, 0, 0, 0, "", "", "", System.currentTimeMillis()); + } + + /** + * a clone method is provided for convienance. In some cases, it may be + * desirable to clone the object. + */ + @Override + public Object clone() { + return new MonitorModel(cloneMonitorStats()); + } + + /** + * a clone method to clone the stats + * + * @return new instance of the class + */ + public MonitorStats cloneMonitorStats() { + return new MonitorStats(current.getHealth(), current.getLoad(), current.getCpuLoad(), current.getMemLoad(), + current.getThreadLoad(), current.getHost(), current.getPort(), current.getProtocol(), current + .getTimeStamp()); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorPerformancePanel.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorPerformancePanel.java new file mode 100644 index 0000000..a70c46b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorPerformancePanel.java @@ -0,0 +1,302 @@ +/* + * 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.visualizers; + +import java.util.HashMap; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FlowLayout; + +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTree; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeSelectionModel; +import javax.swing.event.TreeSelectionListener; +import javax.swing.event.TreeSelectionEvent; + +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +public class MonitorPerformancePanel extends JSplitPane implements TreeSelectionListener, MonitorListener, Clearable { + + private static final long serialVersionUID = 240L; + + private JScrollPane TREEPANE; + + private JPanel GRAPHPANEL; + + private JTree SERVERTREE; + + private DefaultTreeModel TREEMODEL; + + private final MonitorGraph GRAPH; + + private DefaultMutableTreeNode ROOTNODE; + + private final HashMap SERVERMAP; + + private final MonitorAccumModel MODEL; + + private SampleResult ROOTSAMPLE; + + // Don't make these static, otherwise language change does not work + private final String LEGEND_HEALTH = JMeterUtils.getResString("monitor_legend_health"); //$NON-NLS-1$ + + private final String LEGEND_LOAD = JMeterUtils.getResString("monitor_legend_load"); //$NON-NLS-1$ + + private final String LEGEND_MEM = JMeterUtils.getResString("monitor_legend_memory_per"); //$NON-NLS-1$ + + private final String LEGEND_THREAD = JMeterUtils.getResString("monitor_legend_thread_per"); //$NON-NLS-1$ + + private final ImageIcon LEGEND_HEALTH_ICON = JMeterUtils.getImage("monitor-green-legend.gif"); //$NON-NLS-1$ + + private final ImageIcon LEGEND_LOAD_ICON = JMeterUtils.getImage("monitor-blue-legend.gif"); //$NON-NLS-1$ + + private final ImageIcon LEGEND_MEM_ICON = JMeterUtils.getImage("monitor-orange-legend.gif"); //$NON-NLS-1$ + + private final ImageIcon LEGEND_THREAD_ICON = JMeterUtils.getImage("monitor-red-legend.gif"); //$NON-NLS-1$ + + private final String GRID_LABEL_TOP = JMeterUtils.getResString("monitor_label_left_top"); //$NON-NLS-1$ + + private final String GRID_LABEL_MIDDLE = JMeterUtils.getResString("monitor_label_left_middle"); //$NON-NLS-1$ + + private final String GRID_LABEL_BOTTOM = JMeterUtils.getResString("monitor_label_left_bottom"); //$NON-NLS-1$ + + private final String GRID_LABEL_HEALTHY = JMeterUtils.getResString("monitor_label_right_healthy"); //$NON-NLS-1$ + +// private final String GRID_LABEL_ACTIVE = JMeterUtils.getResString("monitor_label_right_active"); //$NON-NLS-1$ + +// private final String GRID_LABEL_WARNING = JMeterUtils.getResString("monitor_label_right_warning"); //$NON-NLS-1$ + + private final String GRID_LABEL_DEAD = JMeterUtils.getResString("monitor_label_right_dead"); //$NON-NLS-1$ + + private final String PERF_TITLE = JMeterUtils.getResString("monitor_performance_title"); //$NON-NLS-1$ + + private final String SERVER_TITLE = JMeterUtils.getResString("monitor_performance_servers"); //$NON-NLS-1$ + + private Font plaintext = new Font("plain", Font.TRUETYPE_FONT, 10); //$NON-NLS-1$ + + /** + * + * @deprecated Only for use in unit testing + */ + @Deprecated + public MonitorPerformancePanel() { + // log.warn("Only for use in unit testing"); + SERVERMAP = null; + MODEL = null; + GRAPH = null; + } + + /** + * + */ + public MonitorPerformancePanel(MonitorAccumModel model, MonitorGraph graph) { + super(); + this.SERVERMAP = new HashMap(); + this.MODEL = model; + this.MODEL.addListener(this); + this.GRAPH = graph; + init(); + } + + /** + * init() will create all the necessary swing panels, labels and icons for + * the performance panel. + */ + private void init() {// called from ctor, so must not be overridable + ROOTSAMPLE = new SampleResult(); + ROOTSAMPLE.setSampleLabel(SERVER_TITLE); + ROOTSAMPLE.setSuccessful(true); + ROOTNODE = new DefaultMutableTreeNode(ROOTSAMPLE); + TREEMODEL = new DefaultTreeModel(ROOTNODE); + SERVERTREE = new JTree(TREEMODEL); + SERVERTREE.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + SERVERTREE.addTreeSelectionListener(this); + SERVERTREE.setShowsRootHandles(true); + TREEPANE = new JScrollPane(SERVERTREE); + TREEPANE.setPreferredSize(new Dimension(150, 200)); + this.add(TREEPANE, JSplitPane.LEFT); + this.setDividerLocation(0.18); + + JPanel right = new JPanel(); + right.setLayout(new BorderLayout()); + JLabel title = new JLabel(" " + PERF_TITLE); + title.setPreferredSize(new Dimension(200, 40)); + GRAPHPANEL = new JPanel(); + GRAPHPANEL.setLayout(new BorderLayout()); + GRAPHPANEL.setMaximumSize(new Dimension(MODEL.getBufferSize(), MODEL.getBufferSize())); + GRAPHPANEL.setBackground(Color.white); + GRAPHPANEL.add(GRAPH, BorderLayout.CENTER); + right.add(GRAPHPANEL, BorderLayout.CENTER); + + right.add(title, BorderLayout.NORTH); + right.add(createLegend(), BorderLayout.SOUTH); + right.add(createLeftGridLabels(), BorderLayout.WEST); + right.add(createRightGridLabels(), BorderLayout.EAST); + this.add(right, JSplitPane.RIGHT); + } + + /** + * Method will create the legends at the bottom of the performance tab + * explaining the meaning of each line. + * + * @return JPanel + */ + private JPanel createLegend() { + Dimension lsize = new Dimension(130, 18); + + JPanel legend = new JPanel(); + legend.setLayout(new FlowLayout()); + + JLabel load = new JLabel(LEGEND_LOAD); + load.setFont(plaintext); + load.setPreferredSize(lsize); + load.setIcon(LEGEND_LOAD_ICON); + legend.add(load); + + JLabel mem = new JLabel(LEGEND_MEM); + mem.setFont(plaintext); + mem.setPreferredSize(lsize); + mem.setIcon(LEGEND_MEM_ICON); + legend.add(mem); + + JLabel thd = new JLabel(LEGEND_THREAD); + thd.setFont(plaintext); + thd.setPreferredSize(lsize); + thd.setIcon(LEGEND_THREAD_ICON); + legend.add(thd); + + JLabel health = new JLabel(LEGEND_HEALTH); + health.setFont(plaintext); + health.setPreferredSize(lsize); + health.setIcon(LEGEND_HEALTH_ICON); + legend.add(health); + + return legend; + } + + /** + * Method is responsible for creating the left grid labels. + * + * @return JPanel + */ + private JPanel createLeftGridLabels() { + Dimension lsize = new Dimension(33, 20); + JPanel labels = new JPanel(); + labels.setLayout(new BorderLayout()); + + JLabel top = new JLabel(" " + GRID_LABEL_TOP); + top.setFont(plaintext); + top.setPreferredSize(lsize); + labels.add(top, BorderLayout.NORTH); + + JLabel mid = new JLabel(" " + GRID_LABEL_MIDDLE); + mid.setFont(plaintext); + mid.setPreferredSize(lsize); + labels.add(mid, BorderLayout.CENTER); + + JLabel bottom = new JLabel(" " + GRID_LABEL_BOTTOM); + bottom.setFont(plaintext); + bottom.setPreferredSize(lsize); + labels.add(bottom, BorderLayout.SOUTH); + return labels; + } + + /** + * Method is responsible for creating the grid labels on the right for + * "healthy" and "dead" + * + * @return JPanel + */ + private JPanel createRightGridLabels() { + JPanel labels = new JPanel(); + labels.setLayout(new BorderLayout()); + labels.setPreferredSize(new Dimension(40, GRAPHPANEL.getWidth() - 100)); + Dimension lsize = new Dimension(40, 20); + JLabel h = new JLabel(GRID_LABEL_HEALTHY); + h.setFont(plaintext); + h.setPreferredSize(lsize); + labels.add(h, BorderLayout.NORTH); + + JLabel d = new JLabel(GRID_LABEL_DEAD); + d.setFont(plaintext); + d.setPreferredSize(lsize); + labels.add(d, BorderLayout.SOUTH); + return labels; + } + + /** + * MonitorAccumModel will call this method to notify the component data has + * changed. + */ + public synchronized void addSample(MonitorModel model) { + if (!SERVERMAP.containsKey(model.getURL())) { + DefaultMutableTreeNode newnode = new DefaultMutableTreeNode(model); + newnode.setAllowsChildren(false); + SERVERMAP.put(model.getURL(), newnode); + ROOTNODE.add(newnode); + this.TREEPANE.updateUI(); + } + DefaultMutableTreeNode node = (DefaultMutableTreeNode) SERVERTREE.getLastSelectedPathComponent(); + if (node != null) { + Object usrobj = node.getUserObject(); + if (usrobj instanceof MonitorModel) { + GRAPH.updateGui((MonitorModel) usrobj); + } + } + } + + /** + * When the user selects a different node in the tree, we get the selected + * node. From the node, we get the UserObject used to create the treenode in + * the constructor. + */ + public void valueChanged(TreeSelectionEvent e) { + // we check to see if the lastSelectedPath is null + // after we clear, it would return null + if (SERVERTREE.getLastSelectedPathComponent() != null) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) SERVERTREE.getLastSelectedPathComponent(); + Object usrobj = node.getUserObject(); + if (usrobj != null && usrobj instanceof MonitorModel) { + MonitorModel mo = (MonitorModel) usrobj; + GRAPH.updateGui(mo); + this.updateUI(); + } + TREEPANE.updateUI(); + } + } + + /** + * clear will remove all child nodes from the ROOTNODE, clear the HashMap, + * update the graph and jpanel for the server tree. + */ + public void clearData() { + this.SERVERMAP.clear(); + ROOTNODE.removeAllChildren(); + SERVERTREE.updateUI(); + GRAPH.clearData(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorStats.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorStats.java new file mode 100644 index 0000000..5ee26e6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorStats.java @@ -0,0 +1,167 @@ +/* + * 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.visualizers; + +import java.io.Serializable; + +import org.apache.jmeter.testelement.AbstractTestElement; + +/* + * TODO - convert this into an immutable class using plain variables + * The current implementation is quite inefficient, as it stores everything + * in properties. + * + * This will require changes to ResultCollector.recordStats() + * and SaveService.saveTestElement() which are both currently only used by Monitor classes + */ +public class MonitorStats extends AbstractTestElement implements Serializable { + + private static final long serialVersionUID = 240L; + + private static final String HEALTH = "stats.health"; + + private static final String LOAD = "stats.load"; + + private static final String CPULOAD = "stats.cpuload"; + + private static final String MEMLOAD = "stats.memload"; + + private static final String THREADLOAD = "stats.threadload"; + + private static final String HOST = "stats.host"; + + private static final String PORT = "stats.port"; + + private static final String PROTOCOL = "stats.protocol"; + + private static final String TIMESTAMP = "stats.timestamp"; + + /** + * + */ + public MonitorStats() { + super(); + } + + /** + * Default constructor + * + * @param health + * @param load + * @param cpuload + * @param memload + * @param threadload + * @param host + * @param port + * @param protocol + * @param time + */ + public MonitorStats(int health, int load, int cpuload, int memload, int threadload, String host, String port, + String protocol, long time) { + this.setHealth(health); + this.setLoad(load); + this.setCpuLoad(cpuload); + this.setMemLoad(memload); + this.setThreadLoad(threadload); + this.setHost(host); + this.setPort(port); + this.setProtocol(protocol); + this.setTimeStamp(time); + } + + /** + * For convienance, this method returns the protocol, host and port as a + * URL. + * + * @return protocol://host:port + */ + public String getURL() { + return this.getProtocol() + "://" + this.getHost() + ":" + this.getPort(); + } + + public void setHealth(int health) { + this.setProperty(HEALTH, String.valueOf(health)); + } + + public void setLoad(int load) { + this.setProperty(LOAD, String.valueOf(load)); + } + + public void setCpuLoad(int load) { + this.setProperty(CPULOAD, String.valueOf(load)); + } + + public void setMemLoad(int load) { + this.setProperty(MEMLOAD, String.valueOf(load)); + } + + public void setThreadLoad(int load) { + this.setProperty(THREADLOAD, String.valueOf(load)); + } + + public void setHost(String host) { + this.setProperty(HOST, host); + } + + public void setPort(String port) { + this.setProperty(PORT, port); + } + + public void setProtocol(String protocol) { + this.setProperty(PROTOCOL, protocol); + } + + public void setTimeStamp(long time) { + this.setProperty(TIMESTAMP, String.valueOf(time)); + } + + public int getHealth() { + return this.getPropertyAsInt(HEALTH); + } + + public int getLoad() { + return this.getPropertyAsInt(LOAD); + } + + public int getCpuLoad() { + return this.getPropertyAsInt(CPULOAD); + } + + public int getMemLoad() { + return this.getPropertyAsInt(MEMLOAD); + } + + public int getThreadLoad() { + return this.getPropertyAsInt(THREADLOAD); + } + + public String getHost() { + return this.getPropertyAsString(HOST); + } + + public String getPort() { + return this.getPropertyAsString(PORT); + } + + public String getProtocol() { + return this.getPropertyAsString(PROTOCOL); + } + + public long getTimeStamp() { + return this.getPropertyAsLong(TIMESTAMP); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorTabPane.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorTabPane.java new file mode 100644 index 0000000..115f5a9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/MonitorTabPane.java @@ -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.visualizers; + +import javax.swing.JTabbedPane; + +public class MonitorTabPane extends JTabbedPane { + + private static final long serialVersionUID = 240L; + + /** + * + */ + public MonitorTabPane() { + super(TOP); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/Printable.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/Printable.java new file mode 100644 index 0000000..291d6fd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/Printable.java @@ -0,0 +1,30 @@ +/* + * 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.visualizers; + +import javax.swing.JComponent; + +/** + * Printable is used by components that can be saved to an external file. It is + * up to the visualizers to get the right component containing the JPanel or + * JComponent to save. + */ +public interface Printable { + JComponent getPrintableComponent(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/PropertyControlGui.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/PropertyControlGui.java new file mode 100644 index 0000000..af1bcc9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/PropertyControlGui.java @@ -0,0 +1,252 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +//import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +//import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; + +import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.config.gui.AbstractConfigGui; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.MenuFactory; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.reflect.Functor; + +public class PropertyControlGui extends AbstractConfigGui + implements ActionListener, UnsharedComponent { + + private static final long serialVersionUID = 1L; + + private static final String COLUMN_NAMES_0 = "name"; // $NON-NLS-1$ + + private static final String COLUMN_NAMES_1 = "value"; // $NON-NLS-1$ + + // TODO: add and delete not currently supported + private static final String ADD = "add"; // $NON-NLS-1$ + + private static final String DELETE = "delete"; // $NON-NLS-1$ + + private static final String SYSTEM = "system"; // $NON-NLS-1$ + + private static final String JMETER = "jmeter"; // $NON-NLS-1$ + + private JCheckBox systemButton = new JCheckBox("System"); + private JCheckBox jmeterButton = new JCheckBox("JMeter"); + + private JLabel tableLabel = new JLabel("Properties"); + + /** The table containing the list of arguments. */ + private transient JTable table; + + /** The model for the arguments table. */ + protected transient ObjectTableModel tableModel; + +// /** A button for adding new arguments to the table. */ +// private JButton add; +// +// /** A button for removing arguments from the table. */ +// private JButton delete; + + public PropertyControlGui() { + super(); + init(); + } + + public String getLabelResource() { + return "property_visualiser_title"; // $NON-NLS-1$ + } + + @Override + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.NON_TEST_ELEMENTS }); + } + + public void actionPerformed(ActionEvent action) { + String command = action.getActionCommand(); + if (ADD.equals(command)){ + return; + } + if (DELETE.equals(command)){ + return; + } + if (SYSTEM.equals(command)){ + setUpData(); + return; + } + if (JMETER.equals(command)){ + setUpData(); + return; + } + + } + + public TestElement createTestElement() { + TestElement el = new ConfigTestElement(); + modifyTestElement(el); + return el; + } + @Override + public void configure(TestElement element) { + super.configure(element); + setUpData(); + } + + private void setUpData(){ + tableModel.clearData(); + Properties p=null; + if (systemButton.isSelected()){ + p = System.getProperties(); + } + if (jmeterButton.isSelected()) { + p = JMeterUtils.getJMeterProperties(); + } + if (p == null) { + return; + } + Set> s = p.entrySet(); + ArrayList> al = new ArrayList>(s); + Collections.sort(al, new Comparator>(){ + public int compare(Map.Entry o1, Map.Entry o2) { + String m1,m2; + m1=(String)o1.getKey(); + m2=(String)o2.getKey(); + return m1.compareTo(m2); + } + }); + Iterator> i = al.iterator(); + while(i.hasNext()){ + tableModel.addRow(i.next()); + } + + } + + public void modifyTestElement(TestElement element) { + configureTestElement(element); + } + private Component makeMainPanel() { + initializeTableModel(); + table = new JTable(tableModel); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + return makeScrollPane(table); + } + + /** + * Create a panel containing the title label for the table. + * + * @return a panel containing the title label + */ + private Component makeLabelPanel() { + JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + ButtonGroup bg = new ButtonGroup(); + bg.add(systemButton); + bg.add(jmeterButton); + jmeterButton.setSelected(true); + systemButton.setActionCommand(SYSTEM); + jmeterButton.setActionCommand(JMETER); + systemButton.addActionListener(this); + jmeterButton.addActionListener(this); + + labelPanel.add(systemButton); + labelPanel.add(jmeterButton); + labelPanel.add(tableLabel); + return labelPanel; + } + +// /** +// * Create a panel containing the add and delete buttons. +// * +// * @return a GUI panel containing the buttons +// */ +// private JPanel makeButtonPanel() {// Not currently used +// add = new JButton(JMeterUtils.getResString("add")); // $NON-NLS-1$ +// add.setActionCommand(ADD); +// add.setEnabled(true); +// +// delete = new JButton(JMeterUtils.getResString("delete")); // $NON-NLS-1$ +// delete.setActionCommand(DELETE); +// +// JPanel buttonPanel = new JPanel(); +// buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); +// add.addActionListener(this); +// delete.addActionListener(this); +// buttonPanel.add(add); +// buttonPanel.add(delete); +// return buttonPanel; +// } + + /** + * Initialize the components and layout of this component. + */ + private void init() { + JPanel p = this; + + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + p = new JPanel(); + + p.setLayout(new BorderLayout()); + + p.add(makeLabelPanel(), BorderLayout.NORTH); + p.add(makeMainPanel(), BorderLayout.CENTER); + // Force a minimum table height of 70 pixels + p.add(Box.createVerticalStrut(70), BorderLayout.WEST); + //p.add(makeButtonPanel(), BorderLayout.SOUTH); + + add(p, BorderLayout.CENTER); + table.revalidate(); + } + private void initializeTableModel() { + tableModel = new ObjectTableModel(new String[] { COLUMN_NAMES_0, COLUMN_NAMES_1 }, + new Functor[] { + new Functor(Map.Entry.class, "getKey"), // $NON-NLS-1$ + new Functor(Map.Entry.class, "getValue") }, // $NON-NLS-1$ + new Functor[] { + null, //new Functor("setName"), // $NON-NLS-1$ + new Functor(Map.Entry.class,"setValue", new Class[] { Object.class }) // $NON-NLS-1$ + }, + new Class[] { String.class, String.class }); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsHTML.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsHTML.java new file mode 100644 index 0000000..6e40796 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsHTML.java @@ -0,0 +1,145 @@ +/* + * 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.visualizers; + +import javax.swing.JEditorPane; +import javax.swing.text.ComponentView; +import javax.swing.text.Document; +import javax.swing.text.EditorKit; +import javax.swing.text.Element; +import javax.swing.text.StyleConstants; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; +import javax.swing.text.html.HTML; +import javax.swing.text.html.HTMLEditorKit; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +public class RenderAsHTML extends SamplerResultTab implements ResultRenderer { + + private static final String TEXT_HTML = "text/html"; // $NON-NLS-1$ + + // Keep copies of the two editors needed + private static final EditorKit customisedEditor = new LocalHTMLEditorKit(); + + private static final EditorKit defaultHtmlEditor = JEditorPane.createEditorKitForContentType(TEXT_HTML); + + /** {@inheritDoc} */ + public void renderResult(SampleResult sampleResult) { + // get the text response and image icon + // to determine which is NOT null + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + showRenderedResponse(response, sampleResult); + } + + protected void showRenderedResponse(String response, SampleResult res) { + showRenderedResponse(response, res, false); + } + + protected void showRenderedResponse(String response, SampleResult res, boolean embedded) { + if (response == null) { + results.setText(""); + return; + } + + int htmlIndex = response.indexOf(" // $NON-NLS-1$ + + // Look for a case variation + if (htmlIndex < 0) { + htmlIndex = response.indexOf(" See + * http://issues.apache.org/bugzilla/show_bug.cgi?id=23315 + * + * Is this due to a bug in Java? + */ + results.getDocument().putProperty("IgnoreCharsetDirective", Boolean.TRUE); // $NON-NLS-1$ + + results.setText(html); + results.setCaretPosition(0); + resultsScrollPane.setViewportView(results); + + } + + private static class LocalHTMLEditorKit extends HTMLEditorKit { + + private static final long serialVersionUID = -3399554318202905392L; + + private static final ViewFactory defaultFactory = new LocalHTMLFactory(); + + @Override + public ViewFactory getViewFactory() { + return defaultFactory; + } + + private static class LocalHTMLFactory extends javax.swing.text.html.HTMLEditorKit.HTMLFactory { + /* + * Provide dummy implementations to suppress download and display of + * related resources: - FRAMEs - IMAGEs TODO create better dummy + * displays TODO suppress LINK somehow + */ + @Override + public View create(Element elem) { + Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute); + if (o instanceof HTML.Tag) { + HTML.Tag kind = (HTML.Tag) o; + if (kind == HTML.Tag.FRAME) { + return new ComponentView(elem); + } else if (kind == HTML.Tag.IMG) { + return new ComponentView(elem); + } + } + return super.create(elem); + } + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("view_results_render_html"); // $NON-NLS-1$ + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java new file mode 100644 index 0000000..502979b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsHTMLWithEmbedded.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.visualizers; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +public class RenderAsHTMLWithEmbedded extends RenderAsHTML + implements ResultRenderer { + + /** {@inheritDoc} */ + @Override + protected void showRenderedResponse(String response, SampleResult res) { + // enable embedded html resources + showRenderedResponse(response, res, true); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("view_results_render_html_embedded"); // $NON-NLS-1$ + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsJSON.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsJSON.java new file mode 100644 index 0000000..0c241cf --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsJSON.java @@ -0,0 +1,117 @@ +/* + * 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.visualizers; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +public class RenderAsJSON extends SamplerResultTab implements ResultRenderer { + + private static final String ESC_CHAR_REGEX = "\\\\[\"\\\\/bfnrt]|\\\\u[0-9A-Fa-f]{4}"; // $NON-NLS-1$ + + private static final String NORMAL_CHARACTER_REGEX = "[^\"\\\\]"; // $NON-NLS-1$ + + private static final String STRING_REGEX = "\"(" + ESC_CHAR_REGEX + "|" + NORMAL_CHARACTER_REGEX + ")*\""; // $NON-NLS-1$ + + // This 'other value' regex is deliberately weak, even accepting an empty string, to be useful when reporting malformed data. + private static final String OTHER_VALUE_REGEX = "[^\\{\\[\\]\\}\\,]*"; // $NON-NLS-1$ + + private static final String VALUE_OR_PAIR_REGEX = "((" + STRING_REGEX + "\\s*:)?\\s*(" + STRING_REGEX + "|" + OTHER_VALUE_REGEX + ")\\s*,?\\s*)"; // $NON-NLS-1$ + + private static final Pattern VALUE_OR_PAIR_PATTERN = Pattern.compile(VALUE_OR_PAIR_REGEX); + + /** {@inheritDoc} */ + public void renderResult(SampleResult sampleResult) { + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + showRenderJSONResponse(response); + } + + private void showRenderJSONResponse(String response) { + results.setContentType("text/plain"); // $NON-NLS-1$ + results.setText(response == null ? "" : prettyJSON(response)); + results.setCaretPosition(0); + resultsScrollPane.setViewportView(results); + } + + // It might be useful also to make this available in the 'Request' tab, for + // when posting JSON. + private static String prettyJSON(String json) { + StringBuilder pretty = new StringBuilder(json.length() * 2); // Educated guess + + final String tab = ": "; // $NON-NLS-1$ + StringBuilder index = new StringBuilder(); + String nl = ""; // $NON-NLS-1$ + + Matcher valueOrPair = VALUE_OR_PAIR_PATTERN.matcher(json); + + boolean misparse = false; + + for (int i = 0; i < json.length(); ) { + final char currentChar = json.charAt(i); + if ((currentChar == '{') || (currentChar == '[')) { + pretty.append(nl).append(index).append(currentChar); + i++; + index.append(tab); + misparse = false; + } + else if ((currentChar == '}') || (currentChar == ']')) { + if (index.length() > 0) { + index.delete(0, tab.length()); + } + pretty.append(nl).append(index).append(currentChar); + i++; + int j = i; + while ((j < json.length()) && Character.isWhitespace(json.charAt(j))) { + j++; + } + if ((j < json.length()) && (json.charAt(j) == ',')) { + pretty.append(","); // $NON-NLS-1$ + i=j+1; + } + misparse = false; + } + else if (valueOrPair.find(i) && valueOrPair.group().length() > 0) { + pretty.append(nl).append(index).append(valueOrPair.group()); + i=valueOrPair.end(); + misparse = false; + } + else { + if (!misparse) { + pretty.append(nl).append("- Parse failed from:"); + } + pretty.append(currentChar); + i++; + misparse = true; + } + nl = "\n"; // $NON-NLS-1$ + } + return pretty.toString(); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("view_results_render_json"); // $NON-NLS-1$ + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsRegexp.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsRegexp.java new file mode 100644 index 0000000..95645f1 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsRegexp.java @@ -0,0 +1,239 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.oro.text.PatternCacheLRU; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; + +/** + * Implement ResultsRender for Regexp tester + */ +public class RenderAsRegexp implements ResultRenderer, ActionListener { + + private static final String REGEXP_TESTER_COMMAND = "regexp_tester"; // $NON-NLS-1$ + + private JPanel regexpPane; + + private JTextArea regexpDataField; + + private JLabeledTextField regexpField; + + private JTextArea regexpResultField; + + private JTabbedPane rightSide; + + private SampleResult sampleResult = null; + + /** {@inheritDoc} */ + public void clearData() { + this.regexpDataField.setText(""); // $NON-NLS-1$ + // don't set empty to keep regexp + // regexpField.setText(""); // $NON-NLS-1$ + this.regexpResultField.setText(""); // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + public void init() { + // Create the panels for the regexp tab + regexpPane = createRegexpPanel(); + } + + /** + * Display the response as text or as rendered HTML. Change the text on the + * button appropriate to the current display. + * + * @param e the ActionEvent being processed + */ + public void actionPerformed(ActionEvent e) { + String command = e.getActionCommand(); + if ((sampleResult != null) && (REGEXP_TESTER_COMMAND.equals(command))) { + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + executeAndShowRegexpTester(response); + } + } + + /** + * Launch regexp engine to parse a input text + * @param textToParse + */ + private void executeAndShowRegexpTester(String textToParse) { + if (textToParse != null && textToParse.length() > 0 + && this.regexpField.getText().length() > 0) { + this.regexpResultField.setText(process(textToParse)); + this.regexpResultField.setCaretPosition(0); // go to first line + } + } + + private String process(String textToParse) { + + Perl5Matcher matcher = new Perl5Matcher(); + PatternMatcherInput input = new PatternMatcherInput(textToParse); + + PatternCacheLRU pcLRU = new PatternCacheLRU(); + Pattern pattern = pcLRU.getPattern(regexpField.getText(), Perl5Compiler.READ_ONLY_MASK); + List matches = new LinkedList(); + while (matcher.contains(input, pattern)) { + matches.add(matcher.getMatch()); + } + // Construct a multi-line string with all matches + StringBuilder sb = new StringBuilder(); + final int size = matches.size(); + sb.append("Match count: ").append(size).append("\n"); + for (int j = 0; j < size; j++) { + MatchResult mr = matches.get(j); + final int groups = mr.groups(); + for (int i = 0; i < groups; i++) { + sb.append("Match[").append(j+1).append("][").append(i).append("]=").append(mr.group(i)).append("\n"); + } + } + return sb.toString(); + + } + /** {@inheritDoc} */ + public void renderResult(SampleResult sampleResult) { + clearData(); + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + regexpDataField.setText(response); + regexpDataField.setCaretPosition(0); + } + + /** {@inheritDoc} */ + public void setupTabPane() { + // Add regexp tester pane + if (rightSide.indexOfTab(JMeterUtils.getResString("regexp_tester_title")) < 0) { // $NON-NLS-1$ + rightSide.addTab(JMeterUtils.getResString("regexp_tester_title"), regexpPane); // $NON-NLS-1$ + } + clearData(); + } + + /** + * @return RegExp Tester panel + */ + private JPanel createRegexpPanel() { + regexpDataField = new JTextArea(); + regexpDataField.setEditable(false); + regexpDataField.setLineWrap(true); + regexpDataField.setWrapStyleWord(true); + + JScrollPane regexpDataPane = GuiUtils.makeScrollPane(regexpDataField); + regexpDataPane.setMinimumSize(new Dimension(0, 200)); + + JPanel pane = new JPanel(new BorderLayout(0, 5)); + + JSplitPane mainSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + regexpDataPane, createRegexpTasksPanel()); + mainSplit.setDividerLocation(300); + pane.add(mainSplit, BorderLayout.CENTER); + return pane; + } + + /** + * Create the Regexp task pane + * + * @return Regexp task pane + */ + private JPanel createRegexpTasksPanel() { + JPanel regexpActionPanel = new JPanel(); + regexpActionPanel.setLayout(new BoxLayout(regexpActionPanel, BoxLayout.X_AXIS)); + Border margin = new EmptyBorder(5, 5, 0, 5); + regexpActionPanel.setBorder(margin); + regexpField = new JLabeledTextField(JMeterUtils.getResString("regexp_tester_field")); // $NON-NLS-1$ + regexpActionPanel.add(regexpField, BorderLayout.WEST); + + JButton regexpTester = new JButton(JMeterUtils.getResString("regexp_tester_button_test")); // $NON-NLS-1$ + regexpTester.setActionCommand(REGEXP_TESTER_COMMAND); + regexpTester.addActionListener(this); + regexpActionPanel.add(regexpTester, BorderLayout.EAST); + + regexpResultField = new JTextArea(); + regexpResultField.setEditable(false); + regexpResultField.setLineWrap(true); + regexpResultField.setWrapStyleWord(true); + + JPanel regexpTasksPanel = new JPanel(new BorderLayout(0, 5)); + regexpTasksPanel.add(regexpActionPanel, BorderLayout.NORTH); + regexpTasksPanel.add(GuiUtils.makeScrollPane(regexpResultField), BorderLayout.CENTER); + + return regexpTasksPanel; + } + + /** {@inheritDoc} */ + public synchronized void setRightSide(JTabbedPane side) { + rightSide = side; + } + + /** {@inheritDoc} */ + public synchronized void setSamplerResult(Object userObject) { + if (userObject instanceof SampleResult) { + sampleResult = (SampleResult) userObject; + } + } + + /** {@inheritDoc} */ + public void setLastSelectedTab(int index) { + // nothing to do + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("regexp_tester_title"); // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + public void renderImage(SampleResult sampleResult) { + clearData(); + regexpDataField.setText(JMeterUtils.getResString("regexp_render_no_text")); // $NON-NLS-1$ + } + + /** {@inheritDoc} */ + public void setBackgroundColor(Color backGround) { + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsText.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsText.java new file mode 100644 index 0000000..b1891e6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsText.java @@ -0,0 +1,46 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +public class RenderAsText extends SamplerResultTab implements ResultRenderer { + + /** {@inheritDoc} */ + public void renderResult(SampleResult sampleResult) { + String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + showTextResponse(response); + } + + private void showTextResponse(String response) { + results.setContentType("text/plain"); // $NON-NLS-1$ + results.setText(response == null ? "" : response); // $NON-NLS-1$ + results.setCaretPosition(0); + resultsScrollPane.setViewportView(results); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("view_results_render_text"); // $NON-NLS-1$ + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsXML.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsXML.java new file mode 100644 index 0000000..0b71893 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/RenderAsXML.java @@ -0,0 +1,234 @@ +/* + * 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.visualizers; + +import java.awt.Component; +import java.awt.GridLayout; +import java.io.ByteArrayInputStream; +import java.io.StringWriter; + +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.ToolTipManager; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeSelectionModel; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.XPathUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.tidy.Tidy; +import org.xml.sax.SAXException; + +public class RenderAsXML extends SamplerResultTab + implements ResultRenderer { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final byte[] XML_PFX = {'<','?','x','m','l',' '};//" 0) { + showErrorMessageDialog(sw.toString(), + "Tidy: " + tidy.getParseErrors() + " errors, " + tidy.getParseWarnings() + " warnings", + JOptionPane.WARNING_MESSAGE); + } + + JPanel domTreePanel = new DOMTreePanel(document); + resultsScrollPane.setViewportView(domTreePanel); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.SamplerResultTab#clearData() + */ + @Override + public void clearData() { + super.clearData(); + resultsScrollPane.setViewportView(null); // clear result tab on Ctrl-E + } + + /* + * + * A Dom tree panel for to display response as tree view author Dave Maung + * TODO implement to find any nodes in the tree using TreePath. + * + */ + private static class DOMTreePanel extends JPanel { + + private static final long serialVersionUID = 6871690021183779153L; + + private JTree domJTree; + + public DOMTreePanel(org.w3c.dom.Document document) { + super(new GridLayout(1, 0)); + try { + Node firstElement = getFirstElement(document); + DefaultMutableTreeNode top = new XMLDefaultMutableTreeNode(firstElement); + domJTree = new JTree(top); + + domJTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + domJTree.setShowsRootHandles(true); + JScrollPane domJScrollPane = new JScrollPane(domJTree); + domJTree.setAutoscrolls(true); + this.add(domJScrollPane); + ToolTipManager.sharedInstance().registerComponent(domJTree); + domJTree.setCellRenderer(new DomTreeRenderer()); + } catch (SAXException e) { + log.warn("Error trying to parse document", e); + } + + } + + /** + * Skip all DTD nodes, all prolog nodes. They dont support in tree view + * We let user to insert them however in DOMTreeView, we dont display it + * + * @param root + * @return + */ + private Node getFirstElement(Node parent) { + NodeList childNodes = parent.getChildNodes(); + Node toReturn = parent; // Must return a valid node, or may generate an NPE + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + toReturn = childNode; + if (childNode.getNodeType() == Node.ELEMENT_NODE){ + break; + } + + } + return toReturn; + } + + /** + * This class is to view as tooltext. This is very useful, when the + * contents has long string and does not fit in the view. it will also + * automatically wrap line for each 100 characters since tool tip + * support html. author Dave Maung + */ + private static class DomTreeRenderer extends DefaultTreeCellRenderer { + + private static final long serialVersionUID = 240210061375790195L; + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, + boolean leaf, int row, boolean phasFocus) { + super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, phasFocus); + + DefaultMutableTreeNode valueTreeNode = (DefaultMutableTreeNode) value; + setToolTipText(getHTML(valueTreeNode.toString(), "
", 100)); // $NON-NLS-1$ + return this; + } + + /** + * get the html + * + * @param str + * @param separator + * @param maxChar + * @return + */ + private String getHTML(String str, String separator, int maxChar) { + StringBuilder strBuf = new StringBuilder(""); // $NON-NLS-1$ + char[] chars = str.toCharArray(); + for (int i = 0; i < chars.length; i++) { + + if (i % maxChar == 0 && i != 0) { + strBuf.append(separator); + } + strBuf.append(encode(chars[i])); + + } + strBuf.append(""); // $NON-NLS-1$ + return strBuf.toString(); + + } + + private String encode(char c) { + String toReturn = String.valueOf(c); + switch (c) { + case '<': // $NON-NLS-1$ + toReturn = "<"; // $NON-NLS-1$ + break; + case '>': // $NON-NLS-1$ + toReturn = ">"; // $NON-NLS-1$ + break; + case '\'': // $NON-NLS-1$ + toReturn = "'"; // $NON-NLS-1$ + break; + case '\"': // $NON-NLS-1$ + toReturn = """; // $NON-NLS-1$ + break; + default: + // ignored + break; + + } + return toReturn; + } + } + } + + private static void showErrorMessageDialog(String message, String title, int messageType) { + JOptionPane.showMessageDialog(null, message, title, messageType); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return JMeterUtils.getResString("view_results_render_xml"); // $NON-NLS-1$ + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/RequestPanel.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/RequestPanel.java new file mode 100644 index 0000000..803c0be --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/RequestPanel.java @@ -0,0 +1,121 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.JPanel; +import javax.swing.JTabbedPane; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Manipulate all classes which implements request view panel interface + * and return a super panel with a bottom tab list of this classes + * + */ +public class RequestPanel { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private LinkedList listRequestView; + + private JTabbedPane tabbedRequest; + + private JPanel panel; + + /** + * Find and instanciate all class that extend RequestView + * and Create Request Panel + */ + public RequestPanel() { + listRequestView = new LinkedList(); + 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 = (RequestView) Class.forName(clazz).newInstance(); + if (rawTab.equals(requestView.getLabel())) { + rawObject = requestView; // use later + } else { + listRequestView.add(requestView); + } + } catch (Exception e) { + log.warn("Error in load result render:" + clazz, e); // $NON-NLS-1$ + } + } + // place raw tab in first position (first tab) + if (rawObject != null) { + listRequestView.addFirst((RequestView) rawObject); + } + + // Prepare the Request tabbed pane + tabbedRequest = new JTabbedPane(JTabbedPane.BOTTOM); + for (RequestView requestView : listRequestView) { + requestView.init(); + tabbedRequest.addTab(requestView.getLabel(), requestView.getPanel()); + } + + // Hint to background color on bottom tabs (grey, not blue) + panel = new JPanel(new BorderLayout()); + panel.add(tabbedRequest); + } + + /** + * Clear data in all request view + */ + public void clearData() { + for (RequestView requestView : listRequestView) { + requestView.clearData(); + } + } + + /** + * Put SamplerResult in all request view + * + * @param samplerResult + */ + public void setSamplerResult(SampleResult samplerResult) { + for (RequestView requestView : listRequestView) { + requestView.setSamplerResult(samplerResult); + } + } + + /** + * @return a tabbed panel for view request + */ + public JPanel getPanel() { + return panel; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/RequestView.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/RequestView.java new file mode 100644 index 0000000..0cf4f3d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/RequestView.java @@ -0,0 +1,60 @@ +/* + * 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.visualizers; + +import javax.swing.JPanel; + +/** + * Interface for request panel in View Results Tree + * All classes which implements this interface is display + * on bottom tab in request panel + * + */ +public interface RequestView { + + /** + * Init the panel + */ + public void init(); + + /** + * Clear all data in panel + */ + public void clearData(); + + /** + * Put the result bean to display in panel + * @param userObject result to display + */ + public void setSamplerResult(Object userObject); + + /** + * Get the panel + * @return the panel viewer + */ + public JPanel getPanel(); + + /** + * Get the label. Use as name for bottom tab + * @return the label's panel + */ + public String getLabel(); // return label + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/RequestViewRaw.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/RequestViewRaw.java new file mode 100644 index 0000000..bd8abe5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/RequestViewRaw.java @@ -0,0 +1,109 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; + +import javax.swing.JPanel; +import javax.swing.JTextArea; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; + +/** + * (historical) Panel to view request data + * + */ +public class RequestViewRaw implements RequestView { + + // Used by Request Panel + static final String KEY_LABEL = "view_results_table_request_tab_raw"; //$NON-NLS-1$ + + private JTextArea sampleDataField; + + private JPanel paneRaw; /** request pane content */ + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#init() + */ + public void init() { + paneRaw = new JPanel(new BorderLayout(0, 5)); + sampleDataField = new JTextArea(); + sampleDataField.setEditable(false); + sampleDataField.setLineWrap(true); + sampleDataField.setWrapStyleWord(true); + + paneRaw.add(GuiUtils.makeScrollPane(sampleDataField)); + + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#clearData() + */ + public void clearData() { + sampleDataField.setText(""); //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#setSamplerResult(java.lang.Object) + */ + public void setSamplerResult(Object objectResult) { + + if (objectResult instanceof SampleResult) { + SampleResult sampleResult = (SampleResult) objectResult; + // load time label + String sd = sampleResult.getSamplerData(); + if (sd != null) { + String rh = sampleResult.getRequestHeaders(); + // Don't display Request headers label if rh is null or empty + if (rh != null && rh.length() > 0) { + StringBuilder sb = new StringBuilder(sd.length() + + rh.length() + 20); + sb.append(sd); + sb.append("\n"); //$NON-NLS-1$ + sb.append(JMeterUtils.getResString("view_results_request_headers")); //$NON-NLS-1$ + sb.append("\n"); //$NON-NLS-1$ + sb.append(rh); + sd = sb.toString(); + } + sampleDataField.setText(sd); + } else { + // add a message when no request data (ex. Java request) + sampleDataField.setText(JMeterUtils + .getResString("view_results_table_request_raw_nodata")); //$NON-NLS-1$ + } + } + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#getPanel() + */ + public JPanel getPanel() { + return paneRaw; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.request.RequestView#getLabel() + */ + public String getLabel() { + return JMeterUtils.getResString(KEY_LABEL); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/ResultRenderer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/ResultRenderer.java new file mode 100644 index 0000000..1299cce --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/ResultRenderer.java @@ -0,0 +1,61 @@ +/* + * 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.visualizers; + +import java.awt.Color; + +import javax.swing.JTabbedPane; + +import org.apache.jmeter.samplers.SampleResult; + + +/** + * Interface to results render + */ +public interface ResultRenderer { + + public void clearData(); + + public void init(); + + public void setupTabPane(); + + public void setLastSelectedTab(int index); + + public void setRightSide(JTabbedPane rightSide); + + public void setSamplerResult(Object userObject); + + public void renderResult(SampleResult sampleResult); + + public void renderImage(SampleResult sampleResult); + + /** + * + * @return the string to be displayed by the ComboBox + */ + public String toString(); + + public void setBackgroundColor(Color backGround); + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/RunningSample.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/RunningSample.java new file mode 100644 index 0000000..0c8869f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/RunningSample.java @@ -0,0 +1,361 @@ +/* + * 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.visualizers; + +import java.text.DecimalFormat; + +import org.apache.jmeter.samplers.SampleResult; + +/** + *

+ * Running sample data container. Just instantiate a new instance of this + * class, and then call {@link #addSample(SampleResult)} a few times, and pull + * the stats out with whatever methods you prefer. + *

+ *

+ * Please note that this class is not thread-safe. + * The calling class is responsible for ensuring thread safety if required. + * Versions prior to 2.3.2 appeared to be thread-safe but weren't as label and index were not final. + * Also the caller needs to synchronize access in order to enure that variables are consistent. + *

+ * + */ +public class RunningSample { + + private static final DecimalFormat rateFormatter = new DecimalFormat("#.0"); // $NON-NLS-1$ + + private static final DecimalFormat errorFormatter = new DecimalFormat("#0.00%"); // $NON-NLS-1$ + + private long counter; + + private long runningSum; + + private long max, min; + + private long errorCount; + + private long firstTime; + + private long lastTime; + + private final String label; + + private final int index; + + /** + * Use this constructor to create the initial instance + */ + public RunningSample(String label, int index) { + this.label = label; + this.index = index; + init(); + } + + /** + * Copy constructor to create a duplicate of existing instance (without the + * disadvantages of clone() + * + * @param src existing RunningSample to be copied + */ + public RunningSample(RunningSample src) { + this.counter = src.counter; + this.errorCount = src.errorCount; + this.firstTime = src.firstTime; + this.index = src.index; + this.label = src.label; + this.lastTime = src.lastTime; + this.max = src.max; + this.min = src.min; + this.runningSum = src.runningSum; + } + + private void init() { + counter = 0L; + runningSum = 0L; + max = Long.MIN_VALUE; + min = Long.MAX_VALUE; + errorCount = 0L; + firstTime = Long.MAX_VALUE; + lastTime = 0L; + } + + /** + * Clear the counters (useful for differential stats) + * + */ + public void clear() { + init(); + } + + /** + * Get the elapsed time for the samples + * + * @return how long the samples took + */ + public long getElapsed() { + if (lastTime == 0) { + return 0;// No samples collected ... + } + return lastTime - firstTime; + } + + /** + * Returns the throughput associated to this sampler in requests per second. + * May be slightly skewed because it takes the timestamps of the first and + * last samples as the total time passed, and the test may actually have + * started before that start time and ended after that end time. + */ + public double getRate() { + if (counter == 0) { + return 0.0; // Better behaviour when howLong=0 or lastTime=0 + } + + long howLongRunning = lastTime - firstTime; + + if (howLongRunning == 0) { + return Double.MAX_VALUE; + } + + return (double) counter / howLongRunning * 1000.0; + } + + /** + * Returns the throughput associated to this sampler in requests per min. + * May be slightly skewed because it takes the timestamps of the first and + * last samples as the total time passed, and the test may actually have + * started before that start time and ended after that end time. + */ + public double getRatePerMin() { + if (counter == 0) { + return 0.0; // Better behaviour when howLong=0 or lastTime=0 + } + + long howLongRunning = lastTime - firstTime; + + if (howLongRunning == 0) { + return Double.MAX_VALUE; + } + return (double) counter / howLongRunning * 60000.0; + } + + /** + * Returns a String that represents the throughput associated for this + * sampler, in units appropriate to its dimension: + *

+ * The number is represented in requests/second or requests/minute or + * requests/hour. + *

+ * Examples: "34.2/sec" "0.1/sec" "43.0/hour" "15.9/min" + * + * @return a String representation of the rate the samples are being taken + * at. + */ + public String getRateString() { + double rate = getRate(); + + if (rate == Double.MAX_VALUE) { + return "N/A"; + } + + String unit = "sec"; + + if (rate < 1.0) { + rate *= 60.0; + unit = "min"; + } + if (rate < 1.0) { + rate *= 60.0; + unit = "hour"; + } + + String rval = rateFormatter.format(rate) + "/" + unit; + + return (rval); + } + + public String getLabel() { + return label; + } + + public int getIndex() { + return index; + } + + /** + * Records a sample. + * + */ + public void addSample(SampleResult res) { + long aTimeInMillis = res.getTime(); + + counter+=res.getSampleCount(); + errorCount += res.getErrorCount(); + + long startTime = res.getStartTime(); + long endTime = res.getEndTime(); + + if (firstTime > startTime) { + // this is our first sample, set the start time to current timestamp + firstTime = startTime; + } + + // Always update the end time + if (lastTime < endTime) { + lastTime = endTime; + } + runningSum += aTimeInMillis; + + if (aTimeInMillis > max) { + max = aTimeInMillis; + } + + if (aTimeInMillis < min) { + min = aTimeInMillis; + } + + } + + /** + * Adds another RunningSample to this one. + * Does not check if it has the same label and index. + */ + public void addSample(RunningSample rs) { + this.counter += rs.counter; + this.errorCount += rs.errorCount; + this.runningSum += rs.runningSum; + if (this.firstTime > rs.firstTime) { + this.firstTime = rs.firstTime; + } + if (this.lastTime < rs.lastTime) { + this.lastTime = rs.lastTime; + } + if (this.max < rs.max) { + this.max = rs.max; + } + if (this.min > rs.min) { + this.min = rs.min; + } + } + + /** + * Returns the time in milliseconds of the quickest sample. + * + * @return the time in milliseconds of the quickest sample. + */ + public long getMin() { + long rval = 0; + + if (min != Long.MAX_VALUE) { + rval = min; + } + return (rval); + } + + /** + * Returns the time in milliseconds of the slowest sample. + * + * @return the time in milliseconds of the slowest sample. + */ + public long getMax() { + long rval = 0; + + if (max != Long.MIN_VALUE) { + rval = max; + } + return (rval); + } + + /** + * Returns the average time in milliseconds that samples ran in. + * + * @return the average time in milliseconds that samples ran in. + */ + public long getAverage() { + if (counter == 0) { + return (0); + } + return (runningSum / counter); + } + + /** + * Returns the number of samples that have been recorded by this instance of + * the RunningSample class. + * + * @return the number of samples that have been recorded by this instance of + * the RunningSample class. + */ + public long getNumSamples() { + return (counter); + } + + /** + * Returns the raw double value of the percentage of samples with errors + * that were recorded. (Between 0.0 and 1.0) If you want a nicer return + * format, see {@link #getErrorPercentageString()}. + * + * @return the raw double value of the percentage of samples with errors + * that were recorded. + */ + public double getErrorPercentage() { + double rval = 0.0; + + if (counter == 0) { + return (rval); + } + rval = (double) errorCount / (double) counter; + return (rval); + } + + /** + * Returns a String which represents the percentage of sample errors that + * have occurred. ("0.00%" through "100.00%") + * + * @return a String which represents the percentage of sample errors that + * have occurred. + */ + public String getErrorPercentageString() { + double myErrorPercentage = this.getErrorPercentage(); + + return (errorFormatter.format(myErrorPercentage)); + } + + /** + * For debugging purposes, mainly. + */ + @Override + public String toString() { + StringBuilder mySB = new StringBuilder(); + + mySB.append("Samples: " + this.getNumSamples() + " "); + mySB.append("Avg: " + this.getAverage() + " "); + mySB.append("Min: " + this.getMin() + " "); + mySB.append("Max: " + this.getMax() + " "); + mySB.append("Error Rate: " + this.getErrorPercentageString() + " "); + mySB.append("Sample Rate: " + this.getRateString()); + return (mySB.toString()); + } + + /** + * @return errorCount + */ + public long getErrorCount() { + return errorCount; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/Sample.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/Sample.java new file mode 100644 index 0000000..ffa536c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/Sample.java @@ -0,0 +1,216 @@ +/* + * 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.visualizers; + +import java.io.Serializable; +import java.text.Format; +import java.util.Date; + +public class Sample implements Serializable, Comparable { + private static final long serialVersionUID = 240L; + + private final long data; // = elapsed + + private final long average; + + private final long median; + + private final long distributionLine; // TODO: what is this for? + + private final long deviation; + + private final double throughput; + + private final long errorCount; + + private final boolean success; + + private final String label; + + private final String threadName; + + private final long count; + + private final long endTime; + + private final int bytes; + + public Sample(String name, long data, long average, long deviation, long median, long distributionLine, + double throughput, long errorCount, boolean success, long num, long endTime) { + this.data = data; + this.average = average; + this.deviation = deviation; + this.throughput = throughput; + this.success = success; + this.median = median; + this.distributionLine = distributionLine; + this.label = name; + this.errorCount = errorCount; + this.count = num; + this.endTime = endTime; + this.bytes = 0; + this.threadName = ""; + } + + public Sample(String name, long data, long average, long deviation, long median, long distributionLine, + double throughput, long errorCount, boolean success, long num, long endTime, int bytes, String threadName) { + this.data = data; + this.average = average; + this.deviation = deviation; + this.throughput = throughput; + this.success = success; + this.median = median; + this.distributionLine = distributionLine; + this.label = name; + this.errorCount = errorCount; + this.count = num; + this.endTime = endTime; + this.bytes = bytes; + this.threadName = threadName; + } + + public Sample() { + this(null, 0, 0, 0, 0, 0, 0, 0, true, 0, 0); + } + + // Appears not to be used - however it is invoked via the Functor class + public int getBytes() { + return bytes; + } + + /** + * @return Returns the average. + */ + public long getAverage() { + return average; + } + + /** + * @return Returns the count. + */ + public long getCount() { + return count; + } + + /** + * @return Returns the data (usually elapsed time) + */ + public long getData() { + return data; + } + + /** + * @return Returns the deviation. + */ + public long getDeviation() { + return deviation; + } + + /** + * @return Returns the distributionLine. + */ + public long getDistributionLine() { + return distributionLine; + } + + /** + * @return Returns the error. + */ + public boolean isSuccess() { + return success; + } + + /** + * @return Returns the errorCount. + */ + public long getErrorCount() { + return errorCount; + } + + /** + * @return Returns the label. + */ + public String getLabel() { + return label; + } + + /** + * @return Returns the threadName. + */ + public String getThreadName() { + return threadName; + } + + /** + * @return Returns the median. + */ + public long getMedian() { + return median; + } + + /** + * @return Returns the throughput. + */ + public double getThroughput() { + return throughput; + } + + /** {@inheritDoc} */ + public int compareTo(Sample o) { + Sample oo = o; + return ((count - oo.count) < 0 ? -1 : (count == oo.count ? 0 : 1)); + } + + // TODO should equals and hashCode depend on field other than count? + + @Override + public boolean equals(Object o){ + return ( + (o instanceof Sample) && + (this.compareTo((Sample) o) == 0) + ); + } + + @Override + public int hashCode(){ + return (int)(count ^ (count >>> 32)); + } + + /** + * @return Returns the endTime. + */ + public long getEndTime() { + return endTime; + } + + /** + * @return Returns the (calculated) startTime, assuming Data is the elapsed time. + */ + public long getStartTime() { + return endTime-data; + } + + /** + * @return the start time using the specified format + * Intended for use from Functors + */ + public String getStartTimeFormatted(Format format) { + return format.format(new Date(getStartTime())); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/SamplerResultTab.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/SamplerResultTab.java new file mode 100644 index 0000000..27d8861 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/SamplerResultTab.java @@ -0,0 +1,544 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.Set; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.JTextPane; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.text.BadLocationException; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.TextBoxDialoger.TextBoxDoubleClick; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.gui.GuiUtils; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.reflect.Functor; + +/** + * Right side in View Results Tree + * + */ +public abstract class SamplerResultTab implements ResultRenderer { + + // N.B. these are not multi-threaded, so don't make it static + private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); // ISO format $NON-NLS-1$ + + private static final String NL = "\n"; // $NON-NLS-1$ + + public static final Color SERVER_ERROR_COLOR = Color.red; + + public static final Color CLIENT_ERROR_COLOR = Color.blue; + + public static final Color REDIRECT_COLOR = Color.green; + + protected static final String TEXT_COMMAND = "text"; // $NON-NLS-1$ + + protected static final String REQUEST_VIEW_COMMAND = "change_request_view"; // $NON-NLS-1$ + + private static final String STYLE_SERVER_ERROR = "ServerError"; // $NON-NLS-1$ + + private static final String STYLE_CLIENT_ERROR = "ClientError"; // $NON-NLS-1$ + + private static final String STYLE_REDIRECT = "Redirect"; // $NON-NLS-1$ + + private JTextPane stats; + + private JPanel resultsPane; /** Response Data pane */ + protected JScrollPane resultsScrollPane; /** Contains results; contained in resultsPane */ + protected JEditorPane results; /** Response Data shown here */ + + private JLabel imageLabel; + + private RequestPanel requestPanel; /** request pane content */ + + protected JTabbedPane rightSide; /** holds the tabbed panes */ + + private int lastSelectedTab; + + private Object userObject = null; // Could be SampleResult or AssertionResult + + private SampleResult sampleResult = null; + + private AssertionResult assertionResult = null; + + protected SearchTextExtension searchTextExtension; + + private JPanel searchPanel = null; + + protected boolean activateSearchExtension = true; // most current subclasses can process text + + private Color backGround; + + private static final String[] COLUMNS_RESULT = new String[] { + " ", // one space for blank header // $NON-NLS-1$ + " " }; // one space for blank header // $NON-NLS-1$ + + private static final String[] COLUMNS_HEADERS = new String[] { + "view_results_table_headers_key", // $NON-NLS-1$ + "view_results_table_headers_value" }; // $NON-NLS-1$ + + private static final String[] COLUMNS_FIELDS = new String[] { + "view_results_table_fields_key", // $NON-NLS-1$ + "view_results_table_fields_value" }; // $NON-NLS-1$ + + private ObjectTableModel resultModel = null; + + private ObjectTableModel resHeadersModel = null; + + private ObjectTableModel resFieldsModel = null; + + private JTable tableResult = null; + + private JTable tableResHeaders = null; + + private JTable tableResFields = null; + + private JTabbedPane tabbedResult = null; + + private JScrollPane paneRaw = null; + + private JSplitPane paneParsed = null; + + // to save last select tab (raw/parsed) + private int lastResultTabIndex= 0; + + // Result column renderers + private static final TableCellRenderer[] RENDERERS_RESULT = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + // Response headers column renderers + private static final TableCellRenderer[] RENDERERS_HEADERS = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + // Response fields column renderers + private static final TableCellRenderer[] RENDERERS_FIELDS = new TableCellRenderer[] { + null, // Key + null, // Value + }; + + public SamplerResultTab() { + // create tables + resultModel = new ObjectTableModel(COLUMNS_RESULT, RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + resHeadersModel = new ObjectTableModel(COLUMNS_HEADERS, + RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + resFieldsModel = new ObjectTableModel(COLUMNS_FIELDS, RowResult.class, // The object used for each row + new Functor[] { + new Functor("getKey"), // $NON-NLS-1$ + new Functor("getValue") }, // $NON-NLS-1$ + new Functor[] { + null, null }, new Class[] { + String.class, String.class }, false); + } + + public void clearData() { + results.setText("");// Response Data // $NON-NLS-1$ + requestPanel.clearData();// Request Data // $NON-NLS-1$ + stats.setText(""); // Sampler result // $NON-NLS-1$ + resultModel.clearData(); + resHeadersModel.clearData(); + resFieldsModel.clearData(); + } + + public void init() { + rightSide.addTab(JMeterUtils.getResString("view_results_tab_sampler"), createResponseMetadataPanel()); // $NON-NLS-1$ + // Create the panels for the other tabs + requestPanel = new RequestPanel(); + resultsPane = createResponseDataPanel(); + } + + @SuppressWarnings("boxing") + public void setupTabPane() { + // Clear all data before display a new + this.clearData(); + StyledDocument statsDoc = stats.getStyledDocument(); + try { + if (userObject instanceof SampleResult) { + sampleResult = (SampleResult) userObject; + // We are displaying a SampleResult + setupTabPaneForSampleResult(); + requestPanel.setSamplerResult(sampleResult); + + final String samplerClass = sampleResult.getClass().getName(); + String typeResult = samplerClass.substring(1 + samplerClass.lastIndexOf('.')); + + StringBuilder statsBuff = new StringBuilder(200); + statsBuff.append(JMeterUtils.getResString("view_results_thread_name")).append(sampleResult.getThreadName()).append(NL); //$NON-NLS-1$ + String startTime = dateFormat.format(new Date(sampleResult.getStartTime())); + statsBuff.append(JMeterUtils.getResString("view_results_sample_start")).append(startTime).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_load_time")).append(sampleResult.getTime()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_latency")).append(sampleResult.getLatency()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_size_in_bytes")).append(sampleResult.getBytes()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_size_headers_in_bytes")).append(sampleResult.getHeadersSize()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_size_body_in_bytes")).append(sampleResult.getBodySize()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_sample_count")).append(sampleResult.getSampleCount()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_error_count")).append(sampleResult.getErrorCount()).append(NL); //$NON-NLS-1$ + statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), null); + statsBuff.setLength(0); // reset for reuse + + String responseCode = sampleResult.getResponseCode(); + + int responseLevel = 0; + if (responseCode != null) { + try { + responseLevel = Integer.parseInt(responseCode) / 100; + } catch (NumberFormatException numberFormatException) { + // no need to change the foreground color + } + } + + Style style = null; + switch (responseLevel) { + case 3: + style = statsDoc.getStyle(STYLE_REDIRECT); + break; + case 4: + style = statsDoc.getStyle(STYLE_CLIENT_ERROR); + break; + case 5: + style = statsDoc.getStyle(STYLE_SERVER_ERROR); + break; + default: // quieten Findbugs + break; // default - do nothing + } + + statsBuff.append(JMeterUtils.getResString("view_results_response_code")).append(responseCode).append(NL); //$NON-NLS-1$ + statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), style); + statsBuff.setLength(0); // reset for reuse + + // response message label + String responseMsgStr = sampleResult.getResponseMessage(); + + statsBuff.append(JMeterUtils.getResString("view_results_response_message")).append(responseMsgStr).append(NL); //$NON-NLS-1$ + statsBuff.append(NL); + statsBuff.append(JMeterUtils.getResString("view_results_response_headers")).append(NL); //$NON-NLS-1$ + statsBuff.append(sampleResult.getResponseHeaders()).append(NL); + statsBuff.append(NL); + statsBuff.append(typeResult + " "+ JMeterUtils.getResString("view_results_fields")).append(NL); //$NON-NLS-1$ $NON-NLS-2$ + statsBuff.append("ContentType: ").append(sampleResult.getContentType()).append(NL); //$NON-NLS-1$ + statsBuff.append("DataEncoding: ").append(sampleResult.getDataEncodingNoDefault()).append(NL); //$NON-NLS-1$ + statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), null); + statsBuff = null; // Done + + // Tabbed results: fill table + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_thread_name"), sampleResult.getThreadName())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_sample_start"), startTime)); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_load_time"), sampleResult.getTime())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_latency"), sampleResult.getLatency())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_size_in_bytes"), sampleResult.getBytes())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_size_headers_in_bytes"), sampleResult.getHeadersSize())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_size_body_in_bytes"), sampleResult.getBodySize())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_sample_count"), sampleResult.getSampleCount())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_error_count"), sampleResult.getErrorCount())); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_response_code"), responseCode)); //$NON-NLS-1$ + resultModel.addRow(new RowResult(JMeterUtils.getParsedLabel("view_results_response_message"), responseMsgStr)); //$NON-NLS-1$ + + // Parsed response headers + LinkedHashMap lhm = JMeterUtils.parseHeaders(sampleResult.getResponseHeaders()); + Set> keySet = lhm.entrySet(); + for (Entry entry : keySet) { + resHeadersModel.addRow(new RowResult(entry.getKey(), entry.getValue())); + } + + // Fields table + resFieldsModel.addRow(new RowResult("Type Result ", typeResult)); //$NON-NLS-1$ + //not sure needs I18N? + resFieldsModel.addRow(new RowResult("ContentType", sampleResult.getContentType())); //$NON-NLS-1$ + resFieldsModel.addRow(new RowResult("DataEncoding", sampleResult.getDataEncodingNoDefault())); //$NON-NLS-1$ + + // Reset search + if (activateSearchExtension) { + searchTextExtension.resetTextToFind(); + } + + } else if (userObject instanceof AssertionResult) { + assertionResult = (AssertionResult) userObject; + + // We are displaying an AssertionResult + setupTabPaneForAssertionResult(); + + StringBuilder statsBuff = new StringBuilder(100); + statsBuff.append(JMeterUtils.getResString("view_results_assertion_error")).append(assertionResult.isError()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_assertion_failure")).append(assertionResult.isFailure()).append(NL); //$NON-NLS-1$ + statsBuff.append(JMeterUtils.getResString("view_results_assertion_failure_message")).append(assertionResult.getFailureMessage()).append(NL); //$NON-NLS-1$ + statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), null); + } + } catch (BadLocationException exc) { + stats.setText(exc.getLocalizedMessage()); + } + } + + private void setupTabPaneForSampleResult() { + // restore tabbed pane parsed if needed + if (tabbedResult.getTabCount() < 2) { + tabbedResult.insertTab(JMeterUtils.getResString("view_results_table_result_tab_parsed"), null, paneParsed, null, 1); //$NON-NLS-1$ + tabbedResult.setSelectedIndex(lastResultTabIndex); // select last tab + } + // Set the title for the first tab + rightSide.setTitleAt(0, JMeterUtils.getResString("view_results_tab_sampler")); //$NON-NLS-1$ + // Add the other tabs if not present + if(rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_request")) < 0) { // $NON-NLS-1$ + rightSide.addTab(JMeterUtils.getResString("view_results_tab_request"), requestPanel.getPanel()); // $NON-NLS-1$ + } + if(rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_response")) < 0) { // $NON-NLS-1$ + rightSide.addTab(JMeterUtils.getResString("view_results_tab_response"), resultsPane); // $NON-NLS-1$ + } + // restore last selected tab + if (lastSelectedTab < rightSide.getTabCount()) { + rightSide.setSelectedIndex(lastSelectedTab); + } + } + + private void setupTabPaneForAssertionResult() { + // Remove the other (parsed) tab if present + if (tabbedResult.getTabCount() >= 2) { + lastResultTabIndex = tabbedResult.getSelectedIndex(); + int parsedTabIndex = tabbedResult.indexOfTab(JMeterUtils.getResString("view_results_table_result_tab_parsed")); // $NON-NLS-1$ + if(parsedTabIndex >= 0) { + tabbedResult.removeTabAt(parsedTabIndex); + } + } + // Set the title for the first tab + rightSide.setTitleAt(0, JMeterUtils.getResString("view_results_tab_assertion")); //$NON-NLS-1$ + // Remove the other tabs if present + int requestTabIndex = rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_request")); // $NON-NLS-1$ + if(requestTabIndex >= 0) { + rightSide.removeTabAt(requestTabIndex); + } + int responseTabIndex = rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_response")); // $NON-NLS-1$ + if(responseTabIndex >= 0) { + rightSide.removeTabAt(responseTabIndex); + } + } + + private Component createResponseMetadataPanel() { + stats = new JTextPane(); + stats.setEditable(false); + stats.setBackground(backGround); + + // Add styles to use for different types of status messages + StyledDocument doc = (StyledDocument) stats.getDocument(); + + Style style = doc.addStyle(STYLE_REDIRECT, null); + StyleConstants.setForeground(style, REDIRECT_COLOR); + + style = doc.addStyle(STYLE_CLIENT_ERROR, null); + StyleConstants.setForeground(style, CLIENT_ERROR_COLOR); + + style = doc.addStyle(STYLE_SERVER_ERROR, null); + StyleConstants.setForeground(style, SERVER_ERROR_COLOR); + + paneRaw = GuiUtils.makeScrollPane(stats); + paneRaw.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + + // Set up the 1st table Result with empty headers + tableResult = new JTable(resultModel); + tableResult.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableResult.addMouseListener(new TextBoxDoubleClick(tableResult)); + setFirstColumnPreferredSize(tableResult); + RendererUtils.applyRenderers(tableResult, RENDERERS_RESULT); + + // Set up the 2nd table + tableResHeaders = new JTable(resHeadersModel); + tableResHeaders.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableResHeaders.addMouseListener(new TextBoxDoubleClick(tableResHeaders)); + setFirstColumnPreferredSize(tableResHeaders); + tableResHeaders.getTableHeader().setDefaultRenderer( + new HeaderAsPropertyRenderer()); + RendererUtils.applyRenderers(tableResHeaders, RENDERERS_HEADERS); + + // Set up the 3rd table + tableResFields = new JTable(resFieldsModel); + tableResFields.setToolTipText(JMeterUtils.getResString("textbox_tooltip_cell")); // $NON-NLS-1$ + tableResFields.addMouseListener(new TextBoxDoubleClick(tableResFields)); + setFirstColumnPreferredSize(tableResFields); + tableResFields.getTableHeader().setDefaultRenderer( + new HeaderAsPropertyRenderer()); + RendererUtils.applyRenderers(tableResFields, RENDERERS_FIELDS); + + // Prepare the Results tabbed pane + tabbedResult = new JTabbedPane(JTabbedPane.BOTTOM); + + // Create the split pane + JSplitPane topSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + GuiUtils.makeScrollPane(tableResHeaders), + GuiUtils.makeScrollPane(tableResFields)); + topSplit.setOneTouchExpandable(true); + topSplit.setResizeWeight(0.80); // set split ratio + topSplit.setBorder(null); // see bug jdk 4131528 + + paneParsed = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + GuiUtils.makeScrollPane(tableResult), topSplit); + paneParsed.setOneTouchExpandable(true); + paneParsed.setResizeWeight(0.40); // set split ratio + paneParsed.setBorder(null); // see bug jdk 4131528 + + // setup bottom tabs, first Raw, second Parsed + tabbedResult.addTab(JMeterUtils.getResString("view_results_table_result_tab_raw"), paneRaw); //$NON-NLS-1$ + tabbedResult.addTab(JMeterUtils.getResString("view_results_table_result_tab_parsed"), paneParsed); //$NON-NLS-1$ + + // Hint to background color on bottom tabs (grey, not blue) + JPanel panel = new JPanel(new BorderLayout()); + panel.add(tabbedResult); + return panel; + } + + private JPanel createResponseDataPanel() { + results = new JEditorPane(); + results.setEditable(false); + + resultsScrollPane = GuiUtils.makeScrollPane(results); + imageLabel = new JLabel(); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(resultsScrollPane, BorderLayout.CENTER); + + if (activateSearchExtension) { + // Add search text extension + searchTextExtension = new SearchTextExtension(); + searchTextExtension.init(panel); + searchPanel = searchTextExtension.createSearchTextExtensionPane(); + searchTextExtension.setResults(results); + searchPanel.setVisible(true); + panel.add(searchPanel, BorderLayout.PAGE_END); + } + + return panel; + } + + private void showImage(Icon image) { + imageLabel.setIcon(image); + resultsScrollPane.setViewportView(imageLabel); + } + + public synchronized void setSamplerResult(Object sample) { + userObject = sample; + } + + public synchronized void setRightSide(JTabbedPane side) { + rightSide = side; + } + + public void setLastSelectedTab(int index) { + lastSelectedTab = index; + } + + public void renderImage(SampleResult sampleResult) { + byte[] responseBytes = sampleResult.getResponseData(); + if (responseBytes != null) { + showImage(new ImageIcon(responseBytes)); //TODO implement other non-text types + } + } + + public void setBackgroundColor(Color backGround){ + this.backGround = backGround; + } + + private void setFirstColumnPreferredSize(JTable table) { + TableColumn column = table.getColumnModel().getColumn(0); + column.setMaxWidth(300); + column.setPreferredWidth(180); + } + + /** + * For model table + */ + public static class RowResult { + private String key; + + private Object value; + + public RowResult(String key, Object value) { + this.key = key; + this.value = value; + } + + /** + * @return the key + */ + public synchronized String getKey() { + return key; + } + + /** + * @param key + * the key to set + */ + public synchronized void setKey(String key) { + this.key = key; + } + + /** + * @return the value + */ + public synchronized Object getValue() { + return value; + } + + /** + * @param value + * the value to set + */ + public synchronized void setValue(Object value) { + this.value = value; + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/SamplingStatCalculator.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/SamplingStatCalculator.java new file mode 100644 index 0000000..2dc1a39 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/SamplingStatCalculator.java @@ -0,0 +1,287 @@ +/* + * 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.visualizers; + +import java.util.Map; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.math.StatCalculatorLong; + +/** + * Aggegate sample data container. Just instantiate a new instance of this + * class, and then call {@link #addSample(SampleResult)} a few times, and pull + * the stats out with whatever methods you prefer. + * + */ +public class SamplingStatCalculator { + private final StatCalculatorLong calculator = new StatCalculatorLong(); + + private double maxThroughput; + + private long firstTime; + + private String label; + + private volatile Sample currentSample; + + public SamplingStatCalculator(){ // Only for use by test code + this(""); + } + + public SamplingStatCalculator(String label) { + this.label = label; + init(); + } + + private void init() { + firstTime = Long.MAX_VALUE; + calculator.clear(); + maxThroughput = Double.MIN_VALUE; + currentSample = new Sample(); + } + + /** + * Clear the counters (useful for differential stats) + * + */ + public synchronized void clear() { + init(); + } + + public Sample getCurrentSample() { + return currentSample; + } + + /** + * Get the elapsed time for the samples + * + * @return how long the samples took + */ + public long getElapsed() { + if (getCurrentSample().getEndTime() == 0) { + return 0;// No samples collected ... + } + return getCurrentSample().getEndTime() - firstTime; + } + + /** + * Returns the throughput associated to this sampler in requests per second. + * May be slightly skewed because it takes the timestamps of the first and + * last samples as the total time passed, and the test may actually have + * started before that start time and ended after that end time. + */ + public double getRate() { + if (calculator.getCount() == 0) { + return 0.0; // Better behaviour when howLong=0 or lastTime=0 + } + + return getCurrentSample().getThroughput(); + } + + /** + * Throughput in bytes / second + * + * @return throughput in bytes/second + */ + public double getBytesPerSecond() { + // Code duplicated from getPageSize() + double rate = 0; + if (this.getElapsed() > 0 && calculator.getTotalBytes() > 0) { + rate = calculator.getTotalBytes() / ((double) this.getElapsed() / 1000); + } + if (rate < 0) { + rate = 0; + } + return rate; + } + + /** + * Throughput in kilobytes / second + * + * @return Throughput in kilobytes / second + */ + public double getKBPerSecond() { + return getBytesPerSecond() / 1024; // 1024=bytes per kb + } + + /** + * calculates the average page size, which means divide the bytes by number + * of samples. + * + * @return average page size in bytes (0 if sample count is zero) + */ + public double getAvgPageBytes() { + long count = calculator.getCount(); + if (count == 0) { + return 0; + } + return calculator.getTotalBytes() / (double) count; + } + + public String getLabel() { + return label; + } + + /** + * Records a sample. + * + */ + public Sample addSample(SampleResult res) { + long rtime, cmean, cstdv, cmedian, cpercent, eCount, endTime; + double throughput; + boolean rbool; + synchronized (calculator) { + calculator.addValue(res.getTime(), res.getSampleCount()); + calculator.addBytes(res.getBytes()); + setStartTime(res); + eCount = getCurrentSample().getErrorCount(); + eCount += res.getErrorCount(); + endTime = getEndTime(res); + long howLongRunning = endTime - firstTime; + throughput = ((double) calculator.getCount() / (double) howLongRunning) * 1000.0; + if (throughput > maxThroughput) { + maxThroughput = throughput; + } + + rtime = res.getTime(); + cmean = (long)calculator.getMean(); + cstdv = (long)calculator.getStandardDeviation(); + cmedian = calculator.getMedian().longValue(); + cpercent = calculator.getPercentPoint( 0.500 ).longValue(); +// TODO cpercent is the same as cmedian here - why? and why pass it to "distributionLine"? + rbool = res.isSuccessful(); + } + + long count = calculator.getCount(); + Sample s = + new Sample( null, rtime, cmean, cstdv, cmedian, cpercent, throughput, eCount, rbool, count, endTime ); + currentSample = s; + return s; + } + + private long getEndTime(SampleResult res) { + long endTime = res.getEndTime(); + long lastTime = getCurrentSample().getEndTime(); + if (lastTime < endTime) { + lastTime = endTime; + } + return lastTime; + } + + /** + * @param res + */ + private void setStartTime(SampleResult res) { + long startTime = res.getStartTime(); + if (firstTime > startTime) { + // this is our first sample, set the start time to current timestamp + firstTime = startTime; + } + } + + /** + * Returns the raw double value of the percentage of samples with errors + * that were recorded. (Between 0.0 and 1.0) + * + * @return the raw double value of the percentage of samples with errors + * that were recorded. + */ + public double getErrorPercentage() { + double rval = 0.0; + + if (calculator.getCount() == 0) { + return (rval); + } + rval = (double) getCurrentSample().getErrorCount() / (double) calculator.getCount(); + return (rval); + } + + /** + * For debugging purposes, only. + */ + @Override + public String toString() { + StringBuilder mySB = new StringBuilder(); + + mySB.append("Samples: " + this.getCount() + " "); + mySB.append("Avg: " + this.getMean() + " "); + mySB.append("Min: " + this.getMin() + " "); + mySB.append("Max: " + this.getMax() + " "); + mySB.append("Error Rate: " + this.getErrorPercentage() + " "); + mySB.append("Sample Rate: " + this.getRate()); + return (mySB.toString()); + } + + /** + * @return errorCount + */ + public long getErrorCount() { + return getCurrentSample().getErrorCount(); + } + + /** + * @return Returns the maxThroughput. + */ + public double getMaxThroughput() { + return maxThroughput; + } + + public Map getDistribution() { + return calculator.getDistribution(); + } + + public Number getPercentPoint(double percent) { + return calculator.getPercentPoint(percent); + } + + public long getCount() { + return calculator.getCount(); + } + + public Number getMax() { + return calculator.getMax(); + } + + public double getMean() { + return calculator.getMean(); + } + + public Number getMeanAsNumber() { + return Long.valueOf((long) calculator.getMean()); + } + + public Number getMedian() { + return calculator.getMedian(); + } + + public Number getMin() { + if (calculator.getMin().longValue() < 0) { + return Long.valueOf(0); + } + return calculator.getMin(); + } + + public Number getPercentPoint(float percent) { + return calculator.getPercentPoint(percent); + } + + public double getStandardDeviation() { + return calculator.getStandardDeviation(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/SearchTextExtension.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/SearchTextExtension.java new file mode 100644 index 0000000..4cac5a7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/SearchTextExtension.java @@ -0,0 +1,284 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.DefaultHighlighter; +import javax.swing.text.Document; +import javax.swing.text.Highlighter; + +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +public class SearchTextExtension implements ActionListener, DocumentListener { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String SEARCH_TEXT_COMMAND = "search_text"; // $NON-NLS-1$ + + private static volatile int LAST_POSITION_DEFAULT = 0; + + private int lastPosition = LAST_POSITION_DEFAULT; + + private final static Color HILIT_COLOR = Color.LIGHT_GRAY; + + private Highlighter selection; + + private Highlighter.HighlightPainter painter; + + private JLabel label; + + private JButton findButton; + + private JTextField textToFindField; + + private JCheckBox caseChkBox; + + private JCheckBox regexpChkBox; + + private String lastTextTofind; + + private boolean newSearch = false; + + private JEditorPane results; + + private JPanel searchPanel; + + + public void init(JPanel resultsPane) { + } + + public void setResults(JEditorPane results) { + if (this.results != null) { + newSearch = true; + resetTextToFind(); + } + this.results = results; + // prepare highlighter to show text find with search command + selection = new DefaultHighlighter(); + painter = new DefaultHighlighter.DefaultHighlightPainter(HILIT_COLOR); + results.setHighlighter(selection); + } + + /** + * Launch find text engine on response text + */ + private void executeAndShowTextFind() { + String textToFind = textToFindField.getText(); + if (results != null && results.getText().length() > 0 + && textToFind.length() > 0) { + + // new search? + if (lastTextTofind != null && !lastTextTofind.equals(textToFind)) { + lastPosition = LAST_POSITION_DEFAULT; + } + + if (log.isDebugEnabled()) { + log.debug("lastPosition=" + lastPosition); + } + Matcher matcher = null; + try { + Pattern pattern = createPattern(textToFind); + Document contentDoc = results.getDocument(); + String body = contentDoc.getText(lastPosition, + (contentDoc.getLength() - lastPosition)); + matcher = pattern.matcher(body); + + if ((matcher != null) && (matcher.find())) { + selection.removeAllHighlights(); + selection.addHighlight(lastPosition + matcher.start(), + lastPosition + matcher.end(), painter); + results.setCaretPosition(lastPosition + matcher.end()); + + // save search position + lastPosition = lastPosition + matcher.end(); + findButton.setText(JMeterUtils + .getResString("search_text_button_next"));// $NON-NLS-1$ + lastTextTofind = textToFind; + newSearch = true; + } else { + // Display not found message and reset search + JOptionPane.showMessageDialog(null, JMeterUtils + .getResString("search_text_msg_not_found"),// $NON-NLS-1$ + JMeterUtils.getResString("search_text_title_not_found"), // $NON-NLS-1$ + JOptionPane.INFORMATION_MESSAGE); + lastPosition = LAST_POSITION_DEFAULT; + findButton.setText(JMeterUtils + .getResString("search_text_button_find"));// $NON-NLS-1$ + results.setCaretPosition(0); + } + } catch (BadLocationException ble) { + log.error("Location exception in text find", ble);// $NON-NLS-1$ + } + } + } + + /** + * Create the text find task pane + * + * @return Text find task pane + */ + private JPanel createSearchTextPanel() { + Font font = new Font("SansSerif", Font.PLAIN, 10); + + // Search field + searchPanel = new JPanel(); + searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS)); + searchPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + label = new JLabel(JMeterUtils.getResString("search_text_field")); // $NON-NLS-1$ + label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); + searchPanel.add(label); + textToFindField = new JTextField(); // $NON-NLS-1$ + searchPanel.add(textToFindField); + searchPanel.add(Box.createRigidArea(new Dimension(5,0))); + + // add listener to intercept texttofind changes and reset search + textToFindField.getDocument().addDocumentListener(this); + + // Buttons + findButton = new JButton(JMeterUtils + .getResString("search_text_button_find")); // $NON-NLS-1$ + findButton.setFont(font); + findButton.setActionCommand(SEARCH_TEXT_COMMAND); + findButton.addActionListener(this); + searchPanel.add(findButton); + + // checkboxes + caseChkBox = new JCheckBox(JMeterUtils + .getResString("search_text_chkbox_case"), false); // $NON-NLS-1$ + caseChkBox.setFont(font); + searchPanel.add(caseChkBox); + regexpChkBox = new JCheckBox(JMeterUtils + .getResString("search_text_chkbox_regexp"), false); // $NON-NLS-1$ + regexpChkBox.setFont(font); + searchPanel.add(regexpChkBox); + + // when Enter is pressed, search start + InputMap im = textToFindField + .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + im.put(KeyStroke.getKeyStroke("ENTER"), SEARCH_TEXT_COMMAND); + ActionMap am = textToFindField.getActionMap(); + am.put(SEARCH_TEXT_COMMAND, new EnterAction()); + + // default not visible + searchPanel.setVisible(true); + return searchPanel; + } + + JPanel createSearchTextExtensionPane() { + JPanel pane = new JPanel(); + pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS)); + pane.add(createSearchTextPanel()); + return pane; + } + + /** + * Display the response as text or as rendered HTML. Change the text on the + * button appropriate to the current display. + * + * @param e + * the ActionEvent being processed + */ + public void actionPerformed(ActionEvent e) { + String command = e.getActionCommand(); + + // Search text in response data + if (SEARCH_TEXT_COMMAND.equals(command)) { + executeAndShowTextFind(); + } + } + + private class EnterAction extends AbstractAction { + private static final long serialVersionUID = 1L; + public void actionPerformed(ActionEvent ev) { + executeAndShowTextFind(); + } + } + + // DocumentListener method + public void changedUpdate(DocumentEvent e) { + // do nothing + } + + // DocumentListener method + public void insertUpdate(DocumentEvent e) { + resetTextToFind(); + } + + // DocumentListener method + public void removeUpdate(DocumentEvent e) { + resetTextToFind(); + } + + void resetTextToFind() { + if (newSearch) { + log.debug("reset pass"); + // Reset search + lastPosition = LAST_POSITION_DEFAULT; + lastTextTofind = null; + findButton.setText(JMeterUtils + .getResString("search_text_button_find"));// $NON-NLS-1$ + selection.removeAllHighlights(); + results.setCaretPosition(0); + newSearch = false; + } + } + + private Pattern createPattern(String textToFind) { + // desactivate or not specials regexp char + String textToFindQ = Pattern.quote(textToFind); + if (regexpChkBox.isSelected()) { + textToFindQ = textToFind; + } + Pattern pattern = null; + if (caseChkBox.isSelected()) { + pattern = Pattern.compile(textToFindQ); + } else { + pattern = Pattern.compile(textToFindQ, Pattern.CASE_INSENSITIVE); + } + return pattern; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/ServerPanel.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/ServerPanel.java new file mode 100644 index 0000000..fda20d2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/ServerPanel.java @@ -0,0 +1,202 @@ +/* + * 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.visualizers; + +import java.awt.Dimension; +import java.awt.FlowLayout; + +import javax.swing.ImageIcon; +import javax.swing.JPanel; +import javax.swing.JLabel; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.monitor.util.Stats; + +/** + * The purpose of ServerPanel is to display an unique server and its current + * status. The server label consist of the protocol, host and port. For example, + * a system with multiple Tomcat's running on different ports would be different + * ServerPanel. + */ +public class ServerPanel extends JPanel implements MonitorGuiListener { + + private static final long serialVersionUID = 240L; + + private JLabel serverField; + + private JLabel timestampField; + + /** + * Preference size for the health icon + */ + private Dimension prefsize = new Dimension(25, 75); + + private JLabel healthIcon; + + private JLabel loadIcon; + + /** + * Health Icons + */ + private static final ImageIcon HEALTHY = JMeterUtils.getImage("monitor-healthy.gif"); + + private static final ImageIcon ACTIVE = JMeterUtils.getImage("monitor-active.gif"); + + private static final ImageIcon WARNING = JMeterUtils.getImage("monitor-warning.gif"); + + private static final ImageIcon DEAD = JMeterUtils.getImage("monitor-dead.gif"); + + /** + * Load Icons + */ + private static final ImageIcon LOAD_0 = JMeterUtils.getImage("monitor-load-0.gif"); + + private static final ImageIcon LOAD_1 = JMeterUtils.getImage("monitor-load-1.gif"); + + private static final ImageIcon LOAD_2 = JMeterUtils.getImage("monitor-load-2.gif"); + + private static final ImageIcon LOAD_3 = JMeterUtils.getImage("monitor-load-3.gif"); + + private static final ImageIcon LOAD_4 = JMeterUtils.getImage("monitor-load-4.gif"); + + private static final ImageIcon LOAD_5 = JMeterUtils.getImage("monitor-load-5.gif"); + + private static final ImageIcon LOAD_6 = JMeterUtils.getImage("monitor-load-6.gif"); + + private static final ImageIcon LOAD_7 = JMeterUtils.getImage("monitor-load-7.gif"); + + private static final ImageIcon LOAD_8 = JMeterUtils.getImage("monitor-load-8.gif"); + + private static final ImageIcon LOAD_9 = JMeterUtils.getImage("monitor-load-9.gif"); + + private static final ImageIcon LOAD_10 = JMeterUtils.getImage("monitor-load-10.gif"); + + // private MonitorModel DATA; + + /** + * + */ + public ServerPanel(MonitorModel model) { + super(); + // DATA = model; + init(model); + } + + /** + * + * @deprecated Only for use in unit testing + */ + @Deprecated + public ServerPanel() { + // log.warn("Only for use in unit testing"); + } + + /** + * Init will create the JLabel widgets for the host, health, load and + * timestamp. + * + * @param model + */ + private void init(MonitorModel model) { + this.setLayout(new FlowLayout()); + serverField = new JLabel(model.getURL()); + this.add(serverField); + healthIcon = new JLabel(getHealthyImageIcon(model.getHealth())); + healthIcon.setPreferredSize(prefsize); + this.add(healthIcon); + loadIcon = new JLabel(getLoadImageIcon(model.getLoad())); + this.add(loadIcon); + timestampField = new JLabel(model.getTimestampString()); + this.add(timestampField); + } + + /** + * Static method for getting the right ImageIcon for the health. + * + * @param health + * @return image for the status + */ + private static ImageIcon getHealthyImageIcon(int health) { + ImageIcon i = null; + switch (health) { + case Stats.HEALTHY: + i = HEALTHY; + break; + case Stats.ACTIVE: + i = ACTIVE; + break; + case Stats.WARNING: + i = WARNING; + break; + case Stats.DEAD: + i = DEAD; + break; + } + return i; + } + + /** + * Static method looks up the right ImageIcon from the load value. + * + * @param load + * @return image for the load + */ + private static ImageIcon getLoadImageIcon(int load) { + if (load == 0) { + return LOAD_0; + } else if (load > 0 && load <= 10) { + return LOAD_1; + } else if (load > 10 && load <= 20) { + return LOAD_2; + } else if (load > 20 && load <= 30) { + return LOAD_3; + } else if (load > 30 && load <= 40) { + return LOAD_4; + } else if (load > 40 && load <= 50) { + return LOAD_5; + } else if (load > 50 && load <= 60) { + return LOAD_6; + } else if (load > 60 && load <= 70) { + return LOAD_7; + } else if (load > 70 && load <= 80) { + return LOAD_8; + } else if (load > 80 && load <= 90) { + return LOAD_9; + } else { + return LOAD_10; + } + } + + /** + * Method will update the ServerPanel's health, load, and timestamp. For + * efficiency, it uses the static method to lookup the images. + */ + public void updateGui(MonitorModel stat) { + // this.DATA = null; + // this.DATA = stat; + loadIcon.setIcon(getLoadImageIcon(stat.getLoad())); + healthIcon.setIcon(getHealthyImageIcon(stat.getHealth())); + timestampField.setText(stat.getTimestampString()); + this.updateGui(); + } + + /** + * update the gui + */ + public void updateGui() { + this.repaint(); + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/SimpleDataWriter.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/SimpleDataWriter.java new file mode 100644 index 0000000..4b7ce20 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/SimpleDataWriter.java @@ -0,0 +1,75 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +/******************************************************************************* + * This listener can record results to a file but not to the UI. It is meant to + * provide an efficient means of recording data by eliminating GUI overhead. + * + ******************************************************************************/ + +public class SimpleDataWriter extends AbstractVisualizer { + private static final long serialVersionUID = 240L; + + /*************************************************************************** + * Create the SimpleDataWriter. + **************************************************************************/ + + public SimpleDataWriter() { + init(); + setName(getStaticLabel()); + } + + public String getLabelResource() { + return "simple_data_writer_title"; // $NON-NLS-1$ + } + + /** + * Initialize the component in the UI + */ + + private void init() { + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + add(makeTitlePanel(), BorderLayout.NORTH); + } + + /** + * Does nothing, but required by interface. + */ + + public void clearData() { + } + + /** + * Does nothing, but required by interface. + * + * @param sample + * ignored + */ + + public void add(SampleResult sample) { + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/Spline3.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/Spline3.java new file mode 100644 index 0000000..7f9b0a7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/Spline3.java @@ -0,0 +1,424 @@ +/* + * 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.visualizers; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/* + * TODO : - implement ImageProducer interface - suggestions ;-) + */ + +/** + * This class implements the representation of an interpolated Spline curve. + *

+ * The curve described by such an object interpolates an arbitrary number of + * fixed points called nodes. The distance between two nodes should + * currently be constant. This is about to change in a later version but it can + * last a while as it's not really needed. Nevertheless, if you need the + * feature, just write me + * a note and I'll write it asap. + *

+ * The interpolated Spline curve can't be described by an polynomial analytic + * equation, the degree of which would be as high as the number of nodes, which + * would cause extreme oscillations of the curve on the edges. + *

+ * The solution is to split the curve accross a lot of little intervals : + * an interval starts at one node and ends at the next one. Then, the + * interpolation is done on each interval, according to the following conditions : + *

    + *
  1. the interpolated curve is degree 3 : it's a cubic curve ; + *
  2. the interpolated curve contains the two points delimiting the interval. + * This condition obviously implies the curve is continuous ; + *
  3. the interpolated curve has a smooth slope : the curvature has to be the + * same on the left and the right sides of each node ; + *
  4. the curvature of the global curve is 0 at both edges. + *
+ * Every part of the global curve is represented by a cubic (degree-3) + * polynomial, the coefficients of which have to be computed in order to meet + * the above conditions. + *

+ * This leads to a n-unknow n-equation system to resolve. One can resolve an + * equation system by several manners ; this class uses the Jacobi iterative + * method, particularly well adapted to this situation, as the diagonal of the + * system matrix is strong compared to the other elements. This implies the + * algorithm always converges ! This is not the case of the Gauss-Seidel + * algorithm, which is quite faster (it uses intermediate results of each + * iteration to speed up the convergence) but it doesn't converge in all the + * cases or it converges to a wrong value. This is not acceptable and that's why + * the Jacobi method is safer. Anyway, the gain of speed is about a factor of 3 + * but, for a 100x100 system, it means 10 ms instead of 30 ms, which is a pretty + * good reason not to explore the question any further :) + *

+ * Here is a little piece of code showing how to use this class : + * + *

 // ... float[] nodes = {3F, 2F, 4F, 1F, 2.5F, 5F, 3F}; Spline3 curve =
+ * new Spline3(nodes); // ... public void paint(Graphics g) { int[] plot =
+ * curve.getPlots(); for (int i = 1; i < n; i++) { g.drawLine(i - 1, plot[i -
+ * 1], i, plot[i]); } } // ...
+ *
+ * 
+ * + */ +public class Spline3 { + private static final Logger log = LoggingManager.getLoggerForClass(); + + protected float[][] _coefficients; + + protected float[][] _A; + + protected float[] _B; + + protected float[] _r; + + protected float[] _rS; + + protected int _m; // number of nodes + + protected int _n; // number of non extreme nodes (_m-2) + + final static protected float DEFAULT_PRECISION = (float) 1E-1; + + final static protected int DEFAULT_MAX_ITERATIONS = 100; + + protected float _minPrecision = DEFAULT_PRECISION; + + protected int _maxIterations = DEFAULT_MAX_ITERATIONS; + + /** + * Creates a new Spline curve by calculating the coefficients of each part + * of the curve, i.e. by resolving the equation system implied by the + * interpolation condition on every interval. + * + * @param r + * an array of float containing the vertical coordinates of the + * nodes to interpolate ; the vertical coordinates start at 0 and + * are equidistant with a step of 1. + */ + public Spline3(float[] r) { + int n = r.length; + + // the number of nodes is defined by the length of r + this._m = n; + // grab the nodes + this._r = new float[n]; + for (int i = 0; i < n; i++) { + _r[i] = r[i]; + } + // the number of non extreme nodes is the number of intervals + // minus 1, i.e. the length of r minus 2 + this._n = n - 2; + // computes interpolation coefficients + try { + long startTime = System.currentTimeMillis(); + + this.interpolation(); + if (log.isDebugEnabled()) { + long endTime = System.currentTimeMillis(); + long elapsedTime = endTime - startTime; + + if (log.isDebugEnabled()) { + log.debug("New Spline curve interpolated in "); + log.debug(elapsedTime + " ms"); + } + } + } catch (Exception e) { + log.error("Error when interpolating : ", e); + } + + } + + /** + * Computes the coefficients of the Spline interpolated curve, on each + * interval. The matrix system to resolve is AX=B + */ + protected void interpolation() { + // creation of the interpolation structure + _rS = new float[_m]; + _B = new float[_n]; + _A = new float[_n][_n]; + _coefficients = new float[_n + 1][4]; + // local variables + int i = 0, j = 0; + + // initialize system structures (just to be safe) + for (i = 0; i < _n; i++) { + _B[i] = 0; + for (j = 0; j < _n; j++) { + _A[i][j] = 0; + } + for (j = 0; j < 4; j++) { + _coefficients[i][j] = 0; + } + } + for (i = 0; i < _n; i++) { + _rS[i] = 0; + } + // initialize the diagonal of the system matrix (A) to 4 + for (i = 0; i < _n; i++) { + _A[i][i] = 4; + } + // initialize the two minor diagonals of A to 1 + for (i = 1; i < _n; i++) { + _A[i][i - 1] = 1; + _A[i - 1][i] = 1; + } + // initialize B + for (i = 0; i < _n; i++) { + _B[i] = 6 * (_r[i + 2] - 2 * _r[i + 1] + _r[i]); + } + // Jacobi system resolving + this.jacobi(); // results are stored in _rS + // computes the coefficients (di, ci, bi, ai) from the results + for (i = 0; i < _n + 1; i++) { + // di (degree 0) + _coefficients[i][0] = _r[i]; + // ci (degree 1) + _coefficients[i][1] = _r[i + 1] - _r[i] - (_rS[i + 1] + 2 * _rS[i]) / 6; + // bi (degree 2) + _coefficients[i][2] = _rS[i] / 2; + // ai (degree 3) + _coefficients[i][3] = (_rS[i + 1] - _rS[i]) / 6; + } + } + + /** + * Resolves the equation system by a Jacobi algorithm. The use of the slower + * Jacobi algorithm instead of Gauss-Seidel is choosen here because Jacobi + * is assured of to be convergent for this particular equation system, as + * the system matrix has a strong diagonal. + */ + protected void jacobi() { + // local variables + int i = 0, j = 0, iterations = 0; + // intermediate arrays + float[] newX = new float[_n]; + float[] oldX = new float[_n]; + + // Jacobi convergence test + if (!converge()) { + if (log.isDebugEnabled()) { + log.debug("Warning : equation system resolving is unstable"); + } + } + // init newX and oldX arrays to 0 + for (i = 0; i < _n; i++) { + newX[i] = 0; + oldX[i] = 0; + } + // main iteration + while ((this.precision(oldX, newX) > this._minPrecision) && (iterations < this._maxIterations)) { + System.arraycopy(oldX, 0, newX, 0, _n); + for (i = 0; i < _n; i++) { + newX[i] = _B[i]; + for (j = 0; j < i; j++) { + newX[i] = newX[i] - (_A[i][j] * oldX[j]); + } + for (j = i + 1; j < _n; j++) { + newX[i] = newX[i] - (_A[i][j] * oldX[j]); + } + newX[i] = newX[i] / _A[i][i]; + } + iterations++; + } + if (this.precision(oldX, newX) < this._minPrecision) { + if (log.isDebugEnabled()) { + log.debug("Minimal precision ("); + log.debug(this._minPrecision + ") reached after "); + log.debug(iterations + " iterations"); + } + } else if (iterations > this._maxIterations) { + if (log.isDebugEnabled()) { + log.debug("Maximal number of iterations ("); + log.debug(this._maxIterations + ") reached"); + log.debug("Warning : precision is only "); + log.debug("" + this.precision(oldX, newX)); + log.debug(", divergence is possible"); + } + } + System.arraycopy(newX, 0, _rS, 1, _n); + } + + /** + * Test if the Jacobi resolution of the equation system converges. It's OK + * if A has a strong diagonal. + */ + protected boolean converge() { + boolean converge = true; + int i = 0, j = 0; + float lineSum = 0F; + + for (i = 0; i < _n; i++) { + if (converge) { + lineSum = 0; + for (j = 0; j < _n; j++) { + lineSum = lineSum + Math.abs(_A[i][j]); + } + lineSum = lineSum - Math.abs(_A[i][i]); + if (lineSum > Math.abs(_A[i][i])) { + converge = false; + } + } + } + return converge; + } + + /** + * Computes the current precision reached. + */ + protected float precision(float[] oldX, float[] newX) { + float N = 0F, D = 0F, erreur = 0F; + int i = 0; + + for (i = 0; i < _n; i++) { + N = N + Math.abs(newX[i] - oldX[i]); + D = D + Math.abs(newX[i]); + } + if (D != 0F) { + erreur = N / D; + } else { + erreur = Float.MAX_VALUE; + } + return erreur; + } + + /** + * Computes a (vertical) Y-axis value of the global curve. + * + * @param t + * abscissa + * @return computed ordinate + */ + public float value(float t) { + int i = 0, splineNumber = 0; + float abscissa = 0F, result = 0F; + + // verify t belongs to the curve (range [0, _m-1]) + if ((t < 0) || (t > (_m - 1))) { + if (log.isDebugEnabled()) { + log.debug("Warning : abscissa " + t + " out of bounds [0, " + (_m - 1) + "]"); + } + // silent error, consider the curve is constant outside its range + if (t < 0) { + t = 0; + } else { + t = _m - 1; + } + } + // seek the good interval for t and get the piece of curve on it + splineNumber = (int) Math.floor(t); + if (t == (_m - 1)) { + // the upper limit of the curve range belongs by definition + // to the last interval + splineNumber--; + } + // computes the value of the curve at the pecified abscissa + // and relative to the beginning of the right piece of Spline curve + abscissa = t - splineNumber; + // the polynomial calculation is done by the (fast) Euler method + for (i = 0; i < 4; i++) { + result = result * abscissa; + result = result + _coefficients[splineNumber][3 - i]; + } + return result; + } + + /** + * Manual check of the curve at the interpolated points. + */ + public void debugCheck() { + int i = 0; + + for (i = 0; i < _m; i++) { + log.info("Point " + i + " : "); + log.info(_r[i] + " =? " + value(i)); + } + } + + /** + * Computes drawable plots from the curve for a given draw space. The values + * returned are drawable vertically and from the bottom of a Panel. + * + * @param width + * width within the plots have to be computed + * @param height + * height within the plots are expected to be drawed + * @return drawable plots within the limits defined, in an array of int (as + * many int as the value of the width parameter) + */ + public int[] getPlots(int width, int height) { + int[] plot = new int[width]; + // computes auto-scaling and absolute plots + float[] y = new float[width]; + float max = java.lang.Integer.MIN_VALUE; + float min = java.lang.Integer.MAX_VALUE; + + for (int i = 0; i < width; i++) { + y[i] = value(((float) i) * (_m - 1) / width); + if (y[i] < min) { + min = y[i]; + } + + if (y[i] > max) { + max = y[i]; + } + } + if (min < 0) { + min = 0; // shouldn't draw negative values + } + // computes relative auto-scaled plots to fit in the specified area + for (int i = 0; i < width; i++) { + plot[i] = Math.round(((y[i] - min) * (height - 1)) / (max - min)); + } + return plot; + } + + public void setPrecision(float precision) { + this._minPrecision = precision; + } + + public float getPrecision() { + return this._minPrecision; + } + + public void setToDefaultPrecision() { + this._minPrecision = DEFAULT_PRECISION; + } + + public float getDefaultPrecision() { + return DEFAULT_PRECISION; + } + + public void setMaxIterations(int iterations) { + this._maxIterations = iterations; + } + + public int getMaxIterations() { + return this._maxIterations; + } + + public void setToDefaultMaxIterations() { + this._maxIterations = DEFAULT_MAX_ITERATIONS; + } + + public int getDefaultMaxIterations() { + return DEFAULT_MAX_ITERATIONS; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/SplineModel.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/SplineModel.java new file mode 100644 index 0000000..e85cc4a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/SplineModel.java @@ -0,0 +1,135 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; + +public class SplineModel implements Clearable { + public static final int DEFAULT_NUMBER_OF_NODES = 10; + + public static final int DEFAULT_REFRESH_PERIOD = 1; + + protected final boolean SHOW_INCOMING_SAMPLES = true; + + // These are not currently updated + protected int numberOfNodes = DEFAULT_NUMBER_OF_NODES; + + protected int refreshPeriod = DEFAULT_REFRESH_PERIOD; + + /** Current Spline curve. */ + //@GuardedBy("this") + private Spline3 dataCurve = null; + + final CachingStatCalculator samples; + + //@GuardedBy("this") + private GraphListener listener; + + //@GuardedBy("this") + private String name; + + public SplineModel() { + samples = new CachingStatCalculator("Spline"); + } + + public synchronized void setListener(GraphListener vis) { + listener = vis; + } + + public synchronized void setName(String newName) { + name = newName; + } + + public boolean isEditable() { + return true; + } + + public synchronized Spline3 getDataCurve() { + return dataCurve; + } + + public long getMinimum() { + return samples.getMin().longValue(); + } + + public long getMaximum() { + return samples.getMax().longValue(); + } + + public long getAverage() { + return (long) samples.getMean(); + } + + public long getCurrent() { + return samples.getCurrentSample().getData(); + } + + public long getSample(int i) { + return samples.getSample(i).getData(); + } + + public long getNumberOfCollectedSamples() { + return samples.getCount(); + } + + public synchronized String getName() { + return name; + } + + public void uncompile() { + clearData(); + } + + public synchronized void clearData() { + // this.graph.clear(); + samples.clear(); + + this.dataCurve = null; + + if (listener != null) { + listener.updateGui(); + } + } + + public synchronized void add(SampleResult sampleResult) { + samples.addSample(sampleResult); + long n = samples.getCount(); + + if ((n % (numberOfNodes * refreshPeriod)) == 0) { + float[] floatNode = new float[numberOfNodes]; + // NOTUSED: long[] longSample = getSamples(); + // load each node + long loadFactor = n / numberOfNodes; + + for (int i = 0; i < numberOfNodes; i++) { + for (int j = 0; j < loadFactor; j++) { + floatNode[i] += samples.getSample((int) ((i * loadFactor) + j)).getData(); + } + floatNode[i] = floatNode[i] / loadFactor; + } + // compute the new Spline curve + dataCurve = new Spline3(floatNode); + if (listener != null) { + listener.updateGui(); + } + } else {// do nothing, wait for the next pile to complete + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/SplineVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/SplineVisualizer.java new file mode 100644 index 0000000..2cf1552 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/SplineVisualizer.java @@ -0,0 +1,337 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.GridLayout; +import java.awt.Image; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.gui.layout.VerticalLayout; + +/** + * This class implements a statistical analyser that takes samples to process a + * Spline interpolated curve. Currently, it tries to look mostly like the + * GraphVisualizer. + * + */ +public class SplineVisualizer extends AbstractVisualizer implements ImageVisualizer, GraphListener { + + private static final long serialVersionUID = 240L; + + private static final String SUFFIX_MS = " ms"; //$NON-NLS-1$ + + protected final Color BACKGROUND_COLOR = getBackground(); + + protected final Color MINIMUM_COLOR = new Color(0F, 0.5F, 0F); + + protected final Color MAXIMUM_COLOR = new Color(0.9F, 0F, 0F); + + protected final Color AVERAGE_COLOR = new Color(0F, 0F, 0.75F); + + protected final Color INCOMING_COLOR = Color.black; + + protected final int NUMBERS_TO_DISPLAY = 4; + + protected final boolean FILL_UP_WITH_ZEROS = false; + + private transient SplineGraph graph = null; + + private JLabel minimumLabel = null; + + private JLabel maximumLabel = null; + + private JLabel averageLabel = null; + + private JLabel incomingLabel = null; + + private JLabel minimumNumberLabel = null; + + private JLabel maximumNumberLabel = null; + + private JLabel averageNumberLabel = null; + + private JLabel incomingNumberLabel = null; + + private transient SplineModel model; + + public SplineVisualizer() { + super(); + model = new SplineModel(); + graph = new SplineGraph(); + this.model.setListener(this); + setGUI(); + } + + public void add(final SampleResult res) { + JMeterUtils.runSafe(new Runnable() { + public void run() { + model.add(res); + } + }); + } + + public String getLabelResource() { + return "spline_visualizer_title"; //$NON-NLS-1$ + } + + public void updateGui(Sample s) { + updateGui(); + } + + public void clearData() { + model.clearData(); + } + + private void setGUI() { + Color backColor = BACKGROUND_COLOR; + + this.setBackground(backColor); + + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + Border margin = new EmptyBorder(10, 10, 5, 10); + + mainPanel.setBorder(margin); + mainPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + + // NAME + mainPanel.add(makeTitlePanel()); + + maximumLabel = new JLabel(JMeterUtils.getResString("spline_visualizer_maximum")); //$NON-NLS-1$ + maximumLabel.setForeground(MAXIMUM_COLOR); + maximumLabel.setBackground(backColor); + + averageLabel = new JLabel(JMeterUtils.getResString("spline_visualizer_average")); //$NON-NLS-1$ + averageLabel.setForeground(AVERAGE_COLOR); + averageLabel.setBackground(backColor); + + incomingLabel = new JLabel(JMeterUtils.getResString("spline_visualizer_incoming")); //$NON-NLS-1$ + incomingLabel.setForeground(INCOMING_COLOR); + incomingLabel.setBackground(backColor); + + minimumLabel = new JLabel(JMeterUtils.getResString("spline_visualizer_minimum")); //$NON-NLS-1$ + minimumLabel.setForeground(MINIMUM_COLOR); + minimumLabel.setBackground(backColor); + + maximumNumberLabel = new JLabel("0 ms"); //$NON-NLS-1$ + maximumNumberLabel.setHorizontalAlignment(JLabel.RIGHT); + maximumNumberLabel.setForeground(MAXIMUM_COLOR); + maximumNumberLabel.setBackground(backColor); + + averageNumberLabel = new JLabel("0 ms"); //$NON-NLS-1$ + averageNumberLabel.setHorizontalAlignment(JLabel.RIGHT); + averageNumberLabel.setForeground(AVERAGE_COLOR); + averageNumberLabel.setBackground(backColor); + + incomingNumberLabel = new JLabel("0 ms"); //$NON-NLS-1$ + incomingNumberLabel.setHorizontalAlignment(JLabel.RIGHT); + incomingNumberLabel.setForeground(INCOMING_COLOR); + incomingNumberLabel.setBackground(backColor); + + minimumNumberLabel = new JLabel("0 ms"); //$NON-NLS-1$ + minimumNumberLabel.setHorizontalAlignment(JLabel.RIGHT); + minimumNumberLabel.setForeground(MINIMUM_COLOR); + minimumNumberLabel.setBackground(backColor); + + // description Panel + JPanel labelPanel = new JPanel(); + + labelPanel.setLayout(new GridLayout(0, 1)); + labelPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 20)); + labelPanel.setBackground(backColor); + labelPanel.add(maximumLabel); + labelPanel.add(averageLabel); + if (model.SHOW_INCOMING_SAMPLES) { + labelPanel.add(incomingLabel); + } + labelPanel.add(minimumLabel); + // number Panel + JPanel numberPanel = new JPanel(); + + numberPanel.setLayout(new GridLayout(0, 1)); + numberPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 20)); + numberPanel.setBackground(backColor); + numberPanel.add(maximumNumberLabel); + numberPanel.add(averageNumberLabel); + if (model.SHOW_INCOMING_SAMPLES) { + numberPanel.add(incomingNumberLabel); + } + numberPanel.add(minimumNumberLabel); + // information display Panel + JPanel infoPanel = new JPanel(); + + infoPanel.setLayout(new BorderLayout()); + infoPanel.add(labelPanel, BorderLayout.CENTER); + infoPanel.add(numberPanel, BorderLayout.EAST); + + this.add(mainPanel, BorderLayout.NORTH); + this.add(infoPanel, BorderLayout.WEST); + this.add(graph, BorderLayout.CENTER); + // everyone is free to swing on its side :) + // add(infoPanel, BorderLayout.EAST); + } + + public void updateGui() { + repaint(); + synchronized (this) { + setMinimum(model.getMinimum()); + setMaximum(model.getMaximum()); + setAverage(model.getAverage()); + setIncoming(model.getCurrent()); + } + } + + @Override + public String toString() { + return "Show the samples analysis as a Spline curve"; + } + + private String formatMeasureToDisplay(long measure) { + String numberString = String.valueOf(measure); + + if (FILL_UP_WITH_ZEROS) { + for (int i = numberString.length(); i < NUMBERS_TO_DISPLAY; i++) { + numberString = "0" + numberString; //$NON-NLS-1$ + } + } + return numberString; + } + + private void setMinimum(long n) { + String text = this.formatMeasureToDisplay(n) + SUFFIX_MS; + + this.minimumNumberLabel.setText(text); + } + + private void setMaximum(long n) { + String text = this.formatMeasureToDisplay(n) + SUFFIX_MS; + + this.maximumNumberLabel.setText(text); + } + + private void setAverage(long n) { + String text = this.formatMeasureToDisplay(n) + SUFFIX_MS; + + this.averageNumberLabel.setText(text); + } + + private void setIncoming(long n) { + String text = this.formatMeasureToDisplay(n) + SUFFIX_MS; + + this.incomingNumberLabel.setText(text); + } + + public JPanel getControlPanel() {// TODO - is this needed? + return this; + } + + public Image getImage() { + Image result = graph.createImage(graph.getWidth(), graph.getHeight()); + + graph.paintComponent(result.getGraphics()); + + return result; + } + + /** + * Component showing a Spline curve. + * + */ + public class SplineGraph extends JComponent { + + private static final long serialVersionUID = 240L; + + private final Color WAITING_COLOR = Color.darkGray; + + private int lastWidth = -1; + + private int lastHeight = -1; + + private int[] plot = null; + + public SplineGraph() { + } + + /** + * Clear the Spline graph and get ready for the next wave. + */ + public void clear() { + lastWidth = -1; + lastHeight = -1; + plot = null; + this.repaint(); + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + + Dimension dimension = this.getSize(); + int width = dimension.width; + int height = dimension.height; + + if (model.getDataCurve() == null) { + g.setColor(this.getBackground()); + g.fillRect(0, 0, width, height); + g.setColor(WAITING_COLOR); + g.drawString(JMeterUtils.getResString("spline_visualizer_waitingmessage"), //$NON-NLS-1$ + (width - 120) / 2, height - (height - 12) / 2); + return; + } + + // boolean resized = true; + + if (width == lastWidth && height == lastHeight) { + // dimension of the SplineGraph is the same + // resized = false; + } else { + // dimension changed + // resized = true; + lastWidth = width; + lastHeight = height; + } + + this.plot = model.getDataCurve().getPlots(width, height); // rounds! + + int n = plot.length; + int curY = plot[0]; + + for (int i = 1; i < n; i++) { + g.setColor(Color.black); + g.drawLine(i - 1, height - curY - 1, i, height - plot[i] - 1); + curY = plot[i]; + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/StatGraphProperties.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/StatGraphProperties.java new file mode 100644 index 0000000..8bb4846 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/StatGraphProperties.java @@ -0,0 +1,58 @@ +/* + * 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.visualizers; + +import java.awt.Font; +import java.util.HashMap; +import java.util.Map; + +import org.apache.jmeter.util.JMeterUtils; +import org.jCharts.properties.LegendProperties; + +public class StatGraphProperties { + + public static final String[] fontSize = { "8", "9", "10", "11", "12", "14", "16", "18", "20", "24", "28", "32"}; + + public static Map getFontNameMap() { + Map fontNameMap = new HashMap(); + fontNameMap.put(JMeterUtils.getResString("font.sansserif"), "SansSerif"); + fontNameMap.put(JMeterUtils.getResString("font.serif"), "Serif"); + return fontNameMap; + } + + @SuppressWarnings("boxing") + public static Map getFontStyleMap() { + Map fontStyleMap = new HashMap(); + fontStyleMap.put(JMeterUtils.getResString("fontstyle.normal"), Font.PLAIN); + fontStyleMap.put(JMeterUtils.getResString("fontstyle.bold"), Font.BOLD); + fontStyleMap.put(JMeterUtils.getResString("fontstyle.italic"), Font.ITALIC); + return fontStyleMap; + } + + @SuppressWarnings("boxing") + public static Map getPlacementNameMap() { + Map placementNameMap = new HashMap(); + placementNameMap.put(JMeterUtils.getResString("aggregate_graph_legend.placement.bottom"), LegendProperties.BOTTOM); + placementNameMap.put(JMeterUtils.getResString("aggregate_graph_legend.placement.right"), LegendProperties.RIGHT); + placementNameMap.put(JMeterUtils.getResString("aggregate_graph_legend.placement.left"), LegendProperties.LEFT); + placementNameMap.put(JMeterUtils.getResString("aggregate_graph_legend.placement.top"), LegendProperties.TOP); + return placementNameMap; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/StatGraphVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/StatGraphVisualizer.java new file mode 100644 index 0000000..8ae24af --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/StatGraphVisualizer.java @@ -0,0 +1,868 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JColorChooser; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.table.TableCellRenderer; + +import org.apache.jmeter.gui.action.ActionNames; +import org.apache.jmeter.gui.action.ActionRouter; +import org.apache.jmeter.gui.action.SaveGraphics; +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.gui.JLabeledTextField; +import org.apache.jorphan.gui.NumberRenderer; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RateRenderer; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Aggregrate Table-Based Reporting Visualizer for JMeter. Props to the people + * who've done the other visualizers ahead of me (Stefano Mazzocchi), who I + * borrowed code from to start me off (and much code may still exist). Thank + * you! + * + */ +public class StatGraphVisualizer extends AbstractVisualizer implements Clearable, ActionListener { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final String[] COLUMNS = { JMeterUtils.getResString("sampler_label"), //$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_count"), //$NON-NLS-1$ + JMeterUtils.getResString("average"), //$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_median"), //$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_90%_line"), //$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_min"), //$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_max"), //$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_error%"), //$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_rate"), //$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_bandwidth") }; //$NON-NLS-1$ + + private final String[] GRAPH_COLUMNS = {JMeterUtils.getResString("average"),//$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_median"), //$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_90%_line"), //$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_min"), //$NON-NLS-1$ + JMeterUtils.getResString("aggregate_report_max")}; //$NON-NLS-1$ + + private final String TOTAL_ROW_LABEL = + JMeterUtils.getResString("aggregate_report_total_label"); //$NON-NLS-1$ + + private final Border MARGIN = new EmptyBorder(0, 5, 0, 5); + + private Font FONT_SMALL = new Font("SansSerif", Font.PLAIN, 10); + + private JTable myJTable; + + private JScrollPane myScrollPane; + + private transient ObjectTableModel model; + + /** + * Lock used to protect tableRows update + model update + */ + private final transient Object lock = new Object(); + + private final Map tableRows = + new ConcurrentHashMap(); + + private AxisGraph graphPanel = null; + + private JPanel settingsPane = null; + + private JSplitPane spane = null; + + //NOT USED protected double[][] data = null; + + private JTabbedPane tabbedGraph = new JTabbedPane(JTabbedPane.TOP); + + private JButton displayButton = + new JButton(JMeterUtils.getResString("aggregate_graph_display")); //$NON-NLS-1$ + + private JButton saveGraph = + new JButton(JMeterUtils.getResString("aggregate_graph_save")); //$NON-NLS-1$ + + private JButton saveTable = + new JButton(JMeterUtils.getResString("aggregate_graph_save_table")); //$NON-NLS-1$ + + private JButton chooseForeColor = + new JButton(JMeterUtils.getResString("aggregate_graph_choose_foreground_color")); //$NON-NLS-1$ + + private JButton syncWithName = + new JButton(JMeterUtils.getResString("aggregate_graph_sync_with_name")); //$NON-NLS-1$ + + private JCheckBox saveHeaders = // should header be saved with the data? + new JCheckBox(JMeterUtils.getResString("aggregate_graph_save_table_header")); //$NON-NLS-1$ + + private JLabeledTextField graphTitle = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_user_title")); //$NON-NLS-1$ + + private JLabeledTextField maxLengthXAxisLabel = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_max_length_xaxis_label"));//$NON-NLS-1$ + + private JLabeledTextField maxValueYAxisLabel = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_yaxis_max_value"));//$NON-NLS-1$ + + /** + * checkbox for use dynamic graph size + */ + private JCheckBox dynamicGraphSize = new JCheckBox(JMeterUtils.getResString("aggregate_graph_dynamic_size")); // $NON-NLS-1$ + + private JLabeledTextField graphWidth = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_width")); //$NON-NLS-1$ + private JLabeledTextField graphHeight = + new JLabeledTextField(JMeterUtils.getResString("aggregate_graph_height")); //$NON-NLS-1$ + + private String yAxisLabel = JMeterUtils.getResString("aggregate_graph_response_time");//$NON-NLS-1$ + + private String yAxisTitle = JMeterUtils.getResString("aggregate_graph_ms"); //$NON-NLS-1$ + + private boolean saveGraphToFile = false; + + private int defaultWidth = 400; + + private int defaultHeight = 300; + + private JComboBox columnsList = new JComboBox(GRAPH_COLUMNS); + + private List eltList = new ArrayList(); + + private JCheckBox columnSelection = new JCheckBox(JMeterUtils.getResString("aggregate_graph_column_selection"), false); //$NON-NLS-1$ + + private JTextField columnMatchLabel = new JTextField(); + + private JButton reloadButton = new JButton(JMeterUtils.getResString("aggregate_graph_reload_data")); // $NON-NLS-1$ + + private JCheckBox caseChkBox = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_case"), false); // $NON-NLS-1$ + + private JCheckBox regexpChkBox = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_regexp"), true); // $NON-NLS-1$ + + private JComboBox titleFontNameList = new JComboBox(StatGraphProperties.getFontNameMap().keySet().toArray()); + + private JComboBox titleFontSizeList = new JComboBox(StatGraphProperties.fontSize); + + private JComboBox titleFontStyleList = new JComboBox(StatGraphProperties.getFontStyleMap().keySet().toArray()); + + private JComboBox valueFontNameList = new JComboBox(StatGraphProperties.getFontNameMap().keySet().toArray()); + + private JComboBox valueFontSizeList = new JComboBox(StatGraphProperties.fontSize); + + private JComboBox valueFontStyleList = new JComboBox(StatGraphProperties.getFontStyleMap().keySet().toArray()); + + private JComboBox fontNameList = new JComboBox(StatGraphProperties.getFontNameMap().keySet().toArray()); + + private JComboBox fontSizeList = new JComboBox(StatGraphProperties.fontSize); + + private JComboBox fontStyleList = new JComboBox(StatGraphProperties.getFontStyleMap().keySet().toArray()); + + private JComboBox legendPlacementList = new JComboBox(StatGraphProperties.getPlacementNameMap().keySet().toArray()); + + private JCheckBox drawOutlinesBar = new JCheckBox(JMeterUtils.getResString("aggregate_graph_draw_outlines"), true); // Default checked // $NON-NLS-1$ + + private JCheckBox numberShowGrouping = new JCheckBox(JMeterUtils.getResString("aggregate_graph_number_grouping"), true); // Default checked // $NON-NLS-1$ + + private JCheckBox valueLabelsVertical = new JCheckBox(JMeterUtils.getResString("aggregate_graph_value_labels_vertical"), true); // Default checked // $NON-NLS-1$ + + private Color colorBarGraph = Color.YELLOW; + + private Color colorForeGraph = Color.BLACK; + + private int nbColToGraph = 1; + + public StatGraphVisualizer() { + super(); + model = new ObjectTableModel(COLUMNS, + SamplingStatCalculator.class, + new Functor[] { + new Functor("getLabel"), //$NON-NLS-1$ + new Functor("getCount"), //$NON-NLS-1$ + new Functor("getMeanAsNumber"), //$NON-NLS-1$ + new Functor("getMedian"), //$NON-NLS-1$ + new Functor("getPercentPoint", //$NON-NLS-1$ + new Object[] { new Float(.900) }), + new Functor("getMin"), //$NON-NLS-1$ + new Functor("getMax"), //$NON-NLS-1$ + new Functor("getErrorPercentage"), //$NON-NLS-1$ + new Functor("getRate"), //$NON-NLS-1$ + new Functor("getKBPerSecond") }, //$NON-NLS-1$ + new Functor[] { null, null, null, null, null, null, null, null, null, null }, + new Class[] { String.class, Long.class, Long.class, Long.class, Long.class, Long.class, + Long.class, String.class, String.class, String.class }); + eltList.add(new BarGraph("average", true, new Color(202, 0, 0))); + eltList.add(new BarGraph("aggregate_report_median", false, new Color(49, 49, 181))); + eltList.add(new BarGraph("aggregate_report_90%_line", false, new Color(42, 121, 42))); + eltList.add(new BarGraph("aggregate_report_min", false, Color.LIGHT_GRAY)); + eltList.add(new BarGraph("aggregate_report_max", false, Color.DARK_GRAY)); + clearData(); + init(); + } + + // Column renderers + private static final TableCellRenderer[] RENDERERS = + new TableCellRenderer[]{ + null, // Label + null, // count + null, // Mean + null, // median + null, // 90% + null, // Min + null, // Max + new NumberRenderer("#0.00%"), // Error %age + new RateRenderer("#.0"), // Throughpur + new NumberRenderer("#.0"), // pageSize + }; + + public static boolean testFunctors(){ + StatGraphVisualizer instance = new StatGraphVisualizer(); + return instance.model.checkFunctors(null,instance.getClass()); + } + + public String getLabelResource() { + return "aggregate_graph_title"; //$NON-NLS-1$ + } + + public void add(final SampleResult res) { + final String sampleLabel = res.getSampleLabel(); + Matcher matcher = null; + if (columnSelection.isSelected() && columnMatchLabel.getText() != null && columnMatchLabel.getText().length() > 0) { + Pattern pattern = createPattern(columnMatchLabel.getText()); + matcher = pattern.matcher(sampleLabel); + } + if ((matcher == null) || (matcher.find())) { + JMeterUtils.runSafe(new Runnable() { + public void run() { + SamplingStatCalculator row = null; + synchronized (lock) { + row = tableRows.get(sampleLabel); + if (row == null) { + row = new SamplingStatCalculator(sampleLabel); + tableRows.put(row.getLabel(), row); + model.insertRow(row, model.getRowCount() - 1); + } + } + row.addSample(res); + tableRows.get(TOTAL_ROW_LABEL).addSample(res); + model.fireTableDataChanged(); + } + }); + } + } + + /** + * Clears this visualizer and its model, and forces a repaint of the table. + */ + public void clearData() { + synchronized (lock) { + model.clearData(); + tableRows.clear(); + tableRows.put(TOTAL_ROW_LABEL, new SamplingStatCalculator(TOTAL_ROW_LABEL)); + model.addRow(tableRows.get(TOTAL_ROW_LABEL)); + } + } + + /** + * Main visualizer setup. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + Border margin = new EmptyBorder(10, 10, 5, 10); + Border margin2 = new EmptyBorder(10, 10, 5, 10); + + mainPanel.setBorder(margin); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + mainPanel.add(makeTitlePanel()); + + myJTable = new JTable(model); + myJTable.setPreferredScrollableViewportSize(new Dimension(500, 80)); + RendererUtils.applyRenderers(myJTable, RENDERERS); + myScrollPane = new JScrollPane(myJTable); + + settingsPane = new VerticalPanel(); + settingsPane.setBorder(margin2); + + graphPanel = new AxisGraph(); + graphPanel.setPreferredSize(new Dimension(defaultWidth, defaultHeight)); + + settingsPane.add(createGraphActionsPane()); + settingsPane.add(createGraphColumnPane()); + settingsPane.add(createGraphTitlePane()); + settingsPane.add(createGraphDimensionPane()); + JPanel axisPane = new JPanel(new BorderLayout()); + axisPane.add(createGraphXAxisPane(), BorderLayout.WEST); + axisPane.add(createGraphYAxisPane(), BorderLayout.CENTER); + settingsPane.add(axisPane); + settingsPane.add(createLegendPane()); + + tabbedGraph.addTab(JMeterUtils.getResString("aggregate_graph_tab_settings"), settingsPane); //$NON-NLS-1$ + tabbedGraph.addTab(JMeterUtils.getResString("aggregate_graph_tab_graph"), graphPanel); //$NON-NLS-1$ + + spane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + spane.setLeftComponent(myScrollPane); + spane.setRightComponent(tabbedGraph); + spane.setResizeWeight(.2); + spane.setBorder(null); // see bug jdk 4131528 + spane.setContinuousLayout(true); + + this.add(mainPanel, BorderLayout.NORTH); + this.add(spane, BorderLayout.CENTER); + } + + public void makeGraph() { + nbColToGraph = getNbColumns(); + Dimension size = graphPanel.getSize(); + String lstr = maxLengthXAxisLabel.getText(); + // canvas size + int width = (int) size.getWidth(); + int height = (int) size.getHeight(); + if (!dynamicGraphSize.isSelected()) { + String wstr = graphWidth.getText(); + String hstr = graphHeight.getText(); + if (wstr.length() != 0) { + width = Integer.parseInt(wstr); + } + if (hstr.length() != 0) { + height = Integer.parseInt(hstr); + } + } + + if (lstr.length() == 0) { + lstr = "20";//$NON-NLS-1$ + } + int maxLength = Integer.parseInt(lstr); + String yAxisStr = maxValueYAxisLabel.getText(); + int maxYAxisScale = yAxisStr.length() == 0 ? 0 : Integer.parseInt(yAxisStr); + + graphPanel.setData(this.getData()); + graphPanel.setTitle(graphTitle.getText()); + graphPanel.setMaxLength(maxLength); + graphPanel.setMaxYAxisScale(maxYAxisScale); + graphPanel.setXAxisLabels(getAxisLabels()); + graphPanel.setXAxisTitle((String) columnsList.getSelectedItem()); + graphPanel.setYAxisLabels(this.yAxisLabel); + graphPanel.setYAxisTitle(this.yAxisTitle); + graphPanel.setLegendLabels(getLegendLabels()); + graphPanel.setColor(getBackColors()); + graphPanel.setForeColor(colorForeGraph); + graphPanel.setOutlinesBarFlag(drawOutlinesBar.isSelected()); + graphPanel.setShowGrouping(numberShowGrouping.isSelected()); + graphPanel.setValueOrientation(valueLabelsVertical.isSelected()); + graphPanel.setLegendPlacement(StatGraphProperties.getPlacementNameMap() + .get(legendPlacementList.getSelectedItem()).intValue()); + + graphPanel.setTitleFont(new Font(StatGraphProperties.getFontNameMap().get(titleFontNameList.getSelectedItem()), + StatGraphProperties.getFontStyleMap().get(titleFontStyleList.getSelectedItem()).intValue(), + Integer.parseInt((String) titleFontSizeList.getSelectedItem()))); + graphPanel.setLegendFont(new Font(StatGraphProperties.getFontNameMap().get(fontNameList.getSelectedItem()), + StatGraphProperties.getFontStyleMap().get(fontStyleList.getSelectedItem()).intValue(), + Integer.parseInt((String) fontSizeList.getSelectedItem()))); + graphPanel.setValueFont(new Font(StatGraphProperties.getFontNameMap().get(valueFontNameList.getSelectedItem()), + StatGraphProperties.getFontStyleMap().get(valueFontStyleList.getSelectedItem()).intValue(), + Integer.parseInt((String) valueFontSizeList.getSelectedItem()))); + + graphPanel.setHeight(height); + graphPanel.setWidth(width); + spane.repaint(); + } + + public double[][] getData() { + if (model.getRowCount() > 1) { + int count = model.getRowCount() -1; + + int size = nbColToGraph; + double[][] data = new double[size][count]; + int s = 0; + int cpt = 0; + for (BarGraph bar : eltList) { + if (bar.getChkBox().isSelected()) { + int col = model.findColumn((String) columnsList.getItemAt(cpt)); + for (int idx=0; idx < count; idx++) { + data[s][idx] = ((Number)model.getValueAt(idx,col)).doubleValue(); + } + s++; + } + cpt++; + } + return data; + } + return null; + } + + public String[] getAxisLabels() { + if (model.getRowCount() > 1) { + int count = model.getRowCount() -1; + String[] labels = new String[count]; + for (int idx=0; idx < count; idx++) { + labels[idx] = (String)model.getValueAt(idx,0); + } + return labels; + } + return null; + } + + private String[] getLegendLabels() { + String[] legends = new String[nbColToGraph]; + int i = 0; + for (BarGraph bar : eltList) { + if (bar.getChkBox().isSelected()) { + legends[i] = bar.getLabel(); + i++; + } + } + return legends; + } + + private Color[] getBackColors() { + Color[] backColors = new Color[nbColToGraph]; + int i = 0; + for (BarGraph bar : eltList) { + if (bar.getChkBox().isSelected()) { + backColors[i] = bar.getBackColor(); + i++; + } + } + return backColors; + } + + private int getNbColumns() { + int i = 0; + for (BarGraph bar : eltList) { + if (bar.getChkBox().isSelected()) { + i++; + } + } + return i; + } + + /** + * We use this method to get the data, since we are using + * ObjectTableModel, so the calling getDataVector doesn't + * work as expected. + * @return the data from the model + */ + public List> getAllTableData() { + List> data = new ArrayList>(); + if (model.getRowCount() > 0) { + for (int rw=0; rw < model.getRowCount(); rw++) { + int cols = model.getColumnCount(); + List column = new ArrayList(); + data.add(column); + for (int idx=0; idx < cols; idx++) { + Object val = model.getValueAt(rw,idx); + column.add(val); + } + } + } + return data; + } + + public void actionPerformed(ActionEvent event) { + final Object eventSource = event.getSource(); + if (eventSource == displayButton) { + if (model.getRowCount() > 1) { + makeGraph(); + tabbedGraph.setSelectedIndex(1); + } else { + JOptionPane.showMessageDialog(null, JMeterUtils + .getResString("aggregate_graph_no_values_to_graph"), // $NON-NLS-1$ + JMeterUtils.getResString("aggregate_graph_no_values_to_graph"), // $NON-NLS-1$ + JOptionPane.WARNING_MESSAGE); + } + } else if (eventSource == saveGraph) { + saveGraphToFile = true; + try { + ActionRouter.getInstance().getAction( + ActionNames.SAVE_GRAPHICS,SaveGraphics.class.getName()).doAction( + new ActionEvent(this,1,ActionNames.SAVE_GRAPHICS)); + } catch (Exception e) { + log.error(e.getMessage()); + } + } else if (eventSource == saveTable) { + JFileChooser chooser = FileDialoger.promptToSaveFile("statistics.csv"); //$NON-NLS-1$ + if (chooser == null) { + return; + } + FileWriter writer = null; + try { + writer = new FileWriter(chooser.getSelectedFile()); // TODO Charset ? + CSVSaveService.saveCSVStats(getAllTableData(),writer,saveHeaders.isSelected() ? COLUMNS : null); + } catch (FileNotFoundException e) { + log.warn(e.getMessage()); + } catch (IOException e) { + log.warn(e.getMessage()); + } finally { + JOrphanUtils.closeQuietly(writer); + } + } else if (eventSource == chooseForeColor) { + Color color = JColorChooser.showDialog( + null, + JMeterUtils.getResString("aggregate_graph_choose_color"), //$NON-NLS-1$ + colorBarGraph); + if (color != null) { + colorForeGraph = color; + } + } else if (eventSource == syncWithName) { + graphTitle.setText(namePanel.getName()); + } else if (eventSource == dynamicGraphSize) { + // if use dynamic graph size is checked, we disable the dimension fields + if (dynamicGraphSize.isSelected()) { + graphWidth.setEnabled(false); + graphHeight.setEnabled(false); + } else { + graphWidth.setEnabled(true); + graphHeight.setEnabled(true); + } + } else if (eventSource == columnSelection) { + if (columnSelection.isSelected()) { + columnMatchLabel.setEnabled(true); + reloadButton.setEnabled(true); + caseChkBox.setEnabled(true); + regexpChkBox.setEnabled(true); + } else { + columnMatchLabel.setEnabled(false); + reloadButton.setEnabled(false); + caseChkBox.setEnabled(false); + regexpChkBox.setEnabled(false); + } + } else if (eventSource == reloadButton) { + if (getFile() != null && getFile().length() > 0) { + clearData(); + FilePanel filePanel = (FilePanel) getFilePanel(); + filePanel.actionPerformed(event); + } + } else if (eventSource instanceof JButton) { + // Changing color for column + JButton btn = ((JButton) eventSource); + if (btn.getName() != null) { + try { + BarGraph bar = eltList.get(Integer.parseInt(btn.getName())); + Color color = JColorChooser.showDialog(null, bar.getLabel(), bar.getBackColor()); + if (color != null) { + bar.setBackColor(color); + btn.setBackground(bar.getBackColor()); + } + } catch (NumberFormatException nfe) { } // nothing to do + } + } + } + + @Override + public JComponent getPrintableComponent() { + if (saveGraphToFile == true) { + saveGraphToFile = false; + graphPanel.setBounds(graphPanel.getLocation().x,graphPanel.getLocation().y, + graphPanel.width,graphPanel.height); + return graphPanel; + } + return this; + } + + private JPanel createGraphActionsPane() { + JPanel buttonPanel = new JPanel(new BorderLayout()); + JPanel displayPane = new JPanel(); + displayPane.add(displayButton); + displayButton.addActionListener(this); + buttonPanel.add(displayPane, BorderLayout.WEST); + + JPanel savePane = new JPanel(); + savePane.add(saveGraph); + savePane.add(saveTable); + savePane.add(saveHeaders); + saveGraph.addActionListener(this); + saveTable.addActionListener(this); + syncWithName.addActionListener(this); + buttonPanel.add(savePane, BorderLayout.EAST); + + return buttonPanel; + } + + private JPanel createGraphColumnPane() { + JPanel colPanel = new JPanel(); + colPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0)); + + JLabel label = new JLabel(JMeterUtils.getResString("aggregate_graph_columns_to_display")); //$NON-NLS-1$ + colPanel.add(label); + for (BarGraph bar : eltList) { + colPanel.add(bar.getChkBox()); + colPanel.add(createColorBarButton(bar, eltList.indexOf(bar))); + } + colPanel.add(Box.createRigidArea(new Dimension(5,0))); + chooseForeColor.setFont(FONT_SMALL); + colPanel.add(chooseForeColor); + chooseForeColor.addActionListener(this); + + JPanel optionsPanel = new JPanel(); + optionsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + optionsPanel.add(createGraphFontValuePane()); + optionsPanel.add(drawOutlinesBar); + optionsPanel.add(numberShowGrouping); + optionsPanel.add(valueLabelsVertical); + + JPanel barPane = new JPanel(new BorderLayout()); + barPane.add(colPanel, BorderLayout.NORTH); + barPane.add(Box.createRigidArea(new Dimension(0,3)), BorderLayout.CENTER); + barPane.add(optionsPanel, BorderLayout.SOUTH); + + JPanel columnPane = new JPanel(new BorderLayout()); + columnPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_column_settings"))); // $NON-NLS-1$ + columnPane.add(barPane, BorderLayout.NORTH); + columnPane.add(Box.createRigidArea(new Dimension(0,3)), BorderLayout.CENTER); + columnPane.add(createGraphSelectionSubPane(), BorderLayout.SOUTH); + + return columnPane; + } + + private JButton createColorBarButton(BarGraph barGraph, int index) { + // Button + JButton colorBtn = new JButton(); + colorBtn.setName(String.valueOf(index)); + colorBtn.setFont(FONT_SMALL); + colorBtn.addActionListener(this); + colorBtn.setBackground(barGraph.getBackColor()); + return colorBtn; + } + + private JPanel createGraphSelectionSubPane() { + Font font = new Font("SansSerif", Font.PLAIN, 10); + // Search field + JPanel searchPanel = new JPanel(); + searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS)); + searchPanel.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); + + searchPanel.add(columnSelection); + columnMatchLabel.setEnabled(false); + reloadButton.setEnabled(false); + caseChkBox.setEnabled(false); + regexpChkBox.setEnabled(false); + columnSelection.addActionListener(this); + + searchPanel.add(columnMatchLabel); + searchPanel.add(Box.createRigidArea(new Dimension(5,0))); + + // Button + reloadButton.setFont(font); + reloadButton.addActionListener(this); + searchPanel.add(reloadButton); + + // checkboxes + caseChkBox.setFont(font); + searchPanel.add(caseChkBox); + regexpChkBox.setFont(font); + searchPanel.add(regexpChkBox); + + return searchPanel; + } + + private JPanel createGraphTitlePane() { + JPanel titleNamePane = new JPanel(new BorderLayout()); + syncWithName.setFont(new Font("SansSerif", Font.PLAIN, 10)); + titleNamePane.add(graphTitle, BorderLayout.CENTER); + titleNamePane.add(syncWithName, BorderLayout.EAST); + + JPanel titleStylePane = new JPanel(); + titleStylePane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 5)); + titleStylePane.add(createLabelCombo(JMeterUtils.getResString("aggregate_graph_font"), //$NON-NLS-1$ + titleFontNameList)); + titleFontNameList.setSelectedIndex(0); // default: sans serif + titleStylePane.add(createLabelCombo(JMeterUtils.getResString("aggregate_graph_size"), //$NON-NLS-1$ + titleFontSizeList)); + titleFontSizeList.setSelectedItem(StatGraphProperties.fontSize[6]); // default: 16 + titleStylePane.add(createLabelCombo(JMeterUtils.getResString("aggregate_graph_style"), //$NON-NLS-1$ + titleFontStyleList)); + titleFontStyleList.setSelectedItem(JMeterUtils.getResString("fontstyle.bold")); // default: bold + + JPanel titlePane = new JPanel(new BorderLayout()); + titlePane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_title_group"))); // $NON-NLS-1$ + titlePane.add(titleNamePane, BorderLayout.NORTH); + titlePane.add(titleStylePane, BorderLayout.SOUTH); + return titlePane; + } + + private JPanel createGraphFontValuePane() { + JPanel fontValueStylePane = new JPanel(); + fontValueStylePane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + fontValueStylePane.add(createLabelCombo(JMeterUtils.getResString("aggregate_graph_value_font"), //$NON-NLS-1$ + valueFontNameList)); + valueFontNameList.setSelectedIndex(0); // default: sans serif + fontValueStylePane.add(createLabelCombo(JMeterUtils.getResString("aggregate_graph_size"), //$NON-NLS-1$ + valueFontSizeList)); + valueFontSizeList.setSelectedItem(StatGraphProperties.fontSize[2]); // default: 10 + fontValueStylePane.add(createLabelCombo(JMeterUtils.getResString("aggregate_graph_style"), //$NON-NLS-1$ + valueFontStyleList)); + valueFontStyleList.setSelectedItem(JMeterUtils.getResString("fontstyle.normal")); // default: normal //$NON-NLS-1$ + + return fontValueStylePane; + } + + private JPanel createGraphDimensionPane() { + JPanel dimensionPane = new JPanel(); + dimensionPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + dimensionPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_dimension"))); // $NON-NLS-1$ + + dimensionPane.add(dynamicGraphSize); + dynamicGraphSize.setSelected(true); // default option + graphWidth.setEnabled(false); + graphHeight.setEnabled(false); + dynamicGraphSize.addActionListener(this); + dimensionPane.add(Box.createRigidArea(new Dimension(10,0))); + dimensionPane.add(graphWidth); + dimensionPane.add(Box.createRigidArea(new Dimension(5,0))); + dimensionPane.add(graphHeight); + return dimensionPane; + } + + /** + * Create pane for X Axis options + * @return X Axis pane + */ + private JPanel createGraphXAxisPane() { + JPanel xAxisPane = new JPanel(); + xAxisPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + xAxisPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_xaxis_group"))); // $NON-NLS-1$ + xAxisPane.add(maxLengthXAxisLabel); + return xAxisPane; + } + + /** + * Create pane for Y Axis options + * @return Y Axis pane + */ + private JPanel createGraphYAxisPane() { + JPanel yAxisPane = new JPanel(); + yAxisPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + yAxisPane.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_yaxis_group"))); // $NON-NLS-1$ + yAxisPane.add(maxValueYAxisLabel); + return yAxisPane; + } + + /** + * Create pane for legend settings + * @return Legend pane + */ + private JPanel createLegendPane() { + JPanel legendPanel = new JPanel(); + legendPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + legendPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), + JMeterUtils.getResString("aggregate_graph_legend"))); // $NON-NLS-1$ + + legendPanel.add(createLabelCombo(JMeterUtils.getResString("aggregate_graph_legend_placement"), //$NON-NLS-1$ + legendPlacementList)); + legendPlacementList.setSelectedItem(JMeterUtils.getResString("aggregate_graph_legend.placement.bottom")); // default: bottom + legendPanel.add(createLabelCombo(JMeterUtils.getResString("aggregate_graph_font"), //$NON-NLS-1$ + fontNameList)); + fontNameList.setSelectedIndex(0); // default: sans serif + legendPanel.add(createLabelCombo(JMeterUtils.getResString("aggregate_graph_size"), //$NON-NLS-1$ + fontSizeList)); + fontSizeList.setSelectedItem(StatGraphProperties.fontSize[2]); // default: 10 + legendPanel.add(createLabelCombo(JMeterUtils.getResString("aggregate_graph_style"), //$NON-NLS-1$ + fontStyleList)); + fontStyleList.setSelectedItem(JMeterUtils.getResString("fontstyle.normal")); // default: normal + + return legendPanel; + } + + private JComponent createLabelCombo(String label, JComboBox comboBox) { + JPanel labelCombo = new JPanel(); + labelCombo.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + JLabel caption = new JLabel(label);//$NON-NLS-1$ + caption.setBorder(MARGIN); + labelCombo.add(caption); + labelCombo.add(comboBox); + return labelCombo; + } + + /** + * @param textToFind + * @return pattern ready to search + */ + private Pattern createPattern(String textToFind) { + String textToFindQ = Pattern.quote(textToFind); + if (regexpChkBox.isSelected()) { + textToFindQ = textToFind; + } + Pattern pattern = null; + try { + if (caseChkBox.isSelected()) { + pattern = Pattern.compile(textToFindQ); + } else { + pattern = Pattern.compile(textToFindQ, Pattern.CASE_INSENSITIVE); + } + } catch (PatternSyntaxException pse) { + return null; + } + return pattern; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/StatVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/StatVisualizer.java new file mode 100644 index 0000000..0260c73 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/StatVisualizer.java @@ -0,0 +1,362 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.table.TableCellRenderer; +//import javax.swing.table.AbstractTableModel; +//import javax.swing.table.TableModel; + +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.gui.NumberRenderer; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RateRenderer; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Aggregrate Table-Based Reporting Visualizer for JMeter. Props to the people + * who've done the other visualizers ahead of me (Stefano Mazzocchi), who I + * borrowed code from to start me off (and much code may still exist). Thank + * you! + * + */ +public class StatVisualizer extends AbstractVisualizer implements Clearable, ActionListener { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String USE_GROUP_NAME = "useGroupName"; //$NON-NLS-1$ + + private static final String SAVE_HEADERS = "saveHeaders"; //$NON-NLS-1$ + + private static final String[] COLUMNS = { + "sampler_label", //$NON-NLS-1$ + "aggregate_report_count", //$NON-NLS-1$ + "average", //$NON-NLS-1$ + "aggregate_report_median", //$NON-NLS-1$ + "aggregate_report_90%_line", //$NON-NLS-1$ + "aggregate_report_min", //$NON-NLS-1$ + "aggregate_report_max", //$NON-NLS-1$ + "aggregate_report_error%", //$NON-NLS-1$ + "aggregate_report_rate", //$NON-NLS-1$ + "aggregate_report_bandwidth" }; //$NON-NLS-1$ + + private final String TOTAL_ROW_LABEL + = JMeterUtils.getResString("aggregate_report_total_label"); //$NON-NLS-1$ + + private JTable myJTable; + + private JScrollPane myScrollPane; + + private final JButton saveTable = + new JButton(JMeterUtils.getResString("aggregate_graph_save_table")); //$NON-NLS-1$ + + private final JCheckBox saveHeaders = // should header be saved with the data? + new JCheckBox(JMeterUtils.getResString("aggregate_graph_save_table_header"),true); //$NON-NLS-1$ + + private final JCheckBox useGroupName = + new JCheckBox(JMeterUtils.getResString("aggregate_graph_use_group_name")); //$NON-NLS-1$ + + private transient ObjectTableModel model; + + /** + * Lock used to protect tableRows update + model update + */ + private final transient Object lock = new Object(); + + private final Map tableRows = + new ConcurrentHashMap(); + + public StatVisualizer() { + super(); + model = new ObjectTableModel(COLUMNS, + SamplingStatCalculator.class, + new Functor[] { + new Functor("getLabel"), //$NON-NLS-1$ + new Functor("getCount"), //$NON-NLS-1$ + new Functor("getMeanAsNumber"), //$NON-NLS-1$ + new Functor("getMedian"), //$NON-NLS-1$ + new Functor("getPercentPoint", //$NON-NLS-1$ + new Object[] { new Float(.900) }), + new Functor("getMin"), //$NON-NLS-1$ + new Functor("getMax"), //$NON-NLS-1$ + new Functor("getErrorPercentage"), //$NON-NLS-1$ + new Functor("getRate"), //$NON-NLS-1$ + new Functor("getKBPerSecond") //$NON-NLS-1$ + }, + new Functor[] { null, null, null, null, null, null, null, null, null, null }, + new Class[] { String.class, Long.class, Long.class, Long.class, Long.class, + Long.class, Long.class, String.class, String.class, String.class }); + clearData(); + init(); + } + + // Column renderers + private static final TableCellRenderer[] RENDERERS = + new TableCellRenderer[]{ + null, // Label + null, // count + null, // Mean + null, // median + null, // 90% + null, // Min + null, // Max + new NumberRenderer("#0.00%"), // Error %age //$NON-NLS-1$ + new RateRenderer("#.0"), // Throughput //$NON-NLS-1$ + new NumberRenderer("#.0"), // pageSize //$NON-NLS-1$ + }; + + /** @deprecated - only for use in testing */ + @Deprecated + public static boolean testFunctors(){ + StatVisualizer instance = new StatVisualizer(); + return instance.model.checkFunctors(null,instance.getClass()); + } + + public String getLabelResource() { + return "aggregate_report"; //$NON-NLS-1$ + } + + public void add(final SampleResult res) { + JMeterUtils.runSafe(new Runnable() { + public void run() { + SamplingStatCalculator row = null; + final String sampleLabel = res.getSampleLabel(useGroupName.isSelected()); + synchronized (lock) { + row = tableRows.get(sampleLabel); + if (row == null) { + row = new SamplingStatCalculator(sampleLabel); + tableRows.put(row.getLabel(), row); + model.insertRow(row, model.getRowCount() - 1); + } + } + /* + * Synch is needed because multiple threads can update the counts. + */ + synchronized(row) { + row.addSample(res); + } + SamplingStatCalculator tot = tableRows.get(TOTAL_ROW_LABEL); + synchronized(tot) { + tot.addSample(res); + } + model.fireTableDataChanged(); + } + }); + } + + /** + * Clears this visualizer and its model, and forces a repaint of the table. + */ + public void clearData() { + synchronized (lock) { + model.clearData(); + tableRows.clear(); + tableRows.put(TOTAL_ROW_LABEL, new SamplingStatCalculator(TOTAL_ROW_LABEL)); + model.addRow(tableRows.get(TOTAL_ROW_LABEL)); + } + } + + /** + * Main visualizer setup. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + Border margin = new EmptyBorder(10, 10, 5, 10); + + mainPanel.setBorder(margin); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + + mainPanel.add(makeTitlePanel()); + + // SortFilterModel mySortedModel = + // new SortFilterModel(myStatTableModel); + myJTable = new JTable(model); + myJTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + myJTable.setPreferredScrollableViewportSize(new Dimension(500, 70)); + RendererUtils.applyRenderers(myJTable, RENDERERS); + myScrollPane = new JScrollPane(myJTable); + this.add(mainPanel, BorderLayout.NORTH); + this.add(myScrollPane, BorderLayout.CENTER); + saveTable.addActionListener(this); + JPanel opts = new JPanel(); + opts.add(useGroupName, BorderLayout.WEST); + opts.add(saveTable, BorderLayout.CENTER); + opts.add(saveHeaders, BorderLayout.EAST); + this.add(opts,BorderLayout.SOUTH); + } + + @Override + public void modifyTestElement(TestElement c) { + super.modifyTestElement(c); + c.setProperty(USE_GROUP_NAME, useGroupName.isSelected(), false); + c.setProperty(SAVE_HEADERS, saveHeaders.isSelected(), true); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + useGroupName.setSelected(el.getPropertyAsBoolean(USE_GROUP_NAME, false)); + saveHeaders.setSelected(el.getPropertyAsBoolean(SAVE_HEADERS, true)); + } + + public void actionPerformed(ActionEvent ev) { + if (ev.getSource() == saveTable) { + JFileChooser chooser = FileDialoger.promptToSaveFile("aggregate.csv");//$NON-NLS-1$ + if (chooser == null) { + return; + } + FileWriter writer = null; + try { + writer = new FileWriter(chooser.getSelectedFile()); // TODO Charset ? + CSVSaveService.saveCSVStats(model,writer, saveHeaders.isSelected()); + } catch (FileNotFoundException e) { + log.warn(e.getMessage()); + } catch (IOException e) { + log.warn(e.getMessage()); + } finally { + JOrphanUtils.closeQuietly(writer); + } + } + } +} + +/** + * Pulled this mainly out of a Core Java book to implement a sorted table - + * haven't implemented this yet, it needs some non-trivial work done to it to + * support our dynamically-sizing TableModel for this visualizer. + * + */ + +//class SortFilterModel extends AbstractTableModel { +// private TableModel model; +// +// private int sortColumn; +// +// private Row[] rows; +// +// public SortFilterModel(TableModel m) { +// model = m; +// rows = new Row[model.getRowCount()]; +// for (int i = 0; i < rows.length; i++) { +// rows[i] = new Row(); +// rows[i].index = i; +// } +// } +// +// public SortFilterModel() { +// } +// +// public void setValueAt(Object aValue, int r, int c) { +// model.setValueAt(aValue, rows[r].index, c); +// } +// +// public Object getValueAt(int r, int c) { +// return model.getValueAt(rows[r].index, c); +// } +// +// public boolean isCellEditable(int r, int c) { +// return model.isCellEditable(rows[r].index, c); +// } +// +// public int getRowCount() { +// return model.getRowCount(); +// } +// +// public int getColumnCount() { +// return model.getColumnCount(); +// } +// +// public String getColumnName(int c) { +// return model.getColumnName(c); +// } +// +// public Class getColumnClass(int c) { +// return model.getColumnClass(c); +// } +// +// public void sort(int c) { +// sortColumn = c; +// Arrays.sort(rows); +// fireTableDataChanged(); +// } +// +// public void addMouseListener(final JTable table) { +// table.getTableHeader().addMouseListener(new MouseAdapter() { +// public void mouseClicked(MouseEvent event) { +// if (event.getClickCount() < 2) { +// return; +// } +// int tableColumn = table.columnAtPoint(event.getPoint()); +// int modelColumn = table.convertColumnIndexToModel(tableColumn); +// +// sort(modelColumn); +// } +// }); +// } +// +// private class Row implements Comparable { +// public int index; +// +// public int compareTo(Object other) { +// Row otherRow = (Row) other; +// Object a = model.getValueAt(index, sortColumn); +// Object b = model.getValueAt(otherRow.index, sortColumn); +// +// if (a instanceof Comparable) { +// return ((Comparable) a).compareTo(b); +// } else { +// return index - otherRow.index; +// } +// } +// } +//} // class SortFilterModel diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/SummaryReport.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/SummaryReport.java new file mode 100644 index 0000000..34ce68f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/SummaryReport.java @@ -0,0 +1,266 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.table.TableCellRenderer; + +import org.apache.jmeter.gui.util.FileDialoger; +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.Calculator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.gui.NumberRenderer; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RateRenderer; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Simpler (lower memory) version of Aggregate Report (StatVisualizer). + * Excludes the Median and 90% columns, which are expensive in memory terms + */ +public class SummaryReport extends AbstractVisualizer implements Clearable, ActionListener { + + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String USE_GROUP_NAME = "useGroupName"; //$NON-NLS-1$ + + private static final String SAVE_HEADERS = "saveHeaders"; //$NON-NLS-1$ + + private static final String[] COLUMNS = { + "sampler_label", //$NON-NLS-1$ + "aggregate_report_count", //$NON-NLS-1$ + "average", //$NON-NLS-1$ + "aggregate_report_min", //$NON-NLS-1$ + "aggregate_report_max", //$NON-NLS-1$ + "aggregate_report_stddev", //$NON-NLS-1$ + "aggregate_report_error%", //$NON-NLS-1$ + "aggregate_report_rate", //$NON-NLS-1$ + "aggregate_report_bandwidth", //$NON-NLS-1$ + "average_bytes", //$NON-NLS-1$ + }; + + private final String TOTAL_ROW_LABEL + = JMeterUtils.getResString("aggregate_report_total_label"); //$NON-NLS-1$ + + private JTable myJTable; + + private JScrollPane myScrollPane; + + private final JButton saveTable = + new JButton(JMeterUtils.getResString("aggregate_graph_save_table")); //$NON-NLS-1$ + + private final JCheckBox saveHeaders = // should header be saved with the data? + new JCheckBox(JMeterUtils.getResString("aggregate_graph_save_table_header"),true); //$NON-NLS-1$ + + private final JCheckBox useGroupName = + new JCheckBox(JMeterUtils.getResString("aggregate_graph_use_group_name")); //$NON-NLS-1$ + + private transient ObjectTableModel model; + + /** + * Lock used to protect tableRows update + model update + */ + private final transient Object lock = new Object(); + + private final Map tableRows = + new ConcurrentHashMap(); + + // Column renderers + private static final TableCellRenderer[] RENDERERS = + new TableCellRenderer[]{ + null, // Label + null, // count + null, // Mean + null, // Min + null, // Max + new NumberRenderer("#0.00"), // Std Dev. + new NumberRenderer("#0.00%"), // Error %age + new RateRenderer("#.0"), // Throughpur + new NumberRenderer("#0.00"), // kB/sec + new NumberRenderer("#.0"), // avg. pageSize + }; + + public SummaryReport() { + super(); + model = new ObjectTableModel(COLUMNS, + Calculator.class,// All rows have this class + new Functor[] { + new Functor("getLabel"), //$NON-NLS-1$ + new Functor("getCount"), //$NON-NLS-1$ + new Functor("getMeanAsNumber"), //$NON-NLS-1$ + new Functor("getMin"), //$NON-NLS-1$ + new Functor("getMax"), //$NON-NLS-1$ + new Functor("getStandardDeviation"), //$NON-NLS-1$ + new Functor("getErrorPercentage"), //$NON-NLS-1$ + new Functor("getRate"), //$NON-NLS-1$ + new Functor("getKBPerSecond"), //$NON-NLS-1$ + new Functor("getAvgPageBytes"), //$NON-NLS-1$ + }, + new Functor[] { null, null, null, null, null, null, null, null , null, null }, + new Class[] { String.class, Long.class, Long.class, Long.class, Long.class, + String.class, String.class, String.class, String.class, String.class }); + clearData(); + init(); + } + + /** @deprecated - only for use in testing */ + @Deprecated + public static boolean testFunctors(){ + SummaryReport instance = new SummaryReport(); + return instance.model.checkFunctors(null,instance.getClass()); + } + + public String getLabelResource() { + return "summary_report"; //$NON-NLS-1$ + } + + public void add(final SampleResult res) { + final String sampleLabel = res.getSampleLabel(useGroupName.isSelected()); + JMeterUtils.runSafe(new Runnable() { + public void run() { + Calculator row = null; + synchronized (lock) { + row = tableRows.get(sampleLabel); + if (row == null) { + row = new Calculator(sampleLabel); + tableRows.put(row.getLabel(), row); + model.insertRow(row, model.getRowCount() - 1); + } + } + /* + * Synch is needed because multiple threads can update the counts. + */ + synchronized(row) { + row.addSample(res); + } + Calculator tot = tableRows.get(TOTAL_ROW_LABEL); + synchronized(tot) { + tot.addSample(res); + } + model.fireTableDataChanged(); + } + }); + } + + /** + * Clears this visualizer and its model, and forces a repaint of the table. + */ + public void clearData() { + //Synch is needed because a clear can occur while add occurs + synchronized (lock) { + model.clearData(); + tableRows.clear(); + tableRows.put(TOTAL_ROW_LABEL, new Calculator(TOTAL_ROW_LABEL)); + model.addRow(tableRows.get(TOTAL_ROW_LABEL)); + } + } + + /** + * Main visualizer setup. + */ + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + Border margin = new EmptyBorder(10, 10, 5, 10); + + mainPanel.setBorder(margin); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + + mainPanel.add(makeTitlePanel()); + + myJTable = new JTable(model); + myJTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + myJTable.setPreferredScrollableViewportSize(new Dimension(500, 70)); + RendererUtils.applyRenderers(myJTable, RENDERERS); + myScrollPane = new JScrollPane(myJTable); + this.add(mainPanel, BorderLayout.NORTH); + this.add(myScrollPane, BorderLayout.CENTER); + saveTable.addActionListener(this); + JPanel opts = new JPanel(); + opts.add(useGroupName, BorderLayout.WEST); + opts.add(saveTable, BorderLayout.CENTER); + opts.add(saveHeaders, BorderLayout.EAST); + this.add(opts,BorderLayout.SOUTH); + } + + @Override + public void modifyTestElement(TestElement c) { + super.modifyTestElement(c); + c.setProperty(USE_GROUP_NAME, useGroupName.isSelected(), false); + c.setProperty(SAVE_HEADERS, saveHeaders.isSelected(), true); + } + + @Override + public void configure(TestElement el) { + super.configure(el); + useGroupName.setSelected(el.getPropertyAsBoolean(USE_GROUP_NAME, false)); + saveHeaders.setSelected(el.getPropertyAsBoolean(SAVE_HEADERS, true)); + } + + public void actionPerformed(ActionEvent ev) { + if (ev.getSource() == saveTable) { + JFileChooser chooser = FileDialoger.promptToSaveFile("summary.csv");//$NON-NLS-1$ + if (chooser == null) { + return; + } + FileWriter writer = null; + try { + writer = new FileWriter(chooser.getSelectedFile()); + CSVSaveService.saveCSVStats(model,writer, saveHeaders.isSelected()); + } catch (FileNotFoundException e) { + log.warn(e.getMessage()); + } catch (IOException e) { + log.warn(e.getMessage()); + } finally { + JOrphanUtils.closeQuietly(writer); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/TableSample.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/TableSample.java new file mode 100644 index 0000000..4ec667c --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/TableSample.java @@ -0,0 +1,132 @@ +/* + * 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.visualizers; + +import java.io.Serializable; +import java.text.Format; +import java.util.Date; + +/** + * Class to hold data for the TableVisualiser. + */ +public class TableSample implements Serializable, Comparable { + private static final long serialVersionUID = 240L; + + private final long totalSamples; + + private final int sampleCount; // number of samples in this entry + + private final long startTime; + + private final String threadName; + + private final String label; + + private final long elapsed; + + private final boolean success; + + private final long bytes; + + /** + * @deprecated for unit test code only + */ + @Deprecated + public TableSample() { + this(0, 1, 0, "", "", 0, true, 0); + } + + public TableSample(long totalSamples, int sampleCount, long startTime, String threadName, + String label, + long elapsed, boolean success, long bytes) { + this.totalSamples = totalSamples; + this.sampleCount = sampleCount; + this.startTime = startTime; + this.threadName = threadName; + this.label = label; + this.elapsed = elapsed/sampleCount; + this.success = success; + this.bytes = bytes/sampleCount; + } + + // The following getters may appear not to be used - however they are invoked via the Functor class + + public long getBytes() { + return bytes; + } + + public String getSampleNumberString(){ + StringBuilder sb = new StringBuilder(); + if (sampleCount > 1) { + sb.append(totalSamples-sampleCount+1); + sb.append('-'); + } + sb.append(totalSamples); + return sb.toString(); + } + + public long getElapsed() { + return elapsed; + } + + public boolean isSuccess() { + return success; + } + + public long getStartTime() { + return startTime; + } + + /** + * @return the start time using the specified format + * Intended for use from Functors + */ + public String getStartTimeFormatted(Format format) { + return format.format(new Date(getStartTime())); + } + + public String getThreadName() { + return threadName; + } + + public String getLabel() { + return label; + } + + public int compareTo(TableSample o) { + TableSample oo = o; + return ((totalSamples - oo.totalSamples) < 0 ? -1 : (totalSamples == oo.totalSamples ? 0 : 1)); + } + + // TODO should equals and hashCode depend on field other than count? + + @Override + public boolean equals(Object o){ + return ( + (o instanceof TableSample) && + (this.compareTo((TableSample) o) == 0) + ); + } + + @Override + public int hashCode(){ + return (int)(totalSamples ^ (totalSamples >>> 32)); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/TableVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/TableVisualizer.java new file mode 100644 index 0000000..347e6f0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/TableVisualizer.java @@ -0,0 +1,329 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.FlowLayout; +import java.text.Format; +import java.text.SimpleDateFormat; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.table.TableCellRenderer; + +import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer; +import org.apache.jmeter.gui.util.HorizontalPanel; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.Calculator; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.gui.ObjectTableModel; +import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.gui.RightAlignRenderer; +import org.apache.jorphan.gui.layout.VerticalLayout; +import org.apache.jorphan.reflect.Functor; + +/** + * This class implements a statistical analyser that calculates both the average + * and the standard deviation of the sampling process. The samples are displayed + * in a JTable, and the statistics are displayed at the bottom of the table. + * + * created March 10, 2002 + * + */ +public class TableVisualizer extends AbstractVisualizer implements Clearable { + + private static final long serialVersionUID = 240L; + + // Note: the resource string won't respond to locale-changes, + // however this does not matter as it is only used when pasting to the clipboard + private static final ImageIcon imageSuccess = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.success", //$NON-NLS-1$ + "icon_success_sml.gif"), //$NON-NLS-1$ + JMeterUtils.getResString("table_visualizer_success")); //$NON-NLS-1$ + + private static final ImageIcon imageFailure = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.failure", //$NON-NLS-1$ + "icon_warning_sml.gif"), //$NON-NLS-1$ + JMeterUtils.getResString("table_visualizer_warning")); //$NON-NLS-1$ + + private static final String[] COLUMNS = new String[] { + "table_visualizer_sample_num", // $NON-NLS-1$ + "table_visualizer_start_time", // $NON-NLS-1$ + "table_visualizer_thread_name", // $NON-NLS-1$ + "sampler_label", // $NON-NLS-1$ + "table_visualizer_sample_time", // $NON-NLS-1$ + "table_visualizer_status", // $NON-NLS-1$ + "table_visualizer_bytes" }; // $NON-NLS-1$ + + private ObjectTableModel model = null; + + private JTable table = null; + + private JTextField dataField = null; + + private JTextField averageField = null; + + private JTextField deviationField = null; + + private JTextField noSamplesField = null; + + private JScrollPane tableScrollPanel = null; + + private JCheckBox autoscroll = null; + + private JCheckBox childSamples = null; + + private transient Calculator calc = new Calculator(); + + private Format format = new SimpleDateFormat("HH:mm:ss.SSS"); //$NON-NLS-1$ + + // Column renderers + private static final TableCellRenderer[] RENDERERS = + new TableCellRenderer[]{ + new RightAlignRenderer(), // Sample number (string) + new RightAlignRenderer(), // Start Time + null, // Thread Name + null, // Label + null, // Sample Time + null, // Status + null, // Bytes + }; + + /** + * Constructor for the TableVisualizer object. + */ + public TableVisualizer() { + super(); + model = new ObjectTableModel(COLUMNS, + TableSample.class, // The object used for each row + new Functor[] { + new Functor("getSampleNumberString"), // $NON-NLS-1$ + new Functor("getStartTimeFormatted", // $NON-NLS-1$ + new Object[]{format}), + new Functor("getThreadName"), // $NON-NLS-1$ + new Functor("getLabel"), // $NON-NLS-1$ + new Functor("getElapsed"), // $NON-NLS-1$ + new SampleSuccessFunctor("isSuccess"), // $NON-NLS-1$ + new Functor("getBytes") }, // $NON-NLS-1$ + new Functor[] { null, null, null, null, null, null, null }, + new Class[] { + String.class, String.class, String.class, String.class, Long.class, ImageIcon.class, Integer.class }); + init(); + } + + public static boolean testFunctors(){ + TableVisualizer instance = new TableVisualizer(); + return instance.model.checkFunctors(null,instance.getClass()); + } + + + public String getLabelResource() { + return "view_results_in_table"; // $NON-NLS-1$ + } + + protected synchronized void updateTextFields(SampleResult res) { + noSamplesField.setText(Long.toString(calc.getCount())); + dataField.setText(Long.toString(res.getTime()/res.getSampleCount())); + averageField.setText(Long.toString((long) calc.getMean())); + deviationField.setText(Long.toString((long) calc.getStandardDeviation())); + } + + public void add(final SampleResult res) { + JMeterUtils.runSafe(new Runnable() { + public void run() { + if (childSamples.isSelected()) { + SampleResult[] subResults = res.getSubResults(); + if (subResults.length > 0) { + for (SampleResult sr : subResults) { + add(sr); + } + return; + } + } + synchronized (calc) { + calc.addSample(res); + int count = calc.getCount(); + TableSample newS = new TableSample( + count, + res.getSampleCount(), + res.getStartTime(), + res.getThreadName(), + res.getSampleLabel(), + res.getTime(), + res.isSuccessful(), + res.getBytes()); + model.addRow(newS); + } + updateTextFields(res); + if (autoscroll.isSelected()) { + table.scrollRectToVisible(table.getCellRect(table.getRowCount() - 1, 0, true)); + } + } + }); + } + + public synchronized void clearData() { + model.clearData(); + calc.clear(); + noSamplesField.setText("0"); // $NON-NLS-1$ + dataField.setText("0"); // $NON-NLS-1$ + averageField.setText("0"); // $NON-NLS-1$ + deviationField.setText("0"); // $NON-NLS-1$ + repaint(); + } + + @Override + public String toString() { + return "Show the samples in a table"; + } + + private void init() { + this.setLayout(new BorderLayout()); + + // MAIN PANEL + JPanel mainPanel = new JPanel(); + Border margin = new EmptyBorder(10, 10, 5, 10); + + mainPanel.setBorder(margin); + mainPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH)); + + // NAME + mainPanel.add(makeTitlePanel()); + + // Set up the table itself + table = new JTable(model); + table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer()); + // table.getTableHeader().setReorderingAllowed(false); + RendererUtils.applyRenderers(table, RENDERERS); + + tableScrollPanel = new JScrollPane(table); + tableScrollPanel.setViewportBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + + autoscroll = new JCheckBox(JMeterUtils.getResString("view_results_autoscroll")); //$NON-NLS-1$ + + childSamples = new JCheckBox(JMeterUtils.getResString("view_results_childsamples")); //$NON-NLS-1$ + + // Set up footer of table which displays numerics of the graphs + JPanel dataPanel = new JPanel(); + JLabel dataLabel = new JLabel(JMeterUtils.getResString("graph_results_latest_sample")); // $NON-NLS-1$ + dataLabel.setForeground(Color.black); + dataField = new JTextField(5); + dataField.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + dataField.setEditable(false); + dataField.setForeground(Color.black); + dataField.setBackground(getBackground()); + dataPanel.add(dataLabel); + dataPanel.add(dataField); + + JPanel averagePanel = new JPanel(); + JLabel averageLabel = new JLabel(JMeterUtils.getResString("graph_results_average")); // $NON-NLS-1$ + averageLabel.setForeground(Color.blue); + averageField = new JTextField(5); + averageField.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + averageField.setEditable(false); + averageField.setForeground(Color.blue); + averageField.setBackground(getBackground()); + averagePanel.add(averageLabel); + averagePanel.add(averageField); + + JPanel deviationPanel = new JPanel(); + JLabel deviationLabel = new JLabel(JMeterUtils.getResString("graph_results_deviation")); // $NON-NLS-1$ + deviationLabel.setForeground(Color.red); + deviationField = new JTextField(5); + deviationField.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + deviationField.setEditable(false); + deviationField.setForeground(Color.red); + deviationField.setBackground(getBackground()); + deviationPanel.add(deviationLabel); + deviationPanel.add(deviationField); + + JPanel noSamplesPanel = new JPanel(); + JLabel noSamplesLabel = new JLabel(JMeterUtils.getResString("graph_results_no_samples")); // $NON-NLS-1$ + + noSamplesField = new JTextField(8); + noSamplesField.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + noSamplesField.setEditable(false); + noSamplesField.setForeground(Color.black); + noSamplesField.setBackground(getBackground()); + noSamplesPanel.add(noSamplesLabel); + noSamplesPanel.add(noSamplesField); + + JPanel tableInfoPanel = new JPanel(); + tableInfoPanel.setLayout(new FlowLayout()); + tableInfoPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + + tableInfoPanel.add(noSamplesPanel); + tableInfoPanel.add(dataPanel); + tableInfoPanel.add(averagePanel); + tableInfoPanel.add(deviationPanel); + + JPanel tableControlsPanel = new JPanel(new BorderLayout()); + tableControlsPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + JPanel jp = new HorizontalPanel(); + jp.add(autoscroll); + jp.add(childSamples); + tableControlsPanel.add(jp, BorderLayout.WEST); + tableControlsPanel.add(tableInfoPanel, BorderLayout.CENTER); + + // Set up the table with footer + JPanel tablePanel = new JPanel(); + + tablePanel.setLayout(new BorderLayout()); + tablePanel.add(tableScrollPanel, BorderLayout.CENTER); + tablePanel.add(tableControlsPanel, BorderLayout.SOUTH); + + // Add the main panel and the graph + this.add(mainPanel, BorderLayout.NORTH); + this.add(tablePanel, BorderLayout.CENTER); + } + + public static class SampleSuccessFunctor extends Functor { + public SampleSuccessFunctor(String methodName) { + super(methodName); + } + + @Override + public Object invoke(Object p_invokee) { + Boolean success = (Boolean)super.invoke(p_invokee); + + if(success != null) { + if(success.booleanValue()) { + return imageSuccess; + } + else { + return imageFailure; + } + } + else { + return null; + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/TreeNodeRenderer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/TreeNodeRenderer.java new file mode 100644 index 0000000..090d2c0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/TreeNodeRenderer.java @@ -0,0 +1,69 @@ +/* + * 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.visualizers; + +import java.awt.Color; +import java.awt.Component; + +import javax.swing.ImageIcon; +import javax.swing.JTree; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; + +/** + * Tree cell renderer used by ComparisonVisualizer. + */ +public class TreeNodeRenderer extends DefaultTreeCellRenderer { + + private static final long serialVersionUID = 240L; + + // Same ViewResultsTree + private static final ImageIcon imageSuccess = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.success", //$NON-NLS-1$ + "icon_success_sml.gif")); //$NON-NLS-1$ + + private static final ImageIcon imageFailure = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.failure", //$NON-NLS-1$ + "icon_warning_sml.gif")); //$NON-NLS-1$ + + public TreeNodeRenderer() { + super(); + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, + boolean leaf, int row, boolean focus) { + super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, focus); + Object obj = ((DefaultMutableTreeNode) value).getUserObject(); + if(obj instanceof SampleResult) + { + if (!((SampleResult) obj).isSuccessful()) { + this.setForeground(Color.red); + this.setIcon(imageFailure); + } else { + this.setIcon(imageSuccess); + } + } + return this; + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java new file mode 100644 index 0000000..8d46d27 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java @@ -0,0 +1,408 @@ +/* + * 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.visualizers; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.ImageIcon; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTree; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.gui.util.VerticalPanel; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * Base for ViewResults + * + */ +public class ViewResultsFullVisualizer extends AbstractVisualizer +implements ActionListener, TreeSelectionListener, Clearable, ItemListener { + + private static final long serialVersionUID = 7338676747296593842L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + public static final Color SERVER_ERROR_COLOR = Color.red; + + public static final Color CLIENT_ERROR_COLOR = Color.blue; + + public static final Color REDIRECT_COLOR = Color.green; + + private JSplitPane mainSplit; + + private DefaultMutableTreeNode root; + + private DefaultTreeModel treeModel; + + private JTree jTree; + + private Component leftSide; + + private JTabbedPane rightSide; + + private JComboBox selectRenderPanel; + + private int selectedTab; + + protected static final String COMBO_CHANGE_COMMAND = "change_combo"; // $NON-NLS-1$ + + private static final ImageIcon imageSuccess = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.success", //$NON-NLS-1$ + "icon_success_sml.gif")); //$NON-NLS-1$ + + private static final ImageIcon imageFailure = JMeterUtils.getImage( + JMeterUtils.getPropDefault("viewResultsTree.failure", //$NON-NLS-1$ + "icon_warning_sml.gif")); //$NON-NLS-1$ + + // Maximum size that we will display + private static final int MAX_DISPLAY_SIZE = + JMeterUtils.getPropDefault("view.results.tree.max_size", 200 * 1024); // $NON-NLS-1$ + + private ResultRenderer resultsRender = null; + + private TreeSelectionEvent lastSelectionEvent; + + private JCheckBox autoScrollCB; + + /** + * Constructor + */ + public ViewResultsFullVisualizer() { + super(); + init(); + } + + /** {@inheritDoc} */ + public void add(final SampleResult sample) { + JMeterUtils.runSafe(new Runnable() { + public void run() { + updateGui(sample); + } + }); + } + + /** + * Update the visualizer with new data. + */ + private synchronized void updateGui(SampleResult res) { + // Add sample + DefaultMutableTreeNode currNode = new DefaultMutableTreeNode(res); + treeModel.insertNodeInto(currNode, root, root.getChildCount()); + addSubResults(currNode, res); + // Add any assertion that failed as children of the sample node + AssertionResult assertionResults[] = res.getAssertionResults(); + int assertionIndex = currNode.getChildCount(); + for (int j = 0; j < assertionResults.length; j++) { + AssertionResult item = assertionResults[j]; + + if (item.isFailure() || item.isError()) { + DefaultMutableTreeNode assertionNode = new DefaultMutableTreeNode(item); + treeModel.insertNodeInto(assertionNode, currNode, assertionIndex++); + } + } + + if (root.getChildCount() == 1) { + jTree.expandPath(new TreePath(root)); + } + if (autoScrollCB.isSelected() && root.getChildCount() > 1) { + jTree.scrollPathToVisible(new TreePath(new Object[] { root, + treeModel.getChild(root, root.getChildCount() - 1) })); + } + } + + private void addSubResults(DefaultMutableTreeNode currNode, SampleResult res) { + SampleResult[] subResults = res.getSubResults(); + + int leafIndex = 0; + + for (int i = 0; i < subResults.length; i++) { + SampleResult child = subResults[i]; + + if (log.isDebugEnabled()) { + log.debug("updateGui1 : child sample result - " + child); + } + DefaultMutableTreeNode leafNode = new DefaultMutableTreeNode(child); + + treeModel.insertNodeInto(leafNode, currNode, leafIndex++); + addSubResults(leafNode, child); + // Add any assertion that failed as children of the sample node + AssertionResult assertionResults[] = child.getAssertionResults(); + int assertionIndex = leafNode.getChildCount(); + for (int j = 0; j < assertionResults.length; j++) { + AssertionResult item = assertionResults[j]; + + if (item.isFailure() || item.isError()) { + DefaultMutableTreeNode assertionNode = new DefaultMutableTreeNode(item); + treeModel.insertNodeInto(assertionNode, leafNode, assertionIndex++); + } + } + } + } + + /** {@inheritDoc} */ + public synchronized void clearData() { + while (root.getChildCount() > 0) { + // the child to be removed will always be 0 'cos as the nodes are + // removed the nth node will become (n-1)th + treeModel.removeNodeFromParent((DefaultMutableTreeNode) root.getChildAt(0)); + } + resultsRender.clearData(); + } + + /** {@inheritDoc} */ + public String getLabelResource() { + return "view_results_tree_title"; // $NON-NLS-1$ + } + + /** + * Initialize this visualizer + */ + protected void init() { + log.debug("init() - pass"); + setLayout(new BorderLayout(0, 5)); + setBorder(makeBorder()); + add(makeTitlePanel(), BorderLayout.NORTH); + + leftSide = createLeftPanel(); + // Prepare the common tab + rightSide = new JTabbedPane(); + + // Create the split pane + mainSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftSide, rightSide); + add(mainSplit, BorderLayout.CENTER); + // init right side with first render + resultsRender.setRightSide(rightSide); + resultsRender.init(); + } + + /** {@inheritDoc} */ + public void valueChanged(TreeSelectionEvent e) { + lastSelectionEvent = e; + DefaultMutableTreeNode node = (DefaultMutableTreeNode) jTree.getLastSelectedPathComponent(); + + if (node != null) { + // to restore last tab used + if (rightSide.getTabCount() > selectedTab) { + resultsRender.setLastSelectedTab(rightSide.getSelectedIndex()); + } + Object userObject = node.getUserObject(); + resultsRender.setSamplerResult(userObject); + resultsRender.setupTabPane(); // Processes Assertions + // display a SampleResult + if (userObject instanceof SampleResult) { + SampleResult sampleResult = (SampleResult) userObject; + if ((SampleResult.TEXT).equals(sampleResult.getDataType())){ + resultsRender.renderResult(sampleResult); + } else { + byte[] responseBytes = sampleResult.getResponseData(); + if (responseBytes != null) { + resultsRender.renderImage(sampleResult); + } + } + } + } + } + + private synchronized Component createLeftPanel() { + SampleResult rootSampleResult = new SampleResult(); + rootSampleResult.setSampleLabel("Root"); + rootSampleResult.setSuccessful(true); + root = new DefaultMutableTreeNode(rootSampleResult); + + treeModel = new DefaultTreeModel(root); + jTree = new JTree(treeModel); + jTree.setCellRenderer(new ResultsNodeRenderer()); + jTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + jTree.addTreeSelectionListener(this); + jTree.setRootVisible(false); + jTree.setShowsRootHandles(true); + JScrollPane treePane = new JScrollPane(jTree); + treePane.setPreferredSize(new Dimension(200, 300)); + + VerticalPanel leftPane = new VerticalPanel(); + leftPane.add(treePane, BorderLayout.CENTER); + VerticalPanel leftDownPane = new VerticalPanel(); + leftDownPane.add(createComboRender(), BorderLayout.NORTH); + autoScrollCB = new JCheckBox(JMeterUtils.getResString("view_results_autoscroll")); + autoScrollCB.setSelected(false); + autoScrollCB.addItemListener(this); + leftDownPane.add(autoScrollCB, BorderLayout.SOUTH); + leftPane.add(leftDownPane, BorderLayout.SOUTH); + return leftPane; + } + + /** + * Create the drop-down list to changer render + * @return List of all render (implement ResultsRender) + */ + private Component createComboRender() { + ComboBoxModel nodesModel = new DefaultComboBoxModel(); + // drop-down list for renderer + selectRenderPanel = new JComboBox(nodesModel); + selectRenderPanel.setActionCommand(COMBO_CHANGE_COMMAND); + 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 textRenderer = JMeterUtils.getResString("view_results_render_text"); // $NON-NLS-1$ + Object textObject = null; + for (String clazz : classesToAdd) { + try { + // Instantiate render classes + final ResultRenderer renderer = (ResultRenderer) Class.forName(clazz).newInstance(); + if (textRenderer.equals(renderer.toString())){ + textObject=renderer; + } + renderer.setBackgroundColor(getBackground()); + selectRenderPanel.addItem(renderer); + } catch (Exception e) { + log.warn("Error in load result render:" + clazz, e); + } + } + nodesModel.setSelectedItem(textObject); // preset to "Text" option + return selectRenderPanel; + } + + /** {@inheritDoc} */ + public void actionPerformed(ActionEvent event) { + String command = event.getActionCommand(); + if (COMBO_CHANGE_COMMAND.equals(command)) { + JComboBox jcb = (JComboBox) event.getSource(); + + if (jcb != null) { + resultsRender = (ResultRenderer) jcb.getSelectedItem(); + if (rightSide != null) { + // to restore last selected tab (better user-friendly) + selectedTab = rightSide.getSelectedIndex(); + // Remove old right side + mainSplit.remove(rightSide); + + // create and add a new right side + rightSide = new JTabbedPane(); + mainSplit.add(rightSide); + resultsRender.setRightSide(rightSide); + resultsRender.setLastSelectedTab(selectedTab); + log.debug("selectedTab=" + selectedTab); + resultsRender.init(); + // To display current sampler result before change + this.valueChanged(lastSelectionEvent); + } + } + } + } + + public static String getResponseAsString(SampleResult res) { + String response = null; + if ((SampleResult.TEXT).equals(res.getDataType())) { + // Showing large strings can be VERY costly, so we will avoid + // doing so if the response + // data is larger than 200K. TODO: instead, we could delay doing + // the result.setText + // call until the user chooses the "Response data" tab. Plus we + // could warn the user + // if this happens and revert the choice if he doesn't confirm + // he's ready to wait. + int len = res.getResponseData().length; + if (MAX_DISPLAY_SIZE > 0 && len > MAX_DISPLAY_SIZE) { + StringBuilder builder = new StringBuilder(MAX_DISPLAY_SIZE+100); + builder.append(JMeterUtils.getResString("view_results_response_too_large_message")) //$NON-NLS-1$ + .append(len).append(" > Max: ").append(MAX_DISPLAY_SIZE) + .append(", ").append(JMeterUtils.getResString("view_results_response_partial_message")) + .append("\n").append(res.getResponseDataAsString().substring(0, MAX_DISPLAY_SIZE)).append("\n..."); + response = builder.toString(); + log.warn(response); + } else { + response = res.getResponseDataAsString(); + } + } + return response; + } + + private static class ResultsNodeRenderer extends DefaultTreeCellRenderer { + private static final long serialVersionUID = 4159626601097711565L; + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, + boolean sel, boolean expanded, boolean leaf, int row, boolean focus) { + super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, focus); + boolean failure = true; + Object userObject = ((DefaultMutableTreeNode) value).getUserObject(); + if (userObject instanceof SampleResult) { + failure = !(((SampleResult) userObject).isSuccessful()); + } else if (userObject instanceof AssertionResult) { + AssertionResult assertion = (AssertionResult) userObject; + failure = assertion.isError() || assertion.isFailure(); + } + + // Set the status for the node + if (failure) { + this.setForeground(Color.red); + this.setIcon(imageFailure); + } else { + this.setIcon(imageSuccess); + } + return this; + } + } + + /** + * Handler for Checkbox + */ + public void itemStateChanged(ItemEvent e) { + // NOOP state is held by component + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/Visualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/Visualizer.java new file mode 100644 index 0000000..d4bb858 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/Visualizer.java @@ -0,0 +1,45 @@ +/* + * 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.visualizers; + +import org.apache.jmeter.samplers.SampleResult; + +/** + * Implement this method to be a Visualizer for JMeter. This interface defines a + * single method, "add()", that provides the means by which + * {@link org.apache.jmeter.samplers.SampleResult SampleResults} are passed to + * the implementing visualizer for display/logging. The easiest way to create + * the visualizer is to extend the + * {@link org.apache.jmeter.visualizers.gui.AbstractVisualizer} class. + * + */ +public interface Visualizer { + /** + * This method is called by sampling thread to inform the visualizer about + * the arrival of a new sample. + */ + public void add(SampleResult sample); + + /** + * This method is used to indicate a visualizer generates statistics. + * + * @return true if visualiser generates statistics + */ + public boolean isStats(); +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/XMLDefaultMutableTreeNode.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/XMLDefaultMutableTreeNode.java new file mode 100644 index 0000000..7ac9795 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/XMLDefaultMutableTreeNode.java @@ -0,0 +1,212 @@ +/* + * 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.visualizers; + +import javax.swing.tree.DefaultMutableTreeNode; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import org.xml.sax.SAXException; + +/** + * A extended class of DefaultMutableTreeNode except that it also attached XML + * node and convert XML document into DefaultMutableTreeNode. + * + */ +public class XMLDefaultMutableTreeNode extends DefaultMutableTreeNode { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + // private static final int LIMIT_STR_SIZE = 100; + // private boolean isRoot; + private transient Node xmlNode; + + /** + * @deprecated only for use by test code + */ + @Deprecated + public XMLDefaultMutableTreeNode(){ + log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$ + } + + public XMLDefaultMutableTreeNode(Node root) throws SAXException { + super(root.getNodeName()); + initAttributeNode(root, this); + initRoot(root); + + } + + public XMLDefaultMutableTreeNode(String name, Node xmlNode) { + super(name); + this.xmlNode = xmlNode; + + } + + /** + * init root + * + * @param root + * @throws SAXException + */ + private void initRoot(Node xmlRoot) throws SAXException { + + NodeList childNodes = xmlRoot.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + initNode(childNode, this); + } + + } + + /** + * init node + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initNode(Node node, XMLDefaultMutableTreeNode mTreeNode) throws SAXException { + + switch (node.getNodeType()) { + case Node.ELEMENT_NODE: + initElementNode(node, mTreeNode); + break; + + case Node.TEXT_NODE: + initTextNode((Text) node, mTreeNode); + break; + + case Node.CDATA_SECTION_NODE: + initCDATASectionNode((CDATASection) node, mTreeNode); + break; + case Node.COMMENT_NODE: + initCommentNode((Comment) node, mTreeNode); + break; + + default: + // if other node type, we will just skip it + break; + + } + + } + + /** + * init element node + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initElementNode(Node node, DefaultMutableTreeNode mTreeNode) throws SAXException { + String nodeName = node.getNodeName(); + + NodeList childNodes = node.getChildNodes(); + XMLDefaultMutableTreeNode childTreeNode = new XMLDefaultMutableTreeNode(nodeName, node); + + mTreeNode.add(childTreeNode); + initAttributeNode(node, childTreeNode); + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + initNode(childNode, childTreeNode); + } + + } + + /** + * init attribute node + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initAttributeNode(Node node, DefaultMutableTreeNode mTreeNode) throws SAXException { + NamedNodeMap nm = node.getAttributes(); + for (int i = 0; i < nm.getLength(); i++) { + Attr nmNode = (Attr) nm.item(i); + String value = nmNode.getName() + " = \"" + nmNode.getValue() + "\""; // $NON-NLS-1$ $NON-NLS-2$ + XMLDefaultMutableTreeNode attributeNode = new XMLDefaultMutableTreeNode(value, nmNode); + mTreeNode.add(attributeNode); + + } + } + + /** + * init comment Node + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initCommentNode(Comment node, DefaultMutableTreeNode mTreeNode) throws SAXException { + String data = node.getData(); + if (data != null && data.length() > 0) { + String value = ""; // $NON-NLS-1$ $NON-NLS-2$ + XMLDefaultMutableTreeNode commentNode = new XMLDefaultMutableTreeNode(value, node); + mTreeNode.add(commentNode); + } + } + + /** + * init CDATASection Node + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initCDATASectionNode(CDATASection node, DefaultMutableTreeNode mTreeNode) throws SAXException { + String data = node.getData(); + if (data != null && data.length() > 0) { + String value = ""; // $NON-NLS-1$ $NON-NLS-2$ + XMLDefaultMutableTreeNode commentNode = new XMLDefaultMutableTreeNode(value, node); + mTreeNode.add(commentNode); + } + } + + /** + * init the TextNode + * + * @param node + * @param mTreeNode + * @throws SAXException + */ + private void initTextNode(Text node, DefaultMutableTreeNode mTreeNode) throws SAXException { + String text = node.getNodeValue().trim(); + if (text != null && text.length() > 0) { + XMLDefaultMutableTreeNode textNode = new XMLDefaultMutableTreeNode(text, node); + mTreeNode.add(textNode); + } + } + + /** + * get the xml node + * + * @return the XML node + */ + public Node getXMLNode() { + return xmlNode; + } +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/gui/AbstractListenerGui.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/gui/AbstractListenerGui.java new file mode 100644 index 0000000..3369e69 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/gui/AbstractListenerGui.java @@ -0,0 +1,64 @@ +/* + * 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.visualizers.gui; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.JPopupMenu; + +import org.apache.jmeter.gui.AbstractJMeterGuiComponent; +import org.apache.jmeter.gui.util.MenuFactory; + +/** + * Basic Listener/Visualiser Gui class to correspond with AbstractPreProcessorGui etc. + */ +public abstract class AbstractListenerGui extends AbstractJMeterGuiComponent { + + private static final long serialVersionUID = 240L; + + /** + * When a user right-clicks on the component in the test tree, or selects + * the edit menu when the component is selected, the component will be asked + * to return a JPopupMenu that provides all the options available to the + * user from this component. + *

+ * This implementation returns menu items appropriate for most visualizer + * components. + * + * @return a JPopupMenu appropriate for the component. + */ + public JPopupMenu createPopupMenu() { + return MenuFactory.getDefaultVisualizerMenu(); + } + + /** + * This is the list of menu categories this gui component will be available + * under. This implementation returns + * {@link org.apache.jmeter.gui.util.MenuFactory#LISTENERS}, which is + * appropriate for most visualizer components. + * + * @return a Collection of Strings, where each element is one of the + * constants defined in MenuFactory + */ + public Collection getMenuCategories() { + return Arrays.asList(new String[] { MenuFactory.LISTENERS }); + } + +} diff --git a/ApacheJmeter/src/org/apache/jmeter/visualizers/gui/AbstractVisualizer.java b/ApacheJmeter/src/org/apache/jmeter/visualizers/gui/AbstractVisualizer.java new file mode 100644 index 0000000..c498e84 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jmeter/visualizers/gui/AbstractVisualizer.java @@ -0,0 +1,354 @@ +/* + * 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.visualizers.gui; + +import java.awt.Component; +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.gui.SavePropertyDialog; +import org.apache.jmeter.gui.UnsharedComponent; +import org.apache.jmeter.gui.util.FilePanel; +import org.apache.jmeter.reporters.AbstractListenerElement; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.samplers.Clearable; +import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.Visualizer; +import org.apache.jorphan.gui.ComponentUtil; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; + +/** + * This is the base class for JMeter GUI components which can display test + * results in some way. It provides the following conveniences to developers: + *

    + *
  • Implements the + * {@link org.apache.jmeter.gui.JMeterGUIComponent JMeterGUIComponent} interface + * that allows your Gui visualizer to "plug-in" to the JMeter GUI environment. + * Provides implementations for the following methods: + *
      + *
    • {@link org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement) configure(TestElement)}. + * Any additional parameters of your Visualizer need to be handled by you.
    • + *
    • {@link org.apache.jmeter.gui.JMeterGUIComponent#createTestElement() createTestElement()}. + * For most purposes, the default + * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} created + * by this method is sufficient.
    • + *
    • {@link org.apache.jmeter.gui.JMeterGUIComponent#getMenuCategories getMenuCategories()}. + * To control where in the GUI your visualizer can be added.
    • + *
    • {@link org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) modifyTestElement(TestElement)}. + * Again, additional parameters you require have to be handled by you.
    • + *
    • {@link org.apache.jmeter.gui.JMeterGUIComponent#createPopupMenu() createPopupMenu()}.
    • + *
    + *
  • + *
  • Provides convenience methods to help you make a JMeter-compatible GUI: + *
      + *
    • {@link #makeTitlePanel()}. Returns a panel that includes the name of + * the component, and a FilePanel that allows users to control what file samples + * are logged to.
    • + *
    • {@link #getModel()} and {@link #setModel(ResultCollector)} methods for + * setting and getting the model class that handles the receiving and logging of + * sample results.
    • + *
    + *
  • + *
+ * For most developers, making a new visualizer is primarly for the purpose of + * either calculating new statistics on the sample results that other + * visualizers don't calculate, or displaying the results visually in a new and + * interesting way. Making a new visualizer for either of these purposes is easy - + * just extend this class and implement the + * {@link org.apache.jmeter.visualizers.Visualizer#add add(SampleResult)} + * method and display the results as you see fit. This AbstractVisualizer and + * the default + * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} handle + * logging and registering to receive SampleEvents for you - all you need to do + * is include the JPanel created by makeTitlePanel somewhere in your gui to + * allow users set the log file. + *

+ * If you are doing more than that, you may need to extend + * {@link org.apache.jmeter.reporters.ResultCollector ResultCollector} as well + * and modify the {@link #configure(TestElement)}, + * {@link #modifyTestElement(TestElement)}, and {@link #createTestElement()} + * methods to create and modify your alternate ResultCollector. For an example + * of this, see the + * {@link org.apache.jmeter.visualizers.MailerVisualizer MailerVisualizer}. + *

+ * + */ +public abstract class AbstractVisualizer + extends AbstractListenerGui + implements Visualizer, ChangeListener, UnsharedComponent, Clearable + { + private static final long serialVersionUID = 240L; + + /** Logging. */ + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** File Extensions */ + private static final String[] EXTS = { ".jtl", ".csv" }; // $NON-NLS-1$ $NON-NLS-2$ + + /** A panel allowing results to be saved. */ + private FilePanel filePanel; + + /** A checkbox choosing whether or not only errors should be logged. */ + private JCheckBox errorLogging; + + /* A checkbox choosing whether or not only successes should be logged. */ + private JCheckBox successOnlyLogging; + + private JButton saveConfigButton; + + protected ResultCollector collector = new ResultCollector(); + + protected boolean isStats = false; + + public AbstractVisualizer() { + super(); + + // errorLogging and successOnlyLogging are mutually exclusive + errorLogging = new JCheckBox(JMeterUtils.getResString("log_errors_only")); // $NON-NLS-1$ + errorLogging.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + if (errorLogging.isSelected()) { + successOnlyLogging.setSelected(false); + } + } + }); + successOnlyLogging = new JCheckBox(JMeterUtils.getResString("log_success_only")); // $NON-NLS-1$ + successOnlyLogging.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + if (successOnlyLogging.isSelected()) { + errorLogging.setSelected(false); + } + } + }); + saveConfigButton = new JButton(JMeterUtils.getResString("config_save_settings")); // $NON-NLS-1$ + saveConfigButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + SavePropertyDialog d = new SavePropertyDialog( + GuiPackage.getInstance().getMainFrame(), + JMeterUtils.getResString("sample_result_save_configuration"), // $NON-NLS-1$ + true, collector.getSaveConfig()); + d.pack(); + ComponentUtil.centerComponentInComponent(GuiPackage.getInstance().getMainFrame(), d); + d.setVisible(true); + } + }); + + filePanel = new FilePanel(JMeterUtils.getResString("file_visualizer_output_file"), EXTS); // $NON-NLS-1$ + filePanel.addChangeListener(this); + filePanel.add(new JLabel(JMeterUtils.getResString("log_only"))); // $NON-NLS-1$ + filePanel.add(errorLogging); + filePanel.add(successOnlyLogging); + filePanel.add(saveConfigButton); + + } + + public boolean isStats() { + return isStats; + } + + /** + * Gets the checkbox which selects whether or not only errors should be + * logged. Subclasses don't normally need to worry about this checkbox, + * because it is automatically added to the GUI in {@link #makeTitlePanel()}, + * and the behavior is handled in this base class. + * + * @return the error logging checkbox + */ + protected JCheckBox getErrorLoggingCheckbox() { + return errorLogging; + } + + /** + * Provides access to the ResultCollector model class for extending + * implementations. Using this method and setModel(ResultCollector) is only + * necessary if your visualizer requires a differently behaving + * ResultCollector. Using these methods will allow maximum reuse of the + * methods provided by AbstractVisualizer in this event. + */ + protected ResultCollector getModel() { + return collector; + } + + /** + * Gets the file panel which allows the user to save results to a file. + * Subclasses don't normally need to worry about this panel, because it is + * automatically added to the GUI in {@link #makeTitlePanel()}, and the + * behavior is handled in this base class. + * + * @return the file panel allowing users to save results + */ + protected Component getFilePanel() { + return filePanel; + } + + /** + * Sets the filename which results will be saved to. This will set the + * filename in the FilePanel. Subclasses don't normally need to call this + * method, because configuration of the FilePanel is handled in this base + * class. + * + * @param filename + * the new filename + * + * @see #getFilePanel() + */ + public void setFile(String filename) { + // TODO: Does this method need to be public? It isn't currently + // called outside of this class. + filePanel.setFilename(filename); + } + + /** + * Gets the filename which has been entered in the FilePanel. Subclasses + * don't normally need to call this method, because configuration of the + * FilePanel is handled in this base class. + * + * @return the current filename + * + * @see #getFilePanel() + */ + public String getFile() { + // TODO: Does this method need to be public? It isn't currently + // called outside of this class. + return filePanel.getFilename(); + } + + /** + * Invoked when the target of the listener has changed its state. This + * implementation assumes that the target is the FilePanel, and will update + * the result collector for the new filename. + * + * @param e + * the event that has occurred + */ + public void stateChanged(ChangeEvent e) { + log.debug("getting new collector"); + collector = (ResultCollector) createTestElement(); + collector.loadExistingFile(); + } + + /* Implements JMeterGUIComponent.createTestElement() */ + public TestElement createTestElement() { + if (collector == null) { + collector = new ResultCollector(); + } + modifyTestElement(collector); + return (TestElement) collector.clone(); + } + + /* Implements JMeterGUIComponent.modifyTestElement(TestElement) */ + public void modifyTestElement(TestElement c) { + configureTestElement((AbstractListenerElement) c); + if (c instanceof ResultCollector) { + ResultCollector rc = (ResultCollector) c; + rc.setErrorLogging(errorLogging.isSelected()); + rc.setSuccessOnlyLogging(successOnlyLogging.isSelected()); + rc.setFilename(getFile()); + collector = rc; + } + } + + /* Overrides AbstractJMeterGuiComponent.configure(TestElement) */ + @Override + public void configure(TestElement el) { + super.configure(el); + setFile(el.getPropertyAsString(ResultCollector.FILENAME)); + ResultCollector rc = (ResultCollector) el; + errorLogging.setSelected(rc.isErrorLogging()); + successOnlyLogging.setSelected(rc.isSuccessOnlyLogging()); + if (collector == null) { + collector = new ResultCollector(); + } + collector.setSaveConfig((SampleSaveConfiguration) rc.getSaveConfig().clone()); + } + + /** + * This provides a convenience for extenders when they implement the + * {@link org.apache.jmeter.gui.JMeterGUIComponent#createTestElement()} + * method. This method will set the name, gui class, and test class for the + * created Test Element. It should be called by every extending class when + * creating Test Elements, as that will best assure consistent behavior. + * + * @param mc + * the TestElement being created. + */ + protected void configureTestElement(AbstractListenerElement mc) { + // TODO: Should the method signature of this method be changed to + // match the super-implementation (using a TestElement parameter + // instead of AbstractListenerElement)? This would require an + // instanceof check before adding the listener (below), but would + // also make the behavior a bit more obvious for sub-classes -- the + // Java rules dealing with this situation aren't always intuitive, + // and a subclass may think it is calling this version of the method + // when it is really calling the superclass version instead. + super.configureTestElement(mc); + mc.setListener(this); + } + + /** + * Create a standard title section for JMeter components. This includes the + * title for the component and the Name Panel allowing the user to change + * the name for the component. The AbstractVisualizer also adds the + * FilePanel allowing the user to save the results, and the error logging + * checkbox, allowing the user to choose whether or not only errors should + * be logged. + *

+ * This method is typically added to the top of the component at the + * beginning of the component's init method. + * + * @return a panel containing the component title, name panel, file panel, + * and error logging checkbox + */ + @Override + protected Container makeTitlePanel() { + Container panel = super.makeTitlePanel(); + // Note: the file panel already includes the error logging checkbox, + // so we don't have to add it explicitly. + panel.add(getFilePanel()); + return panel; + } + + /** + * Provides extending classes the opportunity to set the ResultCollector + * model for the Visualizer. This is useful to allow maximum reuse of the + * methods from AbstractVisualizer. + * + * @param collector + */ + protected void setModel(ResultCollector collector) { + this.collector = collector; + } + + @Override + public void clearGui(){ + super.clearGui(); + filePanel.clearGui(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/collections/Data.java b/ApacheJmeter/src/org/apache/jorphan/collections/Data.java new file mode 100644 index 0000000..10ca09e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/collections/Data.java @@ -0,0 +1,691 @@ +/* + * 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.collections; + +import java.io.Serializable; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Use this class to store database-like data. This class uses rows and columns + * to organize its data. It has some convenience methods that allow fast loading + * and retrieval of the data into and out of string arrays. It is also handy for + * reading CSV files. + * + * WARNING: the class assumes that column names are unique, but does not enforce this. + * + */ +public class Data implements Serializable { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private final Map> data; + + private ArrayList header; + + // saves current position in data List + private int currentPos, size; + + /** + * Constructor - takes no arguments. + */ + public Data() { + header = new ArrayList(); + data = new HashMap>(); + currentPos = -1; + size = currentPos + 1; + } + + /** + * Replaces the given header name with a new header name. + * + * @param oldHeader + * Old header name. + * @param newHeader + * New header name. + */ + public void replaceHeader(String oldHeader, String newHeader) { + List tempList; + int index = header.indexOf(oldHeader); + header.set(index, newHeader); + tempList = data.remove(oldHeader); + data.put(newHeader, tempList); + } + + /** + * Adds the rows of the given Data object to this Data object. + * + * @param d + * data object to be appended to this one + */ + public void append(Data d) { + boolean valid = true; + String[] headers = getHeaders(); + String[] dHeaders = d.getHeaders(); + if (headers.length != dHeaders.length) { + valid = false; + } else { + for (int count = 0; count < dHeaders.length; count++) { + if (!header.contains(dHeaders[count])) { + valid = false; + } + } + } + + if (valid) { + currentPos = size; + d.reset(); + while (d.next()) { + for (int count = 0; count < headers.length; count++) { + addColumnValue(headers[count], d.getColumnValue(headers[count])); + } + } + } + } + + /** + * Get the number of the current row. + * + * @return integer representing the current row + */ + public int getCurrentPos() { + return currentPos; + } + + /** + * Removes the current row. + */ + public void removeRow() { + List tempList; + Iterator it = data.keySet().iterator(); + log.debug("removing row, size = " + size); + if (currentPos > -1 && currentPos < size) { + log.debug("got to here"); + while (it.hasNext()) { + tempList = data.get(it.next()); + tempList.remove(currentPos); + } + if (currentPos > 0) { + currentPos--; + } + size--; + } + } + + public void removeRow(int index) { + log.debug("Removing row: " + index); + if (index < size) { + setCurrentPos(index); + log.debug("Setting currentpos to " + index); + removeRow(); + } + } + + public void addRow() { + String[] headers = getHeaders(); + List tempList = new ArrayList(); + for (int i = 0; i < headers.length; i++) { + if ((tempList = data.get(header.get(i))) == null) { + tempList = new ArrayList(); + data.put(headers[i], tempList); + } + tempList.add(""); + } + size = tempList.size(); + setCurrentPos(size - 1); + } + + /** + * Sets the current pos. If value sent to method is not a valid number, the + * current position is set to one higher than the maximum. + * + * @param r + * position to set to. + */ + public void setCurrentPos(int r) { + currentPos = r; + } + + /** + * Sorts the data using a given row as the sorting criteria. A boolean value + * indicates whether to sort ascending or descending. + * + * @param column + * name of column to use as sorting criteria. + * @param asc + * boolean value indicating whether to sort ascending or + * descending. True for asc, false for desc. Currently this + * feature is not enabled and all sorts are asc. + */ + public void sort(String column, boolean asc) { + sortData(column, 0, size); + } + + private void swapRows(int row1, int row2) { + List temp; + Object o; + Iterator it = data.keySet().iterator(); + while (it.hasNext()) { + temp = data.get(it.next()); + o = temp.get(row1); + temp.set(row1, temp.get(row2)); + temp.set(row2, o); + } + } + + /** + * Private method that implements the quicksort algorithm to sort the rows + * of the Data object. + * + * @param column + * name of column to use as sorting criteria. + * @param start + * starting index (for quicksort algorithm). + * @param end + * ending index (for quicksort algorithm). + */ + private void sortData(String column, int start, int end) { + int x = start, y = end - 1; + String basis = ((List) data.get(column)).get((x + y) / 2).toString(); + if (x == y) { + return; + } + + while (x <= y) { + while (x < end && ((List) data.get(column)).get(x).toString().compareTo(basis) < 0) { + x++; + } + + while (y >= (start - 1) && ((List) data.get(column)).get(y).toString().compareTo(basis) > 0) { + y--; + } + + if (x <= y) { + swapRows(x, y); + x++; + y--; + } + } + + if (x == y) { + x++; + } + + y = end - x; + + if (x > 0) { + sortData(column, start, x); + } + + if (y > 0) { + sortData(column, x, end); + } + } + + /** + * Gets the number of rows in the Data object. + * + * @return number of rows in Data object. + */ + public int size() { + return size; + } // end method + + /** + * Adds a value into the Data set at the current row, using a column name to + * find the column in which to insert the new value. + * + * @param column + * the name of the column to set. + * @param value + * value to set into column. + */ + public void addColumnValue(String column, Object value) { + ArrayList tempList; + if ((tempList = (ArrayList) data.get(column)) == null) { + tempList = new ArrayList(); + data.put(column, tempList); + } + int s = tempList.size(); + if (currentPos == -1) { + currentPos = size; + } + + if (currentPos >= size) { + size = currentPos + 1; + } + + while (currentPos > s) { + s++; + tempList.add(null); + } + + if (currentPos == s) { + tempList.add(value); + } else { + tempList.set(currentPos, value); + } + } + + /** + * Returns the row number where a certain value is. + * + * @param column + * column to be searched for value. + * @param value + * object in Search of. + * @return row # where value exists. + */ + public int findValue(String column, Object value) { + return data.get(column).indexOf(value); + } + + /** + * Sets the value in the Data set at the current row, using a column name to + * find the column in which to insert the new value. + * + * @param column + * the name of the column to set. + * @param value + * value to set into column. + */ + public void setColumnValue(String column, Object value) { + List tempList; + if ((tempList = data.get(column)) == null) { + tempList = new ArrayList(); + data.put(column, tempList); + } + + if (currentPos == -1) { + currentPos = 0; + } + + if (currentPos >= size) { + size++; + tempList.add(value); + } else if (currentPos >= tempList.size()) { + tempList.add(value); + } else { + tempList.set(currentPos, value); + } + } + + /** + * Checks to see if a column exists in the Data object. + * + * @param column + * Name of column header to check for. + * @return True or False depending on whether the column exists. + */ + public boolean hasHeader(String column) { + return data.containsKey(column); + } + + /** + * Sets the current position of the Data set to the next row. + * + * @return True if there is another row. False if there are no more rows. + */ + public boolean next() { + return (++currentPos < size); + } + + /** + * Gets a Data object from a ResultSet. + * + * @param rs + * ResultSet passed in from a database query + * @return a Data object + * @throws java.sql.SQLException + */ + public static Data getDataFromResultSet(ResultSet rs) throws SQLException { + ResultSetMetaData meta = rs.getMetaData(); + Data data = new Data(); + + int numColumns = meta.getColumnCount(); + String[] dbCols = new String[numColumns]; + for (int i = 0; i < numColumns; i++) { + dbCols[i] = meta.getColumnName(i + 1); + data.addHeader(dbCols[i]); + } + + while (rs.next()) { + data.next(); + for (int i = 0; i < numColumns; i++) { + Object o = rs.getObject(i + 1); + if (o instanceof byte[]) { + o = new String((byte[]) o); // TODO - charset? + } + data.addColumnValue(dbCols[i], o); + } + } + return data; + } + + /** + * Sets the current position of the Data set to the previous row. + * + * @return True if there is another row. False if there are no more rows. + */ + public boolean previous() { + return (--currentPos >= 0); + } + + /** + * Resets the current position of the data set to just before the first + * element. + */ + public void reset() { + currentPos = -1; + } + + /** + * Gets the value in the current row of the given column. + * + * @param column + * name of the column. + * @return an Object which holds the value of the column. + */ + public Object getColumnValue(String column) { + try { + if (currentPos < size) { + return ((List) data.get(column)).get(currentPos); + } else { + return null; + } + } catch (Exception e) { + return null; + } + } + + /** + * Gets the value in the current row of the given column. + * + * @param column + * index of the column (starts at 0). + * @return an Object which holds the value of the column. + */ + public Object getColumnValue(int column) { + String columnName = header.get(column); + try { + if (currentPos < size) { + return ((List) data.get(columnName)).get(currentPos); + } else { + return null; + } + } catch (Exception e) { + return null; + } + } + + public Object getColumnValue(int column, int row) { + setCurrentPos(row); + return getColumnValue(column); + } + + public void removeColumn(int col) { + String columnName = header.get(col); + data.remove(columnName); + header.remove(columnName); + } + + /** + * Sets the headers for the data set. Each header represents a column of + * data. Each row's data can be gotten with the column header name, which + * will always be a string. + * + * @param h + * array of strings representing the column headers. + * these must be distinct - duplicates will cause incorrect behaviour + */ + public void setHeaders(String[] h) { + int x = 0; + header = new ArrayList(h.length); + for (x = 0; x < h.length; x++) { + header.add(h[x]); + data.put(h[x], new ArrayList()); + } + } + + /** + * Returns a String array of the column headers. + * + * @return array of strings of the column headers. + */ + public String[] getHeaders() { + String[] r = new String[header.size()]; + if (r.length > 0) { + r = header.toArray(r); + } + return r; + } + + public int getHeaderCount(){ + return header.size(); + } + + /** + * This method will retrieve every entry in a certain column. It returns an + * array of Objects from the column. + * + * @param columnName + * name of the column. + * @return array of Objects representing the data. + */ + public List getColumnAsObjectArray(String columnName) { + return data.get(columnName); + } + + /** + * This method will retrieve every entry in a certain column. It returns an + * array of strings from the column. Even if the data are not strings, they + * will be returned as strings in this method. + * + * @param columnName + * name of the column. + * @return array of Strings representing the data. + */ + public String[] getColumn(String columnName) { + String[] returnValue; + List temp = data.get(columnName); + if (temp != null) { + returnValue = new String[temp.size()]; + int index = 0; + for (Object o : temp) { + if (o != null) { + if (o instanceof String) { + returnValue[index++] = (String) o; + } else { + returnValue[index++] = o.toString(); + } + } + } + } else { + returnValue = new String[0]; + } + return returnValue; + } + + /** + * Use this method to set the entire data set. It takes an array of strings. + * It uses the first row as the headers, and the next rows as the data + * elements. Delimiter represents the delimiting character(s) that separate + * each item in a data row. + * + * @param contents + * array of strings, the first element is a list of the column + * headers, the next elements each represent a single row of + * data. + * @param delimiter + * the delimiter character that separates columns within the + * string array. + */ + public void setData(String[] contents, String delimiter) { + setHeaders(JOrphanUtils.split(contents[0], delimiter)); + int x = 1; + while (x < contents.length) { + setLine(JOrphanUtils.split(contents[x++], delimiter)); + } + } + + /* + * Deletes a header from the Data object. Takes the column name as input. It + * will delete the entire column. + * + * public void deleteHeader(String s) { + * } + */ + + /** + * Sets the data for every row in the column. + */ + public void setColumnData(String colName, Object value) { + List list = this.getColumnAsObjectArray(colName); + while (list.size() < size()) { + list.add(value); + } + } + + public void setColumnData(int col, List data) { + reset(); + Iterator iter = data.iterator(); + String columnName = header.get(col); + while (iter.hasNext()) { + next(); + setColumnValue(columnName, iter.next()); + } + } + + /** + * Adds a header name to the Data object. + * + * @param s + * name of header. + */ + public void addHeader(String s) { + header.add(s); + data.put(s, new ArrayList(Math.max(size(), 100))); + } + + /** + * Sets a row of data using an array of strings as input. Each value in the + * array represents a column's value in that row. Assumes the order will be + * the same order in which the headers were added to the data set. + * + * @param line + * array of strings representing column values. + */ + public void setLine(String[] line) { + List tempList; + String[] h = getHeaders(); + for (int count = 0; count < h.length; count++) { + tempList = data.get(h[count]); + if (count < line.length && line[count].length() > 0) { + tempList.add(line[count]); + } else { + tempList.add("N/A"); + } + } + size++; + } + + /** + * Sets a row of data using an array of strings as input. Each value in the + * array represents a column's value in that row. Assumes the order will be + * the same order in which the headers were added to the data set. + * + * @param line + * array of strings representing column values. + * @param deflt + * default value to be placed in data if line is not as long as + * headers. + */ + public void setLine(String[] line, String deflt) { + List tempList; + String[] h = getHeaders(); + for (int count = 0; count < h.length; count++) { + tempList = data.get(h[count]); + if (count < line.length && line[count].length() > 0) { + tempList.add(line[count]); + } else { + tempList.add(deflt); + } + } + size++; + } + + /** + * Returns all the data in the Data set as an array of strings. Each array + * gives a row of data, each column separated by tabs. + * + * @return array of strings. + */ + public String[] getDataAsText() { + StringBuilder temp = new StringBuilder(""); + String[] line = new String[size + 1]; + String[] elements = getHeaders(); + for (int count = 0; count < elements.length; count++) { + temp.append(elements[count]); + if (count + 1 < elements.length) { + temp.append("\t"); + } + } + line[0] = temp.toString(); + reset(); + int index = 1; + while (next()) { + temp = new StringBuilder(""); + for (int count = 0; count < elements.length; count++) { + temp.append(getColumnValue(count)); + if (count + 1 < elements.length) { + temp.append("\t"); + } + } + line[index++] = temp.toString(); + } + return line; + } + + @Override + public String toString() { + String[] contents = getDataAsText(); + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (int x = 0; x < contents.length; x++) { + if (!first) { + sb.append("\n"); + } else { + first = false; + } + sb.append(contents[x]); + } + return sb.toString(); + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/collections/HashTree.java b/ApacheJmeter/src/org/apache/jorphan/collections/HashTree.java new file mode 100644 index 0000000..ed1be53 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/collections/HashTree.java @@ -0,0 +1,1073 @@ +/* + * 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.collections; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * This class is used to create a tree structure of objects. Each element in the + * tree is also a key to the next node down in the tree. It provides many ways + * to add objects and branches, as well as many ways to retrieve. + *

+ * HashTree implements the Map interface for convenience reasons. The main + * difference between a Map and a HashTree is that the HashTree organizes the + * data into a recursive tree structure, and provides the means to manipulate + * that structure. + *

+ * Of special interest is the {@link #traverse(HashTreeTraverser)} method, which + * provides an expedient way to traverse any HashTree by implementing the + * {@link HashTreeTraverser} interface in order to perform some operation on the + * tree, or to extract information from the tree. + * + * @see HashTreeTraverser + * @see SearchByClass + */ +public class HashTree implements Serializable, Map, Cloneable { + + private static final long serialVersionUID = 240L; + +// private static final Logger log = LoggingManager.getLoggerForClass(); + + // Used for the RuntimeException to short-circuit the traversal + private static final String FOUND = "found"; // $NON-NLS-1$ + + // N.B. The keys can be either JMeterTreeNode or TestElement + protected final Map data; + + /** + * Creates an empty new HashTree. + */ + public HashTree() { + data = new HashMap(); + } + + /** + * Allow subclasses to provide their own Map. + */ + protected HashTree(Map _map) { + data = _map; + } + + /** + * Creates a new HashTree and adds the given object as a top-level node. + * + * @param key + */ + public HashTree(Object key) { + data = new HashMap(); + data.put(key, new HashTree()); + } + + /** + * The Map given must also be a HashTree, otherwise an + * UnsupportedOperationException is thrown. If it is a HashTree, this is + * like calling the add(HashTree) method. + * + * @see #add(HashTree) + * @see java.util.Map#putAll(Map) + */ + public void putAll(Map map) { + if (map instanceof HashTree) { + this.add((HashTree) map); + } else { + throw new UnsupportedOperationException("can only putAll other HashTree objects"); + } + } + + /** + * Exists to satisfy the Map interface. + * + * @see java.util.Map#entrySet() + */ + public Set> entrySet() { + return data.entrySet(); + } + + /** + * Implemented as required by the Map interface, but is not very useful + * here. All 'values' in a HashTree are HashTree's themselves. + * + * @param value + * Object to be tested as a value. + * @return True if the HashTree contains the value, false otherwise. + * @see java.util.Map#containsValue(Object) + */ + public boolean containsValue(Object value) { + return data.containsValue(value); + } + + /** + * This is the same as calling HashTree.add(key,value). + * + * @param key + * to use + * @param value + * to store against key + * @see java.util.Map#put(Object, Object) + */ + public HashTree put(Object key, HashTree value) { + HashTree previous = data.get(key); + add(key, value); + return previous; + } + + /** + * Clears the HashTree of all contents. + * + * @see java.util.Map#clear() + */ + public void clear() { + data.clear(); + } + + /** + * Returns a collection of all the sub-trees of the current tree. + * + * @see java.util.Map#values() + */ + public Collection values() { + return data.values(); + } + + /** + * Adds a key as a node at the current level and then adds the given + * HashTree to that new node. + * + * @param key + * key to create in this tree + * @param subTree + * sub tree to add to the node created for the first argument. + */ + public void add(Object key, HashTree subTree) { + add(key); + getTree(key).add(subTree); + } + + /** + * Adds all the nodes and branches of the given tree to this tree. Is like + * merging two trees. Duplicates are ignored. + * + * @param newTree + */ + public void add(HashTree newTree) { + for (Object item : newTree.list()) { + add(item); + getTree(item).add(newTree.getTree(item)); + } + } + + /** + * Creates a new HashTree and adds all the objects in the given collection + * as top-level nodes in the tree. + * + * @param keys + * a collection of objects to be added to the created HashTree. + */ + public HashTree(Collection keys) { + data = new HashMap(); + for (Object o : keys) { + data.put(o, new HashTree()); + } + } + + /** + * Creates a new HashTree and adds all the objects in the given array as + * top-level nodes in the tree. + */ + public HashTree(Object[] keys) { + data = new HashMap(); + for (int x = 0; x < keys.length; x++) { + data.put(keys[x], new HashTree()); + } + } + + /** + * If the HashTree contains the given object as a key at the top level, then + * a true result is returned, otherwise false. + * + * @param o + * Object to be tested as a key. + * @return True if the HashTree contains the key, false otherwise. + * @see java.util.Map#containsKey(Object) + */ + public boolean containsKey(Object o) { + return data.containsKey(o); + } + + /** + * If the HashTree is empty, true is returned, false otherwise. + * + * @return True if HashTree is empty, false otherwise. + */ + public boolean isEmpty() { + return data.isEmpty(); + } + + /** + * Sets a key and it's value in the HashTree. It actually sets up a key, and + * then creates a node for the key and sets the value to the new node, as a + * key. Any previous nodes that existed under the given key are lost. + * + * @param key + * key to be set up + * @param value + * value to be set up as a key in the secondary node + */ + public void set(Object key, Object value) { + data.put(key, createNewTree(value)); + } + + /** + * Sets a key into the current tree and assigns it a HashTree as its + * subtree. Any previous entries under the given key are removed. + * + * @param key + * key to be set up + * @param t + * HashTree that the key maps to + */ + public void set(Object key, HashTree t) { + data.put(key, t); + } + + /** + * Sets a key and its values in the HashTree. It sets up a key in the + * current node, and then creates a node for that key, and sets all the + * values in the array as keys in the new node. Any keys previously held + * under the given key are lost. + * + * @param key + * Key to be set up + * @param values + * Array of objects to be added as keys in the secondary node + */ + public void set(Object key, Object[] values) { + data.put(key, createNewTree(Arrays.asList(values))); + } + + /** + * Sets a key and its values in the HashTree. It sets up a key in the + * current node, and then creates a node for that key, and set all the + * values in the array as keys in the new node. Any keys previously held + * under the given key are removed. + * + * @param key + * key to be set up + * @param values + * Collection of objects to be added as keys in the secondary + * node + */ + public void set(Object key, Collection values) { + data.put(key, createNewTree(values)); + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key array as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the array. + * Continues recursing in this manner until the end of the first array is + * reached, at which point all the values of the second array are set as + * keys to the bottom-most node. All previous keys of that bottom-most node + * are removed. + * + * @param treePath + * array of keys to put into HashTree + * @param values + * array of values to be added as keys to bottom-most node + */ + public void set(Object[] treePath, Object[] values) { + if (treePath != null && values != null) { + set(Arrays.asList(treePath), Arrays.asList(values)); + } + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key array as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the array. + * Continues recursing in this manner until the end of the first array is + * reached, at which point all the values of the Collection of values are + * set as keys to the bottom-most node. Any keys previously held by the + * bottom-most node are lost. + * + * @param treePath + * array of keys to put into HashTree + * @param values + * Collection of values to be added as keys to bottom-most node + */ + public void set(Object[] treePath, Collection values) { + if (treePath != null) { + set(Arrays.asList(treePath), values); + } + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key list as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the list. + * Continues recursing in this manner until the end of the first list is + * reached, at which point all the values of the array of values are set as + * keys to the bottom-most node. Any previously existing keys of that bottom + * node are removed. + * + * @param treePath + * collection of keys to put into HashTree + * @param values + * array of values to be added as keys to bottom-most node + */ + public void set(Collection treePath, Object[] values) { + HashTree tree = addTreePath(treePath); + tree.set(Arrays.asList(values)); + } + + /** + * Sets the nodes of the current tree to be the objects of the given + * collection. Any nodes previously in the tree are removed. + * + * @param values + * Collection of objects to set as nodes. + */ + public void set(Collection values) { + clear(); + this.add(values); + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key list as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the list. + * Continues recursing in this manner until the end of the first list is + * reached, at which point all the values of the Collection of values are + * set as keys to the bottom-most node. Any previously existing keys of that + * bottom node are lost. + * + * @param treePath + * list of keys to put into HashTree + * @param values + * collection of values to be added as keys to bottom-most node + */ + public void set(Collection treePath, Collection values) { + HashTree tree = addTreePath(treePath); + tree.set(values); + } + + /** + * Adds an key into the HashTree at the current level. + * + * @param key + * key to be added to HashTree + */ + public HashTree add(Object key) { + if (!data.containsKey(key)) { + HashTree newTree = createNewTree(); + data.put(key, newTree); + return newTree; + } + return getTree(key); + } + + /** + * Adds all the given objects as nodes at the current level. + * + * @param keys + * Array of Keys to be added to HashTree. + */ + public void add(Object[] keys) { + for (int x = 0; x < keys.length; x++) { + add(keys[x]); + } + } + + /** + * Adds a bunch of keys into the HashTree at the current level. + * + * @param keys + * Collection of Keys to be added to HashTree. + */ + public void add(Collection keys) { + for (Object o : keys) { + add(o); + } + } + + /** + * Adds a key and it's value in the HashTree. The first argument becomes a + * node at the current level, and the second argument becomes a node of it. + * + * @param key + * key to be added + * @param value + * value to be added as a key in the secondary node + */ + public HashTree add(Object key, Object value) { + add(key); + return getTree(key).add(value); + } + + /** + * Adds a key and it's values in the HashTree. The first argument becomes a + * node at the current level, and adds all the values in the array to the + * new node. + * + * @param key + * key to be added + * @param values + * array of objects to be added as keys in the secondary node + */ + public void add(Object key, Object[] values) { + add(key); + getTree(key).add(values); + } + + /** + * Adds a key as a node at the current level and then adds all the objects + * in the second argument as nodes of the new node. + * + * @param key + * key to be added + * @param values + * Collection of objects to be added as keys in the secondary + * node + */ + public void add(Object key, Collection values) { + add(key); + getTree(key).add(values); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is an array that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * an array of objects representing a path + * @param values + * array of values to be added as keys to bottom-most node + */ + public void add(Object[] treePath, Object[] values) { + if (treePath != null) { + add(Arrays.asList(treePath), Arrays.asList(values)); + } + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is an array that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * an array of objects representing a path + * @param values + * collection of values to be added as keys to bottom-most node + */ + public void add(Object[] treePath, Collection values) { + if (treePath != null) { + add(Arrays.asList(treePath), values); + } + } + + public HashTree add(Object[] treePath, Object value) { + return add(Arrays.asList(treePath), value); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a List that represents a path to a specific node in the tree. + * If the path doesn't already exist, it is created (the objects are added + * along the way). At the path, all the objects in the second argument are + * added as nodes. + * + * @param treePath + * a list of objects representing a path + * @param values + * array of values to be added as keys to bottom-most node + */ + public void add(Collection treePath, Object[] values) { + HashTree tree = addTreePath(treePath); + tree.add(Arrays.asList(values)); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a List that represents a path to a specific node in the tree. + * If the path doesn't already exist, it is created (the objects are added + * along the way). At the path, the object in the second argument is added + * as a node. + * + * @param treePath + * a list of objects representing a path + * @param value + * Object to add as a node to bottom-most node + */ + public HashTree add(Collection treePath, Object value) { + HashTree tree = addTreePath(treePath); + return tree.add(value); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a SortedSet that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * a SortedSet of objects representing a path + * @param values + * Collection of values to be added as keys to bottom-most node + */ + public void add(Collection treePath, Collection values) { + HashTree tree = addTreePath(treePath); + tree.add(values); + } + + protected HashTree addTreePath(Collection treePath) { + HashTree tree = this; + for (Object temp : treePath) { + tree.add(temp); + tree = tree.getTree(temp); + } + return tree; + } + + /** + * Gets the HashTree mapped to the given key. + * + * @param key + * Key used to find appropriate HashTree() + */ + public HashTree getTree(Object key) { + return data.get(key); + } + + /** + * Returns the HashTree object associated with the given key. Same as + * calling {@link #getTree(Object)}. + * + * @see java.util.Map#get(Object) + */ + public HashTree get(Object key) { + return getTree(key); + } + + /** + * Gets the HashTree object mapped to the last key in the array by recursing + * through the HashTree structure one key at a time. + * + * @param treePath + * array of keys. + * @return HashTree at the end of the recursion. + */ + public HashTree getTree(Object[] treePath) { + if (treePath != null) { + return getTree(Arrays.asList(treePath)); + } + return this; + } + + /** + * Create a clone of this HashTree. This is not a deep clone (ie, the + * contents of the tree are not cloned). + * + */ + @Override + public Object clone() { + HashTree newTree = new HashTree(); + cloneTree(newTree); + return newTree; + } + + protected void cloneTree(HashTree newTree) { + for (Object key : list()) { + newTree.set(key, (HashTree) getTree(key).clone()); + } + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @return HashTree + */ + protected HashTree createNewTree() { + return new HashTree(); + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @return HashTree + */ + protected HashTree createNewTree(Object key) { + return new HashTree(key); + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @return HashTree + */ + protected HashTree createNewTree(Collection values) { + return new HashTree(values); + } + + /** + * Gets the HashTree object mapped to the last key in the SortedSet by + * recursing through the HashTree structure one key at a time. + * + * @param treePath + * Collection of keys + * @return HashTree at the end of the recursion + */ + public HashTree getTree(Collection treePath) { + return getTreePath(treePath); + } + + /** + * Gets a Collection of all keys in the current HashTree node. If the + * HashTree represented a file system, this would be like getting a + * collection of all the files in the current folder. + * + * @return Set of all keys in this HashTree + */ + public Collection list() { + return data.keySet(); + } + + /** + * Gets a Set of all keys in the HashTree mapped to the given key of the + * current HashTree object (in other words, one level down. If the HashTree + * represented a file system, this would like getting a list of all files in + * a sub-directory (of the current directory) specified by the key argument. + * + * @param key + * key used to find HashTree to get list of + * @return Set of all keys in found HashTree. + */ + public Collection list(Object key) { + HashTree temp = data.get(key); + if (temp != null) { + return temp.list(); + } + return new HashTree().list(); + } + + /** + * Removes the entire branch specified by the given key. + * + * @see java.util.Map#remove(Object) + */ + public HashTree remove(Object key) { + return data.remove(key); + } + + /** + * Recurses down into the HashTree stucture using each subsequent key in the + * array of keys, and returns the Set of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * Array of keys used to recurse into HashTree structure + * @return Set of all keys found in end HashTree + */ + public Collection list(Object[] treePath) { // TODO not used? + if (treePath != null) { + return list(Arrays.asList(treePath)); + } + return list(); + } + + /** + * Recurses down into the HashTree stucture using each subsequent key in the + * List of keys, and returns the Set of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * List of keys used to recurse into HashTree structure + * @return Set of all keys found in end HashTree + */ + public Collection list(Collection treePath) { + HashTree tree = getTreePath(treePath); + if (tree != null) { + return tree.list(); + } + return new HashTree().list(); + } + + /** + * Finds the given current key, and replaces it with the given new key. Any + * tree structure found under the original key is moved to the new key. + */ + public void replace(Object currentKey, Object newKey) { + HashTree tree = getTree(currentKey); + data.remove(currentKey); + data.put(newKey, tree); + } + + /** + * Gets an array of all keys in the current HashTree node. If the HashTree + * represented a file system, this would be like getting an array of all the + * files in the current folder. + * + * @return array of all keys in this HashTree. + */ + public Object[] getArray() { + return data.keySet().toArray(); + } + + /** + * Gets an array of all keys in the HashTree mapped to the given key of the + * current HashTree object (in other words, one level down). If the HashTree + * represented a file system, this would like getting a list of all files in + * a sub-directory (of the current directory) specified by the key argument. + * + * @param key + * key used to find HashTree to get list of + * @return array of all keys in found HashTree + */ + public Object[] getArray(Object key) { + HashTree t = getTree(key); + if (t != null) { + return t.getArray(); + } + return null; + } + + /** + * Recurses down into the HashTree stucture using each subsequent key in the + * array of keys, and returns an array of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * array of keys used to recurse into HashTree structure + * @return array of all keys found in end HashTree + */ + public Object[] getArray(Object[] treePath) { + if (treePath != null) { + return getArray(Arrays.asList(treePath)); + } + return getArray(); + } + + /** + * Recurses down into the HashTree stucture using each subsequent key in the + * treePath argument, and returns an array of keys of the HashTree object at + * the end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * list of keys used to recurse into HashTree structure + * @return array of all keys found in end HashTree + */ + public Object[] getArray(Collection treePath) { + HashTree tree = getTreePath(treePath); + return (tree != null) ? tree.getArray() : null; + } + + protected HashTree getTreePath(Collection treePath) { + HashTree tree = this; + Iterator iter = treePath.iterator(); + while (iter.hasNext()) { + if (tree == null) { + return null; + } + Object temp = iter.next(); + tree = tree.getTree(temp); + } + return tree; + } + + /** + * Returns a hashcode for this HashTree. + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return data.hashCode() * 7; + } + + /** + * Compares all objects in the tree and verifies that the two trees contain + * the same objects at the same tree levels. Returns true if they do, false + * otherwise. + * + * @param o + * Object to be compared against + * @see java.lang.Object#equals(Object) + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof HashTree)) { + return false; + } + HashTree oo = (HashTree) o; + if (oo.size() != this.size()) { + return false; + } + return data.equals(oo.data); + + // boolean flag = true; + // if (o instanceof HashTree) + // { + // HashTree oo = (HashTree) o; + // Iterator it = data.keySet().iterator(); + // while (it.hasNext()) + // { + // if (!oo.containsKey(it.next())) + // { + // flag = false; + // break; + // } + // } + // if (flag) + // { + // it = data.keySet().iterator(); + // while (it.hasNext()) + // { + // Object temp = it.next(); + // flag = get(temp).equals(oo.get(temp)); + // if (!flag) + // { + // break; + // } + // } + // } + // } + // else + // { + // flag = false; + // } + // return flag; + } + + /** + * Returns a Set of all the keys in the top-level of this HashTree. + * + * @see java.util.Map#keySet() + */ + public Set keySet() { + return data.keySet(); + } + + /** + * Searches the HashTree structure for the given key. If it finds the key, + * it returns the HashTree mapped to the key. If it finds nothing, it + * returns null. + * + * @param key + * Key to search for + * @return HashTree mapped to key, if found, otherwise null + */ + public HashTree search(Object key) {// TODO does not appear to be used + HashTree result = getTree(key); + if (result != null) { + return result; + } + TreeSearcher searcher = new TreeSearcher(key); + try { + traverse(searcher); + } catch (RuntimeException e) { + if (!e.getMessage().equals(FOUND)){ + throw e; + } + // do nothing - means object is found + } + return searcher.getResult(); + } + + /** + * Method readObject. + */ + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + } + + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + } + + /** + * Returns the number of top-level entries in the HashTree. + * + * @see java.util.Map#size() + */ + public int size() { + return data.size(); + } + + /** + * Allows any implementation of the HashTreeTraverser interface to easily + * traverse (depth-first) all the nodes of the HashTree. The Traverser + * implementation will be given notification of each node visited. + * + * @see HashTreeTraverser + */ + public void traverse(HashTreeTraverser visitor) { + for (Object item : list()) { + visitor.addNode(item, getTree(item)); + getTree(item).traverseInto(visitor); + } + } + + /** + * The recursive method that accomplishes the tree-traversal and performs + * the callbacks to the HashTreeTraverser. + */ + private void traverseInto(HashTreeTraverser visitor) { + + if (list().size() == 0) { + visitor.processPath(); + } else { + for (Object item : list()) { + final HashTree treeItem = getTree(item); + visitor.addNode(item, treeItem); + treeItem.traverseInto(visitor); + } + } + visitor.subtractNode(); + } + + /** + * Generate a printable representation of the tree. + * + * @return a representation of the tree + */ + @Override + public String toString() { + ConvertToString converter = new ConvertToString(); + try { + traverse(converter); + } catch (Exception e) { // Just in case + converter.reportError(e); + } + return converter.toString(); + } + + private static class TreeSearcher implements HashTreeTraverser { + + private final Object target; + + private HashTree result; + + public TreeSearcher(Object t) { + target = t; + } + + public HashTree getResult() { + return result; + } + + /** {@inheritDoc} */ + public void addNode(Object node, HashTree subTree) { + result = subTree.getTree(target); + if (result != null) { + // short circuit traversal when found + throw new RuntimeException(FOUND); + } + } + + /** {@inheritDoc} */ + public void processPath() { + // Not used + } + + /** {@inheritDoc} */ + public void subtractNode() { + // Not used + } + } + + private static class ConvertToString implements HashTreeTraverser { + private final StringBuilder string = new StringBuilder(getClass().getName() + "{"); + + private final StringBuilder spaces = new StringBuilder(); + + private int depth = 0; + + public void addNode(Object key, HashTree subTree) { + depth++; + string.append("\n").append(getSpaces()).append(key); + string.append(" {"); + } + + public void subtractNode() { + string.append("\n" + getSpaces() + "}"); + depth--; + } + + public void processPath() { + } + + @Override + public String toString() { + string.append("\n}"); + return string.toString(); + } + + void reportError(Throwable t){ + string.append("Error: ").append(t.toString()); + } + + private String getSpaces() { + if (spaces.length() < depth * 2) { + while (spaces.length() < depth * 2) { + spaces.append(" "); + } + } else if (spaces.length() > depth * 2) { + spaces.setLength(depth * 2); + } + return spaces.toString(); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/collections/HashTreeTraverser.java b/ApacheJmeter/src/org/apache/jorphan/collections/HashTreeTraverser.java new file mode 100644 index 0000000..886a5ba --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/collections/HashTreeTraverser.java @@ -0,0 +1,76 @@ +/* + * 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.collections; + +/** + * By implementing this interface, a class can easily traverse a HashTree + * object, and be notified via callbacks of certain events. There are three such + * events: + *
    + *
  1. When a node is first encountered, the traverser's + * {@link #addNode(Object,HashTree)} method is called. It is handed the object + * at that node, and the entire sub-tree of the node.
  2. + *
  3. When a leaf node is encountered, the traverser is notified that a full + * path has been finished via the {@link #processPath()} method. It is the + * traversing class's responsibility to know the path that has just finished + * (this can be done by keeping a simple stack of all added nodes).
  4. + *
  5. When a node is retraced, the traverser's {@link #subtractNode()} is + * called. Again, it is the traverser's responsibility to know which node has + * been retraced.
  6. + *
+ * To summarize, as the traversal goes down a tree path, nodes are added. When + * the end of the path is reached, the {@link #processPath()} call is sent. As + * the traversal backs up, nodes are subtracted. + *

+ * The traversal is a depth-first traversal. + * + * @see HashTree + * @see SearchByClass + * + * @version $Revision: 674365 $ + */ +public interface HashTreeTraverser { + /** + * The tree traverses itself depth-first, calling addNode for each object it + * encounters as it goes. This is a callback method, and should not be + * called except by a HashTree during traversal. + * + * @param node + * the node currently encountered + * @param subTree + * the HashTree under the node encountered + */ + public void addNode(Object node, HashTree subTree); + + /** + * Indicates traversal has moved up a step, and the visitor should remove + * the top node from its stack structure. This is a callback method, and + * should not be called except by a HashTree during traversal. + */ + public void subtractNode(); + + /** + * Process path is called when a leaf is reached. If a visitor wishes to + * generate Lists of path elements to each leaf, it should keep a Stack data + * structure of nodes passed to it with addNode, and removing top items for + * every {@link #subtractNode()} call. This is a callback method, and should + * not be called except by a HashTree during traversal. + */ + public void processPath(); +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/collections/ListedHashTree.java b/ApacheJmeter/src/org/apache/jorphan/collections/ListedHashTree.java new file mode 100644 index 0000000..133b7ca --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/collections/ListedHashTree.java @@ -0,0 +1,270 @@ +/* + * 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.collections; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.apache.jorphan.util.JMeterError; + +/** + * ListedHashTree is a different implementation of the {@link HashTree} + * collection class. In the ListedHashTree, the order in which values are added + * is preserved (not to be confused with {@link SortedHashTree}, which sorts + * the order of the values using the compare() function). Any listing of nodes + * or iteration through the list of nodes of a ListedHashTree will be given in + * the order in which the nodes were added to the tree. + * + * @see HashTree + */ +public class ListedHashTree extends HashTree implements Serializable, Cloneable { + private static final long serialVersionUID = 240L; + + private final List order; + + public ListedHashTree() { + super(); + order = new LinkedList(); + } + + public ListedHashTree(Object key) { + this(); + data.put(key, new ListedHashTree()); + order.add(key); + } + + public ListedHashTree(Collection keys) { + this(); + for (Object temp : keys) { + data.put(temp, new ListedHashTree()); + order.add(temp); + } + } + + public ListedHashTree(Object[] keys) { + this(); + for (int x = 0; x < keys.length; x++) { + data.put(keys[x], new ListedHashTree()); + order.add(keys[x]); + } + } + + /** {@inheritDoc} */ + @Override + public Object clone() { + ListedHashTree newTree = new ListedHashTree(); + cloneTree(newTree); + return newTree; + } + + /** {@inheritDoc} */ + @Override + public void set(Object key, Object value) { + if (!data.containsKey(key)) { + order.add(key); + } + super.set(key, value); + } + + /** {@inheritDoc} */ + @Override + public void set(Object key, HashTree t) { + if (!data.containsKey(key)) { + order.add(key); + } + super.set(key, t); + } + + /** {@inheritDoc} */ + @Override + public void set(Object key, Object[] values) { + if (!data.containsKey(key)) { + order.add(key); + } + super.set(key, values); + } + + /** {@inheritDoc} */ + @Override + public void set(Object key, Collection values) { + if (!data.containsKey(key)) { + order.add(key); + } + super.set(key, values); + } + + /** {@inheritDoc} */ + @Override + public void replace(Object currentKey, Object newKey) { + HashTree tree = getTree(currentKey); + data.remove(currentKey); + data.put(newKey, tree); + // find order.indexOf(currentKey) using == rather than equals() + // there may be multiple entries which compare equals (Bug 50898) + // This will be slightly slower than the built-in method, + // but replace() is not used frequently. + int entry=-1; + for (int i=0; i < order.size(); i++) { + Object ent = order.get(i); + if (ent == currentKey) { + entry = i; + break; + } + } + if (entry == -1) { + throw new JMeterError("Impossible state, data key not present in order: "+currentKey.getClass()); + } + order.set(entry, newKey); + } + + /** {@inheritDoc} */ + @Override + public HashTree createNewTree() { + return new ListedHashTree(); + } + + /** {@inheritDoc} */ + @Override + public HashTree createNewTree(Object key) { + return new ListedHashTree(key); + } + + /** {@inheritDoc} */ + @Override + public HashTree createNewTree(Collection values) { + return new ListedHashTree(values); + } + + /** {@inheritDoc} */ + @Override + public HashTree add(Object key) { + if (!data.containsKey(key)) { + HashTree newTree = createNewTree(); + data.put(key, newTree); + order.add(key); + return newTree; + } + return getTree(key); + } + + /** {@inheritDoc} */ + @Override + public Collection list() { + return order; + } + + /** {@inheritDoc} */ + @Override + public HashTree remove(Object key) { + order.remove(key); + return data.remove(key); + } + + /** {@inheritDoc} */ + @Override + public Object[] getArray() { + return order.toArray(); + } + + /** {@inheritDoc} */ + // Make sure the hashCode depends on the order as well + @Override + public int hashCode() { + int hc = 17; + hc = hc * 37 + (order == null ? 0 : order.hashCode()); + hc = hc * 37 + super.hashCode(); + return hc; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + if (!(o instanceof ListedHashTree)) { + return false; + } + ListedHashTree lht = (ListedHashTree) o; + return (super.equals(lht) && order.equals(lht.order)); + + // boolean flag = true; + // if (o instanceof ListedHashTree) + // { + // ListedHashTree oo = (ListedHashTree) o; + // Iterator it = order.iterator(); + // Iterator it2 = oo.order.iterator(); + // if (size() != oo.size()) + // { + // flag = false; + // } + // while (it.hasNext() && it2.hasNext() && flag) + // { + // if (!it.next().equals(it2.next())) + // { + // flag = false; + // } + // } + // if (flag) + // { + // it = order.iterator(); + // while (it.hasNext() && flag) + // { + // Object temp = it.next(); + // flag = get(temp).equals(oo.get(temp)); + // } + // } + // } + // else + // { + // flag = false; + // } + // return flag; + } + + /** {@inheritDoc} */ + @Override + public Set keySet() { + return data.keySet(); + } + + /** {@inheritDoc} */ + @Override + public int size() { + return data.size(); + } + + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + } + + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + } + + /** {@inheritDoc} */ + @Override + public void clear() { + super.clear(); + order.clear(); + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/collections/SearchByClass.java b/ApacheJmeter/src/org/apache/jorphan/collections/SearchByClass.java new file mode 100644 index 0000000..622df7b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/collections/SearchByClass.java @@ -0,0 +1,111 @@ +/* + * 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.collections; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Useful for finding all nodes in the tree that represent objects of a + * particular type. For instance, if your tree contains all strings, and a few + * StringBuilder objects, you can use the SearchByClass traverser to find all the + * StringBuilder objects in your tree. + *

+ * Usage is simple. Given a {@link HashTree} object "tree", and a SearchByClass + * object: + * + *

+ * HashTree tree = new HashTree();
+ * // ... tree gets filled with objects
+ * SearchByClass searcher = new SearchByClass(StringBuilder.class);
+ * tree.traverse(searcher);
+ * Iterator iter = searcher.getSearchResults().iterator();
+ * while (iter.hasNext()) {
+ *  StringBuilder foundNode = (StringBuilder) iter.next();
+ *  HashTree subTreeOfFoundNode = searcher.getSubTree(foundNode);
+ *  //  .... do something with node and subTree...
+ * }
+ * 
+ * + * @see HashTree + * @see HashTreeTraverser + * + * @version $Revision: 882000 $ + */ +public class SearchByClass implements HashTreeTraverser { + private final List objectsOfClass = new LinkedList(); + + private final Map subTrees = new HashMap(); + + private final Class searchClass; + + /** + * Creates an instance of SearchByClass, and sets the Class to be searched + * for. + * + * @param searchClass + */ + public SearchByClass(Class searchClass) { + this.searchClass = searchClass; + } + + /** + * After traversing the HashTree, call this method to get a collection of + * the nodes that were found. + * + * @return Collection All found nodes of the requested type + */ + public Collection getSearchResults() { // TODO specify collection type without breaking callers + return objectsOfClass; + } + + /** + * Given a specific found node, this method will return the sub tree of that + * node. + * + * @param root + * the node for which the sub tree is requested + * @return HashTree + */ + public HashTree getSubTree(Object root) { + return subTrees.get(root); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + public void addNode(Object node, HashTree subTree) { + if (searchClass.isAssignableFrom(node.getClass())) { + objectsOfClass.add((T) node); + ListedHashTree tree = new ListedHashTree(node); + tree.set(node, subTree); + subTrees.put(node, tree); + } + } + + /** {@inheritDoc} */ + public void subtractNode() { + } + + /** {@inheritDoc} */ + public void processPath() { + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/collections/SortedHashTree.java b/ApacheJmeter/src/org/apache/jorphan/collections/SortedHashTree.java new file mode 100644 index 0000000..d902566 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/collections/SortedHashTree.java @@ -0,0 +1,109 @@ +/* + * 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.collections; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Comparator; +import java.util.TreeMap; + +/** + * SortedHashTree is a different implementation of the {@link HashTree} + * collection class. In the SortedHashTree, the ordering of values in the tree + * is made explicit via the compare() function of objects added to the tree. + * This works in exactly the same fashion as it does for a SortedSet. + * + * @see HashTree + * @see HashTreeTraverser + * + * TODO does not appear to be used currently + */ +public class SortedHashTree extends HashTree implements Serializable { + + private static final long serialVersionUID = 233L; + + public SortedHashTree() { + super(new TreeMap()); // equivalent to new TreeMap((Comparator)null); + } + + // non-null Comparators don't appear to be used at present + public SortedHashTree(Comparator comper) { + super(new TreeMap(comper)); + } + + public SortedHashTree(Object key) { + this(); + data.put(key, new SortedHashTree()); + } + + public SortedHashTree(Object key, Comparator comper) { + this(comper); + data.put(key, new SortedHashTree(comper)); + } + + public SortedHashTree(Collection keys) { + this(); + for (Object key : keys) { + data.put(key, new SortedHashTree()); + } + } + + public SortedHashTree(Collection keys, Comparator comper) { + this(comper); + for (Object key : keys) { + data.put(key, new SortedHashTree(comper)); + } + } + + public SortedHashTree(Object[] keys) { + this(); + for (int x = 0; x < keys.length; x++) { + data.put(keys[x], new SortedHashTree()); + } + } + + public SortedHashTree(Object[] keys, Comparator comper) { + this(comper); + for (int x = 0; x < keys.length; x++) { + data.put(keys[x], new SortedHashTree(comper)); + } + } + + /** {@inheritDoc} */ + @Override + protected HashTree createNewTree() { + Comparator comparator = ((TreeMap)data).comparator(); + return new SortedHashTree(comparator); + } + + /** {@inheritDoc} */ + @Override + protected HashTree createNewTree(Object key) { + Comparator comparator = ((TreeMap) data).comparator(); + return new SortedHashTree(key, comparator); + } + + /** {@inheritDoc} */ + @Override + protected HashTree createNewTree(Collection values) { + Comparator comparator = ((TreeMap)data).comparator(); + return new SortedHashTree(values, comparator); + } + +} diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/AbstractTreeTableModel.java b/ApacheJmeter/src/org/apache/jorphan/gui/AbstractTreeTableModel.java new file mode 100644 index 0000000..9f5e52d --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/AbstractTreeTableModel.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.swing.event.TableModelListener; +import javax.swing.event.EventListenerList; +import javax.swing.table.DefaultTableModel; +import javax.swing.tree.TreeNode; + +import org.apache.jorphan.reflect.Functor; + +public abstract class AbstractTreeTableModel extends DefaultTableModel implements TreeTableModel { + + private static final long serialVersionUID = 240L; + + protected final TreeNode rootNode; + protected final EventListenerList listener = new EventListenerList(); + + protected transient final ArrayList objects = new ArrayList(); + + protected transient final List headers = new ArrayList(); + + protected transient final ArrayList> classes = new ArrayList>(); + + protected transient final ArrayList readFunctors; + + protected transient final ArrayList writeFunctors; + + public AbstractTreeTableModel(TreeNode root) { + this.rootNode = root; + readFunctors = new ArrayList(); + writeFunctors = new ArrayList(); + } + + public AbstractTreeTableModel(TreeNode root, boolean editable) { + this(root); + } + + public AbstractTreeTableModel(String[] headers, + Functor[] readFunctors, + Functor[] writeFunctors, + Class[] editorClasses) { + this.rootNode = null; + this.headers.addAll(Arrays.asList(headers)); + this.classes.addAll(Arrays.asList(editorClasses)); + this.readFunctors = new ArrayList(Arrays.asList(readFunctors)); + this.writeFunctors = new ArrayList(Arrays.asList(writeFunctors)); + } + + /** + * The root node for the TreeTable + * @return the root node + */ + public Object getRootNode() { + return this.rootNode; + } + + /** + * {@inheritDoc} + */ + public Object getValueAt(Object node, int col) { + return null; + } + + /** + * {@inheritDoc} + */ + public boolean isCellEditable(Object node, int col) { + return false; + } + + /** + * {@inheritDoc} + */ + public void setValueAt(Object val, Object node, int column) { + } + + /** + * The implementation is exactly the same as ObjectTableModel.getColumnCount. + *

+ * {@inheritDoc} + */ + @Override + public int getColumnCount() { + return headers.size(); + } + + /** + * The implementation is exactly the same as ObjectTableModel.getRowCount. + *

+ * {@inheritDoc} + */ + @Override + public int getRowCount() { + if (objects == null) { + return 0; + } + return objects.size(); + } + + /** + * By default the abstract class returns true. It is up to subclasses + * to override the implementation. + *

+ * {@inheritDoc} + */ + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Class getColumnClass(int arg0) { + return classes.get(arg0); + } + + /** + * Subclasses need to implement the logic for the method and + * return the value at the specific cell. + *

+ * {@inheritDoc} + */ + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + + } + + /** + * {@inheritDoc} + */ + @Override + public String getColumnName(int columnIndex) { + return headers.get(columnIndex); + } + + public int getChildCount(Object parent) { + return 0; + } + + public Object getChild(Object parent, int index) { + return null; + } + + /** + * the implementation checks if the Object is a treenode. If it is, + * it returns isLeaf(), otherwise it returns false. + * @param node + * @return whether object is a leaf node or not + */ + public boolean isLeaf(Object node) { + if (node instanceof TreeNode) { + return ((TreeNode)node).isLeaf(); + } else { + return false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addTableModelListener(TableModelListener l) { + this.listener.add(TableModelListener.class,l); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeTableModelListener(TableModelListener l) { + this.listener.remove(TableModelListener.class,l); + } + + public void nodeStructureChanged(TreeNode node) { + + } + + public void fireTreeNodesChanged(TreeNode source, + Object[] path, + int[] indexes, + Object[] children) { + + } + + public void clearData() { + int size = getRowCount(); + objects.clear(); + super.fireTableRowsDeleted(0, size); + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/ComponentUtil.java b/ApacheJmeter/src/org/apache/jorphan/gui/ComponentUtil.java new file mode 100644 index 0000000..f71741f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/ComponentUtil.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import java.awt.Component; +import java.awt.Dimension; + +/** + * This class is a Util for awt Component and could be used to place them in + * center of an other. + * + * @version $Revision: 674365 $ + */ +public final class ComponentUtil { + /** + * Use this static method if you want to center and set its position + * compared to the size of the current users screen size. Valid percent is + * between +-(0-100) minus is treated as plus, bigger than 100 is always set + * to 100. + * + * @param component + * the component you want to center and set size on + * @param percentOfScreen + * the percent of the current screensize you want the component + * to be + */ + public static void centerComponentInWindow(Component component, int percentOfScreen) { + if (percentOfScreen < 0) { + centerComponentInWindow(component, -percentOfScreen); + return; + } + if (percentOfScreen > 100) { + centerComponentInWindow(component, 100); + return; + } + double percent = percentOfScreen / 100.d; + Dimension dimension = component.getToolkit().getScreenSize(); + component.setSize((int) (dimension.getWidth() * percent), (int) (dimension.getHeight() * percent)); + centerComponentInWindow(component); + } + + /** + * Use this static method if you want to center a component in Window. + * + * @param component + * the component you want to center in window + */ + public static void centerComponentInWindow(Component component) { + Dimension dimension = component.getToolkit().getScreenSize(); + + component.setLocation((int) ((dimension.getWidth() - component.getWidth()) / 2), + (int) ((dimension.getHeight() - component.getHeight()) / 2)); + component.validate(); + component.repaint(); + } + + /** + * Use this static method if you want to center a component over another + * component. + * + * @param parent + * the component you want to use to place it on + * @param toBeCentered + * the component you want to center + */ + public static void centerComponentInComponent(Component parent, Component toBeCentered) { + toBeCentered.setLocation(parent.getX() + (parent.getWidth() - toBeCentered.getWidth()) / 2, parent.getY() + + (parent.getHeight() - toBeCentered.getHeight()) / 2); + + toBeCentered.validate(); + toBeCentered.repaint(); + } + + /** + * Private constructor to prevent instantiation. + */ + private ComponentUtil() { + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/DefaultTreeTableModel.java b/ApacheJmeter/src/org/apache/jorphan/gui/DefaultTreeTableModel.java new file mode 100644 index 0000000..f78f4df --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/DefaultTreeTableModel.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; + +import org.apache.jorphan.reflect.Functor; + +public class DefaultTreeTableModel extends AbstractTreeTableModel { + + private static final long serialVersionUID = 240L; + + public DefaultTreeTableModel() { + this(new DefaultMutableTreeNode()); + } + + /** + * @param root + */ + public DefaultTreeTableModel(TreeNode root) { + super(root); + } + + /** + * @param root + * @param editable + */ + public DefaultTreeTableModel(TreeNode root, boolean editable) { + super(root, editable); + } + + /** + * @param headers + * @param readFunctors + * @param writeFunctors + * @param editorClasses + */ + public DefaultTreeTableModel(String[] headers, Functor[] readFunctors, + Functor[] writeFunctors, Class[] editorClasses) { + super(headers, readFunctors, writeFunctors, editorClasses); + } + +} diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/GuiUtils.java b/ApacheJmeter/src/org/apache/jorphan/gui/GuiUtils.java new file mode 100644 index 0000000..f004df9 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/GuiUtils.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import java.awt.Component; + +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; + +public class GuiUtils { + + /** + * Create a scroll panel that sets its preferred size to its minimum size. + * Explicitly for scroll panes that live inside other scroll panes, or + * within containers that stretch components to fill the area they exist in. + * Use this for any component you would put in a scroll pane (such as + * TextAreas, tables, JLists, etc). It is here for convenience and to avoid + * duplicate code. JMeter displays best if you follow this custom. + * + * @param comp + * the component which should be placed inside the scroll pane + * @return a JScrollPane containing the specified component + */ + public static JScrollPane makeScrollPane(Component comp) { + JScrollPane pane = new JScrollPane(comp); + pane.setPreferredSize(pane.getMinimumSize()); + return pane; + } + + /** + * Fix the size of a column according to the header text. + * + * @param column to be resized + * @param table containing the column + */ + public static void fixSize(TableColumn column, JTable table) { + TableCellRenderer rndr; + rndr = column.getHeaderRenderer(); + if (rndr == null){ + rndr = table.getTableHeader().getDefaultRenderer(); + } + Component c = rndr.getTableCellRendererComponent( + table, column.getHeaderValue(), false, false, -1, column.getModelIndex()); + int width = c.getPreferredSize().width+10; + column.setMaxWidth(width); + column.setPreferredWidth(width); + column.setResizable(false); + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledChoice.java b/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledChoice.java new file mode 100644 index 0000000..0553afd --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledChoice.java @@ -0,0 +1,303 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +public class JLabeledChoice extends JPanel implements JLabeledField { + private static final long serialVersionUID = 240L; + + private final JLabel mLabel = new JLabel(); + + private final JComboBox choiceList; + + // Maybe move to vector if MT problems occur + private final ArrayList mChangeListeners = new ArrayList(3); + + private JButton delete, add; + + /** + * Default constructor, The label and the Text field are left empty. + */ + public JLabeledChoice() { + super(); + choiceList = new JComboBox(); + init(); + } + + public JLabeledChoice(String pLabel, boolean editable) { + super(); + choiceList = new JComboBox(); + mLabel.setText(pLabel); + choiceList.setEditable(editable); + init(); + } + + /** + * Constructs a non-edittable combo-box with the label displaying the passed text. + * + * @param pLabel - the text to display in the label. + * @param items - the items to display in the Combo box + */ + public JLabeledChoice(String pLabel, String[] items) { + this(pLabel, items, false); + } + + /** + * Constructs a combo-box with the label displaying the passed text. + * + * @param pLabel - the text to display in the label. + * @param items - the items to display in the Combo box + * @param editable - if true, then Add and Delete buttons are created. + * + */ + public JLabeledChoice(String pLabel, String[] items, boolean editable) { + super(); + mLabel.setText(pLabel); + choiceList = new JComboBox(items); + choiceList.setEditable(editable); + init(); + } + + /** + * Get the label {@link JLabel} followed by the combo-box @link {@link JComboBox}. + */ + public List getComponentList() { + List comps = new LinkedList(); + comps.add(mLabel); + comps.add(choiceList); + return comps; + } + + public void setEditable(boolean editable) { + choiceList.setEditable(editable); + } + + public void addValue(String item) { + choiceList.addItem(item); + } + + public void setValues(String[] items) { + choiceList.removeAllItems(); + for (int i = 0; i < items.length; i++) { + choiceList.addItem(items[i]); + } + } + + /** + * Initialises all of the components on this panel. + */ + private void init() { + /* + * if(choiceList.isEditable()) { choiceList.addActionListener(new + * ComboListener()); } + */ + choiceList.setBorder(BorderFactory.createLoweredBevelBorder()); + // Register the handler for focus listening. This handler will + // only notify the registered when the text changes from when + // the focus is gained to when it is lost. + choiceList.addItemListener(new ItemListener() { + /** + * Callback method when the focus to the Text Field component is + * lost. + * + * @param e + * The focus event that occured. + */ + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + notifyChangeListeners(); + } + } + }); + + // Add the sub components + this.add(mLabel); + this.add(choiceList); + if (choiceList.isEditable()) { + add = new JButton("Add"); + add.setMargin(new Insets(1, 1, 1, 1)); + add.addActionListener(new AddListener()); + this.add(add); + delete = new JButton("Del"); + delete.setMargin(new Insets(1, 1, 1, 1)); + delete.addActionListener(new DeleteListener()); + this.add(delete); + } + + } + + /** + * Set the text displayed in the label. + * + * @param pLabel + * The new label text. + */ + public void setLabel(String pLabel) { + mLabel.setText(pLabel); + } + + /** + * Set the text displayed in the Text Field. + * + * @param pText + * The new text to display in the text field. + */ + public void setText(String pText) { + choiceList.setSelectedItem(pText); + } + + public void setSelectedIndex(int index){ + choiceList.setSelectedIndex(index); + } + /** + * Returns the text in the Text Field. + * + * @return The text in the Text Field. Never returns null. + */ + public String getText() { + Object item = choiceList.getSelectedItem(); + if (item == null) { + return ""; + } else { + return (String) item; + } + } + + public int getSelectedIndex(){ + return choiceList.getSelectedIndex(); + } + + public Object[] getSelectedItems() { + return choiceList.getSelectedObjects(); + } + + public String[] getItems() { + String[] items = new String[choiceList.getItemCount()]; + for (int i = 0; i < items.length; i++) { + items[i] = (String) choiceList.getItemAt(i); + } + return items; + } + + /** + * Returns the text of the label. + * + * @return The text of the label. + */ + public String getLabel() { + return mLabel.getText(); + } + + /** + * Registers the text to display in a tool tip. + * The text displays when the cursor lingers over the component. + * @param text the string to display; if the text is null, + * the tool tip is turned off for this component + */ + @Override +public void setToolTipText(String text) { + choiceList.setToolTipText(text); + } + + /** + * Returns the tooltip string that has been set with setToolTipText + * @return the text of the tool tip + */ + @Override +public String getToolTipText() { + if (choiceList == null){ // Necessary to avoid NPE when testing serialisation + return null; + } + return choiceList.getToolTipText(); + } + + /** + * Adds a change listener, that will be notified when the text in the text + * field is changed. The ChangeEvent that will be passed to registered + * listeners will contain this object as the source, allowing the new text + * to be extracted using the {@link #getText() getText} method. + * + * @param pChangeListener + * The listener to add + */ + public void addChangeListener(ChangeListener pChangeListener) { + mChangeListeners.add(pChangeListener); + } + + /** + * Removes a change listener. + * + * @param pChangeListener + * The change listener to remove. + */ + public void removeChangeListener(ChangeListener pChangeListener) { + mChangeListeners.remove(pChangeListener); + } + + /** + * Notify all registered change listeners that the text in the text field + * has changed. + */ + private void notifyChangeListeners() { + ChangeEvent ce = new ChangeEvent(this); + for (int index = 0; index < mChangeListeners.size(); index++) { + mChangeListeners.get(index).stateChanged(ce); + } + } + + private class AddListener implements ActionListener { + + public void actionPerformed(ActionEvent e) { + Object item = choiceList.getSelectedItem(); + int index = choiceList.getSelectedIndex(); + if (!item.equals(choiceList.getItemAt(index))) { + choiceList.addItem(item); + } + choiceList.setSelectedItem(item); + notifyChangeListeners(); + } + } + + private class DeleteListener implements ActionListener { + + public void actionPerformed(ActionEvent e) { + if (choiceList.getItemCount() > 1) { + choiceList.removeItemAt(choiceList.getSelectedIndex()); + notifyChangeListeners(); + } + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledField.java b/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledField.java new file mode 100644 index 0000000..9400599 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledField.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.gui; + +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.event.ChangeListener; + +/** + * @version $Revision: 804518 $ + */ +public interface JLabeledField { + public String getText(); + + public void setText(String text); + + public void setLabel(String pLabel); + + public void addChangeListener(ChangeListener pChangeListener); + + public List getComponentList(); +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledPasswordField.java b/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledPasswordField.java new file mode 100644 index 0000000..68708b6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledPasswordField.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +/** + * @version $Revision: 905024 $ + */ +public class JLabeledPasswordField extends JLabeledTextField { + + private static final long serialVersionUID = 240L; + + public JLabeledPasswordField() { + super(); + } + + /** + * Constructs a new component with the label displaying the passed text. + * + * @param pLabel + * The text to in the label. + */ + public JLabeledPasswordField(String pLabel) { + super(pLabel); + } + + @Override + protected JTextField createTextField(int size) { + return new JPasswordField(size); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledTextArea.java b/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledTextArea.java new file mode 100644 index 0000000..fe5186b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledTextArea.java @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import java.awt.BorderLayout; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; + +/** + * A Helper component that wraps a JTextField with a label into a JPanel (this). + * This component also has an efficient event handling mechanism for handling + * the text changing in the Text Field. The registered change listeners are only + * called when the text has changed. + * + */ +public class JLabeledTextArea extends JPanel implements JLabeledField, FocusListener { + private static final long serialVersionUID = 240L; + + private JLabel mLabel; + + private JTextArea mTextArea; + + // Maybe move to vector if MT problems occur + private final ArrayList mChangeListeners = new ArrayList(3); + + // A temporary cache for the focus listener + private String oldValue = ""; + + /** + * Default constructor, The label and the Text field are left empty. + */ + public JLabeledTextArea() { + this("", null); + } + + /** + * Constructs a new component with the label displaying the passed text. + * + * @param label + * The text to display in the label. + */ + public JLabeledTextArea(String label) { + this(label, null); + } + + /** + * Constructs a new component with the label displaying the passed text. + * + * @param pLabel + * The text to display in the label. + * @param docModel the document for the text area + */ + public JLabeledTextArea(String pLabel, Document docModel) { + super(); + mLabel = new JLabel(pLabel); + init(); + if (docModel != null) { + mTextArea.setDocument(docModel); + } + } + + /** + * Get the label {@link JLabel} followed by the text field @link {@link JTextArea}. + */ + public List getComponentList() { + List comps = new LinkedList(); + comps.add(mLabel); + comps.add(mTextArea); + return comps; + } + + public void setDocumentModel(Document docModel) { + mTextArea.setDocument(docModel); + } + + /** + * Initialises all of the components on this panel. + */ + private void init() { + setLayout(new BorderLayout()); + + mTextArea = new JTextArea(); + mTextArea.setRows(4); + mTextArea.setLineWrap(true); + mTextArea.setWrapStyleWord(true); + // Register the handler for focus listening. This handler will + // only notify the registered when the text changes from when + // the focus is gained to when it is lost. + mTextArea.addFocusListener(this); + + // Add the sub components + this.add(mLabel, BorderLayout.NORTH); + this.add(new JScrollPane(mTextArea), BorderLayout.CENTER); + } + + /** + * Callback method when the focus to the Text Field component is lost. + * + * @param pFocusEvent + * The focus event that occured. + */ + public void focusLost(FocusEvent pFocusEvent) { + // Compare if the value has changed, since we received focus. + if (!oldValue.equals(mTextArea.getText())) { + notifyChangeListeners(); + } + } + + /** + * Catch what the value was when focus was gained. + */ + public void focusGained(FocusEvent pFocusEvent) { + oldValue = mTextArea.getText(); + } + + /** + * Set the text displayed in the label. + * + * @param pLabel + * The new label text. + */ + public void setLabel(String pLabel) { + mLabel.setText(pLabel); + } + + /** + * Set the text displayed in the Text Field. + * + * @param pText + * The new text to display in the text field. + */ + public void setText(String pText) { + mTextArea.setText(pText); + } + + /** + * Returns the text in the Text Field. + * + * @return The text in the Text Field. + */ + public String getText() { + return mTextArea.getText(); + } + + /** + * Returns the text of the label. + * + * @return The text of the label. + */ + public String getLabel() { + return mLabel.getText(); + } + + /** {@inheritDoc} */ + @Override + public void setEnabled(boolean enable) { + super.setEnabled(enable); + mTextArea.setEnabled(enable); + } + + /** + * Registers the text to display in a tool tip. + * The text displays when the cursor lingers over the component. + * @param text the string to display; if the text is null, + * the tool tip is turned off for this component + */ + @Override + public void setToolTipText(String text) { + mTextArea.setToolTipText(text); + } + + /** + * Returns the tooltip string that has been set with setToolTipText + * @return the text of the tool tip + */ + @Override + public String getToolTipText() { + return mTextArea.getToolTipText(); + } + + /** + * Adds a change listener, that will be notified when the text in the text + * field is changed. The ChangeEvent that will be passed to registered + * listeners will contain this object as the source, allowing the new text + * to be extracted using the {@link #getText() getText} method. + * + * @param pChangeListener + * The listener to add + */ + public void addChangeListener(ChangeListener pChangeListener) { + mChangeListeners.add(pChangeListener); + } + + /** + * Removes a change listener. + * + * @param pChangeListener + * The change listener to remove. + */ + public void removeChangeListener(ChangeListener pChangeListener) { + mChangeListeners.remove(pChangeListener); + } + + /** + * Notify all registered change listeners that the text in the text field + * has changed. + */ + private void notifyChangeListeners() { + ChangeEvent ce = new ChangeEvent(this); + for (int index = 0; index < mChangeListeners.size(); index++) { + mChangeListeners.get(index).stateChanged(ce); + } + } + + public String[] getTextLines() { + int numLines = mTextArea.getLineCount(); + String[] lines = new String[numLines]; + for(int i = 0; i < numLines; i++) { + try { + int start = mTextArea.getLineStartOffset(i); + int end = mTextArea.getLineEndOffset(i); // treats last line specially + if (i == numLines-1) { // Last line + end++; // Allow for missing terminator + } + lines[i]=mTextArea.getText(start, end-start-1); + } catch (BadLocationException e) { // should not happen + throw new IllegalStateException("Could not read line "+i,e); + } + } + return lines; + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledTextField.java b/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledTextField.java new file mode 100644 index 0000000..9fc05a3 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/JLabeledTextField.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +/** + * A Helper component that wraps a JTextField with a label into a JPanel (this). + * This component also has an efficient event handling mechanism for handling + * the text changing in the Text Field. The registered change listeners are only + * called when the text has changed. + * + * @version $Revision: 1174303 $ + */ +public class JLabeledTextField extends JPanel implements JLabeledField, FocusListener { + private static final long serialVersionUID = 240L; + + private JLabel mLabel; + + private JTextField mTextField; + + // Maybe move to vector if MT problems occur + private final ArrayList mChangeListeners = new ArrayList(3); + + // A temporary cache for the focus listener + private String oldValue = ""; + + /** + * Default constructor, The label and the Text field are left empty. + */ + public JLabeledTextField() { + this("", 20); + } + + /** + * Constructs a new component with the label displaying the passed text. + * + * @param pLabel + * The text to in the label. + */ + public JLabeledTextField(String pLabel) { + this(pLabel, 20); + } + + public JLabeledTextField(String pLabel, int size) { + super(); + mTextField = createTextField(size); + mLabel = new JLabel(pLabel); + mLabel.setLabelFor(mTextField); + init(); + } + + public JLabeledTextField(String pLabel, Color bk) { + super(); + mTextField = createTextField(20); + mLabel = new JLabel(pLabel); + mLabel.setBackground(bk); + mLabel.setLabelFor(mTextField); + this.setBackground(bk); + init(); + } + + /** + * Get the label {@link JLabel} followed by the text field @link {@link JTextField}. + */ + public List getComponentList() { + List comps = new LinkedList(); + comps.add(mLabel); + comps.add(mTextField); + return comps; + } + + /** {@inheritDoc} */ + @Override + public void setEnabled(boolean enable) { + super.setEnabled(enable); + mTextField.setEnabled(enable); + } + + protected JTextField createTextField(int size) { + return new JTextField(size); + } + + /** + * Initialises all of the components on this panel. + */ + private void init() { + setLayout(new BorderLayout(5, 0)); + // Register the handler for focus listening. This handler will + // only notify the registered when the text changes from when + // the focus is gained to when it is lost. + mTextField.addFocusListener(this); + + // Add the sub components + add(mLabel, BorderLayout.WEST); + add(mTextField, BorderLayout.CENTER); + } + + /** + * Callback method when the focus to the Text Field component is lost. + * + * @param pFocusEvent + * The focus event that occured. + */ + public void focusLost(FocusEvent pFocusEvent) { + // Compare if the value has changed, since we received focus. + if (!oldValue.equals(mTextField.getText())) { + notifyChangeListeners(); + } + } + + /** + * Catch what the value was when focus was gained. + */ + public void focusGained(FocusEvent pFocusEvent) { + oldValue = mTextField.getText(); + } + + /** + * Set the text displayed in the label. + * + * @param pLabel + * The new label text. + */ + public void setLabel(String pLabel) { + mLabel.setText(pLabel); + } + + /** + * Set the text displayed in the Text Field. + * + * @param pText + * The new text to display in the text field. + */ + public void setText(String pText) { + mTextField.setText(pText); + } + + /** + * Returns the text in the Text Field. + * + * @return The text in the Text Field. + */ + public String getText() { + return mTextField.getText(); + } + + /** + * Returns the text of the label. + * + * @return The text of the label. + */ + public String getLabel() { + return mLabel.getText(); + } + + /** + * Registers the text to display in a tool tip. + * The text displays when the cursor lingers over the component. + * @param text the string to display; if the text is null, + * the tool tip is turned off for this component + */ + @Override + public void setToolTipText(String text) { + mLabel.setToolTipText(text); + mTextField.setToolTipText(text); + } + + /** + * Returns the tooltip string that has been set with setToolTipText + * @return the text of the tool tip + */ + @Override + public String getToolTipText() { + if (mTextField == null){ // Necessary to avoid NPE when testing serialisation + return null; + } + return mTextField.getToolTipText(); + } + + /** + * Adds a change listener, that will be notified when the text in the text + * field is changed. The ChangeEvent that will be passed to registered + * listeners will contain this object as the source, allowing the new text + * to be extracted using the {@link #getText() getText} method. + * + * @param pChangeListener + * The listener to add + */ + public void addChangeListener(ChangeListener pChangeListener) { + mChangeListeners.add(pChangeListener); + } + + /** + * Removes a change listener. + * + * @param pChangeListener + * The change listener to remove. + */ + public void removeChangeListener(ChangeListener pChangeListener) { + mChangeListeners.remove(pChangeListener); + } + + /** + * Notify all registered change listeners that the text in the text field + * has changed. + */ + protected void notifyChangeListeners() { + ChangeEvent ce = new ChangeEvent(this); + for (int index = 0; index < mChangeListeners.size(); index++) { + mChangeListeners.get(index).stateChanged(ce); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/JTreeTable.java b/ApacheJmeter/src/org/apache/jorphan/gui/JTreeTable.java new file mode 100644 index 0000000..c6cb659 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/JTreeTable.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import java.util.Vector; + +import javax.swing.JTable; + +public class JTreeTable extends JTable { + + private static final long serialVersionUID = 240L; + + /** + * The default implementation will use DefaultTreeTableModel + */ + public JTreeTable() { + super(new DefaultTreeTableModel()); + } + + /** + * @param numRows + * @param numColumns + */ + public JTreeTable(int numRows, int numColumns) { + super(numRows, numColumns); + } + + /** + * @param dm + */ + public JTreeTable(TreeTableModel dm) { + super(dm); + } + + /** + * @param rowData + * @param columnNames + */ + public JTreeTable(Object[][] rowData, Object[] columnNames) { + super(rowData, columnNames); + } + + /** + * @param rowData + * @param columnNames + */ + public JTreeTable(Vector rowData, Vector columnNames) { + super(rowData, columnNames); + } + +} diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/NumberRenderer.java b/ApacheJmeter/src/org/apache/jorphan/gui/NumberRenderer.java new file mode 100644 index 0000000..1fedf1e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/NumberRenderer.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.gui; + +import java.text.DecimalFormat; +import java.text.NumberFormat; + +import javax.swing.JLabel; +import javax.swing.table.DefaultTableCellRenderer; + +/** + * Renders numbers in a JTable with a specified format + */ +public class NumberRenderer extends DefaultTableCellRenderer { + private static final long serialVersionUID = 240L; + + protected final NumberFormat formatter; + + public NumberRenderer() { + super(); + formatter = NumberFormat.getInstance(); + setHorizontalAlignment(JLabel.RIGHT); + } + + public NumberRenderer(String format) { + super(); + formatter = new DecimalFormat(format); + setHorizontalAlignment(JLabel.RIGHT); + } + + @Override + public void setValue(Object value) { + setText((value == null) ? "" : formatter.format(value)); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/ObjectTableModel.java b/ApacheJmeter/src/org/apache/jorphan/gui/ObjectTableModel.java new file mode 100644 index 0000000..ff59987 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/ObjectTableModel.java @@ -0,0 +1,306 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import javax.swing.event.TableModelEvent; +import javax.swing.table.DefaultTableModel; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.Functor; +import org.apache.log.Logger; + +/** + * The ObjectTableModel is a TableModel whose rows are objects; + * columns are defined as Functors on the object. + */ +public class ObjectTableModel extends DefaultTableModel { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final long serialVersionUID = 240L; + + private transient ArrayList objects = new ArrayList(); + + private transient List headers = new ArrayList(); + + private transient ArrayList> classes = new ArrayList>(); + + private transient ArrayList readFunctors = new ArrayList(); + + private transient ArrayList writeFunctors = new ArrayList(); + + private transient Class objectClass = null; // if provided + + private transient boolean cellEditable = true; + + /** + * The ObjectTableModel is a TableModel whose rows are objects; + * columns are defined as Functors on the object. + * + * @param headers - Column names + * @param _objClass - Object class that will be used + * @param readFunctors - used to get the values + * @param writeFunctors - used to set the values + * @param editorClasses - class for each column + */ + public ObjectTableModel(String[] headers, Class _objClass, Functor[] readFunctors, Functor[] writeFunctors, Class[] editorClasses) { + this(headers, readFunctors, writeFunctors, editorClasses); + this.objectClass=_objClass; + } + + /** + * The ObjectTableModel is a TableModel whose rows are objects; + * columns are defined as Functors on the object. + * + * @param headers - Column names + * @param _objClass - Object class that will be used + * @param readFunctors - used to get the values + * @param writeFunctors - used to set the values + * @param editorClasses - class for each column + * @param cellEditable - if cell must editable (false to allow double click on cell) + */ + public ObjectTableModel(String[] headers, Class _objClass, Functor[] readFunctors, + Functor[] writeFunctors, Class[] editorClasses, boolean cellEditable) { + this(headers, readFunctors, writeFunctors, editorClasses); + this.objectClass=_objClass; + this.cellEditable = cellEditable; + } + + /** + * The ObjectTableModel is a TableModel whose rows are objects; + * columns are defined as Functors on the object. + * + * @param headers - Column names + * @param readFunctors - used to get the values + * @param writeFunctors - used to set the values + * @param editorClasses - class for each column + */ + public ObjectTableModel(String[] headers, Functor[] readFunctors, Functor[] writeFunctors, Class[] editorClasses) { + this.headers.addAll(Arrays.asList(headers)); + this.classes.addAll(Arrays.asList(editorClasses)); + this.readFunctors = new ArrayList(Arrays.asList(readFunctors)); + this.writeFunctors = new ArrayList(Arrays.asList(writeFunctors)); + + int numHeaders = headers.length; + + int numClasses = classes.size(); + if (numClasses != numHeaders){ + log.warn("Header count="+numHeaders+" but classes count="+numClasses); + } + + // Functor count = 0 is handled specially + int numWrite = writeFunctors.length; + if (numWrite > 0 && numWrite != numHeaders){ + log.warn("Header count="+numHeaders+" but writeFunctor count="+numWrite); + } + + int numRead = readFunctors.length; + if (numRead > 0 && numRead != numHeaders){ + log.warn("Header count="+numHeaders+" but readFunctor count="+numRead); + } + } + + private Object readResolve() { + objects = new ArrayList(); + headers = new ArrayList(); + classes = new ArrayList>(); + readFunctors = new ArrayList(); + writeFunctors = new ArrayList(); + return this; + } + + public Iterator iterator() { + return objects.iterator(); + } + + public void clearData() { + int size = getRowCount(); + objects.clear(); + super.fireTableRowsDeleted(0, size); + } + + public void addRow(Object value) { + log.debug("Adding row value: " + value); + if (objectClass != null) { + final Class valueClass = value.getClass(); + if (!objectClass.isAssignableFrom(valueClass)){ + throw new IllegalArgumentException("Trying to add class: "+valueClass.getName() + +"; expecting class: "+objectClass.getName()); + } + } + objects.add(value); + super.fireTableRowsInserted(objects.size() - 1, objects.size()); + } + + public void insertRow(Object value, int index) { + objects.add(index, value); + super.fireTableRowsInserted(index, index + 1); + } + + /** {@inheritDoc} */ + @Override + public int getColumnCount() { + return headers.size(); + } + + /** {@inheritDoc} */ + @Override + public String getColumnName(int col) { + return headers.get(col); + } + + /** {@inheritDoc} */ + @Override + public int getRowCount() { + if (objects == null) { + return 0; + } + return objects.size(); + } + + /** {@inheritDoc} */ + @Override + public Object getValueAt(int row, int col) { + log.debug("Getting row value"); + Object value = objects.get(row); + if(headers.size() == 1 && col >= readFunctors.size()) { + return value; + } + Functor getMethod = readFunctors.get(col); + if (getMethod != null && value != null) { + return getMethod.invoke(value); + } + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean isCellEditable(int arg0, int arg1) { + return cellEditable; + } + + /** {@inheritDoc} */ + @Override + public void moveRow(int start, int end, int to) { + List subList = new ArrayList(objects.subList(start, end)); + for (int x = end - 1; x >= start; x--) { + objects.remove(x); + } + objects.addAll(to, subList); + super.fireTableChanged(new TableModelEvent(this)); + } + + /** {@inheritDoc} */ + @Override + public void removeRow(int row) { + objects.remove(row); + super.fireTableRowsDeleted(row, row); + } + + /** {@inheritDoc} */ + @Override + public void setValueAt(Object cellValue, int row, int col) { + if (row < objects.size()) { + Object value = objects.get(row); + if (col < writeFunctors.size()) { + Functor setMethod = writeFunctors.get(col); + if (setMethod != null) { + setMethod.invoke(value, new Object[] { cellValue }); + super.fireTableDataChanged(); + } + } + else if(headers.size() == 1) + { + objects.set(row,cellValue); + } + } + } + + /** {@inheritDoc} */ + @Override + public Class getColumnClass(int arg0) { + return classes.get(arg0); + } + + /** + * Check all registered functors. + *

+ * ** only for use in unit test code ** + *

+ * + * @param _value - an instance of the table model row data item + * (if null, use the class passed to the constructor). + * + * @param caller - class of caller. + * + * @return false if at least one Functor cannot be found. + */ + @SuppressWarnings("deprecation") + public boolean checkFunctors(Object _value, Class caller){ + Object value; + if (_value == null && objectClass != null) { + try { + value = objectClass.newInstance(); + } catch (InstantiationException e) { + log.error("Cannot create instance of class "+objectClass.getName(),e); + return false; + } catch (IllegalAccessException e) { + log.error("Cannot create instance of class "+objectClass.getName(),e); + return false; + } + } else { + value = _value; + } + boolean status = true; + for(int i=0;i rows) { // used by TableEditor + clearData(); + for(Object val : rows) + { + addRow(val); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/RateRenderer.java b/ApacheJmeter/src/org/apache/jorphan/gui/RateRenderer.java new file mode 100644 index 0000000..52ddad7 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/RateRenderer.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +/** + * Renders a rate in a JTable. + * + * The output is in units appropriate to its dimension: + *

+ * The number is represented in one of: + * - requests/second + * - requests/minute + * - requests/hour. + *

+ * Examples: "34.2/sec" "0.1/sec" "43.0/hour" "15.9/min" + */ +public class RateRenderer extends NumberRenderer{ + + private static final long serialVersionUID = 240L; + + public RateRenderer(String format) { + super(format); + } + + @Override + public void setValue(Object value) { + if (value == null || ! (value instanceof Double)) { + setText("#N/A"); // TODO: should this just call super()? + return; + } + double rate = ((Double) value).doubleValue(); + if (rate == Double.MAX_VALUE){ + setText("#N/A"); // TODO: should this just call super()? + return; + } + + String unit = "sec"; + + if (rate < 1.0) { + rate *= 60.0; + unit = "min"; + } + if (rate < 1.0) { + rate *= 60.0; + unit = "hour"; + } + setText(formatter.format(rate) + "/" + unit); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/RendererUtils.java b/ApacheJmeter/src/org/apache/jorphan/gui/RendererUtils.java new file mode 100644 index 0000000..0a9066a --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/RendererUtils.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumnModel; + +/** + * Utility class for Renderers + */ +public class RendererUtils { + private RendererUtils(){ + // uninstantiable + } + public static void applyRenderers(final JTable table, final TableCellRenderer [] renderers){ + final TableColumnModel columnModel = table.getColumnModel(); + for(int i = 0; i < renderers.length; i++){ + final TableCellRenderer rend = renderers[i]; + if (rend != null) { + columnModel.getColumn(i).setCellRenderer(rend); + } + } +} +} diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/RightAlignRenderer.java b/ApacheJmeter/src/org/apache/jorphan/gui/RightAlignRenderer.java new file mode 100644 index 0000000..2c09727 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/RightAlignRenderer.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import javax.swing.JLabel; +import javax.swing.table.DefaultTableCellRenderer; + +/** + * Renders items in a JTable right-aligned + */ +public class RightAlignRenderer extends DefaultTableCellRenderer { + private static final long serialVersionUID = 240L; + + public RightAlignRenderer() { + super(); + setHorizontalAlignment(JLabel.RIGHT); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/TreeTableModel.java b/ApacheJmeter/src/org/apache/jorphan/gui/TreeTableModel.java new file mode 100644 index 0000000..a8993e6 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/TreeTableModel.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui; + +import javax.swing.table.TableModel; + +/** + * + * This is a basic interface for TreeTableModel that extends TableModel. + * It's pretty minimal and isn't as full featured at other implementations. + */ +public interface TreeTableModel extends TableModel { + + /** + * The method is similar to getValueAt(int,int). Instead of int, + * the row is an object. + * @param node + * @param col + * @return the value at the column + */ + public Object getValueAt(Object node, int col); + + /** + * the method is similar to isCellEditable(int,int). Instead of int, + * the row is an object. + * @param node + * @param col + * @return if cell is editable + */ + public boolean isCellEditable(Object node, int col); + + /** + * the method is similar to isCellEditable(int,int). Instead of int, + * the row is an object. + * @param val + * @param node + * @param column + */ + public void setValueAt(Object val, Object node, int column); +} diff --git a/ApacheJmeter/src/org/apache/jorphan/gui/layout/VerticalLayout.java b/ApacheJmeter/src/org/apache/jorphan/gui/layout/VerticalLayout.java new file mode 100644 index 0000000..50eb6ee --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/gui/layout/VerticalLayout.java @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.gui.layout; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.io.Serializable; + +/** + * A vertical layout manager similar to java.awt.FlowLayout. Like FlowLayout + * components do not expand to fill available space except when the horizontal + * alignment is BOTH in which case components are stretched + * horizontally. Unlike FlowLayout, components will not wrap to form another + * column if there isn't enough space vertically. VerticalLayout can optionally + * anchor components to the top or bottom of the display area or center them + * between the top and bottom. Revision date 04 April 1999 + * + * @version $Revision: 905024 $ + */ +public class VerticalLayout implements LayoutManager, Serializable { + private static final long serialVersionUID = 240L; + + /** + * The horizontal alignment constant that designates centering. Also used to + * designate center anchoring. + */ + public final static int CENTER = 0; + + /** + * The horizontal alignment constant that designates right justification. + */ + public final static int RIGHT = 1; + + /** + * The horizontal alignment constant that designates left justification. + */ + public final static int LEFT = 2; + + /** + * The horizontal alignment constant that designates stretching the + * component horizontally. + */ + public final static int BOTH = 3; + + /** + * The anchoring constant that designates anchoring to the top of the + * display area. + */ + public final static int TOP = 1; + + /** + * The anchoring constant that designates anchoring to the bottom of the + * display area. + */ + public final static int BOTTOM = 2; + + /** The vertical vgap between components...defaults to 5. */ + private int vgap; + + /** LEFT, RIGHT, CENTER or BOTH...how the components are justified. */ + private int alignment; + + /** + * TOP, BOTTOM or CENTER ...where are the components positioned in an + * overlarge space. + */ + private int anchor; + + // Constructors + /** + * Constructs an instance of VerticalLayout with a vertical vgap of 5 + * pixels, horizontal centering and anchored to the top of the display area. + */ + public VerticalLayout() { + this(5, CENTER, TOP); + } + + /** + * Constructs a VerticalLayout instance with horizontal centering, anchored + * to the top with the specified vgap. + * + * @param vgap + * an int value indicating the vertical seperation of the + * components + */ + public VerticalLayout(int vgap) { + this(vgap, CENTER, TOP); + } + + /** + * Constructs a VerticalLayout instance anchored to the top with the + * specified vgap and horizontal alignment. + * + * @param vgap + * an int value indicating the vertical seperation of the + * components + * @param alignment + * an int value which is one of RIGHT, LEFT, + * CENTER, BOTH + * for the horizontal alignment. + */ + public VerticalLayout(int vgap, int alignment) { + this(vgap, alignment, TOP); + } + + /** + * Constructs a VerticalLayout instance with the specified vgap, horizontal + * alignment and anchoring + * + * @param vgap + * an int value indicating the vertical seperation of the + * components + * @param alignment + * an int value which is one of RIGHT, LEFT, CENTER, + * BOTH + * for the horizontal alignment. + * @param anchor + * an int value which is one of TOP, BOTTOM, + * CENTER + * indicating where the components are to appear if the display + * area exceeds the minimum necessary. + */ + public VerticalLayout(int vgap, int alignment, int anchor) { + this.vgap = vgap; + this.alignment = alignment; + this.anchor = anchor; + } + + /** + * Lays out the container. + */ + public void layoutContainer(Container parent) { + Insets insets = parent.getInsets(); + // NOTUSED Dimension dim = layoutSize(parent, false); + synchronized (parent.getTreeLock()) { + int n = parent.getComponentCount(); + Dimension pd = parent.getSize(); + int y = 0; + // work out the total size + for (int i = 0; i < n; i++) { + Component c = parent.getComponent(i); + Dimension d = c.getPreferredSize(); + y += d.height + vgap; + } + y -= vgap; // otherwise there's a vgap too many + // Work out the anchor paint + if (anchor == TOP) { + y = insets.top; + } else if (anchor == CENTER) { + y = (pd.height - y) / 2; + } else { + y = pd.height - y - insets.bottom; + } + // do layout + for (int i = 0; i < n; i++) { + Component c = parent.getComponent(i); + Dimension d = c.getPreferredSize(); + int x = insets.left; + int wid = d.width; + if (alignment == CENTER) { + x = (pd.width - d.width) / 2; + } else if (alignment == RIGHT) { + x = pd.width - d.width - insets.right; + } else if (alignment == BOTH) { + wid = pd.width - insets.left - insets.right; + } + c.setBounds(x, y, wid, d.height); + y += d.height + vgap; + } + } + } + + public Dimension minimumLayoutSize(Container parent) { + return layoutSize(parent, true); + } + + public Dimension preferredLayoutSize(Container parent) { + return layoutSize(parent, false); + } + + /** + * Not used by this class. + */ + public void addLayoutComponent(String name, Component comp) { + } + + /** + * Not used by this class. + */ + public void removeLayoutComponent(Component comp) { + } + + @Override + public String toString() { + return getClass().getName() + "[vgap=" + vgap + " align=" + alignment + " anchor=" + anchor + "]"; + } + + private Dimension layoutSize(Container parent, boolean minimum) { + Dimension dim = new Dimension(0, 0); + Dimension d; + synchronized (parent.getTreeLock()) { + int n = parent.getComponentCount(); + for (int i = 0; i < n; i++) { + Component c = parent.getComponent(i); + if (c.isVisible()) { + d = minimum ? c.getMinimumSize() : c.getPreferredSize(); + dim.width = Math.max(dim.width, d.width); + dim.height += d.height; + if (i > 0) { + dim.height += vgap; + } + } + } + } + Insets insets = parent.getInsets(); + dim.width += insets.left + insets.right; + dim.height += insets.top + insets.bottom + vgap + vgap; + return dim; + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/io/TextFile.java b/ApacheJmeter/src/org/apache/jorphan/io/TextFile.java new file mode 100644 index 0000000..eaa4700 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/io/TextFile.java @@ -0,0 +1,207 @@ +/* + * 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.io; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * Utility class to handle text files as a single lump of text. + *

+ * Note this is just as memory-inefficient as handling a text file can be. Use + * with restraint. + * + * @version $Revision: 1225215 $ + */ +public class TextFile extends File { + private static final long serialVersionUID = 240L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + /** + * File encoding. null means use the platform's default. + */ + private String encoding = null; + + /** + * Create a TextFile object to handle the named file with the given + * encoding. + * + * @param filename + * File to be read & written through this object. + * @param encoding + * Encoding to be used when reading & writing this file. + */ + public TextFile(File filename, String encoding) { + super(filename.toString()); + setEncoding(encoding); + } + + /** + * Create a TextFile object to handle the named file with the platform + * default encoding. + * + * @param filename + * File to be read & written through this object. + */ + public TextFile(File filename) { + super(filename.toString()); + } + + /** + * Create a TextFile object to handle the named file with the platform + * default encoding. + * + * @param filename + * Name of the file to be read & written through this object. + */ + public TextFile(String filename) { + super(filename); + } + + /** + * Create a TextFile object to handle the named file with the given + * encoding. + * + * @param filename + * Name of the file to be read & written through this object. + * @param encoding + * Encoding to be used when reading & writing this file. + */ + public TextFile(String filename, String encoding) { + super(filename); + } + + /** + * Create the file with the given string as content -- or replace it's + * content with the given string if the file already existed. + * + * @param body + * New content for the file. + */ + public void setText(String body) { + Writer writer = null; + try { + if (encoding == null) { + writer = new FileWriter(this); + } else { + writer = new OutputStreamWriter(new FileOutputStream(this), encoding); + } + writer.write(body); + writer.flush(); + } catch (IOException ioe) { + log.error("", ioe); + } finally { + JOrphanUtils.closeQuietly(writer); + } + } + + /** + * Read the whole file content and return it as a string. + * + * @return the content of the file + */ + public String getText() { + String lineEnd = System.getProperty("line.separator"); //$NON-NLS-1$ + StringBuilder sb = new StringBuilder(); + Reader reader = null; + BufferedReader br = null; + try { + if (encoding == null) { + reader = new FileReader(this); + } else { + reader = new InputStreamReader(new FileInputStream(this), encoding); + } + br = new BufferedReader(reader); + String line = "NOTNULL"; //$NON-NLS-1$ + while (line != null) { + line = br.readLine(); + if (line != null) { + sb.append(line + lineEnd); + } + } + } catch (IOException ioe) { + log.error("", ioe); //$NON-NLS-1$ + } finally { + JOrphanUtils.closeQuietly(br); // closes reader as well + } + + return sb.toString(); + } + + /** + * @return Encoding being used to read & write this file. + */ + public String getEncoding() { + return encoding; + } + + /** + * @param string + * Encoding to be used to read & write this file. + */ + public void setEncoding(String string) { + encoding = string; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + + ((encoding == null) ? 0 : encoding.hashCode()); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (!(obj instanceof TextFile)) + return false; + TextFile other = (TextFile) obj; + if (encoding == null) { + if (other.encoding != null) + return false; + } else if (!encoding.equals(other.encoding)) + return false; + return true; + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/logging/LoggingManager.java b/ApacheJmeter/src/org/apache/jorphan/logging/LoggingManager.java new file mode 100644 index 0000000..043ae9e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/logging/LoggingManager.java @@ -0,0 +1,361 @@ +/* + * 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.logging; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Iterator; +import java.util.Properties; + +import org.apache.avalon.excalibur.logger.LogKitLoggerManager; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; +import org.apache.avalon.framework.context.Context; +import org.apache.avalon.framework.context.ContextException; +import org.apache.avalon.framework.context.DefaultContext; +import org.apache.log.Hierarchy; +import org.apache.log.LogTarget; +import org.apache.log.Logger; +import org.apache.log.Priority; +import org.apache.log.format.PatternFormatter; +import org.apache.log.output.NullOutputLogTarget; +import org.apache.log.output.io.WriterTarget; +import org.xml.sax.SAXException; + +/** + * Manages JMeter logging + */ +public final class LoggingManager { + // N.B time pattern is passed to java.text.SimpleDateFormat + /* + * Predefined format patterns, selected by the property log_format_type (see + * jmeter.properties) The new-line is added later + */ + public static final String DEFAULT_PATTERN = "%{time:yyyy/MM/dd HH:mm:ss} %5.5{priority} - " //$NON_NLS-1$ + + "%{category}: %{message} %{throwable}"; //$NON_NLS-1$ + + private static final String PATTERN_THREAD_PREFIX = "%{time:yyyy/MM/dd HH:mm:ss} %5.5{priority} " //$NON_NLS-1$ + + "%20{thread} %{category}: %{message} %{throwable}"; //$NON_NLS-1$ + + private static final String PATTERN_THREAD_SUFFIX = "%{time:yyyy/MM/dd HH:mm:ss} %5.5{priority} " //$NON_NLS-1$ + + "%{category}[%{thread}]: %{message} %{throwable}"; //$NON_NLS-1$ + + // Needs to be volatile as may be referenced from multiple threads + // TODO see if this can be made final somehow + private static volatile PatternFormatter format = null; + + /** Used to hold the default logging target. */ + //@GuardedBy("this") + private static LogTarget target = new NullOutputLogTarget(); + + // Hack to detect when System.out has been set as the target, to avoid closing it + private static volatile boolean isTargetSystemOut = false;// Is the target System.out? + + private static volatile boolean isWriterSystemOut = false;// Is the Writer System.out? + + public static final String LOG_FILE = "log_file"; //$NON_NLS-1$ + + public static final String LOG_PRIORITY = "log_level"; //$NON_NLS-1$ + + private LoggingManager() { + // non-instantiable - static methods only + } + + /** + * Initialise the logging system from the Jmeter properties. Logkit loggers + * inherit from their parents. + * + * Normally the jmeter properties file defines a single log file, so set + * this as the default from "log_file", default "jmeter.log" The default + * priority is set from "log_level", with a default of INFO + * + */ + public static void initializeLogging(Properties properties) { + setFormat(properties); + + // Set the top-level defaults + setTarget(makeWriter(properties.getProperty(LOG_FILE, "jmeter.log"), LOG_FILE)); //$NON_NLS-1$ + setPriority(properties.getProperty(LOG_PRIORITY, "INFO")); + + setLoggingLevels(properties); + // now set the individual categories (if any) + + setConfig(properties);// Further configuration + } + + private static void setFormat(Properties properties) { + String pattern = DEFAULT_PATTERN; + String type = properties.getProperty("log_format_type", ""); //$NON_NLS-1$ + if (type.length() == 0) { + pattern = properties.getProperty("log_format", DEFAULT_PATTERN); //$NON_NLS-1$ + } else { + if (type.equalsIgnoreCase("thread_suffix")) { //$NON_NLS-1$ + pattern = PATTERN_THREAD_SUFFIX; + } else if (type.equalsIgnoreCase("thread_prefix")) { //$NON_NLS-1$ + pattern = PATTERN_THREAD_PREFIX; + } else { + pattern = DEFAULT_PATTERN; + } + } + format = new PatternFormatter(pattern + "\n"); //$NON_NLS-1$ + } + + private static void setConfig(Properties p) { + String cfg = p.getProperty("log_config"); //$NON_NLS-1$ + if (cfg == null) { + return; + } + + // Make sure same hierarchy is used + Hierarchy hier = Hierarchy.getDefaultHierarchy(); + LogKitLoggerManager manager = new LogKitLoggerManager(null, hier, null, null); + + DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); + try { + Configuration c = builder.buildFromFile(cfg); + Context ctx = new DefaultContext(); + manager.contextualize(ctx); + manager.configure(c); + } catch (IllegalArgumentException e) { + // This happens if the default log-target id-ref specifies a non-existent target + System.out.println("Error processing logging config " + cfg); + System.out.println(e.toString()); + } catch (NullPointerException e) { + // This can happen if a log-target id-ref specifies a non-existent target + System.out.println("Error processing logging config " + cfg); + System.out.println("Perhaps a log target is missing?"); + } catch (ConfigurationException e) { + System.out.println("Error processing logging config " + cfg); + System.out.println(e.toString()); + } catch (SAXException e) { + System.out.println("Error processing logging config " + cfg); + System.out.println(e.toString()); + } catch (IOException e) { + System.out.println("Error processing logging config " + cfg); + System.out.println(e.toString()); + } catch (ContextException e) { + System.out.println("Error processing logging config " + cfg); + System.out.println(e.toString()); + } + } + + /* + * Helper method to ensure that format is initialised if initializeLogging() + * has not yet been called. + */ + private static PatternFormatter getFormat() { + if (format == null) { + format = new PatternFormatter(DEFAULT_PATTERN + "\n"); //$NON_NLS-1$ + } + return format; + } + + /* + * Helper method to handle log target creation. If there is an error + * creating the file, then it uses System.out. + */ + private static Writer makeWriter(String logFile, String propName) { + // If the name contains at least one set of paired single-quotes, reformat using DateFormat + final int length = logFile.split("'",-1).length; + if (length > 1 && length %2 == 1){ + try { + SimpleDateFormat df = new SimpleDateFormat(logFile); + logFile = df.format(new Date()); + } catch (Exception ignored) { + } + } + Writer wt; + isWriterSystemOut = false; + try { + wt = new FileWriter(logFile); + } catch (Exception e) { + System.out.println(propName + "=" + logFile + " " + e.toString()); + System.out.println("[" + propName + "-> System.out]"); + isWriterSystemOut = true; + wt = new PrintWriter(System.out); + } + return wt; + } + + /** + * Handle LOG_PRIORITY.category=priority and LOG_FILE.category=file_name + * properties. If the prefix is detected, then remove it to get the + * category. + */ + public static void setLoggingLevels(Properties appProperties) { + Iterator props = appProperties.keySet().iterator(); + while (props.hasNext()) { + String prop = (String) props.next(); + if (prop.startsWith(LOG_PRIORITY + ".")) //$NON_NLS-1$ + // don't match the empty category + { + String category = prop.substring(LOG_PRIORITY.length() + 1); + setPriority(appProperties.getProperty(prop), category); + } + if (prop.startsWith(LOG_FILE + ".")) { //$NON_NLS-1$ + String category = prop.substring(LOG_FILE.length() + 1); + String file = appProperties.getProperty(prop); + setTarget(new WriterTarget(makeWriter(file, prop), getFormat()), category); + } + } + } + + private static final String PACKAGE_PREFIX = "org.apache."; //$NON_NLS-1$ + + /** + * Removes the standard prefix, i.e. "org.apache.". + * + * @param name from which to remove the prefix + * @return the name with the prefix removed + */ + public static String removePrefix(String name){ + if (name.startsWith(PACKAGE_PREFIX)) { // remove the package prefix + name = name.substring(PACKAGE_PREFIX.length()); + } + return name; + } + /** + * Get the Logger for a class - no argument needed because the calling class + * name is derived automatically from the call stack. + * + * @return Logger + */ + public static Logger getLoggerForClass() { + String className = (new Exception()).getStackTrace()[1].getClassName(); + return Hierarchy.getDefaultHierarchy().getLoggerFor(removePrefix(className)); + } + + /** + * Get the Logger for a class. + * + * @param category - the full name of the logger category + * + * @return Logger + */ + public static Logger getLoggerFor(String category) { + return Hierarchy.getDefaultHierarchy().getLoggerFor(category); + } + + /** + * Get the Logger for a class. + * + * @param category - the full name of the logger category, this will have the prefix removed. + * + * @return Logger + */ + public static Logger getLoggerForShortName(String category) { + return Hierarchy.getDefaultHierarchy().getLoggerFor(removePrefix(category)); + } + + /** + * Set the logging priority for a category. + * + * @param priority - string containing the priority name, e.g. "INFO", "WARN", "DEBUG", "FATAL_ERROR" + * @param category - string containing the category + */ + public static void setPriority(String priority, String category) { + setPriority(Priority.getPriorityForName(priority), category); + } + + /** + * Set the logging priority for a category. + * + * @param priority - priority, e.g. DEBUG, INFO + * @param fullName - e.g. org.apache.jmeter.etc, will have the prefix removed. + */ + public static void setPriorityFullName(String priority, String fullName) { + setPriority(Priority.getPriorityForName(priority), removePrefix(fullName)); + } + + /** + * Set the logging priority for a category. + * + * @param priority - e.g. Priority.DEBUG + * @param category - string containing the category + */ + public static void setPriority(Priority priority, String category) { + Hierarchy.getDefaultHierarchy().getLoggerFor(category).setPriority(priority); + } + + public static void setPriority(String p) { + setPriority(Priority.getPriorityForName(p)); + } + + /** + * Set the default logging priority. + * + * @param priority e.g. Priority.DEBUG + */ + public static void setPriority(Priority priority) { + Hierarchy.getDefaultHierarchy().setDefaultPriority(priority); + } + + /** + * Set the logging target for a category. + * + * @param target the LogTarget + * @param category the category name + */ + public static void setTarget(LogTarget target, String category) { + Logger logger = Hierarchy.getDefaultHierarchy().getLoggerFor(category); + logger.setLogTargets(new LogTarget[] { target }); + } + + /** + * Sets the default log target from the parameter. The existing target is + * first closed if necessary. + * + * @param targetFile + * (Writer) + */ + private static synchronized void setTarget(Writer targetFile) { + if (target == null) { + target = getTarget(targetFile, getFormat()); + isTargetSystemOut = isWriterSystemOut; + } else { + if (!isTargetSystemOut && target instanceof WriterTarget) { + ((WriterTarget) target).close(); + } + target = getTarget(targetFile, getFormat()); + isTargetSystemOut = isWriterSystemOut; + } + Hierarchy.getDefaultHierarchy().setDefaultLogTarget(target); + } + + private static LogTarget getTarget(Writer targetFile, PatternFormatter fmt) { + return new WriterTarget(targetFile, fmt); + } + + /** + * Add logTargets to root logger + * FIXME What's the clean way to add a LogTarget afterwards ? + * @param logTargets LogTarget array + */ + public static void addLogTargetToRootLogger(LogTarget[] logTargets) { + LogTarget[] newLogTargets = new LogTarget[logTargets.length+1]; + System.arraycopy(logTargets, 0, newLogTargets, 1, logTargets.length); + newLogTargets[0] = target; + Hierarchy.getDefaultHierarchy().getRootLogger().setLogTargets(newLogTargets); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/math/NumberComparator.java b/ApacheJmeter/src/org/apache/jorphan/math/NumberComparator.java new file mode 100644 index 0000000..82a7e8b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/math/NumberComparator.java @@ -0,0 +1,46 @@ +/* + * 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. + * + */ + +/* + * Created on May 25, 2004 + */ +package org.apache.jorphan.math; + +import java.io.Serializable; +import java.util.Comparator; + +public class NumberComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 1L; + + public NumberComparator() { + super(); + } + + /** {@inheritDoc} */ + public int compare(Number[] n1, Number[] n2) { + if (n1[0].longValue() < n2[0].longValue()) { + return -1; + } else if (n1[0].longValue() == n2[0].longValue()) { + return 0; + } else { + return 1; + } + } + +} diff --git a/ApacheJmeter/src/org/apache/jorphan/math/StatCalculator.java b/ApacheJmeter/src/org/apache/jorphan/math/StatCalculator.java new file mode 100644 index 0000000..eed1072 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/math/StatCalculator.java @@ -0,0 +1,271 @@ +/* + * 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.math; + +import java.util.ConcurrentModificationException; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.Map.Entry; + +import org.apache.commons.lang.mutable.MutableLong; + +/** + * This class serves as a way to calculate the median, max, min etc. of a list of values. + * It is not threadsafe. + * + */ +public abstract class StatCalculator> { + + // key is the type to collect (usually long), value = count of entries + private final TreeMap valuesMap = new TreeMap(); + // We use a TreeMap because we need the entries to be sorted + + // Running values, updated for each sample + private double sum = 0; + + private double sumOfSquares = 0; + + private double mean = 0; + + private double deviation = 0; + + private long count = 0; + + private T min; + + private T max; + + private long bytes = 0; + + private final T ZERO; + + private final T MAX_VALUE; // e.g. Long.MAX_VALUE + + private final T MIN_VALUE; // e.g. Long.MIN_VALUE + + /** + * This constructor is used to set up particular values for the generic class instance. + * + * @param zero - value to return for Median and PercentPoint if there are no values + * @param min - value to return for minimum if there are no values + * @param max - value to return for maximum if there are no values + */ + public StatCalculator(final T zero, final T min, final T max) { + super(); + ZERO = zero; + MAX_VALUE = max; + MIN_VALUE = min; + this.min = MAX_VALUE; + this.max = MIN_VALUE; + } + + public void clear() { + valuesMap.clear(); + sum = 0; + sumOfSquares = 0; + mean = 0; + deviation = 0; + count = 0; + bytes = 0; + max = MIN_VALUE; + min = MAX_VALUE; + } + + + public void addBytes(long newValue) { + bytes += newValue; + } + + public void addAll(StatCalculator calc) { + for(Entry ent : calc.valuesMap.entrySet()) { + addEachValue(ent.getKey(), ent.getValue().longValue()); + } + } + + public T getMedian() { + return getPercentPoint(0.5); + } + + public long getTotalBytes() { + return bytes; + } + + /** + * Get the value which %percent% of the values are less than. This works + * just like median (where median represents the 50% point). A typical + * desire is to see the 90% point - the value that 90% of the data points + * are below, the remaining 10% are above. + * + * @param percent + * @return number of values less than the percentage + */ + public T getPercentPoint(float percent) { + return getPercentPoint((double) percent); + } + + /** + * Get the value which %percent% of the values are less than. This works + * just like median (where median represents the 50% point). A typical + * desire is to see the 90% point - the value that 90% of the data points + * are below, the remaining 10% are above. + * + * @param percent + * @return the value which %percent% of the values are less than + */ + public T getPercentPoint(double percent) { + if (count <= 0) { + return ZERO; + } + if (percent >= 1.0) { + return getMax(); + } + + // use Math.round () instead of simple (long) to provide correct value rounding + long target = Math.round (count * percent); + try { + for (Entry val : valuesMap.entrySet()) { + target -= val.getValue().longValue(); + if (target <= 0){ + return val.getKey(); + } + } + } catch (ConcurrentModificationException ignored) { + // ignored. May happen occasionally, but no harm done if so. + } + return ZERO; // TODO should this be getMin()? + } + + /** + * Returns the distribution of the values in the list. + * + * @return map containing either Integer or Long keys; entries are a Number array containing the key and the [Integer] count. + * TODO - why is the key value also stored in the entry array? + */ + public synchronized Map getDistribution() { + HashMap items = new HashMap (); + Number[] dis; + + for (T nx : valuesMap.keySet()) { + dis = new Number[2]; + dis[0] = nx; + dis[1] = valuesMap.get(nx); + items.put(nx, dis); + } + return items; + } + + public double getMean() { + return mean; + } + + public double getStandardDeviation() { + return deviation; + } + + public T getMin() { + return min; + } + + public T getMax() { + return max; + } + + public long getCount() { + return count; + } + + public double getSum() { + return sum; + } + + protected abstract T divide(T val, int n); + + protected abstract T divide(T val, long n); + + /** + * Update the calculator with the values for a set of samples. + * + * @param val the common value, normally the elapsed time + * @param sampleCount the number of samples with the same value + */ + void addEachValue(T val, long sampleCount) { + count += sampleCount; + double currentVal = val.doubleValue(); + sum += currentVal * sampleCount; + // For n same values in sum of square is equal to n*val^2 + sumOfSquares += currentVal * currentVal * sampleCount; + updateValueCount(val, sampleCount); + calculateDerivedValues(val); + } + + /** + * Update the calculator with the value for an aggregated sample. + * + * @param val the aggregate value, normally the elapsed time + * @param sampleCount the number of samples contributing to the aggregate value + */ + public void addValue(T val, long sampleCount) { + count += sampleCount; + double currentVal = val.doubleValue(); + sum += currentVal; + T actualValue = val; + if (sampleCount > 1){ + // For n values in an aggregate sample the average value = (val/n) + // So need to add n * (val/n) * (val/n) = val * val / n + sumOfSquares += currentVal * currentVal / sampleCount; + actualValue = divide(val, sampleCount); + } else { // no need to divide by 1 + sumOfSquares += currentVal * currentVal; + } + updateValueCount(actualValue, sampleCount); + calculateDerivedValues(actualValue); + } + + private void calculateDerivedValues(T actualValue) { + mean = sum / count; + deviation = Math.sqrt((sumOfSquares / count) - (mean * mean)); + if (actualValue.compareTo(max) > 0){ + max=actualValue; + } + if (actualValue.compareTo(min) < 0){ + min=actualValue; + } + } + + /** + * Add a single value (normally elapsed time) + * + * @param val the value to add, which should correspond with a single sample + * @see #addValue(Number, long) + */ + public void addValue(T val) { + addValue(val, 1L); + } + + private void updateValueCount(T actualValue, long sampleCount) { + MutableLong count = valuesMap.get(actualValue); + if (count != null) { + count.add(sampleCount); + } else { + // insert new value + valuesMap.put(actualValue, new MutableLong(sampleCount)); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/math/StatCalculatorInteger.java b/ApacheJmeter/src/org/apache/jorphan/math/StatCalculatorInteger.java new file mode 100644 index 0000000..7ffd55b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/math/StatCalculatorInteger.java @@ -0,0 +1,53 @@ +/* + * 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.math; + +/** + * StatCalculator for Integer values + */ +public class StatCalculatorInteger extends StatCalculator { + + public StatCalculatorInteger() { + super(Integer.valueOf(0), Integer.valueOf(Integer.MIN_VALUE), Integer.valueOf(Integer.MAX_VALUE)); + } + + public void addValue(int val){ + super.addValue(Integer.valueOf(val)); + } + + /** + * Update the calculator with the value for an aggregated sample. + * + * @param val the aggregate value + * @param sampleCount the number of samples contributing to the aggregate value + */ + public void addValue(int val, int sampleCount){ + super.addValue(Integer.valueOf(val), sampleCount); + } + + @Override + protected Integer divide(Integer val, int n) { + return Integer.valueOf(val.intValue() / n); + } + + @Override + protected Integer divide(Integer val, long n) { + return Integer.valueOf((int) (val.intValue() / n)); + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/math/StatCalculatorLong.java b/ApacheJmeter/src/org/apache/jorphan/math/StatCalculatorLong.java new file mode 100644 index 0000000..1379af2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/math/StatCalculatorLong.java @@ -0,0 +1,58 @@ +/* + * 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.math; + +/** + * StatCalculator for Long values + */ +public class StatCalculatorLong extends StatCalculator { + + public StatCalculatorLong() { + super(Long.valueOf(0L), Long.valueOf(Long.MIN_VALUE), Long.valueOf(Long.MAX_VALUE)); + } + + /** + * Add a single value (normally elapsed time) + * + * @param val the value to add, which should correspond with a single sample + */ + public void addValue(long val){ + super.addValue(Long.valueOf(val)); + } + + /** + * Update the calculator with the value for an aggregated sample. + * + * @param val the aggregate value, normally the elapsed time + * @param sampleCount the number of samples contributing to the aggregate value + */ + public void addValue(long val, int sampleCount){ + super.addValue(Long.valueOf(val), sampleCount); + } + + @Override + protected Long divide(Long val, int n) { + return Long.valueOf(val.longValue() / n); + } + + @Override + protected Long divide(Long val, long n) { + return Long.valueOf(val.longValue() / n); + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/reflect/ClassFinder.java b/ApacheJmeter/src/org/apache/jorphan/reflect/ClassFinder.java new file mode 100644 index 0000000..fac3575 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/reflect/ClassFinder.java @@ -0,0 +1,566 @@ +/* + * 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.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +/** + * This class finds classes that extend one of a set of parent classes + * + */ +public final class ClassFinder { + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final String DOT_JAR = ".jar"; // $NON-NLS-1$ + private static final String DOT_CLASS = ".class"; // $NON-NLS-1$ + private static final int DOT_CLASS_LEN = DOT_CLASS.length(); + + // static only + private ClassFinder() { + } + + /** + * Filter updates to TreeSet by only storing classes + * that extend one of the parent classes + * + * + */ + private static class FilterTreeSet extends TreeSet{ + private static final long serialVersionUID = 234L; + + private final Class[] parents; // parent classes to check + private final boolean inner; // are inner classes OK? + + // hack to reduce the need to load every class in non-GUI mode, which only needs functions + // TODO perhaps use BCEL to scan class files instead? + private final String contains; // class name should contain this string + private final String notContains; // class name should not contain this string + + private final transient ClassLoader contextClassLoader + = Thread.currentThread().getContextClassLoader(); // Potentially expensive; do it once + + FilterTreeSet(Class []parents, boolean inner, String contains, String notContains){ + super(); + this.parents=parents; + this.inner=inner; + this.contains=contains; + this.notContains=notContains; + } + + /** + * Override the superclass so we only add classnames that + * meet the criteria. + * + * @param s - classname (must be a String) + * @return true if it is a new entry + * + * @see java.util.TreeSet#add(java.lang.Object) + */ + @Override + public boolean add(String s){ + if (contains(s)) { + return false;// No need to check it again + } + if (contains!=null && s.indexOf(contains) == -1){ + return false; // It does not contain a required string + } + if (notContains!=null && s.indexOf(notContains) != -1){ + return false; // It contains a banned string + } + if ((s.indexOf("$") == -1) || inner) { // $NON-NLS-1$ + if (isChildOf(parents,s, contextClassLoader)) { + return super.add(s); + } + } + return false; + } + } + + private static class AnnoFilterTreeSet extends TreeSet{ + private static final long serialVersionUID = 240L; + + private final boolean inner; // are inner classes OK? + + private final Class[] annotations; // annotation classes to check + private final transient ClassLoader contextClassLoader + = Thread.currentThread().getContextClassLoader(); // Potentially expensive; do it once + AnnoFilterTreeSet(Class []annotations, boolean inner){ + super(); + this.annotations = annotations; + this.inner=inner; + } + /** + * Override the superclass so we only add classnames that + * meet the criteria. + * + * @param s - classname (must be a String) + * @return true if it is a new entry + * + * @see java.util.TreeSet#add(java.lang.Object) + */ + @Override + public boolean add(String s){ + if (contains(s)) { + return false;// No need to check it again + } + if ((s.indexOf("$") == -1) || inner) { // $NON-NLS-1$ + if (hasAnnotationOnMethod(annotations,s, contextClassLoader)) { + return super.add(s); + } + } + return false; + } + } + + /** + * Convenience method for + * {@link #findClassesThatExtend(String[], Class[], boolean)} + * with the option to include inner classes in the search set to false. + * + * @return List of Strings containing discovered class names. + */ + public static List findClassesThatExtend(String[] paths, Class[] superClasses) + throws IOException { + return findClassesThatExtend(paths, superClasses, false); + } + + // For each directory in the search path, add all the jars found there + private static String[] addJarsInPath(String[] paths) { + Set fullList = new HashSet(); + for (int i = 0; i < paths.length; i++) { + final String path = paths[i]; + fullList.add(path); // Keep the unexpanded path + // TODO - allow directories to end with .jar by removing this check? + if (!path.endsWith(DOT_JAR)) { + File dir = new File(path); + if (dir.exists() && dir.isDirectory()) { + String[] jars = dir.list(new FilenameFilter() { + public boolean accept(File f, String name) { + return name.endsWith(DOT_JAR); + } + }); + for (int x = 0; x < jars.length; x++) { + fullList.add(jars[x]); + } + } + } + } + return fullList.toArray(new String[fullList.size()]); + } + + /** + * Find classes in the provided path(s)/jar(s) that extend the class(es). + * @param strPathsOrJars - pathnames or jarfiles to search for classes + * @param superClasses - required parent class(es) + * @param innerClasses - should we include inner classes? + * + * @return List containing discovered classes + */ + public static List findClassesThatExtend(String[] strPathsOrJars, + final Class[] superClasses, final boolean innerClasses) + throws IOException { + return findClassesThatExtend(strPathsOrJars,superClasses,innerClasses,null,null); + } + + /** + * Find classes in the provided path(s)/jar(s) that extend the class(es). + * @param strPathsOrJars - pathnames or jarfiles to search for classes + * @param superClasses - required parent class(es) + * @param innerClasses - should we include inner classes? + * @param contains - classname should contain this string + * @param notContains - classname should not contain this string + * + * @return List containing discovered classes + */ + public static List findClassesThatExtend(String[] strPathsOrJars, + final Class[] superClasses, final boolean innerClasses, + String contains, String notContains) + throws IOException { + return findClassesThatExtend(strPathsOrJars, superClasses, innerClasses, contains, notContains, false); + } + + /** + * Find classes in the provided path(s)/jar(s) that extend the class(es). + * @param strPathsOrJars - pathnames or jarfiles to search for classes + * @param annotations - required annotations + * @param innerClasses - should we include inner classes? + * + * @return List containing discovered classes + */ + public static List findAnnotatedClasses(String[] strPathsOrJars, + final Class[] annotations, final boolean innerClasses) + throws IOException { + return findClassesThatExtend(strPathsOrJars, annotations, innerClasses, null, null, true); + } + + /** + * Find classes in the provided path(s)/jar(s) that extend the class(es). + * Inner classes are not searched. + * + * @param strPathsOrJars - pathnames or jarfiles to search for classes + * @param annotations - required annotations + * + * @return List containing discovered classes + */ + public static List findAnnotatedClasses(String[] strPathsOrJars, + final Class[] annotations) + throws IOException { + return findClassesThatExtend(strPathsOrJars, annotations, false, null, null, true); + } + + /** + * Find classes in the provided path(s)/jar(s) that extend the class(es). + * @param searchPathsOrJars - pathnames or jarfiles to search for classes + * @param classNames - required parent class(es) or annotations + * @param innerClasses - should we include inner classes? + * @param contains - classname should contain this string + * @param notContains - classname should not contain this string + * @param annotations - true if classnames are annotations + * + * @return List containing discovered classes + */ + public static List findClassesThatExtend(String[] searchPathsOrJars, + final Class[] classNames, final boolean innerClasses, + String contains, String notContains, boolean annotations) + throws IOException { + if (log.isDebugEnabled()) { + log.debug("searchPathsOrJars : " + Arrays.toString(searchPathsOrJars)); + log.debug("superclass : " + Arrays.toString(classNames)); + log.debug("innerClasses : " + innerClasses + " annotations: " + annotations); + log.debug("contains: " + contains + " notContains: " + notContains); + } + + // Find all jars in the search path + String[] strPathsOrJars = addJarsInPath(searchPathsOrJars); + for (int k = 0; k < strPathsOrJars.length; k++) { + strPathsOrJars[k] = fixPathEntry(strPathsOrJars[k]); + } + + // Now eliminate any classpath entries that do not "match" the search + List listPaths = getClasspathMatches(strPathsOrJars); + if (log.isDebugEnabled()) { + for (String path : listPaths) { + log.debug("listPaths : " + path); + } + } + + @SuppressWarnings("unchecked") // Should only be called with classes that extend annotations + final Class[] annoclassNames = (Class[]) classNames; + Set listClasses = + annotations ? + new AnnoFilterTreeSet(annoclassNames, innerClasses) + : + new FilterTreeSet(classNames, innerClasses, contains, notContains); + // first get all the classes + findClassesInPaths(listPaths, listClasses); + if (log.isDebugEnabled()) { + log.debug("listClasses.size()="+listClasses.size()); + for (String clazz : listClasses) { + log.debug("listClasses : " + clazz); + } + } + +// // Now keep only the required classes +// Set subClassList = findAllSubclasses(superClasses, listClasses, innerClasses); +// if (log.isDebugEnabled()) { +// log.debug("subClassList.size()="+subClassList.size()); +// Iterator tIter = subClassList.iterator(); +// while (tIter.hasNext()) { +// log.debug("subClassList : " + tIter.next()); +// } +// } + + return new ArrayList(listClasses);//subClassList); + } + + /* + * Returns the classpath entries that match the search list of jars and paths + */ + private static List getClasspathMatches(String[] strPathsOrJars) { + final String javaClassPath = System.getProperty("java.class.path"); // $NON-NLS-1$ + StringTokenizer stPaths = + new StringTokenizer(javaClassPath, + System.getProperty("path.separator")); // $NON-NLS-1$ + if (log.isDebugEnabled()) { + log.debug("Classpath = " + javaClassPath); + for (int i = 0; i < strPathsOrJars.length; i++) { + log.debug("strPathsOrJars[" + i + "] : " + strPathsOrJars[i]); + } + } + + // find all jar files or paths that end with strPathOrJar + ArrayList listPaths = new ArrayList(); + String strPath = null; + while (stPaths.hasMoreTokens()) { + strPath = fixPathEntry(stPaths.nextToken()); + if (strPathsOrJars == null) { + log.debug("Adding: " + strPath); + listPaths.add(strPath); + } else { + boolean found = false; + for (int i = 0; i < strPathsOrJars.length; i++) { + if (strPath.endsWith(strPathsOrJars[i])) { + found = true; + log.debug("Adding " + strPath + " found at " + i); + listPaths.add(strPath); + break;// no need to look further + } + } + if (!found) { + log.debug("Did not find: " + strPath); + } + } + } + return listPaths; + } + + /** + * Fix a path: + * - replace "." by current directory + * - trim any trailing spaces + * - replace \ by / + * - replace // by / + * - remove all trailing / + */ + private static String fixPathEntry(String path){ + if (path == null ) { + return null; + } + if (path.equals(".")) { // $NON-NLS-1$ + return System.getProperty("user.dir"); // $NON-NLS-1$ + } + path = path.trim().replace('\\', '/'); // $NON-NLS-1$ // $NON-NLS-2$ + path = JOrphanUtils.substitute(path, "//", "/"); // $NON-NLS-1$// $NON-NLS-2$ + + while (path.endsWith("/")) { // $NON-NLS-1$ + path = path.substring(0, path.length() - 1); + } + return path; + } + + /* + * NOTUSED * Determine if the class implements the interface. + * + * @param theClass + * the class to check + * @param theInterface + * the interface to look for + * @return boolean true if it implements + * + * private static boolean classImplementsInterface( Class theClass, Class + * theInterface) { HashMap mapInterfaces = new HashMap(); String strKey = + * null; // pass in the map by reference since the method is recursive + * getAllInterfaces(theClass, mapInterfaces); Iterator iterInterfaces = + * mapInterfaces.keySet().iterator(); while (iterInterfaces.hasNext()) { + * strKey = (String) iterInterfaces.next(); if (mapInterfaces.get(strKey) == + * theInterface) { return true; } } return false; } + */ + + /* + * Finds all classes that extend the classes in the listSuperClasses + * ArrayList, searching in the listAllClasses ArrayList. + * + * @param superClasses + * the base classes to find subclasses for + * @param listAllClasses + * the collection of classes to search in + * @param innerClasses + * indicate whether to include inner classes in the search + * @return ArrayList of the subclasses + */ +// private static Set findAllSubclasses(Class []superClasses, Set listAllClasses, boolean innerClasses) { +// Set listSubClasses = new TreeSet(); +// for (int i=0; i< superClasses.length; i++) { +// findAllSubclassesOneClass(superClasses[i], listAllClasses, listSubClasses, innerClasses); +// } +// return listSubClasses; +// } + + /* + * Finds all classes that extend the class, searching in the listAllClasses + * ArrayList. + * + * @param theClass + * the parent class + * @param listAllClasses + * the collection of classes to search in + * @param listSubClasses + * the collection of discovered subclasses + * @param innerClasses + * indicates whether inners classes should be included in the + * search + */ +// private static void findAllSubclassesOneClass(Class theClass, Set listAllClasses, Set listSubClasses, +// boolean innerClasses) { +// Iterator iterClasses = listAllClasses.iterator(); +// while (iterClasses.hasNext()) { +// String strClassName = (String) iterClasses.next(); +// // only check classes if they are not inner classes +// // or we intend to check for inner classes +// if ((strClassName.indexOf("$") == -1) || innerClasses) { // $NON-NLS-1$ +// // might throw an exception, assume this is ignorable +// try { +// Class c = Class.forName(strClassName, false, Thread.currentThread().getContextClassLoader()); +// +// if (!c.isInterface() && !Modifier.isAbstract(c.getModifiers())) { +// if(theClass.isAssignableFrom(c)){ +// listSubClasses.add(strClassName); +// } +// } +// } catch (Throwable ignored) { +// log.debug(ignored.getLocalizedMessage()); +// } +// } +// } +// } + + /** + * + * @param parentClasses list of classes to check for + * @param strClassName name of class to be checked + * @param innerClasses should we allow inner classes? + * @param contextClassLoader the classloader to use + * @return true if the class is a non-abstract, non-interface instance of at least one of the parent classes + */ + private static boolean isChildOf(Class [] parentClasses, String strClassName, + ClassLoader contextClassLoader){ + // might throw an exception, assume this is ignorable + try { + Class c = Class.forName(strClassName, false, contextClassLoader); + + if (!c.isInterface() && !Modifier.isAbstract(c.getModifiers())) { + for (int i=0; i< parentClasses.length; i++) { + if(parentClasses[i].isAssignableFrom(c)){ + return true; + } + } + } + } catch (UnsupportedClassVersionError ignored) { + log.debug(ignored.getLocalizedMessage()); + } catch (NoClassDefFoundError ignored) { + log.debug(ignored.getLocalizedMessage()); + } catch (ClassNotFoundException ignored) { + log.debug(ignored.getLocalizedMessage()); + } + return false; + } + + private static boolean hasAnnotationOnMethod(Class[] annotations, String classInQuestion, + ClassLoader contextClassLoader ){ + try{ + Class c = Class.forName(classInQuestion, false, contextClassLoader); + for(Method method : c.getMethods()) { + for(Class annotation : annotations) { + if(method.isAnnotationPresent(annotation)) { + return true; + } + } + } + } catch (NoClassDefFoundError ignored) { + log.debug(ignored.getLocalizedMessage()); + } catch (ClassNotFoundException ignored) { + log.debug(ignored.getLocalizedMessage()); + } + return false; + } + + + /* + * Converts a class file from the text stored in a Jar file to a version + * that can be used in Class.forName(). + * + * @param strClassName + * the class name from a Jar file + * @return String the Java-style dotted version of the name + */ + private static String fixClassName(String strClassName) { + strClassName = strClassName.replace('\\', '.'); // $NON-NLS-1$ // $NON-NLS-2$ + strClassName = strClassName.replace('/', '.'); // $NON-NLS-1$ // $NON-NLS-2$ + // remove ".class" + strClassName = strClassName.substring(0, strClassName.length() - DOT_CLASS_LEN); + return strClassName; + } + + private static void findClassesInOnePath(String strPath, Set listClasses) throws IOException { + File file = new File(strPath); + if (file.isDirectory()) { + findClassesInPathsDir(strPath, file, listClasses); + } else if (file.exists()) { + ZipFile zipFile = null; + try { + zipFile = new ZipFile(file); + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + String strEntry = entries.nextElement().toString(); + if (strEntry.endsWith(DOT_CLASS)) { + listClasses.add(fixClassName(strEntry)); + } + } + } catch (IOException e) { + log.warn("Can not open the jar " + strPath + " " + e.getLocalizedMessage(),e); + } + finally { + if(zipFile != null) { + try {zipFile.close();} catch (Exception e) {} + } + } + } + } + + private static void findClassesInPaths(List listPaths, Set listClasses) throws IOException { + for (String path : listPaths) { + findClassesInOnePath(path, listClasses); + } + } + + private static void findClassesInPathsDir(String strPathElement, File dir, Set listClasses) throws IOException { + String[] list = dir.list(); + for (int i = 0; i < list.length; i++) { + File file = new File(dir, list[i]); + if (file.isDirectory()) { + // Recursive call + findClassesInPathsDir(strPathElement, file, listClasses); + } else if (list[i].endsWith(DOT_CLASS) && file.exists() && (file.length() != 0)) { + final String path = file.getPath(); + listClasses.add(path.substring(strPathElement.length() + 1, + path.lastIndexOf(".")) // $NON-NLS-1$ + .replace(File.separator.charAt(0), '.')); // $NON-NLS-1$ + } + } + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/org/apache/jorphan/reflect/ClassTools.java b/ApacheJmeter/src/org/apache/jorphan/reflect/ClassTools.java new file mode 100644 index 0000000..96f824f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/reflect/ClassTools.java @@ -0,0 +1,106 @@ +/* + * 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.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.apache.commons.lang.ClassUtils; +import org.apache.jorphan.util.JMeterException; + +/** + * Utility methods for handling dynamic access to classes. + */ +public class ClassTools { + + + /** + * Call no-args constructor for a class. + * + * @param className + * @return an instance of the class + * @throws JMeterException if class cannot be created + */ + public static Object construct(String className) throws JMeterException { + Object instance = null; + try { + instance = ClassUtils.getClass(className).newInstance(); + } catch (ClassNotFoundException e) { + throw new JMeterException(e); + } catch (InstantiationException e) { + throw new JMeterException(e); + } catch (IllegalAccessException e) { + throw new JMeterException(e); + } + return instance; + } + + /** + * Call a class constructor with an integer parameter + * @param className + * @param parameter (integer) + * @return an instance of the class + * @throws JMeterException if class cannot be created + */ + public static Object construct(String className, int parameter) throws JMeterException + { + Object instance = null; + try { + Class clazz = ClassUtils.getClass(className); + clazz.getConstructor(new Class [] {Integer.TYPE}); + instance = ClassUtils.getClass(className).newInstance(); + } catch (ClassNotFoundException e) { + throw new JMeterException(e); + } catch (InstantiationException e) { + throw new JMeterException(e); + } catch (IllegalAccessException e) { + throw new JMeterException(e); + } catch (SecurityException e) { + throw new JMeterException(e); + } catch (NoSuchMethodException e) { + throw new JMeterException(e); + } + return instance; + } + + /** + * Invoke a public method on a class instance + * + * @param instance + * @param methodName + * @throws SecurityException + * @throws IllegalArgumentException + * @throws JMeterException + */ + public static void invoke(Object instance, String methodName) + throws SecurityException, IllegalArgumentException, JMeterException + { + Method m; + try { + m = ClassUtils.getPublicMethod(instance.getClass(), methodName, new Class [] {}); + m.invoke(instance, (Object [])null); + } catch (NoSuchMethodException e) { + throw new JMeterException(e); + } catch (IllegalAccessException e) { + throw new JMeterException(e); + } catch (InvocationTargetException e) { + throw new JMeterException(e); + } + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/reflect/Functor.java b/ApacheJmeter/src/org/apache/jorphan/reflect/Functor.java new file mode 100644 index 0000000..4d70489 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/reflect/Functor.java @@ -0,0 +1,496 @@ +/* + * 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.reflect.Method; +import java.util.Arrays; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JMeterError; +import org.apache.log.Logger; + +/** + * Implements function call-backs. + * + * Functors may be defined for instance objects or classes. + * + * The method is created on first use, which allows the invokee (class or instance) + * to be omitted from the constructor. + * + * The class name takes precedence over the instance. + * + * If a functor is created with a particular instance, then that is used for all future calls; + * if an object is provided, it is ignored. + * This allows easy override of the table model behaviour. + * + * If an argument list is provided in the constructor, then that is ignored in subsequent invoke() calls. + * + * Usage: + * f = new Functor("methodName") + * o = f.invoke(object) - OR - + * o = f.invoke(object,params) + * + * f2 = new Functor(object,"methodName"); + * o = f2.invoke() - OR - + * o = f2.invoke(params) + * + * f3 = new Functor(class,"methodName"); + * o = f3.invoke(object) - will be ignored + * o = f3.invoke() - OR - + * o = f3.invoke(params) + * o = f3.invoke(object,params) - object will be ignored + * + */ +public class Functor { + private static final Logger log = LoggingManager.getLoggerForClass(); + + /* + * If non-null, then any object provided to invoke() is ignored. + */ + private final Object invokee; + + /* + * Class to be used to create the Method. + * Will be non-null if either Class or Object was provided during construction. + * + * Can be used instead of invokee, e.g. when using interfaces. + */ + private final Class clazz; + + // Methondname must always be provided. + private final String methodName; + + /* + * If non-null, then any argument list passed to invoke() will be ignored. + */ + private Object[] args; + + /* + * Argument types used to create the method. + * May be provided explicitly, or derived from the constructor argument list. + */ + private final Class[] types; + + /* + * This depends on the class or invokee and either args or types; + * it is set once by doCreateMethod(), which must be the only method to access it. + */ + private Method methodToInvoke; + + Functor(){ + throw new IllegalArgumentException("Must provide at least one argument"); + } + + /** + * Create a functor with the invokee and a method name. + * + * The invokee will be used in all future invoke calls. + * + * @param _invokee object on which to invoke the method + * @param _methodName method name + */ + public Functor(Object _invokee, String _methodName) { + this(null, _invokee, _methodName, null, null); + } + + /** + * Create a functor from class and method name. + * This is useful for methods defined in interfaces. + * + * The actual invokee must be provided in all invoke() calls, + * and must be an instance of the class. + * + * @param _clazz class to be used + * @param _methodName method name + */ + public Functor(Class _clazz, String _methodName) { + this(_clazz, null, _methodName, null, null); + } + + /** + * Create a functor with the invokee, method name, and argument class types. + * + * The invokee will be ignored in any invoke() calls. + * + * @param _invokee object on which to invoke the method + * @param _methodName method name + * @param _types + */ + public Functor(Object _invokee, String _methodName, Class[] _types) { + this(null, _invokee, _methodName, null, _types); + } + + /** + * Create a functor with the class, method name, and argument class types. + * + * Subsequent invoke() calls must provide the appropriate ivokee object. + * + * @param _clazz the class in which to find the method + * @param _methodName method name + * @param _types + */ + public Functor(Class _clazz, String _methodName, Class[] _types) { + this(_clazz, null, _methodName, null, _types); + } + + /** + * Create a functor with just the method name. + * + * The invokee and any parameters must be provided in all invoke() calls. + * + * @param _methodName method name + */ + public Functor(String _methodName) { + this(null, null, _methodName, null, null); + } + + /** + * Create a functor with the method name and argument class types. + * + * The invokee must be provided in all invoke() calls + * + * @param _methodName method name + * @param _types parameter types + */ + public Functor(String _methodName, Class[] _types) { + this(null, null, _methodName, null, _types); + } + + /** + * Create a functor with an invokee, method name, and argument values. + * + * The invokee will be ignored in any invoke() calls. + * + * @param _invokee object on which to invoke the method + * @param _methodName method name + * @param _args arguments to be passed to the method + */ + public Functor(Object _invokee, String _methodName, Object[] _args) { + this(null, _invokee, _methodName, _args, null); + } + + /** + * Create a functor from method name and arguments. + * + * The class will be determined from the first invoke call. + * All invoke calls must include a target object; + * which must be of the same type as the initial invokee. + * + * @param _methodName method name + * @param _args + */ + public Functor(String _methodName, Object[] _args) { + this(null, null, _methodName, _args, null); + } + + /** + * Create a functor from various different combinations of parameters. + * + * @param _clazz class containing the method + * @param _invokee invokee to use for the method call + * @param _methodName the method name (required) + * @param _args arguments to be used + * @param _types types of arguments to be used + * + * @throws IllegalArgumentException if: + * - methodName is null + * - both class and invokee are specified + * - both arguments and types are specified + */ + private Functor(Class _clazz, Object _invokee, String _methodName, Object[] _args, Class[] _types) { + if (_methodName == null){ + throw new IllegalArgumentException("Methodname must not be null"); + } + if (_clazz != null && _invokee != null){ + throw new IllegalArgumentException("Cannot provide both Class and Object"); + } + if (_args != null && _types != null){ + throw new IllegalArgumentException("Cannot provide both arguments and argument types"); + } + // If class not provided, default to invokee class, else null + this.clazz = _clazz != null ? _clazz : (_invokee != null ? _invokee.getClass() : null); + this.invokee = _invokee; + this.methodName = _methodName; + this.args = _args; + // If types not provided, default to argument types, else null + this.types = _types != null ? _types : (_args != null ? _getTypes(_args) : null); + } + + ////////////////////////////////////////// + + /* + * Low level invocation routine. + * + * Should only be called after any defaults have been applied. + * + */ + private Object doInvoke(Class _class, Object _invokee, Object[] _args) { + Class[] argTypes = getTypes(_args); + try { + Method method = doCreateMethod(_class , argTypes); + if (method == null){ + final String message = "Can't find method " + +_class.getName()+"#"+methodName+typesToString(argTypes); + log.error(message, new Throwable()); + throw new JMeterError(message); + } + return method.invoke(_invokee, _args); + } catch (Exception e) { + final String message = "Trouble functing: " + +_class.getName() + +"."+methodName+"(...) : " + +" invokee: "+_invokee + +" "+e.getMessage(); + log.warn(message, e); + throw new JMeterError(message,e); + } + } + + /** + * Invoke a Functor, which must have been created with either a class name or object. + * + * @return the object if any + */ + public Object invoke() { + if (invokee == null) { + throw new IllegalStateException("Cannot call invoke() - invokee not known"); + } + // If invokee was provided, then clazz has been set up + return doInvoke(clazz, invokee, getArgs()); + } + + /** + * Invoke the method on a given object. + * + * @param p_invokee - provides the object to call; ignored if the class or object were provided to the constructor + * @return the value + */ + public Object invoke(Object p_invokee) { + return invoke(p_invokee, getArgs()); + } + + /** + * Invoke the method with the provided parameters. + * + * The invokee must have been provided in the constructor. + * + * @param p_args parameters for the method + * @return the value + */ + public Object invoke(Object[] p_args) { + if (invokee == null){ + throw new IllegalStateException("Invokee was not provided in constructor"); + } + // If invokee was provided, then clazz has been set up + return doInvoke(clazz, invokee, args != null? args : p_args); + } + + /** + * Invoke the method on the invokee with the provided parameters. + * + * The invokee must agree with the class (if any) provided at construction time. + * + * If the invokee was provided at construction time, then this invokee will be ignored. + * If actual arguments were provided at construction time, then arguments will be ignored. + * + */ + public Object invoke(Object p_invokee, Object[] p_args) { + return doInvoke(clazz != null ? clazz : p_invokee.getClass(), // Use constructor class if present + invokee != null ? invokee : p_invokee, // use invokee if provided + args != null? args : p_args);// use argumenrs if provided + } + + /* + * Low-level (recursive) routine to define the method - if not already defined. + * Synchronized to protect access to methodToInvoke. + */ + private synchronized Method doCreateMethod(Class p_class, Class[] p_types) { + if (log.isDebugEnabled()){ + log.debug("doCreateMethod() using "+this.toString() + +"class=" + + p_class.getName() + + " types: " + Arrays.asList(p_types)); + } + if (methodToInvoke == null) { + try { + methodToInvoke = p_class.getMethod(methodName, p_types); + } catch (Exception e) { + for (int i = 0; i < p_types.length; i++) { + Class primitive = getPrimitive(p_types[i]); + if (primitive != null) { + methodToInvoke = doCreateMethod(p_class, getNewArray(i, primitive, p_types)); + if (methodToInvoke != null) { + return methodToInvoke; + } + } + Class[] interfaces = p_types[i].getInterfaces(); + for (int j = 0; j < interfaces.length; j++) { + methodToInvoke = doCreateMethod(p_class,getNewArray(i, interfaces[j], p_types)); + if (methodToInvoke != null) { + return methodToInvoke; + } + } + Class parent = p_types[i].getSuperclass(); + if (parent != null) { + methodToInvoke = doCreateMethod(p_class,getNewArray(i, parent, p_types)); + if (methodToInvoke != null) { + return methodToInvoke; + } + } + } + } + } + return methodToInvoke; + } + + /** + * Check if a read Functor method is valid. + * + * @deprecated ** for use by Unit test code only ** + * + * @return true if method exists + */ + @Deprecated + public boolean checkMethod(Object _invokee){ + Method m = null; + try { + m = doCreateMethod(_invokee.getClass(), getTypes(args)); + } catch (Exception e){ + // ignored + } + return null != m; + } + + /** + * Check if a write Functor method is valid. + * + * @deprecated ** for use by Unit test code only ** + * + * @return true if method exists + */ + @Deprecated + public boolean checkMethod(Object _invokee, Class c){ + Method m = null; + try { + m = doCreateMethod(_invokee.getClass(), new Class[]{c}); + } catch (Exception e){ + // ignored + } + return null != m; + } + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(100); + if (clazz != null){ + sb.append(clazz.getName()); + } + if (invokee != null){ + sb.append("@"); + sb.append(System.identityHashCode(invokee)); + } + sb.append("."); + sb.append(methodName); + typesToString(sb,types); + return sb.toString(); + } + + private void typesToString(StringBuilder sb,Class[] _types) { + sb.append("("); + if (_types != null){ + for(int i=0; i < _types.length; i++){ + if (i>0) { + sb.append(","); + } + sb.append(_types[i].getName()); + } + } + sb.append(")"); + } + + private String typesToString(Class[] argTypes) { + StringBuilder sb = new StringBuilder(); + typesToString(sb,argTypes); + return sb.toString(); + } + + private Class getPrimitive(Class t) { + if (t==null) { + return null; + } + if (t.equals(Integer.class)) { + return int.class; + } else if (t.equals(Long.class)) { + return long.class; + } else if (t.equals(Double.class)) { + return double.class; + } else if (t.equals(Float.class)) { + return float.class; + } else if (t.equals(Byte.class)) { + return byte.class; + } else if (t.equals(Boolean.class)) { + return boolean.class; + } else if (t.equals(Short.class)) { + return short.class; + } else if (t.equals(Character.class)) { + return char.class; + } + return null; + } + + private Class[] getNewArray(int i, Class replacement, Class[] orig) { + Class[] newArray = new Class[orig.length]; + for (int j = 0; j < newArray.length; j++) { + if (j == i) { + newArray[j] = replacement; + } else { + newArray[j] = orig[j]; + } + } + return newArray; + } + + private Class[] getTypes(Object[] _args) { + if (types == null) + { + return _getTypes(_args); + } + return types; + } + + private static Class[] _getTypes(Object[] _args) { + Class[] _types; + if (_args != null) { + _types = new Class[_args.length]; + for (int i = 0; i < _args.length; i++) { + _types[i] = _args[i].getClass(); + } + } else { + _types = new Class[0]; + } + return _types; + } + + private Object[] getArgs() { + if (args == null) { + args = new Object[0]; + } + return args; + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/test/UnitTestManager.java b/ApacheJmeter/src/org/apache/jorphan/test/UnitTestManager.java new file mode 100644 index 0000000..c4d93ed --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/test/UnitTestManager.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.test; + +/** + * Implement this interface to work with the AllTests class. This interface + * allows AllTests to pass a configuration file to your application before + * running the junit unit tests. + *

+ * N.B. This interface must be in the main src/ tree (not test/) because it is + * implemented by JMeterUtils + *

+ * see JUnit class: org.apache.jorphan.test.AllTests + */ +public interface UnitTestManager { + /** + * Your implementation will be handed the filename that was provided to + * AllTests as a configuration file. It can hold whatever properties you + * need to configure your system prior to the unit tests running. + * + * @param filename + */ + public void initializeProperties(String filename); +} diff --git a/ApacheJmeter/src/org/apache/jorphan/util/Converter.java b/ApacheJmeter/src/org/apache/jorphan/util/Converter.java new file mode 100644 index 0000000..5e1e09f --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/util/Converter.java @@ -0,0 +1,383 @@ +/* + * 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.util; + +import java.io.File; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.StringTokenizer; + +/** + * Converter utilities for TestBeans + */ +public class Converter { + + /** + * Convert the given value object to an object of the given type + * + * @param value + * @param toType + * @return Object + */ + public static Object convert(Object value, Class toType) { + if (value == null) { + value = ""; // TODO should we allow null for non-primitive types? + } else if (toType.isAssignableFrom(value.getClass())) { + return value; + } else if (toType.equals(float.class) || toType.equals(Float.class)) { + return Float.valueOf(getFloat(value)); + } else if (toType.equals(double.class) || toType.equals(Double.class)) { + return Double.valueOf(getDouble(value)); + } else if (toType.equals(String.class)) { + return getString(value); + } else if (toType.equals(int.class) || toType.equals(Integer.class)) { + return Integer.valueOf(getInt(value)); + } else if (toType.equals(char.class) || toType.equals(Character.class)) { + return Character.valueOf(getChar(value)); + } else if (toType.equals(long.class) || toType.equals(Long.class)) { + return Long.valueOf(getLong(value)); + } else if (toType.equals(boolean.class) || toType.equals(Boolean.class)) { + return Boolean.valueOf(getBoolean(value)); + } else if (toType.equals(java.util.Date.class)) { + return getDate(value); + } else if (toType.equals(Calendar.class)) { + return getCalendar(value); + } else if (toType.equals(File.class)) { + return getFile(value); + } else if (toType.equals(Class.class)) { + try { + return Class.forName(value.toString()); + } catch (Exception e) { + // don't do anything + } + } + return value; + } + + /** + * Converts the given object to a calendar object. Defaults to the current + * date if the given object can't be converted. + * + * @param date + * @return Calendar + */ + public static Calendar getCalendar(Object date, Calendar defaultValue) { + Calendar cal = new GregorianCalendar(); + if (date != null && date instanceof java.util.Date) { + cal.setTime((java.util.Date) date); + return cal; + } else if (date != null) { + DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT); + java.util.Date d = null; + try { + d = formatter.parse(date.toString()); + } catch (ParseException e) { + formatter = DateFormat.getDateInstance(DateFormat.MEDIUM); + try { + d = formatter.parse((String) date); + } catch (ParseException e1) { + formatter = DateFormat.getDateInstance(DateFormat.LONG); + try { + d = formatter.parse((String) date); + } catch (ParseException e2) { + formatter = DateFormat.getDateInstance(DateFormat.FULL); + try { + d = formatter.parse((String) date); + } catch (ParseException e3) { + return defaultValue; + } + } + } + } + cal.setTime(d); + } else { + cal = defaultValue; + } + return cal; + } + + public static Calendar getCalendar(Object o) { + return getCalendar(o, new GregorianCalendar()); + } + + public static Date getDate(Object date) { + return getDate(date, Calendar.getInstance().getTime()); + } + + public static Date getDate(Object date, Date defaultValue) { + Date val = null; + if (date != null && date instanceof java.util.Date) { + return (Date) date; + } else if (date != null) { + DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT); + // java.util.Date d = null; + try { + val = formatter.parse(date.toString()); + } catch (ParseException e) { + formatter = DateFormat.getDateInstance(DateFormat.MEDIUM); + try { + val = formatter.parse((String) date); + } catch (ParseException e1) { + formatter = DateFormat.getDateInstance(DateFormat.LONG); + try { + val = formatter.parse((String) date); + } catch (ParseException e2) { + formatter = DateFormat.getDateInstance(DateFormat.FULL); + try { + val = formatter.parse((String) date); + } catch (ParseException e3) { + return defaultValue; + } + } + } + } + } else { + return defaultValue; + } + return val; + } + + public static float getFloat(Object o, float defaultValue) { + try { + if (o == null) { + return defaultValue; + } + if (o instanceof Number) { + return ((Number) o).floatValue(); + } + return Float.parseFloat(o.toString()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public static float getFloat(Object o) { + return getFloat(o, 0); + } + + public static double getDouble(Object o, double defaultValue) { + try { + if (o == null) { + return defaultValue; + } + if (o instanceof Number) { + return ((Number) o).doubleValue(); + } + return Double.parseDouble(o.toString()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public static double getDouble(Object o) { + return getDouble(o, 0); + } + + public static boolean getBoolean(Object o) { + return getBoolean(o, false); + } + + public static boolean getBoolean(Object o, boolean defaultValue) { + if (o == null) { + return defaultValue; + } else if (o instanceof Boolean) { + return ((Boolean) o).booleanValue(); + } + return Boolean.valueOf(o.toString()).booleanValue(); + } + + /** + * Convert object to integer, return defaultValue if object is not + * convertible or is null. + * + * @param o + * @param defaultValue + * @return int + */ + public static int getInt(Object o, int defaultValue) { + try { + if (o == null) { + return defaultValue; + } + if (o instanceof Number) { + return ((Number) o).intValue(); + } + return Integer.parseInt(o.toString()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public static char getChar(Object o) { + return getChar(o, ' '); + } + + public static char getChar(Object o, char defaultValue) { + try { + if (o == null) { + return defaultValue; + } + if (o instanceof Character) { + return ((Character) o).charValue(); + } else if (o instanceof Byte) { + return (char) ((Byte) o).byteValue(); + } else if (o instanceof Integer) { + return (char) ((Integer) o).intValue(); + } else { + String s = o.toString(); + if (s.length() > 0) { + return o.toString().charAt(0); + } + return defaultValue; + } + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Converts object to an integer, defaults to 0 if object is not convertible + * or is null. + * + * @param o + * @return int + */ + public static int getInt(Object o) { + return getInt(o, 0); + } + + /** + * Converts object to a long, return defaultValue if object is not + * convertible or is null. + * + * @param o + * @param defaultValue + * @return long + */ + public static long getLong(Object o, long defaultValue) { + try { + if (o == null) { + return defaultValue; + } + if (o instanceof Number) { + return ((Number) o).longValue(); + } + return Long.parseLong(o.toString()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * Converts object to a long, defaults to 0 if object is not convertible or + * is null + * + * @param o + * @return long + */ + public static long getLong(Object o) { + return getLong(o, 0); + } + + public static String formatDate(Date date, String pattern) { + if (date == null) { + return ""; + } + SimpleDateFormat format = new SimpleDateFormat(pattern); + return format.format(date); + } + + public static String formatDate(java.sql.Date date, String pattern) { + if (date == null) { + return ""; + } + SimpleDateFormat format = new SimpleDateFormat(pattern); + return format.format(date); + } + + public static String formatDate(String date, String pattern) { + return formatDate(getCalendar(date, null), pattern); + } + + public static String formatDate(Calendar date, String pattern) { + return formatCalendar(date, pattern); + } + + public static String formatCalendar(Calendar date, String pattern) { + if (date == null) { + return ""; + } + SimpleDateFormat format = new SimpleDateFormat(pattern); + return format.format(date.getTime()); + } + + /** + * Converts object to a String, return defaultValue if object is null. + * + * @param o + * @param defaultValue + * @return String + */ + public static String getString(Object o, String defaultValue) { + if (o == null) { + return defaultValue; + } + return o.toString(); + } + + public static String insertLineBreaks(String v, String insertion) { + if (v == null) { + return ""; + } + StringBuilder replacement = new StringBuilder(); + StringTokenizer tokens = new StringTokenizer(v, "\n", true); + while (tokens.hasMoreTokens()) { + String token = tokens.nextToken(); + if (token.compareTo("\n") == 0) { + replacement.append(insertion); + } else { + replacement.append(token); + } + } + return replacement.toString(); + } + + /** + * Converts object to a String, defaults to empty string if object is null. + * + * @param o + * @return String + */ + public static String getString(Object o) { + return getString(o, ""); + } + + public static File getFile(Object o){ + if (o instanceof File) { + return (File) o; + } + if (o instanceof String) { + return new File((String) o); + } + throw new IllegalArgumentException("Expected String or file, actual "+o.getClass().getName()); + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/util/HeapDumper.java b/ApacheJmeter/src/org/apache/jorphan/util/HeapDumper.java new file mode 100644 index 0000000..c29d7b5 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/util/HeapDumper.java @@ -0,0 +1,208 @@ +/* + * 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.util; + +import java.io.File; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.management.InstanceNotFoundException; +import javax.management.MBeanException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.RuntimeMBeanException; + +/** + * Class allowing access to Sun's heapDump method (Java 1.6+). + * Uses Reflection so that the code compiles on Java 1.5. + * The code will only work on Sun Java 1.6+. + */ +public class HeapDumper { + + // SingletonHolder idiom for lazy initialisation + private static class DumperHolder { + private static final HeapDumper DUMPER = new HeapDumper(); + } + + private static HeapDumper getInstance(){ + return DumperHolder.DUMPER; + } + + // This is the name of the HotSpot Diagnostic platform MBean (Sun Java 1.6) + // See: http://download.oracle.com/javase/6/docs/jre/api/management/extension/com/sun/management/HotSpotDiagnosticMXBean.html + private static final String HOTSPOT_BEAN_NAME = + "com.sun.management:type=HotSpotDiagnostic"; + + // These are needed for invoking the method + private final MBeanServer server; + private final ObjectName hotspotDiagnosticBean; + + // If we could not find the method, store the exception here + private final Exception exception; + + // Only invoked by IODH class + private HeapDumper() { + server = ManagementFactory.getPlatformMBeanServer(); // get the platform beans + ObjectName on = null; + Exception ex = null; + try { + on = new ObjectName(HOTSPOT_BEAN_NAME); // should never fail + server.getObjectInstance(on); // See if we can actually find the object + } catch (MalformedObjectNameException e) { // Should never happen + throw new AssertionError("Could not establish the HotSpotDiagnostic Bean Name: "+e); + } catch (InstanceNotFoundException e) { + ex = e; + on = null; // Prevent useless dump attempts + } + exception = ex; + hotspotDiagnosticBean = on; + } + + /** + * Initialise the dumper, and report if there is a problem. + * This is optional, as the dump methods will initialise if necessary. + * + * @throws Exception if there is a problem finding the heapDump MXBean + */ + public static void init() throws Exception { + Exception e =getInstance().exception; + if (e != null) { + throw e; + } + } + + /** + * Dumps the heap to the outputFile file in the same format as the hprof heap dump. + *

+ * Calls the dumpHeap() method of the HotSpotDiagnostic MXBean, if available. + *

+ * See + * + * HotSpotDiagnosticMXBean + * + * @param fileName name of the heap dump file. Must be creatable, i.e. must not exist. + * @param live if true, dump only the live objects + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + public static void dumpHeap(String fileName, boolean live) throws Exception{ + getInstance().dumpHeap0(fileName, live); + } + + /** + * Dumps live objects from the heap to the outputFile file in the same format as the hprof heap dump. + *

+ * @see #dumpHeap(String, boolean) + * @param fileName name of the heap dump file. Must be creatable, i.e. must not exist. + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + public static void dumpHeap(String fileName) throws Exception{ + dumpHeap(fileName, true); + } + + /** + * Dumps live objects from the heap to the outputFile file in the same format as the hprof heap dump. + *

+ * Creates the dump using the file name: dump_yyyyMMdd_hhmmss_SSS.hprof + * The dump is created in the current directory. + *

+ * @see #dumpHeap(boolean) + * @return the name of the dump file that was created + * @throws IOException + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + public static String dumpHeap() throws Exception{ + return dumpHeap(true); + } + + /** + * Dumps objects from the heap to the outputFile file in the same format as the hprof heap dump. + *

+ * Creates the dump using the file name: dump_yyyyMMdd_hhmmss_SSS.hprof + * The dump is created in the current directory. + *

+ * @see #dumpHeap(String, boolean) + * @param live true id only live objects are to be dumped. + * + * @return the name of the dump file that was created + * @throws IOException + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + public static String dumpHeap(boolean live) throws Exception { + return dumpHeap(new File("."), live); + } + + /** + * Dumps objects from the heap to the outputFile file in the same format as the hprof heap dump. + * The dump is created in the specified directory. + *

+ * Creates the dump using the file name: dump_yyyyMMdd_hhmmss_SSS.hprof + *

+ * @see #dumpHeap(String, boolean) + * @param basedir File object for the target base directory. + * @param live true id only live objects are to be dumped. + * + * @return the name of the dump file that was created + * @throws IOException + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + public static String dumpHeap(File basedir, boolean live) throws Exception { + SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyyMMdd_hhmmss_SSS"); + String stamp = timestampFormat.format(new Date()); + File temp = new File(basedir,"dump_"+stamp+".hprof"); + final String path = temp.getPath(); + dumpHeap(path, live); + return path; + } + + /** + * Perform the dump using the dumpHeap method. + * + * @param fileName the file to use + * @param live true to dump only live objects + * @throws Exception if the MXBean cannot be found, or if there is a problem during invocation + */ + private void dumpHeap0(String fileName, boolean live) throws Exception { + try { + if (exception == null) { + server.invoke(hotspotDiagnosticBean, + "dumpHeap", + new Object[]{fileName, Boolean.valueOf(live)}, + new String[]{"java.lang.String", "boolean"}); + } else { + throw exception; + } + } catch (RuntimeMBeanException e) { + Throwable f = e.getCause(); + if (f instanceof Exception){ + throw (Exception )f; + } + throw(e); + } catch (MBeanException e) { + Throwable f = e.getCause(); + if (f instanceof Exception){ + throw (Exception )f; + } + throw(e); + } + } +} + diff --git a/ApacheJmeter/src/org/apache/jorphan/util/JMeterError.java b/ApacheJmeter/src/org/apache/jorphan/util/JMeterError.java new file mode 100644 index 0000000..3af9da2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/util/JMeterError.java @@ -0,0 +1,47 @@ +/* + * 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.util; + +/** + * The rationale for this class was originally to support chained Errors in JDK 1.3 + * However, the class is now used in its own right. + * + * @version $Revision: 905024 $ + */ +public class JMeterError extends Error { + + private static final long serialVersionUID = 240L; + + public JMeterError() { + super(); + } + + public JMeterError(String s) { + super(s); + } + + public JMeterError(Throwable cause) { + super(cause); + } + + public JMeterError(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/ApacheJmeter/src/org/apache/jorphan/util/JMeterException.java b/ApacheJmeter/src/org/apache/jorphan/util/JMeterException.java new file mode 100644 index 0000000..9677b5b --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/util/JMeterException.java @@ -0,0 +1,46 @@ +/* + * 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.util; + +/** + * The rationale for this class was originally to support chained Exceptions in JDK 1.3 + * However, the class is now used in its own right. + * + * @version $Revision: 905024 $ + */ +public class JMeterException extends Exception { + + private static final long serialVersionUID = 240L; + + public JMeterException() { + super(); + } + + public JMeterException(String s) { + super(s); + } + + public JMeterException(Throwable cause) { + super(cause); + } + + public JMeterException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/util/JMeterStopTestException.java b/ApacheJmeter/src/org/apache/jorphan/util/JMeterStopTestException.java new file mode 100644 index 0000000..a062b01 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/util/JMeterStopTestException.java @@ -0,0 +1,37 @@ +/* + * 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.util; + +/** + * This Exception is for use by functions etc to signal a Stop Test condition + * where there is no access to the normal stop method + * + * @version $Revision: 905024 $ + */ +public class JMeterStopTestException extends RuntimeException { + private static final long serialVersionUID = 240L; + + public JMeterStopTestException() { + super(); + } + + public JMeterStopTestException(String s) { + super(s); + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/util/JMeterStopTestNowException.java b/ApacheJmeter/src/org/apache/jorphan/util/JMeterStopTestNowException.java new file mode 100644 index 0000000..736485e --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/util/JMeterStopTestNowException.java @@ -0,0 +1,37 @@ +/* + * 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.util; + +/** + * This Exception is for use by functions etc to signal a Stop Test Now condition + * where there is no access to the normal stop method + * + * @version $Revision: 905024 $ + */ +public class JMeterStopTestNowException extends RuntimeException { + private static final long serialVersionUID = 240L; + + public JMeterStopTestNowException() { + super(); + } + + public JMeterStopTestNowException(String s) { + super(s); + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/util/JMeterStopThreadException.java b/ApacheJmeter/src/org/apache/jorphan/util/JMeterStopThreadException.java new file mode 100644 index 0000000..9fa07c0 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/util/JMeterStopThreadException.java @@ -0,0 +1,37 @@ +/* + * 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.util; + +/** + * This Exception is for use by functions etc to signal a Stop Thread condition + * where there is no access to the normal stop method + * + * @version $Revision: 905024 $ + */ +public class JMeterStopThreadException extends RuntimeException { + private static final long serialVersionUID = 240L; + + public JMeterStopThreadException() { + super(); + } + + public JMeterStopThreadException(String s) { + super(s); + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/util/JOrphanUtils.java b/ApacheJmeter/src/org/apache/jorphan/util/JOrphanUtils.java new file mode 100644 index 0000000..372ef00 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/util/JOrphanUtils.java @@ -0,0 +1,496 @@ +/* + * 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.util; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +/** + * This class contains frequently-used static utility methods. + * + */ + +// @see TestJorphanUtils for unit tests + +public final class JOrphanUtils { + + /** + * Private constructor to prevent instantiation. + */ + private JOrphanUtils() { + } + + /** + * This is _almost_ equivalent to the String.split method in JDK 1.4. It is + * here to enable us to support earlier JDKs. + * + * Note that unlike JDK1.4 split(), it optionally ignores leading split Characters, + * and the splitChar parameter is not a Regular expression + * + *

+ * This piece of code used to be part of JMeterUtils, but was moved here + * because some JOrphan classes use it too. + * + * @param splittee + * String to be split + * @param splitChar + * Character(s) to split the string on, these are treated as a single unit + * @param truncate + * Should adjacent and leading/trailing splitChars be removed? + * + * @return Array of all the tokens. + * + * @see #split(String, String, String) + * + */ + public static String[] split(String splittee, String splitChar,boolean truncate) { + if (splittee == null || splitChar == null) { + return new String[0]; + } + final String EMPTY_ELEMENT = ""; + int spot; + final int splitLength = splitChar.length(); + final String adjacentSplit = splitChar + splitChar; + final int adjacentSplitLength = adjacentSplit.length(); + if(truncate) { + while ((spot = splittee.indexOf(adjacentSplit)) != -1) { + splittee = splittee.substring(0, spot + splitLength) + + splittee.substring(spot + adjacentSplitLength, splittee.length()); + } + if(splittee.startsWith(splitChar)) { + splittee = splittee.substring(splitLength); + } + if(splittee.endsWith(splitChar)) { // Remove trailing splitter + splittee = splittee.substring(0,splittee.length()-splitLength); + } + } + List returns = new ArrayList(); + final int length = splittee.length(); // This is the new length + int start = 0; + spot = 0; + while (start < length && (spot = splittee.indexOf(splitChar, start)) > -1) { + if (spot > 0) { + returns.add(splittee.substring(start, spot)); + } + else + { + returns.add(EMPTY_ELEMENT); + } + start = spot + splitLength; + } + if (start < length) { + returns.add(splittee.substring(start)); + } else if (spot == length - splitLength){// Found splitChar at end of line + returns.add(EMPTY_ELEMENT); + } + return returns.toArray(new String[returns.size()]); + } + + public static String[] split(String splittee,String splitChar) + { + return split(splittee,splitChar,true); + } + + /** + * Takes a String and a tokenizer character string, and returns a new array of + * strings of the string split by the tokenizer character(s). + * + * Trailing delimiters are significant (unless the default = null) + * + * @param splittee + * String to be split. + * @param delims + * Delimiter character(s) to split the string on + * @param def + * Default value to place between two split chars that have + * nothing between them. If null, then ignore omitted elements. + * + * @return Array of all the tokens. + * + * @throws NullPointerException if splittee or delims are null + * + * @see #split(String, String, boolean) + * @see #split(String, String) + * + * This is a rewritten version of JMeterUtils.split() + */ + public static String[] split(String splittee, String delims, String def) { + StringTokenizer tokens = new StringTokenizer(splittee,delims,def!=null); + boolean lastWasDelim=false; + List strList=new ArrayList(); + while (tokens.hasMoreTokens()) { + String tok=tokens.nextToken(); + if ( tok.length()==1 // we have a single character; could be a token + && delims.indexOf(tok)!=-1) // it is a token + { + if (lastWasDelim) {// we saw a delimiter last time + strList.add(def);// so add the default + } + lastWasDelim=true; + } else { + lastWasDelim=false; + strList.add(tok); + } + } + if (lastWasDelim) { + strList.add(def); + } + return strList.toArray(new String[strList.size()]); + } + + + private static final String SPACES = " "; + + private static final int SPACES_LEN = SPACES.length(); + + /** + * Right aligns some text in a StringBuilder N.B. modifies the input buffer + * + * @param in + * StringBuilder containing some text + * @param len + * output length desired + * @return input StringBuilder, with leading spaces + */ + public static StringBuilder rightAlign(StringBuilder in, int len) { + int pfx = len - in.length(); + if (pfx <= 0) { + return in; + } + if (pfx > SPACES_LEN) { + pfx = SPACES_LEN; + } + in.insert(0, SPACES.substring(0, pfx)); + return in; + } + + /** + * Left aligns some text in a StringBuilder N.B. modifies the input buffer + * + * @param in + * StringBuilder containing some text + * @param len + * output length desired + * @return input StringBuilder, with trailing spaces + */ + public static StringBuilder leftAlign(StringBuilder in, int len) { + int sfx = len - in.length(); + if (sfx <= 0) { + return in; + } + if (sfx > SPACES_LEN) { + sfx = SPACES_LEN; + } + in.append(SPACES.substring(0, sfx)); + return in; + } + + /** + * Convert a boolean to its upper case string representation. + * Equivalent to Boolean.valueOf(boolean).toString().toUpperCase(). + * + * @param value + * boolean to convert + * @return "TRUE" or "FALSE" + */ + public static String booleanToSTRING(boolean value) { + return value ? "TRUE" : "FALSE"; + } + + /** + * Simple-minded String.replace() for JDK1.3 Should probably be recoded... + * + * @param source + * input string + * @param search + * string to look for (no regular expressions) + * @param replace + * string to replace the search string + * @return the output string + */ + public static String replaceFirst(String source, String search, String replace) { + int start = source.indexOf(search); + int len = search.length(); + if (start == -1) { + return source; + } + if (start == 0) { + return replace + source.substring(len); + } + return source.substring(0, start) + replace + source.substring(start + len); + } + + /** + * Version of String.replaceAll() for JDK1.3 + * See below for another version which replaces strings rather than chars + * + * @param source + * input string + * @param search + * char to look for (no regular expressions) + * @param replace + * string to replace the search string + * @return the output string + */ + public static String replaceAllChars(String source, char search, String replace) { + char[] chars = source.toCharArray(); + StringBuilder sb = new StringBuilder(source.length()+20); + for(char c : chars){ + if (c == search){ + sb.append(replace); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Replace all patterns in a String + * + * @see String#replaceAll(String regex,String replacement) - JDK1.4 only + * + * @param input - string to be transformed + * @param pattern - pattern to replace + * @param sub - replacement + * @return the updated string + */ + public static String substitute(final String input, final String pattern, final String sub) { + StringBuilder ret = new StringBuilder(input.length()); + int start = 0; + int index = -1; + final int length = pattern.length(); + while ((index = input.indexOf(pattern, start)) >= start) { + ret.append(input.substring(start, index)); + ret.append(sub); + start = index + length; + } + ret.append(input.substring(start)); + return ret.toString(); + } + + /** + * Trim a string by the tokens provided. + * + * @param input string to trim + * @param delims list of delimiters + * @return input trimmed at the first delimiter + */ + public static String trim(final String input, final String delims){ + StringTokenizer tokens = new StringTokenizer(input,delims); + return tokens.hasMoreTokens() ? tokens.nextToken() : ""; + } + + /** + * Returns a slice of a byte array. + * + * TODO - add bounds checking? + * + * @param array - + * input array + * @param begin - + * start of slice + * @param end - + * end of slice + * @return slice from the input array + */ + public static byte[] getByteArraySlice(byte[] array, int begin, int end) { + byte[] slice = new byte[(end - begin + 1)]; + int count = 0; + for (int i = begin; i <= end; i++) { + slice[count] = array[i]; + count++; + } + + return slice; + } + + // N.B. Commons IO IOUtils has equivalent methods; these were added before IO was included + // TODO - perhaps deprecate these in favour of Commons IO? + /** + * Close a Closeable with no error thrown + * @param cl - Closeable (may be null) + */ + public static void closeQuietly(Closeable cl){ + try { + if (cl != null) { + cl.close(); + } + } catch (IOException ignored) { + } + } + + /** + * close a Socket with no error thrown + * @param sock - Socket (may be null) + */ + public static void closeQuietly(Socket sock){ + try { + if (sock!= null) { + sock.close(); + } + } catch (IOException ignored) { + } + } + + /** + * close a Socket with no error thrown + * @param sock - ServerSocket (may be null) + */ + public static void closeQuietly(ServerSocket sock){ + try { + if (sock!= null) { + sock.close(); + } + } catch (IOException ignored) { + } + } + + /** + * Check if a byte array starts with the given byte array. + * + * @see String#startsWith(String, int) + * + * @param target array to scan + * @param search array to search for + * @param offset starting offset (>=0) + * @return true if the search array matches the target at the current offset + */ + public static boolean startsWith(byte [] target, byte [] search, int offset){ + final int targetLength = target.length; + final int searchLength = search.length; + if (offset < 0 || searchLength > targetLength+offset){ + return false; + } + for(int i=0;i < searchLength; i++){ + if (target[i+offset] != search[i]){ + return false; + } + } + return true; + } + + private static final byte[] XML_PFX = {'<','?','x','m','l'};// "> 4); + byte lower = (byte) (ba[i] & 0x0f); + hb[2*i]=toHexChar(upper); + hb[2*i+1]=toHexChar(lower); + } + return hb; + } + + private static byte toHexChar(byte in){ + if (in < 10) return (byte) (in+'0'); + return (byte) ((in-10)+'a'); + } + + /** + * Read as much as possible into buffer. + * + * @param is the stream to read from + * @param buffer output buffer + * @param offset offset into buffer + * @param length number of bytes to read + * + * @return the number of bytes actually read + * @throws IOException + */ + public static int read(InputStream is, byte[] buffer, int offset, int length) throws IOException { + int remaining = length; + while ( remaining > 0 ) { + int location = ( length - remaining ); + int count = is.read( buffer, location, remaining ); + if ( -1 == count ) { // EOF + break; + } + remaining -= count; + } + return length - remaining; + } + + /** + * Display currently running threads on system.out + * This may be expensive to run. + * Mainly designed for use at the end of a non-GUI test to check for threads that might prevent the JVM from exitting. + * + * @param includeDaemons whether to include daemon threads or not. + */ + public static void displayThreads(boolean includeDaemons) { + Map m = Thread.getAllStackTraces(); + String lineSeparator = System.getProperty("line.separator"); + for(Map.Entry e : m.entrySet()) { + boolean daemon = e.getKey().isDaemon(); + if (includeDaemons || !daemon){ + StringBuilder builder = new StringBuilder(); + StackTraceElement[] ste = e.getValue(); + for (StackTraceElement stackTraceElement : ste) { + int lineNumber = stackTraceElement.getLineNumber(); + builder.append(stackTraceElement.getClassName()+"#"+stackTraceElement.getMethodName()+ + (lineNumber >=0 ? " at line:"+ stackTraceElement.getLineNumber() : "")+lineSeparator); + } + System.out.println(e.getKey().toString()+((daemon ? " (daemon)" : ""))+", stackTrace:"+ builder.toString()); + } + } + } +} diff --git a/ApacheJmeter/src/org/apache/jorphan/util/XMLBuffer.java b/ApacheJmeter/src/org/apache/jorphan/util/XMLBuffer.java new file mode 100644 index 0000000..39aa8f2 --- /dev/null +++ b/ApacheJmeter/src/org/apache/jorphan/util/XMLBuffer.java @@ -0,0 +1,135 @@ +/* + * 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.util; + +import org.apache.commons.collections.ArrayStack; + +// @see org.apache.jorphan.util.TestXMLBuffer for unit tests + +/** + * Provides XML string building methods. + * Not synchronised. + * + */ +public class XMLBuffer{ + private StringBuilder sb = new StringBuilder(); // the string so far + + private ArrayStack tags = new ArrayStack(); // opened tags + + public XMLBuffer(){ + + } + + private void startTag(String t){ + sb.append("<"); + sb.append(t); + sb.append(">"); + } + + private void endTag(String t){ + sb.append(""); + sb.append("\n"); + } + + private void emptyTag(String t){ + sb.append("<"); + sb.append(t); + sb.append("/>"); + sb.append("\n"); + } + + /** + * Open a tag; save on stack. + * + * @param tagname + * @return this + */ + public XMLBuffer openTag(String tagname){ + tags.push(tagname); + startTag(tagname); + return this; + } + + /** + * Close top tag from stack. + * + * @param tagname + * + * @return this + * + * @throws IllegalArgumentException if the tag names do not match + */ + public XMLBuffer closeTag(String tagname){ + String tag = (String) tags.pop(); + if (!tag.equals(tagname)) { + throw new IllegalArgumentException("Trying to close tag: "+tagname+" ; should be "+tag); + } + endTag(tag); + return this; + } + + /** + * Add a complete tag with content. + * + * @param tagname + * @param content + * @return this + */ + public XMLBuffer tag(String tagname, String content){ + if (content.length() == 0) { + emptyTag(tagname); + } else { + startTag(tagname); + sb.append(content); + endTag(tagname); + } + return this; + } + + /** + * Add a complete tag with content. + * + * @param tagname + * @param content + * @return this + */ + public XMLBuffer tag(String tagname,StringBuilder content){ + if (content.length() == 0) { + emptyTag(tagname); + } else { + startTag(tagname); + sb.append(content); + endTag(tagname); + } + return this; + } + + /** + * Convert the buffer to a string, closing any open tags + */ + @Override + public String toString(){ + while(!tags.isEmpty()){ + endTag((String)tags.pop()); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources.properties b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources.properties new file mode 100644 index 0000000..29fbf17 --- /dev/null +++ b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources.properties @@ -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. + +displayName=Access Log Sampler +plugins.displayName=Plugin Classes +accesslogfile.displayName=Log File Location +defaults.displayName=Default Test Values +logFile.displayName=Log File +logFile.shortDescription=Location of log file to parse for requests +parserClassName.displayName=Parser +parserClassName.shortDescription=Choose a parser implementation to parser your log file. +filterClassName.displayName=Filter (Optional) +filterClassName.shortDescription=Choose a filter implementation to filter your log file entries (optional). +domain.displayName=Server +domain.shortDescription=Host name of the server to test against +portString.displayName=Port +portString.shortDescription=Port Number to test against +imageParsing.displayName=Parse Images +imageParsing.shortDescription=If turned on, JMeter will download images and resources contained in each web page \ No newline at end of file diff --git a/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_es.properties b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_es.properties new file mode 100644 index 0000000..67e757c --- /dev/null +++ b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_es.properties @@ -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. + +#Stored by I18NEdit, may be edited! +accesslogfile.displayName=Ubicaci\u00F3n del Archivo de Log +defaults.displayName=Valores por defecto para Prueba +displayName=Muestreador de Acceso a Log +domain.displayName=Servidor +domain.shortDescription=Nombre del servidor contra el que probar +filterClassName.displayName=Filtro (Opcional) +filterClassName.shortDescription=Escoja una implementaci\u00F3n de filtro para filtrar sus entradas en el archivo de log (opcional) +imageParsing.displayName=Parsear Im\u00E1genes +imageParsing.shortDescription=Si lo selecciona, JMeter descargar\u00E1 las im\u00E1genes y recursos contenidos en cada p\u00E1gina web +logFile.displayName=Archivo de Log +logFile.shortDescription=Ubicaci\u00F3n del archivo de log para parsear peticiones +parserClassName.displayName=Parser +parserClassName.shortDescription=Seleccione una implementaci\u00F3n de parser para parsear su archivo de log +plugins.displayName=Classes desplegables +portString.displayName=Puerto +portString.shortDescription=N\u00FAmero de puerto contra el que probar diff --git a/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_fr.properties b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_fr.properties new file mode 100644 index 0000000..e5be124 --- /dev/null +++ b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_fr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +accesslogfile.displayName=Emplacement du fichier journal +defaults.displayName=Valeurs par d\u00E9faut du test +displayName=Echantillon Journal d'acc\u00E8s +domain.displayName=Serveur +domain.shortDescription=Nom d'h\u00F4te du serveur de test +filterClassName.displayName=Filtre (Optionnel) +filterClassName.shortDescription=Choisir une impl\u00E9mentation de filtre pour filtrer vos entr\u00E9es de fichier de journal (optionnel). +imageParsing.displayName=Analyser les images +imageParsing.shortDescription=Si activ\u00E9, JMeter va t\u00E9l\u00E9charger les images et les ressources contenues dans chaque page web. +logFile.displayName=Fichier journal +logFile.shortDescription=Emplacement du fichier journal \u00E0 analyser pour les requ\u00EAtes. +parserClassName.displayName=Analyseur +parserClassName.shortDescription=Choisir une impl\u00E9mentation d'analyseur pour analyser votre fichier journal. +plugins.displayName=Extension de Classes +portString.displayName=Port +portString.shortDescription=Num\u00E9ro de port de test diff --git a/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_pt_BR.properties b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_pt_BR.properties new file mode 100644 index 0000000..4d5e4a8 --- /dev/null +++ b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_pt_BR.properties @@ -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. + +accesslogfile.displayName=Localiza\u00E7\u00E3o do Arquivo de Log +defaults.displayName=Valores Padr\u00F5es de Teste +displayName=Testador de Log de Acesso +domain.displayName=Servidor +domain.shortDescription=Nome do servidor que ir\u00E1 ser testado +filterClassName.displayName=Filtro (Opcional) +filterClassName.shortDescription=Escolha uma implementa\u00E7\u00E3o de filtro para filtrar as entradas do arquivo de log (opcional) +imageParsing.displayName=Processar Imagens +imageParsing.shortDescription=Se configurado, JMeter ir\u00E1 baixar imagens e recursos contidos em cada p\u00E1gina web. +logFile.displayName=Arquivo de Log +logFile.shortDescription=Localiza\u00E7\u00E3o do arquivo de log a ser processado pelas requisi\u00E7\u00F5es +parserClassName.displayName=Processador +parserClassName.shortDescription=Escolha uma implementa\u00E7\u00E3o de processador para processar seu arquivo de log. +plugins.displayName=Classes do Plugin +portString.displayName=Porta +portString.shortDescription=N\u00FAmero da porta a ser testada diff --git a/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_tr.properties b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_tr.properties new file mode 100644 index 0000000..d776cf6 --- /dev/null +++ b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_tr.properties @@ -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. + +#Stored by I18NEdit, may be edited! +accesslogfile.displayName=Log Dosyas\u0131 Yeri +defaults.displayName=\u00D6ntan\u0131ml\u0131 Test De\u011Ferleri +displayName=Log \u00D6rnekleyicisine Eri\u015Fim +domain.displayName=Sunucu +domain.shortDescription=Test edilecek sunucunun makine ismi +filterClassName.displayName=Filtre (\u0130ste\u011Fe ba\u011Fl\u0131) +filterClassName.shortDescription=Log dosyas\u0131 girdilerini filtrelemek i\u00E7in filtre uygulamas\u0131 se\u00E7 (iste\u011Fe ba\u011Fl\u0131) +imageParsing.displayName=Resimleri Ayr\u0131\u015Ft\u0131r +imageParsing.shortDescription=E\u011Fer a\u00E7\u0131ksa, JMeter web sayfas\u0131ndaki resimleri ve kaynaklar\u0131 indirecektir +logFile.displayName=Log Dosyas\u0131 +logFile.shortDescription=\u0130stekler i\u00E7in ayr\u0131\u015Ft\u0131r\u0131lacak log dosyas\u0131n\u0131n yeri +parserClassName.displayName=Ayr\u0131\u015Ft\u0131r\u0131c\u0131 +parserClassName.shortDescription=Log dosyas\u0131n\u0131 ayr\u0131\u015Ft\u0131rmak i\u00E7in bir ayr\u0131\u015Ft\u0131r\u0131c\u0131 uygulamas\u0131 kullan. +plugins.displayName=Eklenti S\u0131n\u0131flar\u0131 +portString.displayName=Port +portString.shortDescription=Test edilecek Port Numaras\u0131 diff --git a/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_zh_TW.properties b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_zh_TW.properties new file mode 100644 index 0000000..3c796f1 --- /dev/null +++ b/ApacheJmeter/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AccessLogSamplerResources_zh_TW.properties @@ -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. + +#Stored by I18NEdit, may be edited! +accesslogfile.displayName=\u6B77\u7A0B\u6A94\u4F4D\u7F6E +defaults.displayName=\u9810\u8A2D\u6E2C\u8A66\u503C +displayName=\u5B58\u53D6\u8A18\u9304\u53D6\u6A23 +domain.displayName=\u4F3A\u670D\u5668 +domain.shortDescription=\u88AB\u6E2C\u8A66\u4E3B\u6A5F\u540D\u7A31 +filterClassName.displayName=\u904E\u6FFE\u5668(\u9078\u64C7\u6027) +filterClassName.shortDescription=\u4F7F\u7528\u81EA\u8A02\u904E\u6FFE\u5668\u5C0D\u8A18\u9304\u6A94\u9032\u884C\u904E\u6FFE(\u9078\u64C7\u6027) +imageParsing.displayName=\u5256\u6790\u5716\u5F62 +imageParsing.shortDescription=\u5982\u679C\u555F\u52D5, JMeter \u6703\u4E0B\u8F09\u6240\u6709\u5716\u5F62\u548C\u76F8\u95DC\u8CC7\u6E90 +logFile.displayName=\u8A18\u9304\u6A94 +logFile.shortDescription=\u88AB\u5256\u6790\u7684\u8A18\u9304\u6A94\u4F4D\u7F6E +parserClassName.displayName=\u5256\u6790\u5668 +parserClassName.shortDescription=\u9078\u64C7\u81EA\u5DF1\u5BE6\u4F5C\u7684\u5256\u6790\u5668 +plugins.displayName=\u63D2\u4EF6 Classes +portString.displayName=\u7AEF\u53E3 +portString.shortDescription=\u88AB\u6E2C\u8A66\u7AEF\u53E3 diff --git a/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources.properties b/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources.properties new file mode 100644 index 0000000..4871bee --- /dev/null +++ b/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +displayName=BSF Sampler +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) +filenameGroup.displayName=Script file (overrides script) +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +script.displayName=Script +script.shortDescription=Script in the appropriate BSF language +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of BSF language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources_fr.properties b/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources_fr.properties new file mode 100644 index 0000000..e7a4547 --- /dev/null +++ b/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/BSFSamplerResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=Echantillon BSF +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage BSF appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage BSF, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables\: ctx vars props SampleResult sampler log Label FileName Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources.properties b/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources.properties new file mode 100644 index 0000000..4ed2b5c --- /dev/null +++ b/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources.properties @@ -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. + +displayName=JSR223 Sampler +scriptingLanguage.displayName=Script language (e.g. beanshell, javascript, jexl) +scriptLanguage.displayName=Language +scriptLanguage.shortDescription=Name of JSR 223 language, e.g. beanshell, javascript, jexl +scripting.displayName=Script (variables: ctx vars props SampleResult sampler log Label Filename Parameters args[] OUT) +script.displayName=Script +script.shortDescription=Script in the appropriate JSR 223 language +parameterGroup.displayName=Parameters to be passed to script (=> String Parameters and String []args) +parameters.displayName=Parameters +parameters.shortDescription=Parameters to be passed to the file or script +filenameGroup.displayName=Script file (overrides script) +filename.displayName=File Name +filename.shortDescription=Script file (overrides script) \ No newline at end of file diff --git a/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources_fr.properties b/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources_fr.properties new file mode 100644 index 0000000..c0dc257 --- /dev/null +++ b/ApacheJmeter/src/protocol/java/org/apache/jmeter/protocol/java/sampler/JSR223SamplerResources_fr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +displayName=Echantillon JSR223 +filename.displayName=Nom de fichier +filename.shortDescription=Fichier script (remplace le script) +filenameGroup.displayName=Fichier script (remplace le script) +parameterGroup.displayName=Param\u00E8tres \u00E0 passer au script (\=> String Parameters and String []args) +parameters.displayName=Param\u00E8tres +parameters.shortDescription=Param\u00E8tres \u00E0 passer au fichier ou au script +script.displayName=Script +script.shortDescription=Script dans le langage JSR223 appropri\u00E9 +scriptLanguage.displayName=Langage +scriptLanguage.shortDescription=Nom du langage JSR223, ex. beanshell, javascript, jexl +scripting.displayName=Script (variables \: ctx vars props SampleResult sampler log Label Filename Parameters args[] OUT) +scriptingLanguage.displayName=Langage de script (ex. beanshell, javascript, jexl) diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources.properties new file mode 100644 index 0000000..ae21476 --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources.properties @@ -0,0 +1,46 @@ +# 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. + +displayName=JDBC Connection Configuration +pool.displayName=Connection Pool Configuration +varName.displayName=Variable Name Bound to Pool +keep-alive.displayName=Connection Validation by Pool +database.displayName=Database Connection Configuration +autocommit.displayName=Auto Commit +autocommit.shortDescription=Whether queries should be auto committed. +poolMax.displayName=Max Number of Connections +poolMax.shortDescription=Maximum number of connections the pool will open at one time +connectionAge.displayName=Max Connection age (ms) +connectionAge.shortDescription=Maximum number of milliseconds an idle connection is kept before discarding +driver.displayName=JDBC Driver class +driver.shortDescription=Full package and class name of the JDBC driver to be used (Must be in JMeter's classpath) +dbUrl.displayName=Database URL +dbUrl.shortDescription=Full URL for the database, including jdbc protocol parts +username.displayName=Username +username.shortDescription=Username to use in connecting to database +password.displayName=Password +password.shortDescription=Password used to connect to database +checkQuery.displayName=Validation Query +checkQuery.shortDescription=A query used to validate a connection still works. Only relevant if Keep Alive is true. +dataSource.displayName=Variable Name +dataSource.shortDescription=Name of the JMeter variable that the pool will be bound to. +timeout.displayName=Pool Timeout +timeout.shortDescription=The pool blocks requests for connection until a connection is available. This is the maximum blocking time before an exception is returned. +trimInterval.displayName=Idle Cleanup Interval (ms) +trimInterval.shortDescription=The pool removes extra idle connections at regular intervals +keepAlive.displayName=Keep-Alive +keepAlive.shortDescription=Whether the pool should validate connections. If no, Connection Age and Validation Query are ignored. +transactionIsolation.displayName=Transaction Isolation +transactionIsolation.shortDescription=Transaction Isolation Level \ No newline at end of file diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_es.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_es.properties new file mode 100644 index 0000000..7b7d367 --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_es.properties @@ -0,0 +1,45 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autocommit.displayName=Auto Commit +autocommit.shortDescription=Queries con auto-commit +checkQuery.displayName=Query de Validaci\u00F3n +checkQuery.shortDescription=Una query utilizada para validar que una conexi\u00F3n funciona. S\u00F3lo es relevante si Keep Alive est\u00E1 a true. +connectionAge.displayName=Edad m\u00E1xima de las Conexiones (ms) +connectionAge.shortDescription=N\u00FAmero m\u00E1ximo de milisegundos que una conexi\u00F3n se mantiene inactiva antes de descartarla. +dataSource.displayName=Nombre de Variable +dataSource.shortDescription=Nombre de la variable JMeter a la que se enlazar\u00E1 el pool. +database.displayName=Configuraci\u00F3n de la Conexi\u00F3n a Base de Datos +dbUrl.displayName=URL de la Base de Datos +dbUrl.shortDescription=URL completa de la Base de Datos, incluyendo las partes del protocolo jdbc +displayName=Configuraci\u00F3n de la Conexi\u00F3n JDBC +driver.displayName=Clase del Driver JDBC +driver.shortDescription=Nombre completo (con paquete) de la clase del driver JDBC a utilizar (Debe estar en el classpath de JMeter) +keep-alive.displayName=Validaci\u00F3n de Conexi\u00F3n por Pool +keepAlive.displayName=Keep-Alive +keepAlive.shortDescription=Validaci\u00F3n de conexiones. Si se indica que no, la Edad de Conexi\u00F3n y la Valicadi\u00F3n de Query son ignorados. +password.displayName=Password +password.shortDescription=Password utilizados para conectar a la base de datos +pool.displayName=Configuraci\u00F3n del Pool de Conexiones +poolMax.displayName=N\u00FAmero M\u00E1ximo de Conexiones +poolMax.shortDescription=N\u00FAmero m\u00E1ximo de conexiones del pool que se abrir\u00E1n a la vez +timeout.displayName=Timeout del Pool +timeout.shortDescription=El pool bloquea peticiones de conexi\u00F3n hasta que una conexi\u00F3n est\u00E1 disponible. Este es el tiempo m\u00E1ximo de bloqueo antes de que una excepci\u00F3n es devuelta. +trimInterval.displayName=Intervalo de Limpieza por Inactividad (ms) +trimInterval.shortDescription=El pool elimina conexiones inactivas a intervalos regulares +username.displayName=Nombre de Usuario +username.shortDescription=Nombre de Usuario a utilizar al conectar a la base de datos +varName.displayName=Nombre Variable Enlazado al Pool diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_fr.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_fr.properties new file mode 100644 index 0000000..23e7eec --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_fr.properties @@ -0,0 +1,47 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autocommit.displayName=Validation automatique (auto commit) +autocommit.shortDescription=D\u00E9fini si les requ\u00EAtes doivent \u00EAtre valid\u00E9es automatiquement. +checkQuery.displayName=Requ\u00EAte de validation +checkQuery.shortDescription=Une requ\u00EAte \u00E0 utiliser pour valider que la connexion fonctionne. Utilis\u00E9 seulement si le param\u00E9tre connexion persistante (keep-alive) est activ\u00E9 (true). +connectionAge.displayName=Dur\u00E9e de vie maximum d'une connexion (ms) +connectionAge.shortDescription=Nombre maximum en millisecondes pendant lequel une connexion disponible est gard\u00E9e avant d'\u00EAtre ferm\u00E9e. +dataSource.displayName=Nom de liaison +dataSource.shortDescription=Nom de la variable JMeter sur laquelle le pool sera li\u00E9. +database.displayName=Configuration de connexion \u00E0 la base de donn\u00E9es +dbUrl.displayName=URL de la base de donn\u00E9es +dbUrl.shortDescription=URL compl\u00E8te pour acc\u00E9der \u00E0 la base de donn\u00E9es, incluant la partie jdbc du protocole +displayName=Configuration de connexion JDBC +driver.displayName=Classe du pilote JDBC +driver.shortDescription=Nom (paquet+classe) complet du pilote JDBC \u00E0 utiliser. (Doit \u00EAtre dans le classpath de JMeter) +keep-alive.displayName=Validation des connexions par le pool +keepAlive.displayName=Connexions persistantes (keep-alive) +keepAlive.shortDescription=Est-ce que le pool doit valider les connexions. Sinon, la dur\u00E9e de vie des connexions et des validations de requ\u00EAtes sont ignor\u00E9es. +password.displayName=Mot de passe +password.shortDescription=Mot de passe \u00E0 utiliser pour se connecter \u00E0 la base de donn\u00E9es +pool.displayName=Configuration du pool de connexions +poolMax.displayName=Nombre maximum de connexions +poolMax.shortDescription=Nombre maximum de connexions que le pool peut ouvrir en m\u00EAme temps +timeout.displayName=Expiration du pool (ms) +timeout.shortDescription=D\u00E9lai d'attente maximum pour obtenir une connexion du pool si ce dernier n'a plus de connexion disponible. A l'expiration, une exception est retourn\u00E9e. +trimInterval.displayName=Intervalle de nettoyage des connexions disponibles (ms) +trimInterval.shortDescription=Le pool supprime les connexions disponibles suppl\u00E9mentaires \u00E0 intervalle r\u00E9gulier +username.displayName=Identifiant +username.shortDescription=L'identifiant \u00E0 utiliser pour la connexion \u00E0 la base de donn\u00E9es +varName.displayName=Nom de liaison du pool +transactionIsolation.displayName=Isolation de la Transaction +transactionIsolation.shortDescription=Niveau d'isolation de la transaction \ No newline at end of file diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_pt_BR.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_pt_BR.properties new file mode 100644 index 0000000..2109d5d --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_pt_BR.properties @@ -0,0 +1,45 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autocommit.displayName=Commit autom\u00E1tico +autocommit.shortDescription=Se a consulta deve realizar commit autom\u00E1tico +checkQuery.displayName=Consulta de Valida\u00E7\u00E3o +checkQuery.shortDescription=Consulta utilizada para verificar se a conex\u00E3o ainda est\u00E1 ativa. Relevante apenas se Manter Ativa est\u00E1 configurado. +connectionAge.displayName=Tempo m\u00E1ximo de conex\u00E3o (ms) +connectionAge.shortDescription=Tempo m\u00E1ximo (em milissegundos) que uma conex\u00E3o n\u00E3o utilizada \u00E9 mantida antes de ser descartada. +dataSource.displayName=Nome da Vari\u00E1vel +dataSource.shortDescription=Nome da vari\u00E1vel do JMeter que referencia o grupo de conex\u00F5es. +database.displayName=Configura\u00E7\u00E3o da Conex\u00E3o com o Banco de Dados +dbUrl.displayName=URL do banco de dados +dbUrl.shortDescription=URL completa do banco de dados, incluindo partes do protocolo jdbc. +displayName=Configura\u00E7\u00E3o da Conex\u00E3o JDBC +driver.displayName=Classe do Driver JDBC +driver.shortDescription=Pacote completo e nome da classe do driver JDBC a ser utilizado (Necessita estar no classpath) +keep-alive.displayName=Valida\u00E7\u00E3o da Conex\u00E3o pelo Grupo de Conex\u00F5es +keepAlive.displayName=Manter Ativa +keepAlive.shortDescription=Se o grupo de conex\u00E3o deve validar conex\u00F5es. Se n\u00E3o, Tempo da Conex\u00E3o e Consulta de Valida\u00E7\u00E3o s\u00E3o ignorados. +password.displayName=Senha +password.shortDescription=Senha usada para conex\u00E3o com o banco de dados +pool.displayName=Configura\u00E7\u00E3o do Grupo de Conex\u00F5es +poolMax.displayName=Limite de Conex\u00F5es +poolMax.shortDescription=N\u00FAmero m\u00E1ximo de conex\u00F5es que o grupo de conex\u00F5es ir\u00E1 criar ao mesmo tempo. +timeout.displayName=Timeout do grupo de conex\u00F5es +timeout.shortDescription=O grupo de conex\u00F5es bloqueia requisi\u00E7\u00F5es de conex\u00F5es at\u00E9 que uma conex\u00E3o esteja dispon\u00EDvel. Este \u00E9 o tempo de bloqueio m\u00E1ximo antes que uma exce\u00E7\u00E3o seja retornada. +trimInterval.displayName=Intervalo de limpeza de conex\u00F5es ociosas (ms) +trimInterval.shortDescription=O grupo de conex\u00F5es remove conex\u00F5es ociosas em intervalos regulares. +username.displayName=Nome do usu\u00E1rio +username.shortDescription=Nome do usu\u00E1rio usado na conex\u00E3o com o banco de dados. +varName.displayName=Nome da Vari\u00E1vel que Referencia o Grupo de Conex\u00F5es diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_tr.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_tr.properties new file mode 100644 index 0000000..052cb4c --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_tr.properties @@ -0,0 +1,45 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autocommit.displayName=An\u0131nda \u0130\u015Flem +autocommit.shortDescription=Sorgular\u0131n an\u0131nda olarak i\u015Flenip i\u015Flenmeyece\u011Fi. +checkQuery.displayName=Do\u011Frulama Sorgusu +checkQuery.shortDescription=Ba\u011Flant\u0131n\u0131n hala \u00E7al\u0131\u015F\u0131p \u00E7al\u0131\u015Fmad\u0131\u011F\u0131n\u0131 kontrol eden sorgu. Sadece "Canl\u0131Tut" se\u00E7ili ise ge\u00E7erlidir. +connectionAge.displayName=Maksimum Ba\u011Flant\u0131 Ya\u015F\u0131 (ms) +connectionAge.shortDescription=Bo\u015Ftaki ba\u011Flant\u0131n\u0131n kopar\u0131lmadan korunaca\u011F\u0131 maksimum milisaniye say\u0131s\u0131 +dataSource.displayName=De\u011Fi\u015Fken \u0130smi +dataSource.shortDescription=Havuzun ba\u011Flanaca\u011F\u0131 JMeter de\u011Fi\u015Fkeninin ismi. +database.displayName=Veritaban\u0131 Ba\u011Flant\u0131s\u0131 Ayar\u0131 +dbUrl.displayName=Veritaban\u0131 Adresi (URL) +dbUrl.shortDescription=Veritaban\u0131 i\u00E7in tam adres (URL), jdbc protokolu k\u0131s\u0131mlar\u0131 dahil +displayName=JDBC Ba\u011Flant\u0131 Ayarlar\u0131 +driver.displayName=JDBC S\u00FCr\u00FCc\u00FC s\u0131n\u0131f\u0131 +driver.shortDescription=Kullan\u0131lacak JDBC s\u00FCr\u00FCc\u00FCs\u00FCn\u00FCn tam paket ve s\u0131n\u0131f ismi\n(JMeter s\u0131n\u0131f yolunda(classpath) yer almal\u0131) +keep-alive.displayName=Havuzla ba\u011Flant\u0131 Do\u011Frulamas\u0131 +keepAlive.displayName=Canl\u0131-Tut +keepAlive.shortDescription=Havuzun ba\u011Flant\u0131 do\u011Frulamas\u0131 yap\u0131p yapmayaca\u011F\u0131. "no" ise, Ba\u011Flant\u0131 Ya\u015F\u0131 ve Ba\u011Flant\u0131 Do\u011Frulamas\u0131 yoksay\u0131lacak. +password.displayName=\u015Eifre +password.shortDescription=Veritaban\u0131na ba\u011Flan\u0131l\u0131rken kullan\u0131lacak \u015Fifre +pool.displayName=Ba\u011Flant\u0131 Havuzu Ayar\u0131 +poolMax.displayName=Maksimum Ba\u011Flant\u0131 Say\u0131s\u0131 +poolMax.shortDescription=Havuzun ayn\u0131 zamanda a\u00E7aca\u011F\u0131 maksimum ba\u011Flant\u0131 say\u0131s\u0131 +timeout.displayName=Havuz Zaman A\u015F\u0131m\u0131 +timeout.shortDescription=Havuz bir ba\u011Flant\u0131 uygun hale gelinceye kadar t\u00FCm istekleri engeller. Bu hata d\u00F6nmeden \u00F6nceki maksimum engelleme zaman\u0131d\u0131r. +trimInterval.displayName=Bo\u015Ftaki Toparlama Ara\u015F\u0131\u011F\u0131 (ms) +trimInterval.shortDescription=Havuz d\u00FCzenli aral\u0131klarla bo\u015Ftaki fazladan ba\u011Flant\u0131lar\u0131 kald\u0131r\u0131r. +username.displayName=Kullan\u0131c\u0131 ismi +username.shortDescription=Veritaban\u0131na ba\u011Flan\u0131l\u0131rken kullan\u0131lacak kullan\u0131c\u0131 ismi +varName.displayName=Havuzla \u0130li\u015Fkilendirilecek De\u011Fi\u015Fkenin \u0130smi diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_zh_TW.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_zh_TW.properties new file mode 100644 index 0000000..131b58b --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/config/DataSourceElementResources_zh_TW.properties @@ -0,0 +1,45 @@ +# 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. + +#Stored by I18NEdit, may be edited! +autocommit.displayName=\u81EA\u52D5 Commit +autocommit.shortDescription=\u662F\u5426\u8981\u81EA\u52D5Commit SQL +checkQuery.displayName=\u9A57\u8B49\u67E5\u8A62 +checkQuery.shortDescription=\u7576KeepAlive\u70BA\u771F\u6642,\u7528\u4F86\u6AA2\u67E5\u9023\u7DDA\u662F\u5426\u4ECD\u6B63\u5E38 +connectionAge.displayName=\u6700\u9577\u9023\u7DDA\u6642\u9593(\u5FAE\u79D2) +connectionAge.shortDescription=\u8A2D\u5B9A\u9023\u7DDA\u6700\u9577\u53EF\u9592\u7F6E\u591A\u5C11\u5FAE\u79D2 +dataSource.displayName=\u8B8A\u6578\u540D\u7A31 +dataSource.shortDescription=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u8B8A\u6578\u540D\u7A31 +database.displayName=\u8CC7\u6599\u5EAB\u9023\u7DDA\u8A2D\u5B9A +dbUrl.displayName=\u8CC7\u6599\u5EAB URL +dbUrl.shortDescription=\u8CC7\u6599\u5EAB\u5B8C\u6574 URL, \u542B JDBC \u654D\u8FF0\u90E8\u4EFD +displayName=JDBC \u9023\u7DDA\u8A2D\u5B9A +driver.displayName=JDBC \u9A45\u52D5\u7A0B\u5F0F +driver.shortDescription=JDBC \u5B8C\u6574 class \u540D\u7A31(\u9808\u5728 JMeter \u7684 CLASSPATH \u4E2D) +keep-alive.displayName=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u9032\u884C\u9023\u7DDA\u9A57\u8B49 +keepAlive.displayName=\u4FDD\u6301\u9023\u7DDA +keepAlive.shortDescription=\u9023\u7DDA\u6C60\u662F\u5426\u8981\u9A57\u8B49\u9023\u7DDA, \u5982\u679C\u4E0D\u8981, \u6700\u9577\u9023\u7DDA\u6642\u9593(\u5FAE\u79D2)\u548C\u9023\u7DDA\u9A57\u8B49\u5169\u500B\u8A2D\u5B9A\u90FD\u6703\u88AB\u5FFD\u7565 +password.displayName=\u5BC6\u78BC +password.shortDescription=\u8CC7\u6599\u5EAB\u9023\u7DDA\u5BC6\u78BC +pool.displayName=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u8A2D\u5B9A +poolMax.displayName=\u6700\u5927\u9023\u7DDA\u6578 +poolMax.shortDescription=\u9023\u7DDA\u6C60\u4E00\u6B21\u6700\u591A\u958B\u5E7E\u500B\u9023\u7DDA +timeout.displayName=\u9023\u7DDA\u6C60 TimeOut +timeout.shortDescription=\u7B49\u5F85\u53D6\u5F97\u9023\u7DDA\u7684\u6700\u9577\u6642\u9593, \u8D85\u904E\u5C31 exception +trimInterval.displayName=\u9592\u7F6E\u6E05\u9664\u6642\u9694(\u5FAE\u79D2) +trimInterval.shortDescription=\u8D85\u904E\u9592\u7F6E\u6E05\u9664\u6642\u9694(\u5FAE\u79D2),\u9023\u7DDA\u6C60\u81EA\u52D5\u79FB\u9664\u8A72\u9023\u7DDA +username.displayName=\u4F7F\u7528\u8005 +username.shortDescription=\u9023\u7DDA\u4F7F\u7528\u8005 +varName.displayName=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u8B8A\u6578\u540D\u7A31 diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources.properties new file mode 100644 index 0000000..073a90a --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources.properties @@ -0,0 +1,33 @@ +# 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. + +displayName=JDBC PostProcessor +varName.displayName=Variable Name Bound to Pool +sql.displayName=SQL Query +query.displayName=Query +query.shortDescription=SQL Query to send to database +queryType.displayName=Query Type +queryType.shortDescription=Determines if the SQL statement should be run as a select statement or an update statement. +dataSource.displayName=Variable Name +dataSource.shortDescription=Name of the JMeter variable that the connection pool is bound to. +queryArguments.displayName=Parameter values +queryArguments.shortDescription=SQL parameter values (comma separated) +queryArgumentsTypes.displayName=Parameter types +queryArgumentsTypes.shortDescription=JDBC Type names from java.sql.Types. VARCHAR, INTEGER, etc. (comma separated) +variableNames.displayName=Variable names +variableNames.shortDescription=Output variable names for each column (comma separated) +resultVariable.displayName=Result variable name +resultVariable.shortDescription=Name of the JMeter variable that stores the result set objects in a list of maps for looking up results by column name. + diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources_fr.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources_fr.properties new file mode 100644 index 0000000..10b6dbc --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPostProcessorResources_fr.properties @@ -0,0 +1,33 @@ +# 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. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=Nom de liaison +dataSource.shortDescription=Nom de la variable JMeter qui sera li\u00E9e au pool de connexion +displayName=Post-Processeur JDBC +query.displayName=Requ\u00EAte +query.shortDescription=Requ\u00EAte SQL \u00E0 envoyer \u00E0 la base de donn\u00E9es +queryArguments.displayName=Valeurs des param\u00E8tres +queryArguments.shortDescription=Valeurs des param\u00E8tres SQL +queryArgumentsTypes.displayName=Types des param\u00E8tres +queryArgumentsTypes.shortDescription=Noms des types JDBC depuis java.sql.Types. Ex. VARCHAR, INTEGER, etc. (s\u00E9par\u00E9s par des virgules) +queryType.displayName=Type de requ\u00EAte +queryType.shortDescription=D\u00E9termine si l'instruction SQL doit \u00EAtre ex\u00E9cut\u00E9e comme une commande SELECT ou une commande UPDATE. +resultVariable.displayName=Nom de la variable des R\u00E9sultats +resultVariable.shortDescription=Nom de la variable JMeter qui stocke les r\u00E9sultats sous forme d'objets dans une liste de type 'maps' permettant la recherche des r\u00E9sultats par nom de colonne. +sql.displayName=Requ\u00EAte SQL +varName.displayName=Nom de liaison avec le pool +variableNames.displayName=Noms des variables +variableNames.shortDescription=Noms des variables en sortie pour chaque colonne (s\u00E9par\u00E9s par des virgules) diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources.properties new file mode 100644 index 0000000..d179c61 --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources.properties @@ -0,0 +1,33 @@ +# 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. + +displayName=JDBC PreProcessor +varName.displayName=Variable Name Bound to Pool +sql.displayName=SQL Query +query.displayName=Query +query.shortDescription=SQL Query to send to database +queryType.displayName=Query Type +queryType.shortDescription=Determines if the SQL statement should be run as a select statement or an update statement. +dataSource.displayName=Variable Name +dataSource.shortDescription=Name of the JMeter variable that the connection pool is bound to. +queryArguments.displayName=Parameter values +queryArguments.shortDescription=SQL parameter values (comma separated) +queryArgumentsTypes.displayName=Parameter types +queryArgumentsTypes.shortDescription=JDBC Type names from java.sql.Types. VARCHAR, INTEGER, etc. (comma separated) +variableNames.displayName=Variable names +variableNames.shortDescription=Output variable names for each column (comma separated) +resultVariable.displayName=Result variable name +resultVariable.shortDescription=Name of the JMeter variable that stores the result set objects in a list of maps for looking up results by column name. + diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources_fr.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources_fr.properties new file mode 100644 index 0000000..085efa8 --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/processor/JDBCPreProcessorResources_fr.properties @@ -0,0 +1,33 @@ +# 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. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=Nom de liaison +dataSource.shortDescription=Nom de la variable JMeter qui sera li\u00E9e au pool de connexion +displayName=Pr\u00E9-Processeur JDBC +query.displayName=Requ\u00EAte +query.shortDescription=Requ\u00EAte SQL \u00E0 envoyer \u00E0 la base de donn\u00E9es +queryArguments.displayName=Valeurs des param\u00E8tres +queryArguments.shortDescription=Valeurs des param\u00E8tres SQL +queryArgumentsTypes.displayName=Types des param\u00E8tres +queryArgumentsTypes.shortDescription=Noms des types JDBC depuis java.sql.Types. Ex. VARCHAR, INTEGER, etc. (s\u00E9par\u00E9s par des virgules) +queryType.displayName=Type de requ\u00EAte +queryType.shortDescription=D\u00E9termine si l'instruction SQL doit \u00EAtre ex\u00E9cut\u00E9e comme une commande SELECT ou une commande UPDATE. +resultVariable.displayName=Nom de la variable des R\u00E9sultats +resultVariable.shortDescription=Nom de la variable JMeter qui stocke les r\u00E9sultats sous forme d'objets dans une liste de type 'maps' permettant la recherche des r\u00E9sultats par nom de colonne. +sql.displayName=Requ\u00EAte SQL +varName.displayName=Nom de liaison avec le pool +variableNames.displayName=Noms des variables +variableNames.shortDescription=Noms des variables en sortie pour chaque colonne (s\u00E9par\u00E9s par des virgules) diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources.properties new file mode 100644 index 0000000..4306658 --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources.properties @@ -0,0 +1,33 @@ +# 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. + +displayName=JDBC Request +varName.displayName=Variable Name Bound to Pool +sql.displayName=SQL Query +query.displayName=Query +query.shortDescription=SQL Query to send to database +queryType.displayName=Query Type +queryType.shortDescription=Determines if the SQL statement should be run as a select statement or an update statement. +dataSource.displayName=Variable Name +dataSource.shortDescription=Name of the JMeter variable that the connection pool is bound to. +queryArguments.displayName=Parameter values +queryArguments.shortDescription=SQL parameter values (comma separated) +queryArgumentsTypes.displayName=Parameter types +queryArgumentsTypes.shortDescription=JDBC Type names from java.sql.Types. VARCHAR, INTEGER, etc. (comma separated) +variableNames.displayName=Variable names +variableNames.shortDescription=Output variable names for each column (comma separated) +resultVariable.displayName=Result variable name +resultVariable.shortDescription=Name of the JMeter variable that stores the result set objects in a list of maps for looking up results by column name. + diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_es.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_es.properties new file mode 100644 index 0000000..be31fb9 --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_es.properties @@ -0,0 +1,30 @@ +# 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. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=Nombre de Variable +dataSource.shortDescription=Nombre de la variable JMeter a la cual est\u00E1 ligado el pool de conexiones +displayName=Petici\u00F3n JDBC +query.displayName=Query +query.shortDescription=Query SQL a enviar a la base de datos +queryType.displayName=Solo Query +queryType.shortDescription=is true, se lanzar\u00E1 como una query y no como un update/inser. Si no, se lanza como update. +sql.displayName=Query SQL +varName.displayName=Nombre de Variable Ligada al Pool +queryArguments.displayName=Argumentos +queryArguments.shortDescription=los valores de los argumentos separados por comas +queryArgumentsTypes.displayName=Tipos de los argumentos +queryArgumentsTypes.shortDescription=los valores de los argumentos separados por comas + diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_fr.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_fr.properties new file mode 100644 index 0000000..86fb271 --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_fr.properties @@ -0,0 +1,33 @@ +# 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. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=Nom de liaison +dataSource.shortDescription=Nom de la variable JMeter qui sera li\u00E9e au pool de connexion +displayName=Requ\u00EAte JDBC +query.displayName=Requ\u00EAte +query.shortDescription=Requ\u00EAte SQL \u00E0 envoyer \u00E0 la base de donn\u00E9es +queryArguments.displayName=Valeurs des param\u00E8tres +queryArguments.shortDescription=Valeurs des param\u00E8tres SQL +queryArgumentsTypes.displayName=Types des param\u00E8tres +queryArgumentsTypes.shortDescription=Noms des types JDBC depuis java.sql.Types. Ex. VARCHAR, INTEGER, etc. (s\u00E9par\u00E9s par des virgules) +queryType.displayName=Type de requ\u00EAte +queryType.shortDescription=D\u00E9termine si l'instruction SQL doit \u00EAtre ex\u00E9cut\u00E9e comme une commande SELECT ou une commande UPDATE. +resultVariable.displayName=Nom de la variable des R\u00E9sultats +resultVariable.shortDescription=Nom de la variable JMeter qui stocke les r\u00E9sultats sous forme d'objets dans une liste de type 'maps' permettant la recherche des r\u00E9sultats par nom de colonne. +sql.displayName=Requ\u00EAte SQL +varName.displayName=Nom de liaison avec le pool +variableNames.displayName=Noms des variables +variableNames.shortDescription=Noms des variables en sortie pour chaque colonne (s\u00E9par\u00E9s par des virgules) diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_pt_BR.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_pt_BR.properties new file mode 100644 index 0000000..62c705d --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_pt_BR.properties @@ -0,0 +1,30 @@ +# 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. + +dataSource.displayName=Nome da Vari\u00E1vel +dataSource.shortDescription=Nome da vari\u00E1vel do JMeter que referencia o grupo de conex\u00F5es. +displayName=Requisi\u00E7\u00E3o JDBC +query.displayName=Consulta +query.shortDescription=Consulta SQL que ser\u00E1 enviada ao banco de dados +queryArguments.displayName=Valores dos par\u00E2metros +queryArguments.shortDescription=Valores dos par\u00E2metros do SQL (separados por v\u00EDrgula) +queryArgumentsTypes.displayName=Tipos dos par\u00E2metros +queryArgumentsTypes.shortDescription=Nomes dos tipos do JDBC de java.sql.Types. VARCHAR, INTEGER, etc. (separados por v\u00EDrgula) +queryType.displayName=Tipo da Consulta +queryType.shortDescription=Determina se a instru\u00E7\u00E3o SQL dever\u00E1 ser executada como uma instru\u00E7\u00E3o de consulta ou de atualiza\u00E7\u00E3o. +sql.displayName=Consulta SQL +varName.displayName=Nome da vari\u00E1vel que referencia o grupo de conex\u00F5es. +variableNames.displayName=Nomes das vari\u00E1veis +variableNames.shortDescription=Nomes das vari\u00E1veis de sa\u00EDda para cada coluna (separado por v\u00EDrgula) diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_tr.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_tr.properties new file mode 100644 index 0000000..f65b944 --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_tr.properties @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=De\u011Fi\u015Fken \u0130smi +dataSource.shortDescription=Ba\u011Flant\u0131 havuzunun ili\u015Fkilendirilece\u011Fi JMeter de\u011Fi\u015Fkeninin ismi. +displayName=JDBC \u0130ste\u011Fi +query.displayName=Sorgu +query.shortDescription=Veritaban\u0131na g\u00F6nderilecek SQL sorgusu +queryArguments.displayName=Parametre de\u011Ferleri +queryArguments.shortDescription=SQL parametresi de\u011Ferleri +queryArgumentsTypes.displayName=Parametre tipleri +queryArgumentsTypes.shortDescription=java.sql.Types'tan JDBC Tip isimleri. VARCHAR, INTEGER, gibi. +queryType.displayName=Sorgu Tipi +queryType.shortDescription=SQL ifadesinin select veya update ifadesi olarak \u00E7al\u0131\u015Ft\u0131r\u0131laca\u011F\u0131n\u0131 belirler. +sql.displayName=SQL Sorgusu +varName.displayName=Havuzla ili\u015Fkilendirilecek De\u011Fi\u015Fkenin \u0130smi diff --git a/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_zh_TW.properties b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_zh_TW.properties new file mode 100644 index 0000000..8d4c0fc --- /dev/null +++ b/ApacheJmeter/src/protocol/jdbc/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerResources_zh_TW.properties @@ -0,0 +1,23 @@ +# 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. + +#Stored by I18NEdit, may be edited! +dataSource.displayName=\u8B8A\u6578\u540D\u7A31 +dataSource.shortDescription=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u8B8A\u6578\u540D\u7A31 +displayName=JDBC \u8981\u6C42 +query.displayName=\u67E5\u8A62 +query.shortDescription=\u50B3\u9001\u7D66\u8CC7\u6599\u5EAB\u7684 SQL \u654D\u8FF0 +sql.displayName=SQL \u654D\u8FF0 +varName.displayName=\u8CC7\u6599\u5EAB\u9023\u7DDA\u6C60\u8B8A\u6578\u540D\u7A31 diff --git a/ApacheJmeter/src/protocol/mail/META-INF/javamail.providers b/ApacheJmeter/src/protocol/mail/META-INF/javamail.providers new file mode 100644 index 0000000..562dc62 --- /dev/null +++ b/ApacheJmeter/src/protocol/mail/META-INF/javamail.providers @@ -0,0 +1,16 @@ +# 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. +# +protocol=file; type=store; class=org.apache.jmeter.protocol.mail.sampler.MailFileStore; vendor=ASF \ No newline at end of file diff --git a/ApacheJmeter/src/test/AfterAnnotatedTest.java b/ApacheJmeter/src/test/AfterAnnotatedTest.java new file mode 100644 index 0000000..1b5ae36 --- /dev/null +++ b/ApacheJmeter/src/test/AfterAnnotatedTest.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 test; + +import static org.junit.Assert.fail; +import org.junit.After; +import org.junit.Test; + +/** + * Test to demonstrate how @After failures are handled + */ +public class AfterAnnotatedTest { + + @After + public void afterFail(){ + fail("afterFail()"); + } + + @Test + public void afterTest(){ + // Dummy to ensure there is a test to run + } +} diff --git a/ApacheJmeter/src/test/BeforeAnnotatedTest.java b/ApacheJmeter/src/test/BeforeAnnotatedTest.java new file mode 100644 index 0000000..6d412a6 --- /dev/null +++ b/ApacheJmeter/src/test/BeforeAnnotatedTest.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 test; + +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Test; + +/** + * Test to demonstrate how @Before failures are handled + */ +public class BeforeAnnotatedTest { + + @Before + public void beginFail(){ + fail("beginFail()"); + } + + @Test + public void beginTest(){ + // Dummy to ensure there is a test to run + } +} diff --git a/ApacheJmeter/src/test/DummyAnnotatedTest.java b/ApacheJmeter/src/test/DummyAnnotatedTest.java new file mode 100644 index 0000000..12d9852 --- /dev/null +++ b/ApacheJmeter/src/test/DummyAnnotatedTest.java @@ -0,0 +1,109 @@ +/* + * 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 test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Sample test cases for demonstrating JUnit4 sampler. + * + */ +public class DummyAnnotatedTest +{ + public int two = 1; //very wrong. + + public DummyAnnotatedTest() { + } + + // Generates expected Exception + @Test(expected=RuntimeException.class) + public void expectedExceptionPass() { + throw new RuntimeException(); + } + + // Fails to generate expected Exception + @Test(expected=RuntimeException.class) + public void expectedExceptionFail() { + } + + @Before + public void verifyTwo() { + System.out.println("DummyAnnotatedTest#verifyTwo()"); + two = 2; + } + + @After + public void printDone() { + System.out.println("DummyAnnotatedTest#printDone()"); + } + + @Test + // Succeeds only if Before method - verifyTwo() - is run. + public void add() { + int four = two+2; + if(4!=four) { + throw new RuntimeException("4 did not equal four."); + } + //or if you have assertions enabled + assert 4 == four; + } + + //should always fail + @Test(timeout=1000) + public void timeOutFail() { + try{ + Thread.sleep(2000); + }catch (InterruptedException e) { } + } + + //should not fail + @Test(timeout=1000) + public void timeOutPass() { + try{ + Thread.sleep(500); + }catch (InterruptedException e) { } + } + + @Test + public void alwaysFail() { + fail("This always fails"); + } + + @Test + // Generate a test error + public void divideByZero() { + @SuppressWarnings("unused") + int i = 27 / 0; // will generate Divide by zero error + } + + @Test + public void stringCompareFail(){ + assertEquals("this","that"); + } + + @Test + public void objectCompareFail(){ + assertEquals(new Object(),new Object()); + } +} diff --git a/ApacheJmeter/src/test/Junit4AnnotationsTest.java b/ApacheJmeter/src/test/Junit4AnnotationsTest.java new file mode 100644 index 0000000..0e061bd --- /dev/null +++ b/ApacheJmeter/src/test/Junit4AnnotationsTest.java @@ -0,0 +1,55 @@ +/* + * 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 test; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test to demonstrate all the common method annotations + */ +public class Junit4AnnotationsTest { + + @BeforeClass + public static void beforeClass(){ + System.out.println("beforeClass"); + } + @Before + public void before(){ + System.out.println("before"); + } + + @Test + public void test(){ + System.out.println("test"); + } + + @After + public void after(){ + System.out.println("after"); + } + + @AfterClass + public static void afterClass(){ + System.out.println("afterClass"); + } +} diff --git a/ApacheJmeter/src/test/RerunTest.java b/ApacheJmeter/src/test/RerunTest.java new file mode 100644 index 0000000..5eb831c --- /dev/null +++ b/ApacheJmeter/src/test/RerunTest.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 test; + +import org.junit.Test; + +import junit.framework.TestCase; + +/** + * Test to demonstrate whether a test instance can be re-run + */ +public class RerunTest extends TestCase { + + private int i = 123; + @Test + public void testRerun(){ + assertEquals(123,i); + i++; + } +} diff --git a/ApacheJmeter/src/test/SetupTestError.java b/ApacheJmeter/src/test/SetupTestError.java new file mode 100644 index 0000000..6115dbb --- /dev/null +++ b/ApacheJmeter/src/test/SetupTestError.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package test; + +import org.junit.Before; +import org.junit.Test; + +import junit.framework.TestCase; + +/** + * Test to demonstrate how setUp errors are handled + */ +public class SetupTestError extends TestCase { + + @Override + @Before + public void setUp(){ + throw new Error("setUp()"); + } + + @Test + public void testSetUpError(){ + // Dummy to ensure there is a test to run + } +} diff --git a/ApacheJmeter/src/test/SetupTestFail.java b/ApacheJmeter/src/test/SetupTestFail.java new file mode 100644 index 0000000..b58f173 --- /dev/null +++ b/ApacheJmeter/src/test/SetupTestFail.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package test; + +import org.junit.Before; +import org.junit.Test; + +import junit.framework.TestCase; + +/** + * Test to demonstrate how setUp failures are handled + */ +public class SetupTestFail extends TestCase { + + @Override + @Before + public void setUp(){ + fail("setUp()"); + } + + @Test + public void testSetUpFail(){ + // Dummy to ensure there is a test to run + } +} diff --git a/ApacheJmeter/src/test/TearDownTestFail.java b/ApacheJmeter/src/test/TearDownTestFail.java new file mode 100644 index 0000000..e4a6a31 --- /dev/null +++ b/ApacheJmeter/src/test/TearDownTestFail.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 test; + +import junit.framework.TestCase; + +/** + * Test to demonstrate how tearDown failures are handled + */ +public class TearDownTestFail extends TestCase { + + @Override + public void tearDown(){ + fail("tearDown()"); + } + + public void testTearDownFail(){ + // Dummy to ensure there is a test to run + } +} diff --git a/ApacheJmeter/src/woolfel/DummyTestCase.java b/ApacheJmeter/src/woolfel/DummyTestCase.java new file mode 100644 index 0000000..3956bba --- /dev/null +++ b/ApacheJmeter/src/woolfel/DummyTestCase.java @@ -0,0 +1,99 @@ +/* + * 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 woolfel; + +import junit.framework.TestCase; + +public class DummyTestCase extends TestCase { + + public DummyTestCase() { + super(); + System.out.println("public DummyTestCase()"); + } + + protected DummyTestCase(String arg0) { + super(arg0); + System.out.println("protected DummyTestCase("+arg0+")"); + } + + @Override + public void setUp(){ + System.out.println("DummyTestCase#setup(): "+getName()); + } + + @Override + public void tearDown(){ + System.out.println("DummyTestCase#tearDown(): "+getName()); + } + + public void testMethodPass() { + try { + Thread.sleep(100); + assertEquals(10,10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void testMethodPass2() { + try { + Thread.sleep(100); + assertEquals("one","one"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void testMethodFail() { + try { + Thread.sleep(100); + assertEquals(20,10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void testMethodFail2() { + try { + Thread.sleep(100); + assertEquals("one","two"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + // Normal test failure + public void testFail() { + fail("Test failure"); + } + + // Generate test error + public void testException() { + @SuppressWarnings("unused") + int i = 27 / 0; // will generate Divide by zero error + } + + public void testStringCompareFail(){ + assertEquals("this","that"); + } + + public void testObjectCompareFail(){ + assertEquals(new Object(),new Object()); + } +} diff --git a/ApacheJmeter/src/woolfel/SubDummyTest.java b/ApacheJmeter/src/woolfel/SubDummyTest.java new file mode 100644 index 0000000..d6f8b5c --- /dev/null +++ b/ApacheJmeter/src/woolfel/SubDummyTest.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 woolfel; + +public class SubDummyTest extends DummyTestCase { + + public SubDummyTest() { + super(); + System.out.println("public SubDummyTest()"); + } + + public SubDummyTest(String arg0) { + super(arg0); + System.out.println("public SubDummyTest("+arg0+")"); + } + + public void oneTimeSetUp() { + System.out.println("SubDummyTest#oneTimeSetUp(): "+getName()); + } + + public void oneTimeTearDown() { + System.out.println("SubDummyTest#oneTimeTearDown(): "+getName()); + } +} diff --git a/ApacheJmeter/src/woolfel/SubDummyTest2.java b/ApacheJmeter/src/woolfel/SubDummyTest2.java new file mode 100644 index 0000000..36f143c --- /dev/null +++ b/ApacheJmeter/src/woolfel/SubDummyTest2.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package woolfel; + +public class SubDummyTest2 extends DummyTestCase { + + @SuppressWarnings("unused") + private SubDummyTest2() { + super(); + System.out.println("private SubDummyTest2()"); + } + + public SubDummyTest2(String arg0) { + super(arg0); + System.out.println("public SubDummyTest2("+arg0+")"); + } + + public void oneTimeSetUp() { + System.out.println("SubDummyTest2#oneTimeSetUp(): "+getName()); + } + + public void oneTimeTearDown() { + System.out.println("SubDummyTest2#oneTimeTearDown(): "+getName()); + } +} diff --git a/ApacheJmeter/xdocs/building.xml b/ApacheJmeter/xdocs/building.xml new file mode 100644 index 0000000..cad5150 --- /dev/null +++ b/ApacheJmeter/xdocs/building.xml @@ -0,0 +1,75 @@ + + + + + Building JMeter and Add-Ons + + +

+ +Note to developers: +This is a very brief overview. +There is more infomation on the JMeter Wiki. + +

Building Add-Ons

+

+There is no need to build JMeter if you just want to build an add-on. +Just download the binary archive and add the jars to the classpath. +You may want to also download the source so it can be used by the IDE. +

+

See the extras/addons* files in the source tree for some suggestions

+ +

Building JMeter

+

Acquiring the source

+

The full source is distributed alongside the binary, or it can be downloaded from SVN.

+

+The source archive and SVN do not contain any of the required library files. +These need to be downloaded by running the Ant command: +

+ant download_jars
+
+

+

Or you can download the binary distribution archive for a release and unpack it into the same directory structure as the source. +This will ensure that the lib/ directory contains the jar files needed for running JMeter. +There are a few additional jars that are needed to build JMeter, download these using: +

+ant download_jars
+
+This will retrieve any missing jars. +

+

Compiling and packaging JMeter using Ant

+

+JMeter can be built entirely using Ant. +The basic command is: +

+ant [install]
+
+See build.xml for the other targets that can be used. +

+

Compiling and packaging JMeter using Eclipse

+

+Once you have downloaded the source from SVN or the release archives and run the ant download_jars target to +install the dependent jars, you can configure Eclipse. The easiest way to do this is to replace the Eclipse .classpath +file with the eclipse.classpath file provided with JMeter. This will set up the source-paths and most of the libraries. +

+See also the file eclipse.readme. +

+

+
+ + diff --git a/ApacheJmeter/xdocs/changes.xml b/ApacheJmeter/xdocs/changes.xml new file mode 100644 index 0000000..666b3e5 --- /dev/null +++ b/ApacheJmeter/xdocs/changes.xml @@ -0,0 +1,137 @@ + + + + + JMeter developers + Changes + + +
+ + +This page details the changes made in the current version only. +

+Earlier changes are detailed in the History of Previous Changes. +
+ + + + +

Version 2.8

+ +

New and Noteworthy

+ + + +

Known bugs

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see Bug 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ + + +

Incompatible changes

+ + + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
+ +

Other Samplers

+
    +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
+ +

Assertions

+
    +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
+ +

Other samplers

+
    +
  • 55310 - TestAction should implement Interruptible
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
+ +

Non-functional changes

+
    +
  • 53311 - JMeterUtils#runSafe should not throw Error when interrupted
  • +
  • Updated to commons-net-3.1 (from 3.0.1)
  • +
+ +
+ +
\ No newline at end of file diff --git a/ApacheJmeter/xdocs/changes_history.xml b/ApacheJmeter/xdocs/changes_history.xml new file mode 100644 index 0000000..b3d9cc0 --- /dev/null +++ b/ApacheJmeter/xdocs/changes_history.xml @@ -0,0 +1,3287 @@ + + + + + JMeter developers + History of Previous Changes + + +
+ +This page details the changes made in previous versions only. +

+Current changes are detailed in Changes. +
+

Changes sections are chronologically ordered from top (most recent) to bottom +(least recent)

+ + + +

Version 2.7

+ +

New and Noteworthy

+ +

OS Process Sampler

+

A new System Sampler that can be used to execute commands on the local machine. +

+

+ +

OS Process Sampler results example with DNS lookup command 'dig' +

+

+ +

JMS Samplers improvements

+

Addition of a "Non Persistent Delivery" option to send "Non-Persistent" (Guaranteed to be delivered at most once. Message loss is not a concern.) JMS messages +

+

+ +

Support sending of JMS Object Messages to enable sending Objects unmarshalled from XML by XStream +

+

+ +

Enable setting JMS Properties through JMS Publisher sampler +

+

+ +

Test Action sampler

+

Allow premature exit from a loop +

+

+ +

Webservice Sampler improvements

+

Add a jmeter property soap.document_cache to control size of Document Cache +

+

+ +

Make Maintain HTTP Session configurable +

+

+ +

Aggregate graph: Clustered Bar char with average, median, 90% line, min and max columns

+

Aggregate graph changes to Clustered Bar chart, add more columns (median, 90% line, min, max) and options, fixed some bugs +

+

+ +

New settings for aggregate graph +

+

+ +

Improvements of HTML report design generated by JMeter Ant task in extras folder

+

HTML report example +

+

+ +

HTML report example with some assertion errors +

+

+ +

Mailer Visualizer

+

    +
  • Enable authentication, and connection security with SSL or TLS
  • +
  • Improve GUI design
  • +
  • Add internationalisation (i18n) support
  • +
+
+

+ +

New Visual Indicator of number of ERROR/FATAL messages in logs

+

Indicator shows number of ERROR/FATAL messsages in logs, it can be clicked to toggle Log Viewer panel +

+

+ +

Dialog box to show detail of a parameter row

+

Add a detail button on parameters table to show detail of a Row +

+

+ +

Detail box example +

+

+ +

Plugin writers

+

+New interface org.apache.jmeter.engine.util.ConfigMergabilityIndicator has been introduced to tell whether a ConfigTestElement can be merged in Sampler (see 53042):
+

public boolean applies(ConfigTestElement configElement);
+

+ +

New interface org.apache.jmeter.protocol.http.proxy.SamplerCreator to allow plugging HTTP based samplers that differ from default HTTP Samplers through Proxy during Recording Phase (see 52674):
+

public String[] getManagedContentTypes();
+
public HTTPSamplerBase createSampler(HttpRequestHdr request, Map<String, String> pageEncodings, Map<String, String> formEncodings);
+
public void populateSampler(HTTPSamplerBase sampler, HttpRequestHdr request, Map<String, String> pageEncodings, Map<String, String> formEncodings) throws Exception;
+

+ + + + +

Known bugs

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ + + +

Incompatible changes

+ +

+When doing replacement of User Defined Variables, Proxy will not substitute partial values anymore when "Regexp matching" is used. It will use Perl 5 word matching ("\b") +

+ +

+In User Defined Variables, Test Plan, HTTP Sampler Arguments Table, Java Request Defaults, JMS Sampler and Publisher, LDAP Request Defaults and LDAP Extended Request Defaults, rows with +empty Name and Value are no more saved. +

+ +

+JMeter now expands the Test Plan tree to the testplan level and no further and selects the root of the tree. Furthermore default value of onload.expandtree is false. +

+ +

+Graph Full Results Listener has been removed. +

+ +

+When calling "Clear All" command, if Log Viewer is displayed its content will be cleared. +

+ + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 52613 - Using Raw Post Body option, text gets encoded
  • +
  • 52781 - Content-Disposition header garbled even if browser compatible headers is checked (HC4)
  • +
  • 52796 - MonitorHandler fails to clear variables when starting a new parse
  • +
  • 52871 - Multiple Certificates not working with HTTP Client 4
  • +
  • 52885 - Proxy : Recording issues with HTTPS, cookies starting with secure are partly truncated
  • +
  • 52886 - Proxy : Recording issues with HTTPS when spoofing is on, secure cookies are not always changed
  • +
  • 52897 - HTTPSampler : Using PUT method with HTTPClient4 and empty Content Encoding and sending files leads to NullPointerException
  • +
  • 53145 - HTTP Sampler - function in path evaluated too early
  • +
+ +

Other Samplers

+
    +
  • 51737 - TCPSampler : Packet gets converted/corrupted
  • +
  • 52868 - BSF language list should be sorted
  • +
  • 52869 - JSR223 language list currently uses BSF list which is wrong
  • +
  • 52932 - JDBC Sampler : Sampler is not marked in error in an Exception which is not of class IOException, SQLException, IOException occurs
  • +
  • 52916 - JDBC Exception if there is an empty user defined variable
  • +
  • 52937 - Webservice Sampler : Clear Soap Documents Cache at end of Test
  • +
  • 53027 - Jmeter starts throwing exceptions while using SMTP Sample in a test plan with HTTP Cookie Mngr or HTTP Request Defaults
  • +
  • 53072 - JDBC PREPARED SELECT statements should return results in variables like non prepared SELECT
  • +
+ +

Controllers

+
    +
  • 52968 - Option Start Next Loop in Thread Group does not mark parent Transaction Sampler in error when an error occurs
  • +
  • 50898 - IncludeController : NullPointerException loading script in non-GUI mode if Includers use same element name
  • +
+ +

Listeners

+
    +
  • 43450 - Listeners/Savers assume SampleResult count is always 1; fixed Generate Summary Results
  • +
+ +

Assertions

+
    +
  • 52848 - NullPointer in "XPath Assertion"
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
  • 52551 - Function Helper Dialog does not switch language correctly
  • +
  • 52552 - Help reference only works in English
  • +
+ +

General

+
    +
  • 52639 - JSplitPane divider for log panel should be hidden if log is not activated
  • +
  • 52672 - Change Controller action deletes all but one child samplers
  • +
  • 52694 - Deadlock in GUI related to non AWT Threads updating GUI
  • +
  • 52678 - Proxy : When doing replacement of UserDefinedVariables, partial values should not be substituted
  • +
  • 52728 - CSV Data Set Config element cannot coexist with BSF Sampler in same Thread Plan
  • +
  • 52762 - Problem with multiples certificates: first index not used until indexes are restarted
  • +
  • 52741 - TestBeanGUI default values do not work at second time or later
  • +
  • 52783 - oro.patterncache.size property never used due to early init
  • +
  • 52789 - Proxy with Regexp Matching can fail with NullPointerException in Value Replacement if value is null
  • +
  • 52645 - Recording with Proxy leads to OutOfMemory
  • +
  • 52679 - User Parameters columns narrow
  • +
  • 52843 - Sample headerSize and bodySize not being accumulated for subsamples
  • +
  • 52967 - The function __P() couldn't use default value when running with remote server in GUI mode.
  • +
  • 50799 - Having a non-HTTP sampler in a http test plan prevents multiple header managers from working
  • +
  • 52997 - Jmeter should not exit without saving Test Plan if saving before exit fails
  • +
  • 53136 - Catching Throwable needs to be carefully handled
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
+ +

Other samplers

+
    +
  • 52775 - JMS Publisher : Add Non Persistent Delivery option
  • +
  • 52810 - Enable setting JMS Properties through JMS Publisher sampler
  • +
  • 52938 - Webservice Sampler : Add a jmeter property soap.document_cache to control size of Document Cache
  • +
  • 52939 - Webservice Sampler : Make MaintainSession configurable
  • +
  • 53073 - Allow to assign the OUT result of a JDBC CALLABLE to JMeter variables
  • +
  • 53164 - New System Sampler
  • +
  • 53172 - OS Process Sampler - allow specification of Environment Variables
  • +
  • 52936 - JMS Publisher : Support sending of JMS Object Messages
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • 52603 - MailerVisualizer : Enable SSL , TLS and Authentication
  • +
  • 52698 - Remove Graph Full Results Listener
  • +
  • 53070 - Change Aggregate graph to Clustered Bar chart, add more columns (median, 90% line, min, max) and options, fixed some bugs
  • +
  • 53246 - Mailer Visualizer: improve GUI design and I18N
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
+ +

Functions

+
    +
+ +

I18N

+
    +
  • Mailer Visualizer has been internationalized. French translation added. (see 53246)
  • +
+ +

General

+
    +
  • 45839 - Test Action : Allow premature exit from a loop
  • +
  • 52614 - MailerModel.sendMail has strange way to calculate debug setting
  • +
  • 52782 - Add a detail button on parameters table to show detail of a Row
  • +
  • 52674 - Proxy : Add a Sampler Creator to allow plugging HTTP based samplers using potentially non textual POST Body (AMF, Silverlight...) and customizing them for others
  • +
  • 52934 - GUI : Open Test plan with the tree expanded to the testplan level and no further and select the root of the tree
  • +
  • 52941 - Improvements of HTML report design generated by JMeter Ant task extra
  • +
  • 53042 - Introduce a new method in Sampler interface to allow Sampler to decide wether a config element applies to Sampler
  • +
  • 52771 - Documentation : Added RSS feed on JMeter Home page under link "Subscribe to What's New"
  • +
  • 42784 - Show the number of errors logged in the GUI
  • +
  • 53256 - Make Clear All command clean LogViewer content
  • +
  • 53261 - Make "Error/fatal" counter added in 42784 open Log Viewer panel when Warn Indicator is clicked
  • +
+ +

Non-functional changes

+
    +
  • Upgraded to rhino 1.7R3 (was js-1.7R2.jar). +Note: the Maven coordinates for the jar were changed from rhino:js to org.mozilla:rhino. +This does not affect JMeter directly, but might cause problems if using JMeter in a Maven project +with other code that depends on an earlier version of the Rhino Javascript jar. +
  • +
  • 52675 - Refactor Proxy and HttpRequestHdr to allow Sampler Creation by Proxy
  • +
  • 52680 - Mention version in which function was introduced
  • +
  • 52788 - HttpRequestHdr : Optimize code to avoid useless work
  • +
  • JMeter Ant (ant-jmeter-1.1.1.jar) task was upgraded from 1.0.9 to 1.1.1
  • +
  • Updated to commons-io 2.2 (from 2.1)
  • +
  • 53129 - Upgrade XStream from 1.3.1 to 1.4.2
  • +
  • Updated to httpcomponents-client 4.1.3 (from 4.1.2)
  • +
  • Updated JMeter distributed testing guide (jmeter_distributed_testing_step_by_step.pdf). Changes source format to OpenOffice odt (from sxw)
  • +
+ + + +

Version 2.6

+ +

New and Noteworthy

+ +

Toolbar

+

A new toolbar on JMeter's main window +

+

+ +

JMeter start test button

+

A new menu option and button allow to start a test ignoring the Pause Timers +

+

+ +

JMeter GUI Look and Feel

+

Allow System or CrossPlatform LAF to be set from options menu +

+

+ +

JMeter GUI - duplicate node

+

Add "duplicate node" in context menu +

+

+ +

JMeter tree view - search facility

+

Functionality to search by keyword in Samplers Tree View +

+

+ +

HTTP Request - raw request pane

+

Improve HTTP Request GUI to better show parameters without name (GWT RPC request or SOAP request for example) +

+

+ +

HTTP Request - other changes

+

    +
  • Allow multiple selection in arguments panel
  • +
  • Allow to add (paste) entries from the clipboard to an arguments list
  • +
  • Ability to move variables up or down in HTTP Request
  • +
+
+

+ +

HTTP Request - file protocol

+

Better support for file: protocol in HTTP sampler +

+

+

Retrieve embedded resources with file: protocol +

+

+ +

HTTP Request - Ignore embedded resources failed

+

Enable "ignore failed" for embedded resources +

+

+

Parent success with a embedded resource failed +

+

+ +

View Results in Table - child sample display

+

Add option to TableVisualiser to display child samples instead of parent +

+

+ +

Key Store - multiple certificates

+

Allowing multiple certificates (JKS) +

+

+ +

Aggregate graph improvements

+

Some improvements on Aggregate Graph Listener: +

  • new GUI for settings
  • +
  • dynamic graph size
  • +
  • allow to change fonts for title graph and legend
  • +
  • allow to change bar color (background and text values)
  • +
  • allow to draw or not bars outlines
  • +
  • allow to select only some samplers by a regexp filter
  • +
  • allow to define Y axis maximum scale
  • +
+
+

+ +

Aggregate Graph bar +

+

+ +

Counter - new reset option

+

Add an option to reset counter on each Thread Group iteration +

+

+ +

Functions

+

    +
  • Add a new function __RandomString to generate random Strings
  • +
  • Add a new function __TestPlanName returning the name of the current "Test Plan"
  • +
  • Add a new function __machineIP returning IP address
  • +
  • Add a new function __jexl2 to support Jexl2
  • +
+
+

+ +

User Defined Variable improvements

+

  • Add a comment field in User Defined Variables
  • +
  • Allow to add (paste) entries from the clipboard to an arguments list
  • +
  • Ability to move up or down variables in User Defined Variables
  • +
+
+

+ +

View Results Tree

+

In View Results Tree rather than showing just a message if the results are to big, show as much of the result as are configured +

+

+ +

Controllers - change elements

+

Add ability to Change Controller elements +

+

+ +

JDBC pre- and post-processor

+

Add JDBC pre- and post-processor +

+

+ +

JDBC transaction isolation option

+

Allow to set the transaction isolation in the JDBC Connection Configuration +

+

+ +

Poisson Timer

+

Add a Poisson based timer +

+

+ +

GUI and OS interaction

+

Support for file Drag and Drop. +

+

+ +

Confirm Remove Dialog box

+

Add a dialog box to confirm removing the element(s) when Remove action is called +

+The dialogue can be skipped by setting the JMeter property confirm.delete.skip=true +

+ +

Remote batching support

+

Use external store to hold samples during distributed testing, +Added DiskStore remote sample sender: like Hold, but saves samples to disk until end of test +

+

+ +

JMS Subscriber sampler

+

With JMS Subscriber, ability to use Selectors +

+

+ +

New Logger Panel

+

A new Log Viewer has been added to the GUI and can be enabled from menu Options > Log Viewer: +

+

+

This Log Viewer shows the jmeter.log file, and useful (for example) to debug BeanShell/BSF scripts: +

+

+ +

The menu item Options / Choose Language is now fully functional

+

+The menu item Options / Choose Language now changes all the displayed text to the new language provided +all messages are translated. You can help on this by translating into your language. +

+ +

Legacy JMX and JTL Avalon format support restored

+

+Support for reading/writing the original Avalon XML format of JMX (script) and JTL (sample result) files was dropped in JMeter version 2.4. +JMeter can now read the Avalon format files again, however there is no support for saving files in the old format. +

+ +

JMeter jars available from Maven repository

+

+JMeter jars are now available from Maven repository. +

+ + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode (see Bugs 40671, 41286, 44973, 50898). +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

Listeners don't show iteration counts when a If Controller has a condition which is always false from the first iteration (see 52496). +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ + + +

Incompatible changes

+ +

+JMeter versions since 2.1 failed to create a container sample when loading embedded resources. +This has been corrected; can still revert to the 51939 behaviour by setting the following property: +httpsampler.separate.container=false +

+

+Mirror server now uses default port 8081, was 8080 before 2.5.1. +

+

+TCP Sampler handles SocketTimeoutException, SocketException and InterruptedIOException differently since 2.6, when +these occurs, Sampler is marked as failed. +

+

+Sample Sender implementations now resolve their configuration on Client side since 2.6. +This behaviour can be changed with property sample_sender_client_configured (set it to false). +

+ +

+The HTTP User Parameter Modifier test element has been removed; it has been deprecated for a long time. +

+ + + +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 51932 - CacheManager does not handle cache-control header with any attributes after max-age
  • +
  • 51918 - GZIP compressed traffic produces errors, when multiple connections allowed
  • +
  • 51939 - Should generate new parent sample if necessary when retrieving embedded resources
  • +
  • 51942 - Synchronisation issue on CacheManager when Concurrent Download is used
  • +
  • 51957 - Concurrent get can hang if a task does not complete
  • +
  • 51925 - Calling Stop on Test leaks executor threads when concurrent download of resources is on
  • +
  • 51980 - HtmlParserHTMLParser double-counts images used in links
  • +
  • 52064 - OutOfMemory Risk in CacheManager
  • +
  • 51919 - Random ConcurrentModificationException or NoSuchElementException in CookieManager#removeMatchingCookies when using Concurrent Download
  • +
  • 52126 - HttpClient4 does not clear cookies between iterations
  • +
  • 52129 - Reported Body Size is wrong when using HTTP Client 4 and Keep Alive connection
  • +
  • 52137 - Problems with HTTP Cache Manager
  • +
  • 52221 - Nullpointer Exception with use Retrieve Embedded Resource without HTTP Cache Manager
  • +
  • 52310 - variable in IPSource failed HTTP request if "Concurrent Pool Size" is enabled
  • +
  • 52371 - API Incompatibility - Methods in HTTPSampler2 now require PostMethod instead of HttpMethod[Base]. Reverted to original types.
  • +
  • 49950 - Proxy : IndexOutOfBoundsException when recording with Proxy server
  • +
  • 52409 - HttpSamplerBase#errorResult modifies sampleResult passed as parameter; +fix code which assumes that a new instance is created (i.e. when adding a sub-sample) +
  • +
  • 52507 - Delete Http User Parameters modifier (deprecated, obsolete)
  • +
+ +

Other Samplers

+
    +
  • 51996 - JMS Initial Context leak newly created Context when Multiple Thread enter InitialContextFactory#lookupContext at the same time
  • +
  • 51691 - Authorization does not work for JMS Publisher and JMS Subscriber
  • +
  • 52036 - Durable Subscription fails with ActiveMQ due to missing clientId field
  • +
  • 52044 - JMS Subscriber used with many threads leads to javax.naming.NamingException: Something already bound with ActiveMQ
  • +
  • 52072 - LengthPrefixedBinaryTcpClientImpl may end a sample prematurely
  • +
  • 52390 - AbstractJDBCTestElement:Memory leak and synchronization issue in perConnCache
  • +
+ +

Controllers

+
    +
  • 51865 - Infinite loop inside thread group does not work properly if "Start next loop after a Sample error" option set
  • +
  • 51868 - A lot of exceptions in jmeter.log while using option "Start next loop" for thread
  • +
  • 51866 - Counter under loop doesn't work properly if "Start next loop on error" option set for thread group
  • +
  • 52296 - TransactionController + Children ThrouputController or InterleaveController leads to ERROR sampleEnd called twice java.lang.Throwable: Invalid call sequence when TPC does not run sample
  • +
  • 52330 - With next-Loop-On-Error after error samples are not executed in next loop
  • +
+ +

Listeners

+
    +
  • 52357 - View results in Table does not allow for multiple result samples
  • +
  • 52491 - Incorrect parsing of Post data parameters in Tree Listener / Http Request view
  • +
+ +

Assertions

+
    +
  • 52519 - XMLSchemaAssertion uses JMeter JVM file.encoding instead of response encoding
  • +
+ +

Functions

+
    +
  • The CRLF example for the char function was wrong; CRLF=(0xD,0xA), not (0xC,0xA)
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • 51937 - JMeter does not handle missing TestPlan entry well
  • +
  • 51988 - CSV Data Set Configuration does not resolve default delimiter for header parsing when variables field is empty
  • +
  • 52003 - View Results Tree "Scroll automatically" does not scroll properly in case nodes are expanded
  • +
  • 27112 - User Parameters should use scrollbars
  • +
  • 52029 - Command-line shutdown only gets sent to last engine that was started
  • +
  • 52093 - Toolbar ToolTips don't switch language
  • +
  • 51733 - SyncTimer is messed up if you a interrupt a test plan
  • +
  • 52118 - New toolbar : shutdown and stop buttons not disabled when no test is running
  • +
  • 52125 - StatCalculator.addAll(StatCalculator calc) joins incorrect if there are more samples with the same response time in one of the TreeMap
  • +
  • 52339 - JMeter Statistical mode in distributed testing shows wrong response time
  • +
  • 52215 - Confusing synchronization in StatVisualizer, SummaryReport ,Summariser and issue in StatGraphVisualizer
  • +
  • 52216 - TableVisualizer : currentData field is badly synchronized
  • +
  • 52217 - ViewResultsFullVisualizer : Synchronization issues on root and treeModel
  • +
  • 43294 - XPath Extractor namespace problems
  • +
  • 52224 - TestBeanHelper does not support NOT_UNDEFINED == Boolean.FALSE
  • +
  • 52279 - Switching to another language loses icons in Tree and logs error Can't obtain GUI class from ...
  • +
  • 52280 - The menu item Options / Choose Language does not change all the displayed text to the new language
  • +
  • 52376 - StatCalculator#addValue(T val, int sampleCount) should use long, not int
  • +
  • 49374 - Encoding of embedded element URLs depend on the file.encoding property
  • +
  • 52399 - URLRewritingModifier uses default file.encoding to match text content
  • +
  • 50438 - code calculates average with integer math, expecting double value
  • +
  • 52469 - Changes in Support of SSH-Tunneling of RMI traffic for Remote Testing
  • +
  • 52466 - Upgrade Test Plan feature : NameUpdater does not upgrade properties
  • +
  • 52503 - Unify File->Close and Window close file saving behaviour
  • +
  • 52537 - Help does not scroll to correct anchor when file is first loaded
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • 51981 - Better support for file: protocol in HTTP sampler
  • +
  • 52033 - Allowing multiple certificates (JKS)
  • +
  • 52352 - Proxy : Support IPv6 URLs capture
  • +
  • 44301 - Enable "ignore failed" for embedded resources
  • +
+ +

Other samplers

+
    +
  • 51419 - JMS Subscriber: ability to use Selectors
  • +
  • 52088 - JMS Sampler : Add a selector when REQUEST / RESPONSE is chosen
  • +
  • 52104 - TCP Sampler handles badly errors
  • +
  • 52087 - TCPClient interface does not allow for partial reads
  • +
  • 52115 - SOAP/XML-RPC should not send a POST request when file to send is not found
  • +
  • 40750 - TCPSampler : Behaviour when sockets are closed by remote host
  • +
  • 52396 - TCP Sampler in "reuse connection mode" reuses previous sampler's connection even if it's configured with other host, port, user or password
  • +
  • 52048 - BSFSampler, BSFPreProcessor and BSFPostProcessor should share the same GUI
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • 52022 - In View Results Tree rather than showing just a message if the results are to big, show as much of the result as are configured
  • +
  • 52201 - Add option to TableVisualiser to display child samples instead of parent
  • +
  • 52214 - Save Responses to a file - improve naming algorithm
  • +
  • 52340 - Allow remote sampling mode to be changed at run-time
  • +
  • 52452 - Improvements on Aggregate Graph Listener (GUI and settings)
  • +
  • Resurrected OldSaveService to allow reading Avalon format JTL (result) files
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 52128 - Add JDBC pre- and post-processor
  • +
  • 52183 - SyncTimer could be improved (performance+reliability)
  • +
  • 52317 - Counter : Add option to reset counter on each Thread Group iteration
  • +
  • 37073 - Add a Poisson based timer
  • +
  • 52497 - Improve DebugSampler and DebugPostProcessor
  • +
+ +

Functions

+
    +
  • 52006 - Create a function RandomString to generate random Strings
  • +
  • 52016 - It would be useful to support Jexl2
  • +
  • __char() function now supports octal values
  • +
  • New function __machineIP returning IP address
  • +
  • 51091 - New function returning the name of the current "Test Plan"
  • +
+ +

I18N

+
    +
+ +

General

+
    +
  • 51892 - Default mirror port should be different from default proxy port
  • +
  • 51817 - Moving variables up and down in User Defined Variables control
  • +
  • 51876 - Functionality to search in Samplers TreeView
  • +
  • 52019 - Add menu option to Start a test ignoring Pause Timers
  • +
  • 52027 - Allow System or CrossPlatform LAF to be set from options menu
  • +
  • 52037 - Remember user-set LaF over restarts.
  • +
  • 51861 - Improve HTTP Request GUI to better show parameters without name (GWT RPC requests for example) (UNDER DEVELOPMENT)
  • +
  • 52040 - Add a toolbar in JMeter main window
  • +
  • 51816 - Comment Field in User Defined Variables control.
  • +
  • 52052 - Using a delimiter to separate result-messages for JMS Subscriber
  • +
  • 52103 - Add automatic scrolling option to table visualizer
  • +
  • 52097 - Save As should point to same folder that was used to open a file if MRU list is used
  • +
  • 52085 - Allow multiple selection in arguments panel
  • +
  • 52099 - Allow to set the transaction isolation in the JDBC Connection Configuration
  • +
  • 52116 - Allow to add (paste) entries from the clipboard to an arguments list
  • +
  • 52160 - Don't display TestBeanGui items which are flagged as hidden
  • +
  • 51886 - SampleSender configuration resolved partly on client and partly on server
  • +
  • 52161 - Enable plugins to add own translation rules in addition to upgrade.properties. +Loads any additional properties found in META-INF/resources/org.apache.jmeter.nameupdater.properties files
  • +
  • 42538 - Add "duplicate node" in context menu
  • +
  • 46921 - Add Ability to Change Controller elements
  • +
  • 52240 - TestBeans should support Boolean, Integer and Long
  • +
  • 52241 - GenericTestBeanCustomizer assumes that the default value is the empty string
  • +
  • 52242 - FileEditor does not allow output to be saved in a File
  • +
  • 51093 - when loading a selection previously stored by "Save Selection As", show the file name in the blue window bar
  • +
  • 50086 - Password fields not Hidden in JMS Publisher, JMS Subscriber, Mail Reader sampler, SMTP sampler and Database Configuration
  • +
  • 29352 - Use external store to hold samples during distributed testing, Added DiskStore remote sample sender: like Hold, but saves samples to disk until end of test.
  • +
  • 52333 - Reduce overhead in calculating SampleResult#nanoTimeOffset
  • +
  • 52346 - Shutdown detects if there are any non-daemon threads left which prevent JVM exit.
  • +
  • 52281 - Support for file Drag and Drop
  • +
  • 52471 - Improve Mirror Server performance by Using Pool of threads instead of launching a Thread for each request
  • +
  • Resurrected OldSaveService to allow reading Avalon format JMX files (removed in 2.4)
  • +
  • Add a dialog box to confirm removing the element(s) when Remove action is called
  • +
  • 41788 - Log viewer (console window) needed as an option
  • +
  • Add option to change the pause time (default 2000ms) in the daemon thread which checks for successful JVM exit. +The thread is not now started unless the pause time is greater than 0. +
  • +
+ +

Non-functional changes

+
    +
  • fixes to build.xml: support scripts; localise re-usable property names
  • +
  • 51923 - Counter function bug or documentation issue ? (fixed docs)
  • +
  • Update velocity.jar to 1.7 (from 1.6.2)
  • +
  • Update js.jar to 1.7R3 (from 1.6R5)
  • +
  • Update commons-codec 1.5 => 1.6
  • +
  • Update commons-io 2.0.1 => 2.1
  • +
  • Update commons-jexl 2.0.1 => 2.1.1
  • +
  • Update jdom 1.1 => 1.1.2
  • +
  • Update junit 4.9 => 4.10
  • +
  • 51954 - Generated documents include </br> entries which cause extra blank lines
  • +
  • 52075 - JMeterProperty.clone() currently returns Object; it should return JMeterProperty
  • +
  • Updated httpcore to 4.1.4
  • +
  • 49753 - Please publish jMeter artifacts on Maven central repository
  • +
+ + + +

Version 2.5.1

+ +

Summary of main changes

+ +
    +
  • HttpClient4 sampler now re-uses connections properly (previously it would use one per sample, which could quickly cause resource exhaustion).
  • +
  • Various fixes to JMS samplers
  • +
  • Functions are no longer spuriously invoked when used with a Configuration element
  • +
  • WebService sampler GUI has been re-organized for better design and more user-friendliness. Some improments on WSDL configuration assistant
  • +
  • Better handling of test shutdown. System.exit now only called if there is no other option; even this can be disabled.
  • +
+ + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

The Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

The If Controller may cause an infinite loop if the condition is always false from the first iteration. +A workaround is to add a sampler at the same level as (or superior to) the If Controller. +For example a Test Action sampler with 0 wait time (which doesn't generate a sample), +or a Debug Sampler with all fields set to False (to reduce the sample size). +

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+ +

+The HttpClient4 and Commons HttpClient 3.1 samplers previously used a retry count of 3. +This has been changed to default to 1, to be compatible with the Java implementation. +The retry count can be overridden by setting the relevant JMeter property, for example: +

+httpclient4.retrycount=3
+httpclient3.retrycount=3
+
+

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • Fix HttpClient 4 sampler so it reuses HttpClient instances and connections where possible.
  • +
  • Temporary fix to HC4 sampler to work round HTTPCLIENT-1120.
  • +
  • 51863 - Lots of ESTABLISHED connections with HttpClient 4 implementation (vs HttpClient 3.1 impl)
  • +
  • 51750 - Retrieve all embedded resources doesn't follow IFRAME
  • +
  • 51752 - HTTP Cache is broken when using "Retrieve all embedded resources" with concurrent pool
  • +
  • 39219 - HTTP Server: You can't stop it after File->Open
  • +
  • 51775 - Port number duplicates in Host header when capturing by HttpClient (3.1 and 4.x)
  • +
  • 50617 - Monitor Results legend show "dead" server although values from the server are retrieved
  • +
+ +

Other Samplers

+
    +
  • 50424 - Web Methods drop down list box inconsistent
  • +
  • 43293 - Java Request fields not cleared when creating new sampler
  • +
  • 51830 - Webservice Soap Request triggers too many popups when Webservice WSDL URL is down
  • +
  • WebService(SOAP) request - add a connect timeout to get the wsdl used to populate Web Methods when server doesn't response
  • +
  • 51841 - JMS : If an error occurs in ReceiveSubscriber constructor or Publisher, then Connections will stay open
  • +
  • 51691 - Authorization does not work for JMS Publisher and JMS Subscriber
  • +
  • 51840 - JMS : Cache of InitialContext has some issues
  • +
  • 47888 - JUnit Sampler re-uses test object
  • +
+ +

Controllers

+
    +
  • If Controller - Fixed two regressions introduced by 50032 (see 50618 too)
  • +
  • If Controller - Catches a StackOverflowError when a condition returns always false (after at least one iteration with return true) See 50618
  • +
  • 51869 - NullPointer Exception when using Include Controller
  • +
+ +

Listeners

+
    +
+ +

Assertions

+
    +
+ +

Functions

+
    +
  • 48943 - Functions are invoked additional times when used in combination with a Config Element
  • +
+ +

I18N

+
    +
  • WebService(SOAP) request - add I18N for some labels
  • +
+ +

General

+
    +
  • 51831 - Cannot disable UDP server or change the maximum UDP port
  • +
  • 51821 - Add short-cut for Enabling / Disabling (sub)tree or branches in test plan.
  • +
  • 47921 - Variables not released for GC after JMeterThread exits.
  • +
  • 51839 - "... end of run" printed prematurely
  • +
  • 51847 - Some Junit tests are Locale sensitive and fail if Locale is different from US
  • +
  • 51855 - Parent samples may have slightly inaccurate elapsed times
  • +
  • 51880 - The shutdown command is not working if I invoke it before all the thread are started
  • +
  • Remote Shut host menu item was not being enabled.
  • +
  • 51888 - Occasional deadlock when stopping a testplan
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • 51380 - Control reuse of cached SSL Context from iteration to iteration
  • +
  • 51882 - HTTPHC3Client uses a default retry count of 3, make it configurable; default is now 1
  • +
  • Change the default HttpClient 4 sampler retry count to 1
  • +
+ +

Other samplers

+
    +
  • Beanshell Sampler now supports Interruptible interface
  • +
  • 51605 - WebService(SOAP) Request - WebMethod field value changes surreptitiously for all the requests when a value is selected in a request
  • +
  • WebService(SOAP) Request - Reorganized GUI for better design and more user-friendliness
  • +
+ +

Controllers

+
    +
+ +

Listeners

+
    +
  • 42246 - Need for a 'auto-scroll' option in "View Results Tree" and "Assertion Results"
  • +
  • View Results Tree: Regexp Tester - little improvements on user interface
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 51885 - Allow a JMeter Variable as input to XPathExtractor
  • +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • 51822 - (part 1) save 1 invocation of GuiPackage#getCurrentGui
  • +
  • Added AsynchSampleSender which sends samples from server to client asynchronously.
  • +
  • Upgraded to htmlparser 2.1; JavaMail 1.4.4; JUnit 4.9
  • +
+ +

Non-functional changes

+
    +
  • 49976 - FormCharSetFinder visibility is default instead of public.
  • +
  • 50917 - Property CookieManager.save.cookies not honored when set from test plan
  • +
  • Improve error logging when Javascript errors are detected.
  • +
  • Updated documentation footer
  • +
+ + + +

Version 2.5

+ +

Summary of main changes

+ +
    +
  • The HTTP implementation can now be selected at run-time, and JMeter now also supports Apache HttpComponents HttpClient 4.x. +Note that Commons HttpClient 3.1 is no longer actively developed, and support may be removed from JMeter in a future release. +
  • +
  • The HTTP sampler now allows concurrent downloads of embedded resources in an HTML page
  • +
  • The HTTP Sampler can now report the size of a request before decompression.
  • +
  • The JMS and Mail samplers have been improved.
  • +
  • The new Test Fragment Test Element makes using Include Controllers easier
  • +
  • There are various improvements to the View Results Tree Listener
  • +
  • 30563 - Thread Group should have a start next loop option on Sample Error
  • +
  • There are two new Thread Group types - setUp and tearDown - which are run before and after the main Thread groups.
  • +
  • Client-Server mode now supports external stop/shutdown via UDP

    +multiple JMeter server instances can be started on the same host without needing to change the port property.
  • +
  • 50516 - "Host" header in HTTP Header Manager is not included in generated HTTP request
  • +
+ +

+

    +
+

+ + + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+ +

+Unsupported methods are no longer converted to GET by the Commons HttpClient sampler. +

+ +

+Removed method public static long currentTimeInMs(). +This has been replaced by the instance method public long currentTimeInMillis(). +

+ +

+ProxyControl.getSamplerTypeName() now returns a String rather than an int. +This is internal to the workings of the JMeter Proxy & its GUI, so should not affect any user code. +

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 50178 - HeaderManager added as child of Thread Group can create concatenated HeaderManager names and OutOfMemoryException
  • +
  • 50392 - value is trimmed when sending the request in Multipart
  • +
  • 50686 - HeaderManager logging too verbose when merging instances
  • +
  • 50963 - AjpSampler throws java.lang.StringIndexOutOfBoundsException
  • +
  • 50516 - "Host" header in HTTP Header Manager is not included in generated HTTP request
  • +
  • 50544 - In Apache Common Log the HEAD requests cause problems.
  • +
  • 51268 - HTTPS request through an invalid proxy causes NullPointerException and does not show in result tree. +Rather than delegating to the JMeter thread handler for "unexpected" failures, ensure all Exceptions generate a sample error. +
  • +
  • 51275 - Cookie Panel clearGui() sets incorrect default policy in Java 1.6
  • +
+ +

Other Samplers

+
    +
  • 50173 - JDBCSampler discards ResultSet from a PreparedStatement
  • +
  • Ensure JSR223 Sampler has access to the current SampleResult
  • +
  • 50977 - Unable to set TCP Sampler for individual samples
  • +
+ +

Controllers

+
    +
  • 50032 - Last_Sample_Ok along with other controllers doesnt work correctly when the threadgroup has multiple loops
  • +
  • 50080 - Transaction controller incorrectly creates samples including timer duration
  • +
  • 50134 - TransactionController : Reports bad response time when it contains other TransactionControllers
  • +
+ +

Listeners

+
    +
  • 50367 - Clear / Clear all in View results tree does not clear selected element
  • +
+ +

Assertions

+
    +
  • 51488 - Assertion: Variable name scope is shared among all assertions (and 51255)
  • +
+ +

Functions

+
    +
  • 50568 - Function __FileToString(): Could not read file when encoding option is blank/empty
  • +
+ +

I18N

+
    +
  • 50811 - Incomplete Spanish translation
  • +
+ +

General

+
    +
  • 49734 - Null pointer exception on stop Threads command (Run>Stop)
  • +
  • 49666 - CSV Header read as data after EOF
  • +
  • 45703 - Synchronizing Timer
  • +
  • 50088 - fix getAvgPageBytes in SamplingStatCalculator so it returns what it should
  • +
  • 50203 Cannot set property "jmeter.save.saveservice.default_delimiter=\t"
  • +
  • mirror-server.sh - fix classpath to use : separator (not ;)
  • +
  • 50286 - URL Re-writing Modifier: extracted jsessionid value is incorrect when is between XML tags
  • +
  • +System.nanoTime() tends to drift relative to System.currentTimeMillis(). +Change SampleResult to recalculate offset each time. +Also enable reversion to using System.currentTimeMillis() only. +
  • +
  • 50425 - Remove thread groups from Controller add menu
  • +
  • +50675 - CVS Data Set Config incompatible with Remote Start +Fixed RMI startup to provide location of JMX file relative to user.dir. +
  • +
  • 50221 - Renaming elements in the tree does not resize label
  • +
  • 51002 - Stop Thread if CSV file is not available. JMeter now treats IOError as EOF.
  • +
  • Define sun.net.http.allowRestrictedHeaders=true by default. This fixes 51238.
  • +
  • 51645 - CSVDataSet does not read UTF-8 files when file.encoding is UTF-8
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • AJP Sampler now implements Interruptible
  • +
  • Allow HTTP implementation to be selected at run-time
  • +
  • 50684 - Optionally disable Content-Type and Transfer-Encoding in Multipart POST
  • +
  • 50943 - Allowing concurrent downloads of embedded resources in html page
  • +
  • 50170 - Bytes reported by http sampler is after GUnZip

    Add optional properties to allow change the method to get response size
  • +
  • Hiding the proxy password on HTTP Sampler (just on GUI, not in JMX file)
  • +
+ +

Other samplers

+
    +
  • 49622 - Allow sending messages without a subject (SMTP Sampler)
  • +
  • 49603 - Allow accepting expired certificates on Mail Reader Sampler
  • +
  • 49775 - Allow sending messages without a body
  • +
  • 49862 - Improve SMTPSampler Request output.
  • +
  • 50268 - Adds static and dynamic destinations to JMS Publisher
  • +
  • JMS Subscriber - Add dynamic destination
  • +
  • 50666 - JMSSubscriber: support for durable subscriptions
  • +
  • 50937 - TCP Sampler does not provide for / honor connect timeout
  • +
  • 50569 - Jdbc Request Sampler to optionally store result set object data
  • +
  • 51011 - Mail Reader: upon authentication failure, tell what you tried
  • +
+ +

Controllers

+
    +
  • 50475 - Introduction of a Test Fragment Test Element for a better Include flow
  • +
+ +

Listeners

+
    +
  • View Results Tree - Add a dialog's text box on "Sampler result tab > Parsed" to display the long value with a double click on cell
  • +
  • 37156 - Formatted view of Request in Results Tree
  • +
  • 49365 - Allow result set to be written to file in a path relative to the loaded script
  • +
  • 50579 - Error count is long, sample count is int. Changed sample count to long.
  • +
  • View Results Tree - Add new size fields: response headers and response body (in bytes) - derived from 43363
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 48015 - Proposal new icons for pre-processor, post-processor and assertion elements
  • +
  • 50962 - SizeAssertionGui validation prevents the use of variables for the size
  • +
  • Size Assertion - Add response size scope (full, headers, body, code, message) - derived from 43363
  • +
+ +

Functions

+
    +
  • 49975 - New function returning the name of the current sampler
  • +
+ +

I18N

+
    +
  • Add French translation for the new labels and reduce size for some labels (by abbreviation) on HTTP Sample
  • +
+ +

General

+
    +
  • 30563 - Thread Group should have a start next loop option on Sample Error
  • +
  • 50347 - Eclipse setup instructions should remind user to download dependent jars
  • +
  • 50490 - Setup and Post Thread Group enhancements for better test flow.
  • +
  • All BeansShell test elements now have the script variables "prev" and "Label" defined.
  • +
  • 50708 - Classpath jar order in NewDriver not alphabetically
  • +
  • 50659 - JMeter server does not support concurrent tests - prevent client from starting another
  • +
  • Added remote shutdown functionality
  • +
  • Client JMeter engine now supports external stop/shutdown via UDP
  • +
  • UDP shutdown can now use a range of ports, from jmeterengine.nongui.port=4445 to jmeterengine.nongui.maxport=4455, +allowing multiple JMeter instances on the same host without needing to change the port property.
  • +
  • Updated to httpcore 4.1.3 and httpclient 4.1.2
  • +
+ +

Non-functional changes

+
    +
  • 50008 - Allow BatchSampleSender to be subclassed
  • +
  • 50450 - use System.array copy in jacobi solver as, being native, is more performant.
  • +
  • 50487 - runSerialTest verifies objects that never need persisting
  • +
  • Use Thread.setDefaultUncaughtExceptionHandler() instead of private ThreadGroup
  • +
  • Update to Commons Net 3.0
  • +
+ + + +

Version 2.4

+ +

Summary of main changes

+ +

+

    +
  • JMeter now requires at least Java 1.5.
  • +
  • HTTP Proxy can now record HTTPS sessions.
  • +
  • JUnit sampler now supports JUnit4 annotations.
  • +
  • Added JSR223 (javax.script) test elements.
  • +
  • MailReader Sampler can now use any protocol supported by the underlying implementation.
  • +
  • An SMTP Sampler has been added.
  • +
  • JMeter now allows users to provide their own Thread Group implementations.
  • +
  • View Results Tree now supports more display options, including search and Regex Testing.
  • +
  • StatCalculator performance is much improved; Aggregate Report etc. need far less memory.
  • +
  • +JMS samplers have been extensively reworked, and should no longer lose messages. +Correlation processing is improved. +JMS Publisher and Subscriber now support both Topics and Queues. +
  • +
  • Many other improvements have been made, please see below and in the manual.
  • +
+

+ + + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+ +

+HTTP Redirect now defaults to "Follow Redirects" rather than "Redirect Automatically". +This is to enable JMeter to track cookies that may be sent during redirects. +This does not affect existing test plans; it only affects the default for new HTTP Samplers. +

+ +

+The Avalon file format for JMX and JTL files is no longer supported. +Any such files will need to be converted by reading them in JMeter 2.3.4 and resaving them. +

+ +

+The XPath Assertion and XPath Extractor elements no longer fetch external DTDs by default; this can be changed in the GUI. +

+ +

+JMSConfigGui has been renamed as JMSSamplerGui. +This does not affect existing test plans. +

+ +

+The constructor public SampleResult(SampleResult res) has been changed to become a true "copy constructor". +It no longer calls addSubResult(). This may possibly affect some 3rd party add-ons. +

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 47445 - Using Proxy with https-spoofing secure cookies need to be unsecured
  • +
  • 47442 - Missing replacement of https by http for certain conditions using https-spoofing
  • +
  • 48451 - Error in: SoapSampler.setPostHeaders(PostMethod post) in the else branch
  • +
  • 48542 - SoapSampler uses wrong response header field to decide if response is gzip encoded
  • +
  • 48568 - CookieManager broken for AjpSampler
  • +
  • 48570 - AjpSampler doesn't support query parameters (GET/POST)
  • +
  • 46901 - HTTP Sampler does not process var/func refs correctly in first file parameter
  • +
  • 43678 - Handle META tag http-equiv charset?
  • +
  • 49294 - Images not downloaded from redirected-to pages
  • +
  • 49560 - wrong "size in bytes" when following redirections
  • +
+ +

Other Samplers

+
    +
  • 47420 - LDAP extended request not closing connections during add request
  • +
  • 48573 - LDAPExtSampler directory context handling
  • +
  • 47870 - JMSSubscriber fails due to NPE
  • +
  • 47899 - NullPointerExceptions in JMS ReceiveSubscriber constructor
  • +
  • 48144 - NPE in JMS OnMessageSubscriber
  • +
  • 47992 - JMS Point-to-Point Request - Response option doesn't work
  • +
  • 48579 - Single Bind does not show config information when LdapExt Sampler is accessed
  • +
  • 49111 - "Message With ID Not Found" Error on JMS P2P sampler.
  • +
  • 47949 - JMS Subscriber never receives all the messages
  • +
  • 46142 - JMS Point-to-Point correlation problems
  • +
  • 48747 - TCP Sampler swallows exceptions
  • +
  • 48709 - TCP Sampler Config setting "classname" has no effect
  • +
+ +

Controllers

+
    +
  • 47385 - TransactionController should set AllThreads and GroupThreads
  • +
  • 47940 - Module controller incorrectly creates the replacement Sub Tree
  • +
  • 47592 - Run Thread groups consecutively with "Stop test" on error, JMeter will not mark to finished
  • +
  • 48786 - Run Thread groups consecutively: with "Stop test now" on error or manual stop, JMeter leaves the green box active
  • +
  • 48727 - Cannot stop test if all thread groups are disabled
  • +
+ +

Listeners

+
    +
  • 48603 - Mailer Visualiser sends two emails for a single failed response
  • +
  • Correct calculation of min/max/std.dev for aggregated samples (Summary Report)
  • +
  • 48889 - Wrong response time with mode=Statistical and num_sample_threshold > 1
  • +
  • 47398 - SampleEvents are sent twice over RMI in distributed testing and non gui mode
  • +
+ +

Assertions

+
    +
+ +

Functions

+
    +
+ +

I18N

+
    +
+ +

General

+
    +
  • 47646 - NullPointerException in the "Random Variable" element
  • +
  • Disallow adding any child elements to JDBC Configuration
  • +
  • BeanInfoSupport now caches getBeanDescriptor() - should avoid an NPE on non-Sun JVMs when using CSVDataSet (and some other TestBeans)
  • +
  • 48350 - Deadlock on distributed testing with 2 clients
  • +
  • 48901 - Endless wait by adding Synchronizing Timer
  • +
  • 49149 - usermanual/index.html has typo in link to "Regular Expressions" page
  • +
  • 49394 - Classcast Exception in ActionRouter.postActionPerformed
  • +
  • 48136 - Essential files missing from source tarball.
    +Source archives now contain all source files, including source files previously only provided in the binary archives. +
  • +
  • 48331 - XpathExtractor does not return XML string representations for a Nodeset
  • +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • 47622 - enable recording of HTTPS sessions
  • +
  • Allow Proxy Server to be specified on HTTP Sampler GUI and HTTP Config GUI
  • +
  • 47461 - Update Cache Manager to handle Expires HTTP header
  • +
  • 48153 - Support for Cache-Control and Expires headers
  • +
  • 47946 - Proxy should enable Grouping inside a Transaction Controller
  • +
  • 48300 - Allow override of IP source address for HTTP HttpClient requests
  • +
  • 49083 - collapse '/pathsegment/..' in redirect URLs
  • +
+ +

Other samplers

+
    +
  • JUnit sampler now supports JUnit4 tests (using annotations)
  • +
  • 47900 - Allow JMS SubscriberSampler to be interrupted
  • +
  • Added JSR223 Sampler
  • +
  • 47556 - JMS-PointToPoint-Sampler Timeout field should use Strings
  • +
  • 47947 - Mail Reader Sampler should allow port to be overridden
  • +
  • 48155 - Multiple problems / enhancements with JMS protocol classes
  • +
  • Allow MailReader sampler to use arbitrary protocols
  • +
  • 45053 - SMTP-Sampler for JMeter
  • +
  • 49552 - Add Message Headers on SMTPSampler
  • +
  • +JMS Publisher and Subscriber now support both Topics and Queues. +Added read Timeout to JMS Subscriber. +General clean-up of JMS code. +
  • +
+ +

Controllers

+
    +
  • 47909 - TransactionController should sum the latency
  • +
  • 41418 - Exclude timer duration from Transaction Controller runtime in report
  • +
  • 48749 - Allowing custom Thread Groups
  • +
  • 43389 - Allow Include files to be found relative to the current JMX file
  • +
+ +

Listeners

+
    +
  • Added DataStrippingSample sender - supports "Stripped" and "StrippedBatch" modes.
  • +
  • Added Comparison Assertion Visualizer
  • +
  • 47907 - Improvements (enhancements and I18N) Comparison Assertion and Comparison Visualizer
  • +
  • 36726 - add search function to Tree View Listener
  • +
  • 47869 - Ability to cleanup fields of SampleResult
  • +
  • 47952 - Added JSR223 Listener
  • +
  • 47474 - View Results Tree support for plugin renderers
  • +
  • Allow Idle Time to be saved to sample log files
  • +
  • 48259 - Improve StatCalculator performance by using TreeMap
  • +
  • Listeners using SamplingStatCalculator have much reduced memory needs +as the Sample cache has been moved to the new CachingStatCalculator class. +In particular, Aggregate Report can now handle large numbers of samples. +
  • +
  • Aggregate Report and Summary Report now allow column headers to be optionally excluded
  • +
  • 49506 - Add .csv File Extension in open dialog box from "read from file" functionality of listeners
  • +
  • 49545 - Formatted (parsed) view of Sample Result in Results Tree
  • +
+ +

Timers, Assertions, Config, Pre- & Post-Processors

+
    +
  • 47338 - XPath Extractor forces retrieval of document DTD
  • +
  • Added Comparison Assertion
  • +
  • 47952 - Added JSR223 PreProcessor and PostProcessor
  • +
  • Added JSR223 Assertion
  • +
  • Added BSF Timer and JSR223 Timer
  • +
  • 48511 - add parent,child,all selection to regex extractor
  • +
  • Add Sampler scope selection to XPathExtractor
  • +
  • Regular Expression Extractor, Response Assertion and Size Assertion can now be applied to a JMeter variable
  • +
  • 46790 - CSV Data Set Config should be able to parse CSV headers
  • +
+ +

Functions

+
    +
  • 47565 - [Function] FileToString
  • +
+ +

I18N

+
    +
  • 47938 - Adding some French translations for new elements
  • +
  • 48714 - add new French messages
  • +
+ +

General

+
    +
  • 47223 - Slow Aggregate Report Performance (StatCalculator)
  • +
  • 47980 - hostname resolves to 127.0.0.1 - specifiying IP not possible
  • +
  • 47943 - DisabledComponentRemover is not used in Start class
  • +
  • HeapDumper class for runtime generation of dumps
  • +
  • Basic read-only JavaMail provider implementation for reading raw mail files
  • +
  • 49540 - Sort "Add" menus alphabetically
  • +
+ +

Non-functional changes

+
    +
  • Beanshell, JavaMail and JMS API (Apache Geronimo) jars are now included in the binary archive.
  • +
  • Add TestBean Table Editor support
  • +
  • Removed all external libraries from SVN; added download_jars Ant target
  • +
  • Updated various jar files: +
      +
    • BeanShell - 2.0b4 => 2.0b5
    • +
    • Commons Codec - 1.3 => 1.4
    • +
    • Commons-Collections - 3.2 => 3.2.1
    • +
    • JTidy => r938
    • +
    • JUnit - 3.8.2 => 4.8.1
    • +
    • Logkit - 1.2 => 2.0
    • +
    • Xalan Serializer = 2.7.1 (previously erroneously shown as 2.9.1)
    • +
    • Xerces xml-apis = 1.3.04 (previously erroneously shown as 2.9.1)
    • +
    • Some jar files were renamed.
    • +
    +
  • +
+ + + +

Version 2.3.4

+ +

Summary of main changes

+ +

+This is a minor bug-fix release, mainly to correct some bugs that were accidentally added in 2.3.3. +

+ + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 47321 - HTTPSampler2 response timeout not honored
  • +
+ +

Other Samplers

+
    +
  • 47290 - Infinite loop on connection factory lookup (JMS)
  • +
  • JDBC Sampler should not close Prepared or Callable statements as these are cached
  • +
+ +

Controllers

+
    +
  • 39509 - Once-only controller running twice
  • +
+ +

Listeners

+
    +
  • Change ResultCollector to only warn if the directory was not created
  • +
  • Fix some synchronisation issues in ResultCollector and SampleResult (wrong locks were being used)
  • +
+ +

I18N

+
    +
  • Fixed bug introduced in 2.3.3: JMeter does not start up if there is no messages.properties file for the default Locale.
  • +
+ +

General

+
    +
  • Fix problems with remote clients - bug introduced in 2.3.3
  • +
  • 47377 - Make ClassFinder more robust and close zipfile resources
  • +
  • Fix some errors in generating the documentation (latent bug revealed in 2.3.3 when Velocity was upgraded)
  • +
+ +

Improvements

+ +

Other samplers

+
    +
  • 47266 - FTP Request Sampler: allow specifying an FTP port, other than the default
  • +
+ + + +

Version 2.3.3

+ +

Summary of main changes

+ +

+The handling of test closedown is much improved. +The gradual "Shutdown" command now waits until all threads have stopped, +and does not report an error if threads don't stop within 5 seconds. +The immediate "Stop" command can now be used if "Shutdown" takes too long. +Also the immediate "Stop" command is able to interrupt samplers which support the new Interruptible interface (e.g. HTTP and SOAP, FTP). +This allows immediate completion of pending responses. +Non-GUI mode tests can also now be sent a "Shutdown" or "Stop" message. + now supports a "Stop Now" action, +as do the and Post Processor elements. +

+ +

+HTTP Cookie handling is improved, and HTTP POST can now use variable file names correctly. +HTTP, SOAP/XML-RPC and WebService(SOAP) sampler character encodings updated to be more consistent. +HTTP Samplers now support connection and response timeouts (requires JVM 1.5 for the HTTP Java sampler). +Together with the closedown improvements described above, this should avoid most cases where a test run hangs. +Multiple Header Manager elements are now supported for a single HTTP sampler. +The Proxy Server is improved, and no longer stores "Host" headers by default. +

+ +

+JDBC Request can optionally save the results of Select statements to variables. +JDBC Request now handles quoted strings and UTF-8, and can handle arbitrary variable types. +

+ +

+There are several new functions: +__char() function: allows arbitrary Unicode characters to be entered in fields. +__unescape() function: allows Java-escaped strings to be used. +_unescapeHtml() function: decodes Html-encoded text. +__escapeHtml() function: encodes text using Html-encoding. +A reference to a missing function - e.g. ${__missing(a)} - is now treated the same as a missing variable. +Previously the function name - and leading { - were dropped. This makes it easier to debug test plans. +

+ +

+Some Assertions can now be applied to sub-samples as well as (or instead of) just the parent sample. +There is a new Configuration element. +

+ +

+JMS samplers are much improved (see details below). The now supports some additional clients and is a bit more flexible. +

+ +

+Client-server mode has been improved, and the server can optionally use a fixed RMI port, which should help with setting up firewalls. +

+ +

+Various I18N changes have been made; language change works better (though not perfect yet). +There are improved French translations as well as new Polish and Brazilian Portugese translations. +

+ +

+The BeanShell jar is now included with the binary archive; there is no need to download it separately. +

+ + + +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves correctly under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +[The behaviour has improved, but language change is still not fully working] +To override the default local language fully, set the JMeter property "language" before starting JMeter. +

+ +

Incompatible changes

+

+When loading sample results from a file, previous results are no longer cleared. +This allows one to merge multiple files. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. +

+

+The test elements "Save Results to a file" and "Generate Summary Results" are now shown as Listeners. +They were previously shown as Post-Processors, even though they are implemented as Listeners. +

+

+The Cookie Manager no longer saves incoming cookies as variables by default. +To save cookies as variables, define the property "CookieManager.save.cookies=true". +Also, cookies names are prefixed with "COOKIE_" before they are stored (this avoids accidental corruption of local variables) +To revert to the original behaviour, define the property "CookieManager.name.prefix= " (one or more spaces). +

+

+The Counter element is now shown as a Configuration element. +It was previously shown as a Pre-Processor, even though it is implemented as a Config item. +

+

+The above changes only affect the icons that are displayed and the locations in the GUI pop-up menus. +They do not affect test plans or test behaviour. +

+

+The PreProcessors are now invoked directly by the JMeterThread class, +rather than by the TestCompiler#configureSampler() method. (JMeterThread handles the PostProcessors). +This does not affect test plans or behaviour, but could perhaps affect 3rd party add-ons (very unlikely). +

+

+Moved the Scoping Rules sub-section from Section 3. "Building a Test Plan" to Section 4. "Elements of a test plan" +

+ +

+The While controller now trims leading and trailing spaces from the condition value before it is compared +with LAST, blank or false. +

+ +

+The "threadName" variable in the _jexl() and __javaScript() functions was previously misspelt as "theadName". +

+ +

+The following deprecated methods were removed from JOrphanUtils: booleanToString(boolean) and valueOf(boolean). +Java 1.4+ has these methods in the Boolean class. +

+ +

+The TestElement interface has some new methods: +

    +
  • void setProperty(String key, String value, String dflt)
  • +
  • void setProperty(String key, boolean value, boolean dflt)
  • +
  • void setProperty(String key, int value)
  • +
  • void setProperty(String key, int value, int dflt)
  • +
  • int getPropertyAsInt(String key, int defaultValue)
  • +
+These are implemented in the AbstractTestElement class which all elements should extend so this is unlikely to cause a problem. +

+

Bug fixes

+ +

HTTP Samplers and Proxy

+
    +
  • 46332 - HTTP Cookie Manager ignores manually defined cookies (bug introduced in r707810)
  • +
  • Cookie Manager was not passing cookie policy to runtime threads so they always used compatibility mode
  • +
  • Add version attribute to JMeter Cookie class (needed for proper cookie support)
  • +
  • Cookie Manager now saves/restores cookie versions
  • +
  • Check validity of cookies before storing them.
  • + +
  • HTTPSamplers can now use variables in POSTed file names
  • +
  • Fix processing of first file name in HTTP POST so functions/variables work (bug introduced with multiple file support)
  • +
  • 45831 - WS Sampler reports incorrect throughput if SOAP packet creation fails
  • +
  • HTTP, SOAP/XML-RPC and WebService(SOAP) sampler character encodings updated to be more consistent
  • + +
  • 46148 - HTTP sampler fails on SSL requests when logging for jmeter.util is set to DEBUG
  • +
  • Fix Java 1.6 https error: java.net.SocketException: Unconnected sockets not implemented
  • + +
  • 46838 - if there was no data, still need to set latency in HTTPSampler
  • +
  • 46993 - Saving from Header Manager generates ClassCastException
  • +
  • +46690 - handling of 302 redirects with invalid relative paths. +JMeter now removes extraneous leading "../" segments (as do many browsers) +
  • +
  • 44521 - empty variables for a POST in the HTTP Request don't get ignored
  • +
  • 46977 - JMeter does not handle HTTP headers not delimited by whitespace
  • +
  • Fix bug in HTTP file: handling - read bytes, not characters in the default encoding.
  • + +
  • Remove Host from headers saved by the Proxy server, as that will normally be generated by the HTTP stack
  • +
  • 45199 - don't try to replace blank variables in Proxy recording
  • +
  • Change HTTPS spoofing so https: links are replaced even when URL match fails
  • +
  • 46436 - Improve error reporting in Proxy Gui
  • +
  • 46435 - More verbose error msg for error 501 (Proxy Server)
  • +
+ +

Other Samplers

+
    +
  • The "prev" and "sampler" objects are now defined for BSF test elements
  • +
  • Fix NPE (in DataSourceElement) when using JDBC in client-server mode
  • +
  • 45425 - JDBC Request does not support Unicode (changed sampler to use UTF-8)
  • +
  • 46522 - Incorrect "Response data" in JDBC sample when column names are missing
  • +
  • 46821 - JDBC select request doesn't store the first column in the variables
  • +
  • 43791 - ensure QueueReceiver is closed in JMS Point to Point sampler
  • +
  • 46016 - avoid possible NPE in JMSSampler
  • +
  • 46142 - JMS Receiver now uses MessageID
  • +
  • 45458 - Point to Point JMS in combination with authentication
  • +
  • 45460 - JMS TestPlan elements depend on resource property
  • +
  • Various ReceiveSubscriber thread-safety fixes
  • +
  • JMSPublisher and Subscriber fixes: thread-safety, support dynamic locale changes, locale independence for JMX attribute values
  • +
  • FTP Sampler now logs out before disconnecting.
  • +
  • TCP sampler now calls setupTest() and teardownTest() methods
  • +
  • 45887 - TCPSampler: timeout property incorrectly set
  • +
+ +

Controllers

+
    +
  • Fix NPE when using nested Transaction Controllers with parent samples
  • +
  • Fix processing of Transaction Controller parent mode so current sampler is set to actual sampler
  • +
  • 44941 - Throughput controllers should not share global counters
  • +
  • 47120 - Throughput Controller: change percent executions to total executions, the value is stored in a String and interpreted as 1 execution
  • +
  • 47150 - ThreadGroup with a loop count of zero causes infinite loop
  • +
  • 47009 - Insert parent caused child controller name to be reset
  • +
  • 47165 - Using duplicate Module Controller names in command line mode causes NPE
  • +
+ +

Listeners

+
    +
  • Mailer Visualizer documentation now agrees with code i.e. failure/success counts need to be exceeded to trigger the mail.
  • +
  • Mailer Visualizer now shows the failure count
  • +
  • Mailer Visualiser - fix parsing of multiple e-mail address when using Test button
  • +
  • 45976 - incomplete result file when using remote testing with more than 1 server
  • +
  • Fix Summariser so it works in client server mode
  • +
  • 34096 - Duplicate samples not eliminated when writing to CSV files
  • +
  • Save "Include group Name in Label" setting in Aggregate and Summary reports
  • +
  • The JMeter variable "sample_variables" is sent to all server instances to ensure the data is available to the client.
  • +
  • CSVSaveService - check for EOF while reading quoted string
  • +
+ +

Assertions

+
    +
  • 45749 - Response Assertion does not work with a substring that happens to be an invalid RE
  • +
  • 45904 - Allow 'Not' Response Assertion to succeed with null sample
  • +
+ +

Functions

+
    +
  • Fix regex function - was failing to process $m$mid$n$ correctly
  • +
  • Protect against possible NPE in RegexFunction if called during test shutdown.
  • +
  • Avoid NPE if XPath function does not match any nodes
  • +
  • Correct the variable name "theadName" to "threadName" in the __jexl() and __javaScript() functions
  • +
  • A reference to a missing function - e.g. ${__missing(a)} - is now treated the same as a missing variable. Previously the function name - and leading { - were dropped.
  • +
+ +

I18N

+
    +
  • Fixed language change handling for menus (does not yet work for TestBeans)
  • +
  • Add HeaderAsPropertyRenderer to support header resource names; use this to fix locale changes in various GUI elements
  • +
  • 46424 - corrections to French translation
  • +
  • 46844 - "Library" label in test plan are not I18N
  • +
  • 47064 - fixes for Mac LAF
  • +
  • 47127 - Unable to change language to pl_PL
  • +
  • 47137 - Labels in View Results Tree aren't I18N
  • +
  • 46423 - I18N of Proxy Recorder
  • +
  • 45928 - AJP/1.3 Sampler doesn't retrieve its label from messages.properties
  • +
+ +

General

+
    +
  • Prompt to overwrite an existing file when first saving a new test plan
  • +
  • Amend TestBeans to show the correct popup menu for Listeners
  • +
  • 45185 - CSV dataset blank delimiter causes OOM
  • +
  • Fix incorrect GUI classifications: +"Save Results to a file" and "Generate Summary Results" are now shown as Listeners. +"Counter" is now shown as a Configuration element. +
  • +
  • 41608 - misleading warning log message removed
  • +
  • 46359 - BSF JavaScript Preprocessor cannot access sampler variable on first interation (Implement temporary work-round for BSF-22)
  • +
  • 46407 - BSF elements do not load script files, attempt to interpret filename as script
  • +
  • Better handling of Exceptions during test shutdown
  • +
  • Fix potential thread safety issue in JMeterThread class
  • +
  • 46491 - Incorrect value for the last variable in "CSV Data Set Config" (error in processing quoted strings)
  • + +
+ + + +

Improvements

+ +

HTTP Samplers

+
    +
  • 45479 - Support for multiple HTTP Header Manager nodes
  • +
  • HTTP Samplers now support connection and request timeouts (requires Java 1.5 for Java Http sampler)
  • +
  • Apache SOAP 2.3.1 does not give access to HTTP response code/message, so WebService sampler now treats an empty response as an error
  • +
  • Mirror server now supports "X-Sleep" header - if this is set, the responding thread will wait for the specified number of milliseconds
  • +
  • 45694 - Support GZIP compressed logs in Access Log Sampler
  • +
+ +

Other samplers

+
    +
  • JDBC Request can optionally save the results of Select statements to variables.
  • +
  • JDBC Request now handles quoted strings.
  • +
  • JDBC Request now handles arbitrary variable types.
  • +
  • LDAP result data now formatted with line breaks
  • +
  • 45200 - MailReaderSampler: store the whole MIME message in the SamplerResult
  • +
  • 45571 - JMS Sampler correlation enhancement
  • +
  • 46030 - Extend TCP Sampler to Support Length-Prefixed Binary Data
  • +
  • Add classname field to TCP Sampler GUIs
  • +
+ +

Controllers

+
    +
  • Allow If Controller to use variable expressions (not just Javascript)
  • +
  • Trim spaces from While Controller condition before comparing against LAST, blank or false
  • +
+ +

Listeners

+
    +
  • Save Responses to a file can save the generated filename(s) to variables.
  • +
  • Add option to skip suffix generation in Save Responses to a File
  • +
  • 43119 - Save Responses to file: optionally omit the file number
  • +
  • Add BSF Listener element
  • +
  • 47176 - Monitor Results : improve load status graphic
  • +
  • 40045 - Allow Results monitor to select a specific connector
  • +
  • Read XML JTL files more efficiently - pass samples to visualisers as they are read, rather than saving them all and then processing them
  • +
+ +

Assertions, Config, Pre- & Post-Processors

+
    +
  • 45903 - allow Assertions to apply to sub-samples
  • +
  • Add Body (unescaped) source option to Regular Expression Extractor.
  • +
  • Random Variable - new configuration element to create random numeric variables
  • +
+ +

Functions

+
    +
  • Add OUT and log variables to __jexl() function
  • +
  • Use Script to evaluate __jexl() function so can have multiple statements.
  • +
  • Add log variable to the __javaScript() function
  • +
  • Added __char() function: allows arbitrary Unicode characters to be entered in fields.
  • +
  • Added __unescape() function: allows Java-escaped strings to be used.
  • +
  • Added __unescapeHtml() function: decodes Html-encoded text.
  • +
  • Added __escapeHtml() function: encodes text using Html-encoding.
  • +
+ +

I18N

+
    +
  • 45929 - improved French translations
  • +
  • 47132 - Brazilian Portuguese translations
  • +
  • 46900 - Polish translations
  • +
  • Added locales.add property to allow for new Locales
  • +
+ +

General

+
    +
  • Allow spaces in JMeter path names (apply work-round for Java Bug 4496398)
  • +
  • Process JVM_ARGS last in script files so users can override default settings
  • +
  • 46636 - Allow server mode to optionally use a fixed rmi port
  • +
  • Make some samplers interruptible: HTTP (both), SoapSampler, FTPSampler
  • +
  • Test Action now supports "Stop Now" action, as do the Thread Group and Result Status Post Processor elements
  • +
  • The Menu items Stop and Shutdown now behave better. Shutdown will now wait until all threads exit. +In GUI mode it can be cancelled and Stop run instead. +Stop now reports if some threads will not exit, and exits if running in non-GUI mode
  • +
  • Add UDP server to wait for shutdown message if running in non-GUI mode; add UDP client to send the message.
  • +
  • 41209 - JLabeled* and ToolTips
  • +
  • Include BeanShell 2.0b4 jar in binary download.
  • +
+ +

Non-functional changes

+
    +
  • Introduce AbstractListenerGui class to make it easier to create Listeners with no visual output
  • +
  • Assertions are run after PostProcessors; change order of pop-up menus accordingly
  • +
  • Remove unnecessary clone() methods from function classes
  • +
  • Moved PreProcessor invocation to JMeterThread class
  • +
  • Made HashTree Map field final
  • +
  • Improve performance of calling ResultCollector#isSampleWanted() for multiple samples
  • +
  • Updated to new versions of: xmlgraphics-commons (1.3.1), jdom (1.1), xstream (1.3.1), velocity (1.6.2)
  • +
+ + + + +

Version 2.3.2

+ +

Summary of main changes

+ +

Bug fixes

+

+Version 2.3.1 changed the way binary and text content types were determined as far as the View Results Tree Listener was concerned: +originally everything except "image/" content types were considered text, but 2.3.1 introduced a check +for specific content types. This has caused problems, +as several popular types were omitted and these were no longer shown by default in the Response tab. +Rather than try to list all the possible text types, JMeter now just checks for the following binary types: +

    +
  • image/*
  • +
  • audio/*
  • +
  • video/*
  • +
+All other types are now assumed to be text. +

+ +

+JMeter 2.3.1 introduced a bug in the Cookie Manager +- if "Clear Cookie each iteration" was selected, all threads would see the same cookies. +This bug has been corrected. +

+ +

Improvements

+ +

+The Proxy server can now record binary requests. +By default the content types +application/x-amf and application/x-java-serialized-object +will be treated as binary and saved in a file. +To change the content types, update the property proxy.binary.types. +

+ +

+The CSV Dataset configuration element has new file sharing options: per thread group, per thread, per identifier. +This allows for more flexible file processing, e.g. each thread can process the same data in the same order. +

+ +

Switch Controller now works properly with functions and variables, +and the condition can now be a name instead of a number. +Simple Controller now works properly under a While Controller

+ +

CSV fields in JTL files can now contain delimiters. +CSV and XML files can now contain additional variables (define the JMeter property sample_variables).

+ +

Response Assertion can now match on substrings (i.e. not regular expression). +Regex extractor can operate on variables.

+ +

+XPath processing is improved; Tidy errors are handled better. +

+ +

Save Table Data buttons added to Summary and Aggregate reports to allow easy saving of the calculated data.

+ +

+HTTP samplers can now save just the MD5 hash of responses, rather than the entire response. +As a special case, if the HTTP Sampler path starts with "http://" or "https://" then this is used as the full URL, +overriding the host and port fields. +The HTTP Samplers can now POST multiple files. +Webservice(SOAP) Sampler can now load local WSDL files using the "file:" protocol. +

+ +

+A simple HTTP Cache Manager has been added. This needs further development. +

+ +

+View Results Tree Listener now uses Tidy to display XML. +This should allow more content to be displayed succesfully. +It also avoids the need to download remote DTD files, which can slow the rendering considerably. +

+ +

+MailReader sampler now supports POP3S and IMAPS protocols. Individual mails are now added as sub-samples. +

+ +

+Various improvements to the BSF Sampler: now supports Jexl, and Javascript bug works properly. +Added BSF PreProcessor, PostProcessor and Assertion test elements. +All now have access to "props" JMeter Properties object. +

+ +

Number of classes loaded in non-GUI mode is much reduced.

+ +

Known bugs

+ +

+The Include Controller has some problems in non-GUI mode. +In particular, it can cause a NullPointerException if there are two include controllers with the same name. +

+ +

Once Only controller behaves OK under a Thread Group or Loop Controller, +but otherwise its behaviour is not consistent (or clearly specified).

+ +

+The menu item Options / Choose Language does not change all the displayed text to the new language. +To override the default local language, set the JMeter property "language" before starting JMeter. +

+

Incompatible changes

+
    +
  • +To reduce the number of classes loaded in non-GUI mode, +Functions will only be found if their classname contains the string +'.functions.' and does not contain the string '.gui.'. +All existing JMeter functions conform to this restriction. +To revert to earlier behaviour, comment or change the properties classfinder.functions.* in jmeter.properties. +
  • +
  • The reference value parameter for intSum() is now optional. +As a consequence, if a variable name is used, it must not be a valid integer.
  • +
  • The supplied TCPClient implementation no longer treats tcp.eolByte=0 as special. +To skip EOL checking, set tcp.eolByte=1000 (or some other value which is not a valid byte) +
  • +
  • +Leading and trailing spaces are trimmed from variable names in function calls. +For example, ${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY '. +
  • +
  • +Synchronization has been removed from the RunningSample class (it was not fully threadsafe anyway). +Developers of 3rd party add-ons that use the class may need to synchronize access. +
  • +
+ +

Bug fixes

+
    +
  • Check that the CSV delimiter is reasonable.
  • +
  • Fix Switch Controller to work properly with functions and variables
  • +
  • 44011 - application/soap+xml not treated as a text type
  • +
  • 43427 - Simple Controller is only partly executed in While loop
  • +
  • 33954 - Stack Overflow in If/While controllers (may have been fixed previously)
  • +
  • 44022 - Memory Leak when closing test plan
  • +
  • 44042 - Regression in Cookie Manager (Bug introduced in 2.3.1)
  • +
  • 41028 - JMeter server doesn't alert the user when the host is defined as a loopback address
  • +
  • 44142 - Function __machineName causes NPE if parameters are omitted.
  • +
  • 44144 - JMS point-to-point: request response test does not work
  • +
  • 44314 - Not possible to add more than one SyncTimer
  • +
  • Capture Tidy console error output and log it
  • +
  • Fix problems using Tidy(tolerant parser) in XPath Assertion and XPath Extractor
  • +
  • 44374 - improve timer calculation
  • +
  • Regular Expression Extractor now deletes all stale variables from previous matches.
  • +
  • 44707 - Running remote test changes internal test plan
  • +
  • 44625 - Cannot have two or more FTP samplers with different "put" and "get" actions
  • +
  • 40850 - BeanShell memory leak
  • +
  • Ensure ResponseCode and ResponseMessage are set for successful JDBC samples
  • +
  • FTPSampler now detects and reports failure to open the remote file
  • +
  • Class directories defined in search_paths and user.classpath no longer need trailing "/"
  • +
  • 44852 SOAP/ XML-RPC Request does not show Request details in View Results Tree
  • +
  • WebService(SOAP) Sampler ResponseData now includes the EOLs sent by server
  • +
  • 44910 - close previous socket (if any) in TCP Sampler
  • +
  • 44912 - Filter not working in Log Parser
  • +
  • The BeanShell and BSF component documentation made some incorrect references to the "SampleResponse" object; +this has been corrected to "SampleResult"
  • +
  • BSF Sampler now works properly with Javascript
  • +
  • Test Action "Stop Test" now works
  • +
  • 42833 - Argument class uses LinkedHashMap in getArgumentsAsMap() to preserve ordering
  • +
  • 45093 - SizeAssertion did not call getBytes()
  • +
  • 45007 - Rewrite Location headers when using Proxy HTTPS spoofing
  • +
  • Use CRLF rather than LF in Proxy when returning headers to the client
  • +
  • 45007 - fix content length header if content may have been changed
  • +
+ +

Improvements

+
    +
  • CSV files can now handle fields with embedded delimiters.
  • +
  • longSum() function added
  • +
  • 43382 - configure Tidy output (warnings, errors) for XPath Assertion and Post-Processor
  • +
  • 43984 - trim spaces from port field
  • +
  • Add optional comment to __log() function
  • +
  • Make Random function variable name optional
  • +
  • Reduce class loading in non-GUI mode by only looking for Functions in class names +that contain '.functions.' and don't contain '.gui.'
  • +
  • 43379 - Switch Controller now supports selection by name as well as number
  • +
  • Can specify list of variable names to be written to JTL files (CSV and XML format)
  • +
  • Now checks that the remoteStart options -r and -R are only used with non_GUI -n option
  • +
  • 44184 - Allow header to be saved with Aggregate Graph data
  • +
  • Added "Save Table Data" buttons to Aggregate and Summary Reports - save table as CSV format with header
  • +
  • Allow most functions to be used on the Test Plan. +Note __evalVar(), __split() and __regex() cannot be used on the Test Plan.
  • +
  • Allow Global properties to be loaded from a file, e.g. -Gglobal.properties
  • +
  • Add "Substring" option to Response Assertion
  • +
  • 44378 - Turkish localisation
  • +
  • Add optional output variable name to Jexl function
  • +
  • Add application/vnd.wap.xhtml+xml as a text type
  • +
  • Add means to override maximum display size in View Results Tree - set the property: view.results.tree.max_size
  • +
  • Use Tidy to display XML in View Results Tree Listener (avoids fetching DTDs)
  • +
  • 44487 - German translation
  • +
  • +As a special case, if the HTTP Sampler path starts with "http://" or "https://" then this is used as the full URL. +
  • +
  • 44575 - Result Saver can now save only successful results
  • +
  • 44650 - CSV Dataset now handles quoted column values
  • +
  • 44600 - 1-ms resolution timer when running with Java 1.5+
  • +
  • 44632 - Text input enhancement to FTP Sampler
  • +
  • 42204 - add thread group name to Aggregate and Summary reports
  • +
  • FTP Sampler sets latency = time to login
  • +
  • FTP Sampler sets a URL if it can
  • +
  • 41921 - add option for samplers to store MD5 of response; done for HTTP Samplers.
  • +
  • Regex Function can now also be applied to a variable rather than just the previous sample result.
  • +
  • Remove HTML Parameter Mask,HTTP User Parameter Modifier from menus as they are deprecated
  • +
  • 44807 - allow session ids to be terminated by backslash
  • +
  • 44784 - allow for broken server returning additional charset
  • +
  • Added TESTSTART.MS property / variable = test start time in milliseconds
  • +
  • Add POP3S and IMAPS protocols to Mail Reader Sampler.
  • +
  • Mail Reader Sampler now creates a sub-sample for each mail.
  • +
  • The supplied TCPClient implementation no longer treats tcp.eolByte=0 as special. +To skip EOL checking, set tcp.eolByte=1000 (or some other value which is not a valid byte) +
  • +
  • JUnit sampler GUI now also finds Test classes defined in user.classpath
  • +
  • +Leading and trailing spaces are trimmed from variable names in function calls. +For example, ${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY ' +
  • +
  • Webservice(SOAP) Sampler can now load local WSDL files using the file: protocol
  • +
  • 44872 - Add "All Files" filter to Open File dialogs
  • +
  • Mirror server can now be run independently (mirror-server.cmd and mirror-server.sh)
  • +
  • 19128 - Added multiple file POST support to HTTP Samplers
  • +
  • Allow use of special name LAST to mean the last test run; applies to -t, -l, -j flags
  • +
  • 44418/42178 - CSV Dataset file handling improvements
  • +
  • Give BeanShell, Javascript and Jexl functions access to JMeter properties via the "props" object
  • +
  • Give BSF Sampler access to JMeter Properties via "props" object
  • +
  • Add Jexl as a supported BSF Sampler language
  • +
  • Give Beanshell test elements access to JMeter Properties via "props" object
  • +
  • Added BSF PreProcessor, PostProcessor and Assertion test elements
  • +
  • All BSF elements now have access to System.out via the variable "OUT"
  • +
  • Summariser updated to handle variable names
  • +
  • Synchronisation added to Summary and Aggregate Report to try to prevent occasional lost samples
  • +
  • 44808,39641 - Proxy support for binary requests
  • +
  • 28502 - HTTP Resource Cache
  • +
+ +

Non-functional changes

+
    +
  • Better handling of MirrorServer startup problems and improved unit test.
  • +
  • Build process now detects missing 3rd party libraries and reports need for both binary and source archives
  • +
  • Skip BeanShell tests if jar is not present
  • +
  • Update to Xerces 2.9.1, Xalan 2.7.1, Commons IO 1.4, Commons Lang 2.4, Commons-Logging 1.1.1, XStream 1.3, XPP3 1.1.4c
  • +
  • Use properties for log/logn function descriptions
  • +
  • Check that all jmx files in the demos directory can be loaded OK
  • +
  • Update copyright to 2008; use copy tag instead of numeric character in HTML output
  • +
  • Methods called from constructors must not be overridable: make GUI init methods private
  • +
  • Make static variables final if possible
  • +
  • Split changes into current and previous
  • +
+ + + +

Version 2.3.1

+

Summary of changes

+ +
JMeter Proxy
+ +

+The Proxy spoof function was broken in 2.3; it has been fixed. +Spoof now supports an optional parameter to limit spoofing to particular URLs. +This is useful for HTTPS pages that have insecure content - e.g. images/stylesheets may be accessed using HTTP. +Spoofed responses now drop the default port (443) from https links to make them work better. +

+

+Ignored proxy samples are now visible in Listeners - the label is enclosed in [ and ] as an indication. +Proxy documentation has been improved. +

+ +
GUI changes
+ +

The Add menus show element types in the order in which they are processed +- see Test Plan Execution Order. +It is no longer possible to add test elements to inappropriate parts of the tree +- e.g. samplers cannot be added directly under a test plan. +This also applies to Paste and drag and drop. +

+ +

+The File menu now supports a "Revert" option, which reloads the current file. +Also the last few file names used are remembered for easy reloading. +

+ +

+The Options Menu now supports Collapse All and Expand All items to collapse and expand the test tree. +

+ +
Remote testing
+ +

+The JMeter server now starts the RMI server directly (by default). +This simplifies testing, and means that the RMI server will be stopped when the server stops. +

+ +

+Functions can now be used in Listener filenames (variables do not work). +

+ +

+Command-line option -G can now be used to define properties for remote servers. +Option -X can be used to stop a remote server after a non-GUI run. +Server can be set to automatically exit after a single test (set property server.exitaftertest=true). +

+ +
Other enhancements
+ +

+JMeter startup no longer loads as many classes; this should reduce memory requirements. +

+ +

+Parameter and file support added to all BeanShell elements. +Javascript function now supports access to JMeter objects; +Jexl function always did have access, but the documentation has now been included. +New functions __eval() and __evalVar() for evaluating variables. +

+ +

+CSV files with the correct header column names are now automatically recognised when loaded. +There is no need to configure the properties. +

+ +

+The hostname can now be saved in CSV and XML output files. +New "Successes only" option added when saving result files. +Errors / Successes only option is now supported when loading XML and CSV files. +

+ +

+General documentation improvements. +

+ +
HTTP
+ +

PUT and DELETE should now work properly. +Cookie Manager no longer clears manually entered cookies. +

+

Now handles the META tag http-equiv charset

+ +
JDBC
+ +

JDBC Sampler now allows INOUT and OUT parameters for Called procedures. +JDBC Sampler now allows per-thread connections - set Max Connections = 0 in JDBC Config. +

+ +
+ + +

Incompatible changes

+
    +
  • JMeter server now creates the RMI registry by default. +If the RMI registry has already been started externally, this will generate a warning message, but the server will continue. +This should not affect JMeter testing. +However, if you are also using the RMI registry for other applications there may be problems. +For example, when the JMeter server shuts down it will stop the RMI registry. +Also user-written command files may need to be adjusted (the ones supplied with JMeter have been updated). +To revert to the earlier behaviour, define the JMeter property: server.rmi.create=false. +
  • +
  • The Proxy server removes If-Modified-Since and If-None-Match headers from generated Header Managers. +To revert to the previous behaviour, define the property proxy.headers.remove with no value
  • +
+ +

Bug fixes

+
    +
  • 43430 - Count of active threads is incorrect for remote samples
  • +
  • Throughput Controller was not working for "all thread" counts
  • +
  • If a POST body is built from parameter values only, these are now encoded if the checkbox is set.
  • +
  • 43584 - Assertion Failure Message contains a comma that is also used as the delimiter for CSV files
  • +
  • HTTP Mirror Server now always returns the exact same content, it used to return incorrect data if UTF-8 encoding was used for HTTP POST body, for example
  • +
  • 43612 - HTTP PUT does not honor request parameters
  • +
  • 43694 - ForEach Controller (empty collection processing error)
  • +
  • 42012 - Variable Listener filenames do not get processed in remote tests. +Filenames can now include function references; variable references do not work.
  • +
  • Ensure Listener nodes get own save configuration when copy-pasted
  • +
  • Correct Proxy Server include and exclude matching description - port and query are included, contrary to previously documented.
  • +
  • Aggregate Graph and Aggregate Report Column Header is KB/Sec; fixed the values to be KB rather than bytes
  • +
  • +Fix SamplingStatCalculator so it no longer adds elapsed time to endTime, as this is handled by SampleResult. +This corrects discrepancies between Summary Report and Aggregate Report throughput calculation. +
  • +
  • Default HTTPSampleResult to ISO-8859-1 encoding
  • +
  • Fix default encoding for blank encoding
  • +
  • Fix Https spoofing (port problem) which was broken in 2.3
  • +
  • Fix HTTP (Java) sampler so http.java.sampler.retries means retries, i.e. does not include initial try
  • +
  • Fix SampleResult dataType checking to better detect TEXT documents
  • +
+ +

Improvements

+
    +
  • Add run_gui Ant target, to package and then start the JMeter GUI from Ant
  • +
  • Add File->Revert to easily drop the current changes and reload the project file currently loaded
  • +
  • 31366 - Remember recently opened file(s)
  • +
  • 43351 - Add support for Parameters and script file to all BeanShell test elements
  • +
  • SaveService no longer needs to instantiate classes
  • +
  • New functions: __eval() and __evalVar()
  • +
  • Menu items now appear in execution order
  • +
  • Test Plan items can now only be dropped/pasted/merged into parts of the tree where they are allowed
  • +
  • Property Display to show the value of System and JMeter properties and allow them to be changed
  • +
  • 43451 - Allow Regex Extractor to operate on Response Code/Message
  • +
  • JDBC Sampler now allows INOUT and OUT parameters for Called procedures
  • +
  • JDBC Sampler now allows per-thread connections
  • +
  • Cookie Manager not longer clears cookies defined in the GUI
  • +
  • HTTP Parameters without names are ignored (except for POST requests with no file)
  • +
  • "Save Selection As" added to main menu; now checks only item is selected
  • +
  • Test Plan now has Paste menu item (paste was already supported via ^V)
  • +
  • If the default delimiter does not work when loading a CSV file, guess the delimiter by analysing the header line.
  • +
  • Add optional "loopback" protocol for HttpClient sampler
  • +
  • HTTP Mirror Server now supports blocking waiting for more data to appear, if content-length header is present in request
  • +
  • HTTP Mirror Server GUI now has the Start and Stop buttons in a more visible place
  • +
  • Server mode now creates the RMI registry; to disable set the JMeter property server.rmi.create=false
  • +
  • HTTP Sampler now supports using MIME Type field to specify content-type request header when body is constructed from parameter values
  • +
  • Enable exit after a single server test - define JMeter property server.exitaftertest=true
  • +
  • Added -G option to set properties in remote servers
  • +
  • Added -X option to stop remote servers after non-GUI run
  • +
  • 43485 - Ability to specify keep-alive on SOAP/XML-RPC request
  • +
  • 43678 - Handle META tag http-equiv charset
  • +
  • 42555 - [I18N] Proposed corrections for the french translation
  • +
  • 43727 - Test Action does not support variables or functions
  • +
  • The Proxy server removes If-Modified-Since and If-None-Match headers from generated Header Managers by default. +To change the list of removed headers, define the property proxy.headers.remove as a comma-separated list of headers to remove
  • +
  • The javaScript function now has access to JMeter variables and context etc. See JavaScript function
  • +
  • Use drop-down list for BSF Sampler language field
  • +
  • Add hostname to items that can be saved in CSV and XML output files.
  • +
  • Errors only flag is now supported when loading XML and CSV files
  • +
  • Ensure ResultCollector uses SaveService encoding
  • +
  • Proxy now rejects attempts to use it with https
  • +
  • Proxy spoofing can now use RE matching to determine which urls to spoof (useful if images are not https)
  • +
  • Proxy spoofing now drops the default HTTPS port (443) when converting https: links to http:
  • +
  • Add Successes Only logging and display
  • +
  • The JMeter log file name is formatted as a SimpleDateFormat (applied to the current date) if it contains paired single-quotes, .e.g. 'jmeter_'yyyyMMddHHmmss'.log'
  • +
  • Added Collapse All and Expand All Option menu items
  • +
  • Allow optional definition of extra content-types that are viewable as text
  • +
+ +

Non-functional Improvements

+
    +
  • Functor code tightened up; Functor can now be used with interfaces, as well as pre-defined targets and parameters.
  • +
  • Save graphics function now prompts before overwriting an existing file
  • +
  • Debug Sampler and Debug PostProcessor added.
  • +
  • Fixed up method names in Calculator and SamplingStatCalculator
  • +
  • Tidied up Listener documentation.
  • +
+ + + +

Version 2.3

+ +

Fixes since 2.3RC4

+ +

Bug fixes

+
    +
  • Fix NPE in SampleResultConverter - XStream PrettyPrintWriter cannot handle nulls
  • +
  • If Java HTTP sampler sees null ResponseMessage, replace with HTTP header
  • +
  • 43332 - 2.3RC4 does not clear Guis based on TestBean
  • +
  • 42948 - Problems with Proxy gui table fields in Java 1.6
  • +
  • Fixup broken jmeter-server script
  • +
  • 43364 - option to revert If Controller to pre 2.3RC3 behaviour
  • +
  • 43449 - Statistical Remote mode does not handle Latency
  • +
  • 43450 (partial fix) - Allow SampleCount and ErrorCount to be saved to/restored from files
  • +
+ +

Improvements

+
    +
  • Add nameSpace option to XPath extractor
  • +
  • Add NULL parameter option to JDBC sampler
  • +
  • Add documentation links for Rhino and BeanShell to functions; clarify variables and properties
  • +
  • Ensure uncaught exceptions are logged
  • +
  • Look for user.properties and system.properties in JMeter bin directory if not found locally
  • +
+ +

Fixes since 2.3RC3

+
    +
  • Fixed NPE in Summariser (bug introduced in 2.3RC3)
  • +
  • Fixed setup of proxy port (bug introduced in 2.3RC3)
  • +
  • Fixed errors when running non-GUI on a headless host (bug introduced in 2.3RC3)
  • +
  • 43054 - SSLManager causes stress tests to saturate and crash (bug introduced in 2.3RC3)
  • +
  • Clarified HTTP Request Defaults usage of the port field
  • +
  • 43006 - NPE if icon.properties file not found
  • +
  • 42918 - Size Assertion now treats an empty response as having zero length
  • +
  • 43007 - Test ends before all threadgroups started
  • +
  • Fix possible NPE in HTTPSampler2 if 302 does not have Location header.
  • +
  • 42919 - Failure Message blank in CSV output [now records first non-blank message]
  • +
  • Add link to Extending JMeter PDF
  • +
  • Allow for quoted charset in Content-Type parsing
  • +
  • 39792 - ClientJMeter synchronisation needed
  • +
  • 43122 - GUI changes not always picked up when short-cut keys used (bug introduced in 2.3RC3)
  • +
  • 42947 - TestBeanGUI changes not picked up when short-cut keys used
  • +
  • Added serializer.jar (needed for update to xalan 2.7.0)
  • +
  • 38687 - Module controller does not work in non-GUI mode
  • +
+ +

Improvements since 2.3RC3

+
    +
  • Add stop thread option to CSV Dataset
  • +
  • Updated commons-httpclient to 3.1
  • +
  • 28715 - allow variable cookie values (set CookieManager.allow_variable_cookies=false to disable)
  • +
  • 40873 - add JMS point-to-point non-persistent delivery option
  • +
  • 43283 - Save action adds .jmx if not present; checks for existing file on Save As
  • +
  • Control+A key does not work for Save All As; changed to Control+Shift+S
  • +
  • 40991 - Allow Assertions to check Headers
  • +
+ +

Version 2.3RC3

+ +

Known problems/restrictions:

+

+The JMeter remote server does not support multiple concurrent tests - each remote test should be run in a separate server. +Otherwise tests may fail with random Exceptions, e.g. ConcurrentModification Exception in StandardJMeterEngine. +See 43168. +

+

+The default HTTP Request (not HTTPClient) sampler may not work for HTTPS connections via a proxy. +This appears to be due to a Java bug, see 39337. +To avoid the problem, try a more recent version of Java, or switch to the HTTPClient version of the HTTP Request sampler. +

+

Transaction Controller parent mode does not support nested Transaction Controllers. +Doing so may cause a Null Pointer Exception in TestCompiler. +

+

Thread active counts are always zero in CSV and XML files when running remote tests. +

+

The property file_format.testlog=2.1 is treated the same as 2.2. +However JMeter does honour the 3 testplan versions.

+

+22510 - JMeter always uses the first entry in the keystore. +

+

+Remote mode does not work if JMeter is installed in a directory where the path name contains spaces. +

+

+BeanShell test elements leak memory. +This can be reduced by using a file instead of including the script in the test element. +

+

+Variables and functions do not work in Listeners in client-server (remote) mode so they cannot be used +to name log files in client-server mode. +

+

+CSV Dataset variables are defined after configuration processing is completed, +so they cannot be used for other configuration items such as JDBC Config. +(see 40394) +

+ +

Summary of changes (for more details, see below)

+

+Some of the main enhancements are: +

+
    +
  • Htmlparser 2.0 now used for parsing
  • +
  • HTTP Authorisation now supports domain and realm
  • +
  • HttpClient options can be specified via httpclient.parameters file
  • +
  • HttpClient now behaves the same as Java Http for SSL certificates
  • +
  • HTTP Mirror Server to allow local testing of HTTP samplers
  • +
  • HTTP Proxy supports XML-RPC recording, and other proxy improvements
  • +
  • __V() function allows support of nested variable references
  • +
  • LDAP Ext sampler optionally parses result sets and supports secure mode
  • +
  • FTP Sampler supports Ascii/Binary mode and upload
  • +
  • Transaction Controller now optionally generates a Sample with subresults
  • +
  • HTTPS session contexts are now per-thread, rather than shared. This gives better emulation of multiple users
  • +
  • BeanShell elements now support ThreadListener and TestListener interfaces
  • +
  • Coloured icons in Tree View Listener and elsewhere to better differentiate failed samples.
  • +
+

+The main bug fixes are: +

+
    +
  • HTTPS (SSL) handling now much improved
  • +
  • Various Remote mode bugs fixed
  • +
  • Control+C and Control+V now work in the test tree
  • +
  • Latency and Encoding now available in CSV log output
  • +
  • Test elements no longer default to previous contents; test elements no longer cleared when changing language.
  • +
+ +

Incompatible changes (usage):

+

+N.B. The javax.net.ssl properties have been moved from jmeter.properties to system.properties, +and will no longer work if defined in jmeter.properties. +

+The new arrangement is more flexible, as it allows arbitrary system properties to be defined. +

+

+SSL session contexts are now created per-thread, rather than being shared. +This generates a more realistic load for HTTPS tests. +The change is likely to slow down tests with many SSL threads. +The original behaviour can be enabled by setting the JMeter property: +

+https.sessioncontext.shared=true
+
+

+

+The LDAP Extended Sampler now uses the same panel for both Thread Bind and Single-Bind tests. +This means that any tests using the Single-bind test will need to be updated to set the username and password. +

+

+41140: JMeterThread behaviour was changed so that PostProcessors are run in forward order +(as they appear in the test plan) rather than reverse order as previously. +The original behaviour can be restored by setting the following JMeter property: +
+jmeterthread.reversePostProcessors=true +

+

+The HTTP Authorisation Manager now has extra columns for domain and realm, +so the temporary work-round of using '\' and '@' in the username to delimit the domain and realm +has been removed. +

+

+Control-Z no longer used for Remote Start All - this now uses Control+Shift+R +

+

+HttpClient now uses pre-emptive authentication. +This can be changed by setting the following: +

+jmeter.properties:
+httpclient.parameters.file=httpclient.parameters
+
+httpclient.parameters:
+http.authentication.preemptive$Boolean=false
+
+

+ +

+The port field in HTTP Request Defaults is no longer ignored for https samplers if it is set to 80. +

+ +

Incompatible changes (development):

+

+N.B.The clear() method was defined in the following interfaces: Clearable, JMeterGUIComponent and TestElement. +The methods serve different purposes, so two of them were renamed: +the Clearable method is now clearData() and the JMeterGUIComponent method is now clearGui(). +3rd party add-ons may need to be rebuilt. +

+

+Calulator and SamplingStatCalculator classes no longer provide any formatting of their data. +Formatting should now be done using the jorphan.gui Renderer classes. +

+

+Removed deprecated method JMeterUtils.split() - use JOrphanUtils version instead. +

+

+Removed method saveUsingJPEGEncoder() from SaveGraphicsService. +It was unused so far, and used the only Sun-specific class in JMeter. +

+ + +

New functionality/improvements:

+
    +
  • Add Domain and Realm support to HTTP Authorisation Manager
  • +
  • HttpClient now behaves the same as the JDK http sampler for invalid certificates etc
  • +
  • Added httpclient.parameters.file to allow HttpClient parameters to be defined
  • +
  • 33964 - Http Requests can send a file as the entire post body if name/type are omitted
  • +
  • 41705 - add content-encoding option to HTTP samplers for POST requests
  • +
  • 40933,40945 - optional RE matching when retrieving embedded resource URLs
  • +
  • 27780 - (patch 19936) create multipart/form-data HTTP request without uploading file
  • +
  • 42098 - Use specified encoding for parameter values in HTTP GET
  • +
  • 42506 - JMeter threads now use independent SSL sessions
  • +
  • 41707 - HTTP Proxy XML-RPC support
  • +
  • 41880 - Add content-type filtering to HTTP Proxy Server
  • +
  • 41876 - Add more options to control what the HTTP Proxy generates
  • +
  • 42158 - Improve support for multipart/form-data requests in HTTP Proxy server
  • +
  • 42173 - Let HTTP Proxy handle encoding of request, and undecode parameter values
  • +
  • 42674 - default to pre-emptive HTTP authorisation if not specified
  • +
  • Support "file" protocol in HTTP Samplers
  • +
  • Http Autoredirects are now enabled by default when creating new samplers
  • + +
  • 40103 - various LDAP enhancements
  • +
  • 40369 - LDAP: Stable search results in sampler
  • +
  • 40381 - LDAP: more descriptive strings
  • + +
  • BeanShell Post-Processor no longer ignores samples with zero-length result data
  • +
  • Added beanshell.init.file property to run a BeanShell script at startup
  • +
  • 39864 - BeanShell init files now found from currrent or bin directory
  • +
  • BeanShell elements now support ThreadListener and TestListener interfaces
  • +
  • BSF Sampler passes additional variables to the script
  • + +
  • Added timeout for WebService (SOAP) Sampler
  • + +
  • 40825 - Add JDBC prepared statement support
  • +
  • Extend JDBC Sampler: Commit, Rollback, AutoCommit
  • + +
  • 41457 - Add TCP Sampler option to not re-use connections
  • + +
  • 41522 - Use JUnit sampler name in sample results
  • + +
  • 42223 - FTP Sampler can now upload files
  • + +
  • 40804 - Change Counter default to max = Long.MAX_VALUE
  • + +
  • Use property jmeter.home (if present) to override user.dir when starting JMeter
  • +
  • New -j option to easily change jmeter log file
  • + +
  • HTTP Mirror Server Workbench element
  • + +
  • 41253 - extend XPathExtractor to work with non-NodeList XPath expressions
  • +
  • 42088 - Add XPath Assertion for booleans
  • + +
  • Added __V variable function to resolve nested variable names
  • + +
  • 40369 - Equals Response Assertion
  • +
  • 41704 - Allow charset encoding to be specified for CSV DataSet
  • +
  • 41259 - Comment field added to all test elements
  • +
  • Add standard deviation to Summary Report
  • +
  • 41873 - Add name to AssertionResult and display AssertionResult in ViewResultsFullVisualizer
  • +
  • 36755 - Save XML test files with UTF-8 encoding
  • +
  • Use ISO date-time format for Tree View Listener (previously the year was not shown)
  • +
  • Improve loading of CSV files: if possible, use header to determine format; guess timestamp format if not milliseconds
  • +
  • 41913 - TransactionController now creates samples as sub-samples of the transaction
  • +
  • 42582 - JSON pretty printing in Tree View Listener
  • +
  • 40099 - Enable use of object variable in ForEachController
  • + +
  • 39693 - View Result Table uses icon instead of check box
  • +
  • 39717 - use icons in the results tree
  • +
  • 42247 - improve HCI
  • +
  • Allow user to cancel out of Close dialogue
  • +
+ +

Non-functional improvements:

+
    +
  • Functor calls can now be unit tested
  • +
  • Replace com.sun.net classes with javax.net
  • +
  • Extract external jar definitions into build.properties file
  • +
  • Use specific jar names in build classpaths so errors are detected sooner
  • +
  • Tidied up ORO calls; now only one cache, size given by oro.patterncache.size, default 1000
  • +
  • 42326 - Order of elements in .jmx files changes
  • +
+ +

External jar updates:

+
    +
  • Htmlparser 2.0-20060923
  • +
  • xstream 1.2.1/xpp3_min-1.1.3.4.O
  • +
  • Batik 1.6
  • +
  • BSF 2.4.0
  • +
  • commons-collections 3.2
  • +
  • commons-httpclient-3.1-rc1
  • +
  • commons-jexl 1.1
  • +
  • commons-lang-2.3 (added)
  • +
  • JUnit 3.8.2
  • +
  • velocity 1.5
  • +
  • commons-io 1.3.1 (added)
  • +
+ +

Bug fixes:

+
    +
  • 39773 - NTLM now needs local host name - fix other call
  • +
  • 40438 - setting "httpclient.localaddress" has no effect
  • +
  • 40419 - Chinese messages translation fix
  • +
  • 39861 - fix typo
  • +
  • 40562 - redirects no longer invoke RE post processors
  • +
  • 40451 - set label if not set by sampler
  • +
  • Fix NPE in CounterConfig.java in Remote mode
  • +
  • 40791 - Calculator used by Summary Report
  • +
  • 40772 - correctly parse missing fields in CSV log files
  • +
  • 40773 - XML log file timestamp not parsed correctly
  • +
  • 41029 - JMeter -t fails to close input JMX file
  • +
  • 40954 - Statistical mode in distributed testing shows wrong results
  • +
  • Fix ClassCast Exception when using sampler that returns null, e..g TestAction
  • +
  • 41140 - Post-processors are run in reverse order
  • +
  • 41277 - add Latency and Encoding to CSV output
  • +
  • 41414 - Mac OS X may add extra item to -jar classpath
  • +
  • Fix NPE when saving thread counts in remote testing
  • +
  • 34261 - NPE in HtmlParser (allow for missing attributes)
  • +
  • 40100 - check FileServer type before calling close
  • +
  • 39887 - jmeter.util.SSLManager: Couldn't load keystore error message
  • +
  • 41543 - exception when webserver returns "500 Internal Server Error" and content-length is 0
  • +
  • 41416 - don't use chunked input for text-box input in SOAP-RPC sampler
  • +
  • 39827 - SOAP Sampler content length for files
  • +
  • Fix Class cast exception in Clear.java
  • +
  • 40383 - don't set content-type if already set
  • +
  • Mailer Visualiser test button now works if test plan has not yet been saved
  • +
  • 36959 - Shortcuts "ctrl c" and "ctrl v" don't work on the tree elements
  • +
  • 40696 - retrieve embedded resources from STYLE URL() attributes
  • +
  • 41568 - Problem when running tests remotely when using a 'Counter'
  • +
  • Fixed various classes that assumed timestamps were always end time stamps: +
      +
    • SamplingStatCalculator
    • +
    • JTLData
    • +
    • RunningSample
    • +
    +
  • +
  • 40325 - allow specification of proxyuser and proxypassword for WebServiceSampler
  • +
  • Change HttpClient proxy definition to use NTCredentials; added http.proxyDomain property for this
  • +
  • 40371 - response assertion "pattern to test" scrollbar problem
  • +
  • 40589 - Unescape XML entities in embedded URLs
  • +
  • 41902 - NPE in HTTPSampler when responseCode = -1
  • +
  • 41903 - ViewResultsFullVisualizer : status column looks bad when you do copy and paste
  • +
  • 41837 - Parameter value corruption in proxy
  • +
  • 41905 - Can't cut/paste/select Header Manager fields in Java 1.6
  • +
  • 41928 - Make all request headers sent by HTTP Request sampler appear in sample result
  • +
  • 41944 - Subresults not handled recursively by ResultSaver
  • +
  • 42022 - HTTPSampler does not allow multiple headers of same name
  • +
  • 42019 - Content type not stored in redirected HTTP request with subresults
  • +
  • 42057 - connection can be null if method is null
  • +
  • 41518 - JMeter changes the HTTP header Content Type for POST request
  • +
  • 42156 - HTTPRequest HTTPClient incorrectly urlencodes parameter value in POST
  • +
  • 42184 - Number of bytes for subsamples not added to sample when sub samples are added
  • +
  • 42185 - If a HTTP Sampler follows a redirect, and is set up to download images, then images are downloaded multiple times
  • +
  • 39808 - Invalid redirect causes incorrect sample time
  • +
  • 42267 - Concurrent GUI update failure in Proxy Recording
  • +
  • 30120 - Name of simple controller is resetted if a new simple controller is added as child
  • +
  • 41078 - merge results in name change of test plan
  • +
  • 40077 - Creating new Elements copies values from Existing elements
  • +
  • 42325 - Implement the "clear" method for the LogicControllers
  • +
  • 25441 - TestPlan changes sometimes detected incorrectly (isDirty)
  • +
  • 39734 - Listeners shared after copy/paste operation
  • +
  • 40851 - Loop controller with 0 iterations, stops evaluating the iterations field
  • +
  • 24684 - remote startup problems if spaces in the path of the jmeter
  • +
  • Use Listener configuration when loading CSV data files
  • +
  • Function methods setParameters() need to be synchronized
  • +
  • Fix CLI long optional argument to require "=" (as for short options)
  • +
  • Fix SlowSocket to work properly with Httpclient (both http and https)
  • +
  • 41612 - Loop nested in If Controller behaves erratically
  • +
  • 42232 - changing language clears UDV contents
  • +
  • Jexl function did not allow variables
  • +
+ +

Version 2.2

+ +

Incompatible changes:

+

+The time stamp is now set to the sampler start time (it was the end). +To revert to the previous behaviour, change the property sampleresult.timestamp.start to false (or comment it) +

+

The JMX output format has been simplified and files are not backwards compatible

+

+The JMeter.BAT file no longer changes directory to JMeter home, but runs from the current working directory. +The jmeter-n.bat and jmeter-t.bat files change to the directory containing the input file. +

+

+Listeners are now started slightly later in order to allow variable names to be used. +This may cause some problems; if so define the following in jmeter.properties: +
+jmeterengine.startlistenerslater=false +

+ +

+The GUI now expands the tree by default when loading a test plan. +This can be disabled by setting the JMeter property onload.expandtree=false +

+ +

Known problems:

+
    +
  • Post-processors run in reverse order (see 41140)
  • +
  • Module Controller does not work in non-GUI mode
  • +
  • Aggregate Report and some other listeners use increasing amounts of memory as a test progresses
  • +
  • Does not always handle non-default encoding properly
  • +
  • Spaces in the installation path cause problems for client-server mode
  • +
  • Change of Language does not propagate to all test elements
  • +
  • SamplingStatCalculator keeps a List of all samples for calculation purposes; +this can cause memory exhaustion in long-running tests
  • +
  • Does not properly handle server certificates if they are expired or not installed locally
  • +
+ +

New functionality:

+
    +
  • Report function
  • +
  • XPath Extractor Post-Processor. Handles single and multiple matches.
  • +
  • Simpler JMX file format (2.2)
  • +
  • BeanshellSampler code can update ResponseData directly
  • +
  • 37490 - Allow UDV as delay in Duration Assertion
  • +
  • Slow connection emulation for HttpClient
  • +
  • Enhanced JUnitSampler so that by default assert errors and exceptions are not appended to the error message. +Users must explicitly check append in the sampler
  • +
  • Enhanced the documentation for webservice sampler to explain how it works with CSVDataSet
  • +
  • Enhanced the documentation for javascript function to explain escaping comma
  • +
  • Allow CSV Data Set file names to be absolute
  • +
  • Report Tree compiler errors better
  • +
  • Don't reset Regex Extractor variable if default is empty
  • +
  • includecontroller.prefix property added
  • +
  • Regular Expression Extractor sets group count
  • +
  • Can now save entire screen as an image, not just the right-hand pane
  • +
  • 38901 - Add optional SOAPAction header to SOAP Sampler
  • +
  • New BeanShell test elements: Timer, PreProcessor, PostProcessor, Listener
  • +
  • __split() function now clears next variable, so it can be used with ForEach Controller
  • +
  • 38682 - add CallableStatement functionality to JDBC Sampler
  • +
  • Make it easier to change the RMI/Server port
  • +
  • Add property jmeter.save.saveservice.xml_pi to provide optional xml processing instruction in JTL files
  • +
  • Add bytes and URL to items that can be saved in sample log files (XML and CSV)
  • +
  • The Post-Processor "Save Responses to a File" now saves the generated file name with the +sample, and the file name can be included in the sample log file. +
  • +
  • Change jmeter.bat DOS script so it works from any directory
  • +
  • New -N option to define nonProxyHosts from command-line
  • +
  • New -S option to define system properties from input file
  • +
  • 26136 - allow configuration of local address
  • +
  • Expand tree by default when loading a test plan - can be disabled by setting property onload.expandtree=false
  • +
  • 11843 - URL Rewriter can now cache the session id
  • +
  • Counter Pre-Processor now supports formatted numbers
  • +
  • Add support for HEAD PUT OPTIONS TRACE and DELETE methods
  • +
  • Allow default HTTP implementation to be changed
  • +
  • Optionally save active thread counts (group and all) to result files
  • +
  • Variables/functions can now be used in Listener file names
  • +
  • New __time() function; define START.MS/START.YMD/START.HMS properties and variables
  • +
  • Add Thread Name to Tree and Table Views
  • +
  • Add debug functions: What class, debug on, debug off
  • +
  • Non-caching Calculator - used by Table Visualiser to reduce memory footprint
  • +
  • Summary Report - similar to Aggregate Report, but uses less memory
  • +
  • 39580 - recycle option for CSV Dataset
  • +
  • 37652 - support for Ajp Tomcat protocol
  • +
  • 39626 - Loading SOAP/XML-RPC requests from file
  • +
  • 39652 - Allow truncation of labels on AxisGraph
  • +
  • Allow use of htmlparser 1.6
  • +
  • 39656 - always use SOAP action if it is provided
  • +
  • Automatically include properties from user.properties file
  • +
  • Add __jexl() function - evaluates Commons JEXL expressions
  • +
  • Optionally load JMeter properties from user.properties and system properties from system.properties.
  • +
  • 39707 - allow Regex match against URL
  • +
  • Add start time to Table Visualiser
  • +
  • HTTP Samplers can now extract embedded resources for any required media types
  • +
+ +

Bug fixes:

+
    +
  • Fix NPE when no module selected in Module Controller
  • +
  • Fix NPE in XStream when no ResponseData present
  • +
  • Remove ?xml prefix when running with Java 1.5 and no x-jars
  • +
  • 37117 - setProperty() function should return ""; added optional return of original setting
  • +
  • Fix CSV output time format
  • +
  • 37140 - handle encoding better in RegexFunction
  • +
  • Load all cookies, not just the first; fix class cast exception
  • +
  • Fix default Cookie path name (remove page name)
  • +
  • Fixed resultcode attribute name
  • +
  • 36898 - apply encoding to RegexExtractor
  • +
  • Add properties for saving subresults, assertions, latency, samplerData, responseHeaders, requestHeaders & encoding
  • +
  • 37705 - Synch Timer now works OK after run is stopped
  • +
  • 37716 - Proxy request now handles file Post correctly
  • +
  • HttpClient Sampler now saves latency
  • +
  • Fix NPE when using JavaScript function on Test Plan
  • +
  • Fix Base Href parsing in htmlparser
  • +
  • 38256 - handle cookie with no path
  • +
  • 38391 - use long when accumulating timer delays
  • +
  • 38554 - Random function now uses long numbers
  • +
  • 35224 - allow duplicate attributes for LDAP sampler
  • +
  • 38693 - Webservice sampler can now use https protocol
  • +
  • 38646 - Regex Extractor now clears old variables on match failure
  • +
  • 38640 - fix WebService Sampler pooling
  • +
  • 38474 - HTML Link Parser doesn't follow frame links
  • +
  • 36430 - Counter now uses long rather than int to increase the range
  • +
  • 38302 - fix XPath function
  • +
  • 38748 - JDBC DataSourceElement fails with remote testing
  • +
  • 38902 - sometimes -1 seems to be returned unnecessarily for response code
  • +
  • 38840 - make XML Assertion thread-safe
  • +
  • 38681 - Include controller now works in non-GUI mode
  • +
  • Add write(OS,IS) implementation to TCPClientImpl
  • +
  • Sample Result converter saves response code as "rc". Previously it saved as "rs" but read with "rc"; it will now also read with "rc". +The XSL stylesheets also now accept either "rc" or "rs"
  • +
  • Fix counter function so each counter instance is independent (previously the per-user counters were shared between instances of the function)
  • +
  • Fix TestBean Examples so that they work
  • +
  • Fix JTidy parser so it does not skip body tags with background images
  • +
  • Fix HtmlParser parser so it catches all background images
  • +
  • 39252 set SoapSampler sample result from XML data
  • +
  • 38694 - WebServiceSampler not setting data encoding correctly
  • +
  • Result Collector now closes input files read by listeners
  • +
  • 25505 - First HTTP sampling fails with "HTTPS hostname wrong: should be 'localhost'"
  • +
  • 25236 - remove double scrollbar from Assertion Result Listener
  • +
  • 38234 - Graph Listener divide by zero problem
  • +
  • 38824 - clarify behaviour of Ignore Status
  • +
  • 38250 - jmeter.properties "language" now supports country suffix, for zh_CN and zh_TW etc
  • +
  • jmeter.properties file is now closed after it has been read
  • +
  • 39533 - StatCalculator added wrong items
  • +
  • 39599 - ConcurrentModificationException
  • +
  • HTTPSampler2 now handles Auto and Follow redirects correctly
  • +
  • 29481 - fix reloading sample results so subresults not counted twice
  • +
  • 30267 - handle AutoRedirects properly
  • +
  • 39677 - allow for space in JMETER_BIN variable
  • +
  • Use Commons HttpClient cookie parsing and management. Fix various problems with cookie handling.
  • +
  • 39773 - NTCredentials needs host name
  • +
+ +

Other changes

+
    +
  • Updated to HTTPClient 3.0 (from 2.0)
  • +
  • Updated to Commons Collections 3.1
  • +
  • Improved formatting of Request Data in Tree View
  • +
  • Expanded user documentation
  • +
  • Added MANIFEST, NOTICE and LICENSE to all jars
  • +
  • Extract htmlparser interface into separate jarfile to make it possible to replace the parser
  • +
  • Removed SQL Config GUI as no longer needed (or working!)
  • +
  • HTTPSampler no longer logs a warning for Page not found (404)
  • +
  • StringFromFile now callable as __StringFromFile (as well as _StringFromFile)
  • +
  • Updated to Commons Logging 1.1
  • +
+ + + + +
+

Version 2.1.1

+

New functionality:

+
    +
  • New Include Controller allows a test plan to reference an external jmx file
  • +
  • New JUnitSampler added for using JUnit Test classes
  • +
  • New Aggregate Graph listener is capable of graphing aggregate statistics
  • +
  • Can provide additional classpath entries using the property user.classpath and on the Test Plan element
  • +
+

Bug fixes:

+
    +
  • AccessLog Sampler and JDBC test elements populated correctly from 2.0 test plans
  • +
  • BSF Sampler now populates filename and parameters from saved test plan
  • +
  • 36500 - handle missing data more gracefully in WebServiceSampler
  • +
  • 35546 - add merge to right-click menu
  • +
  • 36642 - Summariser stopped working in 2.1
  • +
  • 36618 - CSV header line did not match saved data
  • +
  • JMeter should now run under JVM 1.3 (but does not build with 1.3)
  • +
+ + + + +

Version 2.1

+

New functionality:

+
    +
  • New Test Script file format - smaller, more compact, more readable
  • +
  • New Sample Result file format - smaller, more compact
  • +
  • XSchema Assertion
  • +
  • XML Tree display
  • +
  • CSV DataSet Config item
  • +
  • New JDBC Connection Pool Config Element
  • +
  • Synchronisation Timer
  • +
  • setProperty function
  • +
  • Save response data on error
  • +
  • Ant JMeter XSLT now optionally shows failed responses and has internal links
  • +
  • Allow JavaScript variable name to be omitted
  • +
  • Changed following Samplers to set sample label from sampler name
  • +
  • All Test elements can be saved as a graphics image to a file
  • +
  • 35026 - add RE pattern matching to Proxy
  • +
  • 34739 - Enhance constant Throughput timer
  • +
  • 25052 - use response encoding to create comparison string in Response Assertion
  • +
  • New optional icons
  • +
  • Allow icons to be defined via property files
  • +
  • New stylesheets for 2.1 format XML test output
  • +
  • Save samplers, config element and listeners as PNG
  • +
  • Enhanced support for WSDL processing
  • +
  • New JMS sampler for topic and queue messages
  • +
  • How-to for JMS samplers
  • +
  • 35525 - Added Spanish localisation
  • +
  • 30379 - allow server.rmi.port to be overridden
  • +
  • enhanced the monitor listener to save the calculated stats
  • +
  • Functions and variables now work at top level of test plan
  • +
+

Bug fixes:

+
    +
  • 34586 - XPath always remained as /
  • +
  • BeanShellInterpreter did not handle null objects properly
  • +
  • Fix Chinese resource bundle names
  • +
  • Save field names if required to CSV files
  • +
  • Ensure XML file is closed
  • +
  • Correct icons now displayed for TestBean components
  • +
  • Allow for missing optional jar(s) in creating menus
  • +
  • Changed Samplers to set sample label from sampler name as was the case for HTTP
  • +
  • Fix various samplers to avoid NPEs when incomplete data is provided
  • +
  • Fix Cookie Manager to use seconds; add debug
  • +
  • 35067 - set up filename when using -t option
  • +
  • Don't substitute TestElement.* properties by UDVs in Proxy
  • +
  • 35065 - don't save old extensions in File Saver
  • +
  • 25413 - don't enable Restart button unnecessarily
  • +
  • 35059 - Runtime Controller stopped working
  • +
  • Clear up any left-over connections created by LDAP Extended Sampler
  • +
  • 23248 - module controller didn't remember stuff between save and reload
  • +
  • Fix Chinese locales
  • +
  • 29920 - change default locale if necessary to ensure default properties are picked up when English is selected.
  • +
  • Bug fixes for Tomcat monitor captions
  • +
  • Fixed webservice sampler so it works with user defined variables
  • +
  • Fixed screen borders for LDAP config GUI elements
  • +
  • 31184 - make sure encoding is specified in JDBC sampler
  • +
  • TCP sampler - only share sockets with same host:port details; correct the manual
  • +
  • Extract src attribute for embed tags in JTidy and Html Parsers
  • +
+ + + +

Version 2.0.3

+

New functionality:

+
    +
  • XPath Assertion and XPath Function
  • +
  • Switch Controller
  • +
  • ForEach Controller can now loop through sets of groups
  • +
  • Allow CSVRead delimiter to be changed (see jmeter.properties)
  • +
  • 33920 - allow additional property files
  • +
  • 33845 - allow direct override of Home dir
  • +
+

Bug fixes:

+
    +
  • Regex Extractor nested constant not put in correct place 32395
  • +
  • Start time reset to now if necessary so that delay works OK.
  • +
  • Missing start/end times in scheduler are assumed to be now, not 1970
  • +
  • 28661 - 304 responses not appearing in listeners
  • +
  • DOS scripts now handle different disks better
  • +
  • 32345 - HTTP Rewriter does not work with HTTP Request default
  • +
  • Catch Runtime Exceptions so an error in one Listener does not affect others
  • +
  • 33467 - __threadNum() extracted number wrongly
  • +
  • 29186,33299 - fix CLI parsing of "-" in second argument
  • +
  • Fix CLI parse bug: -D arg1=arg2. Log more startup parameters.
  • +
  • Fix JTidy and HTMLParser parsers to handle form src= and link rel=stylesheet
  • +
  • JMeterThread now logs Errors to jmeter.log which were appearing on console
  • +
  • Ensure WhileController condition is dynamically checked
  • +
  • 32790 ensure If Controller condition is re-evaluated each time
  • +
  • 30266 - document how to display proxy recording responses
  • +
  • 33921 - merge should not change file name
  • +
  • Close file now gives chance to save changes
  • +
  • 33559 - fixes to Runtime Controller
  • +
+

Other changes:

+
    +
  • To help with variable evaluation, JMeterThread sets "sampling started" a bit earlier (see jmeter.properties)
  • +
  • 33796 - delete cookies with null/empty values
  • +
  • Better checking of parameter count in JavaScript function
  • +
  • Thread Group now defaults to 1 loop instead of forever
  • +
  • All Beanshell access is now via a single class; only need BSH jar at run-time
  • +
  • 32464 - document Direct Draw settings in jmeter.bat
  • +
  • 33919 - increase Counter field sizes
  • +
  • 32252 - ForEach was not initialising counters
  • +
+ + + +

Version 2.0.2

+

New functionality:

+
    +
  • While Controller
  • +
  • BeanShell intilisation scripts
  • +
  • Result Saver can optionally save failed results only
  • +
  • Display as HTML has option not to download frames and images etc
  • +
  • Multiple Tree elements can now be enabled/disabled/copied/pasted at once
  • +
  • __split() function added
  • +
  • 28699 allow Assertion to regard unsuccessful responses - e.g. 404 - as successful
  • +
  • 29075 Regex Extractor can now extract data out of http response header as well as the body
  • +
  • __log() functions can now write to stdout and stderr
  • +
  • URL Modifier can now optionally ignore query parameters
  • +
+

Bug fixes:

+
    +
  • If controller now works after the first false condition 31390
  • +
  • Regex GUI was losing track of Header/Body checkbox 29853
  • +
  • Display as HTML now handles frames and relative images
  • +
  • Right-click open replaced by merge
  • +
  • Fix some drag and drop problems
  • +
  • Fixed foreach demo example so it works
  • +
  • 30741 SSL password prompt now works again
  • +
  • StringFromFile now closes files at end of test; start and end now optional as intended
  • +
  • 31342 Fixed text of SOAP Sampler headers
  • +
  • Proxy must now be stopped before it can be removed 25145
  • +
  • Link Parser now supports BASE href 25490
  • +
  • 30917 Classfinder ignores duplicate names
  • +
  • 22820 Allow Counter value to be cleared
  • +
  • 28230 Fix NPE in HTTP Sampler retrieving embedded resources
  • +
  • Improve handling of StopTest; catch and log some more errors
  • +
  • ForEach Controller no longer runs any samples if first variable is not defined
  • +
  • 28663 NPE in remote JDBC execution
  • +
  • 30110 Deadlock in stopTest processing
  • +
  • 31696 Duration not working correctly when using Scheduler
  • +
  • JMeterContext now uses ThreadLocal - should fix some potential NPE errors
  • +
+

Version 2.0.1

+

Bug fix release. TBA.

+

Version 2.0

+
    +
  • HTML parsing improved; now has choice of 3 parsers, and most embedded elements can now be detected and downloaded.
  • +
  • Redirects can now be delegated to URLConnection by defining the JMeter property HTTPSamper.delegateRedirects=true (default is false)
  • +
  • Stop Thread and Stop Test methods added for Samplers and Assertions etc. Samplers can call setStopThread(true) or setStopTest(true) if they detect an error that needs to stop the thread of the test after the sample has been processed
  • +
  • Thread Group Gui now has an extra pane to specify what happens after a Sampler error: Continue (as now), Stop Thread or Stop Test. + This needs to be extended to a lower level at some stage.
  • +
  • Added Shutdown to Run Menu. This is the same as Stop except that it lets the Threads finish normally (i.e. after the next sample has been completed)
  • +
  • Remote samples can be cached until the end of a test by defining the property hold_samples=true when running the server. +More work is needed to be able to control this from the GUI
  • +
  • Proxy server has option to skip recording browser headers
  • +
  • Proxy restart works better (stop waits for daemon to finish)
  • +
  • Scheduler ignores start if it has already passed
  • +
  • Scheduler now has delay function
  • +
  • added Summariser test element (mainly for non-GUI) testing. This prints summary statistics to System.out and/or the log file every so oftem (3 minutes by default). Multiple summarisers can be used; samples are accumulated by summariser name.
  • +
  • Extra Proxy Server options: +Create all samplers with keep-alive disabled +Add Separator markers between sets of samples +Add Response Assertion to first sampler in each set
  • +
  • Test Plan has a comment field
  • + +
  • Help Page can now be pushed to background
  • +
  • Separate Function help page
  • +
  • New / amended functions
  • +
      +
    • __property() and __P() functions
    • +
    • __log() and __logn() - for writing to the log file
    • +
    • _StringFromFile can now process a sequence of files, e.g. dir/file01.txt, dir/file02.txt etc
    • +
    • _StringFromFile() funtion can now use a variable or function for the file name
    • +
    +
  • New / amended Assertions
  • +
      +
    • Response Assertion now works for URLs, and it handles null data better
    • +
    • Response Assertion can now match on Response Code and Response message as well
    • +
    • HTML Assertion using JTidy to check for well-formed HTML
    • +
    +
  • If Controller (not fully functional yet)
  • +
  • Transaction Controller (aggregates the times of its children)
  • +
  • New Samplers
  • +
      +
    • Basic BSF Sampler (optional)
    • +
    • BeanShell Sampler (optional, needs to be downloaded from www.beanshell.org
    • +
    • Basic TCP Sampler
    • +
    +
  • Optionally start BeanShell server (allows remote access to JMeter variables and methods)
  • +
+

Version 1.9.1

+

TBA

+

Version 1.9

+
    +
  • Sample result log files can now be in CSV or XML format
  • +
  • New Event model for notification of iteration events during test plan run
  • +
  • New Javascript function for executing arbitrary javascript statements
  • +
  • Many GUI improvements
  • +
  • New Pre-processors and Post-processors replace Modifiers and Response-Based Modifiers.
  • +
  • Compatible with jdk1.3
  • +
  • JMeter functions are now fully recursive and universal (can use functions as parameters to functions)
  • +
  • Integrated help window now supports hypertext links
  • +
  • New Random Function
  • +
  • New XML Assertion
  • +
  • New LDAP Sampler (alpha code)
  • +
  • New Ant Task to run JMeter (in extras folder)
  • +
  • New Java Sampler test implementation (to assist developers)
  • +
  • More efficient use of memory, faster loading of .jmx files
  • +
  • New SOAP Sampler (alpha code)
  • +
  • New Median calculation in Graph Results visualizer
  • +
  • Default config element added for developer benefit
  • +
  • Various performance enhancements during test run
  • +
  • New Simple File recorder for minimal GUI overhead during test run
  • +
  • New Function: StringFromFile - grabs values from a file
  • +
  • New Function: CSVRead - grabs multiple values from a file
  • +
  • Functions now longer need to be encoded - special values should be escaped +with "\" if they are literal values
  • +
  • New cut/copy/paste functionality
  • +
  • SSL testing should work with less user-fudging, and in non-gui mode
  • +
  • Mailer Model works in non-gui mode
  • +
  • New Througput Controller
  • +
  • New Module Controller
  • +
  • Tests can now be scheduled to run from a certain time till a certain time
  • +
  • Remote JMeter servers can be started from a non-gui client. Also, in gui mode, all remote servers can be started with a single click
  • +
  • ThreadGroups can now be run either serially or in parallel (default)
  • +
  • New command line options to override properties
  • +
  • New Size Assertion
  • + +
+ +

Version 1.8.1

+
    +
  • Bug Fix Release. Many bugs were fixed.
  • +
  • Removed redundant "Root" node from test tree.
  • +
  • Re-introduced Icons in test tree.
  • +
  • Some re-organization of code to improve build process.
  • +
  • View Results Tree has added option to view results as web document (still buggy at this point).
  • +
  • New Total line in Aggregate Listener (still buggy at this point).
  • +
  • Improvements to ability to change JMeter's Locale settings.
  • +
  • Improvements to SSL Manager.
  • +
+ +

Version 1.8

+
    +
  • Improvement to Aggregate report's calculations.
  • +
  • Simplified application logging.
  • +
  • New Duration Assertion.
  • +
  • Fixed and improved Mailer Visualizer.
  • +
  • Improvements to HTTP Sampler's recovery of resources (sockets and file handles).
  • +
  • Improving JMeter's internal handling of test start/stop.
  • +
  • Fixing and adding options to behavior of Interleave and Random Controllers.
  • +
  • New Counter config element.
  • +
  • New User Parameters config element.
  • +
  • Improved performance of file opener.
  • +
  • Functions and other elements can access global variables.
  • +
  • Help system available within JMeter's GUI.
  • +
  • Test Elements can be disabled.
  • +
  • Language/Locale can be changed while running JMeter (mostly).
  • +
  • View Results Tree can be configured to record only errors.
  • +
  • Various bug fixes.
  • +
+ +

Version 1.7.3

+
    +
  • New Functions that provide more ability to change requests dynamically during test runs.
  • +
  • New language translations in Japanese and German.
  • +
  • Removed annoying Log4J error messages.
  • +
  • Improved support for loading JMeter 1.7 version test plan files (.jmx files).
  • +
  • JMeter now supports proxy servers that require username/password authentication.
  • +
  • Dialog box indicating test stopping doesn't hang JMeter on problems with stopping test.
  • +
  • GUI can run multiple remote JMeter servers (fixes GUI bug that prevented this).
  • +
  • Dialog box to help created function calls in GUI.
  • +
  • New Keep-alive switch in HTTP Requests to indicate JMeter should or should not use Keep-Alive for sockets.
  • +
  • HTTP Post requests can have GET style arguments in Path field. Proxy records them correctly now.
  • +
  • New User-defined test-wide static variables.
  • +
  • View Results Tree now displays more information, including name of request (matching the name +in the test tree) and full request and POST data.
  • +
  • Removed obsolete View Results Visualizer (use View Results Tree instead).
  • +
  • Performance enhancements.
  • +
  • Memory use enhancements.
  • +
  • Graph visualizer GUI improvements.
  • +
  • Updates and fixes to Mailer Visualizer.
  • +
+ +

Version 1.7.2

+
    +
  • JMeter now notifies user when test has stopped running.
  • +
  • HTTP Proxy server records HTTP Requests with re-direct turned off.
  • +
  • HTTP Requests can be instructed to either follow redirects or ignore them.
  • +
  • Various GUI improvements.
  • +
  • New Random Controller.
  • +
  • New SOAP/XML-RPC Sampler.
  • +
+ +

Version 1.7.1

+
    +
  • JMeter's architecture revamped for a more complete separation between GUI code and +test engine code.
  • +
  • Use of Avalon code to save test plans to XML as Configuration Objects
  • +
  • All listeners can save data to file and load same data at later date.
  • +
+ +

Version 1.7Beta

+
    +
  • Better XML support for special characters (Tushar Bhatia)
  • +
  • Non-GUI functioning & Non-GUI test plan execution (Tushar Bhatia)
  • +
  • Removing Swing dependence from base JMeter classes
  • +
  • Internationalization (Takashi Okamoto)
  • +
  • AllTests bug fix (neth6@atozasia.com)
  • +
  • ClassFinder bug fix (neth6@atozasia.com)
  • +
  • New Loop Controller
  • +
  • Proxy Server records HTTP samples from browser + (and documented in the user manual)
  • Multipart Form support
  • +
  • HTTP Header class for Header customization
  • +
  • Extracting HTTP Header information from responses (Jamie Davidson)
  • +
  • Mailer Visualizer re-added to JMeter
  • +
  • JMeter now url encodes parameter names and values
  • +
  • listeners no longer give exceptions if their gui's haven't been initialized
  • +
  • HTTPS and Authorization working together
  • +
  • New Http sampling that automatically parses HTML response + for images to download, and includes the downloading of these + images in total time for request (Neth neth6@atozasia.com)
  • +
  • HTTP responses from server can be parsed for links and forms, + and dynamic data can be extracted and added to test samples + at run-time (documented)
  • +
  • New Ramp-up feature (Jonathan O'Keefe)
  • +
  • New visualizers (Neth)
  • +
  • New Assertions for functional testing
  • +
+ +

Version 1.6.1

+
    +
  • Fixed saving and loading of test scripts (no more extra lines)
  • +
  • Can save and load special characters (such as "&" and "<").
  • +
  • Can save and load timers and listeners.
  • +
  • Minor bug fix for cookies (if you cookie value + contained an "=", then it broke).
  • +
  • URL's can sample ports other than 80, and can test HTTPS, + provided you have the necessary jars (JSSE)
  • +
+ +

Version 1.6 Alpha

+
    +
  • New UI
  • +
  • Separation of GUI and Logic code
  • +
  • New Plug-in framework for new modules
  • +
  • Enhanced performance
  • +
  • Layering of test logic for greater flexibility
  • +
  • Added support for saving of test elements
  • +
  • Added support for distributed testing using a single client
  • + +
+

Version 1.5.1

+
    +
  • Fixed bug that caused cookies not to be read if header name case not as expected.
  • +
  • Clone entries before sending to sampler - prevents relocations from messing up + information across threads
  • +
  • Minor bug fix to convenience dialog for adding paramters to test sample. + Bug prevented entries in dialog from appearing in test sample.
  • +
  • Added xerces.jar to distribution
  • +
  • Added junit.jar to distribution and created a few tests.
  • +
  • Started work on new framework. New files in cvs, but do not effect program yet.
  • +
  • Fixed bug that prevent HTTPJMeterThread from delaying according to chosen timer.
  • +
+

+

Version 1.5

+
    +
  • Abstracted out the concept of the Sampler, SamplerController, and TestSample. + A Sampler represents code that understands a protocol (such as HTTP, + or FTP, RMI, SMTP, etc..). It is the code that actually makes the + connection to whatever is being tested. A SamplerController + represents code that understands how to organize and run a group + of test samples. It is what binds together a Sampler and its test + samples and runs them. A TestSample represents code that understands + how to gather information from the user about a particular test. + For a website, it would represent a URL and any information to be sent + with the URL.
  • +
  • The UI has been updated to make entering test samples more convenient.
  • +
  • Thread groups have been added, allowing a user to setup multiple test to run + concurrently, and to allow sharing of test samples between those tests.
  • +
  • It is now possible to save and load test samples.
  • +
  • ....and many more minor changes/improvements...
  • +
+

+

+Apache JMeter 1.4.1-dev +

    +
  • Cleaned up URLSampler code after tons of patches for better readability. (SM)
  • +
  • Made JMeter send a special "user-agent" identifier. (SM)
  • +
  • Fixed problems with redirection not sending cookies and authentication info and removed + a warning with jikes compilation. Thanks to Wesley Tanaka for the patches (SM)
  • +
  • Fixed a bug in the URLSampler that caused to skip one URL when testing lists of URLs and + a problem with Cookie handling. Thanks to Graham Johnson for the patches (SM)
  • +
  • Fixed a problem with POST actions. Thanks to Stephen Schaub for the patch (SM)
  • +
+

+

+ Apache JMeter 1.4 - Jul 11 1999 +

    +
  • Fixed a problem with POST actions. Thanks to Brendan Burns for the patch (SM)
  • +
  • Added close button to the About box for those window managers who don't provide it. + Thanks to Jan-Henrik Haukeland for pointing it out. (SM)
  • +
  • Added the simple Spline sample visualizer (JPN)
  • +

+

Apache JMeter 1.3 - Apr 16 1999 +

    +
  • Run the Garbage Collector and run finalization before starting to sampling to ensure + same state every time (SM)
  • +
  • Fixed some NullPointerExceptions here and there (SM)
  • +
  • Added HTTP authentication capabilities (RL)
  • +
  • Added windowed sample visualizer (SM)
  • +
  • Fixed stupid bug for command line arguments. Thanks to Jorge Bracer for pointing this out (SM)
  • +

+

Apache JMeter 1.2 - Mar 17 1999 +

    +
  • Integrated cookie capabilities with JMeter (SM)
  • +
  • Added the Cookie manager and Netscape file parser (SD)
  • +
  • Fixed compilation error for JDK 1.1 (SD)

+

Apache JMeter 1.1 - Feb 24 1999 +

    +
  • Created the opportunity to create URL aliasing from the properties file as well as the + ability to associate aliases to URL sequences instead of single URLs (SM) Thanks to + Simon Chatfield for the very nice suggestions + and code examples.
  • +
  • Removed the TextVisualizer and replaced it with the much more useful FileVisualizer (SM)
  • +
  • Added the known bug list (SM)
  • +
  • Removed the Java Apache logo (SM)
  • +
  • Fixed a couple of typos (SM)
  • +
  • Added UNIX makefile (SD)

+

Apache JMeter 1.0.1 - Jan 25 1999 +

    +
  • Removed pending issues doc issues (SM)
  • +
  • Fixed the unix script (SM)
  • +
  • Added the possibility of running the JAR directly using "java -jar + ApacheJMeter.jar" with Java 2 (SM)
  • +
  • Some small updates: fixed Swing location after Java 2(tm) release, license update and + small cleanups (SM)
  • +

+

Apache JMeter 1.0 - Dec 15 1998 +

    +
  • Initial version. (SM)
  • +

+
+ +
diff --git a/ApacheJmeter/xdocs/css/style.css b/ApacheJmeter/xdocs/css/style.css new file mode 100644 index 0000000..34dd386 --- /dev/null +++ b/ApacheJmeter/xdocs/css/style.css @@ -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. +*/ + +/*Shows the value of the name attribute when hovered*/ +/* Disabled +a[name]:hover:after{ + content: " #" attr(name); + font-size: 90%; + text-decoration: none; +} +*/ + +/* + * Hide class="sectionlink", except when an enclosing heading + * has the :hover property. + * Used to hide the ¶ marker for generating internal links + */ +.sectionlink { + display: none; +} +:hover > .sectionlink { + display: inline; + /* Green so shows up on section headings too */ + color: rgb(0,255,0); +} diff --git a/ApacheJmeter/xdocs/demos/AssertionTestPlan.jmx b/ApacheJmeter/xdocs/demos/AssertionTestPlan.jmx new file mode 100644 index 0000000..0cd3e3b --- /dev/null +++ b/ApacheJmeter/xdocs/demos/AssertionTestPlan.jmx @@ -0,0 +1,128 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836421000 + 1211836421000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + GET + true + false + false + false + + + + false + + + + + + </html> + + 2 + Assertion.response_data + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + assertion.dat + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/xdocs/demos/AuthManagerTestPlan.jmx b/ApacheJmeter/xdocs/demos/AuthManagerTestPlan.jmx new file mode 100644 index 0000000..4b792dc --- /dev/null +++ b/ApacheJmeter/xdocs/demos/AuthManagerTestPlan.jmx @@ -0,0 +1,151 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836473000 + 1211836473000 + false + continue + + + + + + + + http://localhost/secret + kevin + spot + + + + + + + + + + + localhost + + http + + / + + + + + + + + + http + + /secret/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /secret/index2.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /index.html + GET + true + false + false + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + auth-manager.dat + + + + + + diff --git a/ApacheJmeter/xdocs/demos/BeanShellAssertion.bsh b/ApacheJmeter/xdocs/demos/BeanShellAssertion.bsh new file mode 100644 index 0000000..062fb81 --- /dev/null +++ b/ApacheJmeter/xdocs/demos/BeanShellAssertion.bsh @@ -0,0 +1,53 @@ +/* + * 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. + * + */ + +// Sample BeanShell Assertion script +// Derived from http://www.mail-archive.com/jmeter-user@jakarta.apache.org/msg05597.html + +if (ResponseCode != null && ResponseCode.equals ("200") == false ) +{ + // this is standard stuff + Failure=true ; + FailureMessage ="Response code was not a 200 response code it was " + ResponseCode + "." ; + print ( "the return code is " + ResponseCode); // this goes to stdout + log.warn( "the return code is " + ResponseCode); // this goes to the JMeter log file +} else { + try + { + // non standard stuff where BeanShell assertion will be really powerful . + // in my example I just test the size , but you could extend it further + // to actually test the content against another file. + byte [] arr = (byte[]) ResponseData ; + // print ( arr.length ) ; // use this to determine the size + if (arr != null && arr.length != 25218) + { + Failure= true ; + FailureMessage = "The response data size was not as expected" ; + } + else if ( arr == null ) + { + Failure= true ; + FailureMessage = "The response data size was null" ; + } + } + catch ( Throwable t ) + { + print ( t ) ; + log.warn("Error: ",t); + } +} \ No newline at end of file diff --git a/ApacheJmeter/xdocs/demos/ForEachTest2.jmx b/ApacheJmeter/xdocs/demos/ForEachTest2.jmx new file mode 100644 index 0000000..b19c91f --- /dev/null +++ b/ApacheJmeter/xdocs/demos/ForEachTest2.jmx @@ -0,0 +1,322 @@ + + + + + false + + + + + false + + + + + 1 + false + continue + 1076438592000 + + false + 2 + + 1 + + + 1076438592000 + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Sample 1 + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + a b c d + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + (\w)\s + inputVar + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + For 1 ${returnVar} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + ${returnVar1} + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + Sample 2 + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + + = + + + ResultData + a b c d + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + (\w)\sx + inputVar + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + Sleep_Time + 100 + = + + + Sleep_Mask + 0xFF + = + + + Label + For 2 ${returnVar} + = + + + ResponseCode + 200 + = + + + ResponseMessage + + = + + + Status + OK + = + + + SamplerData + ${returnVar} + = + + + ResultData + + = + + + + org.apache.jmeter.protocol.java.test.JavaTest + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/xdocs/demos/HeaderManagerTestPlan.jmx b/ApacheJmeter/xdocs/demos/HeaderManagerTestPlan.jmx new file mode 100644 index 0000000..71621ee --- /dev/null +++ b/ApacheJmeter/xdocs/demos/HeaderManagerTestPlan.jmx @@ -0,0 +1,95 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836504000 + 1211836504000 + false + continue + + + + + + + + User-Agent + Mozilla/4.0 (compatible; MSIE 5.5; Windows 98) + + + + + + + + + jakarta.apache.org + + http + + / + GET + true + false + false + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + header-manager.dat + + + + + + diff --git a/ApacheJmeter/xdocs/demos/InterleaveTestPlan.jmx b/ApacheJmeter/xdocs/demos/InterleaveTestPlan.jmx new file mode 100644 index 0000000..1429f0e --- /dev/null +++ b/ApacheJmeter/xdocs/demos/InterleaveTestPlan.jmx @@ -0,0 +1,160 @@ + + + + + + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 5 + + 0 + 2 + false + 0 + continue + + + + + + 0 + + + + + + + ${server} + + http + + /site/news/index.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + + + + + + + GET + false + true + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/xdocs/demos/InterleaveTestPlan2.jmx b/ApacheJmeter/xdocs/demos/InterleaveTestPlan2.jmx new file mode 100644 index 0000000..1eaabee --- /dev/null +++ b/ApacheJmeter/xdocs/demos/InterleaveTestPlan2.jmx @@ -0,0 +1,234 @@ + + + + + + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 8 + + 0 + 1 + false + 0 + continue + + + + + + 1 + + + + + + + + + + + + + + + 1 + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + 1 + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + ${server} + + http + + /site/faqs.html + GET + true + false + false + false + + + + false + + + + + + + + + + ${server} + + + + + GET + true + false + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/xdocs/demos/JDBC-Pre-Post-Processor.jmx b/ApacheJmeter/xdocs/demos/JDBC-Pre-Post-Processor.jmx new file mode 100644 index 0000000..5cdcb40 --- /dev/null +++ b/ApacheJmeter/xdocs/demos/JDBC-Pre-Post-Processor.jmx @@ -0,0 +1,445 @@ + + + + + Execute a series of concurrent valuations + false + true + + + + CalculateFees + 1 + = + + + CalculatePerformanceDetails + 1 + = + + + DriverURL + jdbc:jtds:sqlserver: + = + + + DatabasePort + 1433 + = + + + UseMiddleTierValuationEngine + 0 + = + + + MiddleTierRequestTimeout + 500000 + = + + + PricePropagationMode + 2 + = + + + + + + + + + + PCOQuality + 5 + = + + + ValueDate + 2011-07-21 + = + + + ReportingDate + 2011-07-21 12:30:08.337 + = + + + + + + + + Database + HSPAD_MI_440_SSD + = + + + DatabaseHost + GAIA + = + + + DatabaseUser + sa + = + + + DatabasePassword + sa2008 + = + + + + + + + + Pfo_1 + 1548 + = + + + Pfo_2 + 1611 + = + + + Pfo_3 + 1613 + = + + + CutOff Nr 11249, 2011-07-2 / 2011-07-21 12:30:08.337 / DailyNAV Estimate / Within Price Cut-Off + + + + false + + 5000 + + ${DriverURL}//${DatabaseHost}:${DatabasePort}/${Database} + net.sourceforge.jtds.jdbc.Driver + true + ${DatabasePassword} + 25 + 10000 + 60000 + ${DatabaseUser} + 4096 + Connect to local HSPAD_Demo_CO and set its isolation mode to SNAPSHOT (4096) and disable auto commit. + + + + continue + + false + 3 + + 3 + 0 + 1316530469000 + 1316530469000 + false + + + + + + + WorkBench + Concurrent Valuation Test Plan + PCO Valuation + + + + + + continue + + false + 1 + + 1 + 1 + 1320821253000 + 1320821253000 + false + + + + + + 1 + 0 + + + + + + UPDATE T_SettingGlobal SET UseMiddleTierValuationEngine=?, MiddleTierRequestTimeout=? + ${UseMiddleTierValuationEngine}, ${MiddleTierRequestTimeout} + BIT, INTEGER + Prepared Update Statement + + + + + + + Commit + + + + + + + + + + 1 + 0 + 0 + + + + + Update Statement + DBCC DROPCLEANBUFFERS + + + + + + + + + Update Statement + DBCC FREEPROCCACHE + + + + + + + + + + + + true + + + + + Update Statement + BEGIN TRAN COMMIT TRAN + + + + + false + + + + + Callable Statement + PfoVal_Recalculate ?, ?, 1 + ${Pfo_1}, ${PfoValInstance} + INTEGER, INTEGER + + + true + + + + groovy + + + import groovy.sql.Sql +import org.apache.jmeter.protocol.jdbc.config.DataSourceElement +try { + // build Pfo List + println("Building Portfolio List") + def pfoList = "<PfoList>" + def pfoNr = 1 + def pfo = vars.get("Pfo_" + pfoNr) + while(pfo != null) { + println("Pfo: $pfo"); + pfoList = pfoList + "<Pfo ID='$pfo' EmptyValuation='true' PropagatePrice='true'/>" + pfoNr++ + pfo = vars.get("Pfo_" + pfoNr) + } + pfoList = pfoList + "</PfoList>" + vars.put("PfoListXML", pfoList) +} catch (Exception e) { + println(e.toString()); +} + + + + + CreatePriceCutOff ?, ?, ?, ?, ?, ?, ?, ? + ${__threadNum},${ValueDate},${PCOQuality},${ReportingDate},]NULL[,${PCO},${PfoListXML},${PricePropagationMode} + VARCHAR, DATE, INTEGER,TIMESTAMP,INTEGER,OUT INTEGER,CLOB,INTEGER + Callable Statement + + + + + + + Prepared Select Statement + select Nr from PfoValInstance where Pfo=? AND PriceCutOff=? + ${Pfo_1},${PCO} + INTEGER,INTEGER + PfoValInstance + + + + + + DeletePriceCutOff ? + ${PCO} + INTEGER + Callable Statement + + + + + + + ${JMeterThread.last_sample_ok} + false + + + + + Commit + + + + + + Commit the transaction of the valuation + false + + + + + ${JMeterThread.last_sample_ok}==false + false + + + + + Rollback + + + + + + false + + + + + + + false + + saveConfig + + + false + true + false + + false + false + true + false + false + false + false + false + false + true + false + false + false + false + false + 0 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + diff --git a/ApacheJmeter/xdocs/demos/JMSPointToPoint.jmx b/ApacheJmeter/xdocs/demos/JMSPointToPoint.jmx new file mode 100644 index 0000000..21e49bc --- /dev/null +++ b/ApacheJmeter/xdocs/demos/JMSPointToPoint.jmx @@ -0,0 +1,103 @@ + + + + + + + + false + false + + + + + + 1115386407000 + + + 5 + false + + false + 4 + + 1115386407000 + continue + 5 + + + + + + + + + + = + tcp://localhost:61616 + brokerURL + + + = + example.MyQueue + queue.MyQueue + + + = + example.Q.REQ + queue.Q.REQ + + + = + example.Q.RPL + queue.Q.RPL + + + + false + Q.RPL + 5000 + Q.REQ + + ConnectionFactory + org.activemq.jndi.ActiveMQInitialContextFactory + <msg>test</msg> + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + + + + + + + + + + diff --git a/ApacheJmeter/xdocs/demos/LoopTestPlan.jmx b/ApacheJmeter/xdocs/demos/LoopTestPlan.jmx new file mode 100644 index 0000000..5ec16e6 --- /dev/null +++ b/ApacheJmeter/xdocs/demos/LoopTestPlan.jmx @@ -0,0 +1,124 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836533000 + 1211836533000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + + + + + + + + + http + + / + GET + true + false + false + false + + + + false + + + + + true + 5 + + + + + + + + + http + + /site/news.html + GET + true + false + false + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + loop-test.dat + + + + + + diff --git a/ApacheJmeter/xdocs/demos/OnceOnlyTestPlan.jmx b/ApacheJmeter/xdocs/demos/OnceOnlyTestPlan.jmx new file mode 100644 index 0000000..1715a67 --- /dev/null +++ b/ApacheJmeter/xdocs/demos/OnceOnlyTestPlan.jmx @@ -0,0 +1,132 @@ + + + + + + + + = + jakarta.apache.org + server + + + = + jakarta.apache.org + server + + + + + false + false + + + + + + 0 + + false + 3 + + 0 + 2 + false + 0 + continue + + + + + + + + + + + + + + + + + + + + + + ${server} + + + + + GET + true + false + true + false + + + + false + + + + + + + + + + + + + + GET + true + false + true + false + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/xdocs/demos/ProxyServerTestPlan.jmx b/ApacheJmeter/xdocs/demos/ProxyServerTestPlan.jmx new file mode 100644 index 0000000..7fe6511 --- /dev/null +++ b/ApacheJmeter/xdocs/demos/ProxyServerTestPlan.jmx @@ -0,0 +1,27 @@ + + + + + + + 8080 + + + true + 0 + false + 0 + false + true + true + false + false + false + + + + + + + + diff --git a/ApacheJmeter/xdocs/demos/SimpleTestPlan.jmx b/ApacheJmeter/xdocs/demos/SimpleTestPlan.jmx new file mode 100644 index 0000000..a10f97e --- /dev/null +++ b/ApacheJmeter/xdocs/demos/SimpleTestPlan.jmx @@ -0,0 +1,166 @@ + + + + + false + + + + + false + + + + + + + false + 1 + + 1 + 0 + 1211836583000 + 1211836583000 + false + continue + + + + + + + + + jakarta.apache.org + + http + + / + + + + + + + + + + + http + + /ant/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /ant/antnews.html + GET + true + false + false + false + + + + false + + + + + + + + + + + + + http + + /log4j/index.html + GET + true + false + false + false + + + + false + + + + + + + + + + http + + /log4j/docs/history.html + GET + true + false + false + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + simple-test.dat + + + + + + diff --git a/ApacheJmeter/xdocs/demos/URLRewritingExample.jmx b/ApacheJmeter/xdocs/demos/URLRewritingExample.jmx new file mode 100644 index 0000000..be60b1e --- /dev/null +++ b/ApacheJmeter/xdocs/demos/URLRewritingExample.jmx @@ -0,0 +1,142 @@ + + + + + + + + + false + false + + + + + 1200525828000 + + + 1 + false + + false + -1 + + 1200525828000 + continue + 0 + + + + + + + my.server.com + + + + / + GET + true + false + true + false + + + + false + + + + + + + false + false + SESSION_ID + false + true + + + + + + + = + user + true + username + true + + + = + password + true + password + true + + + + my.server.com + 80 + http + + /main.jsp + POST + true + false + true + false + + + + false + + + + + + + + my.server.com + + http + + /something_interesting.jsp + GET + true + false + true + false + + + + false + + + + + + + + my.server.com + + http + + /another.jsp + POST + true + false + true + false + + + + false + + + + + + + + diff --git a/ApacheJmeter/xdocs/demos/forEachTestPlan.jmx b/ApacheJmeter/xdocs/demos/forEachTestPlan.jmx new file mode 100644 index 0000000..4dacd01 --- /dev/null +++ b/ApacheJmeter/xdocs/demos/forEachTestPlan.jmx @@ -0,0 +1,123 @@ + + + + + false + + + + + false + + + + + 1 + false + continue + 1076438592000 + + false + 1 + + 1 + + + 1076438592000 + + + + + + + localhost + 80 + + + / + GET + true + false + true + false + + + + false + + + + + inputVar + <a href="([^"]+)" + -1 + fout + $1$ + false + + + + + returnVar + inputVar + true + + + + + + + localhost + 80 + + + ${returnVar} + GET + true + false + true + false + + + + false + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + + diff --git a/ApacheJmeter/xdocs/download_jmeter.cgi b/ApacheJmeter/xdocs/download_jmeter.cgi new file mode 100644 index 0000000..5b64bb5 --- /dev/null +++ b/ApacheJmeter/xdocs/download_jmeter.cgi @@ -0,0 +1,7 @@ +#!/bin/sh +# Wrapper script around mirrors.cgi script +# (we must change to that directory in order for python to pick up the +# python includes correctly) +cd /www/www.apache.org/dyn/mirrors +/www/www.apache.org/dyn/mirrors/mirrors.cgi $* + \ No newline at end of file diff --git a/ApacheJmeter/xdocs/download_jmeter.xml b/ApacheJmeter/xdocs/download_jmeter.xml new file mode 100644 index 0000000..b74c7bb --- /dev/null +++ b/ApacheJmeter/xdocs/download_jmeter.xml @@ -0,0 +1,152 @@ + + + + +]> + + + + Downloads + + +
+

+ We recommend you use a mirror to download our release + builds, but you must verify the integrity of + the downloaded files using signatures downloaded from our main + distribution directories. Recent releases (48 hours) may not yet + be available from the mirrors. +

+ +

+ You are currently using [preferred]. If you + encounter a problem with this mirror, please select another + mirror. If all mirrors are failing, there are backup + mirrors (at the end of the mirrors list) that should be + available. +

+ [if-any logo][end] +

+ +
+

+ Other mirrors: + + +

+
+ +

+ The KEYS link links to the code signing keys used to sign the product. + The PGP link downloads the OpenPGP compatible signature from our main site. + The MD5 link downloads the checksum from the main site. +

+

+ For more information concerning Apache JMeter, see the Apache JMeter site. +

+

+ KEYS +

+
+
+ + + + + + + + + + + + +
apache-jmeter-&release;.tgzmd5pgp
apache-jmeter-&release;.zipmd5pgp
+
+ + + + + + + + + + + + +
apache-jmeter-&release;_src.tgzmd5pgp
apache-jmeter-&release;_src.zipmd5pgp
+
+
+
+

+ Older releases can be obtained from the archives. +

+ +
+
+

+ It is essential that you verify the integrity of the downloaded files using the PGP or MD5 signatures. + Please read Verifying Apache Software Foundation Releases for more information on why you should verify our releases. +

+

+ The PGP signatures can be verified using PGP or GPG. First download the KEYS as well as the asc signature file for the relevant distribution. + Make sure you get these files from the main distribution site, rather than from a mirror. + Then verify the signatures using +

+
+% pgpk -a KEYS
+% pgpv downloaded_file.asc
+or
+% pgp -ka KEYS
+% pgp downloaded_file.asc
+or
+% gpg --import KEYS
+% gpg --verify downloaded_file.asc
+
+

+ Alternatively, you can verify the MD5 signature on the files. + This is not very secure, and should only be used to check that the file has been downloaded successfully. +
+ A unix program called md5 or md5sum is included in many unix distributions. + It is also available as part of GNU Textutils. +
+ Windows users can get binary md5 programs from + here, + here, or + here. +

+
+ +
diff --git a/ApacheJmeter/xdocs/extending.xml b/ApacheJmeter/xdocs/extending.xml new file mode 100644 index 0000000..f3be4b3 --- /dev/null +++ b/ApacheJmeter/xdocs/extending.xml @@ -0,0 +1,152 @@ + + + + + Extending JMeter + + +
+Note to developers: JMeter is undergoing large changes. The following +description of JMeter's architecture will likely change in the near future. If you +would like your changes to work with an upcoming JMeter 1.6, please join our mailing +list, and we will work with you and your modifications. +

Customizing JMeter to suit your needs.

+

Extensible Interfaces

+

+There are five basic objects in JMeter which provide extensibility: + +

    +
  • Visualizers represent the sampling data which is recorded.
  • +
  • Timers specify the delay between samples.
  • +
  • SamplerControllers hold information about all the test cases to be sampled, +and overall information about how the test is conducted.
  • +
  • Samplers are the classes that actually do the sampling of a particular protocol.
  • +
  • TestSamples hold information about a particular test case to be sampled.
  • +
+

+

+ +

Visualizers

+The Visualizer interface exists in the org.apache.jmeter.visualizers package. +JMeter maintains an instance of each visualizer it is aware of for each thread group currently available to the user. +A visualizer provides a method of recording the data which JMeter generates. +A visualizer may represent the data graphically (GraphVisualizer), +persistently (FileVisualizer) or both (TBD). +The visualizer contains three methods: +
+
add(SampleResult result) adds data to the visualization.
+
+JMeter calls the add method to include new data in the visualizer. +The visualizer should add the data into its data representation. +
+
clear() clears all data in the visualizer currently
+
+JMeter calls clear when the user requests that all visualizers be cleared. When the clear method is called the visualizer should clear all data from its representation and re-initialize itself. +
+
getControlPanel() obtains the GUI for the visualizer
+
+JMeter calls getControlPanel at start up time to prepare the +visualizer for display. +
+
+ +

+

+

Timers

+Timers provide a framework for delaying in between samples. This is important in order to obtain a true balanced load on a function rather than a calm-STORM-calm-STORM-calm... pattern. Timers contain two methods: +
+
delay() wait for a Timer specific amount of time
+
JMeter calls this function prior to every sample. The Timer should wait for a period of time and then return.
+
set() prepare for sampling
+
JMeter calls this function prior to the begining of a test session. The timer should initialize itself, read any values from its UI and prepare for operation. +
+
+

+

+ +

SamplerControllers

+The sampler controller is by far the most complicated, but also the most powerful, interface in JMeter. It allows a user to customize what, where and when JMeter tests. It provides six methods: +
+
start()
+
+JMeter calls this immediatly prior to starting a test. It is most often use to disable the SamplerController's GUI. +
+
stop()
+
+JMeter calls this when a user requests a stop to a test. It is most often used to re-enable the SamplerController's GUI. +
+
getControlPanel() Get the GUI for the SamplerController
+
+JMeter calls this at start up to create the its GUI. This is how a user enters +information into the SamplerController. +
+
getName() Get the SamplerController's display name.
+
+JMeter uses this name in the list of SamplerControllers that it displays. +
+
getDefaultThreadGroups()
+
Gets the default list of threadgroups.
+
getSampleThreads(String threadGroup,int numThreads)
+
When the user hits start, use this method to get all the JMeterThread objects you want for a threadgroup. Each JMeterThread +object implements Runnable and it is used to sample the test entries. +
+
+

+

+ +

Samplers

+ Samplers are simple - they are the objects that know the protocol of that which + you wish to sample. The HTTPSampler knows how to request a URL from a web server, for + instance. + + The interface for Sampler is as follows: +
+
public SampleResult sample(Entry e)
+
JMeterThread implementations will loop through all the test samples given + to them, and call the sample method on the Sampler (also given to them) for each + test entry. SampleResult is essentially a Map containing information about + the sampling (timing data is included, as well as the test response from the url). +
+
+

+

+ +

TestSample

+ TestSamples are objects that collect information from users about each test sample + the user wants to test. The TestSample object is also responsible for serving + up its test entries. The interface: +
+
public java.awt.Container getGUI()
+
Returns the GUI used to collect information from the user.
+
public Entry[] getEntries()
+
Gets a list of entries to be sampled from the TestSample object
+
public String[] getThreadGroups()
+
Get all the thread groups the user selected for this TestSample
+
public void setThreadGroups(String[] threadGroups)
+
Set the thread groups the user may choose from
+
public String getName()
+
Get a name for this TestSample
+
public void setName(String name)
+
Set the name for this TestSample
+
public void reset()
+
inform the test sample that a sampling run is starting
+
+

+
+ +
diff --git a/ApacheJmeter/xdocs/extending/JMeter Extension Scenario.xml b/ApacheJmeter/xdocs/extending/JMeter Extension Scenario.xml new file mode 100644 index 0000000..2a26d0c --- /dev/null +++ b/ApacheJmeter/xdocs/extending/JMeter Extension Scenario.xml @@ -0,0 +1,202 @@ + + + + + + + Extending JMeter + + + + + +
+

The purpose of this tutorial is to +describe the general steps involved in a JMeter extension scenerio. The +JMeter documentation describes what must be done on a microscopic level but does +not provide an overall idea of the process. That is the intent of this brief +article. The JMeter extension documentation should be consulted for details.

+

The high level procedure followed these steps. +

+
    +
  1. Planning
  2. +
  3. Code the configuration + object
  4. +
  5. Code the configuration + GUI object
  6. +
  7. Code the controller + object
  8. +
  9. Code the controller GUI + object
  10. +
  11. Code the Sampler object
  12. + +
  13. Install your + extension
  14. +
  15. Tips
+

Planning

I've found planning a JMeter extension to +involve three aspects: + +
    +
  1. What you want the sampler to do
  2. +
  3. What information is needed for the sampler to work
  4. +
  5. How the information is to be acquired from the user
+

You'll notice that the coding steps are somewhat backwards from the planning +steps (the sampler is coded last). The coding order was determined by which +classes could be tested earliest. The config/gui can be tested in isolation. The +controller can be tested with the config element. Neither of these requires a +Sampler to be present initially.

+

Configuration Object

The role of the configuration +object is to supply parameters to the Sampler that can vary from sample to +sample. In the case of the UrlConfig object, this would be information +such as the host name, port, GET or PUT and various parameters. +

The configuration object usually inherits from +org.apache.jmeter.config.AbstractConfigElement. It implements many of +the methods of org.apache.jmeter.gui.JMeterComponentModel that are +needed to effectively interact with JMeter. +

+
    +
  1. Constructor - In the constructor you should at least define the + name of your configuration element. This is best delegated to the base class's + setName method.
  2. +
  3. Property Name Strings - You should define a static final string for + each property you wish to define. These strings will serve as keys into a hash + table maintained by AbstractConfigElement. For example:
     public static final HOST_NAME = "hostname";
    would define a property + in the hash table for storing a host name.
  4. +
  5. Getters/Setters - For each property name you define in the previous + step, define the appropriate accessor methods. The implementation of these + accessors should usually delegate to AbstractConfigElement. For + example:
       public void setHostname(String hostname)
    +	{ setProperty(HOST_NAME, hostname); }
    +
    +	public String getHostname()
    +	{ return (String)getProperty(HOST_NAME); }
    +	
    Some accessor implementations may be more complex. See the + UrlConfig object for a more involved example.
  6. +
  7. String getClassLabel() - This is the label that will + display in the drop-down menu for adding your configuration element.
  8. +
  9. clone() - Your configuration element is expected to be + cloneable.
  10. +
  11. addConfigElement(ConfigElement) - A typical implementation + of this method looks like
       public void addConfigElement(ConfigElement config) {
    +		if (config instanceof MyConfig)
    +			updatePropertyIfAbsent((MyConfig)config);
    +	}
    where updatePropertyIfAbsent is handled by the super class.
  12. +
  13. getGuiClass - return the name of the this class's corresponding GUI class. +
+

+

Configuration GUI

Each configuration element you +define can have a companion GUI class. It helps to have a little knowledge of +Swing for this. Extend Swing's JPanel class and implement JMeter's +org.apache.jmeter.gui.ModelSupported interface. Remember that you can +review the UrlConfigGui example for hints if you get stuck. +

+
    +
  1. Data Members - You should possess at least two data members: a + reference to your partner configuration element and a reference to a + org.apache.jmeter.gui.NamePanel. You will likely have several others + depending on how sophisticated your GUI is.
  2. +
  3. Add Panels - The layout manager used for many of the panels used in + JMeter is org.apache.jmeter.gui.VerticalLayout. As the name implies, + it supports arranging other panels in a vertical fashion. You can define each + of your panels in a get method and add them to the configuration GUI + in a method called init. Once again, refer to + UrlConfigGui for an example.
  4. +
  5. Implement Listeners - Implement listeners for your GUI components. + The UrlConfigGui serves as a satisfactory example.
  6. +
  7. setModel - Use this method to have the model data member + set on your GUI instance. Run init from inside this method also.
  8. +
  9. updateGui - Use this method to set the GUI fields from the + model.
+ +

Generative Controller

A generative controller is a +controller that generates an Entry object for use by a Sampler. + +
    +
  1. createEntry - This method is the raison d'etre of the + org.apache.jmeter.control.SamplerController interface. The general + idea is to construct an Entry object and populate it with config + objects.
  2. +
  3. clone - After you perform you cloning duties, be sure to + pass the cloned instance to the standardCloneProc method so that base + class cloning activities can complete.
  4. +
  5. getClassLabel - This is the label displayed by the + drop-down menu for the controller.
  6. +
  7. getGuiClass - This should return a Class object for the associated GUI class.
+

Generative Controller GUI

A generative +controller GUI class should extend JPanel and implement +ModelSupported. If your controller GUI doesn't involve anything beyond +the configuration GUI, you might be able to get away with inheriting from the +configuration gui class you created a couple steps ago. If you do this, you need +to at least override the setModel method to make sure that the correct +model is set on the class. You'll be passed a controller object but you'll want +to extract the config element from the controller to be used as the model for +your base class (the config gui). +

Sampler

The sampler is responsible for actually +performing the work using the information provided in the configuration element. +The method of importance is
public SampleResult sample(Entry e)
It is here that you extract +configuration elements from the entry object you are passed. Then use these +configuration elements to perform the task you extension is suppose to do. +

Installation

Follow these steps to install your +extension. + +
    +
  1. Package the class files into a JAR file.
  2. +
  3. Place the JAR file into the ext subdirectory of the JMeter root + install directroy.
  4. +
  5. Edit the bin/jmeter.properties file of the JMeter installation. + Find the search_paths entry and add your JAR to the list. It should + look like
    search_paths=ApacheJMeter.jar;classes;../ext/YourJar.jar
  6. +
  7. Run JMeter and watch the magic.
+ +

Tips

+
    +
  1. You might consider using log4j as your logging utility since that's what + JMeter uses. It's helpful for figuring out what's going on. Not all JMeter + classes have been fully outfitted with logging statements. If things get + nasty, you might have to add your own to JMeter and recompile it to see what + is happening. +

    If you do decide to use log4j and you set the priority (or level, as it + will soon be called) to debug, you will probably see way more than you need to + know. You can filter the JMeter stuff by making the following modifications to + log4j.conf in the JMeter's bin directory. The bold + text is added/modified

    	# Set the appenders for the categories
    +	log4j.rootCategory=info,Root_Appender
    +	log4j.category.com.myfirm.jmeter=debug,
    +	log4j.category.org.apache.jmeter.control=debug
    +	log4j.category.org.apache.jmeter.gui.tree.NonGuiTree=INFO,File_Appender
    +	
    Note that the root (default) debugging has been set to info. + This eliminates most log4j output from JMeter. The new line specifies the name + of the package containing JMeter extensions. (com.yourfirm.jmeter) in + this example. Note that it is not necessary to specify a particular class + name. Also, note that no appenders are specified - just the trailing + comma. If you specify Root_Appender here you'll see your message appear twice + (because you specified the same appender twice). All you really want to do is + override the priority.

  2. +
  3. Implement clone carefully. This is an often overlooked method for + a lot of folks. JMeter makes heavy use of cloning. Check out some of the + JMeter coniguration elements and controllers to see how they do it. Notice + that in most cases, a special method is usually invoked to perform base class + cloning activities. For configuration elements, this is + configureClone. For controllers, it is standardCloneProc. +
+
+
+ +
diff --git a/ApacheJmeter/xdocs/extending/index.xml b/ApacheJmeter/xdocs/extending/index.xml new file mode 100644 index 0000000..bf90a86 --- /dev/null +++ b/ApacheJmeter/xdocs/extending/index.xml @@ -0,0 +1,861 @@ + + + + + + + Extending JMeter + + + + + +
+ + + +

Extending JMeter

: + +

There are several ways to extend JMeter and add functionality. JMeter is designed + +to make this task easier. + +

+ + + +
+ + + +

Creating your own Timer

+ +

The timer interface:

+ +
+
+	  public long delay();
+
+
+ +

Not too complicated. Your delay method must, each time it is called, return a + +long representing the number of milliseconds to delay. The constant timer returns the + +same number every time it's called. A random timer returns a different number each time. + +

+ +
+ +
+ +

Creating your own SampleListener

+ +

The SampleListener interface:

+ +
+
+	  public void sampleOccurred(SampleEvent e);
+
+	  public void sampleStarted(SampleEvent e);
+
+	  public void sampleStopped(SampleEvent e);
+
+
+ +

sampleOccurred is the method called when a sample is completed, and the data has been + +collected. The SampleEvent object should contain all the information gathered + +from the sample. If your sample listener is primarily concerned with collecting the + +data from a test run, you can implement this method - the other two are for other purposes and + +can be ignored (though the methods have to be there for your class to compile). + +

+ +

sampleStarted and sampleStopped are used to indicate the state of the sampling thread. + +This is useful for visualizers that show the user the state of all running threads + +(ie, they are running and waiting for response, or they're stopped and waiting + +to begin again). + +

+ +
+ +
+ +

Creating your own Config Element

+ +

The ConfigElement interface:

+ +
+
+	  public void addConfigElement(ConfigElement config);
+
+	  public boolean expectsModification();
+
+	  public Object clone();
+
+
+ +

The ConfigElement interface is sparse. All ConfigElements are expected to implement + +a public clone() method. The reason for this is that config elements will be cloned + +for each different sampling thread, and most will be cloned for each sample.

+ +

If your config element expects to be modified in the process of a test run, + +and you want those modifications to carry over from sample to sample (as in + +a cookie manager - you want to save all cookies that gets set throughout + +the test), then return true for the expectsModification() method. Your config element will not be + +cloned for each sample. If your config elements are more static in nature, + +return false. If in doubt, return false.

+ +

addConfigElement() is required so that config elements can be layered. For + +instance, let's say a user creates a URL entry that contains default values - + +they might use this to specify a server. Then, all their test samples configure + +individual test cases, but leave out the server field. This information is combined + +via the addConfigElement() method. Your custom config elements should do the right + +thing when this method is called. Normally, this involves ignoring such calls unless + +the passed in ConfigElement is of the same type as yours, and then only merging in + +values that are not already set in the object receiving the call (ie you probably + +don't want to overwrite any values). + +

+ +

You may have noticed there's no specification on how to get the config information + +out of a ConfigElement. This raises the question, who is going to use it? + +At the end of the line, there will be a Sampler that will need the information held + +in your config element. The sampler that uses your config element needs to know more + +about the class than the rest of JMeter - that information is not part of this interface. + +

+ +

If at all possible, extend AbstractConfigElement when creating your own. By doing so, + +and by following some simple rules, you will get cloning and saving to XML of your + +config element for free (as in, you don't have to do anything!). AbstractConfigElement + +stores all its values in a Map, and provides getProperty and putProperty methods. Your + +config element can provide getXXX() and setXXX() methods, but these should delegate + +to getProperty() and setProperty(), probably using static Strings as keys in the Map. + +
You can store any type of object, provided the objects are clonable and Saveable + +(Strings, Integer, Long, Double, Float are all good in this regard). + +

+ +

One caveat - if your config element has been restored from file, all the values + +held in the Map will be String objects (except for elements that implement Saveable + +on their own), and you may have to do casting and parsing. Example: an Integer will + +have to be converted from a String to an int, so your getXXX() method should check + +for this possibility to avoid exceptions. + +

+ +
+ +
+ +

Creating your own logic SamplerController

+ +

The SamplerController interface looks as follows:

+ +
+
+	  Entry nextEntry();
+
+	  Collection getListeners();
+
+	  void addSamplerController(SamplerController controller);
+
+	  void addConfigElement(ConfigElement config);
+
+	  Object clone();
+
+
+ +

Again, clone() is a method that must be implemented to all SamplerControllers to avoid + +contamination between sampling threads.

+ +

The nextEntry() method is the essential job of a SamplerController - to deliver + +Entry objects to be sampled. An Entry object encapsulates all the information needed + +by a Sampler to do its job. The nextEntry() method should work like an iterator and + +continuously return new Entry objects. + +

+ +

There are two boundary conditions that need to be handled. If the Controller has no + +more Entries to give, for the rest of the test, it should return null. Therefore, + +if your Controller has sub-controllers it is receiving Entries from, it should remove + +them from its list of controllers to get Entries from. The other condition is when + +your controller reaches the end of its list of Entries, and it needs to start over + +from the beginning. The parent Controller needs to know this so that it can move + +on to its next controller in its list. Therefore, at the end of each iteration, + +your SamplerController needs to return a CycleEntry object instead of a normal Entry. + +Conversely, this means that if your Controller receives a CycleEntry object, it should + +move on to the next Controller in its list.

+ +

A logic controller does not generate Entries on its own, but simply regulates + +the flow of Entries from its sub-controllers. A logic controller might provide + +looping logic, or it might modify the Entries that pass through it, or whatever. + +GenericController provides an implementation that does absolutely nothing but + +pass Entries on from its sub-controllers. This class is useful both for reference + +purposes and to extend, since it provides a lot of methods you're likely to find + +useful + +

+ +

getListeners() is an odd member of this Class. It's there to serve those who + +want their controller to receive sample data. This would be useful for a controller + +that modified Entry objects based on previous sample results (like an HTML spider + +that dynamically reacted to previously sampled webpages for links and forms). The + +responsibility of the controller implementer is to collect all potential listeners + +from the sub-controller list, and add themselves if desired. Most SamplerControllers + +that extend GenericController don't have to do anything.

+ +

addSamplerController(SamplerController controller) is the method used to + +add sub controllers to your SamplerController.

+ +

addConfigElement(ConfigElement config) Your SamplerController should also + +be capable of holding configuration elements and adding them to Entries as they + +pass through your controller. Again, see GenericController for reference. Essentially, + +all Entry objects that get returned by nextEntry() are handed all the ConfigElements + +of the controller. + +

+ +
+ +
+ +

Creating your own test sample SamplerController

+ +

A SamplerController that generates Entry objects is just like a logic controller + +except that it creates its own Entry objects instead of gathering them from + +sub-controllers (although, to be fully correct, your test sample SamplerController + +should handle both possibilities). Your test sample SamplerController can also + +benefit from extending GenericController. By doing so, most of your cloning and + +saving needs are handled (but probably not entirely). See HttpTestSample as + +reference.

+ +
+ +
+ +

Creating your own Sampler

+ +

The Sampler interface:

+ +
+
+	  public SampleResult sample(Entry e)
+
+
+ +

Your Sampler has two responsibilities. Of lesser importance, it should do whatever + +it is you want to do, given an Entry object that hopefully contains information + +about what is to be sampled. Of greater importance, your sampler should return + +a SampleResult object that holds information about the sampling. Information such + +as how long the sample took, the text response from the sample (if appropriate), and + +a string that describes the location of what was sampled. The SampleResult interface + +is essentially a Map with public static Strings as keys.

+ +
+ +
+ +

Making your custom elements play nice as a JMeter UI component

+ +

In order to take part in the JMeter UI, your component needs to implement the + +JMeterComponentModel interface:

+ +
+
+	  Class getGuiClass();
+
+	  public String getName();
+
+	  public void setName(String name);
+
+	  public Collection getAddList();
+
+	  public String getClassLabel();
+
+	  public void uncompile();
+
+
+ +

Most of this stuff is easy, boring, and tedious. getName(), setName() is a simple + +String property that is the name of the object. getClassLabel() should return + +a String that describes the class. This string will be displayed to the user and + +so should be short but meaningful. getGuiClass() should return a Class object for + +the class that will be used as a GUI component. This class should be a subclass + +of java.awt.Container, and preferably a subclass of javax.swing.JComponent.

+ +

getAddList() should return a list of either Strings or JMenus. These Strings + +represent the Classes that can be added to your SamplerController. Each String + +should correspond to the target class's getClassLabel() String. MenuFactory is + +a class that will return some preset menu lists (such as all available SamplerControllers, + +all available ConfigElements, etc).

+ +

uncompile() is a cleanup method used between sampling runs. When the user + +hits "Start", JMeter "compiles" the objects in the tree. Child nodes are added + +to their parent objects recursively until there is one TestPlan object, which is + +then submitted for testing. Afterward, these elements have to un-added from their + +parent objects, or uncompiled. To uncompile your class, simply clear all your + +data structures that are holding sub-elements. For your SamplerController, this + +will be the list of sub-controllers and the list of ConfigElements.

+ +

That's it, except for your GUI class. If your SamplerController has no + +configuration needs, just return org.apache.jmeter.gui.NamePanel, and the user will + +at least be able to change the name of your component. Otherwise, create a gui class + +that implements the ModelSupported interface:

+ +
+
+	  void setModel(Object model);
+
+	  public void updateGui();
+
+
+ +

setModel() is used to hand your JMeterModelComponent class to the GUI class when + +it is instantiated. It is your responsibility for providing the means by which + +the Gui class updates the values in the model class. For updating in the other + +direction, there is updateGui(), which the model class can call if necessary. + +Note, normally, this call is made for you automatically whenever the Gui is brought + +to the screen. If you are creating a Visualizer, then you may need to use updateGui(). + +For reference, refer to UrlConfigGui (in org.apache.jmeter.protocol.http.config.gui).

+ +

If you have done all this correctly, there's just one more step. If you compile + +your classes into the ApacheJMeter.jar file, then you're done. Your classes will + +be automatically found and used. Otherwise, you will need to modify jmeter.properties. + +The search_paths property should be modified to include the path where your + +classes are. This does not obviate the need for your classes to be in the JVM's + +CLASSPATH - it is an additional requirement. Otherwise, your classes will not be + +detected, and the Gui will not make them available to the user.

+ +
+ +
+ +

Making your custom elements saveable and loadable from within JMeter

+ +

The Saveable interface has just one method:

+ +
+
+	  public Class getTagHandlerClass()
+
+
+ +

This method simply returns the Class object that represents the Class that handles + +the saving and loading of your component.

+ +

To write this SaveHandler, make a class that extends TagHandler + +(from org.apache.jmeter.save.xml). Note, if your component extends AbstractConfigElement, + +it is already fully Saveable - provided you only have information stored in + +the Map from AbstractConfigElement.

+ +

To write your own TagHandler, you will have to implement the following methods:

+ +
+
+	  public abstract void setAtts(Attributes atts) throws Exception
+
+	  public String getPrimaryTagName()
+
+	  public void save(Saveable objectToSave,Writer out) throws IOException
+
+
+ +

getPrimaryTagName() should return the String that is the XML tagname that your + +class handles. When you save your object, it should all be contained within an + +XML tag of the same name. This will ensure that when JMeter's parser hits that tag, + +your class will be called upon to handle the data.

+ +

setAtts(Attributes atts) is called when the parser first hits your tag. + +If this primary tag has any attributes, this method represents your chance to save + +the information.

+ +

save(Saveable objectToSave,Writer out) - when the user selects "Save", + +JMeter will call this method and hand the Saveable object to be saved (it will be + +the object that specified your TagHandler as the class responsible for saving it). + +This method should use the given Writer object to print all the XML necessary to + +save the current state of the objectToSave.

+ +

There's more you have to do to handle creating a new Object when JMeter parses + +an XML file. However, there's no standard interface you need to implement, but rather, + +JMeter uses reflection to generate method calls into your class. When JMeter hits + +a tag that corresponds to your PrimaryTagName, an instance of your TagHandler will + +be created, and its setAtts() method will get called. Thereafter, methods are called + +depending on subsequent tags and character data. For every tag, JMeter calls + +<tag-name>TagStart(Attributes atts), and for every end tag, JMeter calls + +<tag-name>TagEnd().

+ +

Additionally, JMeter will call a method that corresponds to all tags that are + +current. So, for instance, if JMeter runs into a tag name "foo", then + +foo(Attributes atts) will be called. If JMeter then parses character data, + +then foo(String data) will be called. If JMeter parses a tag within foo, called + +"nestedFoo", then JMeter will call foo_nestedFoo(Attributes atts) and + +foo_nestedFoo(String data). And so on. + +

+ +

An annotated example:

+ +
+
+public class AbstractConfigElementHandler extends TagHandler
+
+{
+
+	private AbstractConfigElement config;
+
+	private String currentProperty;
+
+
+
+	public AbstractConfigElementHandler()
+
+	{
+
+	}
+
+
+
+	/**
+
+	 * Returns the AbstractConfigElement object parsed from the XML.  This method
+
+	 * is required to fulfill the SaveHandler interface.  It is used by the XML
+
+	 * routines to gather all the saved objects.
+
+	 */
+
+	public Object getModel()
+
+	{
+
+		return config;
+
+	}
+
+
+
+	/**
+
+	 * This is called when a tag is first encountered for this handler class to handle.
+
+	 * The attributes of the tag are passed, and the SaveHandler object is expected
+
+	 * to instantiate a new object.
+
+	 */
+
+	public void setAtts(Attributes atts) throws Exception
+
+	{
+
+		String className = atts.getValue("type");
+
+		config = (AbstractConfigElement)Class.forName(className).newInstance();
+
+	}
+
+
+
+	/**
+
+	 * Called by reflection when a <property> tag is encountered.  Again, the
+
+	 * attributes are passed.
+
+	 */
+
+	public void property(Attributes atts)
+
+	{
+
+		currentProperty = atts.getValue("name");
+
+	}
+
+
+
+	/**
+
+	 * Called by reflection when text between the begin and end <property>
+
+	 * tag is encountered.
+
+	 */
+
+	public void property(String data)
+
+	{
+
+
+
+		if(data != null && data.trim().length() > 0)
+
+		{
+
+			config.putProperty(currentProperty,data);
+
+			currentProperty = null;
+
+		}
+
+	}
+
+
+
+	/**
+
+	 * Called by reflection when the <property> tag is ended.
+
+	 */
+
+	public void propertyTagEnd()
+
+	{
+
+		// Here's a tricky bit.  See below for explanation.
+
+		List children = xmlParent.takeChildObjects(this);
+
+		if(children.size() == 1)
+
+		{
+
+			config.putProperty(currentProperty,((TagHandler)children.get(0)).getModel());
+
+		}
+
+	}
+
+
+
+
+
+	  /**
+
+	* Gets the tag name that will trigger the use of this object's TagHandler.
+
+	*/
+
+	public String getPrimaryTagName()
+
+	{
+
+		return "ConfigElement";
+
+	}
+
+
+
+  /**
+
+	* Tells the object to save itself to the given output stream.
+
+	*/
+
+	public void save(Saveable obj,Writer out) throws IOException
+
+	{
+
+		AbstractConfigElement saved = (AbstractConfigElement)obj;
+
+		out.write("<ConfigElement type=\"");
+
+		out.write(saved.getClass().getName());
+
+		out.write("\">\n");
+
+		Iterator iter = saved.getPropertyNames().iterator();
+
+		while (iter.hasNext())
+
+		{
+
+			String key = (String)iter.next();
+
+			Object value = saved.getProperty(key);
+
+			writeProperty(out,key,value);
+
+		}
+
+		out.write(</ConfigElement>");
+
+	}
+
+
+
+	/**
+
+	 * Routine to write each property to xml.
+
+	 */
+
+	private void writeProperty(Writer out,String key,Object value) throws IOException
+
+	{
+
+		out.write("<property name=\"");
+
+		out.write(key);
+
+		out.write("\">\n");
+
+		JMeterHandler.writeObject(value,out);
+
+		out.write("\n</property>\n");
+
+	}
+
+
+ +

+ +In the propertyTagEnd() method, takeChildObjects() is called on the xmlParent + +instance variable. xmlParent is inherited from TagHandler - the DocumentHandler + +object that is running the show. xmlParent takes an XML file that represents a portion of + +the test configuration tree, and recreates a tree-like data structure. When it is + +done, it will convert its tree-like data structure into the test configuration tree + +structure. + +

+ +

However, sometimes, a tree element has sub objects that you do not want represented + +in the tree - rather, they are part of your object. But, they may + +be complicated enough to warrant their own SaveHandler class, and thus, the xmlParent + +picks them up as part of its tree. When the tag is done, and you know that there are + +child objects you want to grab, you can call the takeChildObjects() method and get a + +List object containing them all. This will remove them from the tree, and you can add + +them to your object that you're creating. + +

+ +

+ +UrlConfig is good example. It extends AbstractConfigElement, so it uses exactly the + +code above to save and reload itself from XML. However, one of the pieces of data + +that UrlConfig stores is an Arguments object. Arguments is too complicated to save + +to file as a simple string, so it has its own Handler object (ArgumentsHandler). In + +the above code, when the call to JMeterHandler.writeObject(value,out) is made, the + +writeObject method detects whether the object implements Saveable, and if so, calls + +the object's SaveHandler class to deal with it. This means, however, that when + +reading that XML file, the Argument object will show up as a separate entity in + +the data tree, whereas it originally was just part of the data of the UrlConfig + +object. In order to preserve that relationship, it's necessary for the + +AbstractConfigElementHandler to check after each property tag is done for child + +objects in the tree, and take them for its own use. + +

+ +

+ +Study the other SaveHandler objects and the TagHandler class to learn more + +about how saving is accomplished. Once you understand the design, writing your + +own SaveHandler is very easy. + +

+ + + +
+ + + +
\ No newline at end of file diff --git a/ApacheJmeter/xdocs/extending/jmeter_tutorial_mike.sxw b/ApacheJmeter/xdocs/extending/jmeter_tutorial_mike.sxw new file mode 100644 index 0000000000000000000000000000000000000000..113c8c416986b7793b355ddf382c1c02e993f720 GIT binary patch literal 58860 zcmZs?1yodD*ggsfI)K6qAR#$)mrCc*peS9^-GU&E0s{;^v=SoakkTNHWQzWce_EnG4z%#TB&n&3jdfG8dCJLUrdKF;>e z)*e1Dt<7J)bh5WJ_po<$;d||3#rw+Bh1c8JiPy{B-ox7c`TuK->n{{x7m-+4m=6yy z#&dg14^MaNS9}8e|JRS8fY9G>f{Oei4}}F3g+)YoU%J>vJXcq`NAQ3ExbL2dvb-i1 z77jV^*#qGKe~}j{rUw4Oex<1d!}>f(vjKd;wUvD;i-lDYM|f$D2YhF+RMvcoh2_hN zh4mJWg>?@6>FqiemM1?J)}|R2me@NiEQ*&I4H^*1V#1 z(~CbUbCtpe9`xf*IR&B(vkmZjb@Mc);uUdA85QO(1tPk2ac@)4*W1K#Qx0ERL~kcg zzg;=qF(>ZmG|@#Sm-kMIw}gD7dUD%oXC+l)fWQ3oK_Y)ebUCe4H@+;}gz?0L&cwv* zQR&X+=c4Ntv4)e3lhUlAnIr%6Lf`qdi^~4~{`s5cXPFrpmlQX9;+?Jj!#LGgJTipC zIN9W2kk7G~G)ExaVhH(1JiDOpI6ruXMfW1#)wV@Mfd7co16RI$N$3r6XQQuvJA}gv zTYh@yjUBe(uX1uqIrP`wz70Pf88|W(xX#SL3B1o7{>gXMi!@ z>uDr0{HROFPf&Q2`BZ?95e~}6v%^+|Hr;0ADZdT-&~#H%R)52B01iC88@FzcZD=M< zh#opk7tZdmJuJ@FKqY$evR0A(9)XlI5ew3z zjIy@CPNX8eM4CdV1Nr3rKaBb8PuncaAB0W%_b@%H6)rb|%HQQ9O?0Ph$H4}d@x$LFNcDcLw zpy|uqht2jvM2nwZeXzlTvBvhLNB#QHv&>>K7qV_kbE$vAaWB$JB%vxuM9A$!V?U~&BseHVwhm7Jja{kwYt!)oX+RZ za_fi>l+Gnmrhi2)0!yPEsEFbAJjv#z&CL`1Ok3C_8$k0sqdb)E6Yas%#S;4<9-g-! zLJKM)a${527wzPwO#F5wzhA?>l+;|c`58GLGDgTCe3V`2QNlge-T9WZs75dLq1v|^ zD@RKG{f`z?`qKkOa0+?Ak>Jm|hGy4t&D9XVh-n6JfSooF1AEB; z1BELSf}tK5vj;~Yjcjk$S6iEww019!=e>8OFGHkHLt0OxS`mkGCAuYeFW`IS2#nug zyM8k1>BkRzXYnKJH0%1{ng1ebo^k~@g62yUSao0wr@HMc5C7{7m-BbA^tP#~=WF{* zHm}*yO}$nzal@~1H+wU4+JE9cF8v!7 z1jjvQ{{=yS-LBovLhDSgr#pt4)+Ppr#-#sb1^zakKOU)ja}Z8{%?gP^&_kjgNkz0_ zNxTwJFo)AX-+|uez74PEeshIt{qx@T!~1;1Uitk4xhGHg1&56)rfkTOAp8i0b_nC( z5TEn~6AqnL)oE3DS24Z!)?}1^o*cTy(W|#aaXTdiu6nV1Qi*HulAub%q+m$#t#Q(^gEa70raBE7FAup(>>ZFE^an%iRbTRqCr`h}HPqE(I z<*G0jT-m+)w_V@_NK{l~`HNdA7wqOm7@ip%{xjPz)nfHNn-u(EhI$CHRigE>-^)n) zwW!9bZQ$~*Xm+m$+5XYZVXE{+z3KI5R~~Ovl!kH`jcO41&slN5k*kxsJT9y=8?n0< zMSro|SsZuOHXk^aKDX>*_4sc6>kb5LTtHm7u@6g0T9pz?^%)~gc5Vg4Hicre}83>7qR8!uZZBzl{=03MoaJQN z4Kvx$hf}u2i38y)9>%;!yjV-2kOUuo^UsoY1^loNZ?uOOiTMI$j!q}}>ie&|l)4_? z^xv94{p{7O)>`dCMzXeYi`a7Ufh}3(=M&TX$9%BPd=Uv+d}%^R*8F`?z4wW<9e975 z{?GO0h3RR};zZVUOBpqz{B7u}0Vo&T=Dem&rikX8^azXj2s%rjZ)FAVkEY_%4Uj^X zPUgR?mEB~WTou!wb=S2VmD&LNM;zF2RyA9G{+Zan#c$(4`rz5cdLGZ1#1hZm!&X+# zfOF^h^VqDjjH0V);-(G`d-t5(ifQ(X!!hSl6utIDR$%$Gu;bh82aZY>!aFTLT)udA zl-JHuhFW<$6GL)5KhQ+NndJR%~b7db~o9Y?hD`^)vF(-O_KDY1;M)4m*E4~grqq%kScL4$fz4XvQ1s6?b8tCGyhVzS%#S8kIfK zTvPu|aXkB$HNTdgpJ?|%Sajos5U)nI%nYEGoKM~wPNNQmh5nHJGli?ngQpdpb zsab~(L<+&v;a_CK^wX8iO~#}67I|a4FIw+FL!JfS>xf4tzE*^8`QGRls<~zG{*3vE zKqv&-?tDFra{FALWXKP!@6$sk+!$Ek(q;OCK#7}MmeLfqH}8LToj1>4NFH!^_J^!8 zcf9=LdQsCaVFMb^<>3^-mM;oCdAVPC^DB|D z2}wV7?=rBv-NJPK_M`M8bQKs~@2aKGV8wVAe4uHTrm&IBE}A?pPB9`9jdbQcH}o@) zEis&*&RAn9z>D-=DQUqxpWTl;pd zpM+=Nnl2bwouJk6#9$z^reW8#P?oW?in9)7T6MNPUHDRAFAi9`vr`US`DrE>-hNv!bpg7}CVi%& zX_b1iD3S>SMn4`rHpR}g zi%Mo>m1)aK6qku^u29C(;p(GK@8m&oY0S1f zeup4>mnTa@bvuBI&2-`h7LL>_Gx3Bl_KsBMp6M_xFU!X-Emm`BmWeM9$Pmh0f}&x7 zePlhHx0UCIwH4Q0FFq&3p@p@q@VHXVq8%vRea+>e4ntAt#z&@Z0uSNrBqX8F-5uz| zGfm6*VU(dFT$C`72S^3pDWdkBN*@dJsEjYDSWx>1YvR!$MUdV)30x)|NeYUWMfi}n zv%~dP;dl&@aCxZsDd+3=`M?J76HqZ){Vs-fu-v1Coe#nq9#Z(Qw#RV0GqnM)r9BtP zP5a%x~a^w*8jH?^%y5E^Uns_|Gz7 zj&T@Klw9914p;$}gKTqh-SuA+&w6w1(|^m;RK!(^%6&Vmlx2cN>ydktaD@6PdI|`$ zf-mT3NC0^Impa0p#ASL919$tU$XS}S8PZI>uoG-l^F0~TrX&X~()twNaWwUh#wpLmB z?=1kt$pbG+Pa}VWM-cb!sgMLSyCT~_3++F{;m<)Iehc7$)YO6vx7z1C@kI3=hkw5su|qMJ6x)E)8PT9hwp}i zPVe%bvD9e_tB@R-?7)*h&JZbwioYI%+jsmO+Ub`u7vG+wG8up8$mJCqC>Ck>UEBQN ztva7^DQB`;f2MmD<@I89j3_P)9n;Pmja-0XT;rpY>n3;J&2wFf%{2PKO|O#ZxI-aW z*tGbFB{5P1VS8`qev8Ptwy!|cTK1TlX`muKwV0KiM?o{MJ9DQ1h0blvKEQelEkkm% zB%_fz~07i@jGEYK?~^6_o# zaH%A6GcwKS^pCna0qov{>FZdVeBxl+3+RK9#+H(Pqke#dT zYv+fl8VY;HBpd2@HfPOzyS=F7jd^Cy__yi=ippVYxR#E8KytH##1*InjGHhM;6M2x z#3|ZUmYMrmY;s>-nTpzgrI9Z-!1iB2!bz<4VMgU~F~i&yaKv~iq2gt?Uq1u%h%f&F z$MaDk9?^+LHbz{}iTadqh&y~X+i6X?m*b~TN1vZyyCx4!B+|Wp!4Lbwcv6YQf;z7B$GiGW7b>GN)9@(j9yJ-eZ;a8=laR>O znw>Yz^Ai&vaa}$n1w#_HY0qC~+Dd5)G*$Gx1VDZaTQ#C34n#96| zI1z5Z9c_WTYwy+VSAclX= zQSDq{I0dKqW}w_!^SCzN^C^DV-le2gWE+U%C7!jI6$m=Kk^juS4_URXWnL(+e@wb+ zx)!FtqmsC-$7J;>{)uzol({S!egy1eo^$iEwwGq$WR2rD#gr3~+2atOm%#SMhd=( zv7l{cwA8QEg&~GXUL-8ohPMm-jzi=F?lvDGTQz%Zpcn_f|k$uVT=+U zQy%(p(#l6DTm}d%@PgSQ*46SukZV>}t+>t=+v{&k9tV55(uDYS%xdZMub=-7w=q{_ zjs4$&fclYoJ6N5ymY(yl84NrcmnchJaoVr1Iw_kiCs0#odR_A&14OCW1lisrcl|e+ zGo8>txDs?)>@ks(8jyHYAEBGL~Bj; z{{ts&dCs{BTK7-Y)w64`Ad3c*GnRrs>g9? z^MsIQ`tJx}cKdWC0IqOEl9GmmLP!E;`Xf1-^grqp>Lj||d^0Ga_KX>2)mO}l zm!cY&FUa?RM9Z13bSno=PEyf@Do{+m6=nt}t9BC6w#RnRp}kandkUe%_|M(mLiM!g zySjyUo4a?Ck2Kjtti{y6(z4hV80J2hh~*A~ka*9Czo;&G*B<}P@bTW%!w6lSNc^gA z>=;n^&jd{n$vV{({AABgBD|l%-);EB+-(ulXwsGaIRe0EM@py$1wYreO;DTVkVYw| zz}|J#nzI9$nqb~V$7y$?wBL7^+h#o~)nnOt&UC#TY(?)Bl-c-4$EyZs3KeS}wqx@0 zaV(F4?vsT9n^FQ=Kcboav=)sm9Zd!?;D*^7uZnm5r3 zl}HY3jW;%0FG>Tt;qJwHKX}n(ttoOrEv*0rfzK2zTKG=I`iw{5|kUnd`?L zu|93ztF5(c%V)yUxw*c@O4CreT)Aye1FE;VYn_o>MnGys8-Q3)m3#A4cX8&>hBuow zQ5-y2w0;c+i%Q9(EaIL0LXU1Q>TuY0*UIr={Yxy`x97y8plukieFc&X6T8(!;N(sm7rztWv}@THWTyJ#30@GYAoQE@uGcN zlvm#H+Hadjf)Bowh;r288T>_km32!NH}x3#1!57ry$15ZE#UX~y)jW#2_OANYe@x0SoS~+eri(^0 zb>hwp{rM#oJNC@^FWONy+j*?!?yx+wsvxFL)KG=-pe8^XjO#wG`yYIElExy+0$!aK8?%i1TlwgWStF zdO3s%APJg+XN~Vin-%$tjxqq;YEr{3WP?p0^XJFZ-2V=yTD8v58P8SnV@Ov*loEQ> zh1+p-_nQV8o!?y}A_@2*p-kwTKYsdxO*_XsF_Nsce^DfEU$f0d=-nZh$Lo_wTIXi1 z5JT|7z{h>kOa$f3;4)t8tLMcvIT?C&55{lKb=6ynZbFHm z0XWZ9<;rD4s?1kpm6_(V_Zh>K_>)hcsOQ%GeL8r87E9yR;~pX=xY}3+7Fj4j9kg(s zYd(7^MnKilC%jvLF`mB^Lmy`ejd4< zZUN@6yijbvwxEUOuqOPgU{;O{*v8mbl8Rgyw)Dh1+hFNA)@Ff}|B#Q|KUP6|i&U!$ z4yjzzEXZ*skkR=gL@tDnU<9x)3kpqa4y>k1_=lzA7U(|V0T<1V0QX;)#veGgQBfaR z`fdlXh$V$u)U3b@D%<+>e+VgPdXBrd@BuCR8ZMJnokCkIP%vTW`!5O1q$gzLG?tD4 zSr729vN9!Abp;=oZe-p6z~xWOC==hp-hu+1Pd`d_C(9yQ%VzETw_;W&kD)iKxga3l zMYKj5RMi0Z$s6kAg#8~dAd2SKB;Hxg&(;JqeFr%A8l3UvM5QQTv}+eTtLgRiAk$*U6#ElE*sc#yf(E>_F0+o*OqnxS7qt=?7PJ-i)iKB*952 zp^v+8BNpkDY*fos{zw2$rdG9XDHnb>hNxyzAy!^|c9Rp3Dk1>ht&@=cOjMT=I>Gb# zDH|>=OnoC%KR4xnsgl*0mq^dxUu2n-p}LWx=0J_X6ZmWbjx@l-DJWOTtw__lzAoo| z5$qAZYo#3P60juj7tX;GbC5ILoh>9%B;y6>5Pe8{r3u|@8=MNsCG^a zPC>T6pbby&226-0?D?4#4oMhl|D}u(n}+|3v@=G0 zqIeqEoEgR8rGWtS{~xQz6O$r00%AL${D9eKjMzl2In<}mwK&f#_>>>}*ZbPOSj7dR zhx=3ddWfnz?b+NjVlGa#(a2wSt~!cL+O2(wh%c^#SLj!3< zl$w!wyV1~S>%TeE>7j`iwD8h%M`-)PIkY9ty5` zGx4!~^qucQ1B)`t>qd4t9Tpgg$%eJiJ&f2yqpUtnRcUozECo#&ES*wY>Lc6RL?mof z_BFNXQa#3qO&IE&73xu0r|Pb1oW{oXZC>!&j!sM}{@GbaGRe;^NH?H24iSXnuo~!> z;hjabBqx?_zR8wD%cgaO-6J4t?k@HHB&%>>c+xtzyXjEPS?^B+09DL>1N(_h41EQM zxOcLMh}yCf0HGGejADijY;*S#d4)t(YBiZr{{x2XqW2d~;-24D1bKuHR)KqhWW$k; z4D!d%kTegN|Bkjm!y)R43mX`OD5A3`b6ofTidgCeLnAjW>wB+Q7C`_$%lxAZ^Vk-?J69Ly0*(iZG`88 zI^*`(ml=2y#xiQLqLCe+J9Jppdkb|2(2CE2g_@JWoZv>70g(|C;*K<`>e(%EE2&2R zA%4!E6WJS+Dh@djM5GJ6)+%{HtIFRW^GjXg%%YO5Q6{$)37)un9;l zI~rPl6diER{U?d%g-&p4sc$qttj|#NaZHM#&g15Eqd~m?M4>8XRN7^e?^qvtviQSR z)&|MZ4}8DyK5ZTG^-H!pOnEIv7Yi1HFJl41JYMu;^fARa#s63qo8PWQv=sS>y*CRC zs0pbMo%m(L&5f}5RJYBrio2ZY2esu{FU^@LMM8eMi7Y0}a}N8?H5^MTe5LWX?Z2>F>U{6|E6iZF$QIh&8-86Q&d~}8 zuQp8mei<$_^`?yr8lS`p4hAiM{7vs);zGOK4hs)0$cN-mspf_{iiV0DlB28F&WSg# zTfGypsdIFbDma5#aG(p)UUNRSf7Kxgn$zptw-C+Fv_e+NYPDc^QhSwyLUc?=~G!Vn-uGg?~UNE40ES2|@tED+_WZUg(*a%O4#- z@8(=IoI^#j^up-^MAr9bKkKTcx z;*Gqov%)c&EUU5?x!{PPhcCW^PK_;nE{KW_rPW=;9eminv84!=mO2v=7w_-rawD?` zFnnJ5gjWB{LLHrc@%j!45(Q{517FMO>W^p?pusnNbp_JdcX}e*bzT&*=th8NUa6egE$iwSG!B$i0bF0^pV`IDF0+rHsX3Gc}=a5x_}DVOGx@ErD1O^~|u@w^Y)@=fIU z;$tUUT+Lg1{2i!wyWg`Buvb;L{v$*tf_Msc-yc?4deSL(Egg;wLMD+D#Y^dZI`qW- zB=P3;L^N4!pM5`hXU9+U=<@n2+{v9K(jj6+<$$_lxh&)Yjb37g>H_xBB#G9eK_Lnw zvs>^-mv$o~m-E-+A>cLr_Tzm;vW4~FsW*l(9#8f-H#XmPYJ5@sZ4x&u5smDcd`OL` zXmbDpMy67t*}JD)HjyQXeK^z%@^_o#V4beSnVk&sghlDuAp|M|nJ6M=Irz_Y=IP8| zKuJXLAy8qQ%Z@8;N(*a9ThGx!mO+mXijCi*;nJ2)J1&8HK@V zB(X)>)V3LfEb+$z$Q;2+r_iM6N-Qn~BL)r_E*mSI#lZIy*prvRZEtfOx|K0S0te;_=BR-A_*^n(dxCltSL|Qf&1kLrHonKQamQB<4E%$GID)R z(G}2>8o8u+#WHFO)-77nyo8A`F~)PM0~zZQD}q}U-`og+#)Q(hcOaETJ%gVGFf@WS zLq}|>gjLi!w&mN89VPTT1|3|OHutDTzy15q(#?1zPQlSgoW%Xj8NBlh#TCREL)6jv3T=tF-ARu7lHi7wPMaVg;O)&y)J(q-`JJCy(Z5|PPhxUBHCr^*B-5w`{rJ% zOdht7ELso{vMh?+x-BiVo>~~o>8rXp*Hk}04^b7!Pw8u+h9!brRSI;uqUI$6g;$#OCH8UahooXAB3U!Nf z!Agi>ylyOL625`zy<(|oup*R85!%4vW|{Zq#L%0tw~)zuOm0x%F-#Yku_=`D*;q>e zF=g%E`N`0eBLr#*5)f)Uk13&}mWw&kq22uRAOzx$e*DgAl$Fy4QmO1-^sIrm(m>@K zd-6lIRn12vcAhHW*$hJ)s7|*=zN!V2P(v8TL4X3}>Ug7MMmf<$jFu~{(V^`gTkI3_ zH?<4m>aNG#%N?Rf=?`P3Ufr0CSVA4DPhI8Ay?FnF@2X^3i~n)UvHY^r?={DvCX=Sa z(+Mu1JXv$Q5W@Jd(Xk8m@LprdM|XXy_Y@aRO>z*Et*7}E zIZ|~spQWuF%Q1w-M1UJ?hs{o4z$t3|$y_);-%5U=lg4D4U9Vb|wS`dvXMS5UDql|d z?c#W;C&U65DuEw98xTqG-`ddMZGC*k`tu%< zIV~H;S6;hK*LxB$G5^l7E==YrPhTce$k7dC^RnZm#guB(<>mt={x1K1=)3I!Lr=?I zj!60KUmJ;@?*TuQ`?NfMJm;TEXAWm>9Gu>Z#$y-lQez&(@q@y9HX2V4W5h!NpxG+% zI=RZ?=eaG9p;*Zjyk|X;H;#dc&_}QB7MXWqCudJDVzqmm*y z*JKYA4m8XQj*Zs$jWz@cDyj?esveCOr~TYXO8OBC<%bD1?o7v9VymkENkCM!v4XH! zP!FW%oz;4m>p$}+seYM4nP^vp##5_FT3XvW`LpFBw?X?PfU@AS9o{e4A}v+?(uH=vEd!j-Y*%Yqq81PNLex&a@!GV_{m=o-BH zCO%Z=g4Hbhyf>PjUd-7B()a0D+YK|_7*{79w9R&Fve5cQ{#A~+ltI+mYQ{Ok|5p6Y%|eou12-vDaXM;D_xL$W zChNr1-_OzGdeyj)O zOLdXz>LlI5%}~O7F@*~Py;Bc42EGbGfP|*pv`iKTzJ32J;jSXOo!WW1PtSTfm%kHe zw6Fx(FWxm`?R=FnXa6Jy+mc=H{-2bpz9MpEIDk`EwkCyXsG^ZNWzIt64ng;sQGl<7 z{m?pAOvxAU9C5a?uR%)byDb;N*3j+|+(;0Vx`E?$a@qd@pZmZTa-No}w|u+h;dY!o z^ga1F>Au6^o!xtkthJ?K?m7`Kn9}b7N^U~gE#5a){IGUj!xY15oJJLOp7(cFic<7l zn;SU5f~;RekNwX+Cp_HyN6(CNkTEQ%lrMaJIy@C7kqr^xeUB+Q@4Jw*QS3s|MFxX2-zJMj2JZ}SQFF4OVE*L&BIHxIp^M{9k|M4zBG1YpIo6+k zcr>bOdeOdFUkl$Tua~TqzIg@tB{ZIj15tBK!7VvX&i!ei;iSBtfsBCmdO)_kLhO z8L#cmMkH|6en?Mrw8pcLRzFjMcIZ8IU2o#7YV=Dy6+35)Fy0&iW(9<3+k-$t?u3lK zp9S*Di^6ud;OIEB2)@Yb8zVo18K``yP${u+T{{Aqqz~xU0Ck(fDX}jA<|FYd$p1zP zRpSxSdP5CYjvUAgeZJ<*@r!Q8Nn=sQ8=b%OE_PaW5;XvMf(}|D}{p-#DSc5@G{c_dZgBsdJ50 zhVB=wNjMu!_p{`ar$@?ZX~*7e+l|B<$dzNc1d)MMR^z8$ngfU_*tD&rA{eOdse8_- z^##pK)g9)#PzSHcjYca#Z_{~H#e8fCLCQ`xM5DeFe6k?@jE6wBH@RzcU`mF$-y+rg zXBc}qmdan)s~jzAW{#J-3GTS?#<*IYu@pIhHo_V|E5S=s^us;bcmSvVM-;jGNZrfh zfyvt^8lvzblQMpEY5w*w3-SQ@{OMZj4#b7Qzxd`ES=6{=9`0)=uPqc0khjCvY=BGN zzbpL95;?;0;+cRJ)|q{gL2q_Z&x{k_+4no4;IG7I2oLoV-C#`Uz__NtB&xhd&H$hc ztVHskU(~xFtbU_|E>IE69l~f%9?W^;2iAWSAc4}^R!XuGuZ)OH$6ydA8=Gxv-46Ih zOd1FJ-9#6WDtvI17U)sC;}jGOJilBhr1HI}Qp^gVo_~lG{Wbf;kw?s^fq@-k_tGuS{FH&q!5(s8BXEoxxh1Nxl5@Rv zoScFHQpo55TW(su351t$i{>l4Eg5o2x{t=oSs7UcpYZIB9AX3h`OfUihip-G=5*?{ z@YAN;>w2qcvqcFr(ab?uDMr@;r~q#NkR*(b~*1{kn|R&S{csq&b|AN z`q7s~S58j}t>LX&_lfz_eQ`OayzoNi|)9)jX`8gAi3DoI=Z zRd*6fY8c^2o}^p^QdI>kSvn4)x(Gh+bo1pSnFxgd_;u3vs;Kev{Fga&Omt@V?lM3@ zd7_c)TVF7uB{_!@K4Ak)!|G$>7=!uMk(9GW2&R7qH^nq$Ajgj}W=t_l?TG~~?L@$N(St!%$d za7@==C5bkh(Di9&7X65z^K%C@14qL^@c_2@7L7W7m`&Nc-1~2HkokQYvHaSOj#OTU zL7n^+pZ(P`UNmu!J9ghiapTs#ZxmDpb`fl%clGET z3dD3ixGp0?xRE%}F<-6yiQmoicfMduAJ@2w`;i4Ur_!HMpvV{j7L<%fWd){@8GMzq zoyYx)A-HV6i)hFGm}-z4(rAx8I{rrxgDx&YlDsrj~F-F6$uj^MA+Zw}H;6P87M)}eo1 z^_&QiR(_Bc)ga=3G0Ma^eF0Crw<~PhcoDP zv)pAyMA>v7-^>S<2E=k|7dk_{$E1D!#>qkuqL`FTb#W8IgaG zk!psKW*{mx_ZsE|eN8X6))&u#7fa9}SLRwUUL=-wVrtzI0cPy}#KCx6CVx4yZz)D# z0MrG0M~XF_dud{xeo~ld86}Rd{}q8uk`IPd_C5Zl$jB{IS_I0ih%A>8gHPGgIVYUF zrf@vuwo>TjSRlXfZ{<p4x+IVwC zZ7_PlmyqFbX;G|W_fkcXrD3`vF*%9%$j`Pu(k4W_~U~86QcO|JWHq_<)dp5YB@;3gvbyWcC$G`m-m{#cMjXkFTv^l%=F-PMJ z--8b7h+6q`($&4E3xg+i4{2dJxkvzXJuv0KVk8n5EA(JH9bNzlS8BTBDWd7r-UgWP zjuq__QXLPOWYh|T4@{wzoH0umAuupE)~5 z)s$#eE>>OJ@nrq^uQwr{$y1h@7FNz~CD+>hS*Qvyk_K`;fhvO{ofYZn;qy?TM+H#L z0Z+)IVIY`Gs!=R$#f=FcIQ+Jg14@aQ)=P&mD%*J7 z20I>%CVy>1TG+$xw}T^aJ%`1&abdKzp^m0)r>MGX%=m4ZO*{={ia%K&#%(9%27nuC zUu*y7(*v$m@ygAd>6M3jsn}-Z8|u`0G$4sNIu0hB<} zfcSvFg%3yQsl;>7UZ;fw1N~?$scSSqQ_b2%!cD<34Dl=6zht9gO4kOH0NJflpINQ! zqD0!AFr1c@jZqIsQ9cK(Z;>9ROwT4OH-_srLP4^653)lIqgU*)$8ZWEqXp`-4RV)T zmms*ApTR~YBdh05MGU(+C(JNS5eY`-P@Neepq|D$ z<;Dc`a#)}3JW))40G+dseCFT-W=yOSCQdfii}XYH)NH{X7A2n8Wwd-pIV9&cjPC?E zKXd$qqN#SOHRDXBWU5TdpbK&MS^(|#n>^Z@_1C`sY}F#37-kN)s^laRxeHWt_MVzr z_oXxBX{0CQCf{6u8A-QliSLsaL^t%~Q8fqZQm!~YJ@K{inHpG|Y0jT6r z{|l+|KF&e$I7J~D<&HRV+HW`8t#t6ilP%88a%R*cJqb3(QTqOZ_O`HO-LB)^!NFvo z7v|Q4wKLk#_|zwgsy=M<4a7i(c8&Zz~|EruFd@WfoKHSEi51eE~q?gN^AC!Daz?^*4>NtNd5}$?n%0+c_i7A zr-Aqdbno9CI5$Dmr&?#wpT><{1aS5SUwb#{HPG&GoV{)smu()Af2BQNy;yC3W53-* zwW;fjGA6u257_!%z4mu3D0#4_NS=%I%mq7&*TpC_wQB)9foAfvzg_y^?2j1%lBV=P z-TIxc#icz*y;`2hkL3muRukujZMjv47g>$7VuZ@*Zs2n{4=G-qj|zq;a%x=xWZ0kq z$CSq-wjAaVc=C?seF!qM`(rCZwp1uE6NhA(fC7<_s) z-!XlBx11RIz3?eri!C-?O*(bRqF}}$ZWxe2+y6CC>Aoc|L{UY$Y3ik zChG##lFFLtm=bHCyofA)C+EUSm9aN>?bi-ztO*jOS^N1VplT)JHf^`^z`C4|qc^Sc z4>rCApVRp?0eCd0=nr}!c5IR{%O`MfCXVBA>g5%K5~VW`t5*05Uf5s-FggN;lU5Z? z5LU2$^7{6L?1P8fPa8bzcIVwTxgKQ_`8}dNUl!8Wnq7yDEFYg0{t$0!*^)p@;f5I0{6JPdsBXoL;40lh-s3h4TT1p@XcSvko zx--muw*lI|0KE5$;^oTYj}k0VNx1`;G}p_F$?0ihQB}aBKyEp*k4mL^GjsJ8E=+en z_alIzcR1j$a7d!T>qN6ni;krrs8zf>SuRq9*!R@15R#C)JT8+oJlgfxWZQ0(kjddO zXFl&r7?V_CafbhSw`CB-{q$)40Tv9+xWW$fx2fb>MWTEH^jjR2YGpLxRzPt?p6*s5 zS1HfLmb~Y)LfdYk%90KrPu0qb$-yG={{`!4fLsv4#N6NEkHr0E7d;WcQ~^q+3BMEe z_UWvNQ3B0QdIKW_G8(`<1r=3b@#gc9-$Q4Eh8O{b4CMlW){EVEcH;q=0a0ZEg>!4h zGQw;!SqM)t0#cQ#))PWeouKp7?X9HdV(HTM9P%zNj1)- z7N2hi+FpQOF773umrIm$5Wn&}2_TgsE;Sd%o=H1jb4}uKREAQ_*AP=e)p)JH-zWd* zPR17kP)s5y4)wO)$TMc?f$%ed6Jui<@n?>JCj$*$q(RO<`#1`EzTj7q^B~7D6**gB zH&(`BMtzI3@@ut#r$imJbOS3bMI#a5p+%PJ1m8zOEfp9HonT3cKtD2YH|!*tO2=AjFylhS$ktyp#oj;vU9V_15(R!x(G%l%{T3!P(ad`5f$F`_ zk+GPP+y<{lt%P}yf}u|3Y}DBGs}vMPYKooCiBclBg5*2dmNY^^JXprJ;)?B(LUyaS z$hnTQTX>1MIdGYFO4&TaQbmN*m*PQoE1e{u>3feTz^QQZ(Ks+#>ODSnb-1 zBjRoJZ6zak94CgQjS{{$Wqb5=0uMOMXmw9Pq$GfKd*)|MiR0$2w&n|0O80LFS&2K$ zz)|RuP2utD??thPUt+$33N4-{3r7G$zwXRto9}^Apm@YEr&IPn$4o;|a_A{zLom$a z`R~~%LHV4|v+Hr+FDiwsqi+$*={&!3&;w5Ga_*YJ$2}agj81c2!5)eC8wY~UE*|ds z9IgES9Dx38((LfmnLE|>e;U;Wd{=S^m=%-{@Q%XiNN!>!f0mJd-fW$k+5sF$bNol4 zZ8?{nj!!gn8Va(MlS_az6t3*6gd?v#ocHTyXKC&rG3P-6KM_gs=r+jHnfU$u>{(Ab zZ3$s2!|Z}Ml_tL2D3m$V(xN{f(>2LbTvTn(JzBI+k7+Od6Gj!IpEb$(O`oRB8JjVW z%nDyEsjgc%y85cY{m=SM$ydp4`pTk_j9pM{TK;I{k5r?UgIrym zmffdwOltp2m?1plTowMZ|6)zMv6E{?QviihYAD8G`IqJ|S5uiq)#-edFMs}N{+3j_ zRu`W^aMo_7{N8RYv-;1vMt+$x+?)LDm>IDnKuFOgyX*`IVUSA^QOLZj^XWgrY#iCN z`|Tl{Z2~W~ih8K`9#U{bO4xpBBv;iGqNqF<6`Gq19J-tYq=1~Wk(aPe`e6DxTo|q( zou-?L8iWuk#?`w?B%Js4|9#)#l^_@IJa+Cf*%{azP$nQuBV>>ZJ&X!0010JC&vg@O?EB(q%iB-@Kj zpY3%c%7KPa#^`R&cAO2cMV3!N^sbgIG{q1NzIWJJ;T098H{&xFyZz8;p;7+ zs(St}P(%;}rBm|KDF{kP$)!O-x88KV+tJ7lbSA)!);H$4@)nf{A4Bzyi!_ z2gu~>5BA+pPT!eP!yCj1R;SYoSG-k8IX~|oLXC>f&J4OzI;K+K6i_|D8#2}x8V_ud zT8|^EVAf@Sji79^5;lSN#-{BN>ZS}?n0Lb{8+LDx zrgFeB#bJ?mt6rJ7u*vul_MQ7tBZa}YDxK#gRZ2kWj~F8Z6Y&9cwKs|cId8E7Limp2 zyiplv+l2jj+i&?ZOrge)H@@MPV&-M$ib{t{wsCQ4Um~n2eL&yfj=o5qtfbF-^2&xS zfaAiN7VYlyoL@NP$&3bt;f#jesNZp^at5qXi6(0Ieo(&6t?+w471!XBJU^2SP!O9U zmT9sSS%tHnKOb*`wTC$VVw=U_p~1~_ofd^wM%5>6uY$Joh+~oX7zsMYF5#h5hnZqo zU0q#4`(cTXUV-~Ds8cc_Bd5(WaX03UBZNSIqLj0ZgefG)*r?9q%Q}R=UaMu8UB=0S z>Zo{W@-`;dRW+>%AyQn#&EU&gK*=E%y9*HvpRKebOT0CwWAeS3Kg#CyAb2J=)d7s+ zT3CRXbU6Z)A26mxnAKvUsQ3f4D+qiV0$ZY)RV^lJq^>W8oR5FD+ZBJ@zI^Lza**~B%P>5JbNQDOYCK@2|LIXFd>&dF^*O-C_I0|= zAei&OKVq5f#T>@m&TU%EKG)k3lqp3x3>dieu9`m{7~c3;r|Y>jfkol6!NCsC)wLX! zjd-ItT=(D*^Hv(_9pAD(A?(+Ry%<~Pls;wZx1O{LeK1|1S+vLQJvFCW^~IXr;fv8c z`(=fi0PXDGb_QOZv; z)O^kA$L3F#`vA}J`?#@Jr_ku6z-jJJeGMe^%{&w)kdkC-9*=z^Ed2SlhFJ?V7dUqJ z;EE61t7S_iYa@8}6Wv(@C|=-k@VR=F7g;2KL5;6{u14t#HHa2l;2aHUVzKK>NY{mgT{qDZeK@*@BMXp=o z{^kDi50#iikQT7&$X`$s0@SGG`~^g%utx@$9`Uk)MVbHcTPC2Wew!#t^h1Y;+t^4i z>-+@wMSS{0iHTsNlc^M`&51R)n-u_|!UHVI2#U0-0NxHyS07QgWxh1&aSXotVQiS8 znZdyP08ffJ#B1e8yUC9&Poj>^kNOcujYa5O^J@Sg5xLHlazi8{6L2q2@o+dwSkthm z41@^2qVj(Gq_D)Rz{}LIThtK*5nIT8P8R$9y< zO^x6}F7!~Qt>D0U*=l@5AD8a#3c^tJ%@RQ)MWwKZ^T%~FAHn>SfU)xP;}KIf$d;SV zx0ts)7in-Bkj`Wl_`vM)x%S*3{KO>H(oh7L8i$P{b0&$^1*z~c0Qe4I5N)7f6?*5d zCv}|V@)V{k5_jv;%USk14gi*~8n2oHR9j-oDCB8OFiP7qLCIGk`7y!@Mob~e4 zZ#fKQAv2ECSg?_3*Yie$CmJ91VeJzc3T3%`OeXea*(C+NA|{Ckfmm}QG?te2&paj! z%1!!oa*$LzgSRXNUtD_$#WfR6+A{hKVlne0y$7w$sDC&U~LlW>P)j)B-SyU(sQ5(9=qwe)qy z?pp)Ic>lsM+e}YkY-~JQ&AIm(Dil~*-!1;q)+R6G_pQqRay^M+{qbnn@0Lbg@9%vs zhCDG^{04t%le#+Z61!{!uJ#LXr&C}cvXS5oc^NHD81BbwdOj>QZ`G69U2|q*LV+9RbxODgW;n5DRX0&#B)!6pa$@W8$kQu zJ<79UY6v5L#~0NnF)>kl;=eMtnydPEq~N7(it*UqW73UP!hue^psniP-0uoY_H~81 zZ4K;GXkY4(shHV(+m37i@p@Pd9gx^+xvonTf5M)f~WR-TKt`|B8kWLiQLZ#pUIAcNpF)?{q|DQe}} zYi8WE^R<&Z^_z9D89kqsYND_)zVh6#lQp(PK2hxZXfK-`ww}hOtDc3!8e&ovFn$b& z;}lCY-$1@S(``f{^LvR*W$ecX@_f*@+#MgmB>c@~o7kejO^Q>VOK|jqTGjsmo6z?U zk{6xYAkNT{1+9lqPxhMg+i#&C&0#mFXIo^f$16qDrCgD|T9Du)`fpVnYnA2JDa_r#mv1uyN>c)#oh@(wNIEZ@^>#+qN|L3ohO%gj z3aS_6w?o{8$#~HtQ__|U)0@Kk9&nDl7g@82*Hmlr81e(@s5U5xEtM43Q=r1o%X77a za|KgX66EqW=d-fR!&pCvP(@3I{2q-R;ciq^Wzub3l8xsIRV@tr(EfQzU3{zXX!2;) z81=6Y%LG2`BXtm$N|8|ia?RTL(?Vj0>>rIEhw3^ocP%9EgKAL|(rB|l@0;=F;eqob z@~412sGXraH@|7#`#@T^5a)#Jyu7Xss-dA#w+S_hC~pm}w!n1Ef`ZS@K8_BR}V? z0v|8LaH%Hjmr-?_ve#&TSh4s)#9Jpk8|GiaJ}bY5xL0s4h#tG>#Gn~Wm7lwc{oE*Z z=tZO-$+R@_TgtDkSSr^cA%rwVz0R|eR1tkuO#nWU{3+J1uzd+3%wsa3mU(y@$dECa zamVa+{JqeTmXh7V$A0nJEec_8RX8yORX$y;X9H8+#W<64J5+;j8Q{L&Rnaa8ZXz=5PLp5qtF*f7)TE$tESgKJ<7 z_W_V^42I{aFqjU8w~G{?0nY9wM*wt}8w>__Ao}g(v)@A?ps@p##n>5amk@{BA0^Q{ zAlmyKKy+aeDy&-!09{~27F3PWHE)ofJ_9!9Dc)--S!d+*mBJ|3dfkZ(j!K8lLoU}S zH5s8A9A%|<(sH=KOhy3Fy{9q2&Vm3dVD{~=SJXrUnbeDYG56Ir7I36h1EiHcIKE%G z_J`zh(eiQ=M~5QZnm*Fb{$=ctlD^e<&JO+>mAPhdqWENPE+C9=HC84yrvcpk5x%-X zw~P2~mFHS3qYQi7cSI3it6MNH9OXqa23Jt_02L~$R0~dtg*;#Ius^YS^%9_8en`Xe=RpH4-*<+y*G;FUm13#n78{XYx(S{aFo)5D3+ec(OoU5VM*D= zv8wA|vx5>BpJgFJOVa3xbZx|rg!U z#C%p3fA#I(hI(^8;je%=g8_9bV<46UJCjIO3k)*%fYa2$^yHp|iVI4eCnk^;l8J4U zV1+sr)U7#2MYRtfPS!kxXsrBb!;`MoNI*6SP`lD=Pp^px3&1DQp^Lxk9&3Lts(>s` z3B`(U6WhLctqUO$!IW9Dx9Ez)XO)~CrUL{Z3LrU{S0>gXQfyC;oH+7xfZ{WzQ<@Yio!+XgG4oKRa`Y+Ru=4>ph$E z@YWV&Y{;zg#~~?3PdK9Hs~){m?T=kD;oBJl0f(qByc3qF%~DkxZ(3>jv5$~aSCn|x zkAbY-#iw_y?%OAEIPUVkGTj*>?z(w3l}Vh(s;Vw*wNw5BCf*MU?WSAbX{uMcWCwC$zPi@uBz^xs@$400c`_$eZS4 zbWesdgEnG1XtR2d@*a1yUmMkfs;%02r2lAJ-Bw{>;5+DJ?~?{l0P&CP*|&Qu^O4PO zos^ofxYh1q_ijig1%|Y#t((O37rU6&FzQ4$$0PB&W)%jz-S|x(qrG>tl3! z=4^qy1QVJtP1fV#5o~Nb?jWMc7L&cA#Hi@v?dYS^Lkvzp-GmT<3WYDT(P^_Z?w4s$ zuKwS8Dws7?Xn~Q5CW!uf=}P2|mgwp2JPk~P%H)HFcqP1|Ku)U|yy6CqRz}ag@iTyl zB_Q)tkaQ-P4#Xm44$p42c!mmuJq2Y z7Xd8Z@sZyIb&{hl=kEXC$o0&@2Fkj)G!Z{>|B>bAC-Q{hlpgN^xiBZ2P=Q1YPy3AB~N$KjJ*M|9mwP$1_1nRB)ncvoVx$+Eoap>#_Lc< zlQY3<^BM-?F+kejtB49jJ&)+(0h|QVCw5VE0vZet@HdkQp8{5$6CTG7o9=hXQ{1*I&X+6BW2a;H1vi9(JSl3AVp5!EJ)PDnf7k7NV8dUf;5|?W_7;FLIzi@` z4wfySqo%eTme6~KS;ds2K8mAMU3`GOqd3;}T%z|ls3SRF>#N_0+COdcPr*Sw{DLC5 zLGQUmUn&@Y90Wm;mIhVt;ew>{QmY6`pH6;Y3Y^xTAN}??IX=(p_MAH>I6phobr(B1 z?$tf7pVocm4XI6b3!S_5oJD%4TvlMJ-Kdy_vKp}Q^)GTXDy*&c`rT?kK9iuFZP#^` zqMSWHPNMROVBnTYGb*keP=ANI$@BU(yLt%8%p*r2(1y)`b~rHUY=PqQ zf(EGsR-~3BE4ca*#EVr}KY4*%*OyD%L0Er!j0nTzKX`v~I2Z2e*2h2-aoel0_u=S6 zP5(a~nOVFQL3N+REq=E#TVn2+u1GoHyTCBpqu?hwrz?j0T~t+DjKc7~FPJs^ohfA=Mqbs4sk4 zOZ-RdH&2SxEgZx)0jX^-_(R;aFt4`b69DyIyd~n3QM!7ERRp@r;K>v34TcQ+!*A`( zubnJTMBHknFhi~4C81}%y!-hENbIZnTRe0#fnYRNv%i$`{>KOE%%jRn>7t?v4x zXx-y}dZE3YzV-7;&(kQOUBz|xb=UKS@H13LX>^lYfih@gO!-%3PXsGHoi#pn|>1~=+#?TE>pq|qDCV!;!YzpQ8l#w3I}o)5#63=+Z3Kh z^M}Wi=U+h2?vT~;@NE73jN;@g1s>s^+)0Dn$x8Rx3ccXwO?f?;p*zA}XHnnw!bW1g z->m|b8DIr#5DiFCVF2pU)WxKMxo&3spw#y1R8fOTr+7k?XfivY2SldNegnsN2`(_6}c?)Y?6j))VC-*1N3ROg~r4 zv;fu%s*blO;3qX=xU(7L%)ZT|Gko;nSb6Q?1M5H@(O442`Lk%^q@81B);Rv=MUJYT zCtLRgzVv0W0J1edTFxl1jl+di-g;;}noiInX%j>-#k%?sQd*;Fuq+7yb`9$Wu4$6* zBbbKx8%0scm#Ek?z6?M((FYZZC=p}jXaUJbiPmy)pG<ExwQCQGf3Xln%QYME8@lY}#6?$!8i4?7tD^ z%_*QvqohYzt+&1jf$wR}KFyo}-3|8wCYjULmKN6u+LonHl}Ah(mbr2%v>k)=<4b)8 zyrH*{viHjNz8?EQV`K)#C_N%6J>m&X*2?36hqSJ2w+0MOQBhP4Pl#H+s&i5qtP)o zucO!AiX%#he`Rpc^y>lfX7x1%|o+!SaZ5c&GJ%EcG}%|7NwkZ!`ONH38m;;k}9fK4q0#0^hJUd@Ba*+1Q28kBwKT6 z?(QLk!v8*{&_wM9F{0#X|Omw3W@a z?u7MWubj5WoUWSC-cQd>iTbJkBw2|arFDopaGrnqViBX?90xK)cC?%O$t=z7(_6Zc6Qe2c9o;Lr1NARI2Rr@tS=3GdZb@awwMP-dS4yC8X5B>f#4>ai!r|DR{uZK$^eA_bB?6&}!CMAsW z-u?|DzpNeC4fLz0-BbHvo&p#Qf@j_5XQ08ZQ*%tArIW1fu_sX@bbdy7R_wH!RlmMG zu@zIHR(eMy-~-izGz-(gfwYCpot8&sZ7T2Xn0a8s5_GNqd2n0260oocVLJGXin`-G zRYe0?N&!90Po23+G~AhT?i-TUVLH@u?Pp4?VqM)!n-vq_a@~8uFoPI>hxX5m1S^%; zH_pAAZfN8Qq-I9}dw%0;3M+*8zrTnGjV{zHA?pYpUn?!5!wth<5zrE>N0VOF$ZSsSL2q)pt~p3L?fs(+rL@<%EXJ#cdkEHipos-|-3 zC6@Jn@9sbQD3D-e4Y3b(Y2j&4Vim;#C_>=hYUyP-(1vYOMOr5arDPxEU{t?Vc5lj6 zY5I=$%EY5$;KPxsVcK4kmUtVpgk>&UX_oqMMfX2ez687s-&v*l$bsLT%DqnuG~VXl zHLM$X>ASOcXX(l9_8+N0s^Q-&xnEAWP%L`GLTZEuO0hV-lw^u#(cdxLydST-9|}Ba zuhaf_#dgUDta$9P09)E01UP~?BCI~Bj9x1L%!jP&mo!=v7iv=?i%E6@qNpqJ?b(04A~0Kq zpAmSa|KG2%l?tu3W6Fw0`O1LalW$pxY5zH`-*~eE&pa_#i;O<1)wzO3Vcutq1qjsv3LCLb)`vs_1-{g~kRUHrC4?YHuIuC>I(S3b$ zc6Bv_)^A!$gwlI;psWAm&BNNK@kJRPZzBoY<=-#uq~q}(ov1&FX}md}D@ONh?%xLh zW+Z}@CdZdXlb(;f{IA0T=7H4LjE46?G4UK95uq`yNmSd! z%2H{QTz?vUckkbK1iC77K@_)4azInEbe16s$mRTXj=&#v)EP&avPL9IPe=cJp$@8A zf1``%T7aG$^mXK{QD=52WrM?jI`zX`{K7C&x7tm*cVXDTKwR52lM#trA{ zBKPDaUo$5@=|wDir18(Y4KQ95Z$`>t1|9AE^!zgWtEtV{Y7B%LG8~ypi?7^ii-o*o zIZy|RSLXN|3zYoU@W)9sW92PJx-PS{cIekX#+{B;bu0=ZJg}P5j+391elDNw$GOFl zqghHaX>I{6Od6Z=axTz#sIY&k-3_?AP>flIv;!x%r#kND{ML4Y%Pe%=N zY8pFxOM9Lue21j5p|Tb4>N6#4e4XF$? zV%lEc#?RiWJ%G%1&d-@b!3P(`%=x8BqcAM^U?i9>TlQNwC2oMZ-?Ak8Y3&9ezSs0( z4&L7h{Af>;H7g!N?acQn@&O%tW9UajHYLb7FF71719^}ZF2{w@W~9-5RCl&n2x#~p zp3KeJ+2L@-eIi9p=uBgC2`Gm<>@F#{x9?`nH62zuPp^_^?bFYn&Df<2+w*kK?1WSz zLRYUM|8z76;3G8(CgdZ)L3@bi&dX50uz+lt()zw?A|(`~kDmBfKcmn>m0|%*g`Gq|(V4ZY{ za0tK(pZS^WFyO)_%Yu0^iEOae>)Y;w5iO?TK?BYU^>SV0@eIz~hji`uc8**!Y?QLM z^(z6O2F%nJYlI@VOt67MRYv&et?2Zy6RwxR58f)xw(iHb4iiizh7sn=|KIMRC*DmsV^vmtB^suUKRy;8q9zx2Rf%gIFYw3N^0W-mYq{N8hQ^=L&J@juNO>qOZup?@`k*hif+;&T8M}EF zDXrfNMq&Fm@>{JpD3{`c`(%S7b`WaK^BW;U$V+)ME$bG@vL6r#umSxD&;+Jxkgj9i zUYEW%62?qmnMU6n^x$&2z5vMB3yeCCEb;{a8U=QS`W@kBH{4|7e7WNqbeHP{Kheum7rd92ppr`J0S2oYL3Y>bHRAPb4&=2AsYmp6Y!d- z#ug&;E?87kDn_BD;4B!PP3kL9TO|Y@7J_8A#tz^6e3rKq)h4f8n?(Eg23UAl&}I~X zf(&u_9@zT?(H`K2Z3bKzx!GSzuQKI)oJggG%lOjxAfXm0r$OX>O5lf$s8h=V;f9Vc zUJcf3kP2S$?M5bv33(9z7%Vi9D+3W~Wex)qW#ujLwR^Y!ghgPRg7ra<&uN^A)aAK* zRyEN(H?JI5<3Yd&4dv&6dona?5TbH}m6JZ`7=Qn{S%X*DW<%gXo0!4nx%|%VPc|bViF;~Qw|k$a9{s+UqyI$) z#9}a;#sA+zuhD~;l8VbRFO|f+yNwj>RhE%KBvB-pXQIR-S|Ui(_e?tSj89hJ)>ClWG zegwP9)<8*MY&lfG?>ngJ8d+2pP5qk%*6Ep5cU%+-tDn3jTZdFf{|3XnXg%ZTA0C}n z6T(2}z+vy%iihPtqu*i?JSy7A0*?XV^5K}Pz$lmR^kI$@JhN>H-s+-+jVt>nx( zwpc%RAiJucw4BKYkbYee9|SFlVJUiXNguP{wYG~luhJX`%~_Ce0dbgdK_P+q@vhPj z{t=8T1CKsOmX9JAssEcm+Wz6?oO(Hb$1vV*!+JMuePjf5Kg0ecHXwDFDJLB!15n%B z%_e#dxPR*##J-QZLf?; z-#ukzQVOEFWZ(X!HN}o#Q1T959ac&?&2Z++SvrtpHV$@vQD4jzwBQ85r8U?3$qs1h<%}*C@_}Z^ zZX*(U_mr)C*z-?$r=)iC0?=@l6!_=Y0rwAf&{_3InPZg7CKdBBJD-T{qlPBd!0{@qr`)p0;Sgtbj8ZYd684n zwSz+GiaP3P3Na^2XM1y~Lq!*Htc2C6X(u+M5orl>CY!elYI6ev*b?7wAfxSSoj;EC z9_nf|JsD_`%b!aADL(&?YldK?8lNc0@qAen!%XPt^#dTK!%+T@oG;8AHnzVLWQ=_NKs zcps}gqozl$$M05R9XK?)V|nDc$e_(0DZ}z;E}M2xfBZ7%uwl5tlE?zNs-AW=r3{;X zoaJJR#DCqlMfv*lReT_cFE^i{9uKA;JpH*rpEkZSLjkbO zW1@!6JK`;0W2eLVvQ!4bn%JsLp+!8+zxd4bE9%V}UGlq?)Jh=^_e+}=53C)Wb3E@h zI*zBfG*|Hd-_XUCep(@n{01{X5khnKkH57KH54qwFUvhD{7^CMp1%`3PSLdg)Bdcf zgZ2AVCxmK9k3i*inPmcU0_(BOcNeIY616*en$)wE2hvjIRwpu@xrut|gNAW zJ}MN1ea%65LLn|$V4AUI&DxuLCt5jn1&eL;$NM-*ZPkk_MO-(0Mg;3|T#(2nT6G37 z#N)i&SbS^opDTLWpOId+10wcOXGbv=w8)S^VyE}to&WJvnMYM&;^Nn1um7y7VuYp= zR6tPxJ^hxIQ=1+CFeY$XH=}%}x^rvidiyFllDwA;NZS9m))*8B=dhI~m3?-V(~Q!h`=CqsA*LKnpQii?(mO$>o-`lvf!gdQtvyWOzArfHwBY(k!ovjATcRp!Wio%7jyb41&vD+Sf4B%gmDv{6 zRb^BqMpRL19>0!am2y?jK`d;-X9h4?lQ)V2?2m2Az5K&uJ9Ilj*^n`r6!M+$&HC~F z=Ta?e#@vv@<0Xo?=U<9~Oeu;HPdVIj%(f0ir~h>TV%WM52;N_S{g;%{t52EqO=Y_D zZo%q8Klbgh`=+b~d9HI8-n<%OP%Z(Q?LZ2SR^KlwYQAMmK$fukj&)!xtx2B@d5WER z`7W2E-wGB4Tm0h`zvNjLqJg*7zGc6Ies!wLQW0+n2vYo+Ck+X%9<`)BC~W2J8^uUJ z{qlC1tH2D@h*D$Ik$bP=u>VR#(me3S7i?JOi3{ZFn@|6pbfhLz)+J?+kpHa^)qDOF zq(fba1RH(u>}Y5HEhc~o^l>3hmXQ}<|H0t3?q1^m3ja`f44`4qV%w0m7(Z5-B!s{h z)^OE9J0X`D|K%-EBzia7-+-Df^xRRnw@xPq0qh)T4Xy!cQ7m!E9UutI1W&#OL|Wb`&SQ;!T=nn?xv9{) z(TJV@PF#{r1@4QzfT&)|4oyjWzEgW+6l#Oc<)feG!YM5p03Yv|S=KXW5k2+GRer}> zm?;!19TMFX3+_V}Dlb0-t=+3+A8chBkW4*G`hMLPh*AO)wBgvZN8b*W>(<^Ap4}ai&NCdH#NNIVc{CR4;pEic}T+y8tDSI}CUo9au zwKtTrc9db8Eh|D-4pYkwc(U379TYX%YS1j3=Ua4=PA zc;^_Ld06gkyl+V7r;;bf&Q+v6`P%J@Z)JZGAgPgyJ|_LM9_o;ebU@oE=cP}}cWmdo zcf&>vA3dKnR?V6d>|sjI6j?ve@r6>cK&y0#0#GU{G_>0AaEwY!lT^Z2C5M)ZggD|u z$G;JYxsmIbnoSUue2=A}A3pR=VL|PC*tcmSXW-jAq;M}Po)@eHXa-UnrS_2f>qONI#Jh){>=-k zc}ajd1(=rSY5zw7GU7}*>0uqK)x?LN3ZYfQWe%n6d@=tx0I0&gN|QCxA6WbN*>*xI z$($NIvxx(H`o-g24wc$1rGyA;230Y+y8r1FRm|NMg8`6Pj#Y+oz5RXD+7ZRvIM&-7 z{B*>wM%>p+^+6fwDq9vpi=>VdKnGh_n%sgVX-INX(w{9z^?4cGA`+=&a@o3dRl^2u zC*Kkj-ytzx3xnI`S!NMxcN7WDk@TDHxfeY*rCc-8bLapV1l$sX3=kQLdKyoo;={6# z0XPB~XiK`ae2-n@8UI#|V$f7BZzwAFaLK$}4Hp`=OzBpQSi%bk!|@x|he;NL-#T}<+CU3r& zmrz?hBoH1}y1}EU@&3*~#e{%n88Zr8q&?8vHgP+O!S(vv-icr(-0H0Zs*C-vQ3^2s zCz$x5iVk{p!Ude{UtRU?Wt$SnLQu0m^$V#|gYO!?IEfGeGRzne!OIMl<%T_(5qC_` zrJ1;#oS^NwP-oo70@O}lpEyyHAO;I-OMDhrxmom2K!fzL-azcV!8&M{JkDo~o|JBV zJLZ13mv+fV-8@D+UP!)UL?O=Pqnl^mPqcasS{7HrFl#OuEv zH!oV`c=ts&NI~(w)+hHjJ>SxUVSegktmwmU6fxj_cLKUNWfA(7=q0DWVyWR?El~VpXX+h{NQ+=a@Qd8_N1VrM07)k zSs7r?{51k#oI~6n81Q6*^(}s8Pln%;;2B=`9^+|;d3I(Z*iH`~;QVXi!75j8Gg;HK z8uAOWPZk_6RPIEg^1uzWPboeUQiIEev0#=lV|9*8O{5rG+O1CIHTA|V|d zM^lZG=Hz!5-}>y|ei2RG2xdRO4}?p|3}5qboJ&rucNGJhZp`oZDMkhqW24H?8ElDC zUsp*mH_aA#Fkb#ROw5O-Zpjc2XA5hH2K&voZj~`Qpr(n0+hco6{X^_|c$JnvNu#miVO{dsw311}zs)~lLOP}e-+-Us2FrWy5+i+Vs*LBl zDKAXe;jfru#pqcP#2^qFzsH!$B9V!Rvvz%a!w`P6`4q>;2ZLdBS&G3!j}g(aV1YIC z33K!%C(HKj45DF_`Lq2m7=HDIzqyVk91tdJcZlWlcu4x!qhT6sxNb@pB0x@UvtQ27 zm&3MmU$iT;{Agpilqli1Y9o6X8ip}BH3q#aT^TGskB%xlYZjG!h|(7G%tsWWBvQ?K zK!Ijd;!<~Qg6(Fi$J<-fOTiH||(-$*QjWct{b+q!(()Rn0W#9olj*5 z*l-CjHtg`U^&iW5lGn0XPbrT9!Wa8Zmsg1)s7(x68{#t}C=ZipY}z5g>FRuDYZ~qc zXCN8?`rIxuZm;7b-JRt>0VZKfBO${7E5J0w*fdgFN#H4`Vxa^(U&pPGZKa~HnZamI zRD5Z{_E_(qqodw`sLPw!xz&!?T=pnDf@&>`M-qa_H6dn-jAcsfDWoC0_>khJCO!aA z-XEZGTZSBf^$7R=NE_g|KtD$l{8E=}BpFG(bMb+fta`8vX-OLeP!xr}5D13CiaHoP zGPFBUFQTYq#uN@LsT1^o0kb6nSw*ZRYQKK(!M@OLbxzV23WalLYisQ~dimB)cGr63 z$W2QgIf0el5lfbBvT%O&k)kMFkNc?L)H?)3l%hOivf=aPm>xy;ee13wg|=LaiZxvi z6PcgUrnqeI-SHrrS-FOQ`RH*zCELpy?4b8Y@vh|CpSyBYNjTqGmJKDprqUe4wGxIJ z(mCLKvnR=G9$M9^o54dOx93TlUiB=%vR$~DO0Aa9#J>jw8YKihQ*57__~dCSN=nx< zB&u&RD5!h>vvx@tSz8~ilT(01@Svr3Uuf>9Vy%f?q&G6p4KSE39f-I<7f}vivl_bh zDhkZKnr@G8-zaYH=O3xENsv5BuPdtCT(``dw3@JxE62nJn>V*~X&3E9V-U@zj7O>1 zYxj=F-%HOkiU=QLez;DT*3^4@fYV9rhp_Glq92N*1}9>*-?i^e3C48as1L*9*4sA( zIFhYlOTo1r9N(Uf3#P_t6NbdN#Nt&=#Sgd57QOVMVqp)9n8?~G7~G@l+}^tT^QV?V zSYds)^6D*tkzW|9`!A4C!pBW}^7dblS2%U+-=QJfez)XJf6U#qB;S1RdumvL zegafz))=O5FMZeZJ|#X^m#p|O=q!>~Wi`E;?K5g$v0ID!vd5633o~VL+M9lTe{bNY z1jYcRXhkLWSj!zZf-e=3yZzq#b`-_-M3eWkGmXqY`jTZ~Vm~MC0%{KJ0@wD$(D96~ z5yIw;1?E~m9d4@cE6SbHh1jBvlcnZyI{fBwcluJ0n6$BFp0&Rmj*Z!6Q|rpplp2hLXUQt@s@U}2isNqyG=L(&^{6Q_l*$1MGp2&t&Dy z(LD!Aj#jJtcPHa*vR;D)pT+{D9=Gl~?XItRP0EIs$Yhk}-GrSu9L1<&9H$|-l>reg zc7zPbFuWqj9tNhNbKSy#MR|T43wZG#h@+aXMiyz?@NGLTY>C0MYiN@kXsMT<4*&)f z=jG?s>*9NtcF2N&M=CF`eGr(nSK#7H+BY;0rVzQzIaJ|=0Wv(OP zLBBvSiIU;4BT>Co0gj=+x4>VsXU>-PcC7l(^j{cWL%4=uRSJIj^d z0XPsR0skO@zX%AJ;8P)3;8zp~2(B+7){dWNWb71o4Amh)$*FOi-e-6+Q!M83R5ETLS;sL?={{BG+NBWi)c7_&q zcb&}5R^3-^Mq8p6&X4ib%*~~x8G?SYWzNyQL_uJ*Z$vjwuZmub7Pte#wRZ}|S|m+&dw z<*n^@N5aC7NvGc4;kb*Q8AhFyq~xSEbspxM>qAS-=uNylJm*FFt5%**=p7t%PORmB zBcxLZ%v5wcid*wLJ6K_TVHf6joHbxVwy{pOD~vbk+CHj?KF%MbJN+TQ*y|>DwOeV!&}+C4)cWa9qQPZQUSU`l^ttV@vmyL9YMMuk50yS zf=#FNg6RzJWHWwH)theOidg(|N|p5bAc3N(M$)}=eb2u)Hq}&Uk{BiI)@f+72Y0!) zgqrHN@Q>Yf=wpH?GcHxOPYC5sR1oGU`}o#T(vw6YmM1RQ@{DaMGOh{1b!FtZ-*<{{r=GJNv9`SPDH@zW|0qui zb>&`&_9F_R&$eU+oy`T7;SMY&Cy$-hu!yGAd_;!AQS18S@(^Am&%H)yd&Nz591NS< zKSp~maw9<3w|6wfd3=#<-=m)E1serwS=6pDU%PIlyQYf{xxu4A+^M4EA(KxSau072 z@UNr5He%i7FrK>%ne1*P;Ugz6tDR4zoo8tJtyv*GIsFp5*lf+txaTeu;3XMbrtO-P zD=zL^lw_Yg5APKG=pj$;IW6|if|xkgHiH*CmB{b;b0mE8D7;A0COaJi;wL zg;Z9LH8(Tqf$Kx)*b!oG~LQpb!{hTX|-;8jtk)}18x12^q*v7>k3>v zsX)rPlmE3nt&k$IEUeC=m#Pj|+PZWyRxexH$MbX7Np(WUIX<5zRbG@g!YL9DmmA*W zdGZAh@0QTkTY|Etyql*;;*@kBDV)Fbyedu`IC9I1f2U;<;O)$g_Waj@sa{DVVOd41 zUBUAyJ}SGx_rIsHlK8PiWt{K0dw+XK+N~xp6Mx4$W8&_S(>>Lh?oQVMVe9@RL4u`s z1N)JtA+Xk zT)pKg-GXv7(=5Y{sjFD}$=S^#3z9P5@~PXiRSVX?h#LDzSpM*s->s@rcBP&(L2fPd zZZ_|>q@K(9cIeJ*R}zW#$%UDGkb#TgZWh5^(M|c#=ibvOy|$y8UqgN$*?49=j&Z2i zrC9Db@#^pQk8?K7k9Yi3SkWbRp4`^ozF3OREA4nqf)RN+=Ki>rTK%07)+_{}%J?V& znw5lSM33E3s11dINt>Lzcb0OEd*BHDuTsnfu}*4PmGe+wr%DRkkb_=;<<*5$t$it>$IUzp3r zJiEVUY^))TK~TeQdMx&4Q23y<2=7OJlY^p;7hI$F=p_p7FSpV?zI*2x^~8OlHnWn6 zp;FCHQ>-~s`p7R?bY{yE#W_+rbCx~>A%Qs))c(BBr^+i9j1up)SKQ#+3uX2iY}w`6Rzzb*Buwv>&(y*{ePR`o zELM6qFJCx%cz1Z@S)MY>dD%O|2@kD0oi-GWHdZo`HN5tu+}J~}7-t!aSlI$^7ms+k zQToZOy2CH+T3!1kPVqyMc6rURD13@0)S9wfWTv zbtoLI<@@!F9K&bNJ`~!urbO8E=biK`u6n4qJF15Mn(*+7FskUwQMInl`~0C#BgvA3 zq@-%Wu5j;@8D@oMAwfy_kV-PC$7tJgzXv(h$mwwwiNBL$v{(;9Z$!I{w$0_FJ^roH zp)AC#=3sC=v)ZU9a$3vTBc$dCFBb|71OtmDy2U)g4h? zo%N)XRtC&5+6@cV%yN9HMc@6Jjo~chE}j|X>EYTO3h+~*ZG(M8>&pbj))!freUd>G zwkc1eo$zTnuGC_tB2veBZEw4bFCVI5V_>gsCrEDo zG95qvu$p!?EzEA2@uszBY^0k^z>Wlqghnev*I#v}+jjPt9b&lNg92MDC+;Wb_TMk4 zP}Wjf2Xbet3TajSBq0#Ckx5)}{)x6>vN&#cY90|MZ^|=?^+SxeT)T-rCFOVI9GxA6 zppDE2=kg=(b52b5y*uo?ALy=SR|wzBzrXbf@RbLcaYzdg<2Ksmc!dac-`O&+vezaJ zUV?bJ4UJb#meMSb4NIgVdfM5sj?1kYc(G;h_z@@&I*v%qy;V9OsM+8M{ zm)(g{(mixVZZ8%h%&Og%{x5<*XwO}PHm7+{^Zz1Vks^*2|L=ZF5y!$*Ih(-|l#{)T z1IZD`kd`6KTMQ%mclLf`8*UVqq*IQ}5Li3xC5fdvw#MA#O%pI?nWX~!gVxKlM6_8p zISDUFjk?0pRrW3J5j(<@F7UE-e^-yFJrSAL*_*omV?1l)+hS$mgg*UC-oi6%wI;t= zm2;hU=z`d^GsIMF*V-|;HZ<)DXKnfHw5Hqu|9441u_vlwmoM!Jp2}4tNHmYn~!7wcNA0<^z z=}+_JY9*g%vsvxa#bQqPTBEw>ljV$Fa}Hmc)8$&Fu4l7Vt;^+NP4`-}dgqhnvfh7{ zh&yXmj6K0cPnU~5-D|Dt?@yN7dY9{-ZL3TFsXyDiJ`r4ZM1OzzczH&aa@Ts{|7MqN zR@&AB$oLt#ybLlamfO{GQgl$3QvN&;$!|{)#s#hp9}Ct`Qr@0J%m9yhcT1nFA1ulY z9AR+X^!dL$5Ii?akFKj1TGjDTHz6I((bcnQ9xu_wMXz5D{6U1zEpsBT*GHc(r%26e zZ;&_SE+;)lSC^){*3#8^?k&yqE+;3J|NXaI4`HqCkt~o~s6>4{6Y1twyoTny|1YTh zudXLjq}#bRfogw}#|vb->#|SZ^V;%S_p-aP+qL<*=4oqjt5ZDfs?g&aV;e)!^O~Ta zruKigta+;cPxJSXZrAgzG{xa})onqw|8|1Bu{ztmnYGsCXz{;2Q`@fCN^gDPjyhAV zcnhf4Dy~qi_&>#)m}-SHqX$))QaI#d&e@A8{&v-Vrn6ULjNQ`h6nBs22*>%$(bIlr z_7J8<(gL1i7C?U2>2D#{V>4@w%kbiK+Zwhy&%ULZ&Slj5a5f-&h=X+@6rG@7qW1sQ zkN-JB?;+Xlk#<#O{Eb&_E=PO^)>i)+x61!%n!NBo#!p+gSj!7t_-BB@NW1^no&3)$ z{MYsx_s4lF8vXIH(OpZd^l^bf*(>SXY<+4;@w^-mE8gyd@p1wIC0hDZ)I*pHEYFL~OB5OSw)nnl07_mQ4+o;%p7PuQKkqgAS;z+;|opVPHlwxl`1&g3`wQt!#{9*bJ&gkz)Ou_xR9`fCo}w-AF7gh_6loPSawFd)#}Jr{(pX z=?KvC)Y<4_@~UtS{Ei;*_3^jU?x;UyDP|2tsnhe!XRSlq1T}8&d)7Fh(Bj}2;5fam zc16zUkgY7%8mVn*OFYYr50@}Z{A1c2ATcKX2!`H%z!FGKTGh79qk7GMk7zjb5m;k9h!`$@g@NFc3?$ zSJW)3{AMF7u>lt5(qC>N94)f#&PG`8^|%AA^+DAL+U^A0d}+lcE%!zU2*LARUl_np z-|*LFV%gwq;$64~Ypas>u1^v&es^dCe4`frhz_}t{1U4M1rH#mz9Q^esy7Btv+R&v z89eUUMQPp0?6eemAbjpu5P><d8N;rg_J*8wr3sh{sSU^z}pX4zDzs`uC@aggw3co&BZ1=M3gGunYfG+#TeRF zm{_KothbBM22I4gH|^Y>vdV^g<{A(UHU!*sy6@%`+?f=@Us4fyK}%R{KBOUUe)73@sa@ApFtVOM=7nUL$vb zD;>$Zd`~=S9?}V5mpz0r>5A1GyPrq}F2}c(THG_J zk{yfjw?@+IudzgkoOnEk{e2GsA2lu*#NJ!mYQG(g*a1K}cBF!&V5*&P0BGr6!Rsyx zFuN>tL5V;)q70~e`bIjO0 zzt6Y;&&DQu{@I-Y?ZVzGUrspqo_zt@yxlSDNoFr(h*}C~cO4LiMYjss&V>ei3Vl zFNq1;RR(FFAJde6t`aBj3Ze_jw~=zC17>j0LFhR}{ddOGh%Med?>h(vkZJJ=#Bqeo zAj%v(n*an0QNV3@@+0}Kqs+Fl85GcX&FrOo{X}?}p@fmx?{Hk?;lFREXK(ns3$}qgQ0LJbgxu5peJ7uE#PcV@Y_Ho0kA;psoavk3QDd+oTmny z1c6>`@otLnW+kA+HXejksQml@1*94$qfy5FHHiL8S>WB;b+~?)+yYW<{>a*S7^Djl zt#l4acvJ%LrMMI0DtHAKoT=v0UJsIp;CC58R~mk7xDbCy`Hx0N@!!HT2D->em#z}M z`#S@0BnVw_7KHo)V7KL1hz=JR9zxBHHPfI1*^|BR1m}NnxZ)Fd5|N&sE^rw~~SQq489z0!qdWS2;H|ZEN*Y%K!v6hfc5=#uRZ_Fb$H3DWf0wpSzEKnTJwh(bh4k zQ%#DtkyvJd&5K29`o{#P&5ojIC+y-R@4-aCDW1ZI{)HTvuYY@{Gqp~-lRyb9myeRH zGj)j82>fV-WYz zOfEL^EZId{Amkx=D&T#JlFhJ(KnHjTOwd5X^GX3rr^-M@IdDyO_+@j53aoiuIf2_6 zIM??&2sDONl{@1xT&xFZBD`ZC-jgsH*Soq;4&nyD`+o-dO-}}teki%VW#7Vxh{0h0 zUBIeYWX?gY_*15DQI%$x`x}s=KjmCf-9p9LE%U>p4+z+zuWlwpefuvEcgn8 zO|H6=XAM&KH~@Y4I~i?Ts4yB+r4eG_O=_5~+K&?Kf`Kr5mN+C1MHXlGS9Wz6N8XS2 z9iS=@rx>OnBRFS@s>s;oUtCe&gN5^bzamY{E&NYb_Eh+RAB1R*ZmRGO4xf!fC8Wyr) zpcD?#`#WJ?l<()gZz8x~3#Y}|B7)*;!w^_)}JK zkf-jJZyN?A@0PovInTH)?C+cBM~anLioB-49NS{8MBK9_&S`+CfuWEN+>7?Giz9Y* z6*OrbbkOt-+sC zj{{iUF<{CW%BH}-ivJZR1fmME*Tnif3PNq>|5Zen(P}iwBPkZw9y12=M^hS3(VMRb zAsjM}_3|N(WT(9wBEzW1W>$j!Z2RnFQ%;^a>0zYx&7sj!!B2d;~ zgN#2KRv@~2S*k*4Jlf=8qdY0!Rb!=ZgH`vH3L{)WVlE#plZ|;uFuai^`2dIL!*;ZR z1Fjp3oPJ`e@F!eGk4C+o-Y%Mtma)O-)p%(*s=W5rwK+GBv^k8!Dg;S0+4=H;$u=`E zr36&TR8h%$NZ5`*58yCa@g%SnwUM?Wd$DbvW)G@S|Hc?wvIfU{r7#GbrgQYlc|XMF z<8BdZNuc*|LahAwjbN*DO+J*hdY4#WdhceJ!)xS8g{dxzK=fy+P6bJ`#PA|LVMK?O z@93ZBVAy?FTqF@Rq{R`7q^F%Q)fn6E|21~|QIFfp5YT}2H<#)~*-9P~%AJ<1Kto0K zoD8@DcU~ArJnaRPcwZl|=qmeE9lDuJti1uo*9jA8r}-S9l>^|;%D{EvE>WGNCX!e; zdey}|g|6YzB}C#a0!yq44BGXmr)wAM8UtdLLMQcq8kZricI)1PcYSs zFpLaUi|On^n`DclFb3i2FfjB4snAz6AXMvmOQ}fC7 z=LK@cE(@%VmAEx>=cet5ac`@kQ`+m z<)MwX;@;(^up5U8*fslMwCg%!=vSEIGtEgpvQ)&}eDlNm=R=e8Pw#34&auW{5>4#J zd3YmwI#XY{dG_(>Tag~J@l#HS7{vVxu>n-ZdF)Sah6V9T-9qIvM0*%e#VB&)yOCpk zkf%ptO4Fn4|KKI%3hd-s9&4x=pn_|3WXQRL?FyMS}yeh^+;g-<7isv9oKspS{^=Arq8i zs5_?4gut#hSX`$2byI`(vS<@bJ8nMLqdq8%CJzbPO|LNKs8T`nw>(K^Pj}cL`%sj; zuO1LrYz-1<%;FaN>fbLsB^cVuO9)WQ0PnLV7+gX#a4H$B zkl-Mhq9&&t@sq^1_~jR9lxsLvnW+=UK@7_(C03(E$Vje%gte zU$#y4Al_-*H8n9HRaz2pFRi1LcA(3O_xdC&4&v-vYe5ra{$pSX-y3y|Z0v^Gw=`Eq zlcBB29(AqH!tF8lPgdy+(q0j4TcO|l;+A%6Wc_UD1BshT_JSj_D|VfY+N}+8wGy#q z1?m^b9=1i89k09lrRS~1ouUGa;KQ)~%2XPS`(51M_TFC>`~G(l-@Or!sv4wfKeWxW zGLQWnJEavxLm4DQ#YnYA82@^X*CuJL8&XR1H)Lr^=hqYTK2TUuh2~||VU2*;GmP#m zL5g(93Q+QSpedBO(IlU;l`Cdh}p(qVE z*RgpRT5=!Ljkl0={YsJEe3URd{pH^SBbi|PwUku|Te~8sKfB46V0Y?fxe&zO5dbcD z&G4`W7I>RrQ}o_=@(0OH>7_IGK*5LWTPP-m$l9le+t<_Q>Fwj6s-RChRz#}5V=nOS zHUU-$Qm6O6)Qlig&F&N)Mvak)QX`NT_xJ6`%;E5onFj~<*W1VEVQ1s{y#o@b15qE# z2AJRSvE4L(ZzluC7Zzf#y+x0#ft#<(=h@{pzn71G3IqgU-jJ!v&C$T>dqQQM)Kv{q z)rgg(ocX0K8CUTX>jy$8V78p8%aWZL*igS64zW@41F2#yxx?C_8&ba zvI!cI83_}UbugcQ`KT$PjWMIL;M_Y`ix5h@(TyM=e8a4aEijX}#0Xm@*yVcB8zI;rh{o9Nt@Ev|iolw2v5DK&f-%{14zkdF3X1cP%{Hzr;M#qEg zmXpT2n~Q!}Ih45eWhqOgGtB6dSyv_CeXXI#WtP_Gi?k=XE^*)DLSn@eNF81pn)FV6 zbcYWcw@kr_Php)Bnh>=HI`Y$kU-aUk=DBoH$D}Qeo4_i^Uw?(FO5QI!Y7JbR2Yj`H z3s2RiV^^#6((syduQ$~eXtxPmA&#JnqNTNQh&SB-f>~JVr0&~Kc@h2sh9=876gCYe`!^-t}{IE|* zOexW?MiXv`W;GUXy3kz9l!r_L9sdg1)3i_)hZj2ssuWYTdoTo~&EXG^Tn&1ow_8z4 zqL<}1LdS8cR+xZR(s9}l&{1gbNNy5)hR}(Xv@p`ZV2P~+Ru_%M;;Cx8tD6E_C3+j*UV(}hS%-d%!)mlED@ba1s8D%gW zW#gIceXmd~`=?}Cg`O+&>HLdYiqdlH_ab_vY0o4G!D-FP&X5E-Y zhDjBC2BAD^m7_>_ZtHjtY{Gmk3V$jKRCc(A${Wz+LYK{RfQ3EJ$3^pjZRu(AmtP__mGWcG8-LJ-0ub z@(o>CPj{-I8N-NwVE8`wK98^C>wtCteWX$@!1-cn3 z84WCJQS3O``c#tucXXJ$MeT>b>O6`HOKd!JSJaqFnEuMC$hTZH8hU{Uf00}wU#XmG zpckE%CYxXV&?kgss74SC?j0IXue8wD>zu@}{)AcW7FBTc2}ql0B5wlFVFYCDW{R!$ zM5!TrzrEwHcHgHrp3N7&36S=Tuvz?6a-FEbm>%5HwvFRS_+MTXuZ|-fr_~;pr!H&5 zK%sj1bKY_8F_;z$G`aiX-mlTET@zqO`u!ijF7^VQ-Jfi1m8Bb?oor$7wsE_%UV?g*2l%bdMV0(yTf& zl4LzPzXLwv$R)n-;+;shz?3&5o8Hpm*9drVIA0sX6b%0OTe+zqiEZLSa;r$E&hyd7 zpL$?EVV<0Rs!@9=f`6~XPH@UrHjkKeoHUNuIgR8rB@#PpGh0ljL|OA_2)`TXjrFVg zoAz)Jr?4@?3WLs4(od7z+7hT6CkEPbJ#t`j2llQ3ko=?195`J=4Cv_>TfS`}%Z>B&oj;kOT zDe)!h%MO>$pToSx;9hW_u*mM8rfsUA&1@}0I!-ELsC4U*tt4^ULYlN*5$nW^AwnBn zxYF2%tybl>v`d#aZt-U&?AhwWcE4uAA}=QQc{i`>%FZ0KN=N>zI;-KK8)J^RzimdL zNgVKf-;3E_0*ehuJ9$?=gNt9>os*5wE{fl8j_6PS&hEAKgT@Jxt*8M?>rwB;N8Z|* z321oalsBA2c6eyCPwghbLVcQIf-C81oQMNG9T`$Gvk&v?FZ~-dCL!9HiRp+g71WqE zF+s?oaQB5TM+zhs%HRJ0#wC$a{BRXOL)z_3zpC*%c`Z

MPZ8;2Q)7b1h#Pi~Q=Xb~#CA-0c@ z5yD6x>&d5G<%OQx1i`?mB^2^vS9k=99jr7!G!T}U{nV5ztMG683y0AW?#8tx#55z8 zVS5G6Tb9)d6*^@SkrF8BsTbiPPf#EgD6T}*YZPFl7Zx&5@VSix-#DneKmOQZA)bm%Zr9L6@Ex6QTnY*5-%K8E%3Yms==zq#mvis&t8@ znVxcfkI-T^)^f%Bylk=0tF0BKTGV5n-!?hkxSPFYSPC>z{o-B}K+!bVXC2}|qT>tS z?Yxy#r6AQy$}vl9J4_0bqtv|-c8621u>V`hsKx)!oq4Kg5^RLsBA@Y3Fdu^$qdHYc z2LyEx0=(;^~!Fg)Qe@cdboq-*1)+z$ic33%cBr`fdx6{%-kQBl02eE3-NTE z053wUhr^h9mO6<;?ve!^8a!i=xb~SI2U{~K>eF?Q_LDNq6%}&DQsEF5N@OKOJVlIy zVCt2{5gl;2yhO@hPA@EF!9?uU&pKNcH66-F1O&UXVodrKQvR)2qW&)J4FHMf3#EE# zWb}zq*%(I*n%w#p5Nl(t`NDWsaEr77DuHoxU2AvM*}1j929cg&_;A#{x#idua!X|H z5l#xp9~f~ zrNm3+s=Z3`FSY$Gq)G#i!XzSbFUy=R^4rO5lV@k*4X zE$wMj5)A%xf4M{R8w|9eZGh4(MdEG_tw)hTDSg&9t7-mGD3RI(CPA&~c~OsJQ_KCl z!4lGz=0T~!E&jVaYI5wl!#)-LE?$#fuhfmw+_iD54-9ya_r#;9I7iZ1J^?^Ky#lbN zf%m(NYG9C@)>JD`;(Cqh!*=kSU!Q=k+AcwMEVp5abVBfY>=LrktEwfW+wibWTJs#7 zVvQ6pe@>!==<1Ahhto$Y%=295&?t|-9*HqZgpQZ=%R`NnZ2pkQz^w^i-+AR+V%y+s z&r4b!vY1?ht*WouA#egARh>hEP)ntte<=XAj#Lunt>U2B)6~~`J?!PZpyxe(iom9h z##R*uwxc4)+Jq{K1V4MhAtCsj7NcHp<)YR5Qa($F^u*qWUv_Q6EI73fGfm=W4Tt9N zEANwchP!_7-U2OeXhOc+5Gp27+_WUmpY*mCgI{dz-n$%fqsFyQ;NPqp_g?XjW#3s; zoTkc%k@bTAhe@q)wY&YiBoL)LgKp@Mm(Ere^uUwfEL*a}d@TIy9iXW`rO4|7$ zFsvU;8$C^e(nx^PX#XG&`5rN8dNqam8^|T8VLTDm>t-5Jb&XKIqeQhTwkMk#QN;C4 z>=dtrN3{dLmlQL7+DQWrd|br5*;`NiL57HIiSCwn8KU~M-aKPMY|4+FZs-Dnf#8qs z;Hcp_*?Sq9yeto%>qsWaO%WN089s%C2Mwd~M%}pK9rU1wqTEe1U-&9{9h9(j751gM zVRYnG)m>0^?)>c^UQyEhYt6 zv}cViq3k$*4%f)tWO!vl4$djs~5Ctb3)gFn??Pea9 zL>SRuQy&i~p+Q)1MfjD%A3%+Dr!_2Nr`u>!nM#Izz4FL(Qt$&Zb8rqsRZHQ~VzY{2p{RZe@B?s5BV(Nvx^A~b|WKzTrPF50ZLQm0q?@LJu{ z!3}u?{h@cH%mG5+RiAU>izjhPQyvFpT0y3>h`*;w;7k9O3{$Tr-Lt7X_`WC;9$wi! zofD>ul(>C13ajuvV0j1>eMbAY#&Z@f0L=Gu7jekzp6j#a_JR88_4(Hx%-7nwYT0`0 zgMRN%(4-vZ7j*_{uX!GEiGjk}<&_MB&#Djd2v*7VXF*KTNB1UCN5F5Ikaom|{Sp{A z)gkS#Qb}wMceH1L8d4U1rP(QR611$OCcc7YXBas3{3u&~Qn#FF z&EsTy$+$ABdU(F@w>mhzVedXOF!Wl%4xfl}}urzqBmz{&kNMA3*m?x~l z5&AxJ#Rk7k1K83I_;+j5#g99Ut$-_mO(3w#e|pBo-(v=NI@0jU(E+;(wLh&P_}zZZ ze_5f%4Wq|CBRt@)?I%Z>M2`?cb30de| zCwFE+XpuPO&8A0rH+&RX%_be+qflxV(KOm{5Lu-@yxQ`es^T|)|9 z5tPW3$4W@UY`G^JT0;oHqrU0()cupLWOXVxoGvvnmVdhNQUSthI@Vb)_2dufKQg4E z50M|5_`(^!$^77m4B7{miW9@DCFEb|!O9-T+0AkIIFJ2mJLwl&lxAO%0`+bb1}!^G zc0Cc_<{ZcryrF>54*_!;37zuUO8wdy#zWP-ErqUqk5Uc)!QPdHGCM2KJz-M^iVq-sJ`au2t#X83Gsz5fdkrupmHyFBTo z26P|HSYXq-Oikwn<^+(OggN=w(=RL0`a3KW?ry1@KboMr#(zT-R;BM#Uj}6y((PsW z$Zk(fqw~Ibt^G{{RpO2vX(2hP{BSElGYJ@!13g8F4ngiH(#F3yHcIR?F;Fyb;@uuQ z4w7r48*V|*P>WwDQXpaUqdzjeIG3wOda~dCNu{Lam7uMKp=;Y<7wSX$ZWAceIz-1$ z(9>OXRDQGZ?{Y0!Tn}u(m}rzB9?(!$dAcEXW$W4;ciKe@ibenUs74S6i;P*>KLDk7 z$Qt5g{Hc?gC%N-2a+;$MFL~+AUZi>m7KXlYA$g*LO*G#4QB~2>+oAVeD;p^}J4GY; zt~0*%xYPlgO$z%n>UiwZPj2RZVx}o3B4$@zi&L1FELxObRj&yz@aN(>fMq$Qzqlz; zHgz;WyJf%44JH>QsAo^5($Q3qbBRy4OBtxfw&apF?)$i}FAglIaDpjL+CiNc#oJon zKpOXW3);HQYeZKsbqL}Qccc!JB5gqgan6kEKTE!@U?RziNIg`g#+q&WE?tz^&-}dt zyy5cd#?#6tjw{!)U1=a=S@>L)trM4cY{dC4 z8a^|n+B^KY=vjn>a1>taAly);V2F^nskVyg5HBy+#p)Mc*WNBo#=8--Exh0>h(jaX zG%2VwZCLu`(@l=gs6uSil@dUE11e)yQJ9_gtSeX5Bu0mZ@Q+nDE?-~r@yKmK>s^hOmT{RFxoz_d}Xc+I21Ft((8kLrWViBZ)ZNxw2PGo{1N zh~py49rGQrSR)|laoA6%Ksj#1jV(dG^A|Z4c&%`b?nWmnil{}FQW!mLZnIeaVW0ra%kMyAQL-Jw2nhZy{Zk#m!$LGm)bBILyU z#!XWxDxAqQoS5JNrG8u&SZd_a)*)Atg2UNHA;lSk4d}wN<1}o-0fe$cTX_P?26^eUl79Cg?xzN*v{D2(`g99*tH!xWM-&!_ z7^a43#Zl12V*TktNhcbuRsFH8-feJIMo+-1^HK+D8SLHpyuuV~fvMLb(>g+RAKSRE zAY?ALElW{WmBjXUps!s}c=DOF8NIIvCRu6@6h2+zmO<{c-dh^DL+*-^6X4W(6>nfo zITMGv>gSD}Wl8%pA+czoG>#@jaWl>)T}DF=oU)}T37EV*PBVtlKq_-vQV!4O9Ib2c zWZlSZh&>(jOD1D0*3$?6Z9tiBiu%^X68ML~bW>C+?al3DRUP`M@~*c7I?gsiHp0$;yXe}@g0T)o57W9S4K`H6!i2FiwVhB+ z{Zqz}?Ut({6qyki^*!ccqaIh9pDIQ=A^SW8=kyG|<}VtsR1|d=k;p&<|2FWHj%f>w zen3M2DBTe!B&-L`xHdza)TdK89*OpTWE+WLdCVtu#we~}Mz!Wd>OMw!lw?K#K3LyD{z;P-pLR9v2 z1x`i`-eV()L4`1A_UewHWR}>K&A{NHR~uB_S;vhXx+MQl&4O?=h(_+a#=)-V=01L< z(Z)+WdsS0LFW&lV4!C@rWjAk>jL^@YtmF>mfYQzJ`=^VH ziSC%br}M=r88%8~e`Vk+Z;+-gBUMlkE=n_HsXsc^4wC982Bb1H$RWZBJg1pHIlIJI^2BG7

gq50uN3-Qo!m+zTZXEdoSu6!M$?^?FNM^CzWmfQ2AELaq%=k2?No$irVzZB-e z?g5Oy>CG@2pi8U(OLL*I%f9a)yN|D#k%-5&x8sd{pj^vcUk|Tc3JCKM!ha3Sa<_Fk z&^BuTnByV$Jm2?+rT3MUk+Fn0>zUghMn~48Hq#AyL*&9abm&AQu=A_G4B)y`q*~*V zx2}(rR@t;AgI^d%>y#>LhB*F~@0A>c8t`wVI;})cS=d%o?phV|CShNgwRgbWiYst>;ydB~8$%9uT%69=Dou8{I;53mbWLR{}ZuHVZ$5?AOX5nSG|uo7J; z<0`mbCLM<<)J{J<6}Kz(%<^(q9s|6e_Eqbtv*yu!c17f>tqS{X zjI`m0*;I`{cvKZA$So|wX=PS4kb<)ec{z@7*J|bKA@Ge{48(UB#N?}+V9`r1@EKMn zE1mK{bW4TP&1if>VGA-r>P-=)-J&^csC)j*gRbrtWTG3&cNZR#7$QHf38>SpspfqA z`?ba;`++WI=eq5eGTM6#`nCQON*{^Q3zX6+g=b2QgiFI}wkJzkNeJOev@O6gxD76xOj@KXzPRg=yxXCcBZ3%}zC0?Q z1WF~tU=S8YD7s44iC&K8Iv)g|L?7Nxla$^Ez;v7t`X#MVZ-7MkH3FPU-4ZuBhr7Uh;r)p zKQkpcRacb-)E3%4V@amO5(Q-zhzhElzkoywiK6r;MiDk8D^est z&ie6@u{&TV?&$Xw%9Jz!R3DK>EcPI=WV+%C4<|5cbr_n6Ime$pxaLaX(WH2 zQjL^%mV{~0ZdbWRkQDsJpBBOR1p`&By7qCn@OXk=*&vE=Q^imAgm9W+Mf62xD}se8 zo!@~~@XmvwJm%{WnJ5OP1SW><(ac=QJd+1wPPT{)90HqqTVT7W54uf+ z9bp=SOKZ@OkD-(Au@SutI7N(xpN5T0!Z#k4Uer7$(I4Y+)wOqLmC=TnXpjvbL<%2| zbZyvjF{7<~sjwmVWp*mVtQjL8&l#wxAL=AbC14>OkDqY3LnVHmY^6{S?LzevKYar5 zSdXtFL6tro)vR~@?xw8b0yUiho5*~{#VPh0sNtvd)}md8r{aPa7?m|Q*(o)GH<nLQA(eGRdU&VRQaO#Rryh?-HQt>ot*k67+Qgk3esd`O?+n&=U zbp{<*g`0#T`QQTJqf_8ZSy$-ItoUzE&$$SO$iu5$?Y9)ksfQ;W?~STp{Id{Y@1#r; zOZ;AZ$D9!8YbI23dxc27WpB@la^TfJ4gZmNst=zaJqNJByqW^P&_ ziU0u}6@dKz@QA?t@Q64%xm*8n{4b4&)@I^58$hNS}=5cy#OVg&D*=kNW&&fI4ByAR;y^!CA?9aBX z+85L4W^FnQpWD~ApRG}5WqK3*K!j)H;@X2F zx5{|%B*(X4B**oJ5XP;b5PI*|eq~=>oi;oBt7Q&j3hS7FgxFf0#x{y4p3dT!wIIR4 zVNR)`b9ITx2|E11D+f<|^}T}s0YYC&vC!p^1?gz&7sy5Vk0wnf6+eNQ{F(snLH_FqNN_|N7OEGG3*s3n7T z6MTRJ6JfDMxMWEi#Dk(RA~4j3kz3T;b9rS{t@(^28>XQw5n2vo=A(y$cDsqN!O?bM z6;48Ge_mU`jkI-R{9hlyf(N5PPh^Df9p*8n34+vqevUq%vQDEFpuZwTL&F@auO5<5 zW!dYNhfOhfNCp=sd&s+0ggbaMhK4u=|7fAh7B*>3gcN#7lkDOgWeMl(g9BHYoC2%H zW%5y>uOxaROU1YT$?^)2RP1mF6vYa`3=-3Jjr$fni>%s68x}vg0<5!J2}}9&m1Qxn zyK}2HBnkS{x`p$M%7Mib<@({}jL{()Qx{~S*cd=rm4qcX?f4yxbmnLwLI<4mZCTxp zbHUZ-NTN=egot5RgIBuS6q$a7{c(?YUeYb0sK&1Px1_Q@s8(T!in8T?Q+=|anKs8?SjjEX}UW|#tt{Si8+h4}NRQ)?56 zCx#O9IDJ%lHOHGaQoiiU6ugKLE{rnWO zJV_D^7?S{)@E(&#M<~&50AqWI)oC|hDg)aEtVKc>$ds@`uHeJ@?kAx^1NYM-vVYa}c;@j~Vf>b(3G#oNJX*#4m-DXyw%-hU52y@M z0$2bi)6{7yBH#~7`_7vV>GBS%|HKT|5WW0VeZaW61?vM7_x!Y|o3ryPnDIN;N#0`_ z%Zz9v^D#L_WdSM6plCA4_g~J?NiA!>3WQafLV|N{O5P!NNFQH>?dE(0#$MZOkf)2^P}R@`d=%-#%)%oQ1;)s~;f%XSBP>;N zE|5E)GWe@}OJ=6O&-r<#xbFLMAus9Cj+GVgh3J#tAl9e)lW_RGho0}!PB#1~G!osW zn+KT0TZSeS${0`R@W{< z(I|rt$%dTOV5@Ks&R$4-*ek;jB@m1FE!b@^Y7Y-~H_zjKJ8rSc@s&GXfql+R>jX`F z0qT$E_Uq4d`|uI40@H|sJ-$11bRahyEpF2Sy)Bs^i=hTAE>e~*RfxdXbHFW zS}BGu00D7DJdWTRy#0YqV{ZzinV=6KYD*c2F`y35t_69#K4rd3gbwv3ky(?o(x)MW zeUu4%KcR}$&^#%nnb3$JwAbnuJuxDM%-d=U1xi&gjUIe zC4o7Dz~p3^^#hwv(ZMC?^8-jjiI01>X7uePG=AO|trY#b-=-xt_lOriW%tS1yI;6q`2kckWM<;b+U#(mqTUu{uizTqKW-34vQnaPIC7 zJ0wz_io;CK7Aq*Nt;eR8)QBCExApCR;G>~vQL)W%zxr8ijrJkp#u0*pbWd7Q-@ZFR zTJN2o9`bi}RP5Y%MJ1%8{@I1D*z;=d$3vaua6JZ}9mDPHw|@P}=CR=NZM9vluR<<# zmK?qLWt;`g2^2m48hkD#Cg!?b#4QF9ib3RQWTC%d0!V%a9nzr858zRt8G)`oc!C`Q zI9vvcsRu`EqTn!qNoCO(Ou8Q2(ZK{kfa{Z1=&Bmo7&`1F8^c(ho(w8Lq_L=cKaiXZ z0Xc$^9d}!q8^YiSBoh3?OQy{fMw7*%Bauc9MzHC+(ToWUYy^okwuZyt)6O($JQ|#& z`$NxQbWARBTBQf~;_=u-B$5TTF=Gm~fkB~A$mu#9Hf<2#%V7G<;{E;oHT|(u@t}2d zbdXb85tK%q#j*Ju-zgN0iUfQCsDE4}S`&>FVWG~>qJI>aq{evJRUFb}t z15AKJ;juWRg;@hL1m*~)?#y6fG23*Ji@>_72m~Id<%cGy@8&anX+#2^puG)&nDNSB zGI$J%FM`SerhKyj9>9Tt37K)USU(-h6dJq+5Ht!8Ab}~xAy7I9G|mB~O+;%Eai|&V zg*aUrmH2~p=1C!h$fXtng+ZXv4rnY9qfNx1b&(<^gz}{@>3j+uAOTE-qy3a_Rx}+W zKm&Wmq$8+-RB%E|LK6rxK`l}&MAP_Sj=`Rh$PQ?f84kM#qYZkW%Bje17BX$g=K>rA z4e(?z0UB%y$fWoIdT?{5Ckuv#BWDu*e_@#a2E+b07;Y9eJMg$Xuvc6jgE}1{kK*AA zAgC-plLroC__UDW2j+9JkmcbGP(j|rX(%|}r+UbqkHpUkITSjF!uArOwXnDul|PF^ z6G?Fxyw;4E>P6vDsNe-B(qT~;j5ZYg%m^hyted|sz`ts=A6!p@t2S>lhZn}+M1AM+jyZeA=L)Ytsp9M#@?kmfM( zcM-KRa*v=CAgVdz)=Jz{qoaAC?ZKY?n z+pTaZDnkg7+@?R(&Dp2$?&g*a-&XF;-7WjAG5gs&zD{s5UdgGz@RzRF+3NYm@kUNM z`ufR<&jfkKwM9jr6?~1YjV}shLTx6UEPD0=Kn1xtjd!y4J{06EhPSNY+dWbyNp4cxr*Gb)^YBRw{1HO zWeBkkpCo*I6((!IC4uXv0xdB~r9*2b`Hu1NWv&FiuuKRE8VeAq3$;~qLEa5XQA zV_jZ<($4vjwdstPA-gSKabJ8x$Nu<9%MoQ&$@3RpjFrdUviY$2mwsx*vbs+8`VD4= zO79G`t);BF{D~XVQl4C`fr7Y8QLdgrMdRcl;}h}V_cKwe#CQ8Vs- zuH~(sYO_IJN5On=^Lb2jS z{i=a|U27ESqkbVT>0&FdWG)%~G&(W9P7|SaeJsE)L1;x961ux7t+tj^P1-7?gakWW z;~Cr;D!3$$QsN%@>f6Y#5I(5)R0|2;8&s;b`J81qW<|D3t113S1URrHg%!rU~Wx5b{z-y2l}%Ee#Uy?bV`6i_JqcqZh~%UbjI z?4(WYNk!RjORJRKuKQ@5=5rq9_Ll31_1?ZKY(FXqP17zN$TlbgCt^oIM%sVB<*fs6 zd3%f<3=rnlCP+UDli>+)r^sLhiCsQbsMYtnJ~`6@Sp|GltV`q<$z>fQ1}bB}30pRr zC7%A}{I?;64UPr|EBdySu|ItY;l8SQVtBbt*-dcOJYe(ghQ^C{`&_fnhl*Btkk0GI zXY^lb>pNV2AP2Yo-Z_)bP(cYxWe8g%`Kx23=b8TP#uPVkR9Af)J-NS4EEd@i;J*b^ zR~qGxE#GFJQ57+CJYVkD(IbcZWUEkNnziArDM=df;u|@Gk<|L6Re1u`s~h>n7F)hJ zSHGZTG2@1_x?8sGzW2yDFq=;{Z{Me}jF=d7AQqT_TXSM$29?L>0N^ANoTAPa3>rW68e@VY zV71XE+600on@L~ndNJSeocEa6h3v;7OA(Rq{H;NVNO-RJ#4Vr|UTipj{Q&J8&XuCZ z`TK|>J4pYd)y16Mz1F$4hR8}3=KNs*J>Tcb;m(Cm`^A>?-$!Ve%oS$$zYH3(< + + + + + Apache JMeter&trade; + + +

+

+ The Apache JMeter&trade; desktop application is open source software, + a 100% pure Java application designed + to load test functional behavior and measure performance. It was + originally designed for testing Web Applications but has + since expanded to other test functions. +

+

What can I do with it?

+

+ Apache JMeter may be used to test performance both on static and dynamic + resources (files, Servlets, Perl scripts, Java Objects, Data Bases and + Queries, FTP Servers and more). It can be used to simulate a heavy +load on a server, network or object to test its strength or to analyze +overall performance under different load types. You can use it to make a +graphical analysis of performance or to test your server/script/object +behavior under heavy concurrent load. +

+

What does it do?

+

Apache JMeter features include:

+
    +
  • Can load and performance test many different server types: +
      +
    • Web - HTTP, HTTPS
    • +
    • SOAP
    • +
    • Database via JDBC
    • +
    • LDAP
    • +
    • JMS
    • +
    • Mail - SMTP(S), POP3(S) and IMAP(S)
    • +
    • Native commands or shell scripts
    • +
    +
  • +
  • Complete portability and 100% Java purity.
  • +
  • Full multithreading framework allows concurrent sampling by many threads and + simultaneous sampling of different functions by separate thread groups.
  • +
  • Careful GUI design allows faster operation and more precise timings.
  • +
  • Caching and offline analysis/replaying of test results.
  • +
  • Highly Extensible: +
      +
    • Pluggable Samplers allow unlimited testing capabilities.
    • +
    • Several load statistics may be choosen with pluggable timers.
    • +
    • Data analysis and visualization plugins allow great extensibility + as well as personalization.
    • +
    • Functions can be used to provide dynamic input to a test or provide data manipulation.
    • +
    • Scriptable Samplers (BeanShell is fully supported; and there is a sampler which supports BSF-compatible languages)
    • +
    +
  • +
+

JMeter is not a browser

+

+JMeter is not a browser. +As far as web-services and remote services are concerned, JMeter looks like a browser (or rather, multiple browsers); +however JMeter does not perform all the actions supported by browsers. +In particular, JMeter does not execute the Javascript found in HTML pages. +Nor does it render the HTML pages as a browser does +(it's possible to view the response as HTML etc, but the timings are not included in any samples, and only one sample in one thread is ever viewed at a time). +

+ +

How do I do it?

+
+

Tutorials (PDF)

+ +

Further Information About JMeter

+ +
+ + diff --git a/ApacheJmeter/xdocs/issues.xml b/ApacheJmeter/xdocs/issues.xml new file mode 100644 index 0000000..1930c89 --- /dev/null +++ b/ApacheJmeter/xdocs/issues.xml @@ -0,0 +1,92 @@ + + + + + Issues + + +
+

+JMeter uses Bugzilla for issue tracking, i.e. for reporting bugs and requesting enhancements. +

+

+Before creating a new issue, please check whether the issue has already been reported by searching Bugzilla. +It's also worth checking first on the JMeter user mailing list; others may already have a solution. +

+
+
+ +
+
+

+First check that the issue has not already been reported. +If reporting a bug, are you sure it really is a bug in JMeter, not just a misunderstanding of how JMeter works? +

+

+If you have not already done so, you need to register an account first, using the "New Account" link at the top of the +main Bugzilla page: https://issues.apache.org/bugzilla/. +

+

+Make sure you read and understand the information on the account creation page before signing up. +

+

+Once logged in, click "File a bug" and select JMeter from the list. +

+
+
+

+Please make sure you provide sufficient information for others to be able to make use of the report effectively. +Use the checklist below to guide you. +

+
    +
  • JMeter version
  • +
  • Java version (output from java -version)
  • +
  • OS version
  • +
  • jmeter.log file (unlikely to contain sensitive information, but check before uploading)
  • +
  • JMX file if relevant (redact any sensitive information first), providing a simplified Test Plan (using DEBUG sampler) will ensure BUG is fixed much more rapidely than without it
  • +
  • JTL file if relevant (may need to redact sensitive information)
  • +
  • For a suspected bug, describe what you did, what happened, and how this differs from what you expected to happen. +Does it happen every time?. +
  • +
  • Add yourself in CC List to be notified when JMeter Team requires more information (in this case bug will be marked as NEEDINFO)
  • +
  • Select accurately the IMPORTANCE level, ENHANCEMENT means it's not a BUG while others mean it's a BUG
  • +
  • If you are providing a patch to fix a bug, please ensure it is in unified diff format. +If using Eclipse, please set the patch root to "Project", not the default "Workspace" which is harder to apply.
  • +
  • New source files can be provided as is; please ensure they have the standard Apache License header (as per other JMeter files). +Please do not use @author tags (credit will be given in the changes file). +
  • +
  • In the case of patches for new features, please also provide documentation patches if at all possible. +Components are documented in xdocs/usermanual/component_reference.xml.
  • +
+

See also the following Bug writing guidelines, +also the terms and conditions noted on the Bugzilla account creation page.

+
+ +
diff --git a/ApacheJmeter/xdocs/jmeter_irc.xml b/ApacheJmeter/xdocs/jmeter_irc.xml new file mode 100644 index 0000000..3eff8b7 --- /dev/null +++ b/ApacheJmeter/xdocs/jmeter_irc.xml @@ -0,0 +1,31 @@ + + + + + JMeter on IRC + + +
+

JMeter developers often hang out on IRC to chat about development issues. +Users are also welcome to stop by and ask questions, offer feature suggestions, +or just chit-chat.

+

IRC Server: irc.us.freenode.net
+Room: #jmeter

+
+ +
diff --git a/ApacheJmeter/xdocs/localising/index.xml b/ApacheJmeter/xdocs/localising/index.xml new file mode 100644 index 0000000..4c9f420 --- /dev/null +++ b/ApacheJmeter/xdocs/localising/index.xml @@ -0,0 +1,156 @@ + + + + + + + Jordi Salvat i Alabart + + JMeter Localisation (Translator's Guide) + + + + + +
+ +

This document describes the process of creating and maintaining translated texts for JMeter in languages +other than English. English has been tacitly chosen as the project's primary (or "default") language -- despite its +obvious inadequacy for reasonably unambiguous communication -- as a tribute to the Power of the Empire :-)
+The metropolitan language texts are thus maintained by the software developers, while other project contributors +(called "translators" in this document) take care of maintaining the texts in the languages of the +provinces. The process of producing and maintaining the later is called "translation" in this document.

+ +

This document assumes you'll be using i18nEdit as your tool to edit properties files, and instructions will +be specific to this software, but this is not mandatory: the process should mostly work also if you prefer to use +another tool, such as or vi or Emacs. + +

This document describes 6 processes:

+
    +
  1. Obtaining the current texts [translators].
  2. +
  3. Providing the current texts to translators [developers].
  4. +
  5. Downloading and running i18nEdit [everyone].
  6. +
  7. Translating [translators].
  8. +
  9. Submitting your translations to the project [translators].
  10. +
  11. Merging in new translations [committers].
  12. +
+

+ +
+ +
+ +

If you want to help with JMeter's translation process, start by reading this document. Then +send a message to dev@jmeter.apache.org +stating your intention. The files you need (*.properties and *.metaprop) are included in the source archive. +But if you are having any difficulty, one of the project contributors will be able to grab the current texts +from SVN and send them to you. You'll receive a jar, zip, tar or tgz file that you'll need to unpack in your +local disk.

+

If you are familiar with SVN or you're brave, feel free to anonymously connect to the Apache SVN server +and obtain the JMeter source yourself, as described in +http://jmeter.apache.org/svnindex.html +-- the files necessary to the translation process are all under the jmeter/src directory. +

+

Once you've unpacked or checked out the files, make sure to find file src/i18nedit.properties in there: +you'll need to know where it is to start working with i18nEdit.

+ +
+ +
+ +

If you have access to JMeter's SVN repository and you want to pack the files necessary for localisation +for sending to a translator, just go to the directory above the project root and issue the following command:
+ +tar czf jmeter-localisation.tgz `find jmeter/src -name "*.properties" -o -name "*.metaprops"` + +Of course you could also send the translator the whole jmeter directory, but this will make his life easier. +

+ +
+ +
+ +

The runtime for i18nEdit can be obtained from +http://www.cantamen.com/i18nedit.php. +Download the binary distribution (i18nedit-1.0.0.jar) and save it locally.

+

To run i18nEdit, just make sure to have a reasonably modern Java Runtime Environment in your PATH, change +to the directory where you saved i18nedit-1.0.0.jar, then issue the following command:
+ +java -jar i18nedit-1.0.0.jar + +

+

Then: +

    +
  1. If you've never run i18nEdit before, choose a language. The rest of this document assumes you chose UK English.
  2. +
  3. Select the "Projects" menu, then "Open project...".
  4. +
  5. Navigate to jmeter/src/, select i18nedit.properties, and press the "Open" button.
  6. +
  7. In the window that opens, select the "Project" menu, then "Project settings". Check that your target language +appears in the list in field "Additional locales (ISO codes)". Otherwise, add it now. Press "Save".
  8. +
+You're now ready to start translating. +

+ +
+ +
+ +

Before you start translating, select the "Project" menu, then "Translation settings". Choose work mode +"Directed translation (source to target)". Enter "en" (without the quotes) in the "Source localization" field. Enter +the ISO code of your target language in the "Target localization field".

+ +

Click on one of the editable fields in the right panel ("Comment" or "Content" for your language). Press F2. +i18nEdit will bring you to the first property that requires your attention, either because a translation does not yet +exist for it or because the English text has changed since the translation was provided. Enter or fix the text if +necessary, then press F2 again to repeat the process.

+ +

i18nEdit's on-line help is excellent: read through it for more information and tips.

+ +
+ +
+ +

Once you're done translating, just pack up the whole set of files in jmeter/src in a jar, zip, tar, +tgz, or alike and attach them to a JMeter bug report +(follow link to "Known bugs" in JMeter's home page for that).

+ +
+ +
+ +

If you're a committer receiving text files from a translator, follow this steps to merge them into +the project: +

    +
  1. Unpack the files submitted by the translator in a separate directory.
  2. +
  3. Start i18nEdit as described in Downloading and running i18nEdit above.
  4. +
  5. If the translator worked in a new language, make sure it is listed in the Additional locales field in the Project Settings.
  6. +
  7. Open the "Team" menu and select "Merge changes as integrator".
  8. +
  9. Enter the path to the src directory in the files submitted by the translator.
  10. +
  11. Select the translator's target language.
  12. +
  13. Press "Perform merge".
  14. +
  15. Close i18nEdit and commit to SVN as usual (remember to Refresh your project if you're using Eclipse).
  16. +
+

+ +
+ + + +
\ No newline at end of file diff --git a/ApacheJmeter/xdocs/mail.xml b/ApacheJmeter/xdocs/mail.xml new file mode 100644 index 0000000..3d42531 --- /dev/null +++ b/ApacheJmeter/xdocs/mail.xml @@ -0,0 +1,192 @@ + + + + + + Apache JMeter Project + Mailing Lists + + + + +
+ +

A mailing list is an electronic discussion forum that anyone can +subscribe to. When someone sends an email message to the mailing list, +a copy of that message is broadcast to everyone who is subscribed to +that mailing list. Mailing lists provide a simple and effective +communication mechanism. With potentially thousands of subscribers, +there is a common set of etiquette guidelines that you should observe. +Please keep on reading. +

+ +

+Please note that usage of these mailing lists is subject to the +Public Forum Archive Policy. +

+ +

+ + Respect the mailing list type +
+ There are generally two types of lists. +

+ +

+

    +
  • + The "User" lists where you can send questions and comments about + configuration, setup, usage and other "user" types of questions. +
  • +
  • + The "Developer" lists where you can send questions and + comments about the actual software source code and general + "development" types of questions. +
  • +
+

+ +

Some questions are appropriate for posting on both the "user" and +the "developer" lists. In this case, pick one and only one. Do not +cross post.

+ +

Asking a configuration question on the developers list is frowned +upon because developers' time is as precious as yours. By contacting +them directly instead of the user base you are abusing resources. In +fact, it is unlikely that you will get a quicker answer, if at +all.

+ +

+ + Join the lists that are appropriate for your discussion. +
+Please make sure that you are joining the list that is appropriate for the +topic or product that you would like to discuss. For example, +please do not join the Regexp mailing list and ask questions about Tomcat. +Instead, you should join the Tomcat User list and ask your questions +there. +

+ +

+ + Ask smart questions.
+ +Every volunteer project obtains its strength from the people involved +in it. You are welcome to join any of our mailing lists. You can +choose to lurk, or actively participate; it's up to you. The level of +community responsiveness to specific questions is generally directly +proportional to the amount of effort you spend formulating your +question. Eric Raymond and Rick Moen have even written an essay entitled "Asking +Smart Questions" precisely on this topic. Although somewhat +militant, it is definitely worth reading.
+Note: Please do NOT send your Java problems to the two authors. They welcome feedback on the FAQ's contents, but are simply not a Java help resource. Follow the essay's advice and choose your forum carefully. +

+ +

+ + Keep your email short and to the point; use a suitable subject line. +
+If your email is more than about a page of text, chances are that it +won't get read by very many people. It is much better to try to pack a +lot of informative information (see above about asking smart questions) +into as small of an email as possible. If you are replying to a previous +email, it is a good idea to only quote the parts that you are replying +to and to remove the unnecessary bits. This makes it easier for people +to follow a thread as well as making the email archives easier to search +and read. +

+ +

+ + Start a new thread for a new topic +
+When asing a new question, please start a new thread with an appropriate new subject line. +This makes it easier to read, and to find later in the archives. +

+ +

+ + Do your best to ensure that you are not sending HTML or + "Stylelized" email to the list. +
+If you are using Outlook or Outlook Express or Eudora, chances are that +you are sending HTML email by default. There is usually a setting that +will allow you to send "Plain Text" email. If you are using Microsoft +products to send email, there are several bugs in the software that +prevent you from turning off the sending of HTML email. +

+ +

+ +Please don't send attachments or include large chunks of code
+Attachments can be difficult to read and are rarely needed by all recipients. +Some mailing lists are set up to drop them. +If you need to send more than a few lines of code, ask first. +Note that code is often mangled by word-wrapping, so it is better to provide a link to a downloadable file. +If necessary, arrange with the person(s) responding to the posting how best to give access to the data, +should it prove necessary. +

+ +

+ + Watch where you are sending email. +
+The majority of our mailing lists have set the Reply-To to go back to the +list. That means that when you Reply to a message, it will go to the list +and not to the original author directly. The reason is because it helps +facilitate discussion on the list for everyone to benefit from. Be careful +of this as sometimes you may intend to reply to a message directly to someone +instead of the entire list. + +The appropriate contents of the Reply-To header is an age-old debate that +should not be brought up on the mailing lists. You can +examine opposing points of view +condemning +our convention and + +condoning +it. Bringing this up for debate on a mailing list will add nothing +new and is considered off-topic. + +

+ +

+ + Do not cross post messages. +
+In other words, pick a mailing list and send your messages to that mailing +list only. Do not send your messages to multiple mailing lists. The reason is +that people may be subscribed to one list and not to the other. Therefore, +some people will only see part of the conversation. +

+
+ +
+

+Now that you have read the guidelines above, here is the page that gives +you a listing of the different mailing lists that you can join. If you +managed to find this without reading the above information, chances +are you will be sent back here. You might as well read it now and save +yourself the embarrassment. +

+
+ + +
diff --git a/ApacheJmeter/xdocs/mail2.xml b/ApacheJmeter/xdocs/mail2.xml new file mode 100644 index 0000000..1a858d2 --- /dev/null +++ b/ApacheJmeter/xdocs/mail2.xml @@ -0,0 +1,169 @@ + + + + + + + Apache JMeter Project + Mailing Lists + + + + +
+

+Before subscribing to any of the mailing lists, please make sure that you have +read and understand the guidelines. +

+ +

+Please note that usage of these mailing lists is subject to the +Public Forum Archive Policy. +

+ +

+For details of how to subscribe/unsubscribe please read +Subscribing and Unsubscribing +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
List NameDescriptionSubscribeUnsubscribeArchive
Apache JMeter User +This is the list where users of Apache JMeter meet and discuss issues. +
+Developers are also expected to be lurking on this list to offer support to users of JMeter. +
+This list starts part-way through Nov 2011, when JMeter became an independent Apache project. +For earlier postings, please see the Jakarta JMeter User list, below. +
user-subscribe@jmeter.apache.orguser-unsubscribe@jmeter.apache.orgArchive
Jakarta JMeter User +This is the old JMeter user list from when JMeter was a sub-project of Apache Jakarta. +--Archive
Apache JMeter Developer +This is the list where participating developers meet and discuss issues, code changes/additions etc. +
+Please do not send usage questions to this list, see user list above. +
+This list also collects Wiki update messages. +
+This list starts part-way through Nov 2011, when JMeter became an independent Apache project. +For earlier postings, please see below. +
dev-subscribe@jmeter.apache.orgdev-unsubscribe@jmeter.apache.orgArchive
Apache JMeter CommitsSVN commit messages are sent here. +
+Prior to Nov 2011, they were sent to the Jakarta Notifications list, see below. +
commits-subscribe@jmeter.apache.orgcommits-unsubscribe@jmeter.apache.orgArchive
Apache JMeter IssuesBugzilla messages are sent here. +
+Prior to Nov 2011, they were sent to the Jakarta Notifications list, see below. +
issues-subscribe@jmeter.apache.orgissues-unsubscribe@jmeter.apache.orgArchive
Jakarta Developer +Combined Jakarta developer list, April 2010 to November 2011 +--Archive
Jakarta JMeter Developer +Historical list, up to April 2010. +--Archive
Jakarta Notifications +Combined Jakarta notifications to November 2011. +Includes Bugzilla, SVN and Wiki commit mails for JMeter. +--Archive
+
+ +
+

+There are several 3rd party sites that archive and provide searching for our mailing lists: +

+

+

+

+
+ + +
diff --git a/ApacheJmeter/xdocs/nightly.xml b/ApacheJmeter/xdocs/nightly.xml new file mode 100644 index 0000000..f85f67e --- /dev/null +++ b/ApacheJmeter/xdocs/nightly.xml @@ -0,0 +1,84 @@ + + + + + Nightly builds for developers + + +
+

+

What are the nightly builds?

+

+ The nightly builds are interim builds that are untested and unsupported. + Use at your own risk! +
+ These unreleased builds may not even load, may have undocumented features, + known defects, and any number of other issues. +
+ They are intended for use by developers and others wishing to help with resolving JMeter bugs. +

+

+ These builds should not be used in production. +

+

Where are the nightly builds?

+

JMeter CI builds are currently run by Jenkins and Buildbot

+

These are located at: +

+

+

What do they consist of?

+

+JMeter is distributed as a set of zip (or tar-gz) archive files. + +The files are called: +

    +
  • apache-jmeter-{version}_bin.zip - JMeter binaries
  • +
  • apache-jmeter-{version}_lib.zip - 3rd party jar files (rarely changes)
  • +
  • apache-jmeter-{version}_src.zip - JMeter source
  • +
  • apache-jmeter-{version}_api.zip - JMeter Javadoc (if available)
  • +
+

Installing JMeter runtime

+Download the _bin and _lib files +
+Unpack the archives into the same directory structure +
+The other archives are not needed to run JMeter. + +

Building JMeter

+Download the _src, _bin and _lib files +
+Unpack all the archives into the same directory structure. +
+

+ +

Warning - please note!

+

+ + The nightly builds may or may not work properly - or at all. + +

+

+ If there is a problem with a particular version, + it may be worth reporting this on the JMeter-dev mailing list and/or trying again in a day or two. +

+
+ +
diff --git a/ApacheJmeter/xdocs/overview.html b/ApacheJmeter/xdocs/overview.html new file mode 100644 index 0000000..6607235 --- /dev/null +++ b/ApacheJmeter/xdocs/overview.html @@ -0,0 +1,24 @@ + + + + + +This is the documentation for Apache JMeter version 2.7 API. +@version 2.7 + + \ No newline at end of file diff --git a/ApacheJmeter/xdocs/presentation/jmeter_presentation.sxi b/ApacheJmeter/xdocs/presentation/jmeter_presentation.sxi new file mode 100644 index 0000000000000000000000000000000000000000..3794ae2ca82594a23bed90ac8eafea24d1a3eb9c GIT binary patch literal 210385 zcmZs?1ymee)GgRR1C0|jxVr^+cMIh62)J$IkI_f?jM0b_yw+tv_%v0ZPkYm)%~|F=Vc-NDAe!rjN&!o=Cx-p0(t z-Nwn0#naK8+0Da|+1tUM*~Y=y)xyn9`G32K;Mky>5fljY-_8WwMA^p7-2-?T3+t!< z_r~#mUjS!T4mL?1c1~tzN2_S%&(bJ}_=v#!P-JB!RDrX|e>(&kI8q@UMuR|5)Upzy z>fX6$nJTDZr05c4e+C4yxfgQgmbeI7I%ydd=6sBBWILVUe_ecx7-*S*)i8 zv}FcWjhmhxH!x7^1-J9irbWgSM4b92S<#?U`fFaNX}{SA8TeAqCWt>c+Phvn8 z5ECam`qivA1M$WY22@4Rib8O+atMESRmGZ?-E7@Wc)LO*)0O8E3$SnR`VF5C9CaaW zzG&u!ZAOQbU!0{159fsx;=u{cMwa;~MG?W{R@cNRxpK$NF3hEc2&Y65uh{-*g+(BI z#Q*c)13eNd5P9+YSsQ3LkgViqi8?Xk(EmMBwMOq+%`qU*fBXM8Qf#08Ct$3+qW=>r z9yV4EF>Z-ZJZ%5}k%GzwJ}DFe2ONWLP|1K3shNzbA_(M54FUy!0fC-?hk}nmAdgQV z&|hN^h%XZa!gbE=R1pB409%PFih@853I5M2DZn+9o2s-JsBW6@4{!lzDlaVodjIdQ zu&3g`rxBcGblgB7)S>@&s5Ayt0^s97|Ep)Lof^PvtB+z9oVfF3%Y2YfR#wOII~T;4 zEhb}qi^ngKGW(@%79Y*ZI+CwlGLk>!sD6rw!?>cX%m+O`K}4wU_z#s8Ia z-gUm&)8@4GZ*$}2j+>u-V$PZaMU=&NF4pj-I0GM}HxOc2J2VumVon+LN1GX5MG7uz z(RRCG+09LK@{_Ut^j~#q>ZFl_&=^57jF8DSe6~OB4tg>_PT~T8-4^5a9ETiormnY% z=H?=sLL*jTeM^(8m)J(PRNveHh zCm1=gg>>jh9HW=*vgubpC^;@ZZPjcfHx8~o3EEwDK4|cNeN7RyLDw^U?TYeH9x^C> zH`l@tIanMB>FvcqjBzpS*Nnxl85t4Chm797f!h1s>iB3MK8&_3Z)|MA5)iPAO~%dU zx3xJ%k{TB>;o^6q=^H$g$v0H08XA_w8Cfb_vs zF#bnYP?-|YJlhQ@`E~{6@8QRiK(oOp@`CZAk9Q@VrA1sMHFrfxLt|FpjKI`V8dc8& zVQ1(>XK2yU%dsFuU*C^}!fy!ff1A&EJ{0_*fUk+I@H4qwQQF3cfa@2F>)U)8?`+En+V<{z z&4s@HZ^=r%HYN_+8zC_Kcj?xz;SQTnFmU!Ymklnia&bZT8!)#QNm_4Rzs3&t9rF8R z*eY=!I%5Q6gN-SW;Xffl)&IEHeE3H-_r7>jj4L?Xt-?q}3;8P;h}?61)ej>&9A?tr zU>ehWNGT+QuG^9gEuHuj5)uOI{;wN6Ib}e~XfK?Z%@?YD3THK~5RpKH3!S0YWR$2} zX)N^aPZ2q2QXR_`orK^c6m)=!ii8waTDkzS#Hm8T|J5Ole0=9HzF;+@FbIc|eI3>F zr)fs$`ggaF+p*sLIltF56tVE#5;3=}JFf4Y!fXvC&@;<%5Npc>yg`cWY=st%%9p?;F#OBVC$0do~1p+?1wx*`!I?dK| zi;f$e<12Q3Z%;M1(|P>st{wV|TS4yut-#}x+*|&8gcumAM_uQ=UCYfjYG*%xKcDxV zv%Bt(T?~Z5_cbUiY-|MN*3{Tl_Von?8&197{|I<}D=}^j;CI*V#5$ZQKzmLk44F_- zsV8GV8X+6=UbIZ|nI=~#6b%zyGh?fr%SMSVF{3%tjc;%vGKXqwiKFJ`*6A8i!<_GX ze>C=c);`bvO9S=un~sCYugda^e_j8Q(x@VX-lBiyKfky+@wkw88_LKu7c?}u&K3l< z>L6cC*#tW+gT7r3R`m`NJSR?!?+m=X=EMdgGqC9xt+Y9z4GpE*&i4eqc6v@BubQ^K zy*{6t%uqOcPi3;2^(auYi7=5fV*Oad{|udYI_8_LA4MWDte1aJ&=ZHh#}VqJqI}r>zN>Y)W|)Bk{DfYcCawup zRTWxuu&KpCh)6OR0YR%!Hs!;bM^<6s!ED)tsyca4NaX0JZch!n&lC%~9&$at(G4X2 z^~-o@u$+rdFfc-snJ!SLrwY!+#Aui_tzZ1^RqM~8+k=EPo_oEVXus*NVh&_{%f?3n zl@SCt6NvnJ2QlBlw5#BfiXv{OU5&i#Y1U?cF?!in{a&-EiT+_A{BvGin!C@>R66dF zcmmutAYU__EPsFZ-T4tt@aN_4VolD$(Gg7ODc!6_Ox6X8`Nt2{^3+k}OQ4VSJkJ)1tF1IjClEH^#18W^Gc(K42R+@`OymptSqgh!h;TSF3&Voy#}-mmpGS%`k992 zryCJ9q!4PnWXZ_)!?u?rNYGG~TKy+xRO-V6QTPa`$mQYR(v}U^M-^gHQXhv#^2N(d zD-41fT5d;#uNk8f(L}<#uzhc|5h5beov-#k9xt6qpLNEIeTDHle~Y+}5T8iLMB2!! z5s!<%E>)|jOLpss^H97KW@18f+*Ami$~h;m&l-3>=l=1wI4CO)&dfsCxjQpAcR+Q1 zaz_sR%Bro0TQF4eBix@PxO4|}BrG!903RhiTU8L#t$e-qw)Z(-zYsR1QKG6jB45xe z9S&@_^vy-=fU6V807uk{+3tzcdb+yhXDbb6aJ_L(zuV_<)7~}F&=Q;7j;2u{5J*y4 zY3a(@W>41!JkVbc2GN8W7X=L{F{*@LZx+tXM&ee39xhB`&wHQ$(Qf)>^@BxC%k-IN z1pV&poz#D2Yz5qJps^TqHiOfyxahlV7wKQVkMREPaEpw+mwtMB3QRn$T0dQFO62Y zIzB#5T~}APBm8m^27IR%_9~qw%cF)@gZl4AWR+*LgFjJ(*hs|((Gjz0X%PWM!whL4YLNyP7IPfNeD zKRY{%flXd}0tx>?ss1;AET;P_Hdw#czvBp)TQE8BQJnV2zTzHE=RMQWyBLaziLp>n zl*~4mjkdMv_>#+&*UFu$=#*Xtx^pw4@o z8iDRC7OrJ;E?v!Ct`P}!97$!+7RBY5V6%m{6S-W_xk+(dBdwb+31CgXB3yaB+asL6 zA-q5mKTUTSVo)#5snx`TK=iEd*Z#65lBvUZ8>BY zDyT6?(dmAOxm&wEi5|P4{A8)X4ndRyV`7@DrXAqPlhx~e1h=#X6T-OOIM3$?K?kBk zWf_CC20ow;eDV(>nm=>ysN!j=XIP)^XGn9wqHUQyI+P7dStwWJf1Y6Jlh`@+bstS2 zdhf@(Tf944N0acZC&YGp?|XlH=Azdnp8k6_KR@4He>%_WcBoLunFl@m8p^}VYe4BN zI(#nR@%LAm@Y};)cA0#JWl?fZgaY-Sb*H{~@rXI70+Pg^0?2p@N?a=>K9i1llr%yDQ3<&0+YNpTgrDU)IysRf<>Z_MgK$R-F(F{rJTn_l&(;rj z-_OI~(S#%ld8R(aaT(*M^?oRrnPIe=sKwP-__6b`}09yEenP>P*SA) zA#sptQvC6;lWX!tQcUH1;{K8Pap&oKo#}k3toD}-E$0cXOGZ08@r$d*kRoxkAH|X( zpFIBZKJ3P(>pC?p);QS^K@$=wwj8z;1oc8WI_{c+BPRL-3edH$Z;7-8j37sRZOou7 z(*u!QmT{6yPZkFx#_;QUY#c^B9I^-D}B@_=W*CcralZ>165sJI?#Eva-72 zu`w%{Y2}^)#z5SxR4%(3*>C#$`_W*ap}DFTZ4JL0_E1*Nw#0NbcRt_zal<|#35}Cb z_!c#~H(p#^EcCXYqVz^*EVx|1`8dYs`1JUgCn+^Y6jF*Bf=*}sP52F%f)^rDiF~mK z4hhpF;at*IFj&z|KdR4kxE=X+bap;rcCoRu$Id&zELq+B2EU4=W@H@w2McHgi06RU zObxl$E9#PpNPU$%2j?E(1!7{Rbd8zMHwfGP@c;%k0qm{!!Z}`C7P?`-z8Khstm_|p9=kl@>iixSV7_-?VC*t@=9n9_uPne<-;a3Yv z+i~d1x_ToXJqd*=w>=f^rpvqdd;VqLrl9!EWt_9IB^s1?op?ubuW-k>ao2kG?_R5~ zZ=fbGgJfDvt*EtK>HcpEbOO;y!Z#^>#J#T~%XJKLT^lPa-on$rzFS&aat#NDf75Od zy2>GL62f3+V)F8wOy<7}-sFf?|h{_RXi5SP?FyWM7v=OsgDpc7a z3?(W5`5DbCP{57JM7rfPRtQY_E-~%A((BQad_mJTI>=+sv%7;A& zbKL5E6~F(tp)}i4tJmgBx-$EuMqw`O_2CQ7`r2Bngfcrtdg$w1V^4c~d#`sdLLqhw z6evswTP_jH$K$`F3k!0wj@zm;5IhvyeEZ#au!+Ni>xYW`00-u{_!F(PlXJHv(#=84 zJYw*rC_X|C$G`|H!1}$YVH>t(H1Gac#S4PC20`9ao-|JnR(HmKc-&w*)65nm;d38b zTVL1H{jD$2-)J>MOh7_Hf-t;1d(>n#lTC&>{;jr8$@IgR_QQ|@m(!+wBqMFD)l7bD z6cd?$_w~r`+$WJOr9B#~0AqfdYWQ>9e6Rx2cVa9HK zQ^w>RU&9ax!@275p0KFxpPi_vM`)=Z3b)i2PCwtw$=VVh&(C|Yw6~9K-fb~9mSjOV z?(396c1$?tY+)1>6yxUQVq_`X)$>a|091t$X+m8>`5ap6La*HMPSd06CVDaZ%z9*c z?=Kk|5C7|mbUHh^)xk*;h3Kixt=7axQqe4p%d7eq{qeh2koKw>RS1N!w^193kib{%K;8Y2s}XDe|guusRenLU$D3Oy#|(z$;%1uVcshY3wXgLY%rxXPn2B&nNcz;#5I7~EtD748cARD&KLrH|46(e=h)At7aMI?$ajPmA z&gFI)%jSzF)^I4;(*IZhqNaI=$4nv|+c}E_D$X!ug#42(w7WAht@rtVMm@f_?lEslGBOv| z%XJ$h=DkJN!2Ii%u~4BjQ|5u>n9XTi#QIqRpwtRN3{jZ!DxhyqW-PJt@W;JPzuj#-T7;;rZ(%mX`Vhf zncc!BM)~vg`$sAmG+m+4R}z~3%QsK-Gkr9y7h5D`N2@K=UdL~GnrNM%-V{qKnyUMpkr69eF(Rnpl$oQ7 zxo^49m~^(pI&oT(>@Z2_-T{}xxQe|1BKh1ocfFrQg$^z+`IhLU~>rvZWY$LozGV;C%QJhc`R5c zd;wujbUrc_Tg@!DC5fnT*_r0e4@wGVUD8{@uv-@W`DopwbDfeNZ`8l^jmZf_AI8a? zub01X--Vu&UCw^$wMehY@<^p<{;7V*(ca?A*>?6#$N29t#K*?QdGDw|^z=~k>Kv+{ z7uk5-H25|H>eO%;edoROeB!5!=+iu#xtLnbVllHpr}^K>$#riZpLkMS)2GwDy_Deo zg}DCyu`yblv_}PD;XaL1Xipoqz!&d=iwkF~57X*zPbc3Ozjg8WSjK;3HRYt2IRi(K zUjdaIM|OgTj)J!K%5x+d$=fGBzD{SKGzr zML=P%D^g_{(`I)#nbo6CkBufOFW~>+e02rw8rRQNH)=;>ZfUt@Oc3-V(Dr)Z6$tR) zG0|w@!TZ~NABlE-u7JZP~ii9L^|o?t#+p?4Q>}YrhG?-hpmx+0T`5GzuE1B6hFTH z!a zwU{cOY;0|9x&5a*m4G6hUtWGfZ!Cfm!le`=eVX#S-DS@@#LD0SsKMVW{7?#-@<6{9 z7Sy8biwW`(qoSf7(pKO?vmm3AUC-OAO;(Zoe0={F&V)n4rAj{38uh)&+mb+wa-X*S z$>y~4{}{pQ>g*iVR$o6hWQ8t*<02^C|L_s&9$M&#F<`{+*_bHm-5`HkLL}<+03N@< zu*cT}7ctzX&2fWI8#J%A!;~xsHKoGyml!O1VwA?M6W>UfQ_43fz5X|;L;9ir2=&Nc zsZ3zCn72Cu6|AY5b$QE@F1QUGV-74BHpT|BY ztGoVod3Dk0BuIc(kR3!P8FTrO&;3|H_2Kj})ScOV3q}3LkwL5OW_TutJ_WDP=lU=Z zf@J`3YC#F{{Z_++gW?3FI&)F}1~$LjFN*+Z@1^{hUG!sdWLMd_Qh~ZjJjq(V%HzKn z&>?0Gx|wS9J8}!Z^dp!B1^`JU`+;&w9?E4tZk7L7#)W)f0a>a$a6aT*Mtq1tyo{h z-k&pH;ajfwnO(i&EgiSIGa^QQIRVqR-RKqA@zEGf%iu#C1$*W zB?^678C+pK+TTzwozDqdJGUnUp7?D0NM0lbwu!>5;K7=Xq!T?&JedC)mNiQ#;K{!R ziH*fmk7@VPTeob)5)OiZkhODg(2~mr!>He0KRF39F)=9!c)eXN2z-CNZ>VhFVp$b< zR-tc1q(!Qi!&E;O1>ks=-}RxQGYyT}yX&4%W9Y7CV~8qEwoPKg;ivJb6eclEuo3s30|0av0p`qK6Ln7DQ)X+Gn z#`v_42+%IK1_+3;s#t%vOr3kJ;5-q*1-4u9+}js0Qui{yIS&-Kw)&i|sP&P`2fZmyt$$L#g!G|;^GsUibX1-B2UR7ThHQ+4V4NodCCv^ zd;opw>HAbs`*c&a)ShEFYd6fwv((cZjn76bt3}Jp4q{3#A-8ke9g05SfmwzgpD&Xa zE-n;TH}9>C%X1!KruMv;;@%?sW^V~nf6A~SvH)t*=7%Yo>)B9Pq`-M5@mmc_aw9Fo z-*xAz$)CACx&cj<^P!syD{*D?>8LnVsM%q?H7M}qa+IUHH$-)pls`#jErVFl_lv6F zSn?8j(?IV!kOxYo#Sc4~^jhsnIIL%JPWODx3E9k@XvQWr zRQc(|e*jd`n^YlRT(Ihjw62~gh`jJ5#H=Z{HgSY#VuKuHsOlLbaE)a+@|gLh54)FF<5mg;5BhN=9C&thACMBYO<}r7m74VOpL( zac2ADS9A<%mE=r5$)ox;&ID}vQ9Lr%6mma|VJNP*;&s?OJ4`Aym>x?G@~rOB=OQRI?DE92E|!z2nA<*$Ts@eYwf_<6=!sH`x-c0w-SmVZ?WxCE zoxVfX-adC{p#noi44OjbGmcAB6O%2XvfK~{8H+gGZfVWb7Zq`T>1j`w%FU<%*K*#E zn5P-9ztm6KP5x1$+<4e@EW_SpLD7?gKO*5WzfjT89F?qXYO=F8IW$LzpGb@ai`4;B zg(_Nq8=STEod-(YtB$7UL7S%YAxrln*%!Am%o3S~i+Stqw1lrAA!2+%Z(ceA`Ndbp zH0+;4`twkXU0r{0`avmE>35Wr!LVs&}p-5V$*}n22spPwY^O zQfVK6%j|jB&wfJfT|PD8NM{i+T+$tEnwgnN?)#)}OGq@eQ&#phGNU%zM5YK==S54) zL_iy-m~H!zw6JoYJRxGo#v4KmA;ot~%Weojjfh*g){;%@_NEul7OiVogy!Jo?O*RB z;bKVJeuX}qC_L{C*yi(OM0TUI5a-s-@CNXM(Vp(&!vEt1h?dw5Bk`+0vXS2bhT^2t zLirR)bH)TP_`1h9B(k9XE`KNaWZWO@s8k^I8a;)qOjug21~F%2V>7g=6E)}9^bNns zhMwZn=B|=rW9|zPLj}8T3ASz{#gR775p9NVs!FDs#b&QnI?NWbYdJXV@vBdA9AY6H z`Bva&j`-6mFgNyb3_~N znjqItIu5Ul*pAP6!9kxNk`09^!1ey8A|SdR_V zkwj#0sCRr5`Pbv_e~&<(Qmp9FwccaDBOGqv;paChdB6lZDq7SfX}^N+&GB4@Mcbv; zNmLYs9SI4E_dw%htMA=_OB!#HHSp!Zk!SM-U@r5?7tp#c;_st2e*gZdUs9OIcKL{+ zQoM4DfLKh(?f5(8U?Rg0K$rLptaQyIp>f~XVR^~rI~5I`?Fo|RJfxgi?dFOltp_Zf z*Pd+Jls5QQp8nHm{~jHAwUoAy+zElDFLQa_ptZ|KTVq@8N3Pp!0wPyPhpEU4g#n|9 z@tBFo_t$C=$MSZz2bX!`Jeo>)c!m8%J8W-o&8H;9@9rq``VKQ{awxQNy}CFj1RPO) z>s1SnS5w=1P*DT`29sfJM_kDl8ft3P`sU{Dm;=uY zO%@A68Clr|x-p*mO@O;&3;|mZ0umLEM#bm6sEdmWC`iH--L@Oh(nLvVbbyTOkj|uc z#|^D$rbO4nJMH-!0B@$R>m*v3Rifpi0m_zl z{$tiN{>`=+=P-x9^SLej-&_AoqwkpTlVSO;OMnvRV3qqO%i56r&9P#u@)9r+K?~dj zA|Lr3iX>~O&0?WcGX9qu7)3s8lO*SSHG$*hy+s6#)iH{g=s+m;56GX##HouJS&(+B z!tutC+XhmH)xjJb_zAkh*gN8q501Kcv!pxD9Ii#mwRr(Z;q-JF6DD88EQs^9 z?E3hM_3yE+jYHx7L5swErxRHWhQk9iGMG=wJ0jK*CPP}^4bPHOQ=6mo^S1oX zd(N$=irIi{hz=H8WvdL%0phYAiPbAYIwnC?kX=v^#KOoJSA6ZIf@dx)3gMDJ{#anN zOIfKs65NSLvU(M#szpNnV%eJc$3{}4e{6xH!_Loc1%Q-cZD$$&as z(7G1s`8bMnS@-(^FgBXGD4?Q^eCWp##v_JKqWcfriUtbd`nkOgEgV&uOC!s0)0?j9 zq0RWs#^T1nHr-gK>#Mlw@n~`I1S|yTI^7+g9q40ksH?x`94j+-&;8*#lB@9dX_REB z+=hO_HF`sUIN+}XyTB}KG9}6Srf&(p(KnO*7PQ|#->ulZLAyD?IE!e>vLH&mUIN{jTelV&( z(g#%_ol3DQTuc!zagerM;M=(J*YJu)?O=3>I&!2q=@lXA1!2ah8Xi`qDW<&-Sq`0d zLjXg@b%>PO?G{u9QkAX8>df9HeQ!9_a9SK8?-hh-fn1;w&OyY?f(=H%3FiB3Jnn#` z*darfLV5h03B`edzCNO8Myqt74#C7v9uZ4EU+E>%fpZ!DXtDmxIE7l`toIeyUufU~ zj46%bXL06nEVHoSDo#v(9v(sl$F&mLKzqO3&-}^mP!imj(2E*G%)fmL>5|NxzuEXu zyt7PofIl1LkHb4&oKF!9l#(s+gfK6U;bVVOi0*hmRU^TFG8;8@mZX(RsvTpY z6)pHNQ@k#!f4CQBo&c`($7UPji4?zU5c zp#v#l-Kl;=tdoNfZbM1a?Yp|dB7f3{o0CGNcjPicdF-?ggGJ3(&I|czbeSs(qTons zKqilz#s*0sBca>2M9dsiH#Zp)1y?E`g4F-^ix%!#dm}oOB08%jb?xKeh-~kX%9_U7 zWnm&QjuoOC@Pre}03f~PRXsAWcc?6DU zDQDW>K8?j$P>3IsFZ&LKqP+mc;Y|cKSB|=I_v@7fLhTmm^Ct>S9UVOQBqr;1aXYcG z-ex=Ixw*NN3!nH8q>n%%U7#Mmxw}K<112&4RMSI?OHnN0CiC%R1#l#3x?+Q!!1dvj z-RFGoW=)B?cl$Ubq3;M;TQSR$S?HEC`BTc%Z2or^_Ilii;6-s6>|ko*-SyPA&;h^q zDepJuWpO4wRxj4&Ku?lg)I6NmoGsRWri48A?h(`!+5NAz_o8XvL;HtAF8&#v_oju- zWLomG4|?(qeli9p_WX1Ce6K-=Vc#vUJK^G$hmVkVe>Y6J+ zO;&~e$(ET!R6N>Vyw(6qt1w||?^2r`8#-7DM2dN(f!AqC%@m7Xt&Jo4S38n4kekGuya6<}dx^r80$cl?uE+aV<;B39%_2GW>+Ni9(VmSio6FiC9${wt?n9 zmO-!n49|J@e_diNB3jj=4#95?kPUhK$7P`;HLHM)7~b&9%hV32 zNT4vO6|{u|o^G@W!zxk*no6zI3IG|BJ(7)1xmd}!{QLKFr=e{0IIpLNha1wSk4s*2 zfH#aL3pTo8rvsa6aS?!z;ZRUe*A4a<39^v*cw(`iG*lyVj3HDL6Km`x9GGjCB=YHxA4GyAf8;J>-ZQWR5QkNo5k zQOPPWSr{Y9EmPv)0C&XT4pIj`O6>-za-|F03_OankA6gAlP5OGcNZVqZSs-eBincI^v#4D;fR2ui z+?2U&v4UVVahqd#cJ>p>bG_{M7J38@B$FL3-m!@@KKTDL6YbsdL+mIAx4ocOvy zWXhLa+Z6^SMKjHxQ?RpOBQzsO6_Y}%!swcxs?*-qY3m+}HO}RdibMq+i@au6Mm0s>!TEi|ud)+!A6&_xABz zdD8Y4siPD_5zpdF$Jw_@OXvz7C%;h4Uhryc`pMc1v8%iBzH~@QX_RlXvKnI%67E^q zKB>z;mZ?_sr!yJcrQ--+)5BW*3;1B2-4|D@o&i*Cr#ju^r=h+7LK|XhY46@4A7Vg=!j-5RV zab_d75}8R8-1&R2RWv@T*k6~H_eMb0Q?pafBd|y1*)hp#jaBS?1S3rRaRizJ-|hAI zW%Tefh!@bPEj4PWhLE?n$-PbZttf&(TRtGeGO1HFduhNeSCloow50hnyCr6(SH*=J zaNpD}v^uOiUmcU5GX(_&EdwOumxcy%d(ZuO*TA{k6dEPevOIn}Qvtf-pz&lX`H2*_ zZ+u@GaU>)qeg0;RG30Y(xU1qjC24FTe)ji;_N_hBpdU+`)kWroB+92_jPTxUQa$Fin=`n~ThTQwQ45)gXy z#Kgrn=ui&;vlr2r{|!K7F-j$|S$gG^3YY*k7y=>p<5^X!gZ)s(uQCGiYHRP=Fp1e8 zAe!DBuCCo4+?q8uWNk;Mr>9R(Mpq0=PE>gc@mTbP6Aeg6OfQ&#yYM3t<-UjIU+Zdv z60&P;L*4wlMR#>Iruy6hU*gWUSq;7X?wQX@JSe=fyt7X=Z8KB`mAmD>5{RZ<`BscX zrZz{Cl7I38Z9Ry#UER>_O_Mg-kAphjANV-C zX>w|+4f*3IWARJ2{a$+;8=rf?QWgGhw3$oH@h2c+_ZVARmTv_5{FyIfZv#r&N`Yu1 z1eql478frsB~my^Iw$o0R%AT3eN1p}B_MC#1zzosJHK?Nc6V4Sf+Z6i>JbnSTo$L8 zMLbNh9J#o-Y%By5J=-bo&AOGkc=7YZjonG%q+A52a=2;Iai;GQ1(uF8r?f>ze@*xI zi?FN}SOElK_$qYN1{rDFaA$tK2l~fE0`5B81cP$Ni11y~UchNNtsOKUfkNQ8;I`uV zHD%yzy^UyWFuqov5@XzEa?|H1?@RVirh7h!6OgA^z-V;>gumMiBkdFCKcX>^vwvNx z`F7j9w8n|!JI9NmyN%lw-D1bZMAHHe`3mB&jbDmi9eR7z?Am`imJQ0Ro0Sd~94g@o89V#J%g;0h7I4yZf$%#6pd*Q>_ljpQ>2Z;=u&~78 ztg7(#5ky}d&rw`Ky8-6O7U8$|35B9Yr!66Pa8k%wpg}$8IhZ|5a4d{oik1+soZyl9DT;5M+85PR^FMDz%DUG4!YB{t%ed z)*u47ag1mL4|r$g~d2}#h43rdS1 z;G%r|&;KaA;R*P?@d*e^6*qTVw7SviH35o`!&IR=d$S-btXQKg z@AyXMu4y)hjjKE@R{5OOk{vSk8E;H`g*#j2FM!u!@sd6`jqJw~nT%mWBb}iCx;PsE zNp90?vBj<=_#Hi?MCE1Ki7Prl5+X*hs$|Dp5cK8)$w!hhUMw#|Kt+8kh>RTNGWAp^ znV_}aYp$=apZP;f$l$OFNY9UrI%r8{zhe^;{^@ipj=7G^{mc60X&a_M?N$J|otM~C zpQL&B(H~x`n9bdwQdfh82=MU2PZX`FUX>T)HcBWoGY9}J#|yTmG;{yDBxmc$yaiE} ze^F7HSL+#O|B4I3wcLE}^vBZuyKVyF)Q|l~{KV?TABvJv*BIY%4YQE;#w09@#JV-J z=^|8{0Z#vpHp^oyjshc^)_6XF5L%h_3LrofRx9Z+Z&>BHqS+rmSd^6xJY)VOD6C>g zmDD}bCZLRA_w$2?kKAtjM{)fN$jMf!xu|Sk$noHd|tVqdnt9NUNqc>zu3k*2%+@g-_{8FoNT>w5l>Np&&ksIXCT13c0> zl24n_HfqE(tbe><+6~RsggjYoGPhPwsPYb1mQGfdotepF`r`|NbJ(8IScX1%2)+&V_Rd)9Bs(Am3h&T3KMMcGKwa49B!6vT)R7k$+ z&ShWVtiKnt9KMZ{ot-U*2ey`Xu%fT9;wXy}8*blRvGuDwu(~AnirMSlT)dsdD_>rDf^C2d;v}0S`Z)}-4LmCjHz~} zXX9-HdB}ivL*X(r1<=uF0OM|NgYiIUOKOMDSQ43?^3JMU**qLz5HpaD`?4$YUuNHm zLij3+0%gTV??^^JPnGs`mXigmydPK~6rlMbDwoIQN7nB&ty+L>gSqScpLLdwj*h0PI<<<=t11SmK3SPy2cQ0ISd}F^>6nQ$ z3TMN9A?_g|r2#9OEb_b66a`)HKRGwb`W50I)N7t{f6=Tg2GlZW!Pgh0glmbpA?~;C){ljeN6EJCxPw}>xjV53+F)?AHt1}nw9{mx@(3bc2@2>yWs%%i% zKdgTLXQ5(PNJuC-jX}%ztNi`b?MWYFK6dcm(iRFnEdl-?fGNNp{mcFVLLH;hQrPaE z?%Im-`Z1Bt^1)(_7MhPngV=YnS<-`@tK`kBoZD2p^n1;UAsqR~x!N$FtSLtZf+c)s z9}~7o#eocPMSx>rhV2IgrKHXESru}P%D**uaMkeEGNy5))GKKD*+NQi$CN1GjC4OJ zeRHQD?LNM~Z)hRZ(Kv%OHJ{mP$*w=@thd=yClUT9`spHxMI8R!-*?ZR^!1fegPTo? z0(VDw;h58Mko)YlS=)b>Z zYZyn=5a%q3u!YG+{}dKh5BVSK*~>duo3=0J(u6I#HK z;;87c!6^5%2)2H?%DO$@dRLas!o5));C1eJbCw%9`=xp!AVu%L0I%9T;b*Afz{J{C(JlcRlH4~S{R3n*idmwP-1$r!L ztdQk2Ob{KW%o460Sc$}iPhvH;p^Wg4u4?k}`Y3Pp6zb+bUgqgP@29$#WpFhl1Q(Wf z$(Aejb!j74ktHp|Ra*9NpDy4tV^hqZcMnT+qui~#_#*dym6ZxaF}>fK1W)=}d<2IK zo5)M$%FRn7YhwrdR3%9J=04N$m1K>~ zXOwM}o}x(afZgM&Q~}0bLUB&tZs2gCu;g?%g7g=7J$R@RPWBrk15x)yk)dVtj=6t=$pax6L-^na?Okhz%SL*K^ zY(!Ye1Ha@7+%v;f09FNHvl`5~srWdG6ebt%I>k)c{u6d^vr&CPtu+$dhR{6MI@RrEttm&i@!+T1X-779>))H( zsYgk_nEMyIadO@-IDQk3T6IT#4lzFVG5Ry2MVH-&ovjvv96y5w)v|_-*gCc-KYmrA zlQ+tMTzf)?SAICJRk?k@ijAC#zuom8E(0eDsaE}3>@PgsZ{rasj%#``Uc%01g8`P7 ztmd%6RI1afh*}0~0_+mO#GvheR92^G7~E(S_E%IXi-2tH{=Lyez2)ylK_ndzHD2{6 zisQVxhE?ce_R#g=`M-|9N+HM2KI3Q06X@%0N^|l62y!Gco|s(F6%zQ;C%@783a88M z=ra#1#vMLeIwq_G-`L)zqbim0zz)Mzfp24|;? zu}!NE1zoz9o+gpS4X(cVop$`RgfkA`H6bJ-QmQYYW6Q_lZ~Vxvz8}q!#2r19x@-KC zopB7u@HkAjR%()sf&E9mYcH8{pj2@1WEG^T|B}vKsr(o3ek-s*_Bybkp~0Zxg_o;E zQBE!ZQT4@}%uG!=0uG!&E}E07ozgmQc~Sz-g=mI@WNaMi=SSP7 z-8JS{%e)vt$l7f4ACeSj~e*2SJF)S^<`On>!WzmZ@ ze!N)Ds}4&zhP^G`=2qG8f*qj=P5dGDixlz>;GaV_G>n*3HHVg$lbffP-vE4a>~{e8 zMx}8#6Rpn1EA%!OIC!#MsW-t2Rr&;Lm8_%0jnNBW3^EAU6q1ONgdU%kbtN*2IGxkS z7J@V{mb_k_COwS*erg_2i91m*h4gDtMx_yw9OvZ{)7n7|kB|~Pp2o8DK7?Eqs8kbo zCfL)(%r71%Y{`%8FtZI?>#~&Kc9n~XmNjci8U_GPt~w54cj7EnHE<6`N`~qM)4KGF zqi;{ud(;0#(_2SH`F-ufLxX^%w4`)*NDLzJp}V`gyF=+zy1QFKxOFp+ z-|Jfb%bL08+-L87)n+J>Rj=SS@3Cls%hvtYwc;h~o}FM~|Ddsk0@EjVgSI==+*)1l z;gerII%mPday(iO$HM#eQ?I1<*JT|h?4YxK-IBpN59p-!c&Yx{vM#G8%uw$8dLk!! z6%iEI&&q&cASOPQr1szjw0Buc%Og~!`2W`ecp)=PA-U@FazgxQX3#m3SY1!;fP7g) z?FU*eF<~6J3I>e%DQG6>zF$>mW$BHA5sP#_<`XTV+sSGRdP8iT_w@mrYPmxER-(_% z^SaL|bH9I;lZ%T_*xzDq!Td=94A_Ntf=8lWGhnI~!5?~;=-vt7?4yWR&$vl#V7zrS z#qh&CYESr4Ni+E-Y9Iylh}Wn$LP1|XEB_G*8UVT_kKZ<|dGG_!E9iw_ zWr$Z!I=bLIwc|jbfW_kAZ~h$nPy5H2s+W`<-5GXXN(A1#|Gtyw&8ZcI*c2|qTq$E2 zLEp6^IeU#ZSN&G*gtnIJ7u32?s$e92VIN3iu_QpGlb0w^ha+Y!$h!@mIC2xfh!)_q zUD7x%pA9eAd@Wjw)rAmdO!E!?EZAxj2{Z%b=1`vwPahu{Mxqd9y)>9`3f#Dcg_uts zPEPzG(w!p2X%p%3bZ%%xLG+N4l*p3~Tu9`mHW}e+{+GYvr^B{|Ts(Q+%vhxp3g`0LQ4g&qrAtQ$7HUzjci zTM|w-p6SPY#=G@%yS>`rp_JpYU4Oggvc_iRBKsV}{6K=4^B{1drM2 z!91LSUvXjew0rd(gE#;>-P@~Cbi-X%jY~IhS(y-lEb7n^6eNl*-rLsJ_VSZK{o*E{ z8D;qQcyBLdqM6|~7JQ@m*)9J+LY^Uifv{N<@j5rxsev#*#06c*4p#< z21H63@uK`PdMf7`q|DXW6at?Lu8#jImj}8xIsWW=eB$Mrrx7C`Sy9{g{exbGk(R$t zQ`my8U!CAMw&sl)mvO>gjSZ@Bjz$vCXGrQ>c+F4Y%I|>hp?d@0FdIKHn=o+gzb~?i z|Ac~f^=Z5VmSd^xRGA}ngm4UB2y@FX$_ox{7`_JSmxm)zXSvqcxYP+Slw4<&D$WH4 zBuD5~S2uMr#qgj6rwqBWB{tOmM(^n(6me1M(kgT&_JuC#VsLQ3yT8|KSjuV9UCNlP z@-&&5MOZ&RjkcUqb6H(2Zc#2M!*gi+>&8@mwJ|IT-N2}A;Tw+MZ0US}-1I~D(_qwV z^o8_jzNT$4EfxUJb3kIF6(Mu#DX9Gl9JC2PssQC*&QK%J+F8s&xL0JYoWF1Tpt%8= z;IF_|mA$U?vxHK4C+8kq9Pv`I@p85uFR5i8EwX)ma^;~K z2u_lsw>jj^&r3{Onz7I?Z(&_=q$5A(QzVvsf2V?3mqH~OJI2n!#? z4jts@$S@VS9N8K*ZGEe*a?mKMqJ_aDze+;cLH;-|1mm8p7K(cW`oTw#-q7`9U_e~! z(fgm|!Ul+zjWXACnyqGb5X}&w+fVlIM!@4)&b@H^e{g5Pi1%v-Adm8YIL2tv^*?#M zzr-WvwbzY(At$>Hm~at6S9b+8c4B8PHvqTKR}_7iuQ7ND z&B-yqV)MOS^AP#?IVUAEb9HmGCq$)GzH;UDq2|loxd}J@8Th-h=xAus_rUh}OCpx! zJ}4wa?1QDHWpjtmZIc89ja*S|a_JvZp3><6ABGE$!|D4Bg(U-6rnun~OpsyMhrB=# zWpLWffd#QDhWs{I@mUWsDUqc%kpPN-e(L%Z7+0Xd6igi1-0DdYT3K6rvNtyV z75FdJrh)|-D_qF$?`-tEQ+(PgwT6jc6#_+>>T&~cA3j7a%oYCkRR?@{{aP^Y9nVkV zVn#arwc}?%<$70Q;oI7{)=Zo^?PBU*)9QpxJ+qgHO_bH!MH*&8S247^dOE1G{aR@^ zomYLE``7-=#zJ9u1NkM1?J{ep^XFYdV_4CN?4Vakk7-+djh}m`XSTln9?Hw9V1yqW z5~G!rOkFQVmp!`;qnV%Aj!#>we%o(q(HAEwdZv>jY$nV#FwO7~>r-ICySA#PASaup z8oiA2rUWMlm90h?rEzh1bMNO=2(P+^Uk5~;1a2?311qK1^PQfI6FfO9yQ;sPd?sHV ziN*19k7lduz9c;50MMhqOZtHZjo`Psk>3n{B?1xMbzma1HZoE&a+lOfPY|st81S`i zCJ_CD9|VO;8;EPAw7KqId?FyIPux!AKqQPB2hF~J@9uIH=nw?5Xup!h2GQCD1iINu z1g?c#SLO+%^VoR@*MCg8vgL4ET3InT2Ey5#;pm1sK_Db>q2q)U4T|R8!{^kc)vKuZ zCGu9uX9-v!Pezp#Q&>PWG&BO>58;L{KA@wc7chJ|-?U4_{%t9ZorBO-o$_aQAzfDx z7R)(8w8fkTN$)l38|}vaEbQ(RDvXCKdZOdLO5cxg#(h<*)rNObtgxWJj8F9HHh%nZ zvauq$ZtS}wOmTyCkr3%hJHP8jn+B>h8Hq63dn9!4pz*@xMbq_b%P)73AcmhGY{QmE zrFo0RiTjiVJ3rIoy(kl0=DT!k3g3LQ7ut>~VE=PjL;A4l!IYLz;lda93;sQ_A(i^U zKXdXX0vo!8tB1`mo671j$Q*@jC|qPZ1~M|6o2#qpr1mTJCO~>bo7L1EirOX$i9dZU zdCSjRJN60ky%O0^FAwc!qVBg!%O}u5;Nq`^`}^K(0T|6!Nt?V>p8>+P&(Gf;)OD2p zV!)Q+ti;K}JFe6nWBXI-1CpX+8-q($6&HiflZLJy+*m*&sHg!CAxNQdoE(N`6WPl% z|FyJevuUk&yeC1y2m?gzeEN{Ph&Q}RI(JCf?oUw{9UbPG{pnf?ob_5G+3#&W@|7V4 z0zIp}|K-6P=I1J^?!Rb2O(?KQp4=7#t+i6<|1*X}%lG%+ZHxGobITF&@gux;bXq!u zCHK(@FfQEuv8}!Exvl9HM=Sb=JT{yc9QQxh66Qi3UrG^kjf!S&Gj?b!ROTUaRlGWc z=Uz%py2DpnWzIh3m(dyJ+w<E zChVg%Ugya#2j(1=g6Jsh?*q;@DO+UKL8lIUk6mpwr-HPsH4J8mh9*+OmogssKMuyw zpvQ(Dqm?mT8rq`~k&zc@7#JIcFSpOP%GM-uRvfKS3f*(zso|#o*-OS0yJwUqTF3B3 zO)Z9%^Zk5bgMF~G7K=hBnYrrF{dyj*~R0cmt!m3g!!?EJKU2g`{_1M^E zh(ye%f^^=A3n9<=)0J9^I1)HKc%;#_bZ^?YOzy)Tq0g|%`Y5=!)n7Z+7sC(nDr(|L+$>4Z1r{Yoccu#i^&&>^i{rIzbyHX`A_D5AxIA1JzKxS zwYD}U0o5tie)YQ;eToOYzg&@O>wuv5?ArX?b#2<`&-H~5etv%V#6(2JATvOqM(f+o zC}@hwIJmi)r{}F^itqb}hT7@qU}g(f-6{6Nd3@B! z?8I*RJ$-C&k;qW9P}V2ixXhr1uKt6;`FIN-EePxEJ4qQuO2$d|Od-F^tXB*_+Jama zm%GZY4nM33gE2u=^EpzlBcE(>nbn8-7Cu@frsN^56$VwwSZ_Ip8TPqy9o_;QDv|oV z$9>DNVMKHiq6RyRXSYh|k8J{MVvm!wnH)OlquwQBa1fIWbg$_~`29S7E zrxX>L(X8bB`fzLX^wx5Q!(Bsu&0YNC$MBcC-*K2cUjs8TbTR=>_Xr|lqP?}A`GpF~ z0$nW?S$#BDXprPreR>mi7_IW|8qMMQf8tulhzoG!O1B|Dvv?) zNBCM<1q6Y9x?jJW__JL;dUSD75TQg}%$f7yACbkyBXbU8G&P;gZG zY+Z;;X40b)2eAK0?E6j3c7N~a@Y?~m0|}$a7Ip=Xxu@|3f~?C&Mig*`rO9K9tfi!c z>9GY{xY~iLPbC@Lz4(2ejp(s^mYrj@zS3Z~rLUb>xMJU_%jx0|?6u7FBl)*Fd!U*~ z;()`{0SYdssNvyZdw>A0t%5pt^zWxb@c%uCzWhK#oWqO%#_rc}0+op{2)R-MR>Wjn zbP?Od+Gk|%i-rcS_wX@VxZ5vhTVhi{EY-9HMcXHf8)BHmC85+v&QHMx7=}lvtUB;Zg*|8K-A`Q zRn@TwsP9P!c|}A7=!~X)om{uy=1?)Rg`D{KTuDnbvZir&UVt){x{0 z`-Y4@>Pm5MIwwl##xFWTX+AWX_f65k6U7XNB5|m2Q~k1;)y8M?Vfk~OV^4I z@dWGhOOff7q|e85YA*&-UfQHle0n}zQb)c(@{$qk$UoRD6Oiw^a_NfwO6PZi{Bffx zuReXCPO&KebqUY#dPo+W31=@|im;)^E$8!rJ_b+SO=|7VL9nf zt;Yh;!=(+wmR-MF5_asP_pZSW@eh-h-^uvgPtc+T$%xPhkZ+FPTaGql-@M#Z!!wmr z9rCOlW$*;EQAUwtAuHi>Q&iJoEf6>jP=nhu=6g?ScnD6`_kL!UaLRlMFp3c}^yniqfIkkGd(~tO@b~%Cr zNPPIK;@`|^E=5j++C9Lor?-ZbiWg%?Oq{{G++c+>a3aHGBZxsC06WR1b99&`np4+F z>>iNnfBKyACMju0u1z~9!ZZn%k9z30-uovU>_n8thH~dKO7o?b!+=w&$5X%W^X+yzL0WCixL%5rrb2$OX$PoPJl~o% zbT#N-4n;!`a!X^<_W#cLwVv7;Rj!ow@LxUP3RhG%l;1bwaZ3>f@;g4dIXkDff{`G= zxfbSH2{AC60A|YKkD>{dPQEwEnj+u=kv}m-0~J?eu6QXjd25bl#9Np-tvMSQ{eE0n z6xCOl6Dm~|;_u22{Cgu)5<4kqSb+D{p$TYRUcu-S>y{kZbM4Ppt+>P>NNttEmVTtH z3u=X^Ad~0DsCC4SSV=PNzb=WY5A4>iT&-3XAvbqm2o@%HXzX%f<*ZB9GQl`W=edBZ zFsE6m#X~pfXz!4gfG|23m6Gvyg*^nmCA(RgStKuTOh-g)JTTrrI9!k+gs!FEE-x>0 zFgZor6{BI|;Wd#)*@_Z&Pfv5?u*10}fBC{u2q;{gyeDz_o0iX=n5W8$ikl(o!h@RM zhmMYqEmSIAN+Tlv1I>nzh}i?UtCrtXehNem&h*xlURqjeNxy)_gEd(J%;gc3JlP-Q zIyx|ABQkT1cqEa6w9fNuP{nR5Wu9=(9ud*MfhyZ#?A~(VmuO$<% ztDMTuohKMi$9DLY2Q%9iLp#N00ZXVQ6+YCwRDCHIX1SgFw27|gH`!KJ7Pp$$n1gz3 z^YKYZ*C6XwMnu2f^v6f*#+>fOfxGkH3)t`3vxI!yIg*8nO{jS7map9H?N7owJ3D2< zVb(lcTu{m5Bhy6!p4ULu(-)u@w&LgK&ni~P=@g&&4hxe`7&JZx)I=VIc)l*i*iHZP z)B^rdg7QECL!CJP1~wI@LcQwX8$h7+A&vhEREcjSCkMy6g0!@@5XfeIQCxo!-#7g* z08Hd{PReCmFr!R3ZLffZ-YX-p{2Gv*;1i2$q&T%VgXfFD48`MB_&_J#`^qA)OeNrxw!rb`^_6Ox$FJj z>Ha(1pAq5VsbAC5tUh|N=l&In-3YnWWOzOl|81#1OUbIs^TM)~f4U|)kYhx_)j4wb zpHPdQ1*N=4T5&b!HapLWGB+ZXDyfMTvRbT$%r|cVH9d=EhW*m@uneRogO<~PbA|wa z5_F+ej7qetCIxr9wwjfVF$2QTEXT9lnV7;t=0r)GB$jW2lCW^BB3+1#aE2sE>BixO z7Hg8%YKYx#cQRwe@MwruAAS}CE@^>6t}p))t194lnOp4R{es(nrnNyJTq-hwCMX?L z`7l#JkJ~4n)B+6Be>}F1!;$JiOW#qJU&2&L=VO=klch4UA(-3v|FdC0RYC4HFm|Mk zczd(eF=qhb8j~+x<3Q(k`?>3GG!LPB?bEGfb;l^nDqA@M^E$Us3|Kb|d5N2evUs&8I4yp< z?n>XdprcXo@qHK5*VjL+)@ystao*AFNQ7~G1gKA+vx`eg$B$Ed)oN<&AMoolkNm|? z8mq29kWa6uVNb>Bm%JD?NaW8(m^|5vRYS|BCOo;#*Y$_5KJ_-JBmNbMuDxF=tKJ#c zzek}{L=2COS=VL>5Vlm&rn!y~Alzmfuo$89vAG^A2H$rbconZHO2T`r8`rpigq;+4 zd8upw$Jq`f5|yn$kUSV4AO9)f`7$R8$X5M<3hmbgh|(h@xa0>4@x6eThYg~+jMI%S zhYz>TUNbpVrN);IGsK8oW37uiG67d)A!!Vf{_-6(>s}{cH0GZqsR|XQ8V5sOI~>Sq zlqQ+y)!dq3IDNarcrj0S5!m*d*^qA6x*3+wWQPdIU1kc%rbszdv9l9Wq`R z3|LbwD0HToVg#b*MJtgA-iwj^Cw8A`V(u>A`wPL|3D(9)$7!Sa4f3!{Z41I z&Wb^a+A5;!iI{o~UN;>a6*1wxRz`58`jG_{FfEh)=7+Bps%7F*VVX?T7{sNOOTX#_ z380(5-6ll9&vQN^!CLRZNXJjP|MbjtBAY zrEp!{-c459<(XQw-EF%)Ioww%YT?GF>LO}k)PR=`Z|z zd}BxJ9X_*B(RH>PogEgYrhhDs-t!-wrip=1b5|D}3_EbO4oYa$Rs*|ERA!BmyZg;b z>cQBwW}WdsGq@S`pt}tNJU1rRO*uIm>Qcj)5WmoCE7ensFgzod7HdMuRX)fYIOPE2 znQwS|xr$phczq;lxktow_L@Y|p_B=(Ds_ z8+i60Hoy%8c-n9@AQms$Hcc4@#_uck!>s=t=bo(3;Ur%g?)tJ~gIY&)*6TUU8jx%!3QxD6QAu1M8Lfu-k zs;iP6q!nZ#X?>kyRgi~8k9j?hmuEi}CdoBS=x$JPLfhh=OEAy5QvW_lXXZ%{+=LAx z6Rc=#LNZ%Ri&Xp(NSr(=sL9#D9kI)`aqMiUY#3z0@zZ;{WKCyj?YFhHeYbZh*{qQwe`|$nzs?CA>jAuUdH`b_%)f?#1p%KoT^^^|j3b2w1)MsKsHnFf3Nm&(Da{xr zNQ1Qyi-<_z_3qO0G7S)*`Mu+|CDWFyCfJyiq|rGLGGTBZZ?!g^7Z(9_+;)I9IzSTW<&t@OOv8V(v-ZPhJP$Rj zJ$x>Fj)OEvak|2!hC97FOqsnd-slf>oSk}MW`>g_KNc8XQHPzVaV9?%78k!}VrF)e zgiuslfe25h{zCF$XCPP3U}q6qc#8~(-A{uA{J&}rzjy>9@!JM~Xktra6g8C!_MTkv zD7-$p@wb!seW{A(<~zk=px-4|uS?N>d?~S=L$Z>755g}(1AB3-W_Iy3kqgZiQ!!!W zFpDw*oyerNavx$yZWI|#X-LR8&BtJemvSqjAjXKW?7%!iGa#J`awzEQQ0UE%u=q%v z+}y5>y#6VwJ%N?S@8IH-_r=-So%Qv>-22|XJ{5`;u|T+Ud8F+-)%Wm~%1eH94)1df zbB_k!SEu8(uBd~I(op{r^nN;*y>%}`KDq6d7nxs{L@UbmmuSrcCEvH-h`2K)3fgO^ z>x2I+9tj%d0ZRrySW*_qV?*^VBdVMBk~1TMkZ@iL zP_IcXQ`S_1`v*JnVwGeK_OYF)8ZGzdK?Ry3^XY6lZH3~H@@yH_<|D90WLQRa*T@j* zN`zi}tgfxSapvz2bX6jCKI0vZYwY_3E9-6ORqS0>q!j+%VENihDP!GzwmV4%lYl*tpaJrZ;wgd+hw)OBMRJCxw+kI1-sQAwcGZk zelPGJh#SEjfsBt`;rKnEtrpPxhw_exmaW%>T%od$Zr{mxH3q%{riBuG?j-T)bgoHD z4%aSmTD`{BzYl&D8)QkEG9x3E*~25uW^a=4>LZ;;BS)m8Ufpw63?9V-@A=f!ynikg z_VyvHtn0^n?^Xc}bn)fS#(r6k>VLhxvSiUO^$f3J{su&pn#*>?%8c?LktZc55la^;FuhaA)=nB_bj?oG&{R`@6>YilSf*=F?MX9f zGv?;q@VOU>G9ei)N@Ra8Jkqt%E%M{Cc9Hk;@}j?SX~`wjHaEv6mpQSKgWG@8YDsXT z(aVJt`fRE~2lsKvo|!d8qy~+g`7$C>V4uw_a)Qps=g^xGu6EwV$*B&hF|`lb(s(jP zmCP)#*i!Ms_kfpY`c4|~i&CSh)a>^`%6^8XIb)YqmfO6BQnRK+S(|Xd4iHkh8qhb{ z>2Uc>%5EqQ`u~`x883v>JC3?D*qLjT%Xl47{n|8@J`pDWr)-W$fNe(-j3I%ig&sFs zwA!o&A*7C~KXhI4T?Z)tUTp91(Bpo`Hyi0e4Ru^&@nOp8*^68UU)2gagY4}Bul zxdaL3fapP&Z7NWX6%mJ%mp4W9GdVl^RLsfOTo%flq9q{E_|P;$ZJvrnV3Hp=14hxx zrN-ff6a)W#XVCRG58FE>A!#;rc69i?PN6mq)s&D3)${bcqgNL$jMIEK=j@0c2}$Mo zDTK7pXt$ycD-p4crD=eG0Hg`GHrE_TC3M#SrNZHZIZ=0gBhUB6~Mz}<} zpIVn3r%^CY&VN`d(E_FZ0#W~ov#aZ>nXRpEVp5XX2SeH(TMp;Wt?)mRroOJOSBoIg zG4TuM+FGU7w+8hpZ9Qtq8pE^j@De{ld3pJiJa|WdCvH2I|NQwg=NYKwc>3#&fdByS zE1U@Ap5)^gj-oda0l^;_lHIYinT&4ATmeG|juKZ)vm6%?(TD)gVgu<-EouUgh>(uX z3MxE{1Be3hL(_Zj4p1G^M4CYfX%K^gZ*6MIg+LzO4zq!kA-1b=%?G#w1^lQ1EBI7* zkeTED^T!V@I3BOTVq~g8>zZT;{&ibRyC*@1z9W7B&EdU_{{GlgzS zYhYddU1yn|lA?jUW<$PR6(EO}ghaIN`Mc_;x)Mc=9wAiU>bCwgDoqpgLW&a8CC26K zD@e3uEJWGedpn>@t5Q}`8LGe1bQOvUr&YvOsd&b5?z}{45MJsEc!31It;1OYk%;gS z_@Cv8*EpX*4{8vy)@@X=Bl829vZo0-lcPI#u?`46$rL#<{op^*^MzT}!{nB(wqT+n zgkw)$lQ%8Mym72^IDqM|{p zG8kq8z*hpxrbyxd5<``WrJzS60UU(gvDKqGpv`~by>G$4s1bH(=l+FCJIhdcOp+jV zSAsTHVDrj25j>wJ5RRJ+b``u|15#99IylQ#3B7k6#g?AN@ap{^P8>y1r>^@cFBXOW zW*F$}vkbZ#ZL+wW0T;hi6 zw6aQ5h}Hg`t{7hUFU=B3cl3sW=ovK8SxX)t`%E_e$$vl`k})b`&whQI*h3~}RKDxU zDJGj4g?wlo1mGq5c_}m@@UhGi+A;(KM&ZI;&1j1=ET$XqZ;v$b$`*Jf$h*C znwA#wx+qM!;63-B$?@qtjOJY@nriQOUj?SbU=6cvPB6xO2rAU5iA7gNRCE{eQ+T~D zN}2Gz*>WoXeULcRFA)awztdm8I3|~tyrQbB?XlQnq8v|FEj|CLk`s|unt`0}wqp6L zRw|I>l(iU)yJqkwz7JgQwHc5dJa)1$ph1!CYXPq;s&=REeKHtYU0rKpNQDG)i(5l3 zIO|XXmXAWhpTXE8SK{j#Okrkbc1>+jvzH%CZUH2(4_KS+;4qR6lBuy9rYBTT!q8@KF(4k*HK%!b}Dx3pb1+fg@7^%P2#D*Eg z4x}@8PfJZ5ZuvIX%@scIpPj9(>Ax*Al}xPp`Vhnq03$avRLm=5Is8JZK@Ite7H$1?QarW881$tAR zQ-#wA`>*$4FN~!C*iti^o|v%2@C9s7YH;2jQQ@l*9)U>hg8er0ten!M3Ad(O4#7k) z#*VeAX|30Go)0fB;7` zV=5}`2Frx_^z`K*2(9r_y_r9N5RgLA2ro{75uN!Izx9%shvf?9U0TJ|ANPA`rRpTzIuSPghJO1^Vc+k@B=8AX>$kIbY=Tr>#9jyoYh%>A$@;S!)-A<)ZpzAEwt#fe zXT8lsVYxzs^)m<8pi8a8MyH=T5RQ6*p?RW~%tHzuwyHj<19Lqciq$HXQ6V0!trimsn9N1wsC9?0vQJp6F@Yk*EKl16F-*X zzqV_stDDErU1JE}k%yDm5G2B6gXq-W5s2m{D(7--jFy4sp+8t&^Fk>Re9~}QOBV71 zPdD`H)hpbdQh3KmLbueIfoV}w!RcRx@A+9-Yq+yk+CU+fsm1uhW2pNfuQs^IZ};ch za{&H){A6FE4evZr6R<*lJ^cK*H~ag6%ceR2oSrC@4Z||C)m$T>=Ux ztA&}E7h6YaHT#e)d##{Z8e&>n+8dAr8E51qk$NuaRvQ$kL2R3FQEg}zekYlQi-)(Y zM6|&P5nWeI276iJ4j1cwOU((2ehBNfO#+v5NMa%`84Y3og?R2>9E)LlU1&S%Uxnp&NXJ*#$T+ElN^7U^F>S&EFA z(Jq}ko8qpaW5~?IGoM2Qvl2(b@0`7;z)<(iAe`N9h@I(Z5HC_JEu!aR-Gg%4P?!CL z5C&KK`FwbB5jB(VP33g?pgm%q)G;}KKh+Ny?b_4Xzr1;PE@cN^{{H?{W!%|_77^8? z))Xeu<`9zie)xdKR(K^Tv+08TE6%cx(?Tc1NKINzk@Cj0yqr`qgWF+Um;D$&7YfNE z_zFAP>xtw#6h2V;wvVEORRRqi$@Y>kB&RM)y!jr+yE46g5lcq76xEQ3 zOZ8Q5Vc@A|O=y;ezP`QmefzC|$gS@JA-KBo_Ya6NYo18_8h0!8I|R})TjBmbpFiDh z1l%@!`=(M0Q_TTkC$ez%rV2Q$($o&NvJL4p>jQn5 zL_{_ef7q^~Fij`v*+2KA?F4n?`o#GNtI$Al6F5)$Z&DHy+u^v)-R^^B(KS)(C~18s ze*V-yJvz$N%`zH^S>(aRw6(LF0q@SS95nT{A8&aM13ig}$?rp8vpdyF5un(-N7wTQ z9=#R#;voXU!OSzI;{Op`CQit`pT7iKYxg&>dD z%Z{+6|7|Gnh);xhALL03qwtP|+Lmu!ycjxt1c5Aw@LEx)I=l@f9e^&{~W}Fe4V_v0r!^`0(q}r{BwT< z&cf%=P>IR=EJuGw$J#;fC}8}d7~K0$)|{2SEJO7fNilFxGpXK&8Ce=GQG%do_rL2+ ziLO&;_yk}$norKagGMf@mHgY^SK{)!%-^{-cg<$t&pjU)2yAp^r1+pXMY2Rd-1B$NqpCKQ}LZL}N zDN7fr#e>CW^zgXFHil9bUK17hkq77ulWLrWVbnoM-3S~=PD*NYcXwYOO19h# z`d1}XY&>R8(gz3nnZNvJHkSm0cCFxf?~SnXeThqxFwlo3D&74)Co(#fm_C(2(7 zQG=!E$xv8Tgf?r~W)awPNPQDuMtS`?AphQ~f77%{b9pdYrL6Xj3&+8qllG%_M0z+! zog?7x^_jX>F))L;`eE_;>8hl$si2`o%#{eov-0R)Xb_KdOWwcm4=NyT#xzSb^nv-D zg*BzkMnGRlY0H)7VC<^ya!8n$0+RbBD@*7EOi+H478~_uG8sSuBS+(2uYavun*xd` z|8Rf*NA?hO!w>=zwAGCCbpK$#e?6=*2=0lpj?CK!#V;Z*Zi40JZ7$TSwolc@UJ!+| zsc94tu`Uo(0Ql))ZQj)cJRRa{;*LNodq`$7I5{~7RPibb3*WuuiG{Ji%~;PA$1nkk z3J#X5PKwxkysRP&otVcH(~<~#B@(H^B;D4~Li4^Jzl7YC1O zz{lgY1)3esARP}NDt>B9oR>=uRc`Di7R?OzQPR*LSOCXoKWJdBx3^c>iOjyUo2D)i zz&i>G3KHmH?O-M0pEs2bAsE+Guxwjw7Fbw6c2E)J)X~kA-sb01qV|tLG}(HB z%HS~RMgUycWg&xAnrMfad3D*ez*G+BHbv&Ackl%b)5g$kYcrjrn2m6t)T<2!1or4&wi=@!r9SdZ zd0hRuK@x$+KQ*TCP#c=Nfp_X8_r=a6XIfb4e) z3bvgr_PG`%Js+~5~Ra<-Q`#59_FCxHNbt><6g|%H3ctZPU#}4GU;V7K| zLE>uOp&tDjziQ{})No8pOvl*8m3OVT1WYYZ=+`Cy)&@+TUXa4vo0i8t!5>%vg1qwE zw{IH*{8Bih_?y{)R=Vu+zAopk;}Hd$Tz%AcZOma{!T6^sdf4m=;eIff*eG`zE!*_jY$suf>lB;2%`qAUd)dyHB^$p*haM z&Whjnsm{HgC5*ZnGBDP&K=BOiM5&0m_*Z*tQbFxim(9#kI;R>i^t0mp_7Q1A*r)Qi z%7D-e0!$^;@N7GOa9L;7*6wRKT&pP7cenb&VE$F&FO$|GG$eI`NXEi-d-FasIUc0{ zFpoNB=5bX_oNrR(YFa;+CeJ=~FYsz^?#u)5zF~9f^CvAMpgEAWzZH`@t(IwIM7mfeU33 z%&0m5JLEPm0=Tn{kg)_{U(O74WgntXE^iI|@6Gf~RIysWr6&5D5Eg^g5ZL(7Fi$Sg z|2G+}xIXJD>Q^ffLKT&^J{ofBu?6@8>^w2yFfz>s^cFkdh`NKhRjMYA140m|%fdTA zfNb)Cpt(nY0jxLWMH!wZpc)<1@#{)qZ0g@T^$f{Le$Qw+QbWRglT?lON zAF%HDfmroYk+R#>)VIV}vckG@>k)`(TJZ7k(t#rN1?k?%+ZKqvTM%d#fHoZINZ^kR zpqNh#JyhqZbgwNwzNjpH1Tc-fx>?977h5uiaX&J!sEq$IP?c*@m&GtKF`+j{`X#*T zYXl23GBzEo4V#Fxy1E*4wx3I8h1Y*bt0&#@E-l!~OrdUv3 z1EJ$2sGfDhexREc1uv%z8*@4dZSnhwelGxSoc{OoePZU{RIvE6Pin387K}|g;*<%3 z+}v)Tnwy*PtBHXSfp?J>FDfdWKVn}OOFgk4G?PQh+#-;o$jDbr6y!94iXRV6lzRb4 z@L_*ZiVpo~qbq=VB-SKxK@^<%uh1-rN=+TcZ+2CR>Is{NUQ}WhMz>#XucTA1gTts9oCWI)2R1;kMm+SI22MAHJ6vr|CoOeB( zv|avbSHNT}owp+(BYSyk?2G*9PtG$%{J<7r1fUr!klWG%&CN%4rw$~Mb92Pp{XiJF zpGE=2{Wj0W7gujsy!;6T$D2}kXn9#6<=}vqVZ$Zik&ZZtk<=E+D!&wy>A-}Uq1mkeAyzB zH$T9Z6zT?f^24!k2efT1lO{vhY5~Ag7j6B!h}`?h!+T#FY$Sf}CdEe6R6dkv*Gsgt z!R|lg+J>9W(boOCAJ+L6A0Pk62~tlt=n_BjX((1$;8T{(>BR}kX7G%|CG;Y1{+hP; zCVVP0GyS!D{SH0SBbxU&mnh8ZUso7-%Bp1H6;rh|1A&ZLce; zT?}BuqEB@(KRLaDl)hm_lrfsR{?lM`ehY|Gn^53gOoE=Vb$m6jI!HI}3{DjzUuUI5 zQms+Xi|!Z#9S3!PG#dL`FQCB-0v=WbUAJJM9ATV0Ou8Re$=X4q2{x2Vc7Uy{DqqO6 zh5v}pj~?i&TjE%&DiY28C9dz3+Tlx_&~sl1P=QYRfmNyO|8{#*tCI3r&z62>y_C3{ z+DEFRv~CFxLOTLF18)FUzmG`9APofqFhmaTC_KXaPZ7$upC1u!JdPHfC>o<*fh32< zY|CB5`SjjO0tlKgDJiK}`bALZ?EetzH*XrTG2TI=*5bO4z5zHg>B=#5Dx?6*R7F`C zTJRR2ikz`+0U(DEMm6B2C(;i-{`nv|WxFaOBS`ePO4IKN%q^-m|5@9NGB7a6YQdEO zNfn#(^4xrjqz#!9b)%jn`5AFH1fA12wJ8lWaR{AXF;bD9*xiMLX_t6LLVfZWBOenn zd=Un9zVB0g|H{q@NMw}68Zq&}i;|LZdFT<<=STV5*2HFzzNv`7_RioN`aT;o_a7u- zxE1hrx5-u3&AfOE%dC4HRyAka6kPP0%_FHNwp?sA-?MZ$Q+He&`oQy&BB# z6%hk$4?`l)Qyaik*)xAuI~^B*Hi3S!P@z$Cycb5ZtgAEw$>NxZ-@216Qll;jn~EUK z~RZ(-u&r$mU0i_2U7m}xuW1PaxAKBSQWu~4+7=-rfQerO<0_~6@)34;ah zhd;Xb;t~?oy#lwu8y>V?KrYu4rCVTQyr-*Ry($jW6%9j^;^NVVKEq3xfy+vr0gCmt z|H3dg;%g>DYM@JdSt5bAZvP)mX8{y-`+j|5=|;Lkq`SMp2kCB*Zc#wGOX+Uu?k?$& zZjkOSN$GfRfA9Qp5Eh0Rc4yh|bzkRm&gl=s#Yp4*L>BUg-eWyKMMXA(iFEtDvOIiB zPB<#s5D-cTfqA-~@%TUg4nhxRDk`cDNv3dZCiMRMgx9?Hk_H{!Fc_o)*LmQesRW@b zaVEFF6u)9?lz2gm$9sr53pYCyXZIg}#9a+7?TTLe7dOzyWYBu;`Cvc2NuB*?GT%?8 z0HB9*&64sx*w5);E<*n0qoy`<_FeZ+I=g8Ie`{3ro`j;}IOcWam(&r^t_w4?iQexD z!YEBa0sD8_f%ZgQv#7Kr1vt*3!9jw%!=fw}wdv*G5VkN)XaeNbUKnPghmUU~sPf7p zbuo}Xi`t~-OmO0GnswaorMIN{!K~U|ORQP2vLq`?jvOA%mAQU*aVgSVZf%9apAQHO zyjjc>ekh71QQPw2n?F*%r<|Xc131?TkaE zSXZ;nsa>Z$rnYlxrbVTU55PCHGE}J|vh3o~(Q-VKC(aLk_f0o^_HI9yDn>#%R6!+f z?>1Y&fM@g%CFe$5k1L7Qi?qjcB=JTV#gG%0SK4(xU5;pf{RH*j@$ZVPTv_<8e<(PG zUEQGf4(LpSQX{3<|I|H;1hfz#yf^QfBjsQYk&gb9sQX5lSeGRw7lhXS6Nz+giHZJP zANE#!kjE-oHy!gGs_0e%;zRq4nLj*yj$m#Ije57{Y#3otRn;qk;BO!qkZ&YBKRwAG zhKdZk6kq%CV?yQEd0WAW`TQ*W!{j##^462@9v^)p;o_??I8BQ+3d>GtrM1N2$Yd&W z8Ffu(OFetY;GcpY7-6aoHg$djKXa_UoFM2&9Q6W4N@Tut585YynX!voA&zaulQSmIbv@=uOr*&* zh;aPlzPNURr~F?}dly5!_W1&zAHm^WPz&Ke}8^2@s{$m z%#Q1}N8hnR{er;E6eJ} zpR_zVj?ubgk5AwFl&}0LMB*3#ea}Pww{HjdF2PQcrH|!B7y!Fexq*tFHz%ppAX;TC`BH8s2^+(WVY~}UxD9 zWH=&4z6h9dM=6vY1E>F8$(X;T6=53}d;$i>6%ZHk14)^UpgC4d2{e>J0OL{j?TxX7Smw^!_*`TEKXGbypKpLu1P}0@^hQ*c|)hN+1Oa3g0oKRYeGV17azzAPxmp6Xo@& z5)W=eJE_V_Kvj;D9D*D0CLd&_pkDv|%Wg0F_iZG{_c~5cQ0RUC-k}p2(TV>LZFO5Z zF+lLbnfoouxy+kxNGXi@o^vY8 z*xj`(yZG;?Nv&RF-}1AJbDwQVljltxeB@3SuF%jR<4GUW+i-91`kd+ZEo7fBx*StO zJW+X-Sjq}d86pm)R)SP7z-)D$Ch$lgZjC)tO1e8S5Jlq`7&x>0-1&^>ddL^TUdN;+ zcXr`RIl@qm_&&IKHCL5BTsx`3rlC~mX+n^%*y;I6u0livdJz=sAfJkvI z89!M)j{lK_r>vui_f2g2w?zY3U1Py@zv=D^^vf4{pO7Y>J!2zYb?=mH+LBv8tc-8Q z5Sh5KvE$g!?W=tcYxP%maAGu+$KT#~N0OS7njSXlOb9&yO}*EY4A#=pmCsj%Y;+I# zU`_ZYj7Lgk_NuKI>}<|*N=tj@Z7RTki6Hj=y=ArbsN>!FSLG6}$)bt(z++GPJz6O| zD*6>B1pPF)t8b4UXV@-DW3NR0$gV**D{qFdwyph{qo?P=e9?dK|39|6^q$aO*w8&? z7rt;}p6Fe3k5>~7c5-qU5S;nn^$nMyj-)u9;^l#Wk8Y z#H$nCi>rj=16c@8!Gx^?UpsRnk<@74zgFNedflyrVo$y$k@NH4MhIY^S9RMP6@PVD zh3^VRu{7Qp3{5*xiY5zutIWVM9?06^hGOXTneb%&^-WNSjD)*8RtDdNlcJ-e+fE3Y z;K$1yEDr4l#DF;jPYQo*vDA8DK|!RgZPz)dSO#2w291i}J62q+LbSB=CpVd}s?XF< zkvFo#;2w_HBXJ>8gc(3l5<(d$yFBPZ!C-1rIognY`&q?iX^_DI7a)4S!xx{nRK`7@ zDzJ(DK7|?2?IjT7Z8pE-16RbiC<|3Wwei$IONpOo2k`jg`Y}-X^euM-(uilIEba3L zT-dN2xFAZQ0Qp-9D!E8(f=|3X=3op3g}%?H8ukD7cvR|BM@HgFo9qRPzeh$w0>$TZ zJ=-8sc;xA0(-U?bX5)=E@cH_|}guSg0_msDsG;q9ip7{3d`+JwaUmF(4 zx&QaXdyUTym^mItq}$~2`VRH5H_puGjy*gxP2s%4_ul7j)3AtEarC;h^fLN%?U}M@C9x zPs|3?(KR0e!EDXX7Pt@u*MNkC8B81;^r)!F_{z36XYe}D`+X{c^f9QOnJSkRz~Y1i zg)2magj*8JJ-vaIHs!c&A_k+@)C+bd7)U{G{xHNsc6fTCYBfm6(-* zo(tPYgsz#N(1s%rH;A|efnwgzTprtS*^)HdU%D#L>ycz=n(_a^qY~x1 zRUNJ7{+a&(&EtE?EIutSE#fgQ{Es}r-Vh94v7%x{zZ0kcOjJ9X(v>5*bWa-_7yt>8 z`#k^EgG7l$uEpzC1oGGih~-UzJ&4{#M#sj{5ga=^)@NZ)k0260MnT}Mz(R)Yn4Y!lXJ+bFG^&aecGxY} z_8$U)8G}&ptDTDJGsxjKIswM)d62Z%`S$UBvg@YAirKhLMdpN-lg^W!4 z=hjv;%juH;&pM|(`}*6fKwql&?Kcai=chNP#^vP@S_A}w#Ucy{)dnAuO;i4x|Jsyd zFzoc|Du!M#o0~maB|aP0T&KPiy`$xKJ)BlDVb9F87spI{2WQ4XZG1RoT7BJ-P4VUx zMF(E8@^x>_)A;V3PPruE92^8?LK5b_#Ly!P4etxO;KEH6G;EXP*Z=TUPff&9iRHw? zJ=u1=*!psJF#yZv`Uj)hakH%2q~D6qd2rkJ=6hMOa@WJ(vMxI^?|zXwcSyP3EpvVW z_XF|Ec`t?$SNfPDaE5*a9YXgUyu4Z*&;J6qDgnrIMp^9jxM-ObJk`U%(5O0RGhmcBqZET zkD$Xf{XAqs^WWC`7D7b;W$`p2et;|2Ld13Ji!|lRFSh6!|M7JZ^8f{kd0c_N$$_+5 zj;5ZN6aI3GB^a?u@~hL%s;hrlWk}=Hl%dY&&ot5X_GOsvYb`A|r~;&Jt*sJ=GAE}WyR|z$WImS&-2WpEg(zha>%@*wT2|UkJ1HAxv3$ENLLj^~B=oEF z?>ku$=^xfy3Z^zPGT3+b4axGBySx1x8ygXQITXG#g~Gz7CRCp_k5FYyX|R#enec4u zw)@UYIe6wpv+5*pE)z7pBQudjoapqlPs})7t|Ozpl<_eql#HQ& zULGhNRXfjM?}>qgiYnv`oN~;~&uhTYX9eJNaoS8jJLavz?a0b2m|| z=OP20uH;|b+t9;+=+!7^4%n74=Zau%OT@(e0llFLo%Pl;Aw~ArfG$#c=ze*+nlK$1 z9_~RwMi#jT+;Q}uwRXK%kP0dd(yksUpvZC_OMKBxY=Dv;X{M7TNwKjDP%5Fdqk9X9o9o+%$JzRfNv+f($!BZj7>44?T+T0E*d|{VuIi-(y*LtzqvZzuQ z+yDO73;cJGnVR$-neYDm=U*&yK5OR_hdBH+0E+0(vRhkq#l3UCcL4#})R9AZ2LyHi zzTk$x6+tlX{(}O_Mo_sqQycWi=9>lBl5=)~&F&Dfko@9OMC|Z4;zmd;7``yDCKe=Y zSE8g;3{3R2FyUA0-t!v;VQ=1S9^^`6J`Xb*4-d!5TWLs3PhMntQdqor9lSmq$JAJ2 zq%`O@Ov{5}6=sC-<>*lM@8W_9cGm$v!`<>iW4{{HEXa>>s61KG>4@MxOvTLPt`2X} zo)mU93MP3cK3?9sqM{;{prHCh7^sKavoMTVk><0G9EFMZc2WD3oKS9z&URy3*2;)9 z+uXqe16POkSY}juLeSNVRF19uXycjBa&DI0{@#yx9j&NIkqUua&+8B0If-PsJR^o? zPuTMbKEvdKi#xOZNeXxvEiBE=UE0A@DDT?C`vEu>4Kul{rxEg>(rwZSra+>eQNb-B zuAL`#c6P*6NB^Y-8fJZuAQNs~2P*J0AjtDuLUba(Iyo_dxOS4@h-3bx8&DA`?*`%l z@$(#iR zQNI1Z0*(_9aQ%vYD%o`*J^7rFAk>c8WrC?62`B^4S|E+bAKZbedxTDq$IW}-?e5-E zw88_k;`dLc>8&{u0%H8`<=vKcg(@A+6ZrkPUhcUoOtpHenJC0)-9M;+vVI8PjTnNxo`=Yvy=|ZrKa|k)> zQ=`!S4-D(|_z$3VkB zD5;n>yx}H}SIK*UP_<_&i5tx56*NN%&krtFFEg5WpD0%{<(VJveqn7|7L0RR8pcF84<8Of+=%tYX0Re$b zN$C#1wcDdPMiFXi$c-mCps0^tX zTG%%Okfj796k-+_=;-w0YHNGj(%ADVHixwD`7>hUV@G{Hb)+`70+4dG`*&H-D0ph>htw?hUGu6X80_X~wuN=?Aa3w}@?PuP*Nyl>%3b zTz|?4>HYD*2uc=km(7qBS^N1C(K?bBEyp@ETkpyHwp@qWA9ZNcbuCuj>d(iI#*&3j z{~ed&99Fe$5naDLmPnYJlNL-VbEQRlu4<#eWLU{snm1u1Hd0a^?5w5hK$vdcS_tCc z<9ufj91}ZX4AvF5m;F?rPbkKnoi`NOf~{Ytzk8%Pia4fM+I>%0nh6JJ4cGs-N1i9* z+@DPXge?m?VI+*@ZxO!ZXl+oh769~nr2?HsyCZ8ktKU&!PnF<8`eRTcU;j~CsIocdW++1zwcn;e03HG%u|3Hg;@v$t@tU&Z@th}R=r>QV zk}Ke#()&UI-vll|MN5tLKQVOTs&p$9+oa;`ML=MMQo9LN=;`lu&E8h7g-)wh__f!P zhXLa(SS&8ix55EpW$FwHh`*w^jaBbx`J4E$Ia_~tbNR*|+Id6l6y1qL0$zKhwWBCz z&TPaiTz@(6WxA4No?e{n+LNvd^stt6c1_^>zy~ftEweg=YDr|d{OCY@XgkNsZ=erp z{NLs%VDaFhy8U`TVA%^gV9mca%A{ zQc+#cObSFTT*`xEy)SY&_RtkDw1!#ZW^kI0bC|!Uodn88i^xo8E~(4m+6nz{?U+69 zEyDH=I`=q#GgDWrvii$kNm|2S9h6J;I)#fc>U9`Hk3RnqMC>fc;`{N(xNg9zA(Bi| zelJS!k6RrL1+_5sPuG9Jo}!-tEX{avn@0djsjzUXNnF+uhqsEg~rRlXZ0j7DEVg=yS&QDLfZ1 zds_>P<%|)<^9lz!#W5UW^cjlqHgooyt?bbTm{M+*Nn`{cLvu2r>lw-OF@trek?Lw% zCyWRbQUus1criI0=>S$_)@0j)Cg` z)aCbqlMMp{k$~$!$$Ygc3nI|xU}I5er?cLXsff>#VLURtRR5KWD}ZVuAHPlSNgC-0 zyJqyi`K4n%K|Mu50lcrD#_}a}Hof%+T`;iWYNR{S!rl@O>?OfF?fxK8vAd9zI@xtB ze;C!ms5AG3cRWR2aW{&!?hcYPcAxuR4B7QP8^+{~wrp;8=+^(PR^1O{;Pl0KYps0} zV;>0@8al?Dt*e4NUcF37{L3X|w!T${AQ~$KEgjw8ANQ{-NS+$p2a8pYQvj|#uYo}@ zblu~)LRNXv{tWajz|pq?-mbEb&`VoaS9d3k+cx75NHdB5XR??FT#e|zoY;GiMMVlm zz;*=rcWRS>yPxO5TWbR^>PB|dM5z;d!0ZNp0)nSY0Og*|%kW& zJx^jXGkq9XStkHb=)`x!Of<0V!!hyk=Q>cW|MPsfK78%H={;W%GZXhmtPFj6evbZE zEupZoh7{e2PdIr2Mgo_l$420PpgnmF0?Wb5LvX+NNqR$Te79o?q`g7Niw_u@1%ppc zcL5XdFm=HvFJ@5Flw964m^Jv_=U7wj6hqHk{&6I7&$nm zXVt&Co_?jPjjUW&#*{wo;N4@*I};a4ev*j>)(;jl(bY|!IATODBt@uYGI75agraPb z3a(i_zl7l_5$PC`Su(qFC8u}w+(@?h#!zutEKd;n0($IT$}Szjugra3Z(twao!Op0 zIjwG7yS_rzsG_Y_-Z->>J>lDIHkH+?!9{NB`9P{=Tzz9VCADyrVKw#z!&V_HAkgUz zK_Nq~G>^Jr{J_qG#-(PBfzyEM+eJt7&!t8Ev+V^?k|*Y`m@YjpJ^^aMjpiE+{v%V+ zmi>7us741zpk6I}u}RaGJ2aTa=wx~~dU#_4C(v+$7Dmb!)lv3=`C_M_;BaP(S7F$h zDVeUmbI9FSIU329lJ0=@gWuZ%0nl&AI|vDqb2m6WU)NIsM+{LI|6fG~{%F35%7|PX zX^Vr|@`DyIAd}(=4Nh#qe=m)2w3QY*V$cLqbUbZV=}UD&Y!=j~Ov_f*n+8L~Q8Kp5 zi~r&@;43!7Q1Fe59D&?%s+qD5an(|@yqf_&DrUVvhA zV1EjpvO(fQT85Q0^mU9RA#%1I3I$us63X%=O7(o6&J2+89 zPorif=mVDUR_{~0i<||XI^hQJg(^A>MZBCb zbl?uOR$MX*>GtM!x9ZPy$0Scg8t@?MDyCJ-uu$%z$WG>3)+96@ilMnv>L-lKZtR0 z|I&9P6)cJF;7ei3d|4RvwKLj-_}EQX#ijBNMys-A#lWrK)=fg-fN` zo@zNG?X$R_WA#51{~o?|Aoa9fdRshrHKzWDG;V~p!D*MFK8J7a#yYA70%vX>6q?0) z1X`%TsgGLBdWBWUIVaJ&x{R%(a ziG?&bMZOIQEW5(-fDmxEr9@9Bt+;;Ld@=&*?&1G(u<42<*JM=mDk@OLRBS+S*Lfo6 z3B8;Rs8zMxL45^lk9m;TkBg;8)rr)IPZ9dxOpRPY95|fHG2AmAxP|xUdVxdB%;6Hb z_qEC(K=a1ucp2v=HfSgRrSV(*lWr8kBrM^>z~z6Z>jko$pxyQ3igA zo+YM`rKL_^-ynSjzebf#y10rOIfS>6L0ZKO#OTOK;TE@xE%kdL3=9mEzbKDda_(|p z`eDMp0%PE8Jr~g=I#)${)+UqNB9~vSFHAj$>~`z*+7<-%tMuk^oDprymGuI#9-NLn zO#0O^mu(o<3nW9hcXM!Dd@{+jt5Z4;8xu3*q6a1Th1@HKb;(XaCaFf~fW-c2dzaxu z)t;5ahrabrFY*a33>(!UJ(Bo-{ZF##6kMgn%O%XTCVdhWOG{kF^(pO$OjU|u?=7}G z2<^Y0!nzS3M-MI@xaD@#Iv*u`snKhi`PJ2>9vB!nmGu!aJcihTy=w4*g83~1OHdl6 zuoxQ6_#b19$(?NR>IR$cj0Hl~c!2=^W&Ay=z$Iba4Nlkeb4flI{rF`7x~@%DDGsXB)g|D48vP-7>wx_BpQS7rkqiSf zg<_)(3kRqEO;eL>cIxXeV^xGKMasjUpJz8kru2cCmB2z2EV;9O|8EJq>24S)c6AL7 ztRlI^#oxqUI3@=PpFlR`7mWqV@+I;p~2Mnm4bd2=>2G;}DBivysvrDTcX z?^s*hzg&-Itw(@^fNT=a9YT)O{ope;EY zq+6uG==CxIj3S|wlaWl=UA((MW+cqXV@2wIoKNwM%>nTi$6 zQN{ghe?*|+p6;esgAOfYHF>*PQoS2thRooi`5|6~9bbSU-W6Rdfp^HzhN?fMy#}{2 zqS0S73e6U&8iG*7cl1|=sID4bxd)Zsrv;IrAcl-iqO$2tiZhPL@o%<~fYBXCx=}?5V;MbWsLbNVZRPZXp4a@dfrRRu!w@iVJM+<{|bA2+}7jnKm^mP%ga3# z=`G)1HHc?l%{-Az=mK2bEpl;#~MUG(vP&?h-{a2Cwp@;#luk>6*mdXmX{LD#WBeK_rnW{1HmMJl``>GHg0pZiH zjwF+HwOEBI?1rHRLQh|vc1B7xVbb~k zV*xhJ@{LC>{}|(@$aMG5)bUk$-f}Lsdy>!b2_yyaATnLA76k2z+kC_2c)oU|7t1z~ zWTIh)aLfdDr+lCBqjE)3Uph~I2N$V`st$4vmohTE3>2gG>^vU4-c0!NdkiNK?}=IX zGvlOPd}I)tvRQ{2skMOklOGC?b*LOFx=$hBnScELOcRhK479PV@-y2#*RB5pbDJWe`A|e}^7*E`Z!(~8S+9Q4mJXkn7 zvRMZgQ@+X$cN<_yVEsL@k=%iwIIMcyo?tAeVgbgr)Nf)JW%>nvJ!!F@6M1kJHVzNn zrGJSIe6Hj}KOM8EV+^q3;ArM-xZU=iflF$9kJD@!L#Ls?HNL)h`S}Sw&7VoT z`gh|p^_%`=I7d9)r#%Z$`)1=?e|ST3m<8qQjAz!VcwFwX5p;*^k_?;DsAxszC{>{9 zbPLdqx;~(~OmQAabhBhtyI2lIJaDBY zRdJ|Y<5peDwK$>ro4D%FGR)1F;HpBO{1jYp+3uPynbA4F9R6#rLrkxqwM0P^+>rIh zBB&lpIE6&WMn2mG$0?PVBf)sElK^JV>yt5xP*;ltiu!%FshVFdJp;q~1iF;NzoSGPIgG6Vs z_+q=X-8`7lc*_HILJCUCj}}r=eV$+<{5x;-sg|RQ)OF>vNmk($WUNp@B&uDorP|Ki%i?w+P-4y<#wn{ zDVFBQ?7bV_C@O^o`F1X0@>Fd9JyWkMf)&glSk0?ij-@rcefMrpbSG4ThrRL_tNJ%6 z1ZL)vz-{Na;Z5a6-_z3_BO*3qF}&T8(JWD;%Yn&uV2l6p1hmuo#P^BCHPIs=$CS)8 zH2xT#>(dx)amwt9ijmi6_{LA2h@lw!v(RZqKL7Qz%A&!-H}_m0c6Mj*KT~i_qX2o9 zNj`H0$zuG8RI`GJ9$H;j_x>+*Dr4rgoGih$t&*2PypgOHQWVF=lN$z!0519}ksb;{ zx6J3yOZ9)CjeNbeQ)4DKa0wW}?V623M%g}4>+#?pc91_Q9q9$$- zw}M-T{O|g&0%r?oC7UnNjja`$J(BxA$752}{)+lwcjp+gU<9_VW6D}8K8b8&aSPg_~}VsBBXp`qSa+ut9h ztn**^?JP9Tp{Mm|w5lqHeQy|s!I`JUujP4SyO8c`X}yof#%A1=k!)n!Vgw7tz9FL~ zAX{(So+llF8EF!vvMCMTgWQoF*OD*}hChSYW)r}U`0OXI3I5={T!1Go{QVI1n+1Rk z-|{Md_Y``$+m77JjJfgz@GG7P`t@RO?>3zVU((1e23Vi7*2WXwxg(4{VOf2HduGUr zMm4tt_~~TYc648VKQoDtTXj9u8-LUQv{WJisz?6s6xZ+p${3eJm>+U>lm%F*sZAjc zXYzA!m_Z$soXa59{DzWmOd2n7lnqs>z~a0kYAdDfU#C+)AUZ#gBsRu|>+rlqlzWwd z(g#^^)2S({z$oNuDkw^As}r%^idt2NB<&|#x53m zu)-%0a$~kz6B38XnJbg^S9MqWP;(K3?L)TZsUaqzO|0(mQo-M{f?La@5D>l(n^q}w zT4z2&7z$7U?t}$vPPz$8kI!fm!)nVpJwG`hIyuIdfRQR6v^cs>mPk%4Y9FA=ZQ$l0TJ*LG>|t_hlXH?Y@e?0%UL<%L?c6f$c3s3a4?kM2*(z6kO?7 z22bX+X#yUoV0BG3&~>K`Pa}L6QR%eQsz)@S$DV>GQCB~qO*&>kwR=|i7kmhne7vay z0VxO+0-nQ8yDoZ-m_jT@Qa_ zPl`*Y)tx%LSGtHN7{`^H+Pyx6+&0)s9x=8tXzh}>ROZ-tmtsv)?VoJ|KTyYhBtlAkGSkA2>zQ{$e3=uo_OFD5llJk@>a} zM-boc_oXg;Ffl=DZnoMSj3pUTy1H2o&8P5(hJAW@P?^^9=*~83XL)oP|N6o2nLX)& z^Y79$&zmKY8q_7c2eF%w|6<|~(OfbP%ng3ZII^(dw`)~8%D_LqfEER5*KV0o|wS{N4!B0E`#G9 z4AU)1{iU390twqq^pyskSj5qgx(WK{sseG93Xl@3u?gKz^F5;BA8Y$AcNcoUeR+XdL! zFU-F<7%7-T!7vV&S9Sg%xdOb^5j|~i+~P5iU^D;5v!%a@AHs3rBsXGcv)20C#5AeQ zv6t$)A@I&bAM{K}~umLqPOxeic7`)@l@Qzz!9TWYxyiD!&hK@?N)0_WR^P_qO~8qI_y^*5XOR0=;6EPq*b_Eko=!OM2ak zTxwXGERyb7KDZ zqny2WAS@oi_EX_-cfQE9D7vDnc?Cb&4?5Kdzk&~a_pK2H3o~%z&Q(FM&kQ+(%!vzO zQ@Cw0HQg;fafgh86L0Le3#*hHrnGZMhUh0VN4Q#y+T23u8E0zW#fKWlkJ{w;Vxv?q z)Q7>0OD8hHA{F6i@W`+^mMix%44wtCrUq*}_esd9XOzZhf1{&lTp$P>mAL!R!sVdl z7rJCJE$~J9j$a8L140kiyMW3x!oSs0$$fGdhS-#L5e&rYHh(dvvAT0y#*Nevu|1_< z7&(YMYL;UGni}xg+{Qb%Ky|5a=R3d2;txwyDA1UyJBcie4-`v7iC8sr!5 z0U5uwb`RI2*n@+c6HV20+0QMCcX6a#gw>YXJZ~FaOU~wz>(JK_c$Y9sY!2fM_wx{TIQ*#F*tmQa`b5a! zA~tI#mh>rc3|VrP%91dbn-sk|(FHCYQzz@+!1>TrJi{GpLKzSj9wYxz()CB2pR~a# z0{wdnMOWCndqpyN^n!jCri?RGJ5ArA>;80->;!qEv@Me|7Qu8UKIiTg9^NKDX8IC~ zveFfs;Xm((4V4+?KwtgB-mrxpS6W1fpA;n>$sm4@ZUe4ytoHkoI4uG!p)bTHl!zoW zKtfXFC)Mi zk3~2gj>sihtW8T}uUe7}91pW;8gzfJ36=O;!OW|7(il zH!}@Kgd+a=E?}e&M1$om><=M2VAWq=fip2#c(ypB*NY8LI%%wMd&KEWU{sZX2Gnwr z7~k8sW*(q#K{*UYQu8Z6M{Mq160_cKyzU}r5M7BuJ-MQ^YpPkP-%<@+U`DDMuqXTb zf5RQ8k@M>Bh=VoG7HR_V)QRPDre>=^Y(6c>g}>?l^~)je{0Ky`b+`2x_k6-RjHvEF9Q@fL4%oU=>5@{PuRW_IdZ^if5 z95yvPd>1m3bVC!Ln&!lBG4ch&9Pa$IfT!POKP-?CW&IV~Q{^3&=g06L&l-9m7Id$O zWg#}^V%kLswGOVYQV}#7mcI#8f*ca7X*EASer)6ES4vIXok)p)fY#%J$4Rsq7dKE> zws@}qH%6|r(Z)HkXTp`1og0wQi~;j~DMU=Qz?{);T)t#XO~=7BML!Dn2OP9MfuTXk z##|gmP)Wke6@?g_tz=kN4MiO$1Y^?5F2UJG#g+*#F>G5WiI)+l4Ng3-jm+(7NEeX_ z0zpxy8kK@068Mc>m6sFc498ehkTFV{4uctR?IG&^W z^h2bnk)vyuFhSy@jP!$$KXxzMG;$hnn}7fJnxb>#l3g1qTTohj`Y86Rrb0&NQx8v+ z3Z5uOY*7rmmzL=l!EdJuwg+hm{Yt2?udZU%cC4Rdt3Sk)K{g&8*1fJ2luQ^J%8&s_ z{Qd;yk<%TtdvPAa!ms)ABxX};26pksHBttq{`2^r?Cxr$#K*_`MMOXSa2XGej?VI@ z;K_Q3dfcX>roJ^LFR1Cgkf&iwAoF&;jn2)>OU9}T{Gb6-xU^ZIlYP(f(5hwZP!2n2 zVit3s6$f@@L7B3f#zt^EZgAOgeX8N*Ed% z4M^C|VM8kxrRMBHBSkr5wALN$4|85etQqGXm8@1VIn63KW z79p%%rHzWRylbpQ9^`i$A|AT09B6OGjJ|c|P9`NPc`tf8NE^c0((*BzE}U*g+}fjo zM{uNkXnap&pzg0BE-v5qeLpQuWqV~m!q;W4uH`nj@Qspo3P*+zEF2+8c|R{o4c`X| zOdNG0ICiwm6oeXd#fqw)7h)QW2nqiqJU4)sR)2(WSLaHMVA6d@jfpqhaTYT4ciOtQ z!Nl31IKPLWMtyt36i^;GNdF2!#`5n85L2m0^oOzjCfUNNE?2n7Q$AGq@gAGExKuf? zrx1p+KeENF(WS5yMGkscc;q5#u3Xx{&|p^%dXq3fVQ7KDV1Y=JUZTWygLwX~&RB;| z3ey49TWlTSSxcHX5%^P*L2n(8C|zo(!^ik^4EpKzcpafIIFDpaG)_BTY)K1qkQ<4L zcD|d3rh@v{uj!<24fRN*k8xd|{2X&f-E%`yT#m|-QnXVFVIdfe&nueml`;qlao!_A5seF zlPt!YXr;DrF3Mo; zk{YI^u?eq8X;ls%wLPaByZBMfI-bxM3|zQfV6#=T`~=1 z6?y?}4Jxgz@%Le60Zt9-0lG}e^!;*W#2A{96+ILjUM=Yq$a0Nx2y-SSWme`od4p8C ziTrinq_NYQ#S*h?k-GL6v_mY_$;b-kOeo7X%I46a9cVOc8WJ=7b#HRt?B_NO4hQgp z>z}Ifx8x@-AwD*@t7DF$Ua8jttw*)V!wq9|8ZHnwj8ajFDi%y(9r?IKp|(9^?E)gM zA-L|OSIWg?S)aHj*yai1H&5Du}r#EfcO*6eV4oHoq2sz{M&s3mzT zfCS(41kf+hkc2(|Z0*6NTCl?!%P+J7CZ)h`$Dk*+FS7<}68-FDrKLhgT1eR#BD@OFeNUHbqe=Obq4_62RA7f<=}c7kK6tPX`K9)IxfnYt~^k?%eZ z{PuN^gkMs7sc|&IgUJG%VzV$kq!1(__R{8Oay1E`c@p;oI6X_ly381rk@nPt;thMg z;6*iE6g%L``ESR{P>mUDJlGV-FcuT~YtZu*govLQZCEUq=)Zoxi6dEC1g&3-b7eqy9>+ZeNWJvVO zBGhO=CwW3H4=6TnLALD9vG*@lq0mer(GDJ>XtrYB&0X~ONtU5a*L578eIGVv(EnYr z_y&}%!(bqU%BiFte|s=0VPinyOYDfSl!pnoQ&BFpw@ZA$=(fSQWyn2=N(>*~ z=n&jJXroX3QRtUHYz~-@V)$2-T-vyZBJ-SvhT&w`j4qN- zz5K)w86D=sQk$q{{$?5kft=l5UbY58K|^m#nm_~6RrFrBLkg3??-70IbP9V=ohBRL z`I5!V>a(21*mWn=xiru=UW!2d5nj(j9&y@0Ia^} z-EjYKye}&veo7f6RXK`oLY8p=y`!Fvg-eQo+lU|_A% zgaX!7X;~&eN-t-80;fk|iS$aM1tg5Dd75q9(~GkDk8tR0!v5xFPhge}S18+Y)$tVk zhco&vWV7`1@5-$o1jIJ-NuP1y8dtt!UE9p|dL(9ZwsH*OrP$1_%h!Z%kf_s@;tX;{ zC?b~`W46uB$#oaoA$3Nw$}UmD7!{aikd%s*tBYGpa|iBodi>Ba zlSh|_`vfWY4f$q?n&MJlPozyM2NAb6%?|VW%VcT_`8Aqc2Xg?y#Es*q!efl#oTdAk z5B8ql?vx9P1=D4?S#`NHkTJ}O$YqGv_+Bbm$#dQrp{p=2D&=D0N>x@WUn1(c=@7@U z;C@MF99BJN3XbF)UQ}Cad(`c)$UqGxg>Sa451ERg5VJ zD!?WH%PlrqYJ810SlI1+qX5i4Q&X{3-UcR`i15ROnt$KEqNWxU+qcMdnMMoqr1iv$ z-faYOtBcireb=e4u>rtLx&o0Qou@D4)YQ}p%sSHQQFifXB{iN=0MSpJ@3*!p-*vK< z$Nc8oGbHBwQ`I(ZYC(MONLuABV)1T4_QEf?zuAfVbLJ!ir7urB=dMI9gJ?!l`W;ZuzbcxbD#+jsK!h|^^MhXV-w(g zlL86Xw&u>>(h49x7dz6|E9tiOg3IGkHyE~XuIErLhs1aHEz}oKeIY4~6JM<|heJux zQB*4;O2M&Zp@WoSeYGAaNFh^kl<>&U?MoDBVv<<>Vq((%noMprr6M-RDlYqcY3f4e zG5s`H$=7fVi#zmwewO6uQZSs%vj~$FHni0uY7R4}E4i?SyfeGs(_H#O*O$_CK;p}M zCEGg@5&O72(>*|By1hONdq67w?e}bgaMu5_h)RaFeSNEOdNZ`{U*~3f8pmGCRhWGo zHKLt|iduUZE|G9S^Qj7!sG&;jr_$fYg$%CQ^};_blmJjpUBBENu~d8JG82JTQBnD{ z4OyAon5R zdH-P^g^Ik&ZU2Yj{BOB{H(iD9cOzs5u0MSQyMpGSs9vUrq2?KI62v}^@g57dOeLZM zYcj!DC!WaeM@(U%lU}FzPE<*kS`LPe1w5B**nQO%4Hmd`0#spovzFC+J+s)BW$kI4 zql~ZfvoHndOMN5A6aUN=|!`P;_j^rKzQhwmHK{)_MP{I1Iv`oa@?@3YpLbB^&D z^&N_13@!-sh#ZWWRs^P|IfFed#gN*knjr|h6riv%Cn(6`q6z{##ukA3SpzS?zT7>R z?EQ6MzG1kOcXvO6%hi6y3Cd8*l}FUzQ@x;D^V40x>wa4Ov?8NR+n` zrydylZ=%a-HD1M*A}_E%SBi~u%8V}lWVoML;W5&l{P}uJDA&vM$m-AN{0dXNaLj+c zHEDW@0VW9J%$dpywUN)pi-@|@_4S6tlMk!~So2N@CFq#g*gr&|Fm&QsPKvr}gyaA# zbqPE#4+-f@rsm?16fsn&?C?fIZ)L~R9V6iX*X8acsZR;>^YaJHg}n=~E?EUp7SU1& zc0EFuBIY!bNh%?aFDYlepyVY1T!tN1|ECB0ZR{t|liA+|ZWVWc9i#_57SGrQl^K%E zb1Xbt7U0%{_nQixRc9L*9ko+JtDfW<8YL9;y0G?o!S4J+j=kpU!L(8!gpC2h)~IY` zz9q$L*+<<5+eHpkgu({UENZ`e@w)H`wF9`veIhLUp;8YgM4^G2{TtF4X{x(#uvSY7 z_Kb-?@4I$hExX6vDa%hc#TBYe&S=!*qxiSWmY1ye%1@F8 z=>TX>fr_G8Ix7jd6bd`h6OIFZ*nowCv1r>cJVGqW0==3XHe@;aYu*2US^%mt-Fn%x z&vE11SqZv+lK0ZG7 zC#QQ=zD^6DRE$fZc>N3b;`}LFCPB!cFtU4RMK`(EOMh8L)Zrk&)@5jN=cRjx~|qB z2pgQaFbQPW7UT}3c_aS6DiUs$!eU>UJ#niK23S|CM1#Lh?*jP6vsZA%CeM5y+cW1| zg&ZH^a!B~&NVPD+eB5L#)tO^vC$jlH=I7pQhF~#;q@Q`=yNTvbm7{3oq_^FW-}zfD z5V&%pS|7eoV)c!Ol)lc3BgVh~@Fk?p*OMO@c&{?Ty`-R0o9(NaLDZKgk!kY|RnJ@5G*jXdAkd3tzIO`T0~ zWNuN`SUedZNl}g>@{~T)k5)>zE=nzRTYnjt!e+89}n(|r##cd(^Igqovnj&n?KO@)g#9SVfu75f^Q$}XG<4D zbFtmKYvXn2Xns=G6XL|<8p`Sol ztoK_3uf6=Z0bDQpg({;b&=CG?W3j8o0*kjz6{6Dm7H0L0#%oKvoen+T>>S73r*9!4 zsYEuFeml>3F+we9i%RbI(~eP*J3m6h+g8jQC&Ovkp}&C6NRKnfyhzbJF9y1Jck+7c z0*d9YAw0L*-#V^fI=XKyYX7YHC6WNIWQ7(=I`$RX8f_xLMVNg+AQO=VpAxNV207(E z`kqZ{Lo}S#{I(ej3RAIHVPp0*?3a}~;$K`R_qY$k>6>(Cz|Wp_TuLp;Ze}$WBXKl) zCC*8ea2r$EY+c`y!1jzqWzZ;0GXKi*0|KJ0g;wEuu}4Ydw#J9@HW3XCEK{?i?~##$ zEG~b43@@{@xpfc5Gv?H=VNg(z{wABso4Rt|ofCC*q;NTT;^5)YJ4-oUWGA}2@I>%W z_~5@NQqAyA2`}--yWQ3mRD;YYJRc!nU6Sh&q`Azitg~0yrKtJ;K8TB001er+FF-XjVvC=E?0o~DSpL@v_dL*%b;GSs&AcQe)VGi z&6#C23pNn8*fEzIMNM9|zjBRh6Trj9cB*)4;fzAjuV8`!W zo%AlfsU@_nEn+axMf(x&UsOtEhsWZuA=Y%g_T05tyDsAMz=mS@ceZwyW1;!nbFV2+ zdL2o)JrHdtJj@`kU-Z0PWqnDg^pr& z-{9a!T|xKXUlEuq0Sa2*86|zIu26#y_@82B3D1CMQFR9Bzt%*nJTn(mG&K`bYwq(u zTDjH(%vo-N(6DWUNEq93nBXH=I!*?6u}X9K%zmEc<>ro&Z^~Bq+m+0Q%!e0cEGmh@ z03_c_BFa4#EiKXqy{1$5#X}mcSAW6f0}kvhA!VUBkg8#1hc7;90YLXLH;1+34Z}}F zup6MVy|O7&V#AS;B2*N8edarN{Kii@-*heS3OKllK3?u_p~AUIO{XL7l>2prXMK`` zF78&OX*MSO4n`9e?T@|=XmqD)ewV-Tv1u9vTZg1_6V_vW+fO6_4O3tZ*0N1Pv zQ|@>U%*k?cjPvHKMK>opKqF~-sD-P+6DxWd#l5C2(@qKhhx#m)r#@J0FbqYtW4=u1 zoLCZe=rOnI6gbx-I~^x5;~zkx!|s%$`t1gIF#}ufjC$oV*xklqV9EMnegcE_%|_K0 zo=y0{ff=2!23|mrlIom5Bl7RkrQ={D4knB8+4#fVf%-<%{(=Ab<=N-5ZvW%=Q`jT^ z;`Hne8C(i8J%OJqbmyAxNjzqF$+LAaVeJlP(3lvqi0rOB?D^@)EPTd19iI38^1R$2;5+i2n}lpjwHuJC%F!PPk&|c3n8Uub z>htUpk|^%os)QGVgM-=AQmdzL>&Km4UElwtP5?|ZEvk8f()TXt+XxDPkVCA2#F8SP zB-d`}e+5nys9FVP36AnzVZO@CFI(qFusfQTsEa%?z$e8a`>LU~`#e&rsw`T4ydyI8`Tqu9wIvTtH> z!m}Oc_KbBEzVKKV+&5v2wpRoQxSU<)q+pB|nXRj@juVMn)Y0)g{E9>_JPKEsVMLAR zAX92t<~EKOSw|7lu(=Al%^AQG;ps$D9rCRN31rewC9Tzu)wE~ z^o02M`0Q}@tYM-Hc>#aDw3=(c(!Dx^#*sCwDlEg=II<~w={g{0$Kgjv!cyxpDHN+G za`mhT2nsUm^WN6OQ~YgG<%F@Y*xdptL7ByESz=XIEz0~}(Yv?9w&>%{534Gwv{zBJ z!bV!JI5=x9Kk3ZdbuoNSy;}&J%WSn`?MwC3`u(ci=S>P-HH$-gituq#9G%j{wsHo(pU|Z47U$iK z*7Mz*)Yh|RMWzoSY{OZ5IQ04XRs59Vd{?opb@~eG2f1R) zd_C#1cR9|XVcEGIWGO+6+m(8$bX<>z17_wSAX%VkENI}|i?HmkutuU(uOIE#osGBb zY4OCCBC{~D&4-klYOQ<_luSwmWI7hWwPN_DdD(M~(bv;cMo|#dNgK>4eA)?z4N)D; zNc0f|##Sn}6l1VcF{fA=@yD4C<>`^$F~xA!N6gFG6}^YgXnFx0FkQxtST?dx&{fvM zh~koLmRw&dsb=u-2M|_HI%m3;6lL+ABT!M5{T9XeT5p3S?sCDHB>J<6P860H8`beO zMiyPL00!wpX5D*+eaS(W0(EO@W$XGmjq{YDV+$v{Apchdd?tAyEr-T>0^jlA!s|CxXOJL97&4+G!srpD+z}cXZU#c4P*P@M+lcdB zkBcqMt*or*7)E^5cPL*xr8+M1Ab~Km^`N5lNxat4ls*m1L0_V`@&)ko*7ciWGK&#a zO%DIrjPC;Zmg8x83c0gV2m(V)QR3wp?j&D*q>t zRKc@4T<2w2dKnJ>10GRc5GGaW?&yR6d^XJNh^#CG6in59$4RK6V=3$woNJ#fjAcPE z-+I*1gZvW|@ZHTkG8BONxBn0T3o7(LVNF=_)_2|adepY){oz5!OY=4+Re`q3KG*+Ht%{@LYfl~b zm2I`zN)g{brbLr#Af9sne#$1T!vET-KhQwq%$XGE^R6{ z{AFvXz@L9QC0;Y$;k`&VTJC0VNdgSvA%a>Jmy5f!jcu}~!&&ojMv~lb^aT?0S&gNG z*Rxz!Y`L35RweArAL`cdMZ{^U`@vH@nZp#dwZEz zWFO1^oepk#uB#$n}1~sfel|}0t)d9m1 zbt_S_ys)Kgp+1WqBrO_;zMC#w%p_5m8IrG_s9Yi+!g}fUz`?K<~C4 zGenneHwrWV1rjG7qrf5Fj*DYvdv~`ngt(?MB}GTa*7nfmvKU<%YV;!=x?xY(jcEkL z;pEp5_QsLs-G2iMKPlew7U~t3B*~wn7IbYf-x_2*8*{O1ik%C8`o|A#8TjPRVeG;A z_CHl--$ST#9>rl%QJH6WR=QDob7h6O+fQ$uQm!`!wzCkHl-@so-PEhjs0ij8$F+2 zF-)+7gJ7afn4Ny-3D31j!+r+`i-{8K4jcUloCaPdPt{d~PNG%VKe-??nuGzP4gF62 zZ)agRalAk?!nwhI{a7@6H)zD`uz#xGIwU2nRfD?_@%-)i&M4Y1kWJ3cwyk3%UsVTa z0tMn(qrJVzbEaxFW5tL+@rRCJ^m z=XkF2`{Mbign-U(I2x-t^#UHsD0@fTqq5Xn)<&)hQ-L+YA>;+)GqeiXx6@qS+b6a?VJY_nagwEX)j8PTznK0K(6kq!pt57*w4S zMUDr6-$d;My3t6=Szu2*#txI1-+RmE4Wu35?##-9~utr9Cbc3y6FtIWS2fM zOSeI_jjWRj%L9wvM)NxHAI+6Tq@AnUdxY#OTMyrD^wVQ^eb9ziHb)<)W12mXkuXSR zqZcBooUjep9qlO^%=kw1PRGBs%l6U@<3?V$lCk+XJ434}+ROMuxV zmoC$H(pi@fbTa3#>G%oISxM7%!T@4@gE$j76pQICR@Lj6@T!5;qz~N_WU=TvI{*bI z9EjL3t~x8=`jmH~`Hjg}A`Hs}jaHzZ_UauC*US`11tMwNo#K@GI~S%sgGmQp#b*t4 zn&5myq4&t8lF|j5yd%GT@XrFPA~{Q$Ha0h%zn(K)LO9&;@rOd^+SRV~e^nF8LdI*b zsm<~OwT3^iX{E@?%HcdwzX{GA_@wJ)VA{(=;P{ZQf2iy1DP1Jc_)(oh=k!U=*88Nc z^7+U^OUa0Iy{TMQ=lFp;bVlI1H+0-0@_jk7OXio7DmmvEOMlwxUlrFPG0vGKsW}FB z)r7>+lgN@K-jnbgNWh@%A>m#Ox!8DD3fmurVr7h_iTtY1u?P7EpdLxXHwTJOBn@E_ zV9-DQ6tPPQ_-hh?nr1itmiM%-u-W}@>BoFke%Nrvn0k~LT8qHwWHl_!Y4|EEOP{EE z>ygn>GIcj)JXA(L;XM>-sp~SLaP)V|GHc|+Wd1nAp9;M}T3TFdX{n8w=YU`?UpS|k z=7%z=ejQ_a>#_KmyJl`%GzxEnGqlh6)yDXK!d& z$J2)30E&L9yx}zd^;fRZ@$ucnrb-N{ZrwvI%6YfDFTwXg*jA?ES{3RA7afi?!{_ ze)&k?Y~dV~HQzgQ!;mz)`R$GHeRbJn$k*K^ep7=ByLZhKza)D};+hLpi>>>2YFUOt zPL#tX3%vavi`Iwk7?{(Ic23Nam0O6a12YX&-21A4fBOhUW=xg}dnAo&9JuDnd-KcD zt3W{1EFel5NJMs5H#IfsfDu`ep>~AVMSLcRcx-zF4I_non*#D6;rH*$`@?W(Ula0W zRzN|HR$$i3>Z$?+d+GA)Db0>N;8(V>y;!s{x~-I5zDyU4V(?^j_I^*lEtF{y06oFl zjEq%S7L=_Va^~OwC!*Ol?imE-=!gipDS%W{+&iMf30ek^b_-~Mo{%@hLoWBfV~|A$ zw{Qd^e8unL0)1mA+)FQntVw%oxI5dtW%F$P2;uq^8sx6ayG>&`Dr7qU$uxhgYIgHmhD-z=JZwI zh8C7h7_W!PhEg)%+}OsBGE$s$cuk&51gE_uY<4;a;38An2seoj%btiJu3xTr3wF;k z(UndQ`_S)+SwHT(Ns!JEwD)@}DiXf=gs{@_-zH^~jn&b+%A%_IOlG59O;Wj^GgN9d zVF}MvT0%n7=hv)CiRahrI|!c?`k?~Xc~;cj)L1)!9^iq{qc>L4nb5>U)RIqMbynBBue3k6xD3`X zXh5z=ORdR{JXt?r&RbRDm9kDchByZN>LP@UP#K z-J&t!HoVgLUh{)Z%8B)lA^IAC4*)*oqcAU>on?kGZrDflzG9AWj>pBJKPa z9uZj_MZd|tp!BGpp!1Vdmc4<+d~A$w7~c2o3??}oyAE$ObBci6_K3IC+aJMma|q2J zJ~XQ7-XxiMcqV_ewJYDEp~>7Dcw|(6hM=C8Nix}NDoYf6G8Q`IuWe63w^Noz8fDBlRNlTrlohh#ltT& zc-PNIrfveF$8O5WL;NNY7fKbezf47lXG4p?MvLho2%+REl?YS`yYL5u{H2e-$XneT7?m;^c#oL2qmzEI{s18*ZiLrpr~`|%&($8 zifVZ*hhrGX(kjX`tOA4c6;dV^;@*y>i1ajLE1!Ouxu~&LKzP{79QDn8Ht-l=SLj@B zv|XgV6Dc^mWn#V-B*bAqejJRqDM!y9MFnWu7?}!Eu(in+N3lV6xRE3EetX46DV=cZ zgusj6IP2?YK7xE-rCMh;!;iqhKl}|;XkUy!(sZ*H6)*X-L|TrFc|Zk>l4*0V2?(7z)D`GAaU_zDJ6mXYEZ z5~fZ1n>fQkorKA7+p1S!$oFC-P>Pw8=iu5Q#ydYWx75ToeeC=Z6?Te%h9COp0uw(fJrPt#ed=$N zA%(sm*^#hNDQoSEu`vbp_ofhtArmnr#Q-=X{})znYP<>sqaNRw$50MRr1$Tnpz?;v zLA_&JPR+^0Rc!?$IZsbdyExd`A+Gx~gJg1KHU{kEjPv9PfS8h6m46Et6L(>qG1@!sBT1`-Ue ze8f~T{|Cp!wl-hv>aqs=w#!63vDUrxKkx#TTsCt|euoRyjDOpOWJ*F6KkqHu15W5Jz2xcfQPwrFVEgT-Xb4{p10aINvG z<}$;$znQJYjeJS-)3JvCPBKUcZ@zMDB$3aq91MP#eLT3E89D>2#fMx7L#L+ zxi{DU^OXe7L~ykQaoyA5F|lp;VD+SstfBRKS7}+-dxM|TvKPN>f@|b{AYR3K#Mf(?^ zUMi7X|3<=eikxMx&Px88oc_S$l&rmgbZ2~^9U^ki7H1j;<@?7SfFK{X_6Zjkmt(#y z6ZCq7RkJbNUX z-H3%&WOScq-yW9Ru#XZ;y92NB?cd6T&MVu#&Qo=-HerX!9Y1>a6aSz;#Ys-YQ?et; zo*Y^oB0=f}n)uYcvRZwAH2OYth0zZ*uMTl+DD31+tg`Mocm{NFeH$FSHh1D;_roI< zPcDmGj&xks9O>PRR+1PlCPYwzgJ)_r6b-Db&+4D{dAha`5W43c62WhnM0-Wb`F4nd z?T5#*?)wMUvV#*h{4T_(x49>@m0}vMb!5ItV2cfSXHfuVubCoS&JDYtiOe9cCK9Tb z#d4}ZzD*B;&NW3GUBs%=7-v$tqnQ^BYodU!nb87zS6YwT7eSw!iBIdSJ@7j?GLfk) zMqMW$*hiflh?UG=(4MCWy zxqxAUK1+CGGvZA1zW>{cxy&aGd0dzvWMyVO`?hfAvK%xvcyDnbk*|+?oPReuYhlDM z?Cn3^SAEjHXKUAt?@Y>^1_#oq%|=$zG}>==p3C$NnHnt0VEI^R#*Y2@i30;zUC;d@ zt2c;q$|<`S*+K7v`B0)Djm91Au)f`3Au$Azus)BAy_D;~z|vZm)(kR|CPVcD;xsY2 z)XiV7f;hpmIP`}RofeSwMSq@Sd!5>bJOQVgF3#ys4V92P9^<#~T!_<8bz819eEYF& z=sG~UUJc-%J5iBbSBLbH`xjC-pqPdbsWNu5A$v%>kOnG-heY{AHgvAwEur7vfdRaX z$l>n>9akyW_HU-@nnnQ{nSFY%7YOU6@u9mBIiveaL68sB3=G!fOVI=xH^ZmPI5X~n zB)|0X3!m5foi~3>Yx!LFPgY-t%Tg4-dH3H6vBmf+!$^YWi~*{Gv|&cpEcn}e;qeyu z;P2zYjqdiA!8!R;p1*S4R$J#k5(cDe**i4_>g($}rmkJQGu;CuslA9o4a5M^zTgU8 zj139q;qBohdvWbcpe1TF$qXX)iWbXpc=6#_+>L!7k#ll#QV07hOC9tVD7war6|FV3 z^z>G+yTNvV1P}l4%{@haM}mn8ad}{e_KFkYObD-B2*T|7q9Du&^Mc^~3X!VqTmaq; z;oKV5x`iLfOL~WtSFwLVzZNDN-e?3|?kl8NI}oi%2`?RHlrLic>Wn93DImaxZzsq> z;YI=WT!Vm*GB$AN3TYxCucf^6ttdRpp1Qjd9r0gAn~7}E^{MOn(4}KLAB#v`n@fb} zS92UjwG9n>E7bR|3gZ#Y-{AVKEOh4(abA{9Po&Hdqbr^3Y;U!X?Z!ZFJ$RMi;r{B? z)({`@S$^l9z7h_L(;ZA4r!$HtJ@&C+k9Vc~#JN7Vjdw5AU8Qugxam##&WL(;@}<5h z)Dh|InHU)G0?$$X5*=K2e>Cqm0G5gDbFF8;HcF;#_PuW?;;V<)L1DSn$aephz*u5b zV-+#H3YN$d7?`QEo|TpDw9Z$tdx9p9odNlT{OXmc=fd|R&~8OubTd52t$cl_O%)gq z2a~d*Jd<+OMre&;7YxHBr;veoQX&!!ms-eT%%d3*lOEQTK})EYWR}aD{U@M+FPqgU za*RI(~?)eX<&kpAyY9*xC&8N$udKY7MTDU?|H|Kjd8zP^$PZ z;QqjiYbL|O0$C=*T_C|N285Y=<1JYeBoYJds=V*HE%C(6WNsswmFLz{`88}6`I_XJW9w*h`7s#nx%=+b=>G@Hq(KMdkbl zLPiH?SVKGnIXuNlfQeO`!tL<>5Ye%hyy2-&4Dt^0LMR%1p2@=Idpgpa2~yoU46EwU z#cdS$VG?RBEW|QZYJ!Rfuf7-`Y(DBc4%o@M8Z_81(xRHp4nbIHP5Ko4o^Ki93_lnF z(Q_bZ(I}ZNVz8~S3d6X;)5iQQ1pu~dQGDSwdDct2iawm1CW z{MelIoIo0D%%jWde05i0Qj!+UC)(t8CcgxKV2zu({*hxuFRt@YG(QAogwvm6da^L` z$z6Ag%At7r@Ve$&Z0Tnt8rtII8D62miA;D!(h%N5jnn0~;tjLs+{oBxW7D6cUsdhT zIhLh#m!bUxH}VFA8i~cb_LOV!yBZou8Ehcmvj{^a$FFjb>-<{oi?ylC(x^m}Z9x2J zB+A+`kr&?r&Am=)5=Z^Xzm40VXO&`gji(*lZPa^GQ9-}iYr-yzH}7?~6c4v3ol2yE z4C;fH20qMh7CE_LVyA-V$D_vewU3mPg^O|N`4&Jh{d)RjePacZp(U#5mH~1vVL&t< z0&C7^Am?Fg*^zYWQ}Yy9GXMa3(h_wBLcl^~f+Y%BnDqS|D~jJyU+?+{gx3`Uk^{}< z!924+I6exK@Hk|@hpLEapwe!jKJTR2zn*@2-J@Q129E&9K>3siIcG{ zPH*iQ8j*@2&3Hkr1_I}gZ(dc^1QS456Ok~#1^EeVY$t&CwpQ2J*r70gQvl#l_sHqE zOP6B!!cdFj{ryzY`KM!$2-yfon+XwKSajc$5)(BsW7i4T%}4mvHSHPBZNd6@I-JCO zoeBaAKD_2j#mr+hwMV?cos=7OsaS8ds4;wQ-XLoBB%z9tHDK1D`{ZQ1+skc2Nu$xT zo8^lAHtXPBgN!XLb{nRr&i2qg$d@kZ%R@IbF^Lz6B!2+9yg^!;gjnEr3tZ?$begIJ zq2bma0>$N=E=Y}xjJ{t=hJqiL68axVTCY;s7Ay%nj#1ZP6=^kju^aw#`KQLrf&U8c z!$Drp$RCE9n{D`^RfkBf8}NVD0j=^gvorp)92`dsD+w*_ZAr`;%xa&_yO4#3^o|u) z{*EG(9C=a-EjnMG4NsV&daQV>zTJ#B=gH?sOrvAhbyWktSSRroJ;Q1m>gDL2yPTwE zkp00ds_)NW57NiyeF%n!Fd|}*5-Ub-@T4=h`N0Gw^SSQtfL&mYFM{j@IiAMB=K!3S zMb)33IS2oX2@UlyR(!4V05%eo?DL`6UZu{s{72rT-e=;>`vT2BPru8llw*Y0kJj1a zE&S)Mmtk@$k-R(3Qu6X|X>d$L8Q!Q*NCX5MvAvJ-SQfbXRk_BB<7QxKGkuQ4pZ3@O z(C+$I5$JW`XjY;dGS~_XK_(|BQ9#;7pt?TGj={j5KE+0g#2A0(dmJwd*U|spAj97d zn28dCS-OtuJd+=gMJ?On3-cFtnf-H)ESTf^ch=V{H^TRdzw^z!<_ zLvnTb)>#5B#?dnx!HE!EX4%7jvQ2KK4ap1OL&w)vGh%UvK>Lko^$xe7o#C zm}h0#8XHHc>-``mqGpaEjO;dNsOF46gcYYQgkAS> z6B!1AspK_u9(o#n!v6&Co&}dmV{fDM*`Yl)anrG}YC-KxX6Li6=CNi8-ML0WsUBl; zxD;O2x^_TFdX66X#%?Nhb~5q}@0{4?Cu0-Qg2GN7+JO^wg0x4ni60#p^iEa#1p?RLjE+CqiAJRKDn;nR(>^}r$`xTREEw zY{K%;ir$bQb$#R3mThSUe~dzs>~;(zT&O$-1~I`~63h*KaOxD7-;`8CEK=H3N6aio zX`-2!UaqN9_M4)FXWQ_ge#`edXTJXj`yq@>j47&0T-{7gQQe464ionWjRgKu^+;a3 z>(D-ShmuKA$jUL#97yk)$ce@CzUCQV4bgE8|X&pUuh{D+TKOd=>XoCN?w@a>D1K0w}%k842 z2+2#p9FxSipO=qsjGl=J9guD>QDIXv%Gg9498TTn;K1=&Ne57Bex@+rUw0LwJBUns zSk=eJr|UD3&n{$Jex`zI3C%zl^2G)#qbG~rod0~j(f%FzyUfT-0J<& z>X2NQFMHH{;``<6Frio@g>rOEXQ!2|!IODiLWtY*(<42E=*oWNK%krrf__c_FI6#+ zXi#M+D($62qY33U?g^c*5@1W)!)Ukw3QZ)6ysV*>=Z)WRPc5zoJ_{OjSbu_v$t41e zyoVC#b7*L2oZK#l!5rX@!+!18iv(Bm-@kt_;I~eDF)Avk1ck9%<@p>m1i=r=N=JK$9F~7JRf0N^+}@^0E%V;^1GK^0Xzg4$v*5K*8W?0-{VwkYyC3 z!Gd4d*mzw5+AKq%zHLh6d*Wk)NyNuaAc<-i*x!$&w^L$B_^;mbK2-vzDP~G)>K!V{ z=Er#-99fta^1O035j)4EinN?^HU~hK`vSJPsG_JL5iv0_=_PxO;Gs@;H#aw=;#E?U zG|-xbW++|@MX3=^Osnkp-K9IuIcVxvl<5mWDw9$z9}dx8;S3<5`yVs_(lf4mds5$i;)-`%rGxsAGU1(u>)~+ zf!~b)BV#Fd;GiEYkIa8RDd>L)b;x#3hz}dlg7U=OTt;Sq^qZLQAYxKJMaPc8^W8kP zv-7z+3-%Rz5&8zZF(*67#<~*;gBXnroE9F?U?K|^uys~64KhGFbwL*A_WBE`5X+>4IsQB;#~S0Dl3X!sDg2Yym>0p! zYHJ|Zp&X>zWE zApMp+n}_+V?CeIDLy&?lK9I)lItHGWnVze|MVzla1Cf!D!7tsfdeOM_(p&Ht2o*je zaYWys^7;x9Kz7pVpaqwNJARl}-;b7x&@t)8>>?T9m?oSeL)gc7O5M>gFj70*PnwmD zIq#q_9`Y*J$4C1=(E~cF%UF3u$V^w47Z+W7QBy#PTofi64$h*z@kRRQEL8ui{o*&1 zO@XU=DKRyb@5^k7M%L!Si-&-UW;P{xJ0q-jWbQJIjukXSO!sD%Lz2o41Y zev@@MPMEM~vsDFjGBjqYBW}I4R@q(YS>^Iy%-%BT|PvIhROh`xwUkA&-otEy`kRW*k!3)4vMm+QQbq3=0(phGR zq-SRT>y2oww_E0dW@vek4ngR$_W83*{uJMFncjkIB%3#l*yfw89|!`#OHX8@V-mHx z)_)l2G-U(x0KJi&^mO#NoO7&IYN%{EsJ9;gW2H{Tz>@6;7@-q2;$kWLKm61pGV$fF`I$DM3~eIs~Cr z>WlP#ejN0o9bhdU|B{xRe9`ZNoEA3rHn6`5geEifOK`%BY=coUS!7rkuj(5!NENB^ z$9ETg@uSg51ETLBALDFk1hsK$?*H);VOb1ZnB9uM+W=Dff%&BEm|1~14Nx$~h1NSR z@DU81A#D`o=QmHaQyvQD`joMiWc;qQDa%a1VqLoI(u;LV^fxu#()q^Wj2>>n0X`|6 zJj46&qNn8ltYdy-zQqk@-K)4KysBM3oJf_Rz)FNdjm~~ii~0Kc`1H9SEpZmK1i&weWXfl-6jvJ)rfdMY5iZMKw++MmP(7orDh#=}b8CPq6 zm8k(PN9DtH5USS?90QqR+byf@zOCXSBAo(<(z#~V)+Mxo2YG2 zVRgX^EBk$u*QK?05rqhAK<7V7Ev3%YMOC;=DhRwNgg1W}fv!5C-T}ZOQl_ z@|U|z2GHI#2N8$BBRb6twkU&(lXU~dXs%f89}A0%I_2mAYXS){*$ml0q2dohQ=l5v zZ_C&v1h48BK+4-W4HAE{2s!D;*wIwZ*04W3PKT6r$(oA#`fFMXQte@Z-VjFF-Zsoq zPl{o(cRh8@@fE&-qh}T;8T%O#p_~DDPR3tWnpnjepJetW+Fw;S2kPkPkW?|#)9*Qh zXK_jANxjd=?v$pFAT=)TAI%UE0G~c={0@R(&PZl#k29cPwuVzQ%;JbB>~2q7V-0{W zI^oc#lgC0ysjn{?Tq0`*A&tp0z>5d!q_H6wI7A7e@S3mj1#7K%KVZEt{yI_#3O<@} zMK*E`LADNu7won&KrUqygaDJbmxk2K%d27s0H;03|<^Ap7Vc>ozy@S87CSHOUCV29f71QF~C8T7wq=` zHP+YGwnkGau;df6o7)=K?v%DNA2Ine?a7ZY7L+jg!!yQ;@7C%(y&TiifwB4vba!oK(#Wj6|1MtOWpW8D#f||Uwr~e5RvJYqtpT294EtCvRj-9!?x+?Gi z34}TWckoc%RMNIm!1UMT5wU=u+Dt-#YFXm^I^=MH$-m`!{cpWF$OTLKt1n*{MD0>e zqw@qfccv1K`-CGS$TBPYd?{O4VRl zdK^yIK=Epv5Y>WRH3Eyw6~gpfGBiDQhE#>KxqnwBYnqfJzI;J_ZIlb((4u*hv#0kKB z?Vy`s2KsH4g3JJaW0_tf@<{Xsy_3-5mF0cn(Y-{mvgZGHHolKvuLQ4_K(E%fxU{4% z_AQYMrvzLsG`*e>eKk5uuMd; zadCf&)8a6sf;EJ(dizl2b1SZ{uAJ!5d;0s4CR`P5#%HVg60IyOk_rb~i$^xrn;i4( zfeDV$yP}~6qSJCULHvWQ_%q1`JsSB(7qIn9FWi?#)QA90U_>XKju{M>RlP>3CikBA z8;+|#T`dGJYn0o{cox|B`o_ovp2?uH8r$|2dMaPYW+?y8mqZ7$3wfTIZh=KvGcHKo z(qHPn{q*7@vd{z(#FApP$;ruKMKj(0|7|3Ttn^#NP)>Rsix^gVN91)9XfL{E5mblzJg*sqe< zVLdCOqwjsep?bOk$cSHh$;}gntxnrRHXzd>$^B&QaTZu{A^|Qsp3P}%0PEtKprnut zO1;+M?++i?V|3(YL~sh$zl@CZ^kY?id1^@8OJ(BHvNjKT=BR~Uf)sP}i4DT(pv4gl zB!un*V`FZC3?iUGq_-p_EfO2kvfAc7uf&9J0>i$kk|&iv3RXsP80}NQr7lCxuBwP$ z2U^@Qqp9bK9C0!%7FxB>d3)(5Ga)gf#iBaof`V;$$MiHb=BO}ote=>TWAefyB2s`_ zpwuPnBwfhY3%7nb&UtsN4S*c{{bbjdnO`;@_OX52Aty6%vXs>TV>!%dpWU$xa%0qW zu@-~~A1r4AbucM{4e`?!iF_Fp_yt3DX)gP&H|&8mX>4ocUX(f;oaK1|vedUh;9?2n z2rtpl(kgYfgKHH&ck^H&X@+~Ms&Vqrd$1AG?F1Sxq)9qO_&KDh`9N*|J0ARt?ITv7;M zuMoeoNaxHZ{1;g=n!5YXQ-c9cK9$2t9P|cvYr7?tE=UnzKfV+eJZ?PNrqw2)uf)~1 z{yuAIYyTCM|6L3!BWXKZTc;d!!@pjt#WRCTOLq-pLaeNb@>lk-8Qu%hZT=Rk^1jA+ z#xhe?_t|t&bnlsT5#>!u1Zqyf5mq~bP_Mis3e!#DQdgY(Lh}_AfnrZzOPT*$%@8d2 zmz65%PkYne(}heB$UGg|w~2bSOM0|Zk6#o=-d((hyDo~`*WoAM7G~w*y}M!l_f1en zzk!-E8TH^NI=Isg43w04gbl1oU)|9&W~B%_0k~XX22vOF`fvKh*RNl%z=7wH;R@@9 zTFe_A{p(|7MZ`OL1Fq%FbZ;Bj0*$BLI5C z2!93*xj1i+nV6U^7|^m6%W5EdznnBrplSp! zh|E_OeZF=B6G-4v@3!9m$DFI*Oi8j{2F7P{EIm7Wd%SyAP?%7xBm2b~GkbskXQ)_? zvY0$2%Gm1I*oUs6A?vodxVRr6ls8=%W}^>CXv+%u@-aWf8s9_^pP`yA<8=O{T=x5a zM12KZlVR8XHo8knIs_$0NH<7#2@;O(ZV(*Z-CYVuhk$fScgScIq(iy{{*UkXe%~K{ z4EBr-w%yNt&biKYt_!fQGjm}BZ$H~@VmEFVK9{sdZ4qi6fdD=W6mpll*kQ;P zVArdCy7KZA6y2(Ksc}NgjEon>e1gCNxIg(~)Z<>RL-G%UG=zM<1nvg}13ig_EbJ@! z`=UZ@m23AFrV$8oTF0KTX(p+mK790F>W11h>!*6ZCjYHY^Rsr#K6d7BUMy=qE?(}h z#zpZUWnlNv^n6(gWHC$jkh2@m>mz^f1$Lqs)IOc1%*(L)qaJr?jBavefV2mFcalhK zJ-n^0O@xbUI;tampny$6*$!ZA|Fr&By2Ld2@@$QKIuRkUG1e>4MfEm!pg7@y+DBKU zSEShGE*WTPlF*V|p~|M3b7#&1oJ7li8C)h3P)co92CxY(MlH6u2{Fv;Pv|oWjbT%g zjUyw^DUgHCxdHIQq0XC(=}F%v1?XPYvfBT!N)zEe8P@GUw?!W6QBYHJ8kS9VDOvvp z0|6)n$viUt6%=FVv+kh%S{8TM$buUAx6R<>7y zH*5naP<##mxL5x(a%clas3&XyyQRvg=jmUx%PAT8g9sb|7kWW<_SmO?X6Z~XUVxD? z^|pC{{o2`7f9h@W)$VjL{L2UhdL4QHasKW5jwbWrB#A5EvU8xfV+7DxWPr9uzypANYc{d>P}VsC!*dfq zfBqB%f)+6%zP$j*HrtYAMOb2BX`=amk^`-(<;5`ChSHDQVln!U5Jk?;|~ z*wN+W1_$@w6p+1%8OFR8F&|2(0%}M|;v6szSK0Hx_jxc6%V|5NA?)ja2rguw>s3cU zm2?UkOv#Ib-@lLd8>%`0kTV(N=lSP+<`>Oj&?Ds?GbfNN9z0$qYa%XT+cWYE_ZJhXY)P1{cltT1CH@7gSST z;7>UuPuArhJD>*^BC|jMfbT6vitp61!;(i-Y-}vOXQLy4yIMs)FcJ#ib8<0VCLyE> zp}?VgErcHvDp8+t`mv9#`0sK}%idXcevZ&QyfOV^Qxf1UsiLZn!}YZbF9NYn75FA4CMD6#Hb)^Y{<01cMdj z#pFvf7rQd9g9_3QsDF`eDc=88>00~`=1IdGujuAUfr$~z-w}qQA6E8=HpgSSyCGAT6nnC&`a&S|;7bvUs5jHOpYa-z% zQp}k`_+lzYb)?pGewnf}LgAfrN^={7&RPH~C{5-xOlkxyiedYSVVBCV#npmaof5Nh zt8&sdTs$m1JRDN_2dX7_I>gAd^C0*foi|}5XOudc8hsfnupMbYy-r2-Nz`=-%^7Ex zk6@Ne5%26b;O6|e)o*1guCby7OpzT(?>O}c>eP|_==^&&+HCLOFmLbhaCdqCMa~6( zou^vvt2b3zL=_9U5>PUC-rSr{u&62E5z8P0BBbuRmccv|86N{E&oevxq=&I6)KNEx zx&A}_q2=PLy9RJB2nOhva}2BHZ@d=i?dhseKjkwvmM66*3nS5{{{*#N(( zO_q&>Gn5_#p3Das`R58j$j8z@z_(Ov8{!TlFu@f>vYgJ?_1ASYotTS{$UvDHM`a*0 z_#|_*jZNF@q5jzQcv+L54usOMIK19dASXdxb^u-#;o(7dCZ=;?4{N8HhaD?R%ZWk5 z)G#B}z!^|#UC_vZ@)5p7Zjj&&2VlAPyU>V4A_}211^9tB2FHo29s#vBOZJRe%|x*Y zvF5O3KzD$|`@0&z8Z;UR#|dM&g(fs{ffvaBO*zzaG9vO@hG=zj1Y@DTS6wrmk6CLI zl6PDmA}&WgY6{QQY0FJ}$~1~Q(Y_Bh(#}GGxeeu}0A#{sfIrZZ*?Du!Ha{Tjiw$Ia zW@T^ZNrHR;S^GX@X^B9$-JfF?3;P9}M`WVjCf}H+od3#~HP1H!3s(Fsn&Dx~lG)Cj zahJ4weD@WPbWwOt$5xY7JZEx3ZBpxOwUD%>pt3}_3={WRCL_g1^Uxi}+XK=kzp`s8@)T&bhbYCXDW9hRMP zM?y5){A6>2l55e_H)iYw02mfGDs~?SvQWDjv+jqctD_w|4raDrzlw*eidc-K&_JCS zl80A@1|L?;!zaGtu#N`G5+;Ac7*TXT(JElq3mYiR^FJTwFD3YVo&!`VW<@Ru17biY z&q6J>?28p(;epM^hyNVI{33L0-g9JYW0FY1gr5Bp;H39lT%&xjkvV^{i}7>R2VkH> zr-ui_$RkYGS^(go0`a3Z%|XblZ>NqL6~P{cs;?M-7U`f9r(mPffvRly~y2oayNPdbK%hR+25EDUpX zVkw}QlCEC@$g4Uh{Jz$d)g$ zlczPPH&?JL8aIk)4C%T*Zi`NgkzHt0-1+$C*(w7t(lz=o$!|E`_o)ojC7KGj2U7PR zK&5_JBqTz~Z9eoK;HtWpUQxh>h`L*2$hhcb`7`4Hn0NEgqT#tsu1Op+#(SVXBx(d0 zcq%+xpJXJyPA8xe=5PrQyi3RYwX|eb3XN$l;#jh1i2Bkx_?$Z?xBj|lyxM&*DF26g z{M?-O^z4)!@OUu|q>GQ@op8b-`WuQG5b&Y!MpdR-3KpcUgoOO;-vQ7P#vHfMPgLiI zOAzSa7dvoF-Nww_!_~r#lM4!ba6|v|4a&{W_wOncD#OkDT0l~YPe6df+0iOeT~!VT z^EoDPKO6;l8BGuf;tBjbgN_PZ4$8*91isOl$!n^BK)y^M5Ih6~x&scucR?TzC}xv9xYD|)u<8PF@Cj-)WTNEJ_8_je;_E#3hG8uY zZ4GTaZEh#IJ0HS24kvPWj&jb0yGm1!NVp;yljQ0D=VH$Lm71gEf3Exa-ot9xNSQHFhEU<(#h1NqAk2AqD*fLPg-@tR!rHX}OmE|gM)aRw`gdU~ zRGSU3^<1z2=YIb^bk6M&Da7}`52o0iXQm2d$_-pCD{ zKhXQbhWIaKWwq=7%Fg-^05_07SJ;aWFc~fv3Yf?dv;<&X45? z&i?Qjq6^OeRBx6}fHuCv_C&57FsovPEApq?)`QZ0O7e;y*@MeNTUR#=n7MyDpZLEo z3Zo{)3#)qBh%_5A@) zYknu0zL)!%3~>Uz?EnwK5{S-M_W(^I3-A@m1cGvgLNb#McmOEvG6@)D5nX_f?RyP9%2c)oV*BhPI*W7`2*ZxzjyTfwRsvS+nHzt*=jI%S>*`hbm zie4L5 zU??#IyHE}Ak2=R1+R?v3_Jo(yNrxR?QdK@*M@s}?1z@lpfFG`&KpZ-WMH8z{4%v(C zJ`FZYUv1{OKE&IS)RvULTP6MJx4PZ|c$gg<=*R(4;PCptYg5x*zn$zN^sfBQDK&LK zIRiEpgkppDbQ=HB@b%LhIv^UVnRu9iz1@<-nKw6enKr3yu9elI0-tZAv zHWi&a$^HbZ`Tl>e<{yHCCja}~Fm&>K2EfjXS1lKs>-mlym%8WaU4Os3?@sj?x_l?r zMG7UKS$}%;a@#EoKW;n`e>}y*f=mkTP`W)mTxSE-lm6v4*C3@?z{A0N1KmSr@!QDx zw8Ec4fH&gN$@=Yt*VuLEwW!jMzvuZ+U(1b_ZWp>9b#Sy>4@A#~7{>qUy_NfZIjI|@ zZa6gfEopGYkOc$oDjsw>eegjvNZbDSTb^Uo&f4!|eNp7?qaV~r)MxwexE2fqS}9z}nr zXpJp&UNycuEhom=+YsUMzjL=UD^(?t2PS&TbGT|}r7uTfw=H`2z^Uwb5fAF~MI~v@qAGHaa!bh#cf;c@HWP5@M^a>{+;lZ9JNn>4nV=Xx_^3D ze`=(~q4fmJp7bR`o3NnU#9kfzYu;u$9h2fu?SeqII1U)JI{>G=Wx#dY`rbEP zE8Ye)fSc9}g$A1Pgi4e%HBl-v)-$ia1b4M~N-)tT9H5RFyAHn)`*uTqzLl)@ZeyUO z;R$$2Z9gIn1+w47wg%jezF%q;V0LI|l4p#eVg()k1XddP* z=-%WzjZ*o~M*4}7kg>+Md*xs(5S|MlRC=W{#M?bq`TjrJFMk;5+J%A=V1Ho!D-c$+ z;vME%GpOq)Y@-C!1jsLt`5=;RXYDYPx2~uZ8KS!{hah55^ zCI9vrka}deYz};OcD+1@%*9d(H|+~7p&?E&n$&BRjAWKUU+Hf!=9n;05T~v_p$SZD z>z5`Bj(F7eKfMKEXDc4y3-xAG&eC-=rBYP_kUVkD>oO!c40zE!3ebGHHum`6$U+7Y z==R~Pa6d@PZBzagEk_-7C`5+9GO#Cw2p)z=3#d0b0`bJHj_l_7D=>uI8SwK{0)q`8qfg z$e14-Gm<6w0rUZ|2)FGa*w=*Pb`?i$33K*sGfBdlYZr=fu2BAhMqu;!hdVIzu7@M1 z(yH`xsTY`u!MSU1NR7-t7OV2D(iE-{sS5N2N$UxTOOys>ku3KDBTkFlPPLI`u^JCW zrm~lc{-lP^fsGwn#2if@jemA_GeV#yX9&DTcZbCOY})@u!=f~kK5iub02(fOyVqqe zO{zgm!292%-|k)47%06u6vh{gr~y%AygHnA?0WFT`Zf?xTGibx}*jgN{onAF-Q$nhlWg*giY$FQ|2ZF{=O|pkdIhTq{)5(OL#Z{`Vp}L79oPA%U9vs<6)3W|Jl`SoRq!S z7_e6ScVa~bvr(g(;5S@@+~^U7uCmC(mRaIUaC7}hHG>}TpRSab&7dZ@-I z!aAOLgXK;3f3bQ0c2%n*M&@Pet`5N~`uOyiIIs4{99t8k5K=VTJY5nQ`&M9Eww6-|NO(6%@%bjexJ3o_9dfkal02J71d^WTJ$65RAF*s!Wop^ zu?9uDTNmElf%cJuuV0r7+(feMvjW6qv%e2Gw{1QT&T z9%AiwWS)Ur2pZ&)_J#1XMY^HC5~2bEP(HgW^tnm_(_NA8w;N+{Eyx6}P!o}7=pilw zjnFmpyf06M>P2vcN|RjVXtTcDSJNb-5(SKNV!OZr@~(urF^hWhKn4k_;%^~*j; zTfP$)iY1}+X4w(>W$U*EGVOn@9^KXIo;T-7RR$_@rrIU-lQ$S#tQdw->;(Q~I{1>~ zr!fcUW^r=wSrU8@knS&Sy9E5fIand!*_K6P22hw3U~#OTxe(IJcga!_H>Tt+Kq+NJU^e7+Y)NX58kSkgnF^R!;cvEdQaEi5Vg`ZZd z=vhkH>zJ&N$i}A8q;vy046+WllR28Wbj2gfa3j$~8iB39Qlf;`%Pm{r`zwF)vrX3j zz8DxvLhWu|<>E6#%tE91F>D+*2CEMT{G4APbw@JiWwgI_@vnhkO4tB1^;emY0-tKb z9ntF*Go%w#0=u_wyTxK+feDGajRA^J3!+XgYOY`evd9lIH%ASL-3jVKeYTZF22guU ze!wDwHw}R(&DvW~KEwqf!PmSA0jGhpM|yj9ZLZKmx<}uUVhP}MX)f2x%_+i2`KO6Z zb<XYI(5C#v5&~z z3{850{#f_gt}vFq_-@SeoMK*f`LznjS_LKYHbw*G(jaO^PB|Msm{fB4i0K7|-CL6x zt2_|C4HL>EF2nJ28zJx>TtGW3plFfdSwhb*x^^eWDP0`HtxVCN&GtoxclWi2b11@- z^igI*)RORR8B@xFjJUgTr+`^c`is9Tl{2P`90f6{JS{dRYan;*+C?VpIp$)yY@L}k zFY`Cv-|sHBi;-|)w?}gfJF|`xlq*?R{i5Zg*McFQ|K?hOG0msNa*x{LISc_s7qy#8 z$4HX+B?iv6amo+{{O&+@%$TI|!@qi?P!wkrqV9rG+VLXmxb{JTy{*b>v))Y6<*U!v zCKkYwCL=)>Tdwa39laSWh9^xTXn6vVUFFEdQIlSU}0&wXpsV~ndZ1uG}0@f<#P-erk zNXJQ(-_w6_`MI$2Q>4FxWz;HZ<3@S#wCE3auGgB6C*W{JNdawtHSfo}evaE{q~D|8 zSW$_<(dJ{LBB-t->MLOm_w1sR*sE2pw?VDZap zCb*@SB=Y+RKsB$F^HW_g2wtz4jT8OBd!PTrHIDAm(-D%|L!ESLyY63T`s>{IH^TA9 zHH$=>(I@+qAg{`N>|}0L#mrX~SIdC2i5D@?~z~$7J zd89l3OE#tY-Kbjsq1>v#-&mL|%1vU;^Y0s08+~`6_c$A7Hh>R5Uhp4>XI;)&L{_n+ zg=1SpvwwH`cyDkXF8;S?|I)|1&gQL9HXAb~6T5|y6?4ql+0>H1^b!>Z$O&EWCJMN^&Arz5F#iMR1&R&W`gep`v>!sv_Nlc zmUl=%4JAKch2eLGjk7BG#0N*fKa9eWYGGX_%}A67jzl`S_#YE`)uq1o{kIde#zShtb1Qh)vN6mNhpqN|Mt*j-iXxzajLQLBA$r6-skT8a3oeZ1#P!~5HG?O)lpfdnL0e|@)H7Q4?- zQx*J9ITjQN2*)F_@mQMrjScV+L#ia_>_#P9Z1;RwE-=+lQ_~I#5i>>^(67GNO)8wha)kjkF1LhIciVYjZOrCFMs>8kawKH!63Eh!Z z;!>w^3{+Y#L|0KYRV6b65?f@mGYGip&hH&VlE61nWp z?+rQ4<}yj&w_n$-D0Qz^>Q9*J#eE^XBOH6F$~H4-DZd_leo3;#AwYTnsd>dbi)Lfa zBg6FNGzf>w_S7SI770oar{KjOsX4i(nIQxcmEplfIn zqDFq?tJ^&*|Hp7cNNqg*v&r8<5W!2bxAlIYUfhZcx9IGf;;ga zME-RI1_uPL+D7(==w-TsO`CE*8DpQV&ylC%8V6s+nB$o1MV-j6u{W+A^%3{>2P>l7a;>G78Ia7BIen%Yo7z)G1Rt>TrcT1o0@u8kYQvX;{~h!uIWzJJ z!Aj0q4*lM;L%ov;Bg-pQWhp-}iWDVdk#@H(ixlPI&4UMUk$;S_6_P2;SF@tF=Tfghx=#0&03gU;}iFPob-T^rsRsB}(Z)idWoGfg*& zNBYl6+r)4PP|A8NW7q$9e$NEehBE~Awfr3xB87Fu;0wuITZoHaa)a<`QJwo{iNUCKqy z@k5_aS(@w9yEbf7bT$>PWfiXu%;S_6GnhGiTx*^RcsKezY0^Unf2(9?8LhTu`@+rj zcI<QC3rTtMPm~lNI$SQkk3(S=_i(fc7oIF4uO2; z`Y8*_+!tx^eexa}XrL1=1M&7lnc5!b2<$Gh@h0wG40@*dn}{~DC3t{v>j}i}lwevP zX00p~`-HZM4q~W4-H5&Fkiz7bTB5B@lhK~?_JPad1WFnGtvU)L=obWgN`?@xP0I-< z)9-z)M-L?PNz}K1Z$*;#m*<4<;ZBe@c`rZE_-I5>H&-|qXmR!~C(mB`_E6=EKiwLi zHlI0MJAgUvr4aK6pYXv`o$t2znqt^C2;_&s4ImyTPJL>|P2 zwSf%ndxf6LvW)^KI~#=j1<9644>v02Ax;sz&&}r34%Pk&>G2P|LkRtvXhA8E7w$)s zferjcmZfB(B*D|g3&TdmC4QM?$A{USS8g{;wKFaYhF+9N7IVzau-u}$RPSkKjS-z) z9W!}z4vBPghY<(P475YNU)mhEzI?O1dbVg)^7DArGCAkmNxhEv%c{ge#YP-Pm}Gpy z%iH#`)Ktscf6!CCOX!Xg)r0vZzo<%9W|EPU-dPV?iL%71EjeZ0%dbW)*Q_vnST@KU zI;X4{vjxT0hc_KzVRx6^)@exPMwdDwC*GUAXs=K+mC2sVhaK|0+MKs)@VAxQYR7saZKVqyzPK=}-qww zuv6xvuNQ_LW}daf0x_p%^qh|$7SWdZaol5a{El1FzVSEU@R2Gm1 z&)aTPma@&Kva&i43;P`baT3)~UxBxL+aQZy>UHJsbjg*rF%Ys#mkpUHv{HmqS16>0 zu*nJzMH_ClxeBYMVXPTA`U%-|lWnT!!uyB)w`pPDbN^!QwU-^jJiN)z>M}8&JFKrB z$D8-AS`A<~C@qP~GCi6$Qr!5OM8M+ozCaNsHON!eb1&W6Kxp>k$TIfr=c==heRwKp zx>D7iLS9FKnc!|O|6JGItPFGn$+&u#RNhdR@(-z#lojKl&Oq+ zGA8g*KyCHUXkXr4&N!}jTBgS+9GFWs8Vq#aQSfDO+cdwbdkH4}pecvzBOUFM4_H^S zlHN1t)vkhgI^9=u`P6wJW3V;!W7T)E1FVmZFV0_Xvz5`@QaSx0c(wgrr%XT!^h5p$ zHM&$4Q{V*%Rb$y!SwK&!x<+$AVgE^jq>p0D*o-E;HQgQ`aa~6{a0-@-28KDZs=2My z`5n5qdJ0^q%@x4%t(yq2h{>XA${Q|0Ym5S1qQMpzMd?R3V-@qjm{_VEc-C9VSNXPm zauhMYb2w?^&wDKs+`cGj){harAL+A}LPH+5`g6h*QJMYYO+4PacukeJRjVn14@`Rk zDp8A$919g=xnWXtYq|M;I#E^i6cenkydFs2q2;Q|X2fr`TqMTn4)8!gvgX$BlXiri z^7e4G*pPnz1ezK(ReC4HS0T#uu|^7q*Y#+9oiW2iauEBN42&C5yh~rzBa%~JPH=zE zTo1xT1doD9QR^1q6VQVG&2*gI6YpJ~u{r-Ks4?lEc>PJc-lcW2@M}du99QTyVf=@5 zDksY7bpBGj%4Cu7rUE9MkTG*$;IXjW2CKu^*aN!pNx|>K=Wn@Y0|{vSZu|l>7PA-| zJ#<6t$83OPCl35IfB5EvVc@q0nQy^9b_3{oP~_J&=?d zdS3gr%9s8?XzSUo!+$C5L z_c+;b2BmRnTWf-|<*<2klwBaaCu`TYGKs&z(KmhN;*D9BN9&C}4Pm3BV4#{wTO>y= z_?2$SiVeoD>po5xuza@h29YooL8UKK>>B&aSG}ufAQa=@cu;DxtWnZTnduwdA=oq~ zzGToY_Vh+!$kXlQ$9G!~R+$^<5{kVJyK5gNg@+!BBv!TC2sXQ}U47w-Y1KsV8B`R826GpHwOYgH( znzn=SMx)~%pxMhgIx=R`yzbbEsQ+AcqLcYm2_5&Bq=okHf?sA+#qr%z-D?Dxy=Ezt zzHJ2WISX3JVe^IJu9oy>9vvchI>Slba&T$GW4FSiYNOijugqB-3ngncVm$~muMWiK zsUfUWAH4^2lkqckQ;h%I$+Q>AiP)%5##5q0-CX0MBlpW;rp<;zr&5}aLw5c)LhQZ(F^c;GdwGoZ4L>@fi2M>Pp2yVMo#r%bz4vC6*=gem@4xg7Mn@7xs$U7e+R1&Jbd8xURb#IGdy12z zbtq$y#;Y4CFT;DX$TYrwwX8aQiV4b0;HOOj%++xL?;Ui-BmMS^+PA_a-~?InztowP z-}LOAvxtZ#&%S0cYCEo#K@iV|^S-8?#O9w%v9h_izRlz}F+EZ=pn# zl8P`zmrZ(74qbaW(5*3FjXMKL8KLK^#C!1loud#sCngbO+vU0Q3EQ%vuDq^g&Hk(x zYl2=1wRtTVHFc}Q6zZAHMH@&MLG+B=GcZ*E^?ok!x#+&9bmv+!DaY3wnYr%jvV~|? zh8UJpY>Sx>Y2INkIww&YE6QE5=Nphk;sJTLG9wiW*&z}#5fYzTFQ9;Df%5kZ(<>dH z^j7mr>|-R0T_VG60R;%Yft5_FJexqm37ilxxblk`WHYmb6e)qAl1xn(G!KlEIFu1m zUDrxVqvIYjBR@ms1s9-GOgp>+i;M{);rsw$|_17H0*v)~}~thc_#SN@C} zzCjSjskA7w;TU&~$GI3-Ei?Viu;zW&S~W7Ji$lPl9yEu))Ts>5`T5?C2&$x$(vK8; zT=QI&LSuQ;*7Z1FLwN<(B@T~f3pTE-A@!)o-s+EO(-V3+(^kwgUA0R6Q5{JrOr+Z! z$c&mn7souwVEj?oE9*>K`&ND#=hT;FBQg8;kI4*u>K`CMo8~? zm~Qya4XA!Z0=d2?w&wT~xr<#!A?#^C@A4H-dWd=7dfZ;p2!Xxd=!GR%tdOR5w>OI>~U`I3A_cs;}>Zgzz_n?s`V606iFrs1j z&(A)G4}u`V!?&$MV722);#v#Ls~MlqC8JV-$pfreeamu)K~AjD^#G6_Kc52JC@+XW zD2812)El!9ZD)Wd^1OTM^4tJPyY3b8s;tDjmJbUWOosSE6@h>hklnMW<}LQ-71PCM z#sh$tfB_8MAlx6+jZ1<*lHq%5yJf;{v%`QU&s@sBgiD!GqZ??oSb{-b8a1S8FF(6l2Q1lazw_aY_RrU8FVFeRovFQ@WdbRAUW(cB zQN=lqu3b9q9Pjfgq{fS8uWQ|$Fe%jF;q2^YeaG+(N$vX~DbFq(im{@DY97uJVdJQK z!>P+(q2r7%yW2DjPuvhkwmEGeRnyQ$VQx?@MdtY)lC64d`SWK}NJBG}by@5|Cnj}| zkJnwxg5~Roo^|G4|h;;DXr(rDEm&z1i+@4v|zU~nao**_g8?X?8ll)V!#e( z1J%H*7lT>P8C@wIqd*Gsv(OJamBdWoyQUpYv;JXvZ`6(aWiyTL$6hA zrh9pbYRMZZx|%8ms@V;izG7PpFTJ@0C8OX^g4qW5ed|i;vXS5s@Xs&WC!q^$65jcX zs$#e1!c)IjIP5X$G}P%VFvy?TwxJk0F=v9>Mr3X*Ds)T0#*wdoitQkd<+xiK(@$%D zH#t}~AAY>JIepVLiDJa*Z*ghcVEP9(%iYaL7Jgm6;raW<$aVF1$YxJ6pM}t~fVMJW zI`o-OdC-fWF7=-$c{k(k!&tN%I5yxQX_TMS(r}hr13+|wVOv|f*qX66v~LSTjw?>C zYB*=aX16Dm+c$v^6FpiNBX4=$#HCY`a&av+3OU-P~??up-w**MKvCZt`qng2-hB1gj!eIBnCSBnpB)N155zWcR^JhC+zWvA)6t#m# zMXN|`^277B=X4Ot>vLay-j`M4j)ud>4N-4XgSS?zmty|d*PEkZG_HQANq=OBZm5aE zhkn&w5Z(gAI(6)+|7}@WzSfgm zdn&Y9I8UDt8An;!KYW#%&jM<>cV8>KooyK+0b<=}3xvKc&n0Ig21PwH42Vg~4=)o= z4sPQho~vxl_j}*ZY4@hJX=P!v8V$ZX?hM{_8$=+Pw>B8w%tzK*TZw}>Dz^Zig#~bA zzYpZ{B}GIoMN~P~>jPlM{z^UcRc*7rkcOMC;%J(Fb~`XHfk>SKgdq-t;e-PNap{qU z9?C=et3?%zV)_b!TVINm;(FXyA-_KiQh6cLtqTmLMQwJJGNDebKT#`=V-;79dYzLF zGjLwnv>{k}e-uubW4+oio32jp9xI#H|2%vZ@>M_TK4(HdB%s?g{Crq2HTXX1?lW6v7SKkeD)o-ie)-lUbJ-Pqn7F^491kf`iT2Gu z?=*Jpsh3E2GZDweH{#-3(`!viYZpk>)GzjGNXo%$&)Up5&A~)5bh%hW{#Rr>vO1Bc zBD>ye&Oak>pD~uq))KSeiwc-R9P5bLkSXSTVBx{iTI6FO&#V)i5*$BagpeK)iUm%l zv&HxGy^5Nw0mvubA$q}|vOvc$Q{d_iJJx4R;utC#TO({?_CQhljL+WlW*GNXA3_BL zgnIaUq5i$dS{$5xC=jL&$77SIHlv!9b|cD{5f8{byfBIJ7GtJY?Q8fVib~%38W^>= z82gF;{J*Xeb7V*;myyR__qD0rh0xSSe*)Im^>R7{FBFy3y<#+EN} zh8)*?8T53V=^voJ|FFc{0H{Meck8IynRtCa3IXAD;Q{$T8`7M9l$#|=4AqzHX(9|udB8)iW(`J%``4MuJiU7VQpF~c^a)|CI>)+ zBVb|o;5~mZo+xkAPL2~zea*`jMXhtmaFP9&`PaSyvIZOwu#YMfh|t2^yW7Ed@7 z%1C>{gY~p5<~cEUzJgsTxw@o2e5|DG<%0j15>{sQ;O^xn_8MT^ZHqL)8?ply5qbR; zEw2iJYL|x5ey3seOAxa#$T9}r&%f%Pj6)f-aiqwHd3gMX`{5_vN9zWi4SNYo^!vj$ z_b*YYJ0wMDu;UP)XI*BTa#RAmQf1E6S@S_|pN+;oL*3}~pc6P57Sl{SVb(eC?9WbJ zL(6&yHp?rj%Wadd!GsY94x4rNpQxp3%gj*nT5^K(ND27<1a)j@+1VQjymNLEh!;L- zZ_o{1I!nqA^d%}(jLK$H_2HZ^><;TJ;$yBZNUkH*T_uxhAoTJvp66REv6-@!lDV9N zcNTNJ*cLlrV;;o?e~#XeNo}!80@rV9$W`>CJ8QY0)>!E(LwZdbe!dJ#5lCAwXo$G;t7)I1-HUuYR^Hvu{H?4IXTnUi= zrvCywx#!!#0@d&@?muejISA4N#9Cf-)EiLHDQp zm$gsAuhOUk75Y5aSP?xgmBK^NpQ@l7Lr&PF!^~$$g0`QHNHEwg|MOS-{%^DRFXudz zS!kd;t?&Ewoqo|?Dg~gzh2TWFk;AVzE?y=x?iBC5UHDwoixCQnCa>pm-0k)GwF`!$ z7X(!e*9TXUkMMS*-+Etsm(^4rdJ8;{$jbfob$7-%`df1ssVFl__6e-L`3grQ;bSB> zjc1c(wf#hVVw4gXjW1~BwSsyyEims48qt;eG{K$`QegGomY5-;<8RI~P}&pLqhgLX zs|U_c_FpId8>Aqy+KF<4kQtw1N+^j$;tsECI$p-g1^GLK*jYQvU zQjVC(Tn$ti^6#x2<=l9Jc}9#y_EeJ22at$}EE{yJx#@i55M5b(RE7!D%_|%1nsA!9 zb&UT<(^-c#{eFFXqZ{c)x*O?kkRIKmy97oFjuPqalrC{}OLrqNIt9T|BA^mJ`~I%$ z`FGp(*>-=n`<(ln^M1X5TI!851>`jiKfGKFC2CKcdG|6`06@-V2%X9|WMhEh;T7AM z3x^+yhnvud-h0=u=rCwi6x9s@K0+=xZP%D6r{JZdP$ z3AX%9@$@O!^H}{d0I6Hrv^<;2x1EO|UBoR)Cwv`Z01LJS)p<1Pf*61b-T@L@E1!0- z4YJu=Rf7qO4&*zDlcCqaN2Hw7JfP0O0uKYJy=8G6 zJAf}-Ap)rC<#RNPwx2_Xa_!`$H*eO_X+I5|GBiZTOE}J1LAg3^)V){htux|>`TZjp zkls4{eLUwBVTr_ut;}r}RH7WefV_~Bm8bK4%6FXLO9%A8qznrUHKxM9SD39;`Z>pX zMl|6HqN>36KH_J}WPDFkTP<=fhXnWY-FUZ$ zP4ynl7_le5g)+jCh(uSJE8L7%uju%!m#7VJ0Qd+?6bSh25L zKhx&iwRf&|JL`<|hc|Ecm^J@#vaM+=d%`s4X`E*h^z`|gs)~qhOPDsTrf>3v^m1k^ zHzc!Ok7zgdX^?!I8`~9|xC_DWF@j2Ze=$blZ;>&kx zOorob2iS7rpX1nvsLgy`7`!Z*0u0W^>y!jJo3cW_)hKW^Vb;})DukCM7NOL1YH)pQ zUF@_C(D*L=i;k@#{OKStJYailju*bMjVvdn$Xz&;b9|H0Zzo7u7$tsvr|oV_5ZXYr;PfZz0BQJ)Y&rh@~YB07r> z@;>sf=KE5egyggAuBLF2yuU!sdG~$797ivt5ht(4Z~Hc!j30|59f&QE7{=ha|0*I1 zw~A9cKfW{uN6FnfoB(FanGu7m&g zr3M*+;3(lp49>Al(p0~7#~d?QEIZfFzRR%rkW`epUi#7+z3c2z15)obvRe0*2BKDl zuzaUPh5@M3!6uQH@*rdJ=jDjm{7r?ZS73l-uP1hSWNJb7O$p1T6kwj_P4o|jZOS!h zAa@=UATz`oskLqK1QG*7Mmi5S00DmPAk_*H;-=Z2ER@q*6cg=k znht}d0I>7nhpH7dp{|POu@BTVRj3OX4g8KJ-GBl{1^X1WqguLWo8QtebaL!JI_uN zUWo7Xn^X{?cjkRfu$2?_GLX~Dt*x7We2zO;1dq;bX1R8(_bO6Qwl+H+rG4d#PSnk6 zK%4*xh!MFr7y;1E$`Iv!z>DR%S9E%Fd|%i+;7DuYi`wHTHP&{5?v z0MVZ`a~PRj$g#g1ZUF)dm8uxV1I!MF;axHhWv_<{VYvIitCjl00BcVB6aF9a^zt?e zPcsj7wO0phaYy|QgoK$8jux=+p7X4HXxu&SQMO4?PmB1 zm+XY8O;cy`zkH*=2PsjkN4hH&12W(Pz=7X1*)Ov3kCUJJc*-mQ0kF^-i0uVD#}C?O zz;7;O431R`gWsnGM1m*61Dg1QJ%M>>ayl$?1AIEgVH!RMzsh*3jF_m2`a{Ly*!PaWKq_KgxDOqC%(et$6A8Mp!%g1;#&74* zU?Fe?4A@Z-+pqp{i+TtIh%=XpqAS+|wp>t8(DBI=w`q{2(YV8r-RC>8O>)t|<-zAV z^GaE=W)+uG@RovPbtxC@x9AlNiD2ZMXHYcIDZR|v4OSDELrC1*H`y2 z^0>DZ;SF=D|65&JL*g;MzVCi<2B!G5>;lT(YjeKEHT5Vr$R_!`(m zl=HzL0Y5cl+B$r_K^^#mKqe&jS?#6glx73t&-W_WTs5Jq6&3ukj zb!h{91O%jUY93C5zl%bL!_3=x=+E4e>P^-Rm`4A5yJ*Qtp7EELXxvU^>}%Ih;9^H@ z@wvd&BvQ_C?WBlv{XA15xuR7pFo<{)yycbo+zqAlJsdBHdW{8|TWH{rn9>&8c33G3 zmvAkA;T^uET&Ee_FvoV_&lkbc^m&?ZyIZ0J2Jw9n<`f{qnjWgi)rOjGK}^$wNT@W> z@qK2KHFI?Q<;gW&g#3OKZTAH0M6S07qsMKbm)p3@wT4r|m{*@ZxEq%!yJe0fG~FEc z^28)r*x69fLR-#WiZ-;K46)PzGHyVSnnoR@K^8dNly($doi&WBXJENBIcsuvCU-wt zF%Go+hx5N}sqMasKc=EDkFm5*2-n5Aq$$UEUPfUR+bHY6Y6EFi_#-c#ozuDkUP!dp zfz;tUO{6l4ve#K)5f1qVb(Tqv0)fsry^hy!SZf4!>d-{l@NkM7#f`4w?9ujh^g0eS z62!R1OG8G9hWG7^;o z2f*wrL4G~A8#JLx1t{z`%VfO^IAx90;!}Wk8c|7a_)-JlE@!;anbrWtFU&RkIz-lb zu{Ag7Py&%-B|;g8e#}nb+4JAEesg}40TOV6?>CBs@bTDVK!=6N-X}fz>Veu_z8(Q zXZ-zdfscDD3_fBw!88#OOSm(X5CsPkj;txmXN29_|IY$APNw$(wXJf*g&@c+*hk{Y ze+iFL*w@bg3_kLuk>#nzv%|~GtY_8Lqv!fjc?Ej`sElNAO^-z@q2qF(BD6|mx0~&d zs#Frhm;0gQ8L}h8(S+rKt|8uhUlb>TEI!89vVV>;xO_hN^om7A+o)onMT~x3;0XsC z2fyYb6kw@tp=>pXRBR7j=aC>x2MkN!OT+q7mN0UE0l zZ~#!aaFeWjw3yT3Lr*Qlr2l|UV%lR^*Yvmcm+nVdLPx)JS&~EtELXlW5s%!{0^rv5 zLj<#G#ZwU;9(4sJR0V8pq&wsfOoe$5S7}G&Iza|52ud-gW0R=!xJg~`X~h@*(^9PJ z1KF|fnDqp-=*$@4FD+zqg*>)k1*p+!J>{gqF50g3gKD59Zq zAi|qexVGI&TV|8rU4@2|f!qFyW$`dn&|d2$(DobEm|!4-HFQg4&C=lZ9hAPn0$`TE z4m7HYWK#w*MtTE)Q0;8Ehwe!E}Sst)SOh$2^S|Pm}YIv40J5dXJn<*^+Cj$M)9gK+lZ z|9<93+X!|XJhMsk{7V^rwxbD8GEH}4;#pI`acaFX~q(}%-zHTkdu zug%w5TG5CcPlcixCXK4*O5E7AiouYJ4f~D`w?nR*yU32&-^}9$%!FKC$JqSP;bMX| zj(x4yoXEOAK)T?L@EG4S&KTnkn~4&TbJ$Q zmNL+%77&ec9Yv^99J3ouiaEv?XPeVs7?8cbQbUAVm}cZF9)Jvv0+2xpb;jHB(Rzx4 zP@A4We!XhNJv+c|B-vt2D%1D>5D3VTs8wKGbkmQ_=6>Yj6^lSTkQiViY3d4<1ac#K z3BAVZB1k@ZAV8tlc|*+A4r+|=7@%QA>8eO%KNCmrQj)y=qB6%iJ;s?ImozR$iO#AA z(iT7uf!lUPNw&#oB=$yO6Og{S)J!+WtMYjYi51a8blhcQ`B@$F)WvHT>t8V4d*mhB zTUBcbIt`)OH}sGbNKSx~jZ!_S)ao)z3vX+m+Q*9?>paVxk$wvv zBgk6r)D!32p-zz5b}wd z1jwJFLpM#)gb5D>csU4)>?0I9dziVKbs>?e>;Fhn)K(g5OeDPQap0#TBlk!)XIuaE zUhv-A4&G6Wt1#s4tUvoL{jxL-jcv|~v>^kd7G5@QS|m=qZUJjyZb0{N<2=mb47FCK z=}7{8$!s>mdFP!qbds*4sg;+h1KQBw6uS=WG~2FDIgLM&xSpR7P5mi8qZvQ2>igLl zQs^ox_J{Y*7-H#qm$S4nf)*t7@`@lfwTvH&ro+c5q>Z0Z@k(fPlQb4p+S1pD;h45G zJ{JNN0r~Iuz21@>m|{+mwgYB{4A!-vfD6Ppd-YNYUzeUo@+Hmy#<2D#4)Nn4pdZmSe5W?d6Dm&uv6Do;7bVjs<8RC|@ zF1KgB`O5ucMfe-%yy=&5SSAGuLk%=Fj3`&~d0w{L+q&M&QHm+or7ED<=}d!)p^{CD zBZ5jw*?yADz=uHo@2#f1Wj8i9cs$w=shYIw2Q9$Vr;ZKxduAKc^(Co=hsE#uXDa|# zA?o`r1)1>3zU#xJnGa*Vf2su4=(74_t2QcB9c=E?G9M}$E?Z^)y_S@iclnTMP?<@) z7XwduG=y~_P$~5b!~sM+6a)BIXde^+a!RyW*Xg@sA!E}O#MG-fMtDb&&k%NY zFzNsuLNFKz2R;K_1pKJY(S4m;7-|F>Xsr7jTJ{S2)_HnM2^KxbFA~W{=E^@IHDa5Q zO*_RZIJZq*mzbqSqXn~(3!GRp&YtOd(Cq|}s)j2I`o2n(O1+N+qX&|(qdG<}?39RZ z$0ZPmqp;}X10OD-nN!c{wzxSd-79XVk^FX7{P7zys4eNrYmC|lEv1AT=N zQ9^-ex{6$JhgW_)WsQ-wB$l`}9FIdGiKeiLj-Lp7duUaf^1hSOPP#`8$Aj+vnUwQ< z2MJB0d+QR8Oial`COv;e2b$)+ZV z2fS|3MbO5M8-koR*kt%p<7|7n6hVKi;W!D9@yPcb;&x~$4TsL>8_K;eV!lDOZb62- z8ujgU9x$2jhhI)D04*R7wgX;yHS&@98g7F`No0`s1!J9|x|e!Y$^u>%JcL(FI}JXS zTg}NBIQFRglvto!I%<+l$!pW51-JgAcqk~(rgfyg#QUOnLfYAcZ>hRNcZ7b% zQ|k3eYb(KWkd#Oo3=Ba=4-+-MrGLo5JA5%uA-^N^bw-Ak7kRsGUxMoocyAn^__d>^ zFpgo4v3XzWAM&S~U$@kqatVLFmA)~%nV-piZ*d1^S^qr#m%eI~LMYnvI~!qDgKDq* zHk+=fXGM9qtbdJ}tc#xh#*xHzFXWY{+i?Ze$XR#h<%T21c36jqYwn-E%PfTS`z_yC zVQb4E+qv-zzD>`a)8EO#pNB*5F2n2p<_+i2c~O1ja0olRb+S4PXisT$?Hvoh;Mz61 z4ZWe8oz?KuZ7!)yC2tNo`}26?6d2s~XRo2<4R!VR@?R?feIa?eO3ke1t-DOZl|M*Z zJ`IXW?Yw+AZK^t~x>6+cy`<8t4$Sx3l;7}n^(L?mfYEU$2&#(0+h_4|kwUOFC% z+v~XajDf)#51>IR_X~;EI4g#e!KO#mheA0|JLc~)?@%av=8F!06~6HJBTlZ*j+MOT zLDm|Fig}ClvYcNXe<~&DkhB=&KEI($WWsYqL4br@CKzrSGIJ_nXEDK&`tjhCR&JN% zc{mm9WB0kJ^XRXF-6M!c_Dv&%r~PfvBLL@{8N;y@2M_CP=(J zo+*1tZOVsJAh^YOhTuMASUNjMZE3OL(`HJzVIibRr2F!Vn9 zP?f8z`TNZa{_uSP5wWekX=SzWzMAi!JBI`%bt@HdLU<5cR-o^iiHyI>nJY;js6M4P zn@&C1YS^~{*%5(-q!`wc z((MkquY)zGfT&db*d=K~I( zxv{`F^#_>53aM#>;1=vu7{r5t^B%n=Lu5VeDhS~G{tDpuyM!6DVGjP_fuq@ zF3XSMx6nIbytU+C@CRCqzk9t;)dU@XWLpjtoqZH~Wh9&WGm*4-7!^dH^QJd*kxem0 zReLd(OA3wuv6vSlmsM(NCbE(nG;E6br}3R}b$>pJBzRHssP^LCsqh2H1@%DzK&SOl zO2ixmVL8O`HjPwrR|!O!>zx*9g_dEgLsBK??2$=me*WX9PvKRwzkmlGq^sdORcCi4 z4nxWVhF
Q&g*vAtguY45#hrO?3W7=22F4jqSpJTJ?fv7Y`owqi&$%N%cnq&3wGSYf`9l=2;bJ z!p@^=54XM-yi5Oy%2{ZykA8QPX;A1vDh?{0MJh`ke!o9N8z%=KV4?PwCsyYSF-^sS zwYf|DMqqtL!d!PiAWu3$jrhQZ()*>rt)bo*p!hGf1}D?@1w3Cj=*j-o8PUvcdh-h$ zj!#ec9Vxh`j5;;2eUPg5Y&@71C#u|YFtcbeTTZFFc~YIasYUso$BnKR86qmcu~^uo z&n~=1P)}k@&qH@+fETM@LXOi7h&cqIgYUO~p3Dp-Cr5~7{9^{auYOIs$IK3#s6|C@ zkYCoR`LfhgK30o8iHnIV~0{#2aBTv^D?s1{u z(~?|0f#S31{mw%^QnFSMtO&4)C3rBy;7?Pw!$f4p49yYTp-$1>@{me$zhP^UPoJWq zkvJ@^>x1qs&Szh2g37?;052ukmv(cPL0|e}KdTSAUsf7^^h9o!E|VDMrc$?wN!0Kz z;pkUfk0HQs_pcRv-bi%ZblrUsGV9IZl17E9ekm=&zLww=Xd?GUs%eb|KE$%^dk1Cz zBKxb(fv~ETu@@2d%^qlk`30&EwxmC3+8l(B7zME}5 zk>^6^;{T3S(FniOO8c;PUuQ_rE=Gm#etp&Lz@wPMMPnITmv7P7`wb1`NhbY|gk^MC zcHm%h(A^|QfOAG0A&lO%ixsK0)%hPgukAa$$v(`C7x7&@d2di0NtJVgEF_Z9CS=4& z9imx(Mx$FzB*|xFvKqsdcKnaJ*F#9(5cKN}5Aeg2kj@JZDmCmt41&3Y-A=ZEc0J_r zZ0Kvpa+mM$xD635IM(Rg5d zpZM&UsCZ%?u*!$yGw*DJD1$zyqa3E)7OSEo3X93YEb89lbB4(BP|4G!iuin!mkJ)t z+~H7BAZy@+;)()Ly(kkIhNL2K=u$Ci3~pD*K#!*K#q9m4q`%gGHe_*5n)TL>#`6Ld zu>|SCEy$OQM~g>Ek`+o|^+zxRp9nEN*DN&_39=V|7x1ld6b8Nz+zpWRv-GzWx^m`( zN*XqX0QL5Esa58pi2=_A8{AVb0vHe*?7{2>;)q7*<# zZlAK*Dpf!71rJsLv1D{2Nwmm+4ISRnr9={d*z@rgQLi4b1j-f2#Si5!O|)+foBQ0i z2b{|ZEnPb#7t#ShB>1JnfI}hHTih3MPM-%X$bAyV8+Ur5o2Hd=C&F1{bjBS1YSimC znB%;bbB~SR5Rii=(PjL@m9c@f(@ibKIN@e-C#OeJP&W{AeGseIvy~Dw@-ZH?XmBB^ zsC$f0i%={-3FJZc<7U|Jb(02iE_QB;ujxF5-PoZkES!|dsKvIwe$!_W0va{|d@)b2 z=&3KU=3d2WVwag$c%ih|RSS(crVhB%b80uv8VQ zCkh4vfmyS@`GXIm*f(gtXgB_)KAS7wS_m7mrNYP&898l{F+ZI$z;cuVL*+qIQM{sH`JDuEY2C|U&BG;`S`$wg_j%=2<=Rjv|Oy7eg>Yf2{H zUVoXB$1EpqV>exRkYGag5MgmaOz$$e@H}KB??1@G>Lm39zN?(ee>_V?k+B1kKyi$J z!o!BxhG+f=R;pFuaDjGE+3?|b%LnkJpE}$R%4p#L+}ok&Z}kLr?>s&LiqY41vV_`9 zF~$f)zsZ3*b+_c8N=dj2MJO^*sR1$j4L43_b<3s1l~Mp)V?A4AsjTwBw`l*B?)$QTOrCmIBaS)WB7;|yE*z%G`y zgLnF@(|GeXk{X-gUIW6&7hTzmZL0_CD>oSCM^28G!?z;$Jwnvr!OF2WsHs!&R6!= zAj->BHL}N=9o-^t0*wJ#75s(Zx&+<|E0E>>J=f=~`6M{>J{X&+CP=URSrG^C6&`*D zhTE0Y`z_l`;k}f?0r=yi&Xg|*$5s+d*_bm_9WrOv`qK1UBgClEr}WCVSY8E#22J2t zwn;rQtTC>1?kVxRpC#(R%ezK~6&pUGAebO3T7}29+f3iR@jt5-7$zNQ=WFD8@re*E zccEi){N=vPCx>L( z9t9y#Y_V+r=`q*mOCUGlNDe>R+iNqL%AL5}49`t5WECz~pB?1?8qpTlNL7HPw+vqoo~!zM%Ga;Nzu^0flN&Pu_*Q8-6s{2?&X*?$1IXvtsSw`L<&GLt6b+zwbO2U=O9}}kX=)+2AE+Tb^G6dDgB)8xIiUjP z!pi>2-szXg=d zvC;>5A5a7ahv<-n(B|TeH6(-hbyXYiPVDaKSgham@Efr8!zM;g>=V_1)(=&gQyuK7 zd#JE{xlmX|N1_n0UQu-_yS$nuf<~| z__SR73{6@o8qW*&Q1r4Hp6%Mpp<#&nr;Jw)?0w>?y z^M`!Vq>bg=Sk0{g-K6(w@BHVP!Uae-MsbU$l+pu<7KpQ7SY9(er&-1lsPlZfMC z&gk3uYT#8O(jbvu@K7>knls#17J;g@9|h7|{3*>HV&ss#joyM?6Lp#>xzG{F^h?lJ z-YD*h;LT2(2Dk%!;X`ovN5BazM%m?)f*t&3US_P++&*oiCXMst*}VjN!smyn+SV7* z+l}SAlc*5d6^R6)BvuJ2qmav8sZhB$DGI?jO=pq;Zmvk6GOxjW)$;P!twbu}fkf&R zcKcL2kbQ0EVK1=V5L}yC`v!9h9qm85C4K4osH6fyWUO<^vuhKLloP3o@P8TD;GTBA zMlgQRS$fc3fAjP8h2OFGeZZT-HMaRDsezT0>NmSD(L{LG0F*3;%APn{y(d=uX|0H?NIY3iL4Jq4xbO4HjaVQSA2+rqU(bGYaM;_VUsSHTl<#;1N!Z#brGn82zvg?aq5l{Vfoo{I zW?1O+vAxz2(8iNebcSCznHOwu@lbzvw`9hH3uDTXJS$G=e!0fSeA|1MACM0>S@%O+ zMK%_J?2Gt%-COp44V57)YYqst3-qNrqu>sSch815BP3#Z!8bdZe9co{k59i3#jt+I zvpEo)=^ShumHag}j6uV8OqMu$QBRJqDc(UwR{vL8b?;y@gMDJ5tJGx;-|zPDcL5E!VvN+4cXk0I#J#xD>w18TWT7a~qMa2?wAvkPp^( zv7wpcbHT%ys3Md&K?#4**jL3P#2Ht}2GLMNv3lkrA50iBmnNeNM_ ziorwCgg|-FOY3$Amjwi=m#y5!SP!{R^}g2cqkmBLaLhtRfG&XP1am@ZrH;$??(lJo za|RI6nt~c#{E~ACOg-rpq4vYMI8hBCYj*-<6ItDv72CB8u!d^SQDpI~b77$m2NPSG z%_IGM(Xp)60k{~CiBZLn8kmuWxeW$HqezM%6||{**gR<01Y*UoHg?YTNVGDXNtLaq zJ^yhPZbZg&O;Rqf!A|iolShYdujuILTVk7|o$P5pmTTF&t91c$p%EiL8F@=7b^F1y z>r-VdHwtbe!la^!Wn0Mqd$Hix!*977t(R*U!ZGT9)7%n}iDoz#-108Mvj@raKd-jf zG9v?M3ItmB2nq=m`i@Z<1snD$!inXHhuHtcs-XI(6`>f<6=5sJbOR#)Ya^m(dg-g- z$mh{(KA?#^y$eWWcyA^7Anmw8-BtJw{xVg=QYKI|p$W^wuwTr-$D;rvX^oVA17npthfU`XzD4yEhf- zm?SSUTWY4%F5K5Iw)cy_Z*>I&r3zVt-ovGdd_-nUVy^@4JlOX!0*Ub{(KP-&CucAn z8kT>UR7l*?FB-)9sH#cR7@>fxH(s$^rt*tFJ0+<|Qs>p~Vy{lb`$n4X{48ygjlC6t zAeFRfKH;9muCe7Gn_|DEil~1Oq$^sEb+bhLO%uC+ll(Diqy0QPtId6T!zL7s5~a{# z`mPl4c|*BmQ~_`mFv*%@nyN}GAqp_+Mv!s|*bh6vq8y_nev!<|N6-c=>t}1A{&J;< z`=1nE^x9%i+83OfZ&R@@IeuL5AcGaR@#3mXKCK4?)Y!gQ(s_;EklEafj2h-COhH@kp4nWR$D#i@FGLXz-9P9n%vSR_FtmR(EpC#v zZo;9>zi@FoO9vocEWcHmR%g5q;{-ZCpik58)BkXD=g8Olo0g7OZ*sDwS|ZDQv!k}o zE1&KZdPQQGStBDd9c7}Or=h3uh9{96c|IA63~DjBdN&I%JyEf+KE^8@_jQO`2=YS^ z3RGGfwml}{YZDPvYnp&+L*_kT`hC6@t{ia7>Gy>%;K}l#vAfFYr2u zzC?YJ4GKarH3Ia)xWz2ODz~`lqSM-Qqrz%X=miN=h3LBDpMI=ws}~nNpibkTr8)=+ z!JxA`u8Ir5?bwgin7}tU-~AHRbpbXY(|wtILW4+iGE8wZ+6(k!A5pZ=iIlc8DdEVl zD|9@B>zs=uKK_yOpzCh|AgR=tM`?c4sT48Y?e(YmVTxSs&JAj030#&k1QucIb>A<= z<~_y|bz+q3CM?W7-5noD*GdFYsEQ1DxOM38C~NCEg=yBm8hByW0lpXRp0?ziuxmeZ?K zgGbPxGdcZ2M^SQXHb3Q$9OIAUZ5V;o!cZ|KZ$u&Qj^0)BdYt&Q>LCp$YGnwF6L((} zvR-6Fq;~&K?*BtqAmy>U#bq<6Z6<-t&BBg~S)*|qN`yS&6P&oQPjt6Ugs#h-{8;aL zTHJh|E4YWBQ+JWqq0>=fCEPBcISxacoN zgckN1H#2=&H-iMVk~aX0B?}!V+W}g z9Tq$Bcp{DD>Pou@L)V;&x~i1D6$9_wJo%pq=JDy!wMpbzV$x$HQ%KuoX9JH?Ky@ry z-!+O9)s&Q$5oYYj>P3i^2%FQ24i`(hBS}14$KoOtZ9&*`a1eV16+5Uavc(RXZ-OiC6k=cj+5RE~Kg<&mv^d|bLnd=`Y<4oxl)hz|(3Xx<>cBgo z6~+pRJErImUsAds-MM@LKD0s8oH?|^R3i9C3Z<;&Husgxl*BzMi;cZ)rnbEr} zBW<*~+I&#XMfn5^9^hk?i~NJ62b%t1S-8v=ZSk#0uK1$u_x38G1P)=ffpA(zoIbRH zQla3Jq7xV2Ds4Ynet@S%hiDVmjuy&qhBsJWu-*>+^-TaK8rZnnNtPJeW@q!ahq>gE z+EidK99X|u)DMCFfv?r7c`$YbM`0!Yc=v2S<2Qc}^xq16rSHBvBS0rb&{P*tEJnl( z2xl+cg9YhJb_1wi(2nW_kMUZWP}WEWKNWc3J z#k2s4QJ`rm54_D8?DKeJZ@RHb71OYh4ni`&Z%U(9a~I1uf7h7}6Q9NWu}6&tT2^c0-FewXL53BmMelYf-0esV=s1{D`vzf zrdJyv&UzXwKu9ny9o9wxkdWEk=5QM(W8Z>I0!t@HLBnVAT^@PD>pmf&ipdfmUkOE^ z%LC=*$Eg4>|LrzcW}L2Y_hV6PuLX7tBo93pJp+XJ~ZQt)j;|JeM1f^Kjt<>5n8)OY@dl8 z6_+7u7L+P15#gsHZAe{e1s2_NvIdFQjL^ zuP3dq!y{1z(3{4b;JlXm*@uvUN@%0rPOTxzf4-OvY5xGuc~56oi3J;;*f62n`wK$A9|pqhkzWRq5qjscO%QcN zc?X=j9M6j#r)$2MA6s?xW5;c7+C1n&Blt7)od9Ov&T8t$D9MMu2gJ}@dLL%?Pjfms zG5f&D4?LFJO2+;Zp68;Pk}SJwnF-QwT12Usx4E=ED_I|C;I(k~#0$%7`fs6vMj8cm zeiwWbmdec&wlHVMKwmACs~%(jtfL1T-@;Kh!Bk0B`jn6R0|RP5T#MeGvX*pZZr#q0 zH+X~wHqm==g{3^+eK9u4Ic@sN)7*L54ki0(j7^?aNr*C5Bu7^1;vtdhvH*zs-c(O4 zxcTNSU_O_I_AN{hIXS54I7&r9rMBo%Iy%rOVxu>=g*pl4bugEb62Z0GLrU71u#uvG zXc-`Uzvvq-!m_vteO%pd^Iatn;&#*!WyNTBURnids$;^C_$(+EiPPw5;amt#Ju)!+ zM0x32vKyb7FK7N-qX6f9q!BWB(LO z|K@L2aREc{nlNk1GK8Fmp80|v%3_nwxYfYV(93|D_5lY= z$vpt(fTQ|nEA&UxuT4M#o3IsIV>56QXM&pC%qV$EH`XjhRipyi!a0>LE+O%pVzyw3 z^GKNfruw|w)E~UB3Oq<(G-lXW1y=iwqD*ImY%Mezy+z(tm7S81joCD%HSJLt3vcn^ zl17Q%34_|Xc+-9wvlKkA|K^u4v`X*^ygae~h%xbWSKvv`OKpcgr7t4LF{F%U#f#br zgw)uJKz)tV(gOAu)e2+NRj92&@6FPi0=KvQ0Fz;n?>!8dE@<6;25X6mANhmDA+L@7>r%oCr zIE?`s%w%aTTf@d5@JoOHJ{&Yo(tLSHNAR^J>pn6t`r*$(Lh?4jA}0E_o@W}hY2}h zr~$df4kO9}Ar!UO7I%7=$L>j<<#OrgH>q#iE_!4vh4>4)ebF3tcvx@fq$!|uT2+_A zOXwgJ1aWP`z{N13Tqc1DBWhWOqRYp`EzNA6rr_PPl}(aqx8z?NA~dVHf7O=7O0p$L zf6slAnnjLnRWgghthxU)rY;)@R3|K9FTw2o#j@OF?c{6Ou-WELK@0!$VS|$>l#9YP za%j_wFxRiem&W00V@*fLQdMve9rYFWdFX|&qC7O~%fBZuq+bAn_qQfR;E~`=y_xLq zwZ*Q`abr(n*CB!Z2j|TC!7uNR_d0o0BcZw#e|n>4Zv(|@y8q#wMGBrD>088{U`Yx7 zu@ANxm(5t8OAeh3OGNzIWbYmVI#vX^*p*tb6V=waPo9_7Lf`)@Sp*P*7Nj#cr61#nBG)E z(S^PQq+*f6|T)!Iw~ z0F1(VCo{!;4CRs{ZV|QJXI$gqSWeMAI26{o+ZO~YXo8(P+F5B%4p*vqz%Gfzx_AG2 z1Whc{Hnkce;!r6u>xve-PcaWwY}pCfp5x=Y7t?rQJ(WG24|Ne{7IINVbYjtFnRzXq zn=U%40rs5Ko|V3ZBGe6jrayFwM5iNp!~R7x+8y5NfAk*QLVgS_c@yWajad6vK}Kez zL^QWw9#-?eOpH;2J+8lBWMw7Q1LzRcmk~S87ryQL%!v&9IB&YdzRbP7fc`yQSGSp&%o3;wcD zyydWmI}2W^69PS~PG((N8V{?C^0{}3ae?v**7GnXMyr>qK?)~AVPFTxg5j}Wk{n-U z?h}T;C~Q=gi><)cD39ct47oecqp$gS#68FzMntIey(@b+>>W}E!U_xEtIZ8^wIQk1&0U6 zJZc7kT)%?J_3~)>u+F2OoK+7Wnc`&&?mz-o;J5bDF54mQ`Wq)Aw%5~RA)yB&^A0=T z?eaPmMQFOvurD{-3W|n3TG=?=oYmwM-SxEvB=LkvKumEX`0!Kd!7&Dt{%~e9t0bb* z7RzTZuoMTUes{nRCn^zm>s{?+2(m+%oS!x6YN9KwG{t@^E2t(a^M+p!%daEbn$IE( z7(0u^Hntm@#%LFauJ`(PU!JUcyc80EGy(h;%o z>C%(_t>b*CVSHhCZ$2t!<13~2{&7d^jyS~6*W;dut=sKO$%pzPI@^%95p?XhplBBL zZ^B6bJ9Bh|SNG({#l`N~;ywe(*HM}KEhII~!8hANVwbOG^Us~fl4UF;7cEv-FZ32t zH8(um>}3Q^URm2h5Ig#}GH%8NO1gTJCQET=pDmoZ`hO2)+IrY0I-UVOAPm~Py9sn^ zNN?+I7f-&w&QCtK)|Pt|=&Z0J6CWyYUnngFd$mA?52SdS-_f*)v|ZmrC`vWwTd9D( zV>-*wvLXD0t6%mplD3iM7`7mkcYj=AahS1n7zHE)$X;USY4q#b$M#LyR~m`Y4EmZP zFYWJViYGsNg^%x~hsmBg6UY??xqAL^aCTbSI7!+$*1Splk^ut*C}JaR8Lxi(_TBB;L}R!q zXuvMCl)z5MI#l*kC$_d^D~=;vQz?L5vBVoB9LF+(R5MrMh=c8(+S#Rb8dEQQNQ4B~ zB~lxK9fOy;w(r=n+bmnQMSxy?8RRyYl`GqrO`FQitXZ&fJ*~lPn>NFxbImjVpS|~h zkLo(}hW{#~hAN{j1QG~=5D0?Y1S z-t{J%^P3SE+M$!4mJS{rU`*iK_G#;#2}&G^*#S{&Yc+vjWjA?$~^aXxuaob zZaL>X&;RK++(7MpI?f1Cou|Y5YKPf2*EpoEy^7%I5 zn#snk#G}LE5G5*6&R4G*0DGdK)>c4Yv#=uuRCiv(b7o8li_c?#~=Mj*SniFLQUos zvJu#g+Sy<3FNxhGv_GI6JEVnLOX%kqVSemWCxxSgyY4emj~wZ0hvU(Os9_v$uc(`2*Jy-F*ZK ze8ep+)A7nHI+uuKfd(rs^y4Cn*Uj#o=gfpM7u}pIxc4U+F>(4GqP@poOTA&2PRRwr}6AfF0xI^zWvlQa(4GkY?L9(Jt30bh2{g zN?}ZUM3!h_GKVRk9>`fLg`XY4)Mf=WTN$y$fX_Xr|1USUPMCnVsc^K$J#T;*0dIud z1G#3j0&5zy1sgEO3jLmuVnM+|B>(+$_y@K(8wvn%W?!MeZ^+_5wti9m`tALSZ`b)B$)-L%$3aOIIN`1uV^;D>41 zK4C211H1_U7iTk4PFe1>Xbb1?pR%!GTedTrQBsj?{D@T_qM!(VKj-Q43O1 zK7uwE(}j6y)D~K?q8cYp#wi|1V4j)jinC*=$2sm;#-~z#REVq^9E6X2blCyB{fs0Y zfe6;>>lpxFtJ+#|Z*M;??@h1WGWjqUMkg93OVw{r>$p zzWBJXqyRJ@YR3M7{emy-A_LPzPW58Rl3KZr>M@^3baVw~&DxH@z%BuXqV-j-hQl!r zgM)rv>t zSvV`e?wyK!OgnpSb=FuBNHofZ3$UB&YfdgMMiN(idpm06{|i$JnT3SD^647jO{?|l zehL0ULr=?EI)(7?8u?y>$kBcimY|iTzP=E&wIvFcw4y0_TOSky+;;;o9eFwZgXW4Y#2i%y9qE&%Vt1a(ar8As*wT@yMlzn_qB z2#JZ@yOj#q-ybZB{7m6Z8ETx!3oq6+$Z^Wbs#W9k``>rqlb>|3pxJqVZ;m2Q@QiZh8-p7?euotOEg-pdH8kH)z~i^>$G31dJ2FBenp76|>P<->75ze@~Q?hYv+UB>7S*&=}^D?^ zHub`BTN);IUT?q4>#H)yVn7xAb#`1wQWBMehX7NS!BG)`mt}4^Vylps@%QKcTx_fg ztfWwy@@r;QZeEj&agzN1PT5ES%`=TK^Lk_6y{p{Hl5GF;clMt5v)S}*d_@-FziqNT7^ zHvfJb`pafx`7%GOU!Og}^Rjf+^JZRJ6efyd2c}OyrgrU_pw)|U)nxXzk6|5+{@u@h zR<~blYz&h*x)j$Yg{x$~_JnriC5-U9j4P5g6n5r}4@vmw@&k5I?w0s*2}7pS-+HS~ zl6>L`;QQa#v3l;Q=A&kfG4|?l>w2P6{`k*1t?n#&pojohK!Tsqxr=z zSZyRiC9{71^Exn}eRB!yicmDU%jZDT-^-$U%u3-2-M|~Oo>evn)##A|{-86H-#%6j z+}wfKo@68kdFvwQ_|OGK%WJ8byISLRS%U};$jIj_aE=RFlA_IASEV^NIInNmOon#n zH<-_I?meL9#&v1w_*0(<>}UrHsvXEl8RqIUZln;a;qX+}Fg55xPcDmMDQ z=yE@5z})aSRE`6!S&U&~C_oI39(5PqZD!qZ3~S8uYB77!5Rk$3Fk=QS=CmfoLMPZ< zn2z*R2mOx|7FX68+e%q?2)C+-4f+%(6Du5-rPJ(QCUTd+-)QW~*1 zyXlD?>H~H@x;%m14~#}e$vE2_XN?K=$V?c|Kukt(_l<98EM{emc~zaApn&x|_#*^( ztU>VXv%2m8``Bb>5Baz=#%_d+Sogxj%nD;$Ardi&XCq*T(N<0Lu>u13Um&5ui()Vw zlNo4<6mFy%j6Z4w;XM=jRSMsK{72yN$Is&A$tab~%51TX8)w08vny)@gNnWG9Xk>+ zpoa}oQ6NnK03ZNKL_t(O$w7%Cc|tQLp+s#AZXBf{B?V;9V(pS!9F#Q*h$}Nf|D*vx zoI_D0DNIi0ee5)Vv%|yn#TyG0pE;9*($x4LKD-@^ z7FoiTpSw>fN7K)y!(lJQmg6M??ADws!D3_K$4Sm|4OVTtsUWu)J6hi@VAofbsBn5> zVw0>T#$df+!1che`<(3tLXYo3eW-4WFd{cm9#u(q13{d8C+5qiI;27>V z?`504*MPf2W}bGtfxK`t)~h|NuU@krIj*$Kw!4jKCETkCq^R*J>jJJMUy8`&>pznc@R>KDs=CU6`Atp>){$mJpg@7THh+#_k6{ z_<;s^R*5q!YiwNA*_o2YEC8l=k#llo5|I%2F;$KK-_ZdZ1QOh}8R?7!)zGB_N03Pcen6FX;qD+eEj zY*SXdNdY?&`!=0ArL#7ey}`~DGB(bjeh>BQh~3$C2)F61!evH)Uz$bM^GF zexoQhc-&lLvYHFaA~W%poTtKe3@t5d&Mjj@p+sQEhdGtN&MGA&z>$E3;4ZW`5}hrP zNSRFq>3unMzMEn)%RJ9{2DnZZOxewzGYmkV6qc}0&dqZ+nl;;DK!T?vl1L}WOG+BR zWNS%&`qQFvJ*vx~+;o!-xiPuU_F^N}96XOyG*F@+f5|-D{MY08CfC&qMp1&9n@smj z9D_gQnEk$C>#NOxy}fE}E?7W5O3T}6Ge)DAg3{|bTAjbE>jXkVdd#@)F}rszV5h3= zuOoWMjwp{-ugpZLtyEP5I0x7nug|1=m;1;^7bmcL%q&DM`dT+lxnl&dr?v9b)Ien$ z3k!?Hm%c>nkS=k?@T-h0S4)?1S7)bcOcj*P=ihlp3oiaREksuBG%KTNh@S7#oa+kh zDzSt3Z{%9KErz-6{TQrtqfxNYnhEgUR93vmNCJspO<0R%DFAv&$>@l;9Di5u%2L?a7%Wp#=BIM~#!f&n;62d|`(I{W zhGL;@o&dYoFqm1QmICV*4k3a$0_v)&v0IOfhujJjJ50<~aBD1R$J13IEM7oi`vpuU zquYDZady?!{xUz@W2M>*cxW(3ha2d#T;MHgEM0E;Y-3%@9XAN2p27$bhWRp%b=lW+ zdohp2q|XzPr#AU(OKMJhqL;Xyy!=I@Gz}PlOXiq2&iU%TzCL5t$w$hf%JB#d?J$-+ z_s_?eeU<@n$p)~c4B=yih5M((1x)e5F`}`d;!G}9g>UEpGtto z^6=a(YlR1EUFP6CXTk(s#pix>uMwUtAG4KiG>SCWwO&BOGXfe;PD+cVK%!NZ&t=!{ zg~E@+P#IQ<(>*PS4VqjnJ!dP7AOBc$Q<)6mR#$`(fF0|D(Q;_}RH4%2trksM5brRb zy4e8O#7T~eyAZm^fV2Z<9}4moFYcMJrSR0L{W3X;{~ZN(1cZhSjGWge$=Q{tE=^Mx$`EkEB2h7=T`jCFP*2B--V8|E9@xOm+!FSt{c5szYIxu#msAev%YS{ zcwC&GEqPgHA5h?~re>eurA!0h;tik+9_MdP-UUo&!efsC`(8|kZH2C9H)7WvCL+=D zO|93nYsW`Ex)gz(CrUQETQ_ed5NTC;*?^3tO8eDCBDK(_Haqw4Gg0d2Wt^Qy16HtJ zq0GA6vPJt7l>$F<#|cvB(R8&=DVvB5-K}(U=f2@4m@XD8-DqSN&~UYYhObOoi{+za zBJ$k1{`kY8rRZP113%fi7vIQPKY33c$Ae94nM@(bKF9)mBP6&mp@kH(cAsL~iDiP?Cbp64vHZ*&8ByVUaxR4b`q5|+ZLpf$$Y^qPpbUtykKsz8tv=$u`M zBS+S$pnZ2>H|XaFKL&kGm9@dm4GXvuC)!UW7=5Zn30sya2@H(9$XTO5Ty9z)#il&P8o4OWNd3(QkV10-iN%rdgx<-mF@LrKL7ZKb?*s+^YrclMv-4HAie0;=;1cO7cSq8k2*cdvwAK-aM9p@fby}Tj zb*2j^H<)|yM^)9T3-nAafcF#Gza|1H&&&%Z68%E<2aPaUX+C?QmfG!RY&)>b63EN< zDx>vK%x_000T?UOTCLkAOCE|751vXj`5H$x(DR}O;>F7@mzuHVIIlOBpF*>4z73rv zI(nBGt|4Lo8cU<=HJ{t-4C^?*M6aRMspZ;~xgI{%g7kC()<+Gfv#U9-_xMYA?8pNd z5BP7;aj9#}_;twKWOo^S@nFX@>B^O4#o0{_ri@29t#`|p4|&^ahRwA9uA+3MsjV3j@rf9l>kEl^E281UsYszBq6mfI!DJnW|pEiv_)AT7=zc=HpxpP z__i42u#OWnDlr6)H0NF|1oLFmZrH8RuAEh>!^g)VFluV@%)Cgx28%LV*7177bn@6!VB z3QgdgWtMFJQT?1oYyr1&C8}9F)n~2qkq_9p3m{$s>xyWpW5wH{*M`9p=}ypfKFI=) ztm(w_4o#RKb9+u5tE*RFqV?e}T`l%$3@u4PJ72J>HK?o)e|JOyauzmz_+cGzJSqCt zw*)MP>MUkAV)z|Pq|rOuI#V-P?G|ys6l>h&)YfuG6M-*GqiTg0#LDMG0<*5x-q|FGPQ?lJ$xQ4$JcXs z-Vh#iewAt9{o?{$Y`vJ)AL*gQ%1OAA!pIM9$K+$_E_NPx9qQ{Bo4;Qe^1rtTOYuLc zl3Q`&)23CPf0JDa#W{0mJ*-n8p|y3!W%|r%iDddQ!yUeCl%^Jwsr}?RpQZw7 zYmw(umv})G*sWWEzF2KFO$qR1HGNj`VGVS$u9($QTbpYD?9eI>Tp}B;6W&;!jy}KM z$roq$k&iBaUQE<VXE$xI8{7e}1UsCIbK!ITn-!kvf%3LtyQ#6>Wu+n|YhANonff!q- z#(u6YOgwTz8ZC9S*oC96Zt)ehQrOp4$4ru*gS(f4&5dm3`g!d?Q|@CcKJo#(u_;)A zCQYxvis@qg;J>+Eo50URm81Ch`7wca^!B1Iv|OEst(b-ua%JW&O&koEUVg<*4mR!v zK5wSU&|Z*iufv|Y-Lf5)jqyUPmBz{?9J{)@aMg~CO+Y5oBb)9c)?u>>hwf8ZZI)o# zVi+)8VX3~=JOa%#of*25>y!&_`}U5Z#<{dKeVmE}3O9>gy=V=Q`7j* z{=De`LKk^rYOgcMe5tBF-rBUD^iKoME-xHdA(@}2>+`U>G12bp(p7i_7-&e7>kd5z|K3e3YIc6$<;kz z)pTtFZ=X?ur=FVHGt#}ct4<5K%)r@hw?1SnrkZX{qO54CVb8;AxMD7?jqe@@~4A^EM|4A1TCzVWr8_+Fz!YWS8__6f#0l4CpQm)WZxX-X#*1#K@g?#lY z8-i=nm4f;5fvI=(vG*yJqxALqyle$Hw3`n>_;&MpBQD*H|EZws48P|yafP3R2@{HM z*I;g|36PR9Ss?z!zKR9nFQF+r8-x@b@2-b(Dp+wkHr0(yX(Th@9z)?Vf}s zf}oBA%7K#Kb6V^wQ!#rsEkV6Tal2CMeI*+uk%-`raF5|)m_c5IR$%uumrTBFkQ-Un zI&}q0b=>2*g^;aUqO{IuUE!;RfcIAOdNbTT6xJ9u%{Z*80Qks9K49k+k@0cyQ1gFN zy+fA)wM*9G3J&j8f%MhYldoFO;-DWe?tb3zm=pJeiTm*Jw>`R;fcNLklz9(UAoxPr zNNkeriHIE@d{Cg_mv#0mL$TOof)+e7$dR;P3L_?8FkbK4Yjv%`d}GPYRe|{g3clDn zZ^)Y4m4W40xTH_;mkm317O+T9SFgmAWf%flwN4F+p836)37B*&2#<8?xp%!B*C-dJVn*{`CkBW=z}`S8>(J z6e@O0oNyKtUjF_H3}zTDSH( zS2-1z63g}atT{5aEXKE3oMQ3Zak7jINUK0oNB7ip-0_i*t_)yD>%ldK**vd~R%(94 zvJz*%OIm!s@eSY?ztHYk_L%ingW`fD;|$IjZmn}qn7GF`J4Pfn0AFjs@2_OEG-zm& ztrFf@e3%G9ckI3QYKt7l;?YNe#~;_&;>eK%<=LYl$?5=NwOm?=05dn}HuA;Rc}vzD zXRIod8Je1+uy-$m}f=OHjaAt|y-a_UzF;nMWel z(6SoICtp?W)Wn7+Oxy~ei*Ky)p{s%q`Dx@p7j8bF5?{QWcrx?Y-HiaxvdR&h|KukE zt@kIX&)jvFt6i*>dArz@(Qw7V_{bZw+;3b%>(=S__U~sKv<4u@WJofIb)sm(Epfs< zzAho5N1sDcifn9Alww)_cY#DStyP)i2O2Ns`pV+UH_r3i#^I61e(&fi$?Jo=L`hn7x z=t^Lm-Rjkq$jggYu2{+<#(i5yg1S8>nTa)Sv$M4ekiq->yPzOmF+REi182Ag6T2r& z-0YhfqY~4HnN0B$C72dqrDA;rvTYn^oDMUQZ@*oGJOb`_-__WH<7OpnvIsK4LL`DP z8w3g~apeZ#PmC;q&NcMF%3f^RWW(XZi_p*zFKgkbHl4B_4GR^=Prwp7q&Ts0={oF~ zx4hPTz>dqiHPv{C7Ba4rXQ4wsG8M40#)!4%ILjH1aOa(#i#e13amd&aj#h8-weoUT zxFd0B1k*tbO}2m~1;Fn%;+=Qsd9e8& zm5+SD&O2HOtMrKt&nc@KtqtyNcbLpa(8itRFv#Q%A9jH}{_PI*`1#&mUAOD6|5q=5 z=~r&d>x?b=FE{OViWjOd+=N5IP|2g292bwvAcma4>Wi}f3=6SGo?y7ZV!b7+n;ahz zbkjn|M2Y|Y?=!gXK36|c%UYL=F~LLa#nI>`x`MT{LD4%y9A2a0BpWo8zoY z73@)1-z=B?rs%buiuE0lZ6d(V2|!evrE)pCfSuKCVySwlOL0lZ)(zZvxw#4x|IF~aHHHaI0d*U^=rD@PWuwF<8zTdm_iOS5zveVDEWNJ? zrQgX>ULcm0iT8#4`slI(c6H}#0n=qcm+T9|?n+e=5-uojfdgF-+^rs_tiif%TRrZ% zCjmQ5-L))Gqx}%tO83{3zX_{S%N{MXt=s7K>qc-wFi>UQAKGhLhq%y~m}ZU{xJ2qA zZ&dbx#d0&d6ka)q4W>WoR1gIcEkHK&NL*RDml&9%!DOe=Q`|dn$c+v54Z<(PPtTb> z(B7Y$Fy$r17Rg4?K&$ud+4P$B!r`!^q9O^ibCR@IuxNr!&P|M6ZDhOAs#w2n5Uw_K zleE}%>u0DygTp9Cq$t@#&5`e2Qr=#xwjCJ;6U82Kj!}L5VXRuUMuBfDUYK#HZ0}2J z-2C|(#Kp$C;!r)26VO%~(fin?Qbm&-_%(;B>?+3oK-xR>SA{CV%?q{QB4v@qT8 zm&O3fq0=$Puy$>ZO#OO9MfpNQd~~@1yC0m-1+2Pc3rjj7tjHf_vR&nW)wq9!%#Sf! z9UWD$+Y@loRNkMVBrUnp?dHNKD0K3TP$ti`+#^IH*FqTqj}#n1US6I)6`3JDF!knG z=K<^m>*tvoDCo2gx*9^s%k7*HY}-6#zihc-vy~{KweCDIP|MO_ zJ!ktA0P8}tQ*Y=P2IA)%Lx9}afr10bo0q3!w6`1ZS7eM_cTF9@W5*7wQwV$fW@a|y z(4hnzJh%k2@2b-Qk3M(P~*PK49mg%MI9t zoo_4DUBWjW6Ztlv_}u_14NzW`x}tgG$$p|A*>~6=sK<;HHuBIs@qJ9t2N3 z&h;kpmY8@|Y>wsTH;eeucvY3cN#SllI>sC$jUY?Ll!pk8O_3r$|BpP&AIkU)U_YLd zcUDZvFK<`HkYQTcp9gDQuNlAB+)|&rC=Gbwrd~FGCL1adxtBof;z@6sU zV#c7!1=igu2}u)vcIvzP&6?&IuAV9#!>VmZ_vmIBpTsXs|GxjHw7=b@yFx|Ku?`ewDxnw*s)&08>@j3ol*3a}f8WV@~)a^NyKkzcNDfCL^#W2;Q?~BWR#SXr_H8l9LBfQxlH-{9r82oS}goV;05(?5t8J zb~~zi>-DvnGa-p8eko7cyt6HY&~BliY_o#OPSBd_8-%rVYKzxGy>?8v8=6- zb(AzQaM!l44dET(dM>yLWh= zK$fCIy4CQtI5W@3%hyIzVylf3RB4XgdJaSrHCBv)gDoXC(15rw{ko&517Yo9dd*s4 z4dKRuw+uRjdcsO?jM3QNl%IG@9?KkIAIF?YDL(Si*q7J7&J3$!nc1!b+gc7FMKL9 zVOx~^)CxEZUc3{?AIQhjWo~o90(N!g8F!l37U*j+yYTjDvN`8Orm^T`V&dh&{0)Fx z=?3r^J@5?ly0NUn)6A4@?OH#)@B(8`uffuFW}?*BkMBpV;>3}WXy|t+ z;ThR$hs}1(d|>tvWo5>m89EWP8bjTCM(J|`xs(Zl3A@|+ac4*ShpnS=P)TwL@|NUj zkbTG~ca>&;&CH=}qnKq*os7TA&35!~4kBx|VC4McjNy?RbJ zr!^z`&gk zQj_{?`{8)Yf%p&O^?@Gh zXrA+;WUV7i^_*C4_EH1z3JkcD*Qs;HWBGQ)+0i1@Y3syNx!#vv8boPnCfe>ktnJq0 zZ7pV%I*E!xeUPrIyC5RbOhP`4bk}FMn9@HM2%BZ_|2*@ti@7A2zp-Ft7-ff`jtScb z+yMW3X1-(^{*XDIMXhVwWniZoBPO|A?SJ*8F4=O!&Q_*4-oU-1f8l+D?Q`n2_3T2C zF$~oD*CIJ@5TB7j|D zVxj_Lg@uLKw{IU-uU_r^{|}FUh`R7PtXs4W@rUEl{7f?fZV$j!S6ziS-gpBWHf#_O z7lbWaw%~>vZcqU4op;{BCqD5B__bytDb$AQz+M#E_aIfNWx;4~pN_b=1Y~C~#k=pm zja94eKxE{|lFrX>P!vOL&PrFS+6%zYW&i={D;|XW#YAp_|FwY=XOxb52kfj~{|>zF zu*R%D0=r#Z(O4QnB$aRW7{f)^d4&?&P>UnVzfT_h$;m$x^5NgX)dV5O@aW0#X4z`)G+N!fQo-9D=R}yO^x~tt#|w_TK}4x zo7MM|T6BsaW!X;${e%s6BXnA=fXRG0=aT{q|AJ}Lz6aeE;RR@$yeoJoBx#?(g+4j8 zA5v8DNiS%d*7h2*8!moOH-%KUxK7WI9dMOy8G~|VqqU*b)+q|}!_Y;HiAGGrd3;Cq zUl{J%>+yF=W$Xr&+?*!RAamk3!R!9D?2j5@OUC^r1JEy3MSGTctPh#Lce?>rFUaa} zP#2-&7gw&-(d9dL=E{Gj=?|5ZjN=*W$g{x=*p?c>?N4HIWksc7vkCmYGr~Dax;T)97D#&m31@ zrjjn(C_w)v`_~Od;~0q!5?dUr%l&xuL$|4aQ=l#UwUdnb_K}Y+dSGV}0bM~s0kTYA zC<3#}%1Y&KRsT;RaE(!{wrY(&A|e8vot+{l+60_UbN&|Jr{%7-wH0f7uE&zvC5Vyj zm@qCj9s7E(zW}{7)QO}X7_bRQG)1ozr{X8RE&JENx-4gs4l2NqB?8A^r8$m4G(2q8 z+FyNc{S`Gix}v*uELXR|WL{9B*}7#6o)=Czpn_T`=^lpD=c#;&%m(;Av`^77OgU)B zVG|g*0+T4|zQvq^t}=SvH<-&AF6!~3sUePgZcau)0k4(^lnnTpm} zgSN`cz5l^xUEZcK)C9~q>_|`F>pYv2v}YY>XP&*dU9OWQ>LK?SkawRO_^$Kj%euCm z_0(!2swaFIt)UKKU)^fOrQ%bgBk3liYZ2owyUZDz%>ePw8HLY^ArgAYFVK)GP!;&e-!_uqeCgh#N7nc?9VY~ z%^k8mDBF{=6{1nTey8E2514UjGU$sI)}I;9I)IqO1K4k#Qr1NJSTY9vM+P%?m{a`_ zuAbhP5!EcO(bL;&;Givb>_X5O)`&LQSh6lBM}xDXCKJL?Hx6NCV(cnHb#dbGrc}4Y z3Db1mBu$YT7BpNL+?4=gohXEh>-D04+1oGPCQog;`g&v7X*EU@p^oJJ24{xIv>zi@ zZ@XbkYjAm4QoWIY8_XDCJY#gH%JCu9PqO@$k9;)MH03dN0|Nutx^=56Nnm%2(Yf)) z8`b}jXt`4{aN)qIj8n)c&QLHj2^9@>4Ora2 zSor2Ty!e+F(UaGM1K|g7GW?|4_Xtv4E8A*(MrYtE5K)TazCX%ccRAfrqY^z!+stc* zzj!H+tZrhoIhiCgQN)uk&0_3U-L5_6S={Um7F>^$jWP~#Ck0n^$XE3iE*nALGTAcm z6%kwn1Un5Xv8wq3XW}#tNS3FPqLO30Bze$jAEfCzSR8~%Hw<~1lGF12Hgvg_uaEtm zVA&XF$5IU}sj*VF6)QrpS0?CNrFmFkds|!VOl`U`$+%*VPwQ02nbRW8;)#Sa*t5{Q zl?b#av`jSb|nyPr{2tahu%7XJv$brVL0=idM1_i{$U9ah$aoXQHek z>`O-HF772xuDvn^p>vEFXIKC)LT)l;7&94kg43Z61-`?*Et%ybA6;s|uA!j;wY9a_ zvqv{ZoiSsEN~Cait-|sMx*q z^mJ5LSEI49QDwkdNg3-Gt;e3f?7=;;_u%NXqiD%$L9t&kwwR4z;wsrPkd4NGZWRMI zLlsROgLW8H#zmewHn1a4IN%QE3{e5@6_A?l?|*4_6uZ&f)LRDNj-k=B5bo8duEtm5 zzM@+15Y#a{k9FyGnT_BgPqxLf<*1mILRf7Vjyf%WOVwC;O)o$vduWAPTN{I~fK|-U zsSgbDDTP;Aek^-JV8=uk0=tC^MJ$DPqN+Xx-LsMqeDahAciz;q>(Gf3_0eba+Ksfb zi{0uD9x%soxWPwgt2;zN@??PV(0BsP^4*W4g=t?%pI$?>AhTS}cf2`ywhUvEj8Ltt zjAq&YqWHlq!-_0kFAJqpk2NmO2dg$+P`|$vHVBBX$*h z#M3w9n z(a|{T)`lrq0O`!LXOS)dG_|D#0mfQ6DA(K~*IpMMj^@x%)C=Hkl4JL)u8-#zJ}>h$821<4FKgEi`S~IE=%Z|WdB%Sux9TI*=H;Pgv|_-TfndpR ze~Up;TE6_te$ilT_}fF*VA-;FMZuHr*W|(0X~V+j7s9{SA4PY`TC#hd!(1O+6D6^+ zGG8LlE^EaiuAyjI1BtRG`pkN1ll9Km9dhgtg|Xkuo?9WpD`Gl3ks#}e$2eJ6li^`w zkvl)BxTXS*d5$r^(;eo(@zkAYv~{7{)`m9!zH>ek-5-SH-Z0sxAyQt)%UX(;zqx)M zk3WpS!2ob;U7p6NprKDvA?b}wXSkK4USVCbwMpQ9}eWG;Drd1Gqz zL%ur-T`SvwYH_t6AIVmrZ0w>7hh>lbeHxG2^|fW)oaH?468T;Qbl0f-VV1cV=)^0Q z<;oUeW1Bu5;O@mWk5tRKSA-OSW3^l9mho!s-ae}_zst(=kH|SoXJE$tGsX;lAL+a+ zOq(%|2`za0WXqSWBd|k(J?<9KeST}^t=?Jeh?!s0F9|y5esb>Q@Am?Hmj+-)vVBxj zg9XXS&JIoC6FF~|w%8Hi=0)UrWwqPUvICWomFQpEudH_Q@h1h?0maQ3$d&byB=axH z!#z3AD~b!KR7J9pYBT)K*s*xAPJBs9x>oi~ox^0@uvr_jMtoLYwbhmDDJUu$^*OsY zo@g?^Wz~{C>TqC?-7)yS0U_Mo&*jN#KH0gkWW^GoM_cUDiqepFZ<<#o!{GgP=XqIL zgI#l2!i29`vZgtpP4*rf4RQ5nrZwDZbrtWIfE7zdY41mKQ=?`onYU$nJ)fHlk7_XE zRs+w8^TFmh#+zf@u)l5NSVyC@ip8;(yd~-vguA@jL@slwQQ%zPeNd@+-ziuf3E#A79}kj8z{VEUf~14j2gkmqti~(_?B0=$z%Dgaz-3}R3hEZ>GPd3n zDmLxJTBm8tT5v-uYzg@oQkdB4bG&q@fSzbu-O=Zk$Ho(gtEfPp06X^3M&n_)e(q zwKYb(Nft6*3m^ID$^>>3Xcdc~88g(Pb6O{^F<^>c+z(778FDOQ84hOD<|O5e4HOy- zkTHSIESgekI$DI8^}b}1U~Vk3y0rOMS0m5F9$JjG%Dg`50qg}E<`yafY0Bb?ZZyE| zJtLH+;KrGqnPoOw@(Av>H*Cj>_!Zv$04Lcd7YJ81(#Ann+&B$>ZNmT-$2rtIw^Z{w z*$DUWQ7mgsW-Zj1@u~D6l!6Tary!gmg0RMnttG@-hOHfU0UBqn%TKP^xsEs{Tiq7j z7*@MkXJ@IhLA3s`69p^iuU>r$n>H;+Yfua#yeU*{LIWBNu(PWxG3Gg5snrjbb7h@N zL7IxTx)WC{Ja<6kvM)Brax=YDaEPq^!^6fP**tfS;WW3o5#`BZ#4p`y6j&yMufe2a zK&|pZYt)Zf^RG#Dt=L_^8!Hl4jE>my%}wMDefy)76g8C^Jj`92xz}{FHY{%qpY8r* zL;Lx*8UEDZ#;Y3L-m~VqDjOdcH)306_w#ya6^n8z8T4?Nb$8ON>v46eb04G4Mh;yG zak@V80lVRJq~L92WnF_cw%~IZrH59rKth758~Y7wk@*|z*weDVVi?WGl_vVeP;1?G zHvzj5UgZy*H?oS|yW`$E1Exv;6qtOvzzzlzxKBqgbc0kw{XTfWDz+A*$(f8tft9=g^Jg#@%$r=jq1=Gq~oYkgRHn$Gk&^)jW)r>Xxv%orXWp4TivN5 z%W9rGY#g|@4%XHpkXiZW`L~&MMk*ZPw_@C^uH6EXoZ$N!1HLV*-G-^Zj~KHJRt;iy z!YJ!#s~MZzq2NU+v5AT2C?I?`bIi!v#{s@AC1|8%V9RxG&F^A)&S~1@zA8gX2C%%r?K>2NZmC1p>R>yI;o9qkqA=b@wAVIrAJwIo!QS zf%=R&(%9lmV_0QC6+cAN$;yx6i5xKJpRG9&8Tm`*zw(6X+?J$U3qt-+a@HvJJHKFms%l z%Nu30+tZMdQHIjexfinem>z1CGMVcFcF^F#y)z#br1j$f2ff|h zXgF~KwFeJkf6c_|hSkbiD3#xd7SIwN8;h{WNUz7iU(T85w)T6?oH%RdL#t5`EDKbV zo00`NUxx+0{mnrA%B-O^z{gT0gBpTW;>0bi!d;2I)8O&s^Jqc!$I!5cX~UlLL?{xj z_R1EUC^Yv~XvW9tCI+M2#-*mZS}wbf(K}{MMZ!luy0UDc_Uo? zF!m42h_oyX@gDGz1^NiG4vRpVpPa0oTjF`fyEZqasbly$;)!oHniB&;7mj{0cG>6# zXFWTDJ5~E}^)~#a)BxPE3Kg3eyQXA)T#;mTteB@CPLT`Pna+HefBH0Tj*T6CtOf?) zsI0`Pf&$c*mZHAA9Q`J{ax&#$YxcUF?Y7`xMA+?!jE~pHxVRyI2aHm~&*o-<%JE=@ zvXWz-H|y(l%lMOP7T5_h8SnoX*3K@tig%mVdc&32TgS=ky4u=MBV#ow@Ga*(+}wfZgA4r&WC#P}>lx7@La(KvK^^Pz^T%y1m}Ti`C9jzimbt7=y^4e+O47!W?5Yvf9N~7ZoXil;7B>#)iVgFCaj@rcj}vu>}te zH3&1jUj^5TK*~!_9&Z+Cp2YF2{??9F+3~hZ*@4-@1GF> za>fG);n-UsuU*zY$7s5YG2i2S@Wu*7+|(=gvneY}h1JcobP!sE9yI{#CV0_&ev0XR zbG+X5)_9ay&Rv3SHD-l{>KS&mwGH7b69(UIrL>0ANMO%erwuZ$C;a?8{R4C*P4_>J zCYji_ZDV2^6Wg{qv2EM7ZQFJ-vF+r4p7;K~cc0a&s;;iJPWP_b`s^$t)&;KnP?~vI z+pcT1kYfAwEi3OeY!aH1HM8c2`DD;bIW=WMt82D2eqTDi&s#9C>*uS@sD%Xtho|LS zL8*zc;vY5ay&)H2pgTGeS`}-YkD=ZGDk#S12ui;l=32Y>i~H5mR(cBj8g*JXxMc2h ztwzRym=#r*roJuve&uORWF>vNG^zU$o$sp4T(?oX^$-@vTR!;IH@{4iC$T6*(!&CF zr!UnqwJu=icH%NCX{ox0IU?NM&Q+?UEjq3fNH7w4THrw@*yso$dvnTjk-Db|uDu)k zt@Z~toiOyg2eCAVEtG{tBz{kJLv;``l0m8|o_Wp-i52NJn`0~w;2w>>OePJPJpB+y z(=|im6Z8TvJ1*uADRBnnt-O3kvofBvV@($4cGR|6DA!UzR)2OZqMUFE*ykeiV|~g!#6(c^di*X*`_hmTqeU?aY3k9# ztm3j&mG9g3jZAnG0hw0jX!fXWcDkT+u<2Zxn6Gg&XBy3t4+cqA5k?2N>?#MD&bbcS z?0KqOhAyz70BfhiEnW~g4iht)!z~$U3`c@Oy6do609yp{+s^g9PbAVOqbJ6by<&^t z?D08wAT6F9T)wywetdMAl;5(!luEBJi|c(m=`#W6{{uX7GH09GY^$FP&ekn_(xtCE;dGWv_9xt#JsK3 zD;8uQIMG82PMDzcI8_?|1OPm}+57#oFLp7SU?4+pG-JSEsT z%k0>+ijmEiUS`y$zHcu|mIjCkY5#({GV8HdT_0g*?d{mZ3SimDLkk;C@u$K=&<)+q zjm*!lckq_M5G_op?CqZ#26ufAaPlU=E=Ek0QbrKinIqij(Kt zyFI>74eD*dnn))>a$R^O^1 z#?9=aqfMS#RxH|YkM%VUODij~Yz|qjeYb2 zuGi&6?`2^G=Bn<~>IBGf0AkwP6Jf@*V#W zg-J=oeMkwa2Z0+IyLU0TxlQ{pRDqeDTh6;4!*0lN&orY)9puz$Bcn#GaMDF8HbO24 zz2U*gtLGj$(8^12?50NFIkSYAz#d~+$Xj6UF2JxFac_-S9*(VS2|0DEt{8Uk1ey>R zRT%qDPe(|J@f&@68CPkCE6=Yyz(9+kQKu~X3B(2rHB8-gS16Un(GCRoWzMcNY}oUd z&mXpTA(?9PH$||H#~c08?{6P?X+wYz{6$2R#vmSZ6z4)J@vKTCyaH2+=b!C~EGj3* z{TxJI9uz(YOZ57^RDBCcW@@JNS_r7GkO> z3`y!17MkD~6X~@%o3DChHhn`g*?F-3zKB&emlFog=2sgF5!mXqgt8Q*i>j*+j4MGQ zjE%+<0Kz~{-);4!hl5(3Q^yI~+h+Up>Qe0M?$Alp7`@y?)%$~Bn}{7 zC6=T49>EmFo_UpIX}Mg8;BdD{Y5k?$5_qnE2tHA888Sbj3ZV)C;`PryMH*j?Azxxe z8~mx_7s6AxWr#N_gLKtmERl^Aw6~``KewFQ)bg*XVF#!w%d%@dJv-;;=L5cd4611Z z2n<-=$gy10N!(J*E*5LorKWue#9-G5xtCZP z5qXIU9N8f7g-Nutzqlm*g{w2KbW`PKXEfW_dcHFek8^2wHN@abs_gFF5C#!-Yb6ZO zZ3+Ub7*EI3_yw5%uZ~$JwJ)gt0LcTuz0aR2IG39rfrx6s1woiyTPMVu^v5ps$G7GX|$XL!Ml;U?v}_>8w58! z_k1kzI`Zk#`fX~-&pdH@MLTZKNm*AHashk6CYSM-`0q^e{{?mPc_C29PS}GL_2_G; zx@Lo>#TvH;xN@iR*EO26u8OIsK(+LKG&P7(^=rd5x9xmA)jBMy^+v1{+utzl`EMZS z8-8YwSc>mEo7rK=V*Uw(wbCF8; zm@%s3acgzP;ilHerylj>iRRZC!hu*rIhHy|6_?4`BEW=|AH%x70 z(I&6T4U%L7F0jM<*n5ij+1fA2I^ps@&Rg8|PJOWJiSRwp26emu<~+~LD*Y@iDWO4_ z@R4;i845|IN!$XXPFUu9G%xP(!O?4^1$2=G zheNMba&CK7DBaFR2vnTOQK>FBGyKQu^+wISudf=cn7*6Jqb5ybU^PET~g z9__@>1;9Ywx$n+neWN^mZh1dx=~WFlE7V(=Ee5a)Pok;*oAVX@w#`|MMtDA#zYq>j z=K-vg}A(p~2TJyI(<90%orYc8JZxxl&xGbJu`}_0I zRT}8L0i2qT0Rdf3%Tz$rKnaAgehuyIaz4E($(a@w7V;MpQt6uXmjCk zI6YCXND<%c?8}8!1(?mz$-h;!{2OIAit#B4nMC{CI)Uymd>9`LE51iHtyv}rg9~yx}qIxk|jp6*<>-na`%21Tj)LtUzOr8snM&8b=H=Ge~>muBUq$jEaPz! zScOcn-VkNT`r4YbR(0>#^&K!H(XlVB7f{&y)1b)^9Gv`mJE*u{d?YvQ^?d2B?fnEo ztH%ey-P#>nn5ieMZwJxB^I6;W63X9aXIPLtnPnPbZ51V_OE_5pfIFfB;zkCZE)cy? zsqSpu^}&|?9paw=yp)`>pPc&xn)`arafb!Wr7Dd%amTO&t#(^sp02dfheDtH)U?-J z4W5L!gtuFd1(CB^@LTWmfOYGx-EM!cKhVhP9O)gz!x|}WnNZ(~kWr-hI)@x6W@nia z*ugk69^~@WBpV%*`T7JC!)!}{#Yqe_71V?0dh0C##Iv^N{bo$ZfdYJ>Kw(iiFfp1! zL6UKeiI!3G_K&?XqoSdrJ>@g#0vM4s=YfT9jI7rvmj%TcI_rx!o_d_04Fz zW7Y&k9%TaQl8*kKa5WlKrk)W=CvdJmJ}#c~cIaybGRVY|*93=zpliV3nOj{XJZaI? z!idH<5K&VrqgDcmh=r6J$xJmN9m?Wh%jsXN?3O<@c{ri1)czcVK`*Ee{r-1>?+#=a zzHpZhRAvRX$3BKjCg!drurlq8z{r*yduEW8D+P3ud8o;YMoadrst~Y~a#6s>f77yw znA;AdmFuf!@5yxe4B0$75|X5;dlrH-gtn-kh>brN2{yZyEf z>B)NSew(@Wxj3qiV&D3c`vca>|4!0{jatN#ZW47+&2%v6IR=6MpSQShSRMu46}Gn9 zcK56KhxG3!IU?NAin~zXFZ7gMZ-{U953Wxw{W#{}(o&EMfI2M`JtK{pZH7<+$+=gz zws{qeRScrLg=zCu74enw#=&4 z*h+%fwYS%&rwRd`H8ogIPt)giJ+{_7kvlv2DW%<>R$_XAAg_?0kgtFLX8c31{P(2~ z>>K{uy8~gC_r(g>Eq7=jxN_9HI_gT>?dNO4`+HyM&wdOR%k5?oYB0)zE%33H3+t>n z8BpbrB#(U8SaR{FMf!`U@0&DQqofHX)eZIM>!`2dtkY}`bt(wa>=20i{QUks7jAg? zyk%@`Ebfcuew%}JM#e_Acr$pU)xJF{ArFs;zm416bRma+PgxoEW-A62O*Y$iiVHmh zgUjvCE*@53)zpMRmhNk5NH?tgaUnV`1~XkC-UrS}2m@t!_g zwKIA!DM)ZgDfg&!c2}&Jv&BS*2a4X1g(jUf`aFZYd<4($M+zD41~{CWb^_mw_-g9> z*&RxtSk+DhCQFYJ%4s7o;hVX`@pQfl>wfX!^=*egZxEnk1N0U21N|F; zlsgJleOia>VqlPBBp{8aac7X2uwq)$6a$0AsE22lb|_T~vDe>n{V}}fV=vXahc~&x zY)Vd(U88ApklLAU+=_<_s9{((S?DvGVQ&aO+&Ld-y?KpY=)Y>O{Cz!PK={7ccur1g zKXMiY7{6KpnLYFet^ZU9r;{h+d5e_^k1U8U-i)t}C; z?ey%-_OoT(&}-a(JJn{k}vBixH|i1$Sl_8stoufHHaRIRs8pB^4aUtwS>9sWsF`c<)K@vMhkU(b&w>ihWx3FLQoqe)6a zox4@-9(iW;8f+;a30lY(ZgTGh4cbLJSQ7ulxX@_eDlT=sxKLSYLZh z?>3LPEV2ELK)!&#r;W4{@XO^wM^8DN_2oNmnsKl;&(`Yo$?u@IGe5pW-4beTugcw9 zVS)Vo@d-*I@WNOI27c|1n%^DAd~=AxM4$q~O6^tZ6IY|I^L+m*r7#Y@=iV;`H5Hqo z7V#xPs^xiBf?ay5q+Ps!&L#3*E_L8J0smBNn)1~F17#8m>VJnR-7A2DJWCyZ_gtiL zxV}!6?^Y4WzI~i4lbGr}f$w^qpIr0wss(`<1V+CJ1|nqQLV{9&!n}kD(sN_%m<2y@ z)}c73*-$t#XUmNVGJ@>F{;msJX0D6w{II&P6E@D7pEnp76GCG-wE80yG!&2v!;G%) z`e_XszPHiw1cjFBcE5)+)^&H&h1?|bukB~^C#tvWmM3CoGel!ELLT{zmdmz>fIoHj z6C#sev6jQ=Q1s!=K_L;zxu}zsgskkNwT6BvB#5xA9BPuIH!9@SmgjS#_xv@TItr?d zlcG>2K}y6Pw1ewiVOc(~E{>7aS%peKU0tKhHL|SEnB0t-QKoLx%rf?tk6oWt+ThFo zuSlpk_bTLSq)Rj|i8P~PpR22DW$(uago3tqY`YWplD2-`Hy#4-Kj+E&vEG}c7mg#K zQ84@2JL1VLse-itgu0cK7}XCA8GdhYid_Z_{S%?9>=^i9UiGZs+jfYUGS1H--d644 z2xkxo1GUAXWIl6-!xuZfy{e!;wl`q>s{#_KjDGmtG(pH^%wxiZZ2aNTSux7MHV)62 zWOEjx1&nTbI7Z77hi}r-5A{~7Rv>a4pug9cJfE?F;pZtC_{neatnH0i2mmQYg#|F`>$BdnC#P+fb;sR)^?Jkf2a4ZfpzfeOJnS5; zG;!jH!`U}Zt$uov;YN>alt#wwL+#*ntFP%SW~i^nUVGXn?|btg`{=i>zu(T=MJ~2f z)TY(yoeJ;7Vs~e=!ZUlG;kuq!fZd*!>waE$?ToKy%mY+RH{a6=Udsxhd0IS1|Vx*LMNe4mcJF=&BXFXezOR|dfC7KOw4kft+G zWj$nXiAvr?Z#gvED%b*QP@0M#=%+e>@cd{R$Tw*k*!9xOF--ms?4P0L$XZb^V;-R| z;(7H=|MPS5r7Y`#nFH$K&k&S!6)~{_#xi<9iaYjvX*#NML;()jpnZ#`CFe5J$7Z=s zmCy!>sSGp<&njffCfvD*Jz8|GU9u^@w{g4uY&Rd0Rh^=P>y_$G0w}0Rv8I@$lojYi zYqt)8nw}HgpmCSEjksnD2H0cUXb^{)$q6Y4XlS|orXqB( zqo=uOB1MOlh6O~4pz(Dfiw+wF-?1-t>J_-_8lg4_Tn)3bRJWd-;F@Q5Cl$* zkRF6Dq!GkZ!hfGFj__eAY2rC^plA?^dnL8Ra9tOz2I6ZL5> z;_F?!uK)10%lgSrx09R0v<;5|>|A~JSaEr_kDFXfrzdagE*aVO1z}_wre`5PSo3T!d9ku9} z8;Eo+u4mMUD0jZRYqwuQZ(6;Uo^}*7uq$q6V}1KtPqnhn>eox{;70QU+O9ptNJ((; zdVI#;8e$_2E~pXrpHO%-CYEu*SLf_TJ%|(p&+16EMY}56L8-JGJO%pb#HKN=+_&G;aR$yNqRYlE7Ek zej?e+d4K#QdZ&(9ys>FnTUR|0W+c#mH-5)n4Cn|R%s0s2*(uJoKh$XHf8W64j^@IX z@8>+__7MZOL;=7@8hCb|qV4uh(j6$LQS*o2W*LJxa~PJBMdpLDhO431rm-ngIl*eUzoE*lGAlaeNVSqaJLkON!jn;uyU zyIWQKmgo4UylrctX3Avdxi)q-Vz9zllB{&HJ0EDdI=PqUw}~t4k0HrIyWlL!c(q~= zK3l~gc8T60q;Y=4Y04G{k+Kk@jH!`;7ZDjTgT8h7vwpi`<4p@fFI}%Ig+So9$@ynp z^xlFcUoNOC1_Xony+E#c8j{@-Y#xDKn=3h4a&gyy-ARAWSm~P<$4J_ zA*|*9Y>Kh)_JbBmKSgTTV>$H`_tV%}K{QGZ{BAAkvz%+5kFk~MFt%RtmY6LQ- zk-yy}o(KjdFhQ>Dayqp#hf4m6LiE@zWqP2&LJKur@{fh?4tk3?<6PfKel(#J&iH>1JWExci9_t1TXyJBu-vD~jJS;hrEujyjD zmLDDfgd>``9O-31P$|$!4MKC7@S=~qshpOD{n-N6ex-#2l47rIn>c)aTgM5N4 z)z{}c3`m;d>TOctG$=MK-DoyF1G4cx`~(MJD&<>b{JH^<4@ZyUblYUO5VF1og?2#FUj8 zZQD)K7c3VS3>h*6{!TKtnaUm`riyxabOSXM7IuhsbjZPseJ4|&^wDEgxtS#^YP2y5 z^?88mdO4&+m_T*$cqU_Ud4Zd2Wt(*IoA{&ie5<^Cow>ycb*~Pmav5=I z&PrU)7XE$`{&2Xu_V7|ad#_k7d2Wc;OBM0>C8&aU$rIlj6(Bz7Txy{CFLxUAVa(i` zzriQcsj)xDY(M?9y5a4qsGPYL$L-ijAe0B8_K}h*k`klaQwA#HqQcCKJ-qcX&)Pvp z%yRAiKKy-Jc1Xz*6SW&g2}?*RDg=jYDPidMC`8yDmy`MYyuoqeh>`%5UskvJ_3UOR zcg<^ZcfVxUEo3z+Hfy+>JixxEOBWkr!)IblaPPUorTt`iOW2PBt^K-A? zg_24FOsx(cCeu&LHl4vqTDKyck;I51A#t@pB~BQFLr-Mai{UXFZ{bhk(0$+CuQKx* zMv?JOl|q#y-%=B!2RENKvjxPX7AVFdR4>L!qMxwBQk3MGF-eLn$|( zorQt^RLO<~MJP4~MCXg(rl!XYy0c$4?1cS}jxGuR5`whf*TUW8EX&5=%SLp8g~r3Z zn%q|m1B{|bZ(6vGhMQf8jlO!BWSSHv7mC-=jzc61qP=QFaHuFh)55lc1s--z=Ld_z z11csak~`xvGzq;G$Y-+PxM6TC?wzEn2PWO#pkt*yra}HdGBhTkPgHK-vi(A4_wY zj`<3u#ET=SNhJHLG#m`;>_oM-mtqSH$_3gxz$4*X#b71AMvpev-}Gm)OeADJxg#PL z>I?=i3Uts3dA0~A<7i`W&&;ezG8WvL(+!%DDY;JlHeqz;#?dFL|`EX;# zr8g0^&?9U4f3P945egPckX=$-PT=~!z*heKJ0^V8r_36*ivCG8@BMIS@_dz0d@;AB zH}qX#QDTF7Es+(EOLAVooFDy4^ozx2tJ-9}rV39lIhQZre@aO*&aa}~tt-MA1xrj~ z4LRl3OBcyN+G=T$yU+G@^3)^sBfVshbzT6iJ>lAFM&ctAl>U`R)goE489*D{J$NM0Vp|5AI?Rn~USA@-av zqQ8JV-#jPFUm_Q+N^G0^9fg1s+c^}})2DHNz!J ziHcg!k};IyVcNm^;r+bMCt23eFtg_mfsDv+gRO~!9hZ|v>8)ViQU+T$dZNv8#uwN= zqz5Zw5PWq@XDhCs!WojH)mJkO-w)T{U_O`GTCmfC-XZp(om_ob5lR>M%Zx^ zn-%=nv&U6)bK6bNT|ySieZp0tUL$zvbti(S9YFwhEAXOJ={WJlu23v?CbsgLa^6${ zEZPcMSwn?TyDaB@_5Hl5k8NI>hbeiMR<^>vdhk*x%G~)Cn3dpp4keB7&@{&jp>ufn z*;+lx_9@HB(Gj7<>u*5->d0>kCCk!70Qtz!i>?9oBU6=oQ!h^Jk0jb4-a(y~5ET}{iWH^IP9jFUnfwAc7ESCz;yD=Q> zmk5~Xm$6!W!6#CHbG%`tz7LAQ6p~&s(Lch594>irW8+mrtcnazsAPQxk9IqUJ-AIx z#`{vzD02HdZ)`;a>80y3H6{0c+>U<^l%LCQu5?$I*pN|LU{K(>xpcq7s!)v0pD!`$ zygwiss(U`~;Ydk&2$Kb(y~LpuXM}_tDOtd9UPz%G)kp&!6KEFqjv+wNfOc-W0<+9= zhrb<;)ACF}){Or$Vy;w~1~Nv5R*thLYE2%t8B{M-y8+O#b(enlzln4>od!4O3@mPS z{=E&kb!IOqqei^12M>x8jCr=bg3KS0nru*WMOiPSUIPMJ6@n#dKq?Q z)lSQ=NXo8d7?fxw@n5F7S{j{_a<_+^hOSCx7vf_kZ;j9yzEi$~Kjf~3GtwRv47Q!T zVrjGDh9XZEba~REB_%OiEYYAbXyf|b_@I6f$l`iqTyodZYFhc zyiEs%tz_t1ukDSu;&80iOyJJY=Kt~PeJm{KU~#*4$9n%Unr!34yv05|FoinUCke-c z5WvYq6~w1SLS&S3U1RMR6Md5=TU72gBF%0wR=k+YU*mCMeoWDR)QRFmGn7YZ)Tkt< zagTU6!&(>b$zcCrXYB;Yot1dDVF>uPKtQN1k|Ki2WFb+vKX2(|Gk-7iwRB^0IoODZ znHx4imxv_ERk)((f`i{ARGu!^7sI0AQhrI;+MeenvA}IC*pModTGP=*kMv4*NWgQ3 zS?Txau8_#C4~9f1e7rouo*T?`D>w8ZV_8V-sP!)k$(iJJ@#yKI%m%lV*7iuHt_q_u zsKYKO$D%XzXsyh5xIidm7lc-KWAI$h?OQL%sV{RJnt$KyV>ZAlw8NWw;!$dlLW&Z+ zK|~9pBCVu_ss?487?|X^`&h#b)r>6H!PDOVQoz+G{7a&0Eg~WW(zBrxM%{Vn=UJ@G zpCY3}?0qb^KQJ`od}VT#Es|gK@1LK?47iY{W|~y(_%7YX-;un{68-`fqmjmi zq0E#PH~`)YDk&KbJUs6zB*}OtqDLcbr<o;f9MoYnXIaKy>_FVF*u_tc$naJ3EZv;ds8m9qz z7#4o$TAX<(4x<{B9^)ZiV^v~i-)3w@HM_#REr2?KmixNVhW^hU@Ql!<;`)82Rk!2>5ru>2`C7wqQ5 zw86GDU1W&x-2u1crh%Gz*z=fz`(3^Gp7jmoO*U_U`|$|@6CZ^aA2A7vF@gQ1dIn*y znwhK~UnIDpd(Xu3eCYWI6s#nE?%BwQrbp~8OD?aCW3g-e zE`h8;w(q3Zzp$^H*^Ssj5AouJRJ!A|!PXk##9D^Sh(GK0fTCth{hDrlXEnFxD zhRMavL^%IK&&7r5gJeU=8Sj6iS{o9Gd3n}lcX3Ip!ht}+)Yw$tZ(f?CP!HQ>6t$|M zb~hJ`);L~3yjHCl9Mi#tRjp=r9fF{zMH3F8GC5g@fnV1AIs>q*t~el9Gi zDK{>-n#5Xnhyc?GHJLIU4_$doAjxL8Q}83UYGx?F?Tt-$eLWYcQLS4l+?<$A**DLZ zvi?v1$fE>HFu3{_F=)1G8_UzSIp}G<(Y>J@s=wgjpSJ*;iWZWeKW%+I{DBv={pnn8 zy_?;j$FIv|#Tjs7DCJT0^K)8;$v;+%fh4hNN{eJU(ZdN#$Nz8{D-tRGkdF%`aGFl3 z3wjCH8I4jE87K7*(<36m|2@Syw{N=zON!MDC_meW^dXSQ-3~76oQjeTWeN+JjJiUt zrDjTXoXr<S5Zn#QP z@aXW-eHQlL9pi+xi?A|BCx)px?K}POzH4=$g4klx4FZYe*{|#V3sXY_z|4F;utLJ( zVZgNF2@GNfGwDw#zfW+3q(#9t78gxKOdBz?tVAsAokhCMb5#YOSr=9h_WhChwKSrN z%3IKTFF`UawQkSFa|_zm1h#1DxIrapfwo81OMVyAaa}{_HB!kBN}AxP|+IW~a=@ zlDZh+_ba|lPjXYgjH2P%cAU*r8Xn_7eqJ>bx^z=WY_@n)^KI(RolMQtfbZ|CQqsZ- z=;{E8u^JDU9ge`@mk#%X;jOTnJK|AeJn&we$6#~gpmL@S+B^Zby}kFd7hzSZ@|r0N zv$@bynos#XeF;O{U`+Qm_3ZqedjaphG&D3c7C`%g#R3{W2??ggU$(30;>j_s$wuKo zu=9Ck_^rVmd6i;0CoeyAZJ7VIV{xwII4BR>$Sxk&>+hG|%vLDvtZFkYYi?&DW@SY3 zC@b55IMuU@)53*tIo2%XV$;KP@FaFuj00_{(eF%OT$L+4$N5-IF1^U- zN9yVR2ohOCcH7-*OJ^zOlQRu+6sXR&S1Gia>1n;0V@`Ps8XIltqs!1O-6&h~@yTgk z+D+NoTJ%8WW72xK2dGkiyrQCgMAI}J2nL=rNq(#;x_k`awIGBM zMeE#;UQhPu9ik9F3{R(FMK&XtWT*y<$7h`Ywv=%CViHQd@0&5vMSXV=E6N42pzRbam0u~AV6a0l;ots7&}@)MYM2(bQip+x`59NZqy+o*<_iH9)(bulhn z8px|jcqO7h#ag7Unu%p zrPZo+oNv4%nNbu9dxrYr!`;zwKFp#TmFi3NI76otg8|?o5 z2af%DJpwk8M{Ja;>?}!Yi%!ZwY?$zOg&qlZCnEd~pAo^tjxkQW#*+A)kDL3K(dZ6Y zU_L7N?yLn>7-m&`aLUM6l*!GdLtI>!%g84?=#orc}NA>sc z;LXU$$h*bdoeJd+nyohrK~NO(Er#T-rKPWJsldp2sGPvA<3E5ot!Ve?cuj1vL>94E zE$G^^83V1Uq5@tfyF(sc+4JRERL~zD3oahgoPOwdMvsK(7EJ{8cmF*D`j~d!=un$h zJVBEz*Aa^H4Ud(V1FcZ(vmHX=a3P9;+>SYoM0`{1#PwReQ>{eK@8HN^7n+rJ;E$Aw zZbt$SY!p<=_ouxTU^Gv>fZ9Ctq|;od{mxesn|^7LDhQzPan@ zWKAM^UIQo5l)e~UNs;xt9w$8fLtgn`bVdofGdf>r#%*z${Q?Gw+FC&e5&XKrCgZc; ze(RJ02df)sgr-};KrvDWz1HuQWQktpl23g{-Qoo*?O1Qz!W@mV0~6EZ3MqwGgYCi! zXjD|xvXU5lUa&v>U&JZ2?O-`b$72o9f(zrl`r`H#$|truXnt~~PC=bB8mQht*o{2_ z$?jkoY?FD>9uMDt=p{=fvIm~vE8L?lucKMaX2vMZ!GdpoQHyB_3hr;o)NYoZmY0qY z^7DgTtlsp^bVZk$4q30uh!io=yEcE;s6g1)RiL@F2t*a4*=E1iIg+cj?JKCNk~@*B zDl3PD&oH)ibN~k1R>F9Jj5&p%Vw@T99}Nr-j)zPCfEkVwR~Kmg#d!NEacm7oEY(Y$PZ%)8Um(=to>|5qD@$s`od6>JqUd0$V`ZloYD z-&SAObwMZ7uM@@`shFIfrytPN#M;!1bQ8Y(J1*8IUTRC1b%U_JwEl7ODzzNIjgD>O88;3bdp zOMYHn-mS1O!f*^y%}vh-Ju?f-{`EDz8Q#Om>`#BTv%Eaw{li1;t50G=0>GJFDqa0P zUL3#Qrghu2Ds}hk+RgFfVV0lai#%*F6yXp6HYK2}jG98LL5y)mPf6J~O5kg~bt28s z?Y!f#^Y?;|R=aJkEw_K~-+1D^S7Bix5(dEC`Mb6E3l0Jv*Dp0Sl}8N%0>UMghVnn! zVobcYZE9))s1^m0I{<(_duOG&DY>;^67qZ>myI{w%+}V{w=>)~djK0tAIcvd|Ahs` zcJG}okAzd+@7Ke&pYM+&cI38D{J)TssSK$>H&3g29JUO!v`IzsBq7~*&zp9IEiKOt zGfx0Syup=ft)tEm0M6UxX6v7p+~#Js@5fU9a&GPa{ZISg0Sfh-4JI%`!oqiJMzQ0) zU!R}G^AbZa3|)Qztxnhbd1XRf+?YI?X7}G_Qg%JByI|$K9ItyxNn42ozPEoUZ*t5- z-ig**-Y?q;Yx3LBkdSc^E17mZRKA1vo4ent)S9G}$`gQ5P`vaXI^e{vt6lRX)aiZS zr-no#e~=3vzTK`RlFJTkG+UV%N+jeMjmC^$9M#L@Q|a;GC)4X<%!ZRj#nfQ~muQL+ za~X}mzI8(ocp)+|F{!s$r+>oNR`M&FLFYigEORe9F;{Eh$GXgEZD(@3*&-;G>bN;L z)K?Ms;B(k!bqoUD8vxl7re!9;Mt41eFIWDD9flm0s^_C&TiEXUa=q<{lg#^_QzD6+ zAC1Fdc)3i+Y2!f%qlo|wjFl|oC(MoP2A5PpE?wng;P@12l# zr%r0^?O$a3{&=R|Y?&M-vM`f4N-p_+oM~5Qu}Iz?y;_d6*3)Nn&rRCG!#=C=Yn(`u zRE6`HoVd+bcKgum+wSxAE+G#d6j|(A?_-MVN~d*;;Nsa(z;L1klg%<|YjikH8ti`* zd=a;%;K8giAVJOw78PW3wov@KTD?S2t#EIi{YYl>$? zCe1AonS69(6;1%U@65n@Q>?RErEdqXDW@LW14ZV|Fp=CTqrBg;*pZcyVMU(ZIZqSA z|KL(na&~qebGv${hXCM4_GNK8k=_;O5{}oK;;YL4kJ|jh@Sw21PEHfWjv_(Dlp-Y> z(CIWuRXC8*z+oT35^yqPu&n#O=9MLXSd`|pB^<>Q$)O;}QNtGqzHcEUN6d+j?$~;H zt^?!SAMY|bok~7+^a!yIeLP~eD7??!e+9z2=aBN3=>MZZ8y-)jnC!J!SuT)nw>wxj z%S|3i+CD4CrvbGjs|4e;h*qz@8bm1rEdt1P%lh3)3;1ueMx?Kn89U_L;xBsZQ0z9ESk! zU!6I*^t1>dJAkhZf%17ty#FdoH!gbHZ*T=;_IEd}ZG-??DRDevtr;~=q?V7f#>p5+`)i&Q`u{eTj-v|8c zRA;@;42WE_%T%q_Ob>+TqI`Jk@&F{nhQpZ}izk`Z^}I6S-|X^e^f*dZyX5+~>Fu>x zZz%rq+U{^M-5&@p(Q36dz1{0i_nx!41WaAeo&W!Fl!O$ZbpnuK3;WG-y`R%H8cn0W za17Pd)XYWzd3ZHjZEbY_o)yCqY1Qfba~_0|$c)C|v6O77>3J`+TCGgsx~>@HPiv-0nkKZ|`z-RloTt&)529z3|wz-&<_I(-Ukj5_mhsGu0Oi3$_vZ{*P55jgbw4 zQtDPL5`=2LkF){pNdq1W^;&EbA@RfiyiM&a#ZvrB1RB11`)s)iY1h>Mtw$|`Ya)Zt znbmq2%|iRXCHZf73=4kvP3H&PRhy(P>e#%!LtsP;WQ8N6jb)LE%VJd!C}niXp~)ru z03#kj&-3kA^+iOo)@7^5`euJDt>Bt4XrSC$dvq&}$zbw7n6ujEl)ZwRZ;>Tcs-aO^ zhx%yv_pitQZL}fN-zdv?p%#Oeo)?^l7hQ3BO_XOdA8%V9LLBc>!4l|vl)t3o;HHX~ zX%81FHOf~BOWlvA?>Sy?fWH6T|5KBh9JMPr?zMOs-yH1iYiICt<&DYmD|Ia(PHO*`%i`6XT?LB+zwT$m5$Gzst z?X0$4f1VlqdRHIlBuf8lP3ZM%aEc}1I%d*yWfS(Zpy|k7S9pWx$=vUCzuPHm$c5{f zZ?^|oMCaSPjL37M$I9*{Lw)z$^H`|J+lh)NVWfD=%!|nT_2=8(kC)yy;-$9%yq-VX zg4bPi8fCh{#|s)Ntzalk(e+mAsVu7px%S^rLrLB@V>H?SJvDAu8#NaioI(f@H0N^_ zOUI3a_(lJ~^>~1oLntBV%Sz-D@=mG0z={Yd*5}wdQ@6q6@53~bl<7~l1ffa@<}fIc5Yclm)Ii>+m6DC=K@3;AfGmp3UBMD%L)yYQ<>C4vpJ%{XC$C7{Ceg5@O)N3vVJr<_*ukpIG)(y{Z`~-J^-;lB;)=4p_d0D zMC^J)n`a~cyq3Z3r2j*gt%u{H`P2L1GFc`_G@g*%w;vu4?rK+NQsT3aWfbAd^fQDo z+)D_`j!%2wPp} zSKsPcY%nO7&;3vrL#m$_G9EEOzxoRD3QBpWad>8ob z9b9x?d3yge6S!&UKE;KiW*3eCOPtJe-O!lU4i>=ime+?+q$BX{*wh8(EeD9| zjIN&T-3~uHxE}pEHz1yccy0Ue8bJ;a+t0nsEy?!3K|YcI1RL^jQ{=MsT4i*(>BDmi z+3>?z1ZsJz8{LTwkmQC3l9;w|$>asTKcy*>1r_*-S!&WvM9iy*Y&_4I1Uwxq`4Pai zy=;emUIiBgz(bP{KLtnT`5b0yXToOq{VJ)2X0zKAyb`qF54h+!ax+GatV#DnXzl-Q z{2sKOf+;}y+K(Nc)UO_p{?7g8^t|@3UCILToi6sr%8n|_3FaFuzSqrd{disr2~zP9 zHb1%*_#BdDL8b%{48hdml6VYyjK${|PNJAB1rYa)V2{}ZgeyTqi*F2=Byj8E-N4dG zXu&?t6aoVF2)9N%jqXvCX{(3>z8~d!<-I94n;$<1fl0j9UDJq)=HM~-;jQ)kP+hvO zrK`IOsm-~E|HVW>N=|D65ZGILnVa?X`t3IKHY5qDUvkVsXmTzNekvfE#tOM_y-L-j z-8|4_{BmxqtXC;8`@PK+Kovw;OA$O?*C_4+8cM-ZW#g`p`UFO z`y#OPWir-TwSW{(MYICQ1jkemfDs!RgSEtZANe<}f#1BA0x=<%rcZ#O>W^^eHyTR1C_FFkfiB{1JWbhYPW_ zi7eq(BeOQI-!WUF`A)IX>~r-jNIZqVp)>umS=q zhgk^Q+e`TogOng3$&v<#a>EPu9j#|)gveqtQD%Ub;1!Mwd7f+^ei!D1%(lPU@(KF- z`8pxc@#9Ac{L`G#eoW8XTzZeC2?`7<_OoxKRoh)~R=D0w7xUwVSFgck`{vsNX{gs$ z_gXOUdxDW1DGU5iqE64ARIa5^iiqC@bI;?hw0m~xP+e4LCf12L?T2>dz0Yhw3SX8w zCk9A)_oO_oKV1fym^v+4h1-2Ia4VL&o;u9sR8xM5m&N)7jnOZ?OrHL;xb{8uSLMj? zYfCBFMnZ;>&zv*yfoqIwCmB{Rf6+N*GK0&!(kO8A&{qGj_{O88usdb)BJ{dFO zRM-dX;=n03Nv?j^>W~krFr23A3CS|xq4LD(1W-uFMmoOiZx52;=n-aq3523IAfjug zvtO5nf8~^&7w-b(LU7NP{HvPHwsg@5>28F`YoX94FY-p~Ut@=4Evy~I0>#VCUryfN zExPN~heVHQCEyATx|W{Ms4h}8h;|QSLBp)U{p;|F6!Rj{dF!F~;d}h!V;O98{S0>m zuO}*H0(1fpWTqxLX1J}Uk;QL4=GM-*W~D~bzFNb@GRiKOHmD>~iX6ylY#IKih0<&f zGGV{LBFDsv0hcz0z$Xh;-^l#w<^ZP)_uPi;B@43)5FFTdR^Pk1?Y=)xi)boK3{Emg z@bg=K>DWP@@3|U4rJ(ZUy%azHdMYVjZjTrkc1?VKn z?8b;Wi~I4$3*GCwuQ|1k>H5igZ0LwnepktNo-7a>hL}3U8#gATHRlY~$sg}$wuYgL zC&m?+B@v5kvIYKnEV3XdHa;Tu;$7oErL{m8TF5}6agRd>ShFgLL@q+9tbxL+v-*fcl1wS>nZ+|1I;x>2a+poT+;T=--2d| zQAVevK!A8Pay3?o=V6>X>sr*Xr>HUFt4n+RFplK`&h-HUVC~(0mSc0mvuKT&% z!+0St;MLWp>tVxY(jtM+a{st^-aXeOgzc~&5u9Ib*|c<4(@dluF_wKc*s;HkPU<{q z7QuITO?{suNUi{=ysdXn?4Uh+C}AV}0~IJRN>~Z$|C-fNjY22WCulYpWRBU z!qN@s;?2{Psl(te;|3#TsEi>;1ntOFE>bNc$`{Ozr0W}n_}jdGx*=n1>x3JL$y&~+ zQqZJol=wxyb5%H*C3KZW5ChXP*7vz_k2-jPuV_-A14)XS{OkO+Dkw6uf`kPls>z52 zs2!FxlP9#+Qv^4w{taD{e8-{C+H~4ojIWU1$HdBgG6Dbyjtcz-^|PM!t{5WvcgZOj z{5{KSA1E!vJk3S?FIh#{ZVH@b*y4x@eXZXhN=kCBrERuGM*N5+4& z<@PS*7h5KyuJCfp^-caKm_QzLqu4@{jQjj);keiW7%um-Uif{=MNuFx9Fl5esTC`- z$P!XJ8Pir;e#q$M3jFn{jE+e5#pv-)+|=O{{kR&!Qc3YN>CiR5(aF=bPCNJs*2#tx zFHuA%Mu&uo^Zxgpx00^w^@K4L?9I@ZwSg*oH2;>37DGwcOr7BCKUxv(?QiJ1O>fDVN0B__j$4Y{NcYBtx76Zm)qVsgdsy@_K^BEApbi+ zjJu;QlId==-oPkitC1x(;c=w$3|n#r^3&E}SZCDPqKa89btpc9hBhpRuw%PhjH-{Y zWABbn;=(wscwM36arJMHwMoG1vA`Ca!(|7eG-g!h=hErV2$aTZc{Xh`5|fkB(@7uD ziH2%z221{>0fw|#RYkkKR9HcOt;z%q`EjaMO);-O9fNfIP;&Fp+77;dfeet?jqY;* z;2ytrQ$<{@S*lvXU|4nSr<$u45XUD@r|lgOfr<(#NBOoQzEOSqXNWoqt5%!ipyT?> z00A;{=y|jX4M5EA5q}#maM6IQjtbM`bCi1okj0;jDgbl4q&weWB zNP6b41qDPo*wk67uZ#6)nZ%>DHXq*CV_*ao$NM5!%^28l8ElI}cDgKA5iMkMV^L6L znQ5m3w@e_KdK`nWq@$@TVm?;zPBVF~4G+8B$AuT~AQyc;DgS7N&1xZVlr3L51bxV3 za!cM=W;6)xLc8^BwRKZe$|me72t84cirVFplAPXD*#^p*daC9NskAKT(PnlGq-?v{ zy@KhrK*?{2V5()Qz}{M%HwIVE5k_!JcZkg&*gmgaU|T@>N4O`877yTq&|I_@UXYxD z13AW6bm~_dsNM+IDy0Zz^si7_8D~VL4q~O3)wlakLRVun2hhv*+7ov_MdSI-{13Q% zC9!XkxK|id_T@LTnNAu=GaTiHs*yu) z3l~l?V^iht=T0%NDH;n+1S@QEJ<|Lm8~PbCO1bVm%jIR9oPYMS@tk~$wcyFWT+hrq zVqg2UpQJFU#Is>J#s8>w3Ppr8S^d{2LUS9$zkoK{5{4hrOHl_6t_{ z6xb{?Nuc)P#EvA+oAIlNwsNd)!LlYCCXiU3lr88F^8D;fHqQ*g3APo&aQBG+%pHb$ zd!r7E#TQnfuCO{H>x5~RxoUqYG!8Bn-6d6d^zp`;Ul5)4XpGWVq98~n_GeRq|7lOq zqH6Dhc4`ynSN*==gOqFX?TzQleOb7m4E&Z3$XXVE1{aH5vnxTET}XB6iBSP_>(50s zImWO**K{nZ6|smd>3oH;`!p)0LKGpjG7_yn6pPzjp*;9gC$;tL$X2W^^liZ*rrQtw$?Gm zJp>t-RAkA4Ue@sd*}BkBbTWBT9@4&b_V0@&6^yL z#Y@-dPeD-IELc`;{?im3ma7z3k$QsT2y&wMj41YasmchhzFWmf$Qa%hLrC)!5i@l1 znEld^n6riR_d*Zr7E{F)7o{YZ25#U^_LuVS@Utx>;-#=6p-9oRQAU|Hjw?;SrCad) zx~X!GqBnKK-PT5hdTN~vZa>0wm3U7|AbIk%MFNR(--H-qW?T$jwzguym$2(!x0y&1 z9rg0Oo2(brzpN4~qvGN}4e07Ar<4vaSZ@5_xg>o1Vu{NY_wp$`8e-P;@$R|B+HbzJ z?`K939=3}rQyQV1%fGTU|6txzQU&*M+uq(8=FDK=f!s65cv{aK>{8j=33pa?rbGm} z1@VT{hxLuf@h}~tOOqv&ey8u+h`eMz)k6jcHA|0v%C#zX&~fhWXE#N|oio#d#vdwc zx3kzxM0Du<_6KptHrD4rrCXZ401&!67vA;u`SyL0^$(jsKB&1f%bOgyyacy@Os80Bs zD;2SSIj7$?^jRCyEI;gYo4&PHJVV>O>^TE?k|`AuyWm$W`~PO^P-|c+b?Ib@Jtby0 zWx-8ieFnEfGQkB7am5<`vnaQ^As_U!9&#Nc6}`M9t6#3LVwQZ;X%tqKQ)ZI4+R9hj z&OtS{4RBY7HWH;FqJ^u7bPzgN(WarNSjT2nWhY!>UtDXaq!y-tb+&Bnspbdi$2DnM zrf)7gp;Q;Gz>O*iA_ml>m7I)c1{(R@s+FqQ*FnngdA2IGzU`#EqgSxGI2k^>TqX|b zsHq!)@E15yQEJu5tDPbamZkbcv15ZRx5*R0P}gPKhsCDpk4Y%FMj`=EN5*$mtwrYP zyGPohBut|If)H_S$${32C)c1()z$FP*)0rC zg#J+39AD?@?M8e48Caco9v2aTO-8-LpNnYcCJqQBRPEkyU7st>V2G(vYMC)OGi5&g zt8!3-O|5U#POp$R$J^IbF(xuro0hb2z|#teM|H=N6i=@Dju49g7JvS7{N*uYYHl|5 z=@0kwZBDU~(r2i;FwE)HTtnl3d7GQ4sErZ7E8E9HK})aEt5}6j_mg zf@6nQsw{h3b`PFfXmgrUR$*WNv-&$6+RbZ%X}W`&Ky&m-knDBQFR$ZQ=8;o-fBm$j z71(~{Mlr1f3Zsfq^mKsQGh93UD)b0cVO`((g^RP-NJD~mXqt<(j21xaygxSZFM?id zxnpJ>gp&csw?%0!^$P+YBe2V8Kc5OJhM0u_2ZE%v#y$8|5z61JpelYNZ)GUvamYHR z#9yR14!Y{*o@1>rCle~xiw8J~3>5Su>4@vq%O<41^?y@o`1}wPvAokg(|3@|7&Nsh z7=Z!2_f#v$_nSMfs$>g1@UuHzc4Uje^PIGnQFiBU&cv=)PO{azba7K$V6NO3DQyfv4=?4oZ9NDAqYG&pm6EGm44 zw4<~LCVrInYHxC~Af>_+9~*zlIKb!OHmFKln(SsY0dFA6K4L7RWb~p0^YXDB7~hxSQklKr=#wz=V|J@g(6pu&2GaD6_;sl z3=E12pdt7Pi-Q&mPd%+b8e=G!6#wBTy&2rM2Id|w+P@iO$W5mHMY_lZKo(L{14%XC z!bOU!8H%5=&n;DLcc+UJoqSi07?eY8YK&1~R1G1XTm-334xJcDc(97ak{eCTN1Fa0 zNM*kNW@W$+eup%F^nbya@QH2{gO39W$>*qh7pt)0R2ob)x0idP78%f4HjpjDyD$+0 ziLj%r2Ss2ame|$5eGS!uDQs1+`Dq!2A34v@H_fY(q3ZM9=SkM6AB_1twSXQn!pG8l zu3Khwk4IX>P{(SNNi~yiLVVPb<4HCe@=$#9CTc~@GhJXu%J19e<9TCg5Rri-Lhj~3 z!N{xf?~4;?6~0-`d^P;b+qn&TrNtGs5}LgoehG8h9#s&6n@qkm7d)QiDx_@`iP_i| zgPgXFkvZfgd{*O~kluFHoP4|WIhl4ZrhKOQ$89%d;0|&!1ER2;w<||qy^4mUnEjRB zp1@of00_0>yY9hI_zWA&`N#sRo_*X|gJ)^{14?`d=q%X;BZ~#zorYj4zk_AmpfBR| za>P;un4;gYR=rOv%g>+XYrLuHo_^ar`yx?}NN_UUEt3TVh^ms=r4Wa9i+x+6F@F5R zmbmBMR(fcBFEXuO5U0{OC`VN8B@1N<)zsg`(*&SO;3S?Lk5R3lT~&7GAWxbGP3M1o z=2g}10_nx)u8iLtXlE|Z&mHU2WTgfR$DDL-d#>OL8`65Vc%9Xv7c#qE6>anydZa!% zw>y_L|AjQ=M}fE)$QA$YgnVATZ@K)uGp?VkqY7bm$#czo?atI+FUNms{|?baQ`07+ ztui7re4`yH8(PJ>*5?|D5jmV!5x~J#1(VmA17wbWb|sLpaeLb;V`k$FeR?p_phz&()`9jJ&aE&}dm=3fJ8;{mKy zN}`(~;{l29k`(%COxJ{xwHjPn)sl@&?@OL~?R4Zs=7_HIok38&s7TPc(3g`n8HHUQ zC&xp+w?XDTQ}cz%V_AfmdI*YnIbxIJ9T`)89VG*jYGCzVP_`jQg>RGj*>zp&9#}f! z%i%S9K}D!An4+FT78kWc*hYL4NF;vhU6YsUlu!Ok(V>oNEuQv4rL_K1fY)Zib-J#S z4b{?(UHxK5cJ;SoSTa{KaJqa_uz4}XX(5Au@teAO`Q~nG>!JtRKZA^Ad|jXy+d^n9 zZN_oo`X8BXR6{u$*6Fu|z;igQDB%}frlF4 zCirE{4f%X{*N_Yh>@Yc;2N>DOFk~#o>(DP|=u~!B;Px%%gbc`#wl+(Dx4y#8*}p|g zN`o82ur7BUje$sRxG8%>hd6X1Sz&qfI!CDcz#MoyWTBF%PXYgG~3gMY=GQGk- zC1UVA0cJ?@OeK*0m-US24c742iIgVvAq05z-QNObGJZSpo-{aqKk(HlOf$bov~cRx zd-*T*KYY4VLjIyftlHPTNO1v~ovbkQ7^mIXSArsDDHIS&P*D>9!KSu%CQmwX8KTJx zzh-9Q!-i0S84;y5)l{X+u3W%9=M0*GT zy;7kCpmn3V#7Bu>uh=n0>ggeN%}u$+}3xqr+Ipfwmx4iXegEUzAr8aJ{IUD@D(|&jgm@OKL*Up6J52h$z8My zY4%hc?oo{*1d2Yh&fWf;F;Jg&w*@}ygc^LMxw?bK5i&lh)(3Z#pHRL@$NkgbR>P)5 z*8x6rZMr{s&AV`{EDa6A$I2WKvEUo3o*Slb_FcMrtbwSs3V@CohP|TF@H&+Pl+|*+ zYIn4~Wo5F$z3R`rN(8UB`5)_5UR`S>4ss-i+KPERNISn3W9;xSNA9l=2Jz6F!o(5T z!ek3UY-H4bU{V3*Kk`XR0I8rY%)Y+1Uu7nDDwZNwzq{_144k0gUz9}7KNbAZlmZFd zV@V}~o2OSu7;s*O+JHd-k;tzaoFl{eBk>VYdI(-#(3t~r zft*6?nPKo*d>jY}wAONDQqbvs-H*&g08Et`Gz0$bz7}G=nni0p?TGqk+)2QuQoHW*3I@ey@+qtH0|LZlq`+edtkxE#>>dPlvUUg<#B; z^2ou<>8*>>8-n9l$;H5KENWi++2$F3zKgJblm~(lbIktX21WzYyNDU4$zQumWZv5f zLqc6^aE-UAB}qrBHE51>e@wg-5>8{(N`z?Vq+IQWeQH3y#HTm=1AVEz5E2vs4=~Nm-66Ivc4q93<94qp1 zN3bzdO)uj9ClPV$I=-fYh!tnGk%^RqTV%8T{h*G47^0`!yu?gh(=LECze0!@InXn; zvuECF*sEod)9Vk}`1!Z4oCMz0(g5;iBL(0Ae`^x5vdE<5w@8en-0$G;vfpY(P`?_9 zDn%VXMcsLypi--NA_bpCWMx2nQ@RAxK>~Tckw!_E3@7uOkTiHcSuhdAy^L8ePQ5M_ zD`Q4<`FPE6a91-~oeNlA)vq{FtALN9LIJR7w)+J^JtOO_i>eL9#;8B;#;{n2aAsj< zy)7v0#XvRVO3j9+JJ?UAWpXH?A|WV`@)cPk2TK=)`)qxiSK@}IgKvNNcVZ#GaVdWP zcgwlCJf9mfKjf=sBc?ig%Bu71NHsMxxtrWLeXg618+U>sDA~0w%6zlC|jH>Ri1T^9h|X-yUKZFFILSbz%toJ6f|^R!@7Qi{;ggb|Gj07o`?|u^o!#o;Ts-b+ng|jh+?# zqlX+7LuK+h;JMOA1>q)dnO!T6LDQebT#)|5yRzoyW6UAk@yy~SDKofR78HL_)bdVa zFPem+*7GcvuT5Tx1Yg2QPR6%?quPPD${8McH$>|hqVljh(W2Kr&31c03#{d!AOX51 zs2y>{%i_6oP2e(fzBsN~H`o4^G)!Pk*|YjH^jXrH;RV@sJZL(TT?x=7z9zH}C{LD5 z?ehe^Nutk1X1rEn1-VYzkz(UUWKHw?TI zup8S5SFVd%ibAloAVZBI;YtrYE=jTH84|}T^GBzccawa$_-2)g?Jf7uA&{PlB?*Co zAjO5>@*{&)L!L!S{@i@xhf7?*m`I+CK1+dB<-NNGy!$V>o}`L6eoU&uvysGw4YX(5?q0q^*L-me z>HK$GJfB%W{0P9e7Ms2AR?+~Ys2d5HSUFxxH@pRBBI@SvVDrmkqX z3V{ki$uZP^g&`m2_KlNyB@V@;3SvnXnztU5!VU2e$e;12ME$SKueQxoLed`*+Xt0& ziAfYRmWWITjRxWip}uORU}NLk;CzeX2?`2=2>~vn%M+DZO{Pi?V{$2cV_4gDUj?G1 zg7=z#TxLBlB%Y!35CkYdg+6yC!h{1+6pJ9qE5 zn7f|uk2?`(9c)3f#WI)EGDsLY2h2mX`!(QU?`{uvsNDT33NXD#UNKY^?!$B}{$W1* zXzjm6mADtd^bO`J9NxEo-NN>en*V@p`=9HIZckKNA+x6RJmAkD(PjuScR{*Jyi@iw zOb59=UicCIQz?gcHi-s_f~q@`m8z0e z5)DQU#wU_^A9RYj@aIk@cP>Hvcr-$$$Q)_w#EqqqsYu(^hb(L9>}A*$RAdQh9o@Sx zMX25w+{e7_t=|$y#io8UW=XyY$0*;EkIHo&=r?;Hgu6l}AldN*>H8{dig)0hOJsYezqVZ<1V6sN>*|PfKIX2KajC z{}unTdq-mA*sGtKT1IM|XCj#;Rl86IRuw{WK2jryl5YL57ntcpGk!_%bE=qNCbUDn z8eLQ;AI_sw7K z!PW~EYWKUg#zYE$Fj_}QY*NhSU)-ep-bhzB`FrpoIzo=>>(bfgudX*D&(AM^hoVPY zAv(V{H@67P;)~IvK~J(wUb7^V+BYLFJ<_bHurZmiKF^ICeKmSH_|}OUV$3-6h>oj1 z*y?+45f)-)j!eqQ#IXT8n_L`&u4oDWs4C`;{iKZ^|3??`3)PE?J6X3sLC-0L?Sy}? z*=dc76i%`5yPrq5R}u3W9=~gktA1Nyxz{V>mi3gJ=q`S!7j-JTPVtu z!miLO=&J8jGxulAEXSMUY5IrA>X!+`&EZ2E4N$L{55sSxHZ;QscW>V1D!-qxl)+MK}tfH#_EM@ z&+l&@FT$GEL8RmUxH?@Zy>vDQs&u$Ii6(?^7i=tgEmz@w$2nQ(64p5~hz6hFgStIH?Z?V5~Y zS+EFy1+czLQ&C2-jT5ig^oG!jLCF%Il9U;x@I3pS{=JF+Sa+WhvQfyp7Fv_|GLHM$ z-=>R~%z;805Z01kBuuX06(TNE=%E{6pknQBKS@^ZiJi2N{Ey7d0u1V}iCM>X#QGiY zxW7GWCsYAa|D246&~jkCCkWXP^P>9Cs+42U zF{|h3GFCv>jbe7(a8&%K5XHPtDE>;JEJBMK)H%M7!)dOg(=^>g}0t4tr)g6;14T%g^waTt2)FPE6wQBW8vaQ#-;OO#~neT{iUAlIBRc-Vv%20%-+_LDR+E4e8 z_cGAXv~F5*UB|4odS5YAhTP5z^V3)i6m$E(u-Z(rWUJ6EL#HL=hW67zJR_W~YyB}B>__p89RAm>%88K6HR zEbA9#2DKY^Ocd>cCk6lJN6N}wq;~Bj#f{^K2-$*bE8)>~Ui(p=wRtx8VR&^N``Od_okaL|SJ>9&*3C9E?vUv)05&1?*+Eup0`ztgg>B^H zmKvI#-lcJ&qS7WvPQs9f|FW7?my|qLH3zjOYCu!QWao|IjN%O`+Td|9^V#0tueIHi z%rOdo@2)`RabD-G9uiw;oe(ET3|70l+K|A26G~dX8?5F%Kh%t|vwgm>4}Jez~5`vGb4cL@DfIm_TD0zkO}3_n+#4G-b}8 zZjTJOI-yzFOv%aR(^eSmR0U_Z6I%&?U%PEu{c$9lv4cP+$admS+4<9oKW=x!3e=!|ZTr{9GZvrpF6Uth;B*7yZV4?0OuW@)e$R7sZW_M#xp9~&P&0mg>q~$@ zh0N!9vqk#m;}x6QP>)ukOQaQ(x^1_PK<-1}*VJVj>#@hg-2niS--mPpw~*zFK;VNw zvno;}kGMdSr-bk(>{4yBgq}NJ9`9P*i+5(2r+()`O~92{^XYb|g~&`PiHi;WdEces zVwwJbam%QC89#yBxkYZUy<_$!yk)Hl6jbntqEaFoW%NW#6$toT|F-e(gxlpW5&}Iv z5%%pGra)X#Kny;bxcd->^|XP@@4wFKfeK2Kz87qvYUgf_Jdn!&lP>k=$I4IqCnYse zfefuP@In%PfoHX4_Xp`ye`xYp4`oD} z3lKzgO1p!1NcYKkDc~1Evl6(x1b{_jY(m1B{4IbO9h0*vQJQ{g-U(^ zlul8NCaV>rEoeqa(X6hm-E{vfXW9^~nLO(K_I$eHl}Dv&9xzq~GZu}1e4X&kNWBWi zK4emjp>s2tJFsM2>)DG&o;>D?*b%xQH?nFzk#@TL0D*Ix59`f#f;9Ty zT}~HHmrby>UJHMYG?hHtVn(_N($`;w)#gUKA-dkmBox`&@dL=;9>%d=cj0}^D;ala zbT{d-HRGW`bW3-b(kcAprf>H#hVn|z2GiKZg8q8adW6D zKf7A^H4=k+2O~{?lVW4hQ7SuFzZ7cGrhp1tpxFt794Xl=#^Kh;q)^9=wbe0I!;9QS zd;8mfIT=1rpY*u8`P{SF>FbwW`;TQg$rfjD|n=vkv;S_?vB%pL0k-*>c&oMLFGJ1n7@ddC`y<N{;#jM|aLDGu6z+12 zlA;i;k;#T!rU0KGCf2A;*D_q8t4M=C2i+~S$NTTOC=H#fNKdvc-$V3ywPU=Kf_{s! zhf&L@)^hTgNt_mnQAVDo_TtJT5y!C{NgWBOc>I?OpXv?^q8vff&N@nQmBD@N0IHR+ zvVJV3-y`S+HAT+F@{~_}*TM{TCPz82vczwJ%XP4&IC-|J5ja{3JGGPBwf9ho8tg}J z>n}X-4q|GR+Yk|Go4?h|WEb&hnFzjX+gr%c6GNu>VRt^f)91&)h$_TF3U9bY49pC2 zx*_FU5?6@5*Pnef$!^szSxMunw8yqUPnKRq>xq2~n%7fK0&cno&E?~zDaR2a1r&nU zpYk=y_^92&p29R>={U7wB6Au1o)YmzI%A++nCeh2!QSp#^#1_gyK%00X&s<9^(F56kw zP{S#Q3=}oDa`Gs9suE(jGpebMkml#S?UD@qlbRO#J5thUo{ToUAt#XhZzxBJPOR~B zcB#l2lw6P>%`B7XOJ`Y5FSD)SoZ=_4$_&WW(-4eCNwf5gX1{S>R

8i#OO5wn!P- z^p7YaCQ2yj>oy|yafqzO>uk@%ugjHFjK?MnEfl0^;6o0nW?I3LfUuLFN*-SuoB#7S zLi~vU+a`dpj`clJlSn~>dq7}uhxBQDz7QAXyu%`mZ62nrK)FEd*3sXiD6~t0q6O=AR zJx9{Ax5W-%r2(%89+Z-?u!f*kb~q*z0mO>gn$bWi6v{u@-@|&JVm+RSDPw!dtN;{k zSdynu$RM&v$x_U>Q?$ftCw)vLN}m&19vHhY?;HFziiY4(y{{1MN`_jo^ooZMT_bre zGMxs{AWI=(OSM&;!Q}NnN_>L~Xtw9ZaTYx+6`Pd^@#VLVsR@~f;~7Nt-2j(t@gpZ- zb#y~4N#Z<3l#0qjB5SZNsH@zw-Q6{QUCOn{`Y$t$ZHT|XVTN=dvM|M(Kv3XM948_5NU~40@+VFjzBR(&adQ5**^&}GAt(O1cNc*G06WCTGA6nhP?SD;&>|a z?!XZaef^&z8Lq5+I%8wiUsz}GHy7UuQxG4ymtI2e3i_Fbv|@15VcemXYFX^ocLp6* z8r*@gYw`OEl`}-iB!5IWCDA3-J*dPUeYZF#F?8q`eMPN!d#T$YAz3+ErZUWK`5U^X z(4k(*;a!^}>Wmu7d(ArlmZJvS%iZY#2mzZ+DZbYYj+H?popIHK^qSs3y}uKqZZ$RJGx6{WTYs%cG!$c19F(3tFTZ+W$IVMtIrE-y#-g$v6GT0A zQr-o2CX3-1Zj$fEh2NbDCe3!O3^Jmxe_0r{4Z4(f(Uk)=C>gCqR1u$y>c;UkJ4Guc zS}puHyj&L1mip5ZEGQe~6VW%4qF*7$nj zGF{`keIty~U-Kk%B+Ym)3z`LY-;;G+^z!Zgq$v3-2C-UH<{PQs2q&!*l!%$%VTgTl zM^PwPR+_r@Z~H?qN<@il9xjrV#d+N>Ar|@vAXr%1n?u$*{XD6*5qydfLF}n3%{2(Q zq1Ig5B@?&Q4xq&&T=J+9$}r*JKy5%3$njYY@mRD(%`_JPQB{dHQ*f;|BwBUSg(gc!6sRB5ta*v{I^dvWuq#Gjyl5B&%UR_ z-yI_@Whln#e}??e1u5c_O%jhNksm6OaC0@wZF$Xh_09T`T^Ya7tyM#GJI$-aHm8-1&qYCxncq=T2Hh0L(0cQ#hj=l#`d0s-!K|Gw^0 z>VHV%P*fyDw>5gxe6j?M3jXJw4=zDA`!OapukR&MQMag{K=lBJ43;#TZ>Jkx*~DvJ zBfZWkU>1xy*T)h$>#f1h^;z?YDcU_>tN6!P%^sW-{(p$rwf9Maf{@H$Z8C zY%UXG+~K@t(9X?7&@oAZR7EIy&RRh$Ot>J_Ljq)IPVOz-&Oxo*JhkC9GxE=UK=11bh z1~~I8gCeb`SbGe~)fbOE7&uTW5zv8&!ahEcR#;m zD<)LG3o^lVIrol0l{+AD!mIc3H%rY)>f5eB{7H`cbYp<8eq)Fe#^T!YWOHkjFQ}y@ zKJNC9>7y@--S;VuuPYOmxcewV@I7ghkWBt&9>sr)=Te zU23M^2_;ZcgVBF}a+9OBOKy?Eve3)}`fYcWXo)`ZOISFP$|%xo-G18OH#vQPXp;#H z9?)Pv4;DmNC2-6Qs&A_=aEc%`-9>DXyWdCKMI^0jZDrEsWlA$Da%l75m#0x)p#bi2|Bms|7 z_s4mYiR7$!m`5+KP7c;wV*`_XoZW8}AukBg1ZSZ>4lzoHDyLSiu5|5AFPz$1>_{(3R58#16O*Uy24=0Bo zB}jP6xKr(;6_e&;WvYfh;UBJ@zD+A%YS9iEg~#T@=Gi1mLwT2m_;#p$g1Jio#{4JR zZu>>W+;QMyES5?OOx<5F*d-f~x347c@9}&w|-s8$5Y) z)m*(;(&3uOPc9z5;N&81RgKg~Mq3`iC?r=TN&^oOLn)#(x?@#l!Q4t;pDQN9tB;f* zseg@Sl$n^5PUGu3)b;uiW2I)^E}71&EeHsMtShPyt=e)8C8S9Z zIKioRoR5%Af45gmj_6DR*kK201q_3ZJnaaZg<)6f&tHVde?Krf(?+f_g=&7|JOJ*m_Xo+Y2>z zEEaI~_{8(Wab>VoYKB)SQma}T^A%%6mPb;0NJahFd|iJXUwXr{3XSgAF=)vni2KEK zCPG$H_lW3FM@E04S2_R##>9cMD@_2!e#ujGJ=Trgm@J0VZgbP!LaQxKzOSX&0xp3D z)YfAmB5abcDe&bPs8(d7;SS=QB3P=L%T*GQhs%ZVnuGZI*1w66h)NJJ5ogtQr9#Xh zR73#>15H+Y|uVTOIC08?q4NikTmY&17#@$hsyFfSzeIkpRzy@Arb@^ zv7N-JMf5KyHZhQCd=uO`=Ql_db%`R#9iYsnnS zA#D=qz`FR{u|Cx#Xa`?BN`yheFuk&G?6t)#*JLk8974)ZeK)1I1B@uAc0;X|h> zPP|Ew0p|rGh@yzOSe4C`*!e&@kK<$sB?W<+29#vQSz4^j8nFyeCCMIF&GY6ZE)XD9s=QSiAWq)u z5&D_G;)J(igSSmclvbk1Raqja9exYxQh5F9ywxrI`^@jW$I0*fK6&2nA*=rF1ra;U zup9$~5K`)|W|0y*7c<-iCw|fpltpONVO2YyIGr2IC?&?A3#o%xF_17o#RWQ3lsi-k zt^=Jc=BbJ}l0(epp=plq0w&U^RWv0r+$X#m0kK1Q#k7tRr;(ss-_f#sUKVc-!{KX( zn<@KDF)z!qe=F&fyLNRHJI+HAh+UQ(b`Vhp`4zG#S>>L^eW4scCh*4_CkyBA30da4 z%b;tK+|Ewz?`J69AKWsY`Sm6 z0c{YZFiUk|9)@UU^OY+VsE?Sc@vj>&V1T*$>Z?;?7X)U*3`DQG%YYpN%>JwQH$!#~ zF@1aY&8+vq2OpRP3l^ANyLOox*B5HXjvdzSzG>4Y8I!u2p+ko{rB#F!EPVQD^U>hJ z_Tsi|+0w4RZrwWb_SQQ`UqMHdd-FwnfP&kL5=ohC~M%oq?mf+)x>SZVlt?3nmKL>J4#F+y0s zB}>@q4U-!3+@Tf`WQ2;hxYJ_m!>0-!*SR8K0LRY`AG=DFPYl5fs}Zm~Hw1{Gz~y^h zNcD7$ERVYjWE&-#;26xag-k?wRw=KzQkLbiK5_a?2dk2yK${~VaW?-(&3ZL;roUk1jqP_`IGb>_2sFXMr(~}@0pY9LtIjHTaRBAY>$jI8C3ab7$mA_z zfO%x=BW7(ICrXV6xBsU8=Agj`nXOy5+HmR?t_h%1r%q<&%9UpO_U%@R=Kqi6#W1kP zd+(W^J$qW`PUK`lrD5+lW}?eOa_N@CULek>kDI0M*G= z-(+DB8X-$i6lgXaBn!lj!SU$+?%-I-vV30neC#+MfQcXjd|W^o9Q)2#^NsG`ut6wT z7$7Fs$%5hpsX#T1jo_h{k=V5o`Rw%GC{Zay{bUl)b90d_rI04B2ZWGofPXO0DE+9+ zQEVT|v&!@92W|2E^^t|^hUt}UfjRT$n31DLT2jaL<=MxF6LpY4j>BX@H7wdih4Tan zr!N~?y0ZMpocxt1Sm&aN!IdYnReSJPRUq1vF%YOPoK|iis#F6P7*h#!U^}Q|Y*UjL z6C6Q069NnHLnF|D{CEERb{c`owlHmyFqv)87G_Qa9CGH-9+9|2urh8V#^8^q6(nef zeE}0DOvqHb;I~xDpndj_U@7NyOrX7MxFcb5;zA_$>uowlC^TM$L z*5&2*J8aU#&O0N+=d0t2j%I$%e6w`WQv2|(-DjC`hO8V?{$)Z>Ljp~7j2H#++{^XFLQnCps?hpP9sED$~Z$6V?sdkk(| z`z-^u*rcpVS*nVMVpyQxb%gOY^}|S%LK-MvyZsUJxw=68zX$j~CS( z2)tk{CTbJ{3F&#|l~-&!5(p#P^@8=-F25f$W{f3l1S{~~S?+1b6YW#h3=12?Q*+Vv z=+VQD<&60dORRSTeXctk8>V8SYS}-C9ovGak@%Q438>iHQ*j8wNA5iK8@|B#y79&v zZD0la#re?+7W|$H7wi)e#MvrsGTC{{<;|DgG$Tijv}ROTG$z!~G?}TJ@f%i+n#aw9Pafc1{q#`k_@8xj-J(%~8J3QS zL7&co3J^)kde1&Fo4#}Hon~pbrQrs+6e860?CB7@*|Uu~_Sk3|VHV8{*sYVw%p3=5 z?<51_#(=Hwg_zy=zNfn|Kqwd}P!`lI1c`$@au1ljWcDhHUzOb*agFm@S#(S&Gv$fbudbAJvQ>NVR#iX|gS7dR{9@4} z&4(&eeo*ZJ@}$)%P~IR?5HG4zKmu0>1dy;Th#=p+ugS<=K)e7%Ppe3ZLM1EcGqBi$Q#?zY8GB%2YJJ?r)f}7068{=R_xGf6&lssn5t03lE==f zS?`@>KHL78S!p;@oCGJW{+*#%xBA&+lz>a z7KV=+hZ&a}2{SF=eZ7!CE)1nq_AaGfs78tvIE!WGNy*9HU0#3h zbu*~Ppo|KqDqvh-kj2lFb8ve9!KOy5w^Xvwyw41a;;D*V7tLMhd6CUHj%$?Rs|(wirQ%a7oHn?Li4%gMq$z?x7y?b-@J} zSdzy0%L_u4tvZDQWo+!&vDS2oFcSo=Kmm?8;s{GTA$_WjW!vX$`;AJd3T3w6cq>X| zt3sjGDo{NeTNR2(>tUUC0N67v?8E!&DJ=mj0cn0a0hz*F0cCUNEyj8us{X59_;w+7^eu6bc{% z-RO5mTLlOo9tdUJ>MesJDhKB1m4o)EKUt|1e1Q|FouKWuwuwwo%eA9a8Ey;&u0Ue& z557_s3fl+ye3jSWu-(r`0d+(}fj)}`1{*0MN2x=x=D8wAXSIT7ll`aPBLxc+TdwPz z9&@acmsMRw`QEy9cZs|bFmC9$lGjmn3zDSO9)dtH1m;++^6-uZmlXi2Q}A09Er5v= zGpfHj1^;Ws2Hh{(TH-602GQepBth1EbF2?BWxM$0@i%5r{)SYtKK^mJfU3xT!Z9!x z^IJe=3ePvb%!0@gMI1Fx*7A59o8YwfO7F)KM zxo4hfw&y8-GQ)bHd^i>P_KKq!ZA)8~HO9wIPuDi^RR;|oyXPsrH>@&^0+2u_$Z0#^} z*vi99Qv_ycd<+kp+ryl7%X?;9`}StuSHD`Z{prL+?U`rH2QtxGe)Q4it-andPY!s} z^xD|VOk6xM@#piyJw7zA)xBmGby{Sf86uDeuODn*-9HJZ6xWEq{?)YHw8<bkll!YxDJa zUk~ih2mSM(HtG732@}jm0Y1YjGUUTO&);LF&JV6UF?P^c^Nl0F5$H+;A%c8B>>yy6 zOEFU_aq}kO#a9ecg-S-H2gXH+mnPyx{Uh-(gb0$RNr>4N|D(RCQU)KE=*_{8CJ2g$-jj98<=3#`L$Nxd>;$Pi1cR;*ZIecWi9?a&TpX12|K z@{Tgc@4Uy!cLa>+G13M;bnGANd$-4aP<=y5DcjH#Dx4bvH`os6_MwL!vS#R%*cEY8 zd-2$tkC}(|eaOxLe(2PaqC zg%?_-1%>6f#mAX14f;~z&T|~UMM*|oLjC#m4~(M%U3XnpKfB2H8He+G?lFdmnar4R z1t7)6zVGjAe$eFyRdb$Eg71CrUNgVP{7f`6t_=8@;k#K@yYzANbtjx)jF7uXj~Aci zE3R;!Ln8ljJdgTYB49}>Ot25BlnTlHS8dM`)`}l)|DoB^VvEfvfO6Xa4?c(kn=vP! zY|Qb;m(}ibZ#-w7n){SlzsZ?YcW&R=95eD5bL7w?BTG^oHz0N-{`FUVNE#rFkV4F` z5NG`HD4AC6s93F7X1tg%39KMVWrGBzan_72&nj^Tp{7{Z#|~dafHG1|;JzC3?ilm+9$&Z5h^9*Ur7T2+;;V|^ zWl82`Orj9GsN5C2(9|j#k(&oRR}ad0P}jvb?K4@;wqT1|NbBN3ECR5{NG=b2O~_@CjoN(BYdXcR^8wE6@7 zkWtsh)^r`7iaptY8Tv1>(BI)lTK$egEvud z)T&(pIqc}jk?JuW6Nu1Qfpal6JFk!hpx}`otz?XG(}ieik}!Z1GuKRJxI62*W%A0A zsJ9Zh^G;)4d#$@6rzVy)l(ZY+iYj(Q4iZs_ zj~AUGIfv@YNd29`fexvFS!T(|qu9|&Gdl;US4oWM3mcf24tVbXTR#-wPQ(hLg(7IuUS&N%&MsjuBeXGS&b8rsyBA$E4EfOUQ7ZfOem{_t*6r`le#ev zKfGH6LxlUvSB$y-`fcXpk6YT3xjZ7=n+(QthddY0q$n4Bbz(KDw8Pd8jp7-h|H2GQ z#TgWClHp=*CRGaV3`faAq;y`O+HupMLPyold5L45o02?!D0WO@ndCKv{!WDk%s)kELn2Mp36cvFa6qEqVbTE*5I|HYfe}Q45-Ezm z82)~KKQV#`_#M7vm`+KXf~k~X0s;u|6C|e_5wS#y5*$I$1n)$elF%2kC6TfCqVXF{ zubK*lbz==i|nH9%~~4L3M3BkXReVkK7W ztFLxq5NMBpyvr^#=KJ4wTp0J>dy`#nmgn+7C^mGiHQQUbJGp*+Wo{r(x)aQ>`h2(9 z-lkDKC-h&KU{UJ`;(;K1DhuT9a#^PKof=rmE(1+H5~aH8CVS5)gxUC?ELt!5Te2i6 z>3Y=Q8UiZF1VSx3t5%h?{X*2ZWy`Y5{W$tVY>47~;DHD1XGEAng!l}{e=0o$IsZeN zAchb+0vd>%1rRq#9l7O5I|g}zY_UzERUuFO4S59Qt-}}Xx#ym%N37T%NEyG;{B#gW zs!xz}4w6Q~V?RzgJPgAd?NGP$hNhxK4 zI5i*-aRg9!JG=n@79ysYiGz`AMmDLNgS01Vb|%MD$+_;vx*6&hEC^@E+jlaxDQOyOutYXsrC=hq$(8>w;xA8s12o*1g*g=M_l}?4<)bF0hTEz?kN69V}HmxYZ`kJ2( z!UsXqv?*RtxdOG&tH?n%Ny|cnEN%Pq+-cUP*uyk~qm=iyYrUquQn8^vMU~noa3qQ<9Vb$pt)|{KCy(7`H7li0p?1yopGF?c{Rb3(yDKkI_MBF#L6H4*fE_# zx=`P+g76tWbDQ1M$>)FilViGt)KP(liR?AkI3zJ!CWL0qc4h=oudg3|c5CXK`}MO$ z^W(p9^uXFUW>@vEqkL0S0_1?2*_dLrJ4g?54GZZe~NxFOU%{SY3fRcwIM&ey1ZkSI|%K)T_ZQz@x zl`C|;M<0FEDu5_#tV5KjuCEm=0Hl((sXD>>UUe-9m~1hjvd?UX-vCraYTBX@;)_Qj zVZOUP3o#6FWfa6MKWYX3qu>QwaHk0-^qU z-Ma^NbfjY;3a(NSmq`GQ#6(*{>Xhgf4b5rv^2_Dzj4NgB=-<~yQ-iRIkr#@G9&-A2 z2wkBCH4tIQvyFe;fd}qRSt9+Fx$U;m){hQ#uURu^oBWSiH%Qg;A2ljIDAAm&~{R3J!#_5SdOKUk&imRoMI>*LFZ zQihKkeshp8YS#jM<1}Rm#1gYHfTZzT_KB1zt6f5j=~vCE_|UbIPLAz$moXSc(ei=M zf4*KClG9E*&8~}zNN`4%E?rED>}Z~p9bs^ONET+bq&~@*Y`6g!%%d_Vnjq$jCr^`YRP|!%*R0#3%0`G$J z&U0=%YG$hsFcxOav&S9h_~sP@#84DI4Ioz!hJ!j!FdI9(ZaQrI)a+cp&eV498s80* zrA}-YS$3ISvERRV0Q~*$`aJIEZk2ppVPnW{?Zc}O8`X)A@Ag>>)ErT zdFrXTa+x=|e6hVb=9rN}!iMHdQj9rP0g?MLvcC(69 ziey<==O{1h7tV3O;gV2XpMP+syd! zEA5NF`7FxH6PQ+mB1s`-UcwkAAY-dT5@b*xG5r$FJ7Bw-zllnv- zq$nc$azwT+o!PwePC%S@VB0>}Aq#8Q%!~JC@#L`E=q@@zh#RDc zQ~yJ{5t2N|4OD-7>&wz0ty6}HyX<`N;&2<+?PW2;($8r|0^QGQ0wrAMI$jyph4;}% z%iG=%S+bgA%M=%o@sAs1KWCVY8`s(we?R!(bwY%mkL(;lur!s37nnN-hLKw| z0GMC#r3(Wnbp&#t?x6?<1#6^8D6zvtK@KPq&T1~DjEPi9SeQ_<=#$jxc!`PvlnF}V zK+7gE5d77YkQ}nyd|ht1{ObN~>@`A>ic4MKR#6k|q)E|Mekuj@&6tG|61?uHUHdyq zQLAOEvJ;9Z;PJ6jwT@I0apy&2H{6t8X4I%P_8dL=WPN|cxcWBkSNv4722n)iXO}0{ ziKaMV9~+c*OgQlCXTk+gzIi?$d#t<|Evz)^Q?27b24(nsuUogmOqp`0*|DSHb4cRi zsZ;M30=C4YNKq~lM|<&a;9(;BIi|l_Bn@8l&^%0Gr1IZAjj2BibD;@|HCi{tZjfo8 zWYUY=g>zIeh7Wg~!Ein<#gEACt>+DP9GBx7giyflz1MjsupwI%Y$X9s!Xu)ahHLIB>sl9>ov6# z&o4UVsrg(dW2JyJHJlU94{5l(&yzimG_Y}Fkvc+tHE!+N>@G)`H6dLPI!Fx>u7I3# z_?$iU)Kiw=;ZMg45(E*1tmOi!L9x?gM)GKfi*Z9)9B zA_Zw*QkY~;&(zN&6GWlJE9?M@fWQgJBxVWBMzN_IRR)w)5(3ldyI`vm|;;5nVb>aQB9HmsIFW; zc$P2d(tPb}j#7u;otyciq-&-AyIS2QgAz-N1({%rSqDW*Q-BaPN^UsF7VoHH0D~%0pxF7NUje!7P};nT9PMaD3UcT9 zD^dUmqqic3m#|TSOw*;XEzRYZR-{NcvC~e5s8l;B=qxtO01B^(!ejE#6em=M_{S0mS=1Oq-vgSl14jfrl%!NS}&%wop)!f?Rc^AVC?+*PfbC z31m=`srwOxY!xX`@Yvr4GQkf@ccK+3Kp_<=Kw6O^;lvIJoI>WA)EZHkVgn*#F-hP9 z1F5XW=&(3}Z(T`Ai*W|S;R)7MjB4kmNSQOx@!i4)CTb8}hw_Ds=P9RD$8|Z-JuUU} zK)Yn2zs!s9kBpPl*+<~F7PaYCkAsL+mkUN#f8ucb9iv+u-gFk+F8dVuJ2teYw zAwZ1i@&;fMt2N@n=aFg`XS#bdWl7(ovCt&r#nXLb-sV=dDPg+x9DV z{10i$SCIm8g|Azo6)7l*h8fvkk%HfA6fI^}%*uSvJKGP!Ip>^Xx0P0;r~+aKv}P>P zv75Di9#3Z}PiPPV%&_Eg(%EZL%o1>)S6=C1O$w#F_-$CH%#W|g!Zm`}6=mYnDirgJ zD0SryPzwj|vL=$KQ_wK3KH<1pnlsN#wh7jMY9TV*-Wl@bD&GB<*O>L&SKA3pqLd||fL3J`$*30?rG zUs{m@r4Cam4~;_{*^lDj%vFet0Nl`0?R4S4c$)Osnd1=dDOV z1q?N(;xkAf49CQ=sh=RGWv_V|b1nXLX+?_qNNh3~1_5Ta_&2=N;x#jljw!5JtXKVW zXJ6#mf<*E#Yk9Lu0mT8ug?`;pm&*y=>uGa%;r@VAahN$UNsoZP9m)d%ClI}YfVVS_ zYs~lxsY1bZ-yqbnTMM(H<92gG3~C+ZfL-I9F*sBUAfW6or_2{z#Vu!N%iY&((_l8mn(ux&DAIIT`Pcj`l z_B38nexL1_vQ?yD`&6VT#HTPNb`{16vOw&fljVrI z?xseGUE8+hZ(K(hclO2X6`vELaw}1&oIc|D=bc2VUNaSAa;Hw8NM}~sF+ctEXJ+%} z=H`w&oIq5f1IaiN7DxqvpAOfE+)o5GoDb28)V{Zi5>S+rticQCkKW(_Qkpv>!)xC8=h1rn!^rK2f6dk%NV2?Q-1iv za7X|urB`Jwl%R2qF%k0&Lg=&9ArB2&&E^qVs2_W_IlKM}YBrT;qxOg+N^YmUnhFD+ zFRg%q9~rJM0BD5gj_ zu|rir6{5<)`~7;Gu5Y-6jC^QeDRwdg!Iw{?)bQ<#3-r4)XF8ptcfdn+!K8+;MJF_O z?weL>E=jNJ`I_0XWwcGw%0T~hO$GDTrtV?nLJ%^furo8a&oGU zj@UX0p~8Q7jx49kQqT2+6X8yL+LmD-&ivLFEr^P$sieyBC8qx(Wf+qPz2rPE1VnQk zc#a@@5V667OFvh%!K4&22k9=QJ~LL!`|me)f_u$1g->oFz4+Skyl~DkDV8{=9CR50 zeT5P_g_>UDfSOQ~4irT4>!t8d9up3&{P1es>iUGN9QYz)F~Q=@j7fxYGEsuv$5|U6 zBRUvzr12S@6^`#jd6Axocb=^k$m-y;p8$dbcOdB$FQeQFgt zEJIpTot$Ll1XO6C3x`=spbJV6YBuBn=lpUi7ZlWugWqt!L5}IWzVn?q=E4gdb&PAyy7V`t`1qLu+ zFQrz$f~fO6^K9c!Sxkx>C3E$zlaggU$0<^z#IA4`&wdt8X(eyc;)i7^ zC}}aW2s^@w$fZg!8pz1BEZj+b#cMsGx{6;r|&D2RBu zfJGUl#Zshb^2Ck@ioAAfy6rOC+O@P#d5d!M)hMy++SSVII4~1s8D>IaIxkGDzxjO|hi+acG6;oKbgBU>+oI}j!1tl%!fHf-N z<(IRs=Z3<+wU6WP5{3Vxst{AA>|_7ORNbLNbwAjU6x1h9pM6|7-wa0b{ET1G-v%@+ z=@?O6%7AiWUd66MhpqLLGDrb)ES?YCh>Ep7>fq17Uv9ZKd=hycAr-gX7Jhl^de=x6 zo*DWdNJBl~ZXy@3EqUnjv0W}O6C_9mj0?LZ8GOA2#082vrdUn8k`MG@OgiynC-{J- zQ0Fu#0kV{>$|Lt5$)T^h$~mXZ$FWv0xy_g9i2jpER_+DV^Dxp4!Exz#nNM;oOA5UG znYa!&j{8q0I#z>PN(i7xks@j|uZEqZJ}I-zw|jnO21>uxx?C2bb`}}^boM$ymN3*h z`d~T@`c~zijHv?#d|-|^qEoCSE*lt3F_mCOVHQs3O($25?;Yn3hx}V*nJmlkEsxLt zBTfdA;Y%-N4_9+D?atrNN$wgJ>1GV%xVaUy>XMzE4-3!#u+rBUa)%2e_oDR~L$zDB z%#2;u)jT|Kfmzf!B70*NAG_JE=-#EvmIebZr#x*9fX|Uu&(AUD;KD>SU_=~ zy6-;6M=YvIEB7?Msi=1K>Mk?0fbFs)zy390g}X_dzQ{qAkM#Nc>oh_25wc|K@7OdG zQI1_V&ozQLA96_6A{IICMXV9ri)<6J#)Ox79ntzh*BR3~eG+X!BEyR2hv%XqV%~)~ zq9PVjPxfG0Aaz_PtVovFu~X~TnVMa@QYMk2X%jmhYG&qi99SC)QATjk$G2*I$Cqu) zWgE5WlnU2x<-@u?>Wz64>oV_vB*5#Y_U1;<8is2lyR7$b0j2@(IjBy9dFDv z&E$RIu;xM=N0xQpk)Rxp4et_s$6gwVINM1g2$NbLge-)Ms1BwpH&J8y>k?Qc_)~%zt zM)W%m$%3CR6Qg=%@=)zK8T!fw4xAli$9aZ$$!G6<@`>qj@WHz;i!6{6`nw~|ky$s# zImLCvKGTDpd~oy=Ia2>*SO~I_Ybg9#4 z<5OEmUR%^nHOJzg*ovK^(~!ZeWmJhmQ=~`|vD>t1lTA9fZChI*Jw5HX4dHqB-JLRi z>(r~x4z8!1veLZs&T_N!|7Y(_;G?R~_3>wBGTHZiB?}}hVGlcsAc%-4#jU8dw!Lbv zSKGg>*W2aV>#xhTi`UkAYpu4mTCPh`iXx(-h$4$DfshcEKoYX=3)$Dn|9!saOfm`C z6ClWW=W{rdVdl)7@AAISyW4D{!A*z1zXpC98L5FawrA2on;H}%As?SUnM`4FGQu4Er1rkBERfvSUgF;WLXV{2 z*pDQewO1>p{M6N}H6F=vCBI=wOGaZbzOkglLxDc>8uFkE)&7&Qm;{-WOZ+~2Gl{;4 zao()bKw!sAW@WY0Gk;u-1PX(`tiXwu`7x~w+tTbjVKWCc>45d#K*_0 z)+TLjZNdi^W6PE;swuE{AJV`=^BSeTXt%q4G&djJu!AKs9O0Mv zm2&w0ep+0Ph`5(&Nuu>=+qMJ+9!DC(*Bdd3fuN3VzHY!yP)%V{62gXA>d293WwMua z>aWBJW8k=NCQT|AP

~_;cQ9jiU@8&!ODV>fTQ3+!4dA`}K@BlnhP@VgUh@b8mv>^-K( zUcZ1AJG)6or|QgP_!8g6nsChkMTz-Q&C(OnX&E5jr3;CcIXx-#iX&Q%3GBGX7A?{f zDCC6+Iad0Y6!@YQXVkL6`$(Ly=PMCV4+fAr#@tc(I)XcUh9-$E!8|WqSwjUH86TgX zZdSkZJvpfv<$tihoEZuc;;)p|o3^Hge z0y_#-ZbEJpu63SUT5{p$RxY5YyQSsiNt@31n^hBT21>vFI^{u@0%;nEs)L6^cL^^q zFJ-w4EDS?spc#RsHe^+QhQ>y%Yyw@s_V@F>h5f{%c+-?4lr}*N;I`Mv8;EGZN=?;3 z11&!kn6%J+8NmXzWrCs2n+q|1e0VqTHkkXRn`ceVb;z++<3sn&$eg?tkDoh^M}6)) zzit-Mmtz+S2cMetux3%MnI6+`QDUw?3V-{wGuZt?m5sUF$etZbYr4YC+?)dJyw8-v zGSPw>bB*c_F>1Yc`>0{ZI6soU#Rp~4Vay%pmB7jl?nt!o@LmY6M!}dhAknh(_rI4Q zH#bCC33+gB(Va;BaWs@Vx?B0mpb30gO%$uug+eKTWeTkR5=tT+m#7;tf}O#RDj0V39#HLFMkP7`Ypun+Zq zZ}C}<^?8p0rNOB1$-+H0rg6|3&otN6O9Q8x?->L~mPK)?FcV`^>; zP}en&#BvneYIyB^LvwQx*r-`#@{vD!UpPPwyP?EPaSplPi2Z4~W7&slaQ^&!{QT!y+#MA=t~(YN|HUu5k}P^A&5Aj)O$c4Dbj`NPhM@(INflvXVQ6*N>IW^`2|W3eMnpfi z6>*j{{4L`@@TTn_I9Y#Ey{}JTr)yV&H3ns^qlbtn5#w;DVYH41wI_E{3G08lo*K1QDph7`VN9CD-o0edMx|7`q z$}q;xG*gk{f%O`&V`l8Ak=+&BqtuDLj_XjZTikaH53pDMU^oQnLc3u5vMkY>&2`3u z0EoYVu5MZp|MNe!wpGKaS*YiQ zv(u-SsOL;J>MxF%wN5#PbeSt278t0NJ2Bj`27TFpT~A_+8;`SR&*F<_!Sw%W*6*~$CL7~2iGfUPYKRIEL|!xzD~_k zY;dm`%Txep!J&(hibZb$_6F&~NUyuiq@M=ga1EizSVvT7*6&fZ*x8dOe0-FPmt?69 zPzH^`y7GIom=Q?XOYjh=&YgQ{$COq(f=hdmaau*WCU@-6tax0Tv;YmIbrr341P%7= z)lr!ad9aj<9XR$zOb+31=RR7n;L6-11a`FeQ%U@ypZe;|VvdjpJ5Z#_qcfYv=s;o6 z7$Q+NhF4vYfNV607P~E5CZeLk9TO*3;F@dBDz~c1WKynKM`~_vRzS}pT+>@N6oXei z3eLj2w7UzVP6WcK$xbMq5DJt3L`0VbAW>9mwx-(rP!eVp_Z^^9UG1uVPg9eVau3j| zXwb;$!w)AQr?DC42mTCOR4%MGD;DK0!o-S+h!u(GpP7lGS+h_mQak8_KVw{O2lm`D z9j<{3dd$B?Whs`Gxr?xG#Hv+i4A4CQZQPis*7Wk_*|_o8-&99~BR4I;pVoWc;Ew+ncpMU&}WAJ9ci5S4Fz{{llf&T4#Ll!Gtk8-yGl5r~O4C z3_yLoRuO>V8}ad#Dy(5Jacl2PdIu#QCAi`A4T!24dGKCZa2obS?Zf!W@mQXHzK^x4 z$_@Yg=OnbZn{oa1X9TC5yJGi9L4h~kdoNzOW!JAyQ|I0&JvbeEqV`~N*<`HU5vsCc zL$ANS?-m>e8FZ0pRFWr>h7rL`VA%6>4A^J?QugeeG9{qTW(3_n>zMt@bh)%NG^8Jd zDJK`6RZU1bD9rR=zOqZ$DL2}L^$~Z$Gs3eEkg2uHVPSX13x4fuQ81gMdi~8Kx!)`8 zv#d1FOvj8Te+1wA-a<{%PJ9;g8Jc36Fx7Gm@O&o1&Yi>Yj%*|esB3lsW?gqL+|6C{ zWRIz%=Yxj6M;>v5r{^?doChGfFqL}_4&OY zf2>Y{Q#DXRH>`)VmN7ATvcD?QJ(BzN^gMiCLe^LsXS!$Y?F6-wEKb6bg5|YfJpAy$ zAuF`h{Y4%n@g?|*JVyGPv!gU1{T2VOAi@9qqzslAVR$mLQ*XIt!WF%bZoWBQFAZct zNy*GU+nBMQ9ZS>X1mz%l{daLB`3OP{P-oB>qA^v;i>{V$ep6$9OjyXs7{&#bEP?xj zkHYO&zlZJdAERvTB2>irpw+{87IS{Mu37Sg zrB4hp$N+W&5Uo0PCPQsD3ZWj=Bf4lnXUMB>UmKSMyt>_Ns` zu%GIcSo`ckl06un@m9H8r(@bbpHSJ!S>EM16Q{)$&EKKL-Z@8i)y7~I4T3tZ2Lsd% zojBeER;&|tra?rlv@c?}BGO3b z$RLBR2w+E|JA-95=yrSYMNJrul&oeKjGw#)9aC?Icfy=r-^0SZ%z$N97(tx@94-uT zTnX+7^7eh`jqzK)hc)s@o};xe^VWr*f2`(K;X3%kA2bb`0EM45=4R+LUvG!J@1&p7 zSgehWm{z4_nU0FZj;Mta(c2qkRxLTow>?0CawCe7EAm1O`9+;pN$~Z7dM2;T-SO6IOIYYB6xpXS9tqDi%9?AvZzn_EIM*Qwd*= z&MYG#hICM2WOoN1{SjTKY&y>|JnMV5&-bz12Hmx+skTub&&uO_@_0fXo1s-3w%5mV zB*p;JdPri-zpK{5&9K0N-gL(@>p0&l-xxH53}82?A7<9#u?PUz=h6!cs2e%4oQ2&TTjrz1 zTm@z7u2Xq*t1UPja_AFl2X<{G0i8hpN+zyfyJd`4J6iAhBleFa9Sb_vHN5g5h$CqG zwLA#oej<+-GWBJF~A-V@S_AKoB z(o1w(&4$OR!)S}2)EQGpP(YAxfVzF9$9`_quX!U zAcID_elG`2m+@}gXe`%Y&*B;lvUe_^R>lIOciw3%h)Y3?sa&&0vzY$bpWG-5Gh zy837ZVhmT`jM=QZ9N6cDppGDpr8bt!<6e3ETpmx!<8gW1Ef2E z1T#ZsDr;PzJVs&_qNm*6I*k0(;CBPl+O=8&1#1$r2X)WvVS?<;qD;7jxfz3H(3qji zW@|GAovu>G2eFDi-3hel*oN78ho7hRO6F!{S&j{2GDU2H6+47k+WZ3W{qJA7nUoi7 z!<)O>apjsdDHU@{17ah#*y%BL=e)HnLC@xqwG#WX(ux%tz@~wdy-E8b2ItSupN~cI zpe1gku0C3w$Uo_hH+o(Vj%aCSyd9~xxgxMjDoh&CQfiPv2C(ac=qj~0G^e$VJsIhy zd+I4I4uZWIO9xTYlgSu-zhi=iL6@HF1&mLH&%tAlY1R#cMxX4>T8=eNROi5at>zrv zNh3<2=uw>5xzBo{O-FF{@>`PHo6Rg@wG5&$>R33I>(glSH-9I#)GgiXx&O3L`1^@SdT09pw zEVWmI#tw1ru3M9XfPg?_FfI_$T0{%=i8>9~IcJ=Oi_MB=bE`_CprvS7q{rA51!#R- zn+r8yXSiW|p97W}VchVB4Z!1%_wAA$4Wc4T?*@wzU+JZ1hNEy248QVAFbGw0A9%cH1>J!Z8ueu6z}sP>BYBr?s22);H|{Y|&-%9X|fFzCu8 zW+U(15sVfmcR0(2nU^;XpGW5+OCG}pc6uGV68G9}z|OGP^-j#drv=E~$Crxcg&=+h ztxa}-V$jfO!2;luPc&JxWy^(|D}xNWB7t3Rnr}}H)DHI8qmnhy2=EwNVh4J(;;{g- zK?Yr^G-(p!OwBm!Gyw^1sTg12D!@+m3`&N7Q9Z^k&s%fJR-QJB6Zc01TCC&vt6u@z zw`(r)(H<*$CTL|M(L!h>A`Or1aRKy==(@-dj-AF*4V{zhhSpipcE3v^4l}y&RV=@ zR)xHeR#T%!=g8-Zu589CG7+V}I)nxpbV(JFE9FM1snY#4Z(f0+wHkEA6GIci!)tH? zT6&~!R;4-*J2SStT%Fnj{Zs;mN4qy#8>~vfGL)$Pi6u3dC`2#Awr!(#-PTrf=k@p< z#ypIi=*qU&A%+3z>7`EUd@SnhT&W?tkPu^>2Sd!gO5sx0NUaeVJ8MDn2TLu|q06T*WGH6UtU|^%NK%}*4?%44; zTFC9N;_P}9CvMEw0_)f{#-PE`ZMSI_A-edOOfgh2N1#C~(vN?vl~5tkQfJJ;=n^fg zx8JTkXJSfov&9$`gT@p&8AD)@L02Z3OdU$_7wyz6J5gC$apGZR#_Bcf$^tY~Q+=*c z>SQ>8fiBr+pVi8r*!%kS2j*D6lUAcchdQgxC}LTtsHh68UvFftTnJ+QQzlaogdaT` zs*)-UGROdS1{q|~B`3N7-`c4aD)z~)gjKLzmUS2wJ3Yp(!c9XLwAgujt0Y;2hCuAB z!z3>T=hNzT%{8X;-b2gWzI{MinwIhE2<8X|SwNT9NliTqKR*N5T`0QyZms;u(W4=% zvh5+uGX(_&Va^=QGRH#0JRHFsfitbH?Egy((uM3^VNiGCc(c%bO-%`We2m3z zkOAxrGRUAYKm-OcF+gTk9Q;v%safs<>>7|Qj|6!Pn)DdEBJX-wY*w__cq7hmPYsLM z?u~u1Uw8r7vnNW9?*Gp?JX+>>Fn-QRjJR|}t6g$(fdFa)*coI1JA(`|XpGRTSwKma zmcBZ^L{J49RA%gufSn#=R}la>*9L%&StBEM_(aggIIh%G?QgnS>0VvDSSv1VfH{K< zGROdS1{q{fuS9q3W=zByY(>PWI`tYYc1z?jC}Q6#Ic!!7TCMG{kW#K?Ysn#O@IJ)>su)=T^~xfQlj3veVtL&E8soS4)x6 zN^CTUZt^8da#ReR0qP7g$RGpQ8Dx+_gCc^4*aWNULP45%3doZO6DS5ndg4UAkLCcd zl?5nJXMhod3^K?d1K1g4kU^J#n0c9@$J9+b?W#cCpj(US@pY8}dTEY|0K*MykU<6+ zbcyJK#MotIWT3XTRy_|33qyE#xW@P^D{r!b93S4<%NWV1Y-sbGH4Wt0r{JuW%h*^Yx*8L zP=sO-*?xKryK`CFX{rA=X^Y3ljwqeDJ6$;>e^wCGS@x~h!SUhmx0HUIz)brD) zPa{4){xTTm69^4Wk#n;g6qJE-7?oQM8aqVSX_a4``~w8#HLF6!CDuW9pwL~hW!}x5 zz%DB4Lj1}agoGTGbK$T)9UfjJ|5Bl(qRDEqhQq=jpjKql$<3QFWzr;g8G)e&8Nkj? z#pf|fyOqvP1Yr{2o;-=>iKj%}5W@$Td}s030(1YbYk@E!)52k7I*b5M`JjQ}Ns zE-nR64nW1;00iiTin9_1*?~e&o~ZJ%!Bp#wj#ld^7biX;$ER|nA~Mo8?5l*S&S}Z| zU!FR^i__212G|=FzIXX4u{-|q7c|$_V$U0IVAVqp8PjNx0qit-q8p=}Hmw!Ac5OsM zgC~|Qy;gxVTI_h#)zzV}un_Jn%p}y_-rnhErFD;%I4373czAepUY)Pga>xG*6^T(? zT&!Me)LlShht9}xxD(8EQnTD^og0uZ!Xv&6aA!NI5vaBA;FEW%!uK=E0^ATwhfYS?v@1*&Y$ksChPUSYo+yLe^&HFx0wKC<_d zhIPft$`kPSXT$b*b`x9^bVKrCFK+ zlbf2Vj&Up?Xplh$uygOmDA{U-M|-;q>g)Flu$wNMy&NGSi$!ams;qBxyY{5Ewl?La zrS*=0&+&D$*{lGc{dJxl9X>z3<^7~Qj3Iyhi@N7Z>*?;jIHXHdC45rQ%`&GLK2e(hx_mkR z-m3j~g1mEK3TE155H{(u>v0j@x6838x{vRug686`kb6zng=L-oTAjo5-pEr234Vx- zY|)urqAsMqywuQ8gHxxrVA`~4vhIe`k(=ZAoQ!d)KBsnNB~}M`k-D%L3E1O&X3J5a z1Bc?ywWBTLgS`klJJ1W!&H|cnT;?!S0Q_;Yl9yrf0H^rjk>gNCN0`GyS?dHLMfQ;? z9vIl^F?Q#uR~!u)2C%Eqtv!1jKmx~qp&VD~V?UZ?*2gut7t2$DK2Lf2xmhW1T7&mA z`$*|)!*1D&(&y^S;l?EtOO{316JGfuM^@WmnT>-oJ6##mEy8cgFj+YNY>@(!kXv;Q zlZ010>8hry@G3ufzl+Q*KWFMkiS0hlL!+U(+CO{wWwZ*I3yzCJ_%+uc>%I3-Q(B7j zojWme)hYuW7<8oryY7tg(4j-hnpRhrApkB^fLs|mI&$U6g4x^*C#TcU5?Wuz{P}k& z5JxcQ=H{kctt7_K?b@|V0lRtg<|%h=Y-}tF3JO$aEVF1AE?jsKhpI-jNeZ4fbs_Bt z=9nllM`!eA&4~A+uBc9yPd+4Yp*R2y!v7er0YKqn^Zg6Yd;+1tJQRkXD=$-3Eq)vLd! zth@sg0o4XwfJ2)wHFga;0#A2@J{n3_;~1$6jetHt+!=jda&(YT)64r@6j-dOX%-+F zslT6d+Ug+e9IQv7`f;`Vjs=@KX+;rcPRhgCI4l&eFs2OISHZ0^Of zXVpJDO$QNCm#t-QGvSejl4P}K$Y>lCxOP&wd#S?;_mf#mlvgJq71L!z9Of#_y__Pn$zG}Yz6BHGNg-~1*{ z=jCC^J@;Vi(@*2bwrxmQvhJ9AWvHl_px$roQPI@VHS0)B!07GT7m~)0-wt2jv-PaTk2T z{d{<$kE4@Q6Ixq6MTsev_1fIao^X+M>n-mMm9h3y=av>P^?jXtquBzFQV;l^^@VR; zJFIR3m_my?kB;DS*xVXn@$3N-bE47s=ZA6f=1kZuwh@hEQIQuC-hEdI^jGc$N+ylR z^6ce(TlpFrF@3`Z)Onfl=>N!f|M~}5JFP_&iuzvP&#?&#I)T8zQ?f?S>HFMO_EN0O z!89~A`S$ub?w?W-_@y2m@RmK|DTlv&w{?*F#I@QLJ~=+9imO74SIfxMSaT~pJj!Kl zl&baN?p~qp#c_Yx=e)5d80OqaoxgzhnK-p=H>$F;;WvIf;+8KT^lu$* zcz{6$jaiG`R9VzR)va@KTm;bhTofm6`p0Z)i9^Pl$+gtJGn{VsD!K(hQsyc?HOK(BwbA<;f`hL@Ufw|8TdG^t z(sV0anT%tDK88S7kUy_EK{u~yXNx6D0Du{K&QRCo1hbXyDn)Qv@cEu~Z~43>LAy-2 z2rNHs&Nb^6R}wR?Hv!Q(a9i*hW)2j*$T2ZX_FvS)@`KD{ zb{|3C75B71k75!RKhy*Hq2uR-r z#BV0R0}{>v|F|94yT~4O0u?;cAM0>*ZP=NdpZ<4!XRHk0F&B0>lZT*+V5AG9%s`Nw zoz%A-ggw@)UjEm0QVhxwkH9fruHfX!-KeQ4g};9|#*MoUSy}I+va%3Ij%>r6Icv{j z?!faps^Ta;2)m1DV6ulBI5PV@brcmHlWCfwi<(~Itz>u`WS^Mi?H#i0o#pIQW4~%} z7ak1NFHetNOkb$o?)D2uZf>dEvmlr2MWgtQd~jjZ`JrP&AO*-+t@zkhl^OerfYnyj zMYfL}t-E2Xy=^K%D8SkusN-JRt3PL1iRtIT=99X9bW#`mJxAYCTZ|*{jWj||c3ZJ1L7on1A@*@oQZZbJ0p3 zoDdG&E@wB zzAhhSffkfEwaOSq!0hHNW2~8cnEOeVRHgrhKAS)3u`+#wlw~5&Y@!9{iI`1QF1T1^ zE8v&tQHs-Ofj_=2?-`qJBqmH8Jh(+YCy1-4XvdNz_h8$$?x*cS001BWNkl!=1ai)Ka_1XGC|}s zGgeu+2#~zxUb4NjWwiQeCE=JjF<>jPFO;~hu8x7P@AMuvZ@Uy?W7HIqrPqI=C+z1M(S?xDZG0w&@ii=c1 z{S@9NyDwfqhxc{zKiq7BjPOH@t;{lVvXAuV_rPXr==MLK%HjO3fUvFFYj58pLPZqZ z2PzAYou7^u3Qi)sBpVGa`sWJz|F*Y+-@+pEBHUhZJ0h}zaN}Ryuqk2_(gVK`;Gjv2 z0IvW<2SsDDe-t7EBM}oA12=bX#b@oVjdlltceBr@&ZqcQ@Y4(irbsW5W)_K4 zAmGT|$43dDz6x#x>sVnNwA|e2ZVJ-3Mn7X#7F>FW_KgFsSIjK?kUBF&gqzlLmCI{J%i2RVu!9E8Q9}%?d zL)$_T`r(?_mDfC4SO_iNapdqUmDgnx&)Bb7%Xu&ig1IO(R8<6YSCEn&?4@&M zs2A^+c@O|&t#OzrV$L<)%~3hl2n6KF9YqU#&3#?|@7rT8L7l*Q-LVhgF*OezWkGoN zqa&#G+=Uw-c?7<_r8(E=o93L(XliuEBj=K=5`Fz{O@U~Yw9l9 zgS~^{Zg$7i@lyx&t$XVCbXo6m?!^t2Hz0LlD$?51aI*F!DrzfmIO}ld`|V&aIxrf3 z9)1Y-5648m2$dD;Ey65mKw#ICD0OCId)eJs5%jv;`DRp#$o0qAY5-bXd`7up8Ams7 z-nwr8N9*0$vnis9A4F4AF?CJ*QGOUZ-&?1xoaJ6M=~nh7BGl!w$R3RhzUz#W@K%eZSeUhFxA% zKNdSJOSt7+I1=8RfdtW0Zqom!%|)~DTkV$pfh;=IacB|Rp|cG;MeBzDg&MdsqkEwg zvnV-9j^LfwzeA4OwCmY~4fzSeSuJ|z4Kj?ivgHex$X5tRK3-W4_y@u#MKI$|4Zz#2 zg0!?AlJBjUCZO2;{G8YhCRKQg*4O+~L6Wj>56Urj_L$mBIR_fBYMKVTzYra}xHVU4 zhj0J-*I4=Bg9z{0m)mZ1>;>=SC+8z1$pPWlp{ue<|0O$R>Vq_t*%#U4PJDFf<*py{ zye7K_-frIL$k&vsgbeM_$Rx_ub;ysr4ut_7*mckZFWDk-0ge_}dXSz}awsZs4#v-& zi_C)uRjs=f-~P6N77V(wfZea()_k!pRu=^_2xJ0$0uU4x1b?qUWcdCboIpaanmtAbUYHm6V!MNIgojY{`E3W!L`9yacBkjX!C1B zZBQ+Wyo!)%%|x;-8AY{4=Yc)WMN~i(JUToOHz`g5z99?vTn{7%t;Jjcc20OnUimS` zN_!yIIAmE9p6AV5D`2EMWa^N07`f^e%naBDSLZxcp5aA2fGosy$|bv-Z%VV~IxYIF z5n&#_nz^&D3MD;-3_(bs$fu4{1kVgsXv-9;fw|$Fuv6dQuegmIFvfc4nXMKzTUV)16Mn) zQa;ft!3jQs3C$whodjh&4bq|$pC8}7;*c_*>0oZSdWTs9gy#7=lHMb8_=S9Jx^_&G zt+TAn8>&H$?s~Mz(B{Z@9+o2|N}D6%yi<-AT^(yW3*VUNC*V6EZipQFI)Ss?D-ULgD1?~-29qe^w|1%sSlY2H` zGHNHf!^?A-zE)qu;`I-r;KT`J9X_nwxaWaegADpI13Ll;0t>nv3o8rN(JOy;_ePL^ zBpRcp;`>i6!GHeBiU8SY9nU4g^c~^O7G`I%iqcrnj^5I_&RQjEU$C_!oqnbc8}50r z7FQkiL9@9T&&NHFEI|d+!=@uXJRTFnCMt`W)iMe-;udM>qj+O6LT#a{F0xaV6a1?D zz{eDM+$4{7zjjmyRwLglALW*Eq_?NzMC%EhDb{M)C6jPBR zO7;f9F=L~b;;~~Nqq@3S#m`Nfw!G6Ya3CTA?<=g8f^UZX6D|V73Fcn3{VPJvKDd?l zB-0upSjF5UgvHoeSurl^DTa8ampo zaQb{9(vupI_<-B6EIQtH#e&{JMsdu3i)HP| zWJFsvtg;U0<-{r|++YXa!Z)6g(_^vA;8G~yJ6vugYoom3TjvK}UTg(PerRk;I!ynv z7ws)h_+(QVR($(GRLHtYTC)Zoett;Zz8$+?eH9H=Rihto*1cpW3Ox!DmJ~MfYwDYZ z1&H5G`ES(78lE|Q73R)chgw{_4{jKbx%>V=<4}}8Uyx_ z<^A9K_~WYXok0d&5y0;8TOZf>b5orHc%?O^>ReG*fwFUDov+n2)W{*MFJF$-kMPuY ze}eBn=Ye3^SRI?z!gL2Wl)cyL+)FqtyL8b>A+j8AHq|2C)eG+Dn(@$+rI=71g$8Q_ zj;=k5>!)0g385242gvnmHUkRg6(BT8k8{b>wcC9CxiBM3l?4G+0i7KJmdm5VxkFj@ zQkqjx=vIhNUi<{tEx8W9KE7QV@IbXvbeY+PZ;4Xsg`8|&i0wPdk(BpG+!XsTT(iCUOu4M^t3bfKfUc$w zff%#R1UfByX=&IxbEd+lf0Q5ou+wdtb@kfqq9aWa)G>Kj*N>eOfQpx8MYUESG`kUb z33&oydNH{;#Mni;^`$s`q>yv_-eoX1xubDXMtAet5G*_>?NLaW=mH4R8OL#1}^;UTJ`YLzi;ceT{R9C00cY`bbed&pSjNK$$ zJv}k~-s@nJb>{6LT3Te1N@O`k$>anJKP`RU1;LNpAxxE-^II|bnxEjru5i_UjPZ6! zH{E2Q27|5)VAsi?T|L#oc(3#LvoN=+`YIGx7b~E*{m6Ed?|TOS`@r|`gJ;~~xg!9! zs9RuO_&V(n&~l+Jg*scITkA-7;H}~ud>kAH_c|NC_seq_C!nswqXX{$?v91w3ori9 zi_#BA+*_=h337EIC}7W2WrBBsHWh>O!=l{Ts&5a2EFZn`RbA(A=!9!%P` z2(c;kaABdf!;a=-oR@NWHu)?9k}j_xX%=jKOwPa6twVpue0;Ml4JId1j+P6+%lN5a zsG;v88YXHzLJ?4}B~TP36!Z@48Z?B=+E{jQ_AbjYzYg+B!jPe|%62o>7?91Fhy}lzL{tb6x;^ z76gy1?dupL8eYExt#A;ai5XUQq(@5&9+5NM zu}r**7`Fn~RvZvsk|*OUN@T;`7gvXE(0+`$xdI)xzolxwrEl}a;`R6TX^D$UOvKHP zJ*F~68E;0QLvZIG9zG`LjxJcbP)U=nx@y2e!|vuf0m3w;bAvuR??mE3S zZu+^MIB;Y$yxW6uYv|*!n!j8{M`;Z}D?BK1YtgPGL1P6}v z`o4V%;3O3F||#g_(GYVkLLm_E+U_MO=EQp55EI< z=VoMP`C`U(-yQIN0-~a@_J=>jd(S+htab!<*FErnD&jlVXy;2Wp}wk0^?POMlEKg{ z;g{RK3(S93?)7s&*ru+7c_zW(Te$kJ$B-xBp2-x&XU-sW!UPy(kOA!ap|AJ^<6R$3 z9zXP4G_2dNS7jS2>wk~D@^4{A^sI{z)4@VXFDqfR`=mSe1kFKWK`8EfsRQnHW>ps~ zb7m%Hc+a>ztvVilDuLqZtxqd(_uF@Wi|^m`eGT08Q3jp`l^p|_gOpHu2+QzG1P7<1 zea%i-(0f0{l<*WJolO#<1yzMZ#RQVSjvy3~1&7PZa#V+jq@*<}3i5TCw;z|jh|u6v z}{X=pYUni{NHPKhs z|4P{6^UHuI56QYeBlG={>PT_uop-Qn!v@v0;&O>IG{^vU!y;;LWPl@C=nP5SAd25h zqSoI3@*0?DA4XtsDLzhp5!;W(VVwwKmJYdqIv$}6%4^KnSi2C*>K5S+)lI_0<_xOd zRojuaVO^P89vN18O^sq!ZazLrJEk_tGc(UF29uzr?y;L6Q=pE?EIjk?p=8T`MbOe; z@lD(>@WDyNA|{~s>TJ+F>Eq>Kr4&{WVD>4KYKIY;Q(s?&)Ksm`7C~9P#frbx0FM{G z0<~sA1>U5YS62v0dt4^S=4W zn`lPVMKDUy(pDuPjKS^&Cr_&94K;n&t>GRd?@|EH-(Q`5f`Zi94hWd6^U+6t$MWT0 zLup19j_=&5fMVK?9qO=K@d$1P0wXkBGm4jmo&I0gD*U?oFwAUCk}j__+THo2(AvZM@ zMKYEp+1bj1suW7OCix+)V!?57h?y}%f#<Uw=9VuXa)}at!@6)$j6_4a z{}};yf4Cd?ub%^^opOJVA}1vpWjQ%G^4@z!B85R;ZeX`jv==f###?YB`pG#cI(o=p zkJ{{WvjyNaS@HFE*1>fD3%GTj89&>Ur7TT}QHfYP=R&l1sY7$?R=CKn-gq(`H|5=d zgPWBT^m$>14BzhqB+-h)h9a!T!32+A=*VM; zad!s}Y*q;qloRQ2wBO1DqDn{J>szqGm4(M@Mqse!k$~IMtmZE9)@!?c0bI zE54jXM$c~C`2E1yL?CAV&@M**ssOu?5+-5ibq971rB3{8J=c%c zy|ts+1*T7z!uf#`)wRdVKYDw(}m-pH5QoSiXY^5jk{9FKv(0*hSl-uG0FC|DCy&j~yHFPkvexezsNK8OsuXIPixxY1JQ8xo@Wsvg+9S-G;Wy1Vb5 z-*j>33r{Q*MVIH6hVH59oR?MsckaUX2*?SZc`zH5a~L*pq5^r8Ep{tuU0oBZs_Ic# zSczlDig9||cExWwx5F*X!pMS^!k=Cc_VD2I;jn(VQhAWf=2tQQ_Vw8I+h>sS;fE@A zufKUV$e_y_*pZ>x-5op}Uk{9oL|{w|0_02$Y#@{WH|2On)Y>;2XXB28b77r(5RY9O zfhV?AAw4f$9U&ee@Cx%nY)I^g%Z#x0US9c6F)_Rur%SC^l=>sgWEFb3tciulhfsBe zu@~8g4|fA|_HnQXV~U+N4Q*|0$^}I2l<#S4YEox{LAz_HH{n#Mmmi=8%JCd0*CK-( zj=O^3jxlz2w=B<{F?WL?Mwet@l^p4+w`@CJfd${~8~rlA`Ui5n2+ce9TOGv@5@WE# zL}uneSgkHtGXHL5ZWQ6@^$tuo?ADR=eL(Es>R-&Ce;e{-4H4Y&U{-BuX^nhGHI5u9 z5&@AstaVgWRD}Ba<8X3vf{%}z{8kHmeLdBge~+d`)*c&1a8YvfC~8E=Q7HBVcQly3 z;WZr#@|GbXYKMTdH)N~?+?fY&GWDFs1d3gV%VJr#Ou^|6{#J(=yD0ab`Z7|6m zE)8-=$ixw-T_U`5PGzI`Y%a3W(s1mZ57Aarj`qg-^8ts88=O3SVe#^Zb3izpz5QW! z_l8XrqY{}TGZSm%ddOmbzrK6?wYRq-H#beW?(OwhTwR;6ef#UO77tv$&D3?xR&6N#m zI&dK215BEHM7`W$`8r10+l*E#zlaDG({@sTF4v~l)Ty1;GlK5Cqa%eu zUtVDM^{1a!aVunOmBKt3D?y<{jjSiw>(rh_08Wx~PmPE6D8>o>ayj7j@p|0QkS;>2 z0KZy!AGYS^;xT?gl}t6h3@?AG<(-Sv@^STZ`zNf9t6+RwUBV7Q(E6;r- zU$7T#4H`gk7V%Ipf_;#atO*?o8J6>HlkLSb|E6H^_I8Jhixr-pt^yLA)Y;9=1=ZCR zXl~9xXs9oOgT2-36oLeA1o^Z^F(!=bp{}}G?KO^vo!%5>V(!g1tNxdcKz|vuyqMod z+=gc>(_ryuuRj_1z=sEIg7!?BsJfppGxoiP-0rSIhZ=U>fn8UL6YN+H^QwX8xV*d$ zWo73S$SyA|MnzsO8Y{|x+A_FwG{{JHg+bU;8aTd9a78V=)R_Xlso7O~1Ap4tO8KX< z=dK^(te#&iH$B~k9Xnn{e0&6E%$SLBF_W-Hggy8Fnf?25YTrJ^7kDrx%Uk$dLN6J$ zN{N-@4}oj(R9zZeqVh!Xk#$|ScJzuav)1MF8mK!R_63~pKMr=5F6fws2-DPIUzT9I zxw%!H+dM-tam{+1eD6)IyoemB|Ja1~`g7>$=;-{6N%*zZFBq;tk#H8C?IfVPK>+tD zCU5V~()LhJK8_u$RqLUmqE>VVGg@0G$?P_%-|Z!{&9k|!5x(G|w*qmJb>F6~j1p5A z@{5|0xiuMEw?gf?xVT_UnG%J`lOqrjF}TndF54Fbm%rL-#XYay4Se@m8OLpyf6I@M zEdZ3Ue6-XWWYCuq*r}eRdYp$OBN1_Xt=JLd4%erke$_`e^ zczT5=CLFy1@iR8T#cLyOjd%h}6PMy2pZo*WwrT~;Qgc()u}vPVp+|RZVq~JKr5Ebk zcfFyMvnhCK=Sz6xMvWQRK3$98qai32Fy|{um^ri`A$0aEGK6^(%sE=-2vP~+Xiej} zzOhvas-tOn${JKtldhPI-J(N5&$FMOhZ4w2DEDH~Y!3=jppb%|T5NGKUDZ`d(+oUO0PO#KlL<3I2#m;|wzlr3+7Y%zLA zqI3%aL$GL^ydQmL8rwTeOsl;XleR2_)AYT-y3M+z;0L>UZW0tTmhlUj9|Gh0b0Qcj ztI=3nr)4;{HNn)O0iuwLywPl}0`w}JIssq39Orc4$3n`VChwEc)Bp?*Gi-fci*7Jq z*=q#yT!XZde6eX0j(`5Ss=4JDS46j8qwKBC=kCRAzE2CN6G2oXpv!yN;C{HQ$9h*E zcr?kcJ)5W1{nM$V=9~&wXcI0nIoNT{XeCZ?W&(<>;oaJd5eOtF7pWe<1ag(d0$8g{ z;o4EJ?%zHJTwfjzSL`rZgfaM{-Rh3O*a>j<4TiMTqq7o0s?ik6l()Yf|7@ZoAXKGhUf*-zo&{+Kpx9H!p+udvh> zBk%A5wKp4Nj$W68nY8wd+HOiLT<}Mad+De^}H?vc4zpA%kelh$;3RX;q20| zY^2TFiCetSsx{|lx}$F|Mnj9@SEo{l18n z)!$wvvZ^^zd5|#oAlyt_G5@yPRPkdT<8Qv%KoSOh`GDQK??;7m2m{Eb(PAT&EVOd1te)KqjT@+87uLN(xU#YvK;I`ys!J040^}TD59Vg6IIIVLp z-U(Vza`)`P$v5BZ=2CF=_JLpQM8Gc!%~mg@m7K$l%^9e9B}FkoM>=@0uwQUY;6(sN z#^Y$wDJ(1&fYyS>#%44%wFzVOLyh}fw43T+t|@|hN1bS{0$|Q*2cTJwnv|aFie1Tv z`utv#Xv`jNRz!MPVKZ0Z`KO;iuq6(zRyX;s2Gkc+W6|nsl#8(^5&H^+#=8O^)(Wt* z;U$^vA7ems!)*P-v?S1&`=pNKqk9UN8k|Vkjpf<*VSJ@9)kS;J@%0Vzn{7JF?E-bQ zVltSU)~wz}+2jZkEY2`HSmVnjTXT{TNZU|6pv;R{5-emu&)Dd2r&C0m}q!r1eVPT!--EmQbFBY zpMF{a2gW+l)kUHR-ymQpKXnx%r*A`t%N7}r!IwX=Malj>eHi1;)}v}lm1<|Ftju+_ zV56-n7k7mC+Xv4kzwbkBymu`@^=g@FbC*)|w?A)3)yZUaXP$yMB5{5~lF90#l$5}j zIE2MTBRX+%XFS}HdoDXrf1jnRf8V};%D$^XWTaIk=p-g~DV!|VX8$ZLRiAyf9fuAb z5rF3;`_e=9iYsz*%GA-Rpvt<5nXm#bHQDfJEL9+!2NghO)Y@b5loD%7KuXP3nCTV#I!t8Otg(Qp|Cp+t*Nv;hmfYXqx*utvHXEx1g7t2h&&n#r-~RTAQb_x= zZrYVYH!s;c9_6TaE=Gg75~pPSGaj?vLo0LH=G2TX6KWUFaW%WcVzR=;;wCHF8O~-` zIEfc!C#KKbH}Uy=e?w- z=OH7bNSz5(4<5`^2Z>hlkPtV-#Z5X7v4>P`rIJB-N*S0h!FD_UvG zXti6gz(5lQjTtg^INHjG$RzFb;)}zJOA2?cMwGcwgkPfqb&OYGK&m6{SDw3K{Fm*g zny~o8k8y13IZVCwd^Z9+TcqWrAw6FMdHmav=w_s4iWKDSgrD7J5&_?;>a6VYoep1- zfYW>7A!NooUw#>_dKO@Z2>o-`P?R*ei)iW_#1NQ~X^;ul*Vl@s8KML~HOSW1cEw-_ z)Oa2VsW>h#S6JKY;b#$H+gJ`u^Er{E?W3|t+JynTN5*6FqIsCTWNtT*M~L;59Bc5b zfV^}aY~3&G|*<#`h5a`2u*g1fVS`73TY&huK8nutwHG zS4J9Rnv%C{QC1dOBT5jWxkJrXghT`*AjnSyYy`r`g~IIJg1x8rDn)=UXh$GtFROa5im`vi z#pl$Se-l)2bbV8lD8Uls_>OJcwr$(CZQHtI+q`4jwr$(qdGGChZuC!`Q$O9=b+Wp% zG9td5k$A7C)q8vyxX&N#Z?9UG+z>#fG`loSDA)VON-(i(Es}hY6{DL&L5gxZ!G3jH zFxKu;O#b1K)SLqcCJ6&V5U*eW7eklHKp~$v@rS58gXC(q|}4$}in|M0@2X z|6%>M_1t1ay4tDKFpGtd8h^UKhJ+9p_k*y2K}X+AsIad0)|8jalFVlh1OB94p3F_)LVWU{qTn>J< z9aq3F8sjPvP=T~{j*ONoF<9IgI*-1^6&QbZ-a)jsL+q@#!>V^ej%ft7>SbO#@`waO z(hTOy>bdnzxmUjb5M0`_QR~S2I|4diztV9`n>lZ^lo~!91E06FwW|L!ozfl}0C%NE zP24v^O@+;(w>ZslnEcwM&7)x@R6TI%4{A$&?JjBlrSk32=_P&*YnDQ{ zq$4v(1n{_-y%+$xiN!&kkg~9-0d`$pA`V4KfW%j-PQ@D?dtAurEx9v$pdlvcf>wOV z`+o9vcI{aVxplucm{JfO|FxT-*Jd?>+KqDB8CYv39MZ%2C zF5H2oWVk{CL;!of0qrYQ9^U8f7sRR@bVYK2JT2NR&f9d<7l0lfDoOiTj{Wlj*H&}B zY#PDwbQXkCPnP1V~7~O9S2)?^=H@9pg zSw&1%*VMtD4T;TBL#M4bxD}f($UOa8PHCvDXB~DEju_3!tQhq#y4z&+6|GQu%i-(# z&rhmNe2K!!0w|GpLD5{t&ZGj0?Z@hSTM%z=|1ow^X5?Ha9!@ z^1d%Xk&kf~e$%E5$=_gwW!h%c8d|c$8P7Ve-At^Of*@$EuZ>NegS9-HzCtwooWsBs zb~cK9nKJry*XC6Kw3)O)p5EPT|Mn3tVHoK4oyOnA#!2&=+e3fy04K@sa~`Dg=k=uJ z2#R5?nwa7B#q!kVYxas(4nY9sL)3RymsM3N z``5HJwaNAN4OXZ1qJ!RUSdPd4ZPz(2#+U};KA!H<(D*Q?7@wgV8IJKXrn8dV%V3dc z^|S?%y<9Kep98;@Xx!AeZUshZ(7ug7(;$Di+xdYCGdnbT&RRS90Y9%IbH-1Nt+{4! zMP${u)D54x{|RPYVcdK!LZK~?DBdTK7JErkrcCksejo#KhFsFF(qe=A{-#M|GK&Q= z)c!G(oW}1U{^<1KrvndIX>?dfnm;%=4k8kJS375~0hyB4wz9TWp1I1q5nhH+-=vPz zdjSK2z%Ex8`6F7nYDuZ)DmQ7rESeqt^Owltmx^dqz;01MPdtcIsV0}i5skEcUUHn& z2%{N(`;){2T8qEO!KJp}O92(h6=^@v(#>+&?gfUyOd7Cdk50oUhc;Xyqeo7UbgURO z8q2+^nUS;e<4#F84>9{lufhr237{6eXn zu=gjtp`1E|DR4XC0m^hDN5>iT@YX93x3r7-&~G89Itz#ifp6>9&)m*dak)+K!o5nj zNAkKy7J?Wu@5_w2rL)jvT4Xr*`mN2uiGjiqQ5N?DISjU|kec~mWGC9=;oY^|7$t|_ zw2uOtCo@nH;rpqYrk)k7#V4s!Cm6RU*MDYdOa+3N4FP)zW$A0!_eqRIge3evEf%gB zTkRt}a`>u+PO!)f3D$P4z)(v9L?LRWHmp()K$CY3HV!3ocAJ#x=&`Jf3fK&}h4=HH zexGXREk9v*_M=YG^>1H07Hgiy^X3k28_Ex6i=x11Y%H09Wy9_!ogZ-R$X0Swtp8rj z&NoIrPRPI3R>S=aEQh7>bub%dG4UGZt@p9))w;e|lhy%{) z*+d#~6sc6hsdZ6lryV-rRR2I8H~jrfh4t=TEm=XUcqdi0;7c!(GMwtaGUm_~LQQTA z6&q2?n>1(Vxup7z=3w>o7!2-3TBo=)YRa(Yprm>o7d4e4SY>^&LR|7Y3Oh0c%|Yww z3``6pmXSctzFmuf*fh6O9w+X()a(&VLuN?@OB<^XY`69o_5)di9N@^jXopCP7j{Zd z4~IF9B4b3kBr`TEH?<|hSRNh<1 z=&*yi!~2G)X=&v;x97>VtNu0B3U0== z$3H85EF=iBBcNR@k4m_`RY6>CIHx2~Y@cUHKmbw>dEPABP}-H04yy#1IwoE_-bke( zYmVmT*;I}>{wjKHki5K8kj&9Z1_*<5K1wPoWtaZN>L1lMzrjj_1{ zGa5>FjV~YzY?(R;FYfRR9E2g41>|hy@u>yGHiG_*BVT;O_Fc=!x~#Uj@}X9JikH+v z%7_P3;R;(dzz7B3k8=-wN=Y>&iIk8vv1_Qv`?(0waAr|c z($<0ENFL8muh}BXFFNe8-6!1&FB*H{|D+wOW^gcxwwXP^UMsi^oA(uq1S~z2%C_Rt z=Jfo9A-$MBjxl|ZGmifF^U3n_McnD+bbGAEZT|t&Bk~@LBfie@d z2`pXZ-{FQ(pLrv4$w*$g57qNY)t|wrU$3s5J4L9Fg6$D1DPBP(w${c7x=U$A#TB6K zm(nnY?`EbJ{2@jOaB_20I}HNLy+I?u^Frqb2RO2J-!NVvbvv-L=7h&NW-k01GPVYj zctCov*#?Hqn?>T)su3bLnTAk^EZpRmmS#^0Cxh`u{(Uav?)>c98d)F3wp?iPBFNsP zWzg)Bd}+?Ol0TXdu)oX%RJx!Lv#)zYx0gXr-Bu}_L|CccKf)jsSAAgTbJIn%+I$u6 ziOaTtf}ljrp5UYoSYf*Yj;4>!tC4*Mi&HkZ7!nyo?2z>^@$i@IBLQ`gdM))!LzN@7 zyGGZZA$?Y0LSF`hy0MDWp7@@ZbXZtSE~TwpNK+Gsktut2-VkwUu%uVV{vT@p0_pDc z;#8bL(+aM(-8;WW{JGx`Jh$($o5RP~Z?Q}r%++Yqh2pfHjTpzvlK{Pg?Zl1pGO3&>@q%co;J$02H-| z-8M7e@eRSd9oe`8uKwH0CP$4-^B=yS@+#-(rEwu-Z!+TjZLd@c8`oa;ie*#KZt&Ji zGa%N$+J(}%zJUP|Ku*;4D+%V84{#*I=(wGqo$X|PHI2;=z2?l!t7FqHiBTY90s?9< zMI-z&?tg+Xw0W(HscLb>1vYRa2a1iJp6@w1%LQUQi|flFl$uB7a=3veP#~1!q`}ni z4g90sC!}o>*Z0S4Y&%DY3F1HsNpB9v-fVa;)01ouQT71y&fj1ha~^M?d*(hg=hauIr)SPGNg87s^GqID_*uoE zBXu_oIOYf@v)wa@C?N%8IlYcMJ%*Y+z+yf9V6tv$5nn684Wqaha*Rvv`^46GS`Me< zkkqXuf|BMNpyf>9iYs%Av2j0-gzcCYSHQB*b#t?(wL z#uFFz%^XWhBmWEr9%T3I)U&Ea^oe5?TW)Xf>h1db4G{=X+X8$BziY}V5V(E>vp7`_ z3HLGQ9+v;~qGSFMGH3oAwEGymtz=hlNExiDjoTw`H||iU#z~-Gzfzs^LtP77|7SAe z{B%MiQ5A-}{4T$0Ik0?@hS#tTi{P4CVDWcyxu8Y$Vh)<1jDB=|Tw zd*_24DD7pv9o(f62uG_MGmVo=@U|d#_6m7Hr%J$rFMRkFY+i`__Wj*ORVMi*s&!1ERsbWUqt(PXHXIogd`wJ;K3OB$&Zj zIZ?;Qrx-dyz(ZQvox3L6i>_bLP?SXvrbL%`j2!t(SJMld43o+Lr!REkDR!;67g`Bz zW=Kb3Bb%)}pIlbN#|CY@U2--Yaxp*ans>TwU`G#(k<6hK0t)G-DeBGX`b!1;)|?;L zk>O9&VR)gl?Nr88nRSJ~nK09*G^+EnOIA+Qpef1vN;t9-cL9*l-U$;U$^n}Pp5KBU z5+Y*pF+tu=aDbYOA!|}fz%gnE>{Vb`ZdlT&qmq8zvo~=0OAy6suy`TFvn-dRlcUaG zWOGO%2rFOTy3Y+`$l>{7ra{hKl4Vf1P6P(nnO%!uPv(4+6+e9A=jxNIp{hK41%!^j zBcYD4ySKOWXaoiY>;L}I!^_BgI}(WBafJGJ`wBS}Cc>Tef5N-DAPUB?ra4d1dp1tM zLkQ7|6Ox7LNW-hv{nPp!tU8M>e{tIkD<4g`3!^A5TraRMeE=tx46yV;{C8TiHaaZ} zAJnOkP_Vy001g9_5FGK;tV!cYuoAAl!y$=!=)e((Eze`g$Wcp-yc62Telcc7;IX%4 zZR_~A`}z63K?TiTbgcApTBih^mUPWxt~`x_0ob5Vp^Q^C>`UujfrA4VC+cKR(^}N= zfUmMwN9%fd;x%QTwY{i#HzR{Sa23r|E$F5ycMafEjt zqe)ljkV6@-P6h)$-4ajtPd8CUW3O>!qNO|8U#xE(LmY8(jnd&^&AL3VLhaAy>FZvM z`V`edXht>)P6Ri)4+iUkT(_==Sml}d{qx~nF?h!tPyA|d9#=nRuW!gXp>mUnasQK? zM1~93?KVWEXB^@C9GiqwnJ&8~^dM|5yvQ3F;iEb+gp`HI71f?Ud~kG z!`XEjlss*)5)NFwLA#iDTGEI<-`~eG{|)nQ-?4CLaZx>{fnczwZ1NF6PXjg}A$QaaTl{|XXw8c;u?B_& z`DCfTx51yR``qIs-NPYfl}=z5jw0y2TB2~WWf2opC8Z4s^P%8E)r`^mfoZ^>8Hpg9 z6hlT1qnOaf3dO|$Rd-bxd{lg&Q0~9);MCDG1gQy<<~>5g@M)?wsB2hGq?a2-g1BUC z`u~muzkyE7$jDTSOJ-2VHSGs}>R?2}{qcAYS^b*tjuZYZ1aZ?`S)m+u08Yo(y0(C> zt@0J84;o{kqlbP@Zp`u0E=$A@vaS6=QV3O791f=@Kzmdg{L%K7r4*@M{ap0oBY&r`ps%C9gyRE|6TsC| zPl>XEWhLy|b+S%B391U|37Ixy@qUsy^*Je3-)X=g8R>#wQ>L4uvNq5?MYl^718o@T z1hE*yPK}iB5|G?WJN?W3JfFv@?Q!%}FEiqZ4fCw);-WIfa0r_@Oeu}3DVTb5@&U}IQ(ID7oZE>2v4`SqHUtq$*bD<~y< z6IUO+Kr|aKO&RKQEa&23D!&~wq0^mUu<3AbO+!I(HQ1C4OBuy05RGA;YIwk_9m7Ab z=<kT zqC{h@;-!Pa7KjwKBNOJbQv$i_tse71B6h<0YUHu#i26%2K}-7l#OEY^QX+MqlZ9i> zxIVOsE3}>!4TcQ7?sq-@HK-oM$}&}GmPA~_BAkbVQsvkXI70H;;1c}S)~LTCEgU6R zQuCvShmt(=t~a}VTY-xT4WNpjOp*{@}WpRn{C3eZebifNDu` ztY>D&{^?@1yWHx4d^iybW#^bhh#ha|4Q6eUP%MpO=*vKf8Bhs3GUTK(L7fL$u=hAV zGU`^qxNIpTa6#Me%HViFOGukVF08+gS6+Cy85|6n*YD}(MYQWm4(sg99-WdwdRaKK zQ%HzB{5~nhk`BOk%{E-nJTXTzt_1^&W92BlW2dn-_31kG(efB3bzLZZFALQE$XAV@9vifn z8vzd~LMXpLg^yRh7=Fcl?C#=U!PRrw`9i?uUViVRn>gJc{ZG9|Z(Qaa@B)BllnO+>+?y9;$$M5=ZnCGtiVh3}q|0zDh7LzjNl(;G%mGLXHGFjzfr( zxr}-Vp;8FMJKULR61KYUOOM=%DLDfpu_BQ3Cow%=R{*5a>U5*1#Xrz#Uh9h?jxs`i z^%1NrAHoHrd>F}wITbH3{u0870!F&oi`~eEx|uQR-c^Z(Ap~c)2^AZ- zEh`IuHYK6iJLJ}lz~|A*UPP>YfX|~|+P~vQHkCg}58AQn8Qg5&L3zr)6akm=q@@~C z4gKDHFR9llgpi!qXv%1?i0xgfm_Xeg398y)LeEWKH$dj7DEJRx^Oh%&wpmLfle+|J zCt{2Ajb+m!J(P~ewi^0Jds($SHiXR0Er%*EmJf?fv|OM zAt8tB4sjP(P$zC_scLixc923Ac83{wcoc^_wOZp%qea{=s=LY%aFmpjNW*t11g^)2 zLhHR(z0K?Q>DKvFwK2%s2GFD4XXU*S7!YE&KzmnG2`I|ZIG~0h`q@=%Cp$&;{X8iG zWs%u*e32Z@j>ly#uJ^YTBU>8m$zJIQNB}V+kmS>LNaP?C<>d!$gY`SfWIV+X+|xC} z>LI`3iY*-O%ScHp4%^L?0RSL&C4>c(ecfU1uao~Uys=XoYFTl}yf;St(~~rl&Ila2 z?$-3|q|Cw-kF+=#9^ASf=E2&ty2W99_tuKnA)R5Li!z-Xq4BRve%_Q-9C;AS9Flea z4!m&im_Lei)&md@#;CI#^5ZPt&9xR&6UV?Vn1k}N%9ncXi(8+3eVwwUKF@;NV$q66 ztwSUG^b33iFyl8TZUOd)(>H7b*4dVoL;Ha*ji|A?p|<|~)B#!5G+24tzmV`TZ)%ia zZ226S_!C1>VoypcD?OkjC*v4Gke|(<o*H`r~9 z4=X)AQ5iT6$hmi1V(M=MmMRS^DGSQy3ELK2d-z#JJ~DD077cmV!QRxx%1H#o1ilT; z`g(h@0M)@B1HV<}TA(KznKb5&Rj{0t4ewv|&PU0^!y?LldeM%pvywBSSl*c#5--?- z>UuD~?kiYniOd2_C+Ly6c9#u#P1P9N1Bk~^RwFlimI9<9@V7Fo6}KM{z_v_~@{RDs zDZ>wBPhb&g`mj@d^RtlrpUn+>rQ}XjZyuEHDXU%hgL2{cc3QDGZD@+IukOmpo~vIH zs0fqE4}CY8P$D+Zy9`03b3*SO2&U(1=6*^EA&^=4jv?*9u9hi#A8 zkpj=q4N^)@n*$d02J8{tz%%Th{D9YrpEF)z=R7*POX?bG86Ab(s8@Ygp#YO_uOM(n zfP#!%^cV&!{PXoXhQNckdu>VZJ#k53aQphgMuc%o;1h2X8Qc$ z-1$T}rse`NT86;gAT&VP=pPR}D|59T7<_ZGzo^m@b6yV%?1w{eadBakwGt8n-aj}% zKrFxxL+;km&LbK^{vSev`t*BW?+*433R(FfjlvY9F5Qi##l-$hnl@O9{K}Mqf`%Bt zqoIY3j*2hALmI^@Y^=^HQIV62H{}KX2mSQ_F(M~)f!x8Io*0fjjFk`&*qaaB+6I=B zL&=pDFu;e!>(o~qf#Wh z#}#lNsUrU$wECmd`gaHRr`dI?ApJ(+zxbrL4;_rft)c7rWR#SYgvZ6Db;D`da@`P7 zJA*(X&Fk#^3_Quq&5dK*@m2y$`L&4ZaQL;^?N~p0>G|pE{K?$;6kV5r|9HL{sI2Xd ze0+4SQm+%leN$3U__d&LA4on~kmK`F<)G`l1^V?#$5rDk6mW2;tGY2s)d`)PlwSGb z`+1vL?Qo;}mDn`a0R{$M(yGb(59}j)N$MKZssD4vM(q51B#G(!Hla5@vjZkBzwPs~ zb<@f4=*gq&c}DWN2j|y&e>9<>rA2kK<2XT+aLRMr3*7qkym7>X*lIG7V(@gnlyJ-Q zaoJGwk(`{IfNVKvkFV=}C(_v5oTH`#1qu1jGtg;*XZSXtNzE^$LDT@?^~g3 z?^nd`7pd^th#fMStOOJk5PTnNGlx5Mb#;su#QS4pxxz+9M7}Q_I}-hZghJ)BUN>E2 z5mO&GJ#-}Ua6FIk#B9V8Rx35hY6%3#GWB)(eLxb{FKOH)GxUc{^|XS3QUI~dL-u6@ltJ6SO*2ncV*gjzdkZo*!Xo64PrP*K_ zLySJd{Se(L=3=9(o6{*ahU+J{L>w_omg^l@RYjeOd|epF+uyXJOBDeJv3*h7LyNSO zGYG@BiH{@!GY0uUqL9UAmopjUk&sqLjo94BujsbrvSP5+?lO-gQAsvID)GM3^Bn?% z#X9QcEgRDd3zjl=6Tez-y{rK|Mj%04>=-R0Oz$VtJ~H#!b9rekAs+?|LG-Y~+aU9& zndZT0BL_-cdJRpfR5sZo3#v`|ziVFp6%dh2GG~*-fEXzQBs%!&ZUk?HD2|&jA{4BG zjAF}>kbI1ihGwYiWt-}%llhj7gM;Iq2?G8BWAa&=D)p`PL_mdR45e5;gV_dOT}(z$ zM6mELL5j~o4BPmOCXWY>ZUx&|!Yv=tqmEt%D zCbad26CjB%@uG-`NWE-^r@Fv@u{ZB8g(B5NXme?9FE=j)k-){_`Kvk`mUL>77c51@ zDiko!C%IK3oxY#23NsmGDfYrmZb4)~EY7_X<)qXrla)TLEom!zr$^MpL!*WI9V7`6W;xZvGA_+KC3ebEB` zeF4S7a`dK;&KDVw;GM$YiLaOIJROWNga={7#NxhtYzrJQ zNJ;uPX<(IB@05Te1;Yk5$Q>rbQhL`s z%S@2u1a6V`|GNXwGJoS~5@TrwoeEX}{6a!RR=7q`KTuu@7z7yr{P$XiSY-Uq%>M(_ z@4(2;*4f0?nbzIL`buZR@qj(SXQ#H;(vY#=uy-V9W0@qavhLKq2XPP-=EK(o4`_k^HTNBAC^CkzCNIdnQa#@6O?F-h%ecG zFO+P@n;vS^igVD}EJ_r~k|IAh+PD4_rOW5{6~gLSg=pRNjnwccp+PJzm52roiG5?^ z^Y#9_+|+zU3TMAgPie|3V~!nLwBOqo$av%p2E-FlA!9GaM@w7($bW?x4J%0ku&3|# z=?^1y{k6+SBgqax3whmfX%Z&QemMHAq6xHQx+xh78!T(`8!SUZK$Q+iE*m4`_DkM( zjLe348q`#2p@d(dL$j9>K13Y%W*MkY+tu1U>I1!;fEh61d_jj!?M@vi`3;C_-^aRq zYkl9}VLx{u8bdrYWI52Z(FL)zlpMy?8asi-$kBMu=n2>l(A)57S2_ThyW`NG!qfeR zX;mp_(#e#KG#EHv@r>u4qG8s6(GG%_$0RNH+~eK7N0I9l8_?E4fbF(ehX(Xl$RWaiW`x`B5V?Vq?_!OB8SzW z$jU0i0i;3^q?vSNV>z7pY^RYE13^4bo$`&2LY5UlfZf3y*bEb*3hKvbJZ#4pEPi`LD0B~jI&RSqVHQoeT8f_ z{t%dcqxi`^qCJ0t*8Go4uTe>B-ATw=!$ag%yXp`-QL;+V$f@t%_iV~UY8tU4 z$ws^w5&}&@eJIj-fN3JL@u%ZrKdrH8p3q{!#cRH-6roAGtmypNm2xe?lq1GES&$~# zx>~uO1jY_?t-=e3R$`%Tf>#9+ec5|}zOmrg)a{-~7=9pBJy!bP#dNYgJzFr3Pw2h| z3PAeGxunq;x^yY`RU>iP@o}m?MYN}c-87nJ!eUlx!<5Y*v-~D3)OwlXKL!Lu`_@R0 zqTz0pWoxFVK~(3m>ZfF)m5_RL!$0d1>z&Di=BrgGN1N8$l7hf1P4zbz)=qOs?X7~3 z_T_?dXvjpM~fPtR%;f$_5IeHsT4Z{TocdV;RtvT&SLFRc&Rr z^H_X?BCPaf6rFWt-J`52 z3m*7mCK6ztKTw>DUrKlz1+TT{KsiMOFLX7;JoK**gDF&=NSW&`UJndpppiB=Q)4@S z9riQ;p?HX>!b#w`Cj_xy*^XSASSr3B?T(Htf+{zAzMtb{cAwdeyUKa;9@0#3Xc22B zrM6+NhnfEseM^9Ro%<=1tBqv?sgQtDs?56N5m*WejTZsYY2IPety{7-qIAtJ*XLKC zhcxHF*vS0KqFI|?c;?%f3T@0n*-&Avm*|>rtj#vZ0@++Xlg5gXG#uQ`{QP_<1|EVn zf&M9&*1zy<2Cj&ZrGAuD706kM0Jw>WubZ`4%ZkQV_aQW|54krllvnmiFSqk_S}Oq)sSR(GlGUbPxAE>5?& ziSDuYy9Ll)-}$yXXmc-4U&}V?NsB&K@UXCB{_2d-rGB1cU5;XFXtgUq!|-W**LpLz zE5P_24Qp|b+6jt^(pA5yI^AW)|DBVv%7sVyopC(-_v}O`t9b>_9W`Jf`M&w-vIo&Mx@MD=ZtYq$!fNSXnpE9 zsN5e7YM0+F8G`UL;5QhkbbhxxmMPmt$K)lvhIbGA&ZsA(jh_r-pvvl7iM5UJG!aef z%hccFh1&(iS*l3WIAN(i9(Ct7ptU!IW>F&fbZVF0mp^M=j+>iEIjV`+-CWZ?0I_JI zTX8WHCw>e14s94XMt;gA=Ddb|j!dkak|3`mCs-|S)2Nr2w@qYMr7uu5d9h?dJ(xg4 zO+vU1f?60seDeaT0Ef$9+%yXmpO!ZwqC|U2lsaRcD5p0=J~_V$^1$$FJu$uJaWqs| zaok?X{4Z$iTH_G{q-C+0VTUcbR%|khu_6hC0V?&xXa*NPyt>E)tM2JoFg8Mwh(U3% zpU9TBB1E(r1B*OaZ~~8_*4v z2!>aMdbm^jrlu{={t7yiDwlW&Dt9mH!y%$12ozCMT~B3+P~EExgV;+H=arRN8p?|} zgm_3g47V?f*jnOmmxCctM>DX7f9*2xY|HpHM>TwN5WC2$;O)3Vy#=%0f6(9c3M{R7GcVpgDR=BaX6cKr)twK!K`qSsuf*O|!IBCQ;4N&b%F&DV z?HKrDpk_J_-XJgv93>j48r9Rx{Kw=??vZ_GD@7JTWs=bTn*e=JwcUAKe{k1AxIo}>T^7GNGvZ(kC0|3uzPAQ? zrpVzCR79C*2H$3IrC3D9qYs$6Lqt}DP`M=+77?kUDYiT@iY(z|79=vV_wm!81&=lz zyvuuE89FkiXN8{~FmTcUHgTFcSzcK}dp5{h+88*EH^EJD-92F2$X*#brEFwbLPUpH zbuBM0(v^@VmP5Gnk_stRww%DoaKLsdn5$)CVP;4+>|teeNLFvRzs;IAP0!6P(uJB9 z=R=fkG!2(HFtBRG#6Xl~1d+AdC}XPa_#EtPwhZEx{4;!e+FAiP!WF0%?T61bvE=2t8TuGEN{G?gW7wV;G(xykd)qOHX zRu6MJW`16in4xMdTP+mg4S+_mGba1ypQPC?Q=;)e*Cmf`vpK41@nw$3(8Y7aGShnR z$p*mwYlEsF_~I1{bGeZ+C=L&-r|C&!ckcs5IK*5-ENW zRejh$r*?C}MZjBt2^_ADN^M9^J$R4`5#!*QK3Lti>`GV~N$)vp6BJrpaXz9zd5h33 zE)3%&xK{}WJ}j+ApLP}GD180kKEDNQ9|bXF{UG*^xm7++$NLFqkI4SE$1&a|VEueh zMGN!}JoHZvqX=8|OawL&n;iRsO3KH>Q8totdIgD)u(5&^{kHH(x@x^ccsbJhYcdxB zY2H3$XQYt-(*fqwA*bmH&S>O)OuRYqqKn{_3EUWlahh8K94hB(|HGD&kMf3@gT11i z6VOyF1qF_nxUO!X7RKX(ey#O=I9+B!;OhJ#nJK|HNEBz{ zSNlPK|DsGSCUwqLfO&R*PW};dF;4BR0bKQmk$rVJ@rhQ*BTT^~I*?Z4*k$uxNg-w$ z;5y=S+e(3Y{74Q4a4;odZ8=(#4s0WJXqPprZ&W}xH@4AP1H!fm(Yp(5ZNXt|{0t{m z+w4=bj{*=0CIp)FmFCsG#nsmz;QkQ7KaT;7yV)6t)-iAMvY`Z1F!Cr>#c20$#`;t( z&@fw@1Vh0nXc_=`BMe33VI3)};+YjO7(-N6(+ECUW7I_awn@B0IUwP%d)LIjT;$`@ z8%AMdJ?+pftUi3k9g|0q5sjue>oCUA5`Dr(5dJ+H^YDH>+mG@^)9+ZSh57YI4V_X* zeQ)%vBWjiINkuIhYa`1h-vZ8i?h`RC1yE|g=&J|g^h!;y^vd;Q6>RhFF$(Lm+I=>d zQh%%__N4zjMA*(>bsd7IB;Dom7y8%&GvJvEk}GBgDgOdJMo2o|6m zBBWo!!T3=}6traH;C%ifAlQ?5^J88ZLbqA;-!qMgZdIqvdZ$rgE_Ep^FF9!)k)DM| zfFNWXh!PYrNhqWD>wYjd>dg>-(z*s3c|q!P~=As;g~~bi_>noOFf>VQs|YZb9Q|eoM4qM z-wTf_F3-X!I9F%R+j*tya^uo-ebWey-Tyd5O2Gy!lGY#HctmoXvt4@5_VY1k)qsS@ zL30}2?rJ!GEH62>5DDfp_~#Hk={V^`)*D=GBJ~(BM;zWT1v`T{Fkvz<2Vz4I!@rEvm5#L|~l_t-%@@%(uqB1uy!d6o4)tg~~Kf z(CVqT&{bvCspUh&xq499PRkpay*9#WnzN2*NwZ>0xpF>Csp zv3zfg4d8)st@PF7%T8YE54hCh37;<_001l$Ai)2>Yz6sSwsLazur_h}U)4$@VS_W} z>qh0D%G}dZ5xcoFhtA&Jn@aJHlW+-D1s?%o!mTu-b^LgrUQIh(@8C$m9OYWq? zNu1sH(NmA_>(kelpZAyf?iupit&0Db2*|;tbi)aJ$?%HZjjf-ro%5va%L>SFk0S+C zqDITd-gnSnzi)YN)gpQq)stpjeY*^z!O`ss*RTxbV!mq?cF#GyD){Z_($f0GB12B^G-eFA;#PPjo zUMg#kL1fRUx(WHT4haC74W6sev@P>$}!(Wu6ywW zL}Q7{6PLMVadnb28%K;~%g(%kh`)E3uwN1d7nD&=F*!OEX(U`8_nSp;G*U0mG(1z_->_2vMaeZU0Y17G z3?T36r-tlOIF{aTg&6(r!@~v}^;`7^b<{1CXUpQW9X2`ADu;HyD7_Tw!Jul)_l*?c z^5R$&Pd;PVZx&+UWWlks#6n5RvUeFlc@j#}7Nzc=zOfS60Lxraj;U?O(U|G@xr8GK z;Z%qa1vO_S4*=lf5_#n8B7EdPEhDD@GEf$U+OywMA6lDZD4K&9`F7u~rJf|>2zj#S zFZg|Dc)UNx-U4-eBf~TqQ%nbP9``+1Z#Q&qBCaeRZ;QF-jJ8&c!j9h(o-=AoQReS* z?1jU6R5Ws`ojs%OYKa614|4Y(Ds-L`RMV}6*8{K550bdmsk5)BM3~3?2NOChFRpuq z0LXI3jDowklNKhmD@|qTCNWZoRr4YIx=$V@Wj=q=4qCncnS)nQw+Xza#o)*lNR&BH zEYjpwsaIpvV#8CT~ez zf29#Y0zhzTv15&8&I8%2!&W7P>Ab5PXGBGdA=~SXV&^AS6|ugl>mL(KbDMhW0mV10_zu@87U!%XAlM#tD<09kxzU|BJXM5;@U)~ zRY8rJYL2G}9&#N`IsX6_8aN??OfpGDkjAVirlremvby>mVf_8lPNb<~s9)o|ekj*4 zBQGi3RzCeFO`KpFc_(?U;r_R-MnN?q=?E!{QFqcd{;G^N+<(6fl zT1?Oxk>o=5aNp5KnlG^-1GvobdQ`jm0}0f)y?AU}$PRKc%dU57ep@GW3y$QNl8eQ^3;vwG)g*Vhs5 z31cKIw(Ose5x=GQBuSpRNPM@MIgqe%Foh;NI=I|+nGBo!mX45Z?RYOwC!o?a`)yYJ zsk9T}s=goyAFVSpFcGXC^ww=(dz^_b%GNHQz1SK^TR*xUXSx9;i8WBYinsyRK|S|-tj7kiBn2t>Pj#%&i0}j% zT-7scjJ~GmMdZ7*qsJDQG1U_!EW>64r^%Fu`QdmOBxM8(z1-a zZtB1cIbGb4eAI;kIC7Ch$B`h**mj3d&wfIgHAW{`#4HC3(Q-_@ zQ?JYi9YFsw?kt8&F?OO%y^*lFb90`m@!h~AO49R812Z5jN!?lOS&$Z`<(UMgK=cri zK5hP*DuGX!eOpMsV;6(1z0sPt%Sh0~okF4|DDy?DA# zt1R+L0y4mK5wvB1Szs2KdkSW5d$qQ@7{}E704We0wQ% zSEC!b^1q1GLe%^(Ol9f2Qig%0Ix%Xxpv(z9aa!IntSnl25Cx>+8Tgig|B0f&B8UQ# zunfE&q5$`!;43Uh9T}3a?yk%q6MW(BpAZ)Zn%cONu8E( z5%h{QK!bFv%Gpx6(IrY8V&Z7bZ>9T%d+ha#*v8+T@NbjftdwiyKuiLSJNeW=V+n*& zM54dBgT996|1@?b;81q$f3okgWoHOw8DkjxAcWLw-?A?=V+>=)K9)gIk>#~hwnEwW zH4({Hq)4(aSqe!>mZlzR!JTo;l~f&Uqf^=U#2a>wYE| z?@cy73+oUe+j3N{ENpAZvGoz(nx9~#ULWJ#iB*1ew5yI%e#9WkIzsV{PBu$vcTk&Fp-;q&$h~UK7>_MK<=!JW!TIO zAJ#n5HC0<5jbNIvdPc2887m{#Mn)ERr02wxmv#B7Otz`e%u?e}gsunogBovUeZQX3 zSyOz2QHsA*uP3I#)5Fh-!d9Zn=1NkQ^v<$QUF86HGQh$`^V`hpA_ zc*KdeCx#-7f%29+nBP$^9kZ)n8Ac_}CgSJDI8?E6ZGisJoNX-euqnK*VgNRc|HGAgntTZ#K>~$x~=IxaaqF^m9?` zRJb~6Q6I~`RYu$0X|AEJpJ|1{7o>W`>wU3I4)uU>H74^}FSJdbedb+WGwN|-qORx3 zot))Cp2@fDUY;IMo_e;e@}WWW36*Ny(5jr}F`l3TyZRQ(ZdLG!uQIb64xV!*8??Bl znd()t8CDUHPs6+S0~g&II&r~z{p-`KX-Lkg-Dlf5)@tOys=%wNRLl^j&ob}OX3s44 z$;!5ws&{g)O6DS9>Jz}0qL*?J3**vPs+IB@`5|cw9-S_diZ}^7z7p6RY&i zlFzPpFWpa-pxS*E&HuFe14ZFU;L&T$Yx;thTrR~LAH7s<*02@`<1&{bZcI`ikfn6C z$w6+=8jdd#rypt;jgynS$mUTxlM=4cPU+m?4vFk)80B=)lbf# z04~GVVf4khLgvi$t6z)=2f(=NmaeVQV(dpIJ(4Eh&GkaQ9^ek{z4c~z6jL6@ZhFed zHCR7N-rcyQaT7OjEZEvD4JAKO;9o^w@u65Zq<+Rf&m#A^TS6n=-Yt!qSo7AA(`HFO ze%nnE>(DM$-xtaAc8H0D&L*M@Vo!zKhtqTTWJ-&@eqCgXsp4$>`01%o$=Vmb6eWNV z22o0(2b{T55?mQk($g(;>C5$1cYVoW+jxY(r)|u2!A|9o>+?AYWcKOd%A4Mc{jxpR znI#?5Ax{XYpK9a0UqwF2K*rSxM7BwpWq(Tih~!>)tXB-~=R&DFg^sj_k$rwA7a<-F zld8k*))L>n=GzZq;;0hjk(TH#nF=;r9H1|JW3FJg=1LFy#JD#;5jGqkhQ?nS|G>ip zsY1O74d2NtNS>DBTwqvttB4=PRc`0TCM?>Gh~`BlEb?eKPM6kHzn&NxAs<;}da24+ z9d2d3@|t!wDS`S%=R7FCSGRS6Upwbgxj3+WS=O z>U&=G@oByEX~C}C-pWkq{bPupfD%xDl617a;tg#3MrJpglb=wY)VO&BuQ)N_b(B&NEkArAFp!0iP zl&iR2z^~ZU*?f2-+rLA zZ5zTP31g9{=Ga1Lw)+fro25v(;2D3f&45wPr?1Ks91k>PT#vya1$VO*q6AYfgYglu z1AP_Su}$rPsL*EHDKUp()FQ{2R#8KfzelKdQc}}`Y#Ka2EJw8FddJdhJ}k?cSMSrA z!Y3I4oArq-Eq3lYq3&T3om5xnB^$Yl=1iw8_?k`V@}=y|{M{hYty6d(1POL~&s) zyVcqx%5=T6VIlV1?-&9~Mhj2)zi@A1uV`>{Tq~JC#}-nF6I$2o$E(e`F194lhLr|H zPAJDmOf)uG$kkQAi=hDijlqWeG{FccXvLpjRJt+K4^Mt)%DPr#LXbQcU76=%k<27thQQz$w>R4 zv#;Pvr4?o+RdlCi(L~ZuUxa$h@w8})!R%)?QlQJ}eNbx+G0-)@aTw?#t;r&-&-(k~ zT&Wrb06@w##4=B$pED`U1=AoNfB{G!Uo;M@CJd355e6c$a2x`SMX3o}nCpU+gf-OZ zU_z&jw9T!Jb$~cmS2P@{g22K3J&6mvKqMldv4z%O`r1HY5EyLag~S?t!${+NP+;(B z^V7iZ*QbAo0EtS#U>!qYpzwDxX@nm_SRM99W}hbT#h ztc;8d`1`dm@Vh`D295RjiO1vd(s;RV_CSaWBEjEk51|P7Pn?&(59S*R0S6;7NK*TJ z!4PQ(_z+8K>`(NctRMjn!CpQ{Um}CE-#5?x7t?s(VL~8~0#u&(e#Mho{%8zBMM+5(Dh>kuU`1om zerRV52=0UY#_fglL;3)T-uyxI@A~hyd_xnP2|_sgA=QcfArF#K06~<@Wg#k1h>8s4 zhxG3_7y_>H2R*-(k`TXBDx1s7sK_a*K;>cJ!%7kg|5Q>qVF~$ z5yX~ZQ6P8_oEYL&ms9*<)4$4p2P6E6E+#fi-Pjx=a|Q}EfXWidzq#~K?kD8Cq`xoH z2ZTVnqOnK>@EZ{8?1@wp*2lWyfDmEu4?q7`nC$-sll$Lb(4VlMUs_+%r_$FC4gYQ- z&cz)G_XELk{#ZXXVWn^Ce|(`-NKH_O9bWxp_*VR%W9bZ}SUS>=Rj_FTZQdu0WLP#@q85YOmLjnq@qdiylb)P+U zbLFdSQ*Sn0q0T=dHq&6PEPYON@p{*T+)z;bm8}Bb6_2+GWf~2YDum?~QP~_9HhY^c z2K&XD0sy3EpX&C^88o-MzDn{^WkM@<>fJSC+w_J!OB)A|GG|dkk^W#k3H@{T;jz}H zZzVW#&aF^p^!JWFi-R1PrqjL_(g-SD^09VsI9GPWh-v#()>jjDe!Uakvwhv&m-#g8 z@mu0_N}`Ajt=5kBwF|-3`mN%Pvs{)*8TPlf$grSKuJdm`y{Jr|wz{u(O~^`07wtb8 zDeZVgkV_{^Gkv(w&vBv*;Xm?Ekx;|25hgV)S8y|*TY^qYf4+j~BVt{uy)J^g;T z@Ac-?GJedZPGmQwZWw&Sw^Na&5p{LmN)pA1+MgF}9@Kb{dpS3;q5pFJ;y_HV*Fb)3 zsEBTo^V?`{nLc^VQrdgP2-~Yv-RUglFW}T+TG5d=PmB}}(i#hVRuP?9=fMqfGs;Eq zcHEErnn;sOhH&iMxfwr^I59>alNGs(ac1T?Gn4}pP8ktzx1MtcSqER5G+wpy8Tyzl ze6|dU@oT7@mgsOBNSFI`LMJ~6zHV7cREN$O_2kzX=X6Z9HQ83Mr*r~=-OA#pRwRFxh zHqJlKpxmW3BjRmZkrai)aiLkAk&ah{=-g}Ams(7puAR!!e5$L6;Exg{sE||Jo?I3R z*3MtFS05nAJUR2c;LIiT6;3@i9nalkX1r7kLu8+*qCHsxw0q>r6~}$DQ|wwOK3wpQ zzv4IAwNM8sxLB2^dg)$ms`eZmuZXitauNh{@D3Faf^y)NYl#yeE&!Lf2t8Hu%&OE; zcD3K=z2Ep|-e_}BAzfHNG9<_0c+t5WdYeU#l`{J$JWN%2v*+JTjn$0p?Yu&%ape~;aXFb8Al20-o;{5 z1Z)4ZA*~V!Q9RAj$tt_%RvI*4&6l&&th;S>T$Iu7i5;DKtaPq~WbHhvf-K9k2A(ne zevi!5wm#?t*)la7OByPlrxflzavi~Tskff}!{dn3E)EM<6QF>PI8)}mu97LC6CSQ9 z+L0Y=i7w~4yHA;Msl=U=b$XO!w#C^R7Chc$xJ+St?1Ms9c00|~ytwhT#7mXwPy5`M zC1@$b=}HSxab3Xf_ZB1g%XjrIYHKQNpVomApBitC`#Zc>`Pf~nhmtN;x{(Jf4);P5 z@Ab~!9DKmHmMGBoq>Z9EvbV1gf zz9MP)OiGt6ZdWRi)u`m;J3xCFwAaaEEp7=(pAPeWvazt9D4^au$(lQCbaZK`l%CB4 z$Q?VQCh&2BeXmY)`L0gd+JgOR)L;ijQMk{nG2WR)K5*^6Rc@Aw2k`;i2_NKAW9jLd z@38)bWYpL^cZ}{;0<7gL!9hCX4wV!8q6;tZ*m|M*WR=$O{f!j6939<-9!S~R{%#u% zev6_t3v@+r#gy}L}S z3oHk@MtgvINyX{OGcc>@5{b=?&?v2)MvPm*a3d9_F1Y0azUk@Y+Ffj%IU97(;O#t{ z@owPYXj|EOGlc3C3*o~@JC4~_G<8m(BcUtAy8u&}l9@j-(&GAz4Kqe1Q=KOU;~nR- zO>pVx<6fV%g^=b-u{T2*zGXSRh@mxr2BD5eWLwEeL&itSh1cHMXJi6!F_`;n$_bnG3@I* z7$>sIFY)SJJ87sv8bCb=N3krQ|QPZ<-q9l0Ohz; zV?u#C%^moW!V2??GgNHe?Vo_}ImvbkLhBQ@hrViq^wxZHHfCA$+9TwW%^$c{(H9`7@x?oRdEZP<6 z`)vzEcd8~XLWaHf-9CFcKJY^ENVG0Ris&S9#R)$xJBI9bo>9SUm^>5Y`lI=IS8%B3 zURZlL^INxCFg?82y<74nTW{^vj?<>M?H1Lot6qtTrfdu^E89J`6zqR^65b(K=rzXP zhN39xb$Vf+c1R<;B_Mxar)e@VjQDtl8w8h6sRFk+@p{k z-BRmma!s};-sr-816GF~WpeQkZ|;a1d6jw2jb(Itpn83ePpsJJbqSW*@2B49{>#XH zRmEYDdFwM^3p1y~(ok?<+VW*apZ~5yhGC?@ZD!*Z%@c92{Jzp}G57GIA8ym^iQ5&4 zrJb545G3(S5-|3^rul7UoKXFhG zh7czl|0L*-Q{sOJR8~hxPF~szi~2Wph2w!$eGC9V%D+>;BP#oyT2|(}28goecWxzF z2vke)w2YGMpQwA{SAr{&002_{oq9d)f1uV@hU(}j$>{uvTID5`SKSr>K+3;UzY-u$ zF@JN&q1CdA3g4|hEen-b($rE=Qu-4$l?bcj^eO;A%D+=DvXlM))UsM~r*(9dbbqBj z+>G?=dJocOq<<0vfc)33Nr#rI6;VE!jDOAxjFGJO8GKlAWA2$=O?34)zHa)MKhbSYka}nb#(U;Kz&r$@bRvKo?I3poxQn zowb>Xi!~Soa08h$IlF?GJnZe5tnD3~fX>d!|Di;nzsuPO8Vc$$GeVRoTbsGKLTCUM z=6@#k-v!8>g`HJ`n~j6X0c07jtSE(wgogx?LzR&hSB2b#A2R|Be3}!#u8D?0e%#XtTc!yNKXv>vq@(ULtbs5SmarH! z`@b&9*x#BeG;tk!9Tx~0Lmo~D5=qMAu~{$9FWL=nH$l1x z4PNNx*$qZp6hRJ>*;^BW^3hLXYK<&1QFB591}x8rP}60<)jBfeXCv+thTOgILC?;C z!bAG^#S^CYHGk;w!mT#Ma8NQ5isBWb#;BHp!5uq4ilA!p?g+a<~t`OU2yv5VF-Q$ z|248}4#mWpa+0u4jfQQc-RmMHt|oj&%un~Llt|H*(q`hv(L3y^h0s+}K0$%_XY@kbQ)^e2Wj zrtlgftX=hfj!VoOlJDO1QHGrz8YM9aq7UYmo1^{Ink1CihQI5MLUx#WKgBW$YxXS1 zVFaC4s-}=6U~6rVcC(#UjG1%V(I7hD5=%MGc-c#mC5~y-s?&g6DJtq7S^ygy=DdG_ z*Xz>`y$C~~%$z|2SbC~SMPXU=EGS2|yQyZ`D7PTJ2XrMx{Fjtg|I%b)ZC7rLOSezCG13QPdhRd0&O z5;59e9}W_2%kO1|9(cggyrFL}knX;NLcL-XQXwRVL}~Z=4jjL_B{{D@aj4;KTdsC9_`<3xb&kRQr6*`9VL!w(oq+dlc59@wwzd zAi=D;lQPoL1z8TklAA<}>^ftMs1;+1IrB2#R+BJ7mPtx)x(a5<8{FM|?h(?|-8^1k z_|jtZb(_&cV%}29u|vbk+FR%w?pR3*{b~xuNEXxn@iuKouOy?!Fmr|LQGGNjFHtUw z<}>dSAjTo4Zk?-mc0`bC>L zx7`B!%GCNrFYEcrLiL=ua7EM!nUrIPm z!ZS2v-VW+yNuLWmXLZA7yp4a$^mHUBDuY7-m-WU{ymutd(w|k=X55v^e7`L0g1Sq9 zoX#ytj*)U8ugl(fdV0y9dv^mvE=(V zOLK;g_c5OH&^hoN@tj=}7LzX={5rZ@k3aPe!QIVK>n&`sTviv)B#6~Z*EJ@lmO?PX zX#WuZ-~dNT?)aOUVBOWK)tg@S(bB;@g0mt;Mg6L?NxJuzkHz)A zat)97vzLyxFEZ)sedl)3{h#OS?c9k=g(LFi})U_kyv~f(7 zlj05YR8ZEG#}zIEM5Hm45m9X!pAy?n(_S?@jC`}x9QQP#{_{Niz&C+TdhEz7Ms&dauL4Gf)KDne#&K+O#nZ#46k z9h2cyoLLujM={$9RiGt;w3=nL5`CBT zb_6T-nHN$Emi3k|zL$c}bS82?-NCM2zl(}Ku+EWrsr8iUnEz#8%MGHsV-Dj2-|Ida z>0+p1gD5;U?lo-RkSXJ!O7f3^$2aycs3^gb0VAkEeNIC@6;V{O{Gh5K9Kl@8kURP&7W;CEFu`Vo7~Zqjk8VS zUU)w>@DOs(UQ(Sx&FEC1d*OSA)+VK9sGTc&5*Rb548iol zs;!W6CIeBIsNG%b`VFpQk-nUbzv>gsBOr_3RC+Zu3Cpvn5!TcB1W)@L{>TAtkkaZx zwu5Xjr}ZlNab{cz#^|+6lneCvJLB%K{k#ablHDAe*9V3^h(a(vHl8;wnL-^^UMZ|F z021fzVyO=}R8>FpgeobHGqDkW1dZPNf@41EIr)V7_zV_Z*D5p; z14E->VoA|=<38V~hgrO~DvcPSEHZ_&wriaJ?q>c0R#^@nAy{+0Vipk!O5Go_IsdtJ|8E<@$1S+Ci>DpX`R9Hx zV?J)a9=Gr47ORZ+Kttvve{y;+ijZ1y!nKCYaI`l0nM`7xE7DsP72_q!V? z-w;12L?Vi~(n;A|xC?oLRbjse^!89<1SGGsVYQbuu|&}<;UMpj!b>cGQ&?o)wf)gh z*X8cV%i;Ou*6X{_tTx}PzB_lMcf%jn7e99Egcv3iJDw7FR5?9t-Yv#1(+SiqKX|T6 z2%V|o<(&CFbalZ~-u)mg&2P8AxjVX_R`c4~Wao191~0Sg<1<99rLmvYZ&MGf6)sad z;HWP2Ou4pZ`5gjZcpOZhwgk~+dML@*T5FO3q6z`}Y01xe3?ekk3v{55Khg-1d+vO( zDJW+!oJf(Ux@+_QwCAs673QRu2}9s(&oJsNF>ZycC@=>?lvVBlAn*2!y`)(s%rCHL zG+u&>J>wlxJrdgTA|xJn+djh#=3~5k>aJTsgOYiQ-@$a>bxQIlk5 zTe-!mfuC{WIcyQ@)G1Te#rFoVw$R6SY8$4U@>fL&!DEwx5$t)%RF??*+((-#f&ePxS>ZeDC$Hei^b z)oZfg@;0qc6{qBuY@@~IbQ0~Cn_bP7kLR0)CLOD+T>;PhR}gIpX?yoYb#%+enc()w zuwT1Uf1aShcqKA=#MLE|z4mOl^;BVbokAQrb}=b5A3w{B?PHICTO6f$%%_~>huNOC z5PLtadbqjQeNu*8o`w$bT~A}x_!%z{yS33_(&^)#>Elr<$BDZ?X>1Helu&6A#-eK4 zu8v0E{Eh>!Tj%ITKt!5rB%u50nlL6?sUz`YED84#zbW`UlV3zYLQCGD>N`8(;S0Ps zg?J3ZT<1m&&1E{?ItEqegTXr0Z>etSHo%^)dt;@2h?GVGUnCq08vKm2X7B>N6v@0O zWlUkyjqZ!#4B0JJjAmTej!GGZ%97aeWw8YW6yTm;XMpv=AxR>VRI;g@Vx(xs(xUm= zSF_>XxyP?NeT1K*Ah40+n<2$nJRy-%_diCA*6-$Zaa$zQ$_R|7bra@6R8gm`YG+Ob zd&h>YVexbaoBg1tVYcVI7S@Hza6$~k?3JF9L)Dc;4CeE{)_0e~GJCRqqhuwr*7oCA zb<=y4vHbZl&{?ttg?Ro{N7)4Q*>}6wRGe*8Y1tPcqx{bBd z@Lf&RB`Pi%&z<@w|HbH~lMppQu`A_IKRzbGA7IiLk)bFb&}Ip0IUgmVxv6NfF_I(k zlMv&1*OW^PVKExgb>JCbNv8=8W0eC=$uKux)Wj$!YaBj9Gg-ZmaS8V_;Mm$aPemPx zoJvjKp2jsu8&Eco7zz?*x9ob^OtjjiCehJ3>f59(=@SxwC~qL*#!_I8E&N%lXi6+# z=2^yv`1$%=?pKIvs3LryQPmX+uRp+_hF%F{vxrCEdr|bq)4df}XADhbzo`=5Altog zL*Oj1Lc$?|cJm(e3SwNNLmt-YMQY~9t`^pottfcq!lbNbjPZhlJ~k)O2i`bbA%G35 z+prF5J#}lBAi;)?t+&`_eM&`}mvCFY$N5QbiNcdUIY)KXJ(`jLJl8-=)#aOhAk>=K z3=q0Lp%`YP&oEa|gHn?%%a$!TyK3bA^aE<u7&YwN{j4Uf ze3v$~Lpc-{?EQmbCUt^3t;mHmUKMEO7j5sH1uHM-no1Z9SGkvL>F5vp_5~){31Z-mvC$n?(`Ws#ucC)bM$W_vWuZE~h z{)`=kTU$U^f#^^+DA_%ieFmz{xIFp z4ISWMuTgV45ZjF5qjYyj(O0X1+&xnCOVv!J;3E#Sv+;8v^Oz98>@QTUn6}O$3%~*-X=*sm1WjdqwZe% z1Mn+{jgeb{{#?^;TmT)k2F&{>Qx*M*f8ZX>)c4iR(q}do+TStyJW`L&h;OfJ^QC3p z#*tAianX>__CQ7jenp!YFS^!_te|j9pt#U zD~c1DwfJu8@Jd)y%Ae5vOE(wg;n_|JSfPvzD;~(guKyfw1s%SB5 zD~P+I5&lMp^b_(hLr0CJOmG|$@}EZWm|MJ>%M0k;Twcc(%0mS?Qoy_8|dPC(Jz=`{H%o8ap2M`WK$Kg|Q$$}CVW(li)!4_Q*iU1a1r}U6Hxoz?} z+0B|o?kcPic(LmNmW zp!)@FSH@O&R9;cFDFzl4O=k+#W*efQL}1G2`ev!0KCxh5VNfO3QGkECX3I?Kw%A_k zVab^$A0lRgR9XUAN<_6w7Zx;Ot+GjCG6d^ZBZFnmg>O!nQgVy0B#5gezbt#p;#x(}ORmi>i9a8}p@gO$GHyeGOW+?sE2wG2lwKF~+(&{!guQI2fDNQ`2i# z7dBE5s@(c~DWOr&+|27q4;}%bti$6?@Ty$s0}s`p;8l<$6J29&j5;l%N-c7Ut_5m| zWKakVH(W`Dk@W%JaruW-EzIRNTB8n8cY?^Dh9Ur#e&2j_M!3(&cZQsL-w|sYC9#eC z;J&R+cbpDgOx}!O6GCcZuuQ)exd!sAe~umJ8$MUt@X1hv`Qf2o4d$7cs-CUcB(qd} z8~N26yTp^YCN@HaDM*ZcRH=aVy_9N!r=MgKLydnnp#MA0sLa}(ejG~^`W`mGMi6Pp zh7ZY>yk;k1+N@Ic;!Iy06RZSoy^1@(`PO)qn67h4cXguWR4!M4m&$Us-8;h=I~C{6 zu-r;|JO4^jX!Nwzx0E(#=%(j(V=D6UJkgFmSuuulHDbv*1b7F2Sn@iV9xUkdey@EL zH7>!pr&MdmBhyxFGzDG+D29Ba-ob%q+C}$0Td(%&eHD4LTB2D^?b!Qj=w`eG8~h6m zyl7l&muSt&;_}OM*T`Zy?VT~DCt3bM8G0ija)SUN;pUg51T=j>A&n3CH<)bGJ;S*kfahJ*O~+D^vJ9NR zdboBFDZzikEi3)Fx|nfqT;-cS-2*|hW)cHQJGTNJ_?Bz1pXUASR+U?lac;Um%&EkQ9fhN{lrQFj#%uU;~ z48obSmAd8wBRJcl`lerZ5Z$skxYC)Tf$SSkm3l~17K+W zXxeI6Wmxplce<2@C`;@JS*~Kn&LmG0Jbf4)ffGc9wVH2mOa@^r@AlIg!=+|s-+C#_f(f*NgEsR1$1tBHoere zypSY;{bZMBh?oA5rs!CoxTg2mQxB=={2%L9w2O ze5u!pVK?BG7ExE$9NzCkV{Gzn<(_m#9CQYP!k5N66E8J-=1RLDs5*Ynlljxn-9{mk z2&=-h@+Hp=B>K*EJn~V+F|&rH%xc)va4Z9k^$PX6>~8BH=8|5D8HbDy6{b{z2bdui7VP=w$k^uv}{LtCHJEi*G9KQkeR%>NOMYqpsgDJdkNQZ{vciLKT9nk@jq5N&dfXsa*=cr) zRhLNpa|cX%g<&H5+KHw_So=5QVC;$P0D%gf;FE?`-QJ9^%or^b+-9u`%N{H3oc+C& zu1TD=^w4_vMFt-#k{v}ConHcvkPeO8%2A_ELheE=@L$|2c&i?Y)myO7Wqmz9eYpeX zv?EH8TWwe==FrC+U~)_{QLH}seWbncH%LoEgMy0M|8t~$^lLo&IGO~kDEOa1K|SW5 zE-j#o$>V{RUkGv`aRWLzL!4RyWGqa~WF$b48Q9z!WGO(Vp)SeDO(rCW#7`=&B&M#T zEI|Ucu&_1*@|uIqTy2pS(w;KXtbAlCe?=->6f`tp+vSwM{DCN4lh$b4`xGIKJr@Tjx0@Un9A zGPC}c{!bpixf$<&Y3CnL9~u9llJ%!bW)5C9c7DL0%14%+3CPmb#1be71Tku;{jB@z z(XT!N%^@QLSu&b=nn6D12(obh?$f`X|0mhp6=Gt@zyy`m*`(RH6xi4xx_?^qkJw*~ zUxKdAKqp3XpoKLEXioBz2r{t;3XsWwEWjizWPsmh{$FXV|2G=j|Bc4}7wxZi*7~c<+UK_|Oj5AP;$5eJE3A0J`L$3XmMg#hIVT7vqM;9)6 z@L~2lw~`Z|tt<$b+V~q4cldIlsg@Ged6?uVH{Nx3X8KY_OGW3UelNK1+sx8cB3!&Y z$Ao+$3uj_54PiQ+RS9^x?i;A;Usj7+M?pB?G%95XICPgUW*nN6rf00I`|<&kIu6)M zpO{a%Qq{vy;`c6Mq~QDO$jxyZ?H(xITUH^ls3oEjMbl?7h{sNub1)9zKU2&JeU zmj|z}8wrFA+|Ox|xGBv~L|ZzJtJl3g$h6?rFTd1$n{1GO4g+GmwpiP{o(3c@YPCzH zkZLhVTD#5%G8u&&y_86!jEn2FyKL)#&g4Z^$0oT_y-9f3y|6-BTEdV zy@*{+mB2ZA`%)sLj@+W1X68u=(|59US$5j`DC0UJzGF2}f+qqcf`N(wrz>1ECvGzy zfg@Ha7z+p?MBk;v-(Zp4;(bE5pL#XbYpA?H&~1tDT`U~2^d0(LnaK4pjF#=d1oc9Q z>ULaY;ZcwXvR6_d%!;^!O7;cP@TR{N)|?o~S`ni;k8iez$h=r7U82R#-Y*bZBzYr= zE^BDBtj2R#qZ_;YA`cbk$LIN;P-tF0jgQYf9`dv0jx*Pf~4>S6@Vu z9fj^phybf7Pkszu9+uD8fUXkftcvj7t{k&2El( zY`zl3l7P|uevDL;WkjyyVo(Jert<9@aPF@0nPX693TRby_;3f$se>roDlR)UQ@`+vz!(I+N=$=7`ZF?Ml*+tpG-g%$QqU0 zC#o3v1r!ZsqPKPovuPaH#7E)PNGc}t;?(xjO=vZzbG$SZYpa@WtTL-QYRZYEF%QCS zV0Xtml_vN~U9&-hX2*T_5bU{t!-h^CRzoY2=S_x7ldDTIhAcft74%U6K>FRMFDKRE z3^+`CaPmGXae-8L-?wLQQ$l(@;K1^IQu}2_54W&-APSLSoHFxzJOLudOQQxgIvckA zY~;*uB{+bHd8hthUB|%=x@SCS{p@KZ{P_iMwqK}IRur>Do*swEIgTw*DsG_h=&+>` z;Gt}VVs#DDz@>JKndrxn%EhF=7g{}^jQoTuS~Oezrs3cd=ELBH8QgjhA>*O0E`MSbu4qExQbx0bpVInbXe&$Dsc!Sllaod!DwY1yrWEoL zEZ270GCJ2DjIb{|*ypJ%7B3xfw9Ck%9Y{|jonwgIL%$#MEF~VN`$f>iY8s?y!wzVl z9iC;+6pm$~i_I{;HzhurAPGXVXvR>r-;-!e zXM5FXRIPk`)n2nVTIbZ*%>k81f;0cD%7N?rs~-m5SM;onU4GWonHU}5GL<`QGhcOH z%zJ*N!}|+rcfw#Ur9@p{ zuDd`97db69hj%*%r@TMqZroG-wv>Qs?=v?ZV6O?rKEBxUOf#5?uCjT(m0ghG4}(OE z4mZd3r5yJ;6jlfIsLlJUEd{=B-SBJVOOduf=Zq<=FBMDmahb+loB^*;Iaa^UZq0gO zAq#$8u|1(bb|v;@R-UG?IA6&4IMPL-5pz)ZI=QSVZjQxW4Qq@Qih#y>Eb7AD-;(dZ znsdy|fR&T3!2b)?W#Q?&DD()2$1U$M_<%PGxr!lFqnSCnl;I zVGLIKt&$Yo&F0;sKRSs@C7227;h>-rAyGpA^Z(ro$p4*!gt`c$jG`pK-UMWA0d)Qu zKU5yy5z)!~ECk{&`r61f*~Ap49bCu6uwOy6+hJx^#4%QwgDbJ@x1OV^tR1w)I>3B( z=O1ET&;2ElTXoOH%rwj#;kEqq##{o6-Q=h-Oyu>@K8Frqa?yg)2IwLZQ?w{)S5Lfb zBnJ(>HW#OZ6Fef#X&;`FAAYs7%a8APjttY3@RN_&T|}^u@jY3fsamKdnn!z_fT8QQ%eeE_Dz+s?56?F z@Dui~^xxtW=sho8ZN;p4VAX11Yw?~s6MD3+nx zor{0u9_# + + + + Apache JMeter + Apache JMeter + + + + diff --git a/ApacheJmeter/xdocs/stylesheets/project.xml b/ApacheJmeter/xdocs/stylesheets/project.xml new file mode 100644 index 0000000..098b423 --- /dev/null +++ b/ApacheJmeter/xdocs/stylesheets/project.xml @@ -0,0 +1,66 @@ + + + + + Apache JMeter + + Apache JMeter + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApacheJmeter/xdocs/stylesheets/site.vsl b/ApacheJmeter/xdocs/stylesheets/site.vsl new file mode 100644 index 0000000..ddccf1d --- /dev/null +++ b/ApacheJmeter/xdocs/stylesheets/site.vsl @@ -0,0 +1,535 @@ + + + + + +## Defined variables +#set ($bodybg = "#ffffff") +#set ($bodyfg = "#000000") +#set ($bodylink = "#525D76") +#set ($bannerbg = "#525D76") +#set ($bannerfg = "#ffffff") +#set ($subbannerbg = "#828DA6") +#set ($subbannerfg = "#ffffff") +#set ($tablethbg = "#039acc") +#set ($tabletdbg = "#a0ddf0") +#set ($notebackground = "#bbbb00") +#set ($space = " ") +#set ($space = $space.charAt(0)) +#set ($udsc = "_") +#set ($udsc = $udsc.charAt(0)) +#set ($imgdir = "$relativePath/images") +#set ($sshotdir = "$imgdir/screenshots" ) +#set ($cssdir = "$relativePath/css") +#set ($year = $date.getYear()+1900) + + +#document() + + +## This is where the macros live + +#macro ( sectionlink $anchor) +#if($anchor)#end +#end + +#macro ( table $table) + +#foreach ( $items in $table.getChildren() ) +#if ($items.getName().equals("tr")) +#tr ($items) +#end +#end +
+#end + +#macro ( tr $tr) + +#foreach ( $items in $tr.getChildren() ) +#if ($items.getName().equals("td")) +#td ($items) +#elseif ($items.getName().equals("th")) +#th ($items) +#end +#end + +#end + +#macro ( td $value) +#if ($value.getAttributeValue("colspan")) +#set ($colspan = $value.getAttributeValue("colspan")) +#end +#if ($value.getAttributeValue("rowspan")) +#set ($rowspan = $value.getAttributeValue("rowspan")) +#end + + +#if ($value.getText().length() != 0 || $value.getChildren().size() > 0) +$value.content +#else +  +#end + + +#end + +#macro ( th $value) +#if ($value.getAttributeValue("colspan")) +#set ($colspan = $value.getAttributeValue("colspan")) +#end +#if ($value.getAttributeValue("rowspan")) +#set ($rowspan = $value.getAttributeValue("rowspan")) +#end + + +#if ($value.getText().length() != 0 || $value.getChildren().size() > 0) +$value.content +#else +  +#end + + +#end + +#macro ( projectanchor $name $value ) +#if ($value.startsWith("http://")) +$name +#elseif ($value.startsWith("/site")) +$name +#else +$name +#end +#end + +#macro ( metaauthor $author $email ) + + +#end + +#macro ( image $value ) +#if ($value.getAttributeValue("width")) +#set ($width=$value.getAttributeValue("width")) +#end +#if ($value.getAttributeValue("height")) +#set ($height=$value.getAttributeValue("height")) +#end +#if ($value.getAttributeValue("align")) +#set ($align=$value.getAttributeValue("align")) +#end + +#end + +#macro ( source $value) +
+ + + + + + + + + + + + + + + + +
$escape.getText($value.getText())
+
+#end + +#macro (properties $properties) +

+Parameters +#if ($properties.getParent().getName() == 'component') +#set ($name = $properties.getParent().getAttributeValue("name").replace($space,$udsc)) +#set ($suff = "_parms") + +#sectionlink ("$name$suff") +#end + + +#foreach ($items in $properties.getChildren("property")) + + + + + +#end +
AttributeDescriptionRequired
$items.getAttributeValue("name")#runloop($items) +#if("$!items.getAttributeValue('required')" != "") +$items.getAttributeValue("required") +#else +No +#end +
+

+#end + +#macro (seeAlso $seeAlso) +

See Also: +

+

+#end + +#macro (figure $figure) +#set ($width = "") +#set ($width = $figure.getAttributeValue('width') ) +#set ($height = "") +#set ($height = $figure.getAttributeValue('height') ) +#set ($dim= "") +#if ("$!width" != "") +#set ($dim = "width='$width' height='$height'") +#end +


+#runloop($figure)

+#end + +#macro (example $example) + +#sectionlink ($example.getAttributeValue("anchor")) +

$example.getAttributeValue("title")

+#runloop($example) +#end + +#macro (note $note) +

+ + +
#runloop($note)
+

+#end + +#macro (scope $scope) +#if ($scope.getText() == "") +
scope +#else +$scope.getText() +#end +#end +## +#macro ( bugzilla $id) +Bug $id.getText() +#end + +#macro (unknown $u_node) +#if($u_node.getName() == "note") +#note($u_node) +#elseif($u_node.getName() == "complink") +#complink($u_node) +#elseif($u_node.getName() == "figure") +#figure($u_node) +#elseif ($u_node.getName() == "links") +#seeAlso ($u_node) +#elseif ($u_node.getName() == "properties") +#properties ($u_node) +#elseif ($u_node.getName() == "example") +#example ($u_node) +#elseif ($u_node.getName().equals("source")) +#source ($u_node) +#elseif ($u_node.getName().equals("table")) +#table ($u_node) +#elseif ($u_node.getName().equals("component")) +#component($u_node) +#elseif ($u_node.getName().equals("subsection")) +#subsection ($u_node) +#elseif ($u_node.getName().equals("scope")) +#scope ($u_node) +#elseif ($u_node.getName().equals("bugzilla")) +#bugzilla ($u_node) +#else +#outputTag($u_node) +#runloop($u_node) +#outputEndTag($u_node) +#end +#end + +#macro (complink $complink) +$complink.getAttributeValue("name") +#end + +#macro (outputTag $tag) +<$tag.getName()#getAtts($tag)> +#end + +#macro (getAtts $tag) +#foreach ($att in $tag.getAttributes()) $att.getName()="$att.getValue()"#end +#end + +#macro (outputEndTag $tag) + +#end + +#macro (runloop $itemToLoop) +#foreach ($rl_node in $itemToLoop.getContent()) +#if($rl_node.getClass().getName().indexOf("Element") > -1) +#unknown($rl_node) +#else +$rl_node.getText() +#end +#end +#end + +#macro ( component $component) +#set ($screenshot = "") +#set ($screenshot = $component.getAttributeValue('screenshot') ) +#set ($width = "") +#set ($width = $component.getAttributeValue('width') ) +#set ($height = "") +#set ($height = $component.getAttributeValue('height') ) +#set ($dim= "") +#if ("$!width" != "") +#set ($dim = "width='$width' height='$height'") +#end + + +#if($component.getAttribute("useinstead")) + +#end + + +
+ +

+$!component.getAttributeValue("index") $component.getAttributeValue("name") +#sectionlink ($component.getAttributeValue("name")) +

+
+
*** This element is deprecated. Use $component.getAttributeValue("useinstead") instead ***
+#foreach ( $c_items in $component.getChildren() ) +#if ($c_items.getName().equals("description")) +#runloop($c_items) +#if ("$!screenshot" != "") +

Control Panel

+
+#end +#else +#unknown($c_items) +#end +#end +

+
+#end + +#macro ( subsection $subsection) + + + + +
+ +$subsection.getAttributeValue("name") +#sectionlink ($subsection.getAttributeValue("anchor")) + +
+
+#foreach ( $su_items in $subsection.getChildren() ) +#if ($su_items.getName().equals("img")) +#image ($su_items) +#else +#unknown($su_items) +#end +#end +
+

+#end + +#macro (pagelinks) +#if (("$!next" != "") || ("$!prev" != "")) + + + +#if ("$!next" != "") + +#end +#if ("$!prev" != "") + +#end + +
+ + + + + +
+#end +#end + +#macro ( section $section) + + + + +
+ +#set ($anchor = $section.getAttributeValue("anchor")) +#if($anchor)#end$section.getAttributeValue("name")#if($anchor)#sectionlink ($anchor)#end + +
+
+#foreach ( $s_items in $section.getChildren() ) +#if ($s_items.getName().equals("img")) +#image ($s_items) +#else +#unknown($s_items) +#end +#end +
+

+

+#end + +#macro ( makeProject ) +#set ($menus = $project.getChild("body").getChildren("menu")) +#foreach ( $menu in $menus ) +

$menu.getAttributeValue("name")

+
    +#foreach ( $item in $menu.getChildren() ) +#set ($name = $item.getAttributeValue("name")) +
  • #projectanchor($name $item.getAttributeValue("href"))
  • +#end +
+#end +#end + +#macro (makeIndex $subsections) + +#end + +#macro (getProjectImage) +#if ($project.getChild("logo")) +#set ( $logoString = $project.getChild("logo").getAttributeValue("href") ) +#set ( $logoHeight = $project.getChild("logo").getAttributeValue("height") ) +#set ( $logoWidth = $project.getChild("logo").getAttributeValue("width") ) + + + + + + + + +#set ( $logoString = $project.getChild("logo").getAttributeValue("href") ) +#set ( $logoHeight = $project.getChild("logo").getAttributeValue("height") ) +#set ( $logoWidth = $project.getChild("logo").getAttributeValue("width") ) +#if ( $logoString.startsWith("/") ) +$project.getChild( +#else +$project.getChild( +#end + +#else + + + +#end +#end + +#macro (document) + + + + + + + + +#set ($next = "") +#set ($next = $root.getAttributeValue("next")) +#set ($prev = "") +#set ($prev = $root.getAttributeValue("prev")) + +#set ($authors = $root.getChild("properties").getChildren("author")) +#foreach ( $au in $authors ) +#metaauthor ( $au.getText() $au.getAttributeValue("email") ) +#end + +$project.getChild("title").getText() - $root.getChild("properties").getChild("title").getText() + + + + + + +#getProjectImage() + +
+ + + + + + + + + + +
+
+
+#makeProject() + +#pagelinks() +
+#if ($root.getAttributeValue("index") == "yes") +#makeIndex($root.getChild("body").getChildren("section")) +#end +#set ($allSections = $root.getChild("body").getChildren("section")) +#foreach ( $section in $allSections ) +#section ($section) +#end +#pagelinks() +
+
+
+
+
+Copyright © 1999-$year, Apache Software Foundation +
+
+
+Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
+
+ + +#end diff --git a/ApacheJmeter/xdocs/stylesheets/site.xsl b/ApacheJmeter/xdocs/stylesheets/site.xsl new file mode 100644 index 0000000..3c97f7a --- /dev/null +++ b/ApacheJmeter/xdocs/stylesheets/site.xsl @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:value-of select="$project/title"/> - <xsl:value-of select="properties/title"/> + + + + + + + + + + + + + + + + + PAGE HEADER + + + HEADER SEPARATOR + + + + + + + LEFT SIDE NAVIGATION + + + RIGHT SIDE MAIN BODY + + + + + FOOTER SEPARATOR + + + + + PAGE FOOTER + + +
+ + JAKARTA LOGO + + The Jakarta Project + + + + + + + + + + + + + PROJECT LOGO + + {$alt} + + + +
+
+
+ + + +
+
+
+
+ Copyright © 1999-2001, Apache Software Foundation +
+
+ + + +
+ + + + +

+
    + +
+
+ + + + + + + +
  • +
    + + + + + + + + + + + + +
    + + + +
    + +
    +
    + + + + + + + + + + + + +
    + + + +
    + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + +
    + + + + + +
    + +
    +				
    +			 
    + +
    + + + + + +
    +
    +
    + + + + + + + + + +
    diff --git a/ApacheJmeter/xdocs/stylesheets/site_printable.vsl b/ApacheJmeter/xdocs/stylesheets/site_printable.vsl new file mode 100644 index 0000000..1ca1d7e --- /dev/null +++ b/ApacheJmeter/xdocs/stylesheets/site_printable.vsl @@ -0,0 +1,549 @@ + + +## +## Content Stylesheet for Site +## +## Java cannot currently handle HTML tags of the form , e.g.
    ; +## the trailing > characters are output to the display, which messes it up. +## Java does handle

    OK, but as the output from this stylesheet +## does not have to be XHTML, for simplicity the trailing slashes have been removed. +## +## +## Defined variables +#set ($bodybg = "#ffffff") +#set ($bodyfg = "#000000") +#set ($bodylink = "#525D76") +#set ($bannerbg = "#525D76") +#set ($bannerfg = "#ffffff") +#set ($subbannerbg = "#828DA6") +#set ($subbannerfg = "#ffffff") +#set ($tablethbg = "#039acc") +#set ($tabletdbg = "#a0ddf0") +#set ($notebackground = "#bbbb00") +#set ($space = " ") +#set ($space = $space.charAt(0)) +#set ($udsc = "_") +#set ($udsc = $udsc.charAt(0)) +#set ($imgdir = "$relativePath/../docs/images") +#set ($cssdir = "$relativePath/../docs/css") +#set ($sshotdir = "$imgdir/screenshots") +#set ($year = $date.getYear()+1900) + +#document() + +## This is where the macro's live + +#macro ( table $table) + +#foreach ( $items in $table.getChildren() ) +#if ($items.getName().equals("tr")) +#tr ($items) +#end +#end +
    +#end + +#macro ( tr $tr) + +#foreach ( $items in $tr.getChildren() ) +#if ($items.getName().equals("td")) +#td ($items) +#elseif ($items.getName().equals("th")) +#th ($items) +#end +#end + +#end + +#macro ( td $value) +#if ($value.getAttributeValue("colspan")) +#set ($colspan = $value.getAttributeValue("colspan")) +#end +#if ($value.getAttributeValue("rowspan")) +#set ($rowspan = $value.getAttributeValue("rowspan")) +#end + + +#if ($value.getText().length() != 0 || $value.getChildren().size() > 0) +$value.content +#else +  +#end + + +#end + +#macro ( th $value) +#set ($colspan = $value.getAttributeValue("colspan")) +#set ($rowspan = $value.getAttributeValue("rowspan")) + + +#if ($value.getText().length() != 0 || $value.getChildren().size() > 0) +$value.content +#else +  +#end + + +#end + +#macro ( projectanchor $name $value ) +#if ($value.startsWith("http://")) +$name +#elseif ($value.startsWith("/site")) +$name +#else +$name +#end +#end + +#macro ( metaauthor $author $email ) + + +#end + +#macro ( image $value ) +#if ($value.getAttributeValue("width")) +#set ($width=$value.getAttributeValue("width")) +#end +#if ($value.getAttributeValue("height")) +#set ($height=$value.getAttributeValue("height")) +#end +#if ($value.getAttributeValue("align")) +#set ($align=$value.getAttributeValue("align")) +#end + +#end + +#macro ( source $value) +
    + + + + + + + + + + + + + + + + +
    $escape.getText($value.getText())
    +
    +#end + +#macro (properties $properties) +

    +Parameters + + +#foreach ($items in $properties.getChildren("property")) + + + + + +#end +
    AttributeDescriptionRequired
    $items.getAttributeValue("name")#runloop($items) +#if("$!items.getAttributeValue('required')" != "") +$items.getAttributeValue("required") +#else +No +#end +
    +

    +#end + +#macro (seeAlso $seeAlso) +

    See Also: +

      +#foreach ($items in $seeAlso.getChildren()) +#if($items.getName() == "link") +
    • $xmlout.outputString($items,true)
    • +#elseif($items.getName() == "complink") +
    • #complink($items)
    • +#end +#end +
    +

    +#end + +#macro (figure $figure) +#set ($width = "") +#set ($width = $figure.getAttributeValue('width') ) +#set ($height = "") +#set ($height = $figure.getAttributeValue('height') ) +#set ($dim= "") +#if ("$!width" != "") +#set ($dim = "width='$width' height='$height'") +#end +


    +#runloop($figure)

    +#end + +#macro (example $example) + +

    $example.getAttributeValue("title")

    +#runloop($example) +#end + +#macro (note $note) +

    + +
    #runloop($note)

    +#end + +#macro (scope $scope) +#if ($scope.getText() == "") +
    scope +#else +$scope.getText() +#end +#end + +#macro ( bugzilla $id) +Bug $id.getText() +#end + +#macro (unknown $node) +#if($node.getName() == "note") +#note($node) +#elseif($node.getName() == "complink") +#complink($node) +#elseif($node.getName() == "figure") +#figure($node) +#elseif ($node.getName() == "links") +#seeAlso ($node) +#elseif ($node.getName() == "properties") +#properties ($node) +#elseif ($node.getName() == "example") +#example ($node) +#elseif ($node.getName().equals("source")) +#source ($node) +#elseif ($node.getName().equals("table")) +#table ($node) +#elseif ($node.getName().equals("component")) +#component($node) +#elseif ($node.getName().equals("subsection")) +#subsection ($node) +#elseif ($node.getName().equals("scope")) +#scope ($node) +#elseif ($node.getName().equals("bugzilla")) +#bugzilla ($node) +#else +#outputTag($node) +#runloop($node) +#outputEndTag($node) +#end +#end + +#macro (complink $complink) +$complink.getAttributeValue("name") +#end + +#macro (outputTag $tag) +<$tag.getName()#getAtts($tag)> +#end + +#macro (getAtts $tag) +#foreach ($att in $tag.getAttributes()) $att.getName()="$att.getValue()"#end +#end + +#macro (outputEndTag $tag) + +#end + +#macro (runloop $itemToLoop) +#foreach ($node in $itemToLoop.getContent()) +#if($node.getClass().getName().indexOf("Element") > -1) +#unknown($node) +#else +$node.getText() +#end +#end +#end + +#macro ( component $component) +#set ($width = "") +#set ($width = $component.getAttributeValue('width') ) +#set ($height = "") +#set ($height = $component.getAttributeValue('height') ) +#set ($dim= "") +#if ("$!width" != "") +#set ($dim = "width='$width' height='$height'") +#end +#set ($screenshot = "") +#set ($screenshot = $component.getAttributeValue('screenshot') ) + + +#if($component.getAttribute("useinstead")) + +#end + + +
    + +#set ($tag = "") +#set ($tag = $component.getAttributeValue("tag")) +#if ("$!tag" != "") + +#end +

    $!component.getAttributeValue("index") $component.getAttributeValue("name")

    +
    +
    *** This element is deprecated. Use $component.getAttributeValue("useinstead") instead ***
    +#foreach ( $items in $component.getChildren() ) +#if ($items.getName().equals("description")) +#runloop($items) +#if ("$!screenshot" != "") +

    Control Panel

    +
    +#end +#else +#unknown($items) +#end +#end +

    +
    +#end + +#macro ( subsection $subsection) + + + + +
    + +$subsection.getAttributeValue("name") + +
    +
    +#foreach ( $items in $subsection.getChildren() ) +#if ($items.getName().equals("img")) +#image ($items) +#else +#unknown($items) +#end +#end +
    +

    +#end + +#macro (pagelinks) +#if (("$!next" != "") || ("$!prev" != "")) + + + +#if ("$!next" != "") + +#end +#if ("$!prev" != "") + +#end + +
    + + + + + +
    +#end +#end + +#macro ( section $section) + + + + +
    + +#set ($anchor = $section.getAttributeValue("anchor")) +#if($anchor)#end$section.getAttributeValue("name")#if($anchor)#end + +
    +
    +#foreach ( $items in $section.getChildren() ) +#if ($items.getName().equals("img")) +#image ($items) +#else +#unknown($items) +#end +#end +
    +

    +

    +#end + +#macro ( makeProject ) +#set ($menus = $project.getChild("body").getChildren("menu")) +#foreach ( $menu in $menus ) +

    $menu.getAttributeValue("name")

    +
      +#foreach ( $item in $menu.getChildren() ) +#set ($name = $item.getAttributeValue("name")) +
    • #projectanchor($name $item.getAttributeValue("href"))
    • +#end +
    +#end +#end + +#macro (makeIndex $subsections) +#set ($level2 = $root.getAttributeValue("index-level-2")) +## Should we display numbers for index level 2 ? (useful for checking numbering) +#set ($index2 = $root.getAttributeValue("index-numbers")) +#set ($colbreak = $root.getAttributeValue("colbreak")) +#if ("$!colbreak" != "") + + +
    +#end +
      +#foreach ($sect in $subsections) +#if (("$!colbreak" != "") && ($sect.getAttributeValue("name").startsWith("$colbreak"))) +
    +#if ("$!colbreak" != "") +
    +#end +#end + +#macro (getProjectImage) +#if ($project.getChild("logo")) + + + + +#set ( $logoHeight = $project.getChild("logo").getAttributeValue("height") ) +#set ( $logoWidth = $project.getChild("logo").getAttributeValue("width") ) +#set ( $logoString = $project.getChild("logo").getAttributeValue("href") ) +#if ( $logoString.startsWith("/") ) +$project.getChild( +#else +$project.getChild( +#end + +#else + + + +#end +#end + +#macro (document) +## ====================================================================== +## Main Page Section --> +## ====================================================================== + + + + +#set ($authors = $root.getChild("properties").getChildren("author")) +#foreach ( $au in $authors ) +#metaauthor ( $au.getText() $au.getAttributeValue("email") ) +#end +#set ($next = "") +#set ($next = $root.getAttributeValue("next")) +#set ($prev = "") +#set ($prev = $root.getAttributeValue("prev")) +#set ($id = "") +#set ($id = $root.getAttributeValue("id")) +$project.getChild("title").getText() - $root.getChild("properties").getChild("title").getText() + + + +## + +#getProjectImage() + +
    + + + + + + + + + +
    +
    +
    +#pagelinks() +
    +#if ($root.getAttributeValue("index") == "yes") +#makeIndex($root.getChild("body").getChildren("section")) +#end +#set ($allSections = $root.getChild("body").getChildren("section")) +#foreach ( $section in $allSections ) +#section ($section) +#end +
    +#pagelinks() +
    +
    +
    + + +#if ("$!id" != "") + +#if ("$!id" != "") + +#end + + +
    +#else + +#end + +Copyright © 1999-$year, Apache Software Foundation + + + +$id + +
    +
    +Apache, Apache JMeter, JMeter, the Apache feather, and the Apache JMeter logo are +trademarks of the Apache Software Foundation. + +
    +
    +
    + + +#end \ No newline at end of file diff --git a/ApacheJmeter/xdocs/svnindex.xml b/ApacheJmeter/xdocs/svnindex.xml new file mode 100644 index 0000000..0edb66a --- /dev/null +++ b/ApacheJmeter/xdocs/svnindex.xml @@ -0,0 +1,73 @@ + + + + + + Apache JMeter Project + Source Repositories + + + + +
    + +

    Most users of the source code probably don't need to have day to +day access to the source code as it changes. For these users we +provide easy to unpack source code downloads via our download page.

    + +
    + +
    + +

    For information on connecting to the ASF Subversion repositories, see the version control +page.

    + + +

    Modules available for access are listed below.

    + + + +

    Subversion is an open-source version control system. The root url of the +ASF Subversion repository is http://svn.apache.org/repos/asf/ for non-committers and https://svn.apache.org/repos/asf/ for committers.

    + +

    NOTE: +When checking out a subproject using Subversion, ensure that you are checking out a tag, a branch or trunk (the main-line) and not all tags and branches to avoid filling up your hard-disk and wasting bandwidth.

    + + + + + + + + + + + + + + +
    Projecthttp (read-only)https (committers)View-SVN
    Apache JMeterhttp://svn.apache.org/repos/asf/jmeter/trunkhttps://svn.apache.org/repos/asf/jmeter/trunkhttp://svn.apache.org/viewcvs.cgi/jmeter/
    + +
    + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/best-practices.xml b/ApacheJmeter/xdocs/usermanual/best-practices.xml new file mode 100644 index 0000000..4ecefbc --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/best-practices.xml @@ -0,0 +1,317 @@ + + + + +]> + + + + + User's Manual: Best Practices + + + + +
    +
    + +
    +

    Your hardware's capabilities will limit the number of threads you can effectively +run with JMeter. It will also depend on how fast your server is (a faster server +gives makes JMeter work harder since it returns request quicker). The more +JMeter works, the less accurate its timing information will be. The more work +JMeter does, the more each thread has to wait to get access to the CPU, the more +inflated the timing information gets. If you need large-scale load testing, consider +running multiple non-GUI JMeter instances on multiple machines.

    +
    + +
    +

    See Building a Web Test +for information.

    +
    + +
    +

    See Building an Advanced +Web Test for information.

    +
    + +
    +

    Refer to for details on setting up the proxy +server. The most important thing to do is filter out all requests you aren't +interested in. For instance, there's no point in recording image requests (JMeter can +be instructed to download all images on a page - see ). +These will just clutter your test plan. Most likely, there is an extension all your files +share, such as .jsp, .asp, .php, .html or the like. These you should "include" by +entering ".*\.jsp" as an "Include Pattern".

    +

    Alternatively, you can exclude images by entering ".*\.gif" as an "Exclude Pattern". +Depending on your application, this may or may not be a better way to go. You may +also have to exclude stylesheets, javascript files, and other included files. Test +out your settings to verify you are recording what you want, and then erase and start +fresh.

    + +

    The Proxy Server expects to find a ThreadGroup element with a Recording Controller +under it where it will record HTTP Requests to. This conveniently packages all your samples under one +controller, which can be given a name that describes the test case.

    +

    Now, go through the steps of a Test Case. If you have no pre-defined test cases, use +JMeter to record your actions to define your test cases. Once you have finished a +definite series of steps, save the entire test case in an appropriately named file. Then, wipe +clean and start a new test case. By doing this, you can quickly record a large number of +test case "rough drafts".

    +

    One of the most useful features of the Proxy Server is that you can abstract out +certain common elements from the recorded samples. By defining some +user-defined variables at the Test Plan level or in + elements, you can have JMeter automatically +replace values in you recorded samples. For instance, if you are testing an app on +server "xxx.example.com", then you can define a variable called "server" with the value of +"xxx.example.com", and anyplace that value is found in your recorded samples will be replaced +with "${server}". + +Please note that matching is case-sensitive. + +

    +

    +If JMeter does not record any samples, check that the brower really is using the proxy. +If the browser works OK even if JMeter is not running, then the browser cannot be using the proxy. +Some browsers ignore proxy settings for localhost or 127.0.0.1; try using the local hostname or IP instead. +

    +

    +The error "unknown_ca" probably means that you are trying to record HTTPS, and the browser has not accepted the +JMeter Proxy server certificate. +

    + + +
    + +
    +

    +Some test plans need to use different values for different users/threads. +For example, you might want to test a sequence that requires a unique login for each user. +This is easy to achieve with the facilities provided by JMeter. +

    +

    For example:

    +
      +
    • Create a text file containing the user names and passwords, separated by commas. +Put this in the same directory as your test plan. +
    • +
    • +Add a CSV DataSet configuration element to the test plan. +Name the variables USER and PASS. +
    • +
    • +Replace the login name with ${USER} and the password with ${PASS} on the appropriate +samplers +
    • +
    +

    The CSV Data Set element will read a new line for each thread. +

    +
    + +
    +

    +Some suggestions on reducing resource usage. +

    +
      +
    • Use non-GUI mode: jmeter -n -t test.jmx -l test.jtl
    • +
    • Use as few Listeners as possible; if using the -l flag as above they can all be deleted or disabled.
    • +
    • Rather than using lots of similar samplers, +use the same sampler in a loop, and use variables (CSV Data Set) to vary the sample. +Or perhaps use the Access Log Sampler. +[The Include Controller does not help here, as it adds all the test elements in the file to the test plan.] +
    • +
    • Don't use functional mode
    • +
    • Use CSV output rather than XML
    • +
    • Only save the data that you need
    • +
    • Use as few Assertions as possible
    • +
    +

    +If your test needs large amounts of data - particularly if it needs to be randomised - create the test data in a file +that can be read with CSV Dataset. This avoids wasting resources at run-time. +

    +
    + +
    +

    +The BeanShell interpreter has a very useful feature - it can act as a server, +which is accessible by telnet or http. +

    + +There is no security. Anyone who can connect to the port can issue any BeanShell commands. +These can provide unrestricted access to the JMeter application and the host. +Do not enable the server unless the ports are protected against access, e.g. by a firewall. + +

    +If you do wish to use the server, define the following in jmeter.properties: +

    +
    +beanshell.server.port=9000
    +beanshell.server.file=../extras/startup.bsh
    +
    +

    +In the above example, the server will be started, and will listen on ports 9000 and 9001. +Port 9000 will be used for http access. Port 9001 will be used for telnet access. +The startup.bsh file will be processed by the server, and can be used to define various functions and set up variables. +The startup file defines methods for setting and printing JMeter and system properties. +This is what you should see in the JMeter console: +

    +
    +Startup script running
    +Startup script completed
    +Httpd started on port: 9000
    +Sessiond started on port: 9001
    +
    +

    +As a practical example, assume you have a long-running JMeter test running in non-GUI mode, +and you want to vary the throughput at various times during the test. +The test-plan includes a Constant Throughput Timer which is defined in terms of a property, +e.g. ${__P(throughput)}. +The following BeanShell commands could be used to change the test: +

    +
    +printprop("throughput");
    +curr=Integer.decode(args[0]); // Start value
    +inc=Integer.decode(args[1]);  // Increment
    +end=Integer.decode(args[2]);  // Final value
    +secs=Integer.decode(args[3]); // Wait between changes
    +while(curr <= end){
    +  setprop("throughput",curr.toString()); // Needs to be a string here
    +  Thread.sleep(secs*1000);
    +  curr += inc;
    +}
    +printprop("throughput");
    +
    +

    The script can be stored in a file (throughput.bsh, say), and sent to the server using bshclient.jar. +For example: +

    +
    +java -jar ../lib/bshclient.jar localhost 9000 throughput.bsh 70 5 100 60
    +
    +
    + +
    + +

    +Each BeanShell test element has its own copy of the interpreter (for each thread). +If the test element is repeatedly called, e.g. within a loop, then the interpreter is retained +between invocations unless the "Reset bsh.Interpreter before each call" option is selected. +

    +

    +Some long-running tests may cause the interpreter to use lots of memory; if this is the case try using the reset option. +

    +

    +You can test BeanShell scripts outside JMeter by using the command-line interpreter: +

    +$ java -cp bsh-xxx.jar[;other jars as needed] bsh.Interperter file.bsh [parameters]
    +or
    +$ java -cp bsh-xxx.jar bsh.Interperter
    +bsh% source("file.bsh");
    +bsh% exit(); // or use EOF key (e.g. ^Z or ^D)
    +
    +

    +
    + +

    +Variables can be defined in startup (initialisation) scripts. +These will be retained across invocations of the test element, unless the reset option is used.\ +

    +

    +Scripts can also access JMeter variables using the get() and put() methods of the "vars" variable, +for example: vars.get("HOST"); vars.put("MSG","Successful");. +The get() and put() methods only support variables with String values, +but there are also getObject() and putObject() methods which can be used for arbitrary objects. +JMeter variables are local to a thread, but can be used by all test elements (not just Beanshell). +

    +

    +If you need to share variables between threads, then JMeter properties can be used: +

    +import org.apache.jmeter.util.JMeterUtils;
    +String value=JMeterUtils.getPropDefault("name","");
    +JMeterUtils.setProperty("name", "value");
    +
    +The sample .bshrc files contain sample definitions of getprop() and setprop() methods. +

    +

    +Another possible method of sharing variables is to use the "bsh.shared" shared namespace. +For example: +

    +if (bsh.shared.myObj == void){
    +    // not yet defined, so create it:
    +    myObj=new AnyObject();
    +}
    +bsh.shared.myObj.process();
    +
    +Rather than creating the object in the test element, it can be created in the startup file +defined by the JMeter property "beanshell.init.file". This is only processed once. +

    +
    +
    + +
    +

    +It's quite hard to write and test scripts as functions. +However, JMeter has the BSF (and BeanShell) samplers which can be used instead. +

    +

    +Create a simple Test Plan containing the BSF Sampler and Tree View Listener. +Code the script in the sampler script pane, and test it by running the test. +If there are any errors, these will show up in the Tree View. +Also the result of running the script will show up as the response. +

    +

    +Once the script is working properly, it can be stored as a variable on the Test Plan. +The script variable can then be used to create the function call. +For example, suppose a BeanShell script is stored in the variable RANDOM_NAME. +The function call can then be coded as ${__BeanShell(${RANDOM_NAME})}. +There is no need to escape any commas in the script, +because the function call is parsed before the variable's value is interpolated. +

    +
    + +
    +

    +Often it is useful to be able to re-run the same test with different settings. +For example, changing the number of threads or loops, or changing a hostname. +

    +

    +One way to do this is to define a set of variables on the Test Plan, and then use those variables in the test elements. +For example, one could define the variable LOOPS=10, and refer to that in the Thread Group as ${LOOPS}. +To run the test with 20 loops, just change the value of the LOOPS variable on the Test Plan. +

    +

    +This quickly becomes tedious if you want to run lots of tests in non-GUI mode. +One solution to this is to define the Test Plan variable in terms of a property, +for example LOOPS=${__P(loops,10))}. +This uses the value of the property "loops", defaulting to 10 if the property is not found. +The "loops" property can then be defined on the JMeter command-line: +jmeter ... -Jloops=12 .... +If there are a lot of properties that need to be changed together, +then one way to achieve this is to use a set of property files. +The appropriate property file can be passed in to JMeter using the -q command-line option. +

    +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/boss.xml b/ApacheJmeter/xdocs/usermanual/boss.xml new file mode 100644 index 0000000..9ba8839 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/boss.xml @@ -0,0 +1,217 @@ + + + + +]> + + + + + Martin Ramshaw + User's Manual: My boss wants me to... + + + + +
    +

    This is a fairly open-ended proposition. There are a number of questions to +be asked first, and additionally a number of resources that will be needed. You +will need some hardware to run the benchmarks/load-tests from. A number of +tools will prove useful. There are a number of products to consider. And finally, +why is Java a good choice to implement a load-testing/Benchmarking product. +

    + +

    What is our anticipated average number of users (normal load) ? +

    +

    What is our anticipated peak number of users ? +

    +

    When is a good time to load-test our application (i.e. off-hours or week-ends), +bearing in mind that this may very well crash one or more of our servers ? +

    +

    Does our application have state ? If so, how does our application manage it +(cookies, session-rewriting, or some other method) ? +

    +

    What is the testing intended to achieve?

    +
    + +

    The following resources will prove very helpful. Bear in mind that if you +cannot locate these resources, you will become these resources. As you +already have your work cut out for you, it is worth knowing who the following +people are, so that you can ask them for help if you need it. +

    + +

    Who knows our network topology ? If you run into any firewall or + proxy issues, this will become very important. As well, a private + testing network (which will therefore have very low network latency) + would be a very nice thing. Knowing who can set one up for you + (if you feel that this is necessary) will be very useful. If the + application doesn't scale as expected, who can add additional + hardware ? +

    +
    + +

    Who knows how our application functions ? The normal sequence is +

      +
    • test (low-volume - can we benchmark our application?)
    • +
    • benchmark (the average number of users)
    • +
    • load-test (the maximum number of users)
    • +
    • test destructively (what is our hard limit?)
    • +
    + The test process may progress from black-box testing to + white-box testing (the difference is that the first requires + no knowledge of the application [it is treated as a "black box"] + while the second requires some knowledge of the application). + It is not uncommon to discover problems with the application + during this process, so be prepared to defend your work. +

    +
    +
    + +

    This should be a widely-used piece of hardware, with a standard +(i.e. vanilla) software installation. Remember, if you publish your results, +the first thing your clients will do is hire a graduate student to verify them. +You might as well make it as easy for this person as you possibly can. +

    +

    For Windows, Windows XP Professional should be a minimum (the others +do not multi-thread past 50-60 connections, and you probably anticipate +more users than that). +

    +

    Good free platforms include the linuxes, the BSDs, and Solaris Intel. If +you have a little more money, there are commercial linuxes. +This may be worth it if you need the support. +

    +

    +For non-Windows platforms, investigate "ulimit -n unlimited" with a view to +including it in your user account startup scripts (.bashrc or .cshrc scripts +for the testing account). +

    +

    As you progress to larger-scale benchmarks/load-tests, this platform +will become the limiting factor. So it's worth using the best hardware and +software that you have available. Remember to include the hardware/software +configuration in your published benchmarks. +

    +

    Don't forget JMeter batch mode. This can be useful if you have a powerful server +that supports Java but perhaps does not have a fast graphics implementation, +or where you need to login remotely. +Batch (non-GUI) mode can reduce the network traffic compared with using a remote display or client-server mode. +The batch log file can then be loaded into JMeter on a workstation for analysis, or you can +use CSV output and import the data into a spreadsheet.

    +
    + +

    The following tools will all prove useful. It is definitely worthwhile to +become familiar with them. This should include trying them out, and reading the +appropriate documentation (man-pages, info-files, application --help messages, +and any supplied documentation). +

    + +

    + This can be used to establish whether or not you can reach your + target site. Options can be specified so that 'ping' provides the + same type of route reporting as 'traceroute'. +

    +
    + +

    + While the user will normally use a human-readable internet + address, you may wish to avoid the overhead of DNS lookups when + performing benchmarking/load-testing. These can be used to determine + the unique address (dotted quad) of your target site. +

    +
    + +

    + If you cannot "ping" your target site, this may be used to determine + the problem (possibly a firewall or a proxy). It can also be used + to estimate the overall network latency (running locally should give + the lowest possible network latency - remember that your users will + be running over a possibly busy internet). Generally, the fewer hops + the better. +

    +
    +
    + +

    There are a number of commercial products, which generally have fairly +hefty pricetags. If you can justify it, these are probably the way to go. +If, however, these products do not do exactly what you want, or you are on a +limited budget, the following are worth a look. In fact, you should probably +start by trying the Apache ab tool, as it may very well do the job +if your requirements are not particularly complicated. +

    + +

    + You should definitely start with this one. It handles HTTP 'get' requests + very well, and can be made to handle HTTP 'post' requests with a little + effort. Written in 'C', it performs very well, and offers good (if basic) + performance reporting. +

    +
    + +

    + This is worth a look. It is a library (and therefore of more interest to + developers) that can be used to perform HTTP tests/benchmarks. It is + intended to be used instead of a web browser (therefore no GUI) in + conjunction with JUnit. +

    +
    + +

    + This is definitely worth a look. It has an excellent user interface + but it may not do exactly what you want. If this is the case, be aware + that the functionality of this product is not likely to change. +

    +
    + +

    + If you have non-standard requirements, then this solution offers an + open-source community to provide them (of course, if you are reading + this, you are probably already committed to this one). This + product is free to evolve along with your requirements. +

    +
    +
    + +

    Why not Perl or C ? +

    +

    Well, Perl might be a very good choice except that the Benchmark package +seems to give fairly fuzzy results. Also, simulating multiple users with +Perl is a tricky proposition (multiple connections can be simulated by forking +many processes from a shell script, but these will not be threads, they will +be processes). However, the Perl community is very large. If you find that +someone has already written something that seems useful, this could be a very +good solution. +

    +

    C, of course, is a very good choice (check out the Apache ab tool). +But be prepared to write all of the custom networking, threading, and state +management code that you will need to benchmark your application. +

    +

    Java gives you (for free) the custom networking, threading, and state +management code that you will need to benchmark your application. Java is +aware of HTTP, FTP, and HTTPS - as well as RMI, IIOP, and JDBC (not to mention +cookies, URL-encoding, and URL-rewriting). In addition Java gives you automatic +garbage-collection, and byte-code level security. +

    +

    And once Microsoft moves to a CLR (common language run-time) a Windows Java +solution will not be any slower than any other type of solution on the Windows +platform. +

    +
    +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/build-adv-web-test-plan.xml b/ApacheJmeter/xdocs/usermanual/build-adv-web-test-plan.xml new file mode 100644 index 0000000..1161230 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/build-adv-web-test-plan.xml @@ -0,0 +1,73 @@ + + + + +]> + + + + + User's Manual: Building an Advanced Web Test Plan + + + + +
    +

    In this section, you will learn how to create advanced +Test Plans to test a Web site.

    + +

    For an example of a basic Test Plan, see +Building a Web Test Plan.

    + +
    +

    If your web application uses URL rewriting rather than cookies to save session information, +then you'll need to do a bit of extra work to test your site.

    +

    To respond correctly to URL rewriting, JMeter needs to parse the HTML +received from the server and retrieve the unique session ID. Use the appropriate +to accomplish this. Simply enter the name of your session ID parameter into the modifier, and it +will find it and add it to each request. If the request already has a value, it will be replaced. +If "Cache Session Id?" is checked, then the last found session id will be saved, +and will be used if the previous HTTP sample does not contain a session id. +

    + + +

    Download this example. In Figure 1 is shown a +test plan using URL rewriting. Note that the URL Re-writing modifier is added to the SimpleController, +thus assuring that it will only affect requests under that SimpleController.

    +
    Figure 1 - Test Tree
    +

    In Figure 2, we see the URL Re-writing modifier GUI, which just has a field for the user to specify +the name of the session ID parameter. There is also a checkbox for indicating that the session ID should +be part of the path (separated by a ";"), rather than a request parameter

    +
    Figure 2 - Request parameters
    +
    +
    + +
    +

    The lets you customize what information +JMeter sends in the HTTP request header. This header includes properties like "User-Agent", +"Pragma", "Referer", etc.

    +

    The , like the , +should probably be added at the Thread Group level, unless for some reason you wish to +specify different headers for the different objects in +your test.

    + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/build-db-test-plan.xml b/ApacheJmeter/xdocs/usermanual/build-db-test-plan.xml new file mode 100644 index 0000000..720a91f --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/build-db-test-plan.xml @@ -0,0 +1,186 @@ + + + + +]> + + + + + Martin Ramshaw + User's Manual: Building a Database Test Plan + + + + +
    +

    In this section, you will learn how to create a basic +Test Plan to test a database server. +You will create ten users that send five SQL requests to the database server. +Also, you will tell the users to run their tests three times. So, the total number +of requests is (10 users) x (2 requests) x (repeat 3 times) = 60 JDBC requests. +To construct the Test Plan, you will use the following elements: +Thread Group, +, .

    + +This example uses the MySQL database driver. +To use this driver, its containing .jar file must be copied to the JMeter +lib directory (see JMeter's Classpath +for more details). + +
    + +
    +

    The first step you want to do with every JMeter Test Plan is to add a +Thread Group element. The Thread Group +tells JMeter the number of users you want to simulate, how often the users should +send requests, and the how many requests they should send.

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

    + +

    Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.1 +below)

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter JDBC Users.

    + +You will need a valid database, database table, and user-level access to that +table. In the example shown here, the database is 'mydb' and the table name is +'Stocks'. + +

    Next, increase the number of users to 10.

    + +

    In the next field, the Ramp-Up Period, leave the the default value of 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

    + +

    Finally, enter a value of 3 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + +In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + +

    See Figure §-num;.2 for the completed JDBC Users Thread Group.

    + +
    +Figure §-num;.2. JDBC Users Thread Group
    + +
    + +
    +

    Now that we have defined our users, it is time to define the tasks that they +will be performing. In this section, you will specify the JDBC requests to +perform.

    + +

    Begin by selecting the JDBC Users element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element --> JDBC Connection Configuration. +Then, select this new element to view its Control Panel (see Figure §-num;.3).

    + +

    Set up the following fields (these assume we will be using a local MySQL database called test):

    +
      +
    • Variable name bound to pool. This needs to uniquely identify the configuration. It is used by the JDBC Sampler to identify the configuration to be used.
    • +
    • Database URL: jdbc:mysql://localhost:3306/test
    • +
    • JDBC Driver class: com.mysql.jdbc.Driver
    • +
    • Username: guest
    • +
    • Password: password for guest
    • +
    +

    The other fields on the screen can be left as the defaults.

    +

    JMeter creates a database connection pool with the configuration settings as specified in the Control Panel. +The pool is referred to in JDBC Requests in the 'Variable Name' field. +Several different JDBC Configuration elements can be used, but they must have unique names. +Every JDBC Request must refer to a JDBC Configuration pool. +More than one JDBC Request can refer to the same pool. +

    +
    +Figure §-num;.3. JDBC Configuration
    + +

    Selecting the JDBC Users element again. Click your right mouse button +to get the Add menu, and then select Add --> Sampler --> JDBC Request. +Then, select this new element to view its Control Panel (see Figure §-num;.4).

    + +
    +Figure §-num;.4. JDBC Request
    + +

    In our Test Plan, we will make two JDBC requests. The first one is for +Eastman Kodak stock, and the second is Pfizer stock (obviously you should +change these to examples appropriate for your particular database). These +are illustrated below.

    + +JMeter sends requests in the order that you add them to the tree. + +

    Start by editing the following properties (see Figure §-num;.5): +

      +
    • Change the Name to "Kodak".
    • +
    • Enter the Pool Name: MySQL (same as in the configuration element)
    • +
    • Enter the SQL Query String field.
    • +
    +

    + +
    +Figure §-num;.5. JDBC Request for Eastman Kodak stock
    + +

    Next, add the second JDBC Request and edit the following properties (see +Figure §-num;.6): +

      +
    • Change the Name to "Pfizer".
    • +
    • Enter the SQL Query String field.
    • +
    +

    + +
    +Figure §-num;.6. JDBC Request for Pfizer stock
    + +
    + +
    +

    The final element you need to add to your Test Plan is a +Listener. This element is +responsible for storing all of the results of your JDBC requests in a file +and presenting a visual model of the data.

    + +

    Select the JDBC Users element and add a +listener (Add --> Listener --> Graph Results).

    + +
    +Figure §-num;.7. Graph results Listener
    + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/build-ftp-test-plan.xml b/ApacheJmeter/xdocs/usermanual/build-ftp-test-plan.xml new file mode 100644 index 0000000..d173185 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/build-ftp-test-plan.xml @@ -0,0 +1,196 @@ + + + + +]> + + + + + Martin Ramshaw + User's Manual: Building an FTP Test Plan + + + + +
    +

    In this section, you will learn how to create a basic +Test Plan to test an FTP site. You will +create four users that send requests for two files on the O'Reilly FTP site. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (4 users) x (2 requests) x (repeat 2 times) = 16 FTP requests. To +construct the Test Plan, you will use the following elements: +Thread Group, +, +, and +.

    + +This example uses the O'Reilly FTP site, www.oro.com. Please be considerate +when running this example, and (if possible) consider running against another +FTP site. + +
    + +
    +

    The first step you want to do with every JMeter Test Plan is to add a +Thread Group element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send.

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

    + +

    Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.1 +below)

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter O'Reilly Users.

    + +

    Next, increase the number of users to 4.

    + +

    In the next field, the Ramp-Up Period, leave the the default value of 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

    + +

    Finally, enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + +In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + +

    See Figure §-num;.2 for the completed O'Reilly Users Thread Group.

    + +
    +Figure §-num;.2. O'Reilly Users Thread Group
    + +
    + +
    +

    Now that we have defined our users, it is time define the tasks that they +will be performing. In this section, you will specify the default settings +for your FTP requests. And then, in section §-num;.3, you will add FTP Request +elements which use some of the default settings you specified here.

    + +

    Begin by selecting the O'Reilly Users element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element --> FTP Request +Defaults. Then, select this new element to view its Control Panel (see Figure §-num;.3). +

    + +
    +Figure §-num;.3. FTP Request Defaults
    + +

    +Like most JMeter elements, the Control +Panel has a name field that you can modify. In this example, leave this field with +the default value.

    + +

    Skip to the next field, which is the FTP Server's Server Name/IP. For the +Test Plan that you are building, all FTP requests will be sent to the same +FTP server, ftp.oro.com. Enter this domain name into the field. +This is the only field that we will specify a default, so leave the remaining +fields with their default values.

    + +The FTP Request Defaults element does not tell JMeter +to send an FTP request. It simply defines the default values that the +FTP Request elements use. + +

    See Figure §-num;.4 for the completed FTP Request Defaults element

    + +
    +Figure §-num;.4. FTP Defaults for our Test Plan
    + +
    + +
    + +

    In our Test Plan, we need to make two FTP requests. The first one is for the +O'Reilly mSQL Java README file (ftp://ftp.oro.com/pub/msql/java/README), and the +second is for the tutorial file (ftp://ftp.oro.com/pub/msql/java/tutorial.txt).

    + +JMeter sends requests in the order that they appear in the tree. + +

    Start by adding the first +to the O'Reilly Users element (Add --> Sampler --> FTP Request). +Then, select the FTP Request element in the tree and edit the following properties +(see Figure §-num;.5): +

      +
    1. Change the Name to "README".
    2. +
    3. Change the File to Retrieve From Server field to "pub/msql/java/README".
    4. +
    5. Change the Username field to "anonymous".
    6. +
    7. Change the Password field to "anonymous".
    8. +
    +

    + +You do not have to set the Server Name field because you already specified +this value in the FTP Request Defaults element. + +
    +Figure §-num;.5. FTP Request for O'Reilly mSQL Java README file
    + +

    Next, add the second FTP Request and edit the following properties (see +Figure §-num;.6: +

      +
    1. Change the Name to "tutorial".
    2. +
    3. Change the File to Retrieve From Server field to "pub/msql/java/tutorial.txt".
    4. +
    5. Change the Username field to "anonymous".
    6. +
    7. Change the Password field to "anonymous".
    8. +
    +

    + +
    +Figure §-num;.6. FTP Request for O'Reilly mSQL Java tutorial file
    + +
    + +
    +

    The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your FTP requests in a file and presenting +a visual model of the data.

    + +

    Select the O'Reilly Users element and add a +listener (Add --> Listener --> Spline Visualizer).

    + +
    +Figure §-num;.7. Spline Visualizer Listener
    + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/build-jms-point-to-point-test-plan.xml b/ApacheJmeter/xdocs/usermanual/build-jms-point-to-point-test-plan.xml new file mode 100644 index 0000000..2694ed5 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/build-jms-point-to-point-test-plan.xml @@ -0,0 +1,215 @@ + + + +]> + + + + + User's Manual: Building a JMS (Java Messaging Service) Point-to-Point Test Plan + + + + + +
    + + + Make sure the required jar files are in JMeter's lib directory. If they are not, shutdown JMeter, + copy the jar files over and restart JMeter. + See Getting Started for details. + + +

    In this section, you will learn how to create a + Test Plan to test a JMS Point-to-Point messaging solution. +The setup of the test is 1 threadgroup with 5 threads sending 4 messages each through a request queue. +A fixed reply queue will be used for monitoring the reply messages. +To construct the Test Plan, you will use the +following elements: + Thread Group, + , and + . +

    + +

    General notes on JMS: There are currently two JMS samplers. One uses JMS topics +and the other uses queues. Topic messages are commonly known as pub/sub messaging. +Topic messaging is generally used in cases where a message is published by a producer and +consumed by multiple subscribers. A JMS sampler needs the JMS implementation jar files; +for example, from Apache ActiveMQ. See here for the list +of jars provided by ActiveMQ 3.0.

    + +
    + +
    +

    The first step you want to do with every JMeter Test Plan is to add a + Thread Group element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. +

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

    + +

    Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.1 +below)

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter Point-to-Point.

    + +

    Next, increase the number of users (called threads) to 5.

    + +

    In the next field, the Ramp-Up Period, leave set the value to 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

    + +

    Clear the checkbox labeled "Forever", and enter a value of 4 in the Loop +Count field. This property tells JMeter how many times to repeat your test. +If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + + In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + + +
    + +
    + +

    Start by adding the sampler +to the Point-to-Point element (Add --> Sampler --> JMS Point-to-Point). +Then, select the JMS Point-to-Point sampler element in the tree. + In building the example a configuration will be provided that works with ActiveMQ 3.0. +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameValueDescription
    JMS Resources
    QueueuConnectionFactoryConnectionFactory This is the default JNDI entry for the connection factory within active mq.
    JNDI Name Request QueueQ.REQThis is equal to the JNDI name defined in the JNDI properties.
    JNDI Name Reply QueueQ.RPLThis is equal to the JNDI name defined in the JNDI properties.
    Message Properties
    Communication StyleRequest ResponseThis means that you need at least a service that responds to the requests.
    ContenttestThis is just the content of the message.
    JMS PropertiesNothing needed for active mq.
    JNDI Properties
    InitialContextFactoryorg.apache.activemq.jndi.ActiveMQInitialContextFactoryThe standard InitialContextFactory for Active MQ
    Properties
    queue.Q.REQexample.AThis defines a JNDI name Q.REQ for the request queue that points to the queue example.A
    queue.Q.RPLexample.BThis defines a JNDI name Q.RPL for the reply queue that points to the queue example.B
    Provider URL
    Provider URLtcp://localhost:61616This defines the URL of the active mq messaging system.
    +

    + +
    + +
    +

    The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your JMS requests in a file and presenting +a visual model of the data. +

    + +

    Select the Thread Group element and add a + listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename. +

    + +
    +Figure §-num;.2. Graph Results Listener
    + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/build-jms-topic-test-plan.xml b/ApacheJmeter/xdocs/usermanual/build-jms-topic-test-plan.xml new file mode 100644 index 0000000..ee5e459 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/build-jms-topic-test-plan.xml @@ -0,0 +1,199 @@ + + + +]> + + + + User's Manual: Building a JMS (Java Messaging Service) Test Plan + + + + +
    + +JMS requires some optional jars to be downloaded. Please refer to Getting Started for full details. + +

    In this section, you will learn how to create a +Test Plan to test JMS Providers. You will +create five subscribers and one publisher. You will create 2 thread groups and set +each one to 10 iterations. The total messages is (6 threads) x (1 message) x +(repeat 10 times) = 60 messages. To construct the Test Plan, you will use the +following elements: +Thread Group, +, +, and +.

    + +

    General notes on JMS: There are currently two JMS samplers. One uses JMS topics +and the other uses queues. Topic messages are commonly known as pub/sub messaging. +Topic messaging is generally used in cases where a message is published by a producer and +consumed by multiple subscribers. Queue messaging is generally used for transactions +where the sender expects a response. Messaging systems are quite different from +normal HTTP requests. In HTTP, a single user sends a request and gets a response. +Messaging system can work in sychronous and asynchronous mode. A JMS sampler needs +the JMS implementation jar files; for example, from Apache ActiveMQ. +See here for the list of jars provided by ActiveMQ 3.0.

    + +
    + +
    +

    The first step is add a Thread Group + element. The Thread Group tells JMeter the number of users you want to simulate, + how often the users should send requests, and how many requests they should +send.

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

    + +

    Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.1 +below)

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter Subscribers.

    + +

    Next, increase the number of users (called threads) to 5.

    + +

    In the next field, the Ramp-Up Period, set the value to 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, JMeter will immediately start all users.

    + +

    Clear the checkbox labeled "Forever", and enter a value of 10 in the Loop +Count field. This property tells JMeter how many times to repeat your test. +If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + +

    Repeat the process and add another thread group. For the second thread +group, enter "Publisher" in the name field, set the number of threads to 1, +and set the iteration to 10. +

    + +In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + + +
    + +
    + +

    Make sure the required jar files are in JMeter's lib directory. If they are +not, shutdown JMeter, copy the jar files over and restart JMeter.

    + +

    Start by adding the sampler +to the Subscribers element (Add --> Sampler --> JMS Subscriber). +Then, select the JMS Subscriber element in the tree and edit the following properties: + +

      +
    1. Change the Name field to "Sample Subscriber"
    2. +
    3. If the JMS provider uses the jndi.properties file, check the box
    4. +
    5. Enter the name of the InitialContextFactory class. For example, with ActiveMQ 5.4, the value is "org.apache.activemq.jndi.ActiveMQInitialContextFactory"
    6. +
    7. Enter the provider URL. This is the URL for the JNDI server, if there is one. For example, with ActiveMQ 5.4 on local machine with default port, the value is "tcp://localhost:61616"
    8. +
    9. Enter the name of the connection factory. Please refer to the documentation +of the JMS provider for the information. For ActiveMQ, the default is "ConnectionFactory"
    10. +
    11. Enter the name of the message topic. For ActiveMQ Dynamic Topics (create topics dynamically), example value is "dynamicTopics/MyStaticTopic1" +Note: Setup at startup mean that JMeter starting to listen on the Destination at beginning of test without name change possibility. +Setup on Each sample mean that JMeter (re)starting to listen before run each JMS Subscriber sample, +this last option permit to have Destination name with some JMeter variables
    12. +
    13. If the JMS provider requires authentication, check "required" and enter the +username and password. For example, Orion JMS requires authentication, while ActiveMQ +and MQSeries does not
    14. +
    15. Enter 10 in "Number of samples to aggregate". For performance reasons, the sampler +will aggregate messages, since small messages will arrive very quickly. If the sampler +didn't aggregate the messages, JMeter wouldn't be able to keep up.
    16. +
    17. If you want to read the response, check the box
    18. +
    19. There are two client implementations for subscribers. If the JMS provider +exhibits zombie threads with one client, try the other.
    20. +
    +

    + +
    +Figure §-num;.2. JMS Subscriber
    + +

    Next add the sampler +to the Publisher element (Add --> Sampler --> JMS Subscriber). +Then, select the JMS Publisher element in the tree and edit the following properties: +

    + +
      +
    1. Change the Name field to "Sample Publisher".
    2. +
    3. If the JMS provider uses the jndi.properties file, check the box
    4. +
    5. Enter the name of the InitialContextFactory class. For example, with ActiveMQ 5.4, the value is "org.apache.activemq.jndi.ActiveMQInitialContextFactory"
    6. +
    7. Enter the provider URL. This is the URL for the JNDI server, if there is one. For example, with ActiveMQ 5.4 on local machine with default port, the value is "tcp://localhost:61616"
    8. +
    9. Enter the name of the connection factory. Please refer to the documentation +of the JMS provider for the information. For ActiveMQ, the default is "ConnectionFactory"
    10. +
    11. Enter the name of the message topic. For ActiveMQ Dynamic Topics (create topics dynamically), example value is "dynamicTopics/MyStaticTopic1". +Note: Setup at startup mean that JMeter starting connection with the Destination at beginning of test without name change possibility. +Setup on Each sample mean that JMeter (re)starting the connection before run each JMS Publisher sample, +this last option permit to have Destination name with some JMeter variables
    12. +
    13. If the JMS provider requires authentication, check "required" and enter the +username and password. For example, Orion JMS requires authentication, while ActiveMQ +and MQSeries does not
    14. +
    15. Enter 10 in "Number of samples to aggregate". For performance reasons, the sampler +will aggregate messages, since small messages will arrive very quickly. If the sampler +didn't aggregate the messages, JMeter wouldn't be able to keep up.
    16. +
    17. Select the appropriate configuration for getting the message to publish. If you +want the sampler to randomly select the message, place the messages in a directory +and select the directory using browse.
    18. +
    19. Select the message type. If the message is in object format or map message, make sure the +message is generated correctly.
    20. +
    +

    +
    +Figure §-num;.3. JMS Publisher
    + + +
    + +
    +

    The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data.

    + +

    Select the Test Plan element and add a listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename.

    + +
    +Figure §-num;.4. Graph Results Listener
    + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/build-ldap-test-plan.xml b/ApacheJmeter/xdocs/usermanual/build-ldap-test-plan.xml new file mode 100644 index 0000000..657d542 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/build-ldap-test-plan.xml @@ -0,0 +1,152 @@ + + + + +]> + + + + + + User's Manual: Building an LDAP Test Plan + + + +
    +

    In this section, you will learn how to create a basic Test Plan to test an LDAP server. +You will create four users that send requests for four tests on the LDAP server.Also, you will tell +the users to run their tests twice. So, the total number of requests is (4 users) x (4 requests) x +repeat 2 times) = 32 LDAP requests. To construct the Test Plan, you will use the following elements: +Thread Group, +, +, and + +.

    +

    This example assumes that the LDAP Server is installed in your Local machine.

    +
    +
    +

    The first step you want to do with every JMeter Test Plan is to add a Thread Group element. +The Thread Group tells JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send.

    +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, clicking your +right mouse button to get the Add menu, and then select Add-->ThreadGroup. You should now see the +Thread Group element under Test Plan. If you do not see the element, then "expand" the Test Plan tree by +clicking on the Test Plan element. +

    +Figure §-num;.1. Thread Group with Default Values
    + +

    +
    +
    +

    Begin by selecting the Siptech Users element. Click your right mouse +button to get the Add menu, and then select Add --> Config Element --> Login Config Element. +Then, select this new element to view its Control Panel.

    +

    Like most JMeter elements, the Login Config Element Control Panel has a name +field that you can modify. In this example, leave this field with the default value.

    + +
    + Figure §-num;.2 Login Config Element for our Test Plan
    + +

    Enter Username field to "your Server Username",
    + The password field to "your Server Passowrd"

    + +

    These values are default for the LDAP Requests.

    +
    + +
    +

    Begin by selecting the Siptech Users element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element -->LDAP Request Defaults. Then, +select this new element to view its Control Panel.

    +

    Like most JMeter elements, the LDAP Request Defaults Control Panel has a name +field that you can modify. In this example, leave this field with the default value.

    + + +
    + Figure §-num;.3 LDAP Defaults for our Test Plan
    + + Enter DN field to "your Server Root Dn".
    + Enter LDAP Server's Servername field to "localhost".
    + The port to 389.
    + These values are default for the LDAP Requests.
    +
    + + +
    +

    In our Test Plan, we need to make four LDAP requests.

    +
      +
    1. Inbuilt Add Test
    2. +
    3. Inbuilt Modify Test
    4. +
    5. Inbuilt Delete Test
    6. +
    7. Inbuilt Search Test
    8. +
    +

    JMeter sends requests in the order that you add them to the tree. +Start by adding the first LDAP Request to the Siptech Users element (Add --> +Sampler --> LDAP Request). Then, select the LDAP Request element in the tree +and edit the following properties

    +
      +
    1. Change the Name to "Inbuilt-Add Test".
    2. +
    3. Select the Add test Radio button
    4. +
    +
    + Figure §-num;.4.1 LDAP Request for Inbuilt Add test
    + + +

    You do not have to set the Server Name field, port field, Username, Password +and DN because you already specified this value in the Login Config Element and +LDAP Request Defaults.

    +

    Next, add the second LDAP Request and edit the following +properties

    +
      +
    1. Change the Name to "Inbuilt-Modify Test".
    2. +
    3. Select the Modify test Radio button
    4. +
    + Next, add the Third LDAP Request and edit the following properties +
    + Figure §-num;.4.2 LDAP Request for Inbuilt Modify test
    + +
      +
    1. Change the Name to "Inbuilt-Delete Test".
    2. +
    3. Select the Delete test Radio button
    4. +
    + Next, add the fourth LDAP Request and edit the following properties + +
    + Figure §-num;.4.3 LDAP Request for Inbuilt Delete test
    + +
      +
    1. Change the Name to "Inbuilt-Search Test".
    2. +
    3. Select the Search test Radio button
    4. +
    +
    + Figure §-num;.4.4 LDAP Request for Inbuilt Search test
    + +
    +
    +

    The final element you need to add to your Test Plan is a Listener. + This element is responsible for storing all of the results of your LDAP +requests in a file and presenting a visual model of the data.Select the Siptech +Users element and add a View Results in Table (Add --> Listener -->View Results in Table)

    +
    + Figure §-num;.5 View result in Table Listener
    + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/build-ldapext-test-plan.xml b/ApacheJmeter/xdocs/usermanual/build-ldapext-test-plan.xml new file mode 100644 index 0000000..c2e92ee --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/build-ldapext-test-plan.xml @@ -0,0 +1,432 @@ + + + + +]> + + + + + + User's Manual: Building an Extended LDAP Test Plan + + + +
    +

    +In this section, you will learn how to create a basic Test Plan to test an LDAP +server.

    +

    +As the Extended LDAP Sampler is highly configurable, this also means that it takes +some time to build a correct testplan. You can however tune it exactly up to your +needs. +

    + +

    +You will create four users that send requests for four tests on the LDAP server.Also, you will tell +the users to run their tests twice. So, the total number of requests is (4 users) x (4 requests) x +repeat 2 times) = 32 LDAP requests. To construct the Test Plan, you will use the following elements:
    +Thread Group,
    +,
    +, and
    + +

    +

    +This example assumes that the LDAP Server is installed in your Local machine. +

    +

    +For the less experienced LDAP users, I build a small +LDAP tutorial which shortly explains +the several LDAP operations that can be used in building a complex testplan. +

    +

    +Take care when using LDAP special characters in the distinghuished name, in that case (eg, you want to use a + sign in a +distinghuished name) you need to escape the character by adding an "\" sign before that character. +extra exeption: if you want to add a \ character in a distinguished name (in an add or rename operation), you need to use 4 backslashes. +examples: +cn=dolf\+smits to add/search an entry with the name like cn=dolf+smits +cn=dolf \\ smits to search an entry with the name cn=dolf \ smits +cn=c:\\\\log.txt to add an entry with a name like cn=c:\log.txt +

    + + + +

    +The first step you want to do with every JMeter Test Plan is to add a Thread Group element. +The Thread Group tells JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send. +

    +

    +Go ahead and add the ThreadGroup element by first selecting the Test Plan, clicking your +right mouse button to get the Add menu, and then select Add-->ThreadGroup. You should now see the +Thread Group element under Test Plan. If you do not see the element, then "expand" the Test Plan tree by +clicking on the Test Plan element. +

    +

    +

    +Figure §-num;.1. Thread Group with Default Values
    + +

    +
    + +

    +Begin by selecting the Thread Group element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element -->LDAP Extended Request Defaults. Then, +select this new element to view its Control Panel. +

    +

    +Like most JMeter elements, the LDAP Extended Request Defaults Control Panel has a name +field that you can modify. In this example, leave this field with the default value. +

    +


    + Figure §-num;.2 LDAP Defaults for our Test Plan
    +

    +

    + For each of the different operations, some default values can be filled in. + In All cases, when a default is filled in, this is used for the LDAP extended requests. + For each requst, you can override the defaults by filling in the values in the LDAP extended request sampler. + When no valueis entered which is necesarry for a test, the test will fail in an unpredictable way! +

    + We will not enter any default values here, as we will build a very small testplan, so we will explain all the different fields when we add the LDAP Extended samplers. +
    + +

    +In our Test Plan, we want to use all 8 LDAP requests. +

    +
      +
    1. +Thread bind +
    2. +
    3. +Search Test +
    4. +
    5. +Compare Test +
    6. +
    7. +Single bind/unbind Test +
    8. +
    9. +Add Test +
    10. +
    11. +Modify Test +
    12. +
    13. +Delete Test +
    14. +
    15. +Rename entry (moddn) +
    16. +
    17. +Thread unbind +
    18. +
    +

    +JMeter sends requests in the order that you add them to the tree. +

    +

    +Adding a requests always start by:
    +Adding the LDAP Extended Request to the Thread Group element (Add --> +Sampler --> LDAP Ext Request). Then, select the LDAP Ext Request element in the tree +and edit the following properties.

    + + + +

    +

      +
    1. +Select the "Thread bind" button. +
    2. +
    3. +enter the hostname value from the LDAP server in the Servername field +
    4. +
    5. +Enter the portnumber from the LDAP server (389) in the port field +
    6. +
    7. +(optional) enter the baseDN in the DN field, this baseDN will be used as thestarting point for searches, add, deletes etc.
      +take care that this must be the uppermost shared level for all your request, eg When all information is stored under ou=people, dc=siemens, dc=com, you can use this value in the basedn.
      +You cannot search or rename anymore in the subtree ou=users,dc=siemens,dc=com!
      +If you need to search or rename objects in both subtrees, use the common denominator (dc=siemens,dc=com) as the baseDN. +
    8. +
    9. +(Optional) enter the distinghuised name from the user you want to use for authentication. +When this field is kept empty, an anonymous bind will be established. +
    10. +
    11. +(optional) Enter the password for the user you want to authenticate with, an empty password will also lead to an anonymous bind. +
    12. +
    +

    +

    +

    +Figure §-num;.3.1. Thread Bind example
    +

    +
    + + +

    +

      +
    1. +Select the "Search Test" button. +
    2. +
    3. +(Optional) enter the searchbase under which you want to perform the search, relative to the basedn, used in the thread bind request.
      +When left empty, the basedn is used as a search base, this files is important if you want to use a "base-entry" or "one-level" search (see below) +
    4. +
    5. +Enter the searchfilter, any decent LDAP serach filter will do, but for now, use something simple, like cn=john doe +
    6. +
    7. +(optional) enter the scope in the scope field, it has three options: +
        +
      1. Base level, Enter the value 0
        only the given searchbase is used, only for checking attributes or existence. +
      2. +
      3. One level, Enter the value 1
        Only search in one level below given searchbase is used +
      4. +
      5. Subtree, Enter the value 2
        Searches for object at any point below the given basedn +
      +
    8. +
    9. +(Optional) Sizelimit, specifies the maximun number of returned entries, +
    10. +
    11. +(optional) Timelimit, psecifies the maximum number of miliseconds, the SERVER can use for performing the search. it is NOT the maximun time the application will wait!
      +When a very large returnset is returned, from a very fast server, over a very slow line, you may have to wait for ages for the completion of the search request, but this parameter will not influence this. +
    12. +
    13. (Optional) Attributes you want in the search answer. This can be used to limit the size of the answer, especially when an onject has very large attributes (like jpegPhoto). There are three possibilities: +
      1. Leave empty (the default setting must also be empty) This will return all attributes. +
      2. +
      3. Put in one empty value (""), it will request a non-existent attributes, so in reality it returns no attributes +
      4. +
      5. Put in the attributes, seperated by a semi-colon. It will return only the requested attributes +
    14. +
    15. +(Optional) Return object, possible values are "true" and "false". True will return all java-object attributes, it will add these to the requested attributes, as specified above.
      +false will mean no java-object attributes will be returned. +
    16. +
    17. +(Optional) Dereference aliases. possible values "true" and "false". True will mean it will follow references, false says it will not. +
    18. +
    +

    +

    +

    +Figure §-num;.3.2. search request example
    +

    + + +

    +

      +
    1. +Select the "Compare" button. +
    2. +
    3. +enter the entryname form the object on which you want the compare operation to work, relative to the basedn, eg "cn=john doe,ou=people" +
    4. +
    5. +Enter the compare filter, this must be in the form "attribute=value", eg "mail=John.doe@siemens.com" +
    6. +
    +

    +

    +

    +Figure §-num;.3.3. Compare example
    +

    +
    + + +

    +

      +
    1. +Select the "Single bind/unbind" button. +
    2. +
    3. +Enter the FULL distinghuised name from the user you want to use for authentication.
      +eg. cn=john doe,ou=people,dc=siemens,dc=com +When this field is kept empty, an anonymous bind will be established. +
    4. +
    5. +Enter the password for the user you want to authenticate with, an empty password will also lead to an anonymous bind. +
    6. +
    7. +Take care: This single bind/unbind is in reality two seperate operations but cannot easily be split! +
    8. +
    +

    +

    +Figure §-num;.3.4. Single bind/unbind example
    +

    +
    + + +

    +

      +
    1. +Select the "Add" button. +
    2. +
    3. +Enter the distinghuised name for the object to add, relative to the basedn. +
    4. +
    5. +Add a line in the "add test" table, fill in the attribute and value.
      +When you need the same attribute more than once, just add a new line, add the attribute again, and a different value.
      +All necessary attributes and values must be specified to pass the test, see picture!
      +(sometimes the server adds the attribute "objectClass=top", this might give a problem. +
    6. +
    +

    +

    +

    +Figure §-num;.3.5. Add request example
    +

    +
    + + +

    +

      +
    1. +Select the "Modify test" button. +
    2. +
    3. +Enter the distinghuised name for the object to modify, relative to the basedn. +
    4. +
    5. +Add a line in the "modify test" table, with the "add" button. +
    6. +
    7. +You need to enter the attribute you want to modify, (optional) a value, and the opcode. The meaning of this opcode: +
      1. add
        this will mean that the attribute value (not optional in this case) willbe added to the attribute.
        +When the attribute is not existing, it will be created and the value added
        +When it is existing, and defined multi-valued, the new value is added.
        +when it is existing, but single valued, it will fail.
      2. +
      3. replace
        +This will overwrite the attribute with the given new value (not optional here)
        +When the attribute is not existing, it will be created and the value added
        +When it is existing, old values are removed, the new value is added.
      4. +
      5. delete
        +When no value is given, all values will be removed
        +When a value is given, only that value will be removed
        + when the given value is not existing, the test will fail +
      +
    8. +
    9. +(Optional) Add more modifications in the "modify test" table.
      +All modifications which are specified must succeed, to let the modification test pass. When one modification fails, NO modifications at all will be made and the entry will remain unchanged. +
    10. +
    +

    +

    +

    +Figure §-num;.3.6. Modify example
    +

    +
    + + +

    +

      +
    1. +Select the "Delete" button. +
    2. +
    3. +enter the name of the entry, relative to the baseDN, in the Delete-Field.
      +that is, if you want to remove "cn=john doe,ou=people,dc=siemens,dc=com", and you set the baseDN to "dc=siemens,dc=com", +you need to enter "cn=john doe,ou=people" in the Delete-field. +
    4. +
    +

    +

    +

    +Figure §-num;.3.7. Delete example
    +

    +
    + + +

    +

      +
    1. +Select the "Rename Entry" button. +
    2. +
    3. +enter the name of the entry, relative to the baseDN, in the "old entry name-Field".
      +that is, if you want to rename "cn=john doe,ou=people,dc=siemens,dc=com", and you set the baseDN to "dc=siemens,dc=com", +you need to enter "cn=john doe,ou=people" in the old entry name-field. +
    4. +
    5. +enter the new name of the entry, relative to the baseDN, in the "new distinghuised name-Field".
      +whne you only change the RDN, it will simply rename the entry
      +when you also add a differten subtree, eg you change from cn=john doe,ou=people to cn=john doe,ou=users, it will move the entry. +You can also move a complete subtree (If your LDAP server supports this!!!!), eg ou=people,ou=retired, to ou=oldusers,ou=users, this will move the complete subtee, plus all retired people in the subtree to the new place in the tree. +
    6. +
    +

    +

    +

    +Figure §-num;.3.8. Rename example
    +

    +
    + +

    +

      +
    1. +Select the "Thread unbind" button. +This will be enough as it just closes the current connection. +The information which is needed is already known by the system +
    +

    +

    +

    +Figure §-num;.3.9. Unbind example
    +

    +
    +
    + + +

    +The final element you need to add to your Test Plan is a Listener. + This element is responsible for storing all of the results of your LDAP +requests in a file and presenting a visual model of the data.Select the Thread group +element and add a View Results Tree (Add --> Listener -->View Results Tree) +

    +

    +

    +Figure §-num;.4. View result Tree Listener
    +

    +

    +In this listener you have three tabs to view, the sampler result, the request and the response data. +

      +
    1. +The sampler result just contains the response time, the returncode and return message +
    2. +
    3. +The request gives a short description of the request that was made, in practice no relevant information +is contained here. +
    4. +
    5. +The response data contains the full details of the sent request, as well the full details of the received answer, +this is given in a (self defined) xml-style. +The full description can be found here. +
    6. +
    +

    +
    +
    + +
    diff --git a/ApacheJmeter/xdocs/usermanual/build-monitor-test-plan.xml b/ApacheJmeter/xdocs/usermanual/build-monitor-test-plan.xml new file mode 100644 index 0000000..2bdd247 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/build-monitor-test-plan.xml @@ -0,0 +1,162 @@ + + + +]> + + + + User's Manual: Building a Monitor Test Plan + + + + +
    +

    In this section, you will learn how to create a +Test Plan to monitor webservers. Monitors +are useful for a stress testing and system management. Used with stress +testing, the monitor provides additional information about server performance. +It also makes it easier to see the relationship between server performance +and response time on the client side. As a system administration tool, the +monitor provides an easy way to monitor multiple servers from one console. +The monitor was designed to work with the status servlet in Tomcat 5. In +theory, any servlet container that supports JMX (Java Management Extension) +can port the status servlet to provide the same information.

    +

    For those who want to use the monitor with other servlet or EJB containers, +Tomcat's status servlet should work with other containers for the memory +statistics without any modifications. To get thread information, you will +need to change the MBeanServer lookup to retrieve the correct MBeans.

    + +
    + +
    +

    The first step is to add a Thread Group +element. The Thread Group tells JMeter the number of threads you want. Always use +1, since we are using JMeter as a monitor. This is very important for those not +familiar with server monitors. As a general rule, using multiple threads for a +single server is bad and can create significant stress. +

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, "expand" the Test Plan tree by clicking on the Test Plan element.

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Change the loop count to forever (or some large number) so that enough samples are generated.

    + +
    + +
    +

    Add the to the Thread Group element +(Add --> Config element --> HTTP Authorization Manager). Enter the username +and password for your webserver. Important note: the monitor only works with +Tomcat5 build 5.0.19 and newer. For instructions on how to setup Tomcat, please +refer to tomcat 5 documentation.

    +
      +
    1. leave the base URL blank
    2. +
    3. enter the username
    4. +
    5. enter the password
    6. +
    +
    + +
    + +

    Add the to the Thread Group element +(Add --> Sampler --> HTTP Request). Then, select the HTTP Request element +in the tree and edit the following properties): +

      +
    1. Change the Name field to "Server Status".
    2. +
    3. Enter the IP address or Hostname
    4. +
    5. Enter the port number
    6. +
    7. Set the Path field to "/manager/status" if you're using Tomcat.
    8. +
    9. Add a request parameter named "XML" in uppercase. Give it a value of +"true" in lowercase.
    10. +
    11. Check "Use as Monitor" at the bottom of the sampler
    12. +
    +

    + +
    + +
    + +

    Add a timer to this thread group (Add --> Timer --> Constant Timer). +Enter 5000 milliseconds in the "Thread Delay" box. In general, using intervals shorter +than 5 seconds will add stress to your server. Find out what is an acceptable interval +before you deploy the monitor in your production environment.

    + +
    + +
    +

    If you want to save the raw results from the server, add a simple data + Listener. If you want to save the + calculated statistics, enter a filename in the listener. If you want to save both + the raw data and statistics, make sure you use different filenames.

    + +

    Select the thread group element and add a listener +(Add --> Listener --> Simple Data Writer). Next, you need to specify a directory +and filename of the output file. You can either type it into the filename field, or +select the Browse button and browse to a directory and then enter a filename.

    + +
    + +
    + +

    Add the Listener by selecting the +test plan element (Add --> Listener -- > Monitor Results). +

    +By default, the Listener will select the results from the first connector in the sample response. +The Connector prefix field can be used to select a different connector. +If specified, the Listener will choose the first connector which matches the prefix. +If no match is found, then the first connector is selected. +

    +

    There are two tabs in +the monitor results listener. The first is the "Health", which displays the status of +the last sample the monitor received. The second tab is "Performance", which shows a +historical view of the server's performance. +

    + +
    +

    A quick note about how health is calculated. Typically, a server will crash if +it runs out of memory, or reached the maximum number of threads. In the case of +Tomcat 5, once the threads are maxed out, requests are placed in a queue until a +thread is available. The relative importance of threads vary between containers, so +the current implementation uses 50/50 to be conservative. A container that is more +efficient with thread management might not see any performance degradation, but +the used memory definitely will show an impact.

    +
    +

    The performance graph shows for different lines. The free memory line shows how +much free memory is left in the current allocated block. Tomcat 5 returns the maximum +memory, but it is not graphed. In a well tuned environment, the server should never +reach the maximum memory.

    +

    Note the graph has captions on both sides of the graph. On the left is percent and +the right is dead/healthy. If the memory line spikes up and down rapidly, it could +indicate memory thrashing. In those situations, it is a good idea to profile the +application with Borland OptimizeIt or JProbe. What you want to see is a regular +pattern for load, memory and threads. Any erratic behavior usually indicates poor +performance or a bug of some sort.

    + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/build-test-plan.xml b/ApacheJmeter/xdocs/usermanual/build-test-plan.xml new file mode 100644 index 0000000..00a6e2d --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/build-test-plan.xml @@ -0,0 +1,152 @@ + + + + +]> + + + + + User's Manual: Building a Test Plan + + + + +
    +

    A test plan describes a series of steps JMeter will execute when run. A complete +test plan will consist of one or more Thread Groups, logic conrollers, sample generating +controllers, listeners, timers, assertions, and configuration elements. +

    + + +

    Adding elements to a test plan can be done by right-clicking on an element in the +tree, and choosing a new element from the "add" list. Alternatively, elements can +be loaded from file and added by choosing the "merge" or "open" option.

    + +

    To remove an element, make sure the element is selected, right-click on the element, +and choose the "remove" option.

    +
    + + +

    To load an element from file, right click on the existing tree element to which +you want to add the loaded element, and select the "merge" option. Choose the file where +your elements are saved. JMeter will merge the elements into the tree.

    + +

    To save tree elements, right click on an element and choose the "Save Selection As ..." option. +JMeter will save the element selected, plus all child elements beneath it. In this way, +you can save test tree fragments and individual elements for later use.

    + +The workbench is not automatically saved with the test plan, but it can be saved separately as above. +
    + + +

    Any element in the test tree will present controls in JMeter's right-hand frame. These +controls allow you to configure the behavior of that particular test element. What can be +configured for an element depends on what type of element it is.

    + +The Test Tree itself can be manipulated by dragging and dropping components around the test tree. +
    + + +

    Although it is not required, we recommend that you save the Test Plan to a +file before running it. To save the Test Plan, select "Save" or "Save Test Plan As ..." from the +File menu (with the latest release, it is no longer necessary to select the +Test Plan element first).

    + +JMeter allows you to save the entire Test Plan tree or +only a portion of it. To save only the elements located in a particular "branch" +of the Test Plan tree, select the Test Plan element in the tree from which to start +the "branch", and then click your right mouse button to access the "Save Selection As ..." menu item. +Alternatively, select the appropriate Test Plan element and then select "Save Selection As ..." from +the Edit menu. + +
    + + +

    To run your test plan, choose "Start" (Control + r) from the "Run" menu item. +When JMeter is running, it shows a small green box at the right hand end of the section just under the menu bar. +You can also check the "Run" menu. +If "Start" is disabled, and "Stop" is enabled, +then JMeter is running your test plan (or, at least, it thinks it is).

    +

    +The numbers to the left of the green box are the number of active threads / total number of threads. +These only apply to a locally run test; they do not include any threads started on remote systems when using client-server mode. +

    +
    + + +

    +There are two types of stop command available from the menu: +

      +
    • Stop (Control + '.') - stops the threads immediately if possible. +In Versions of JMeter after 2.3.2, many samplers are now Interruptible which means that active samples can be terminated early. +The stop command will check that all threads have stopped within the default timeout, which is 5000 ms = 5 seconds. +[This can be changed using the JMeter property jmeterengine.threadstop.wait] +If the threads have not stopped, then a message is displayed. +The Stop command can be retried, but if it fails, then it is necessary to exit JMeter to clean up. +
    • +
    • Shutdown (Control + ',')- requests the threads to stop at the end of any current work. +Will not interrupt any active samples. +The modal shutdown dialog box will remain active until all threads have stopped.
    • +
    +Versions of JMeter after 2.3.2 allow a Stop to be initiated if Shutdown is taking too long. +Close the Shutdown dialog box and select Run/Stop, or just press Control + '.'. +

    +

    +When running JMeter in non-GUI mode, there is no Menu, and JMeter does not react to keystrokes such as Control + '.'. +So in versions after 2.3.2, JMeter non-GUI mode will listen for commands on a specific port +(default 4445, see the JMeter property jmeterengine.nongui.port). +In versions after 2.4, JMeter supports automatic choice of an alternate port if the default port is being used +(for example by another JMeter instance). In this case, JMeter will try the next higher port, continuing until +it reaches the JMeter property jmeterengine.nongui.maxport) which defaults to 4455. +If maxport is less than or equal to port, port scanning will not take place. +Note that JMeter 2.4 and earlier did not set up the listener for non-GUI clients, only non-GUI standalone tests; +this has been fixed. +

    +The chosen port is displayed in the console window. +
    +The commands currently supported are: +

      +
    • Shutdown - graceful shutdown
    • +
    • StopTestNow - immediate shutdown
    • +
    +These commands can be sent by using the shutdown[.cmd|.sh] or stoptest[.cmd|.sh] script +respectively. The scripts are to be found in the JMeter bin directory. +The commands will only be accepted if the script is run from the same host. +

    +
    + + +

    +JMeter reports warnings and errors to the jmeter.log file, as well as some information on the test run itself. +Just occasionally there may be some errors that JMeter is unable to trap and log; these will appear on the command console. +If a test is not behaving as you expect, please check the log file in case any errors have been reported (e.g. perhaps a syntax error in a function call). +

    +

    +Sampling errors (e.g. HTTP 404 - file not found) are not normally reported in the log file. +Instead these are stored as attributes of the sample result. +The status of a sample result can be seen in the various different Listeners. +

    +
    + +
    + + +
    + diff --git a/ApacheJmeter/xdocs/usermanual/build-web-test-plan.xml b/ApacheJmeter/xdocs/usermanual/build-web-test-plan.xml new file mode 100644 index 0000000..d6f9483 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/build-web-test-plan.xml @@ -0,0 +1,232 @@ + + + + +]> + + + + + User's Manual: Building a Web Test Plan + + + + +
    +

    In this section, you will learn how to create a basic +Test Plan to test a Web site. You will +create five users that send requests to two pages on the JMeter Web site. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (5 users) x (2 requests) x (repeat 2 times) = 20 HTTP requests. To +construct the Test Plan, you will use the following elements: +Thread Group, +, +, and +.

    + +

    For a more advanced Test Plan, see +Building an Advanced Web Test Plan.

    +
    + + + +
    +

    The first step you want to do with every JMeter Test Plan is to add a +Thread Group element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send.

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

    + +

    Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.1 +below)

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter JMeter Users.

    + +

    Next, increase the number of users (called threads) to 5.

    + +

    In the next field, the Ramp-Up Period, leave the the default value of 1 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

    + +

    Finally enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. If you enter a loop count value of 1, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + +In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + +

    See Figure §-num;.2 for the completed JMeter Users Thread Group.

    + +
    +Figure §-num;.2. JMeter Users Thread Group
    + +
    + +
    +

    Now that we have defined our users, it is time to define the tasks that they +will be performing. In this section, you will specify the default settings +for your HTTP requests. And then, in section §-num;.3, you will add HTTP Request +elements which use some of the default settings you specified here.

    + +

    Begin by selecting the JMeter Users (Thread Group) element. Click your right mouse button +to get the Add menu, and then select Add --> Config Element --> HTTP Request +Defaults. Then, select this new element to view its Control Panel (see Figure §-num;.3). +

    + +
    +Figure §-num;.3. HTTP Request Defaults
    + +

    +Like most JMeter elements, the Control +Panel has a name field that you can modify. In this example, leave this field with +the default value.

    + +

    Skip to the next field, which is the Web Server's Server Name/IP. For the +Test Plan that you are building, all HTTP requests will be sent to the same +Web server, jmeter.apache.org. Enter this domain name into the field. +This is the only field that we will specify a default, so leave the remaining +fields with their default values.

    + +The HTTP Request Defaults element does not tell JMeter +to send an HTTP request. It simply defines the default values that the +HTTP Request elements use. + +

    See Figure §-num;.4 for the completed HTTP Request Defaults element

    + +
    +Figure §-num;.4. HTTP Defaults for our Test Plan
    + +
    + +
    +

    Nearly all web testing should use cookie support, unless your application +specifically doesn't use cookies. To add cookie support, simply add an + to each Thread +Group in your test plan. This will ensure that each thread gets its own +cookies, but shared across all objects.

    + +

    To add the , simply select the +Thread Group, and choose Add --> +Config Element --> HTTP +Cookie Manager, either from the Edit Menu, or from the right-click pop-up menu.

    +
    + + +
    + +

    In our Test Plan, we need to make two HTTP requests. The first one is for the +JMeter home page (http://jmeter.apache.org/), and the second one is for the +Changes page (http://jmeter.apache.org/changes.html).

    + +JMeter sends requests in the order that they appear in the tree. + +

    Start by adding the first +to the JMeter Users element (Add --> Sampler --> HTTP Request). +Then, select the HTTP Request element in the tree and edit the following properties +(see Figure §-num;.5): +

      +
    1. Change the Name field to "Home Page".
    2. +
    3. Set the Path field to "/". Remember that you do not have to set the Server +Name field because you already specified this value in the HTTP Request Defaults +element.
    4. +
    +

    + +
    +Figure §-num;.5. HTTP Request for JMeter Home Page
    + +

    Next, add the second HTTP Request and edit the following properties (see +Figure §-num;.6: +

      +
    1. Change the Name field to "Changes".
    2. +
    3. Set the Path field to "/changes.html".
    4. +
    +

    + +
    +Figure §-num;.6. HTTP Request for JMeter Changes Page
    + +
    + +
    +

    The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data.

    + +

    Select the JMeter Users element and add a listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename.

    + +
    +Figure §-num;.7. Graph Results Listener
    + +
    + +
    +

    +It's not the case here, but some web-sites require you to login before permitting you to perform certain actions. +In a web-browser, the login will be shown as a form for the user name and password, +and a button to submit the form. +The button generates a POST request, passing the values of the form items as parameters. +

    +

    +To do this in JMeter, add an HTTP Request, and set the method to POST. +You'll need to know the names of the fields used by the form, and the target page. +These can be found out by inspecting the code of the login page. +[If this is difficult to do, you can use the JMeter Proxy Recorder to record the login sequence.] +Set the path to the target of the submit button. +Click the Add button twice and enter the username and password details. +Sometimes the login form contains additional hidden fields. +These will need to be added as well. +

    +
    +Figure §-num;.8. Sample HTTP login request
    + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/build-ws-test-plan.xml b/ApacheJmeter/xdocs/usermanual/build-ws-test-plan.xml new file mode 100644 index 0000000..b46737a --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/build-ws-test-plan.xml @@ -0,0 +1,184 @@ + + + + +]> + + + + + User's Manual: Building a WebService Test Plan + + + + +
    +

    In this section, you will learn how to create a +Test Plan to test a WebService. You will +create five users that send requests to One page. +Also, you will tell the users to run their tests twice. So, the total number of +requests is (5 users) x (1 requests) x (repeat 2 times) = 10 HTTP requests. To +construct the Test Plan, you will use the following elements: +Thread Group, +, and +.

    + +

    If the sampler appears to be getting an error from the webservice, double check the +SOAP message and make sure the format is correct. In particular, make sure the +xmlns attributes are exactly the same as the WSDL. If the xml namespace is +different, the webservice will likely return an error. +Xmethods contains a list of public webservice for those who want to test +their test plan.

    + +
    + +
    +

    The first step you want to do with every JMeter Test Plan is to add a +Thread Group element. The Thread Group tells +JMeter the number of users you want to simulate, how often the users should send +requests, and the how many requests they should send.

    + +

    Go ahead and add the ThreadGroup element by first selecting the Test Plan, +clicking your right mouse button to get the Add menu, and then select +Add --> ThreadGroup.

    + +

    You should now see the Thread Group element under Test Plan. If you do not +see the element, then "expand" the Test Plan tree by clicking on the +Test Plan element.

    + +

    Next, you need to modify the default properties. Select the Thread Group element +in the tree, if you have not already selected it. You should now see the Thread +Group Control Panel in the right section of the JMeter window (see Figure §-num;.1 +below)

    + +
    +Figure §-num;.1. Thread Group with Default Values
    + +

    Start by providing a more descriptive name for our Thread Group. In the name +field, enter Jakarta Users.

    + +

    Next, increase the number of users (called threads) to 10.

    + +

    In the next field, the Ramp-Up Period, leave the the default value of 0 +seconds. This property tells JMeter how long to delay between starting each +user. For example, if you enter a Ramp-Up Period of 5 seconds, JMeter will +finish starting all of your users by the end of the 5 seconds. So, if we have +5 users and a 5 second Ramp-Up Period, then the delay between starting users +would be 1 second (5 users / 5 seconds = 1 user per second). If you set the +value to 0, then JMeter will immediately start all of your users.

    + +

    Finally, clear the checkbox labeled "Forever", and enter a value of 2 in +the Loop Count field. This property tells JMeter how many times to repeat your +test. If you enter a loop count value of 0, then JMeter will run your test only +once. To have JMeter repeatedly run your Test Plan, select the Forever +checkbox.

    + +In most applications, you have to manually accept +changes you make in a Control Panel. However, in JMeter, the Control Panel +automatically accepts your changes as you make them. If you change the +name of an element, the tree will be updated with the new text after you +leave the Control Panel (for example, when selecting another tree element). + +

    See Figure §-num;.2 for the completed Jakarta Users Thread Group.

    + +
    +Figure §-num;.2. Jakarta Users Thread Group
    + +
    + +
    + +

    In our Test Plan, we will use a .NET webservice. Since you're using +the webservice sampler, we won't go into the details of writing a +webservice. If you don't know how to write a webservice, google for +webservice and familiarize yourself with writing webservices for +Java and .NET. It should be noted there is a significant difference +between how .NET and Java implement webservices. The topic is too +broad to cover in the user manual. Please refer to other sources to +get a better idea of the differences.

    + +JMeter sends requests in the order that they appear in the tree. + +

    Start by adding the sampler +to the Jakarta Users element (Add --> Sampler --> WebService(SOAP) Request). +Then, select the webservice Request element in the tree and edit the following properties +(see Figure §-num;.5): +

      +
    1. Change the Name field to "WebService(SOAP) Request".
    2. +
    3. Enter the WSDL URL and click "Load WSDL".
    4. +
    +

    + +
    +Figure §-num;.3. Webservice Request
    + +

    If the WSDL file was loaded correctly, the "Web Methods" drop down should +be populated. If the drop down remains blank, it means there was a problem +getting the WSDL. You can test the WSDL using a browser that reads XML. +For example, if you're testing an IIS webservice the URL will look like this: +http://localhost/myWebService/Service.asmx?WSDL. At this point, SOAPAction, URL +and SOAP Data should be blank.

    + +

    Next, select the web method and click "Configure". The sampler should +populate the "URL" and "SOAPAction" text fields. Assuming the WSDL is valid, +the correct soap action should be entered. +

    + +

    The last step is to paste the SOAP message in the "SOAP/XML-RPC Data" +text area. You can optionally save the soap message to a file and browse +to the location. For convienance, there is a third option of using a +message folder. The sampler will randomly select files from a given +folder and use the text for the soap message.

    + +

    If you do not want JMeter to read the response from the SOAP Webservice, +uncheck "Read Soap Responses." If the test plan is intended to stress test +a webservice, the box should be unchecked. If the test plan is a functional +test, the box should be checked. When "Read Soap Responses" is unchecked, +no result will be displayed in view result tree or view results in table.

    + +

    An important note on the sampler. It will automatically use the proxy host +and port passed to JMeter from command line, if those fields in the sampler are +left blank. If a sampler has values in the proxy host and port text field, it +will use the ones provided by the user. If no host or port are provided and +JMeter wasn't started with command line options, the sampler will fail +silently. This behavior may not be what users expect.

    + +

    Note: If you're using Cassini webserver, it does not work correctly and is not a reliable webserver. Cassini is meant to be a simple example and isn't a full blown webserver like IIS. Cassini does not close connections correctly, which causes JMeter to hang or not get the response contents.

    +

    Currently, only .NET uses SOAPAction, so it is normal to have a blank SOAPAction for all other webservices. The list includes JWSDP, Weblogic, Axis, The Mind Electric Glue, and gSoap.

    + +
    + +
    +

    The final element you need to add to your Test Plan is a + Listener. This element is +responsible for storing all of the results of your HTTP requests in a file and presenting +a visual model of the data.

    + +

    Select the Jakarta Users element and add a listener (Add --> Listener +--> Graph Results). Next, you need to specify a directory and filename of the +output file. You can either type it into the filename field, or select the +Browse button and browse to a directory and then enter a filename.

    + +
    +Figure §-num;.7. Graph Results Listener
    + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/component_reference.xml b/ApacheJmeter/xdocs/usermanual/component_reference.xml new file mode 100644 index 0000000..678d409 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/component_reference.xml @@ -0,0 +1,5499 @@ + + + +]> + + + + User's Manual: Component Reference + + + + + +
    + +

    + +

    + + Several test elements use JMeter properties to control their behaviour. + These properties are normally resolved when the class is loaded. + This generally occurs before the test plan starts, so it's not possible to change the settings by using the __setProperty() function. + +

    +

    +
    +
    + +
    + +

    + Samplers perform the actual work of JMeter. + Each sampler (except Test Action) generates one or more sample results. + The sample results have various attributes (success/fail, elapsed time, data size etc) and can be viewed in the various listeners. +

    +
    + + +This controller lets you send an FTP "retrieve file" or "upload file" request to an FTP server. +If you are going to send multiple requests to the same FTP server, consider +using a Configuration +Element so you do not have to enter the same information for each FTP Request Generative +Controller. When downloading a file, it can be stored on disk (Local File) or in the Response Data, or both. +

    +Latency is set to the time it takes to login (versions of JMeter after 2.3.1). +

    +
    + + Descriptive name for this controller that is shown in the tree. + Domain name or IP address of the FTP server. + Port to use. If this is >0, then this specific port is used, otherwise JMeter uses the default FTP port. + File to retrieve or name of destination file to upload. + File to upload, or destination for downloads (defaults to remote file name). + Provides the contents for the upload, overrides the Local File property. + Whether to retrieve or upload a file. + Check this to use Binary mode (default Ascii) + + Whether to store contents of retrieved file in response data. + If the mode is Ascii, then the contents will be visible in the Tree View Listener. + + FTP account username. + FTP account password. N.B. This will be visible in the test plan. + + + Assertions + + Building an FTP Test Plan + + +
    + + + + +

    This sampler lets you send an HTTP/HTTPS request to a web server. It + also lets you control whether or not JMeter parses HTML files for images and + other embedded resources and sends HTTP requests to retrieve them. + The following types of embedded resource are retrieved:

    +
      +
    • images
    • +
    • applets
    • +
    • stylesheets
    • +
    • external scripts
    • +
    • frames, iframes
    • +
    • background images (body, table, TD, TR)
    • +
    • background sound
    • +
    +

    + The default parser is htmlparser. + This can be changed by using the property "htmlparser.classname" - see jmeter.properties for details. +

    +

    If you are going to send multiple requests to the same web server, consider + using an + Configuration Element so you do not have to enter the same information for each + HTTP Request.

    + +

    Or, instead of manually adding HTTP Requests, you may want to use + JMeter's to create + them. This can save you time if you have a lot of HTTP requests or requests with many + parameters.

    + +

    There are two different screens for defining the samplers: +

      +
    • AJP/1.3 Sampler - uses the Tomcat mod_jk protocol (allows testing of Tomcat in AJP mode without needing Apache httpd) + The AJP Sampler does not support multiple file upload; only the first file will be used. +
    • +
    • HTTP Request - this has an implementation drop-down box, which selects the HTTP protocol implementation to be used:
    • +
        +
      • Java - uses the HTTP implementation provided by the JVM. + This has some limitations in comparison with the HttpClient implementations - see below.
      • +
      • HTTPClient3.1 - uses Apache Commons HttpClient 3.1. + This is no longer being developed, and support for this may be dropped in a future JMeter release.
      • +
      • HTTPClient4 - uses Apache HttpComponents HttpClient 4.x.
      • +
      +
    +

    +

    The Java HTTP implementation has some limitations:

    +
      +
    • There is no control over how connections are re-used. + When a connection is released by JMeter, it may or may not be re-used by the same thread.
    • +
    • The API is best suited to single-threaded usage - various settings (e.g. proxy) + are defined via system properties, and therefore apply to all connections.
    • +
    • There is a bug in the handling of HTTPS via a Proxy (the CONNECT is not handled correctly). + See Java bugs 6226610 and 6208335. +
    • +
    • It does not support virtual hosts.
    • +
    +

    Note: the FILE protocol is intended for testing puposes only. + It is handled by the same code regardless of which HTTP Sampler is used.

    +

    If the request requires server or proxy login authorization (i.e. where a browser would create a pop-up dialog box), + you will also have to add an Configuration Element. + For normal logins (i.e. where the user enters login information in a form), you will need to work out what the form submit button does, + and create an HTTP request with the appropriate method (usually POST) + and the appropriate parameters from the form definition. + If the page uses HTTP, you can use the JMeter Proxy to capture the login sequence. +

    +

    + In versions of JMeter up to 2.2, only a single SSL context was used for all threads and samplers. + This did not generate the proper load for multiple users. + A separate SSL context is now used for each thread. + To revert to the original behaviour, set the JMeter property: +

    +https.sessioncontext.shared=true
    +
    + By default, the SSL context is retained for the duration of the test. + In versions of JMeter from 2.5.1, the SSL session can be optionally reset for each test iteration. + To enable this, set the JMeter property: +
    +https.use.cached.ssl.context=false
    +
    + Note: this does not apply to the Java HTTP implementation. +

    +

    + JMeter defaults to the SSL protocol level TLS. + If the server needs a different level, e.g. SSLv3, change the JMeter property, for example: +

    +https.default.protocol=SSLv3
    +
    +

    +

    + JMeter also allows one to enable additional protocols, by changing the property https.socket.protocols. +

    +

    If the request uses cookies, then you will also need an + . You can + add either of these elements to the Thread Group or the HTTP Request. If you have + more than one HTTP Request that needs authorizations or cookies, then add the + elements to the Thread Group. That way, all HTTP Request controllers will share the + same Authorization Manager and Cookie Manager elements.

    + +

    If the request uses a technique called "URL Rewriting" to maintain sessions, + then see section + 6.1 Handling User Sessions With URL Rewriting + for additional configuration steps.

    +
    + + Descriptive name for this controller that is shown in the tree. + + Domain name or IP address of the web server. e.g. www.example.com. [Do not include the http:// prefix.] + Note: in JMeter 2.5 (and later) if the "Host" header is defined in a Header Manager, then this will be used + as the virtual host name. + + Port the web server is listening to. Default: 80 + Connection Timeout. Number of milliseconds to wait for a connection to open. + Response Timeout. Number of milliseconds to wait for a response. + Hostname or IP address of a proxy server to perform request. [Do not include the http:// prefix.] + Port the proxy server is listening to. + (Optional) username for proxy server. + (Optional) password for proxy server. (N.B. this is stored unencrypted in the test plan) + Java, HttpClient3.1, HttpClient4. + If not specified (and not defined by HTTP Request Defaults), the default depends on the value of the JMeter property + jmeter.httpsampler, failing that, the Java implementation is used. + HTTP, HTTPS or FILE. Default: HTTP + GET, POST, HEAD, TRACE, OPTIONS, PUT, DELETE + Content encoding to be used (for POST and FILE) + + Sets the underlying http protocol handler to automatically follow redirects, + so they are not seen by JMeter, and thus will not appear as samples. + Should only be used for GET and HEAD requests. + The HttpClient sampler will reject attempts to use it for POST or PUT. + Warning: see below for information on cookie and header handling. + + + This only has any effect if "Redirect Automatically" is not enabled. + If set, the JMeter sampler will check if the response is a redirect and follow it if so. + The initial redirect and further responses will appear as additional samples. + The URL and data fields of the parent sample will be taken from the final (non-redirected) + sample, but the parent byte count and elapsed time include all samples. + The latency is taken from the initial response (versions of JMeter after 2.3.4 - previously it was zero). + Note that the HttpClient sampler may log the following message:
    + "Redirect requested but followRedirects is disabled"
    + This can be ignored. +
    + In versions after 2.3.4, JMeter will collapse paths of the form '/../segment' in + both absolute and relative redirect URLs. For example http://host/one/../two => http://host/two. + If necessary, this behaviour can be suppressed by setting the JMeter property + httpsampler.redirect.removeslashdotdot=false +
    + JMeter sets the Connection: keep-alive header. This does not work properly with the default HTTP implementation, as connection re-use is not under user-control. + It does work with the Apache HttpComponents HttpClient implementations. + + Use a multipart/form-data or application/x-www-form-urlencoded post request + + + When using multipart/form-data, this suppresses the Content-Type and + Content-Transfer-Encoding headers; only the Content-Disposition header is sent. + + The path to resource (for example, /servlets/myServlet). If the +resource requires query string parameters, add them below in the +"Send Parameters With the Request" section. + +As a special case, if the path starts with "http://" or "https://" then this is used as the full URL. + +In this case, the server, port and protocol are ignored; parameters are also ignored for GET and DELETE methods. + + The query string will + be generated from the list of parameters you provide. Each parameter has a name and + value, the options to encode the parameter, and an option to include or exclude an equals sign (some applications + don't expect an equals when the value is the empty string). The query string will be generated in the correct fashion, depending on + the choice of "Method" you made (ie if you chose GET or DELETE, the query string will be + appended to the URL, if POST or PUT, then it will be sent separately). Also, if you are + sending a file using a multipart form, the query string will be created using the + multipart form specifications. + See below for some further information on parameter handling. +

    + Additionally, you can specify whether each parameter should be URL encoded. If you are not sure what this + means, it is probably best to select it. If your values contain characters such as &amp; or spaces, or + question marks, then encoding is usually required.

    + Name of the file to send. If left blank, JMeter + does not send a file, if filled in, JMeter automatically sends the request as + a multipart form request. +

    + If it is a POST or PUT request and there is a single file whose 'name' attribute (below) is omitted, + then the file is sent as the entire body + of the request, i.e. no wrappers are added. This allows arbitrary bodies to be sent. This functionality is present for POST requests + after version 2.2, and also for PUT requests after version 2.3. + See below for some further information on parameter handling. +

    +
    + Value of the "name" web request parameter. + MIME type (for example, text/plain). + If it is a POST or PUT request and either the 'name' atribute (below) are omitted or the request body is + constructed from parameter values only, then the value of this field is used as the value of the + content-type request header. + + Tell JMeter to parse the HTML file +and send HTTP/HTTPS requests for all images, Java applets, JavaScript files, CSSs, etc. referenced in the file. + See below for more details. + + For use with the listener. + + If this is selected, then the response is not stored in the sample result. + Instead, the 32 character MD5 hash of the data is calculated and stored instead. + This is intended for testing large amounts of data. + + + If present, this must be a regular expression that is used to match against any embedded URLs found. + So if you only want to download embedded resources from http://example.com/, use the expression: + http://example\.com/.* + + Use a pool of concurrent connections to get embedded resources. + Pool size for concurrent connections used to get embedded resources. + + [Only for HTTP Request HTTPClient] + Override the default local IP address for this sample. + The JMeter host must have multiple IP addresses (i.e. IP aliases or network interfaces). + If the property httpclient.localaddress is defined, that is used for all HttpClient requests. + +
    +

    +N.B. when using Automatic Redirection, cookies are only sent for the initial URL. +This can cause unexpected behaviour for web-sites that redirect to a local server. +E.g. if www.example.com redirects to www.example.co.uk. +In this case the server will probably return cookies for both URLs, but JMeter will only see the cookies for the last +host, i.e. www.example.co.uk. If the next request in the test plan uses www.example.com, +rather than www.example.co.uk, it will not get the correct cookies. +Likewise, Headers are sent for the initial request, and won't be sent for the redirect. +This is generally only a problem for manually created test plans, +as a test plan created using a recorder would continue from the redirected URL. +

    +

    +Parameter Handling:

    +For the POST and PUT method, if there is no file to send, and the name(s) of the parameter(s) are omitted, +then the body is created by concatenating all the value(s) of the parameters. +Note that the values are concatenated without adding any end-of-line characters. +These can be added by using the __char() function in the value fields. +This allows arbitrary bodies to be sent. +The values are encoded if the encoding flag is set (versions of JMeter after 2.3). +See also the MIME Type above how you can control the content-type request header that is sent. +

    +For other methods, if the name of the parameter is missing, +then the parameter is ignored. This allows the use of optional parameters defined by variables. +(versions of JMeter after 2.3) +

    +
    +

    Since JMeter 2.6, you have the option to switch to Post Body when a request has only unnamed parameters +(or no parameters at all). +This option is useful in the following cases (amongst others): +

      +
    • GWT RPC HTTP Request
    • +
    • JSON REST HTTP Request
    • +
    • XML REST HTTP Request
    • +
    • SOAP HTTP Request
    • +
    +Note that once you leave the Tree node, you cannot switch back to the parameter tab unless you clear the Post Body tab of data. +

    +

    +In Post Body mode, each line will be sent with CRLF appended, apart from the last line. +To send a CRLF after the last line of data, just ensure that there is an empty line following it. +(This cannot be seen, except by noting whether the cursor can be placed on the subsequent line.) +

    +
    Figure 1 - HTTP Request with one unnamed parameter
    +
    Figure 2 - Confirm dialog to switch
    +
    Figure 3 - HTTP Request using RAW Post body
    + +

    +Method Handling:

    +The POST and PUT request methods work similarly, except that the PUT method does not support multipart requests. +The PUT method body must be provided as one of the following: +

      +
    • define the body as a file
    • +
    • define the body as parameter value(s) with no name
    • +
    +If you define any parameters with a name in either the sampler or Http +defaults then nothing is sent. +The GET and DELETE request methods work similarly to each other. +

    +

    Upto and including JMeter 2.1.1, only responses with the content-type "text/html" were scanned for +embedded resources. Other content-types were assumed to be something other than HTML. +JMeter 2.1.2 introduces the a new property HTTPResponse.parsers, which is a list of parser ids, + e.g. htmlParser and wmlParser. For each id found, JMeter checks two further properties:

    +
      +
    • id.types - a list of content types
    • +
    • id.className - the parser to be used to extract the embedded resources
    • +
    +

    See jmeter.properties file for the details of the settings. + If the HTTPResponse.parser property is not set, JMeter reverts to the previous behaviour, + i.e. only text/html responses will be scanned

    +Emulating slow connections (HttpClient only):

    +The HttpClient version of the sampler supports emulation of slow connections; see the following entries in jmeter.properties: +
    +# Define characters per second > 0 to emulate slow connections
    +#httpclient.socket.http.cps=0
    +#httpclient.socket.https.cps=0
    +
    +

    Response size calculation

    +Optional properties to allow change the method to get response size:

    +

    • Gets the real network size in bytes for the body response +
      sampleresult.getbytes.body_real_size=true
    • +
    • Add HTTP headers to full response size +
      sampleresult.getbytes.headers_size=true
    + + +The Java and HttpClient3 inplementations do not include transport overhead such as +chunk headers in the response body size.

    +The HttpClient4 implementation does include the overhead in the response body size, +so the value may be greater than the number of bytes in the response content. +
    + +Versions of JMeter before 2.5 returns only data response size (uncompressed if request uses gzip/defate mode). +

    To return to settings before version 2.5, set the two properties to false.
    +

    +

    +Retry handling

    +In version 2.5 of JMeter, the HttpClient4 and Commons HttpClient 3.1 samplers used the default retry count, which was 3. +In later versions, the retry count has been set to 1, which is what the Java implementation appears to do. +The retry count can be overridden by setting the relevant JMeter property, for example: +

    +httpclient4.retrycount=3
    +httpclient3.retrycount=3
    +
    +

    + + Assertion + Building a Web Test Plan + Building an Advanced Web Test Plan + + + + + + + HTTP Requests and Session ID's: URL Rewriting + + +
    + + + +

    This sampler lets you send an JDBC Request (an SQL query) to a database.

    +

    Before using this you need to set up a + Configuration element +

    +

    +If the Variable Names list is provided, then for each row returned by a Select statement, the variables are set up +with the value of the corresponding column (if a variable name is provided), and the count of rows is also set up. +For example, if the Select statement returns 2 rows of 3 columns, and the variable list is A,,C, +then the following variables will be set up: +

    +A_#=2 (number of rows)
    +A_1=column 1, row 1
    +A_2=column 1, row 2
    +C_#=2 (number of rows)
    +C_1=column 3, row 1
    +C_2=column 3, row 2
    +
    +If the Select statement returns zero rows, then the A_# and C_# variables would be set to 0, and no other variables would be set. +

    +

    +Old variables are cleared if necessary - e.g. if the first select retrieves 6 rows and a second select returns only 3 rows, +the additional variables for rows 4, 5 and 6 will be removed. +

    +

    +Note: The latency time is set from the time it took to acquire a connection. +

    +
    + + + Descriptive name for this controller that is shown in the tree. + + Name of the JMeter variable that the connection pool is bound to. + This must agree with the 'Variable Name' field of a JDBC Connection Configuration. + + Set this according to the statement type: +
      +
    • Select Statement
    • +
    • Update Statement - use this for Inserts as well
    • +
    • Callable Statement
    • +
    • Prepared Select Statement
    • +
    • Prepared Update Statement - use this for Inserts as well
    • +
    • Commit
    • +
    • Rollback
    • +
    • Autocommit(false)
    • +
    • Autocommit(true)
    • +
    • Edit - this should be a variable reference that evaluates to one of the above
    • +
    +
    + + SQL query. + Do not enter a trailing semi-colon. + There is generally no need to use { and } to enclose Callable statements; + however they mey be used if the database uses a non-standard syntax. + [The JDBC driver automatically converts the statement if necessary when it is enclosed in {}]. + For example: +
      +
    • select * from t_customers where id=23
    • +
    • CALL SYSCS_UTIL.SYSCS_EXPORT_TABLE (null,?, ?, null, null, null) +
        +
      • Parameter values: tablename,filename
      • +
      • Parameter types: VARCHAR,VARCHAR
      • +
      +
    • + The second example assumes you are using Apache Derby. +
    +
    + + Comma-separated list of parameter values. Use ]NULL[ to indicate a NULL parameter. + (If required, the null string can be changed by defining the property "jdbcsampler.nullmarker".) +

    + The list must be enclosed in double-quotes if any of the values contain a comma or double-quote, + and any embedded double-quotes must be doubled-up, for example: +
    "Dbl-Quote: "" and Comma: ,"
    + There must be as many values as there are placeholders in the statement. +
    + + Comma-separated list of SQL parameter types (e.g. INTEGER, DATE, VARCHAR, DOUBLE). + These are defined as fields in the class java.sql.Types, see for example: + Javadoc for java.sql.Types. + [Note: JMeter will use whatever types are defined by the runtime JVM, + so if you are running on a different JVM, be sure to check the appropriate document] + If the callable statement has INOUT or OUT parameters, then these must be indicated by prefixing the + appropriate parameter types, e.g. instead of "INTEGER", use "INOUT INTEGER". + If not specified, "IN" is assumed, i.e. "DATE" is the same as "IN DATE". +

    + If the type is not one of the fields found in java.sql.Types, versions of JMeter after 2.3.2 also + accept the corresponding integer number, e.g. since INTEGER == 4, you can use "INOUT 4". +

    + There must be as many types as there are placeholders in the statement. +
    + Comma-separated list of variable names to hold values returned by Select statements, Prepared Select Statements or CallableStatement. + Note that when used with CallableStatement, list of variables must be in the same sequence as the OUT parameters returned by the call. + If there are less variable names than OUT parameters only as many results shall be stored in the thread-context variables as variable names were supplied. + If more variable names than OUT parameters exist, the additional variables will be ignored + + If specified, this will create an Object variable containing a list of row maps. + Each map contains the column name as the key and the column data as the value. Usage:

    + columnValue = vars.getObject("resultObject").get(0).get("Column Name"); +
    +
    + + + Building a Database Test Plan + + +Versions of JMeter after 2.3.2 use UTF-8 as the character encoding. Previously the platform default was used. +
    + + + +

    This sampler lets you control a java class that implements the +org.apache.jmeter.protocol.java.sampler.JavaSamplerClient interface. +By writing your own implementation of this interface, +you can use JMeter to harness multiple threads, input parameter control, and +data collection.

    +

    The pull-down menu provides the list of all such implementations found by +JMeter in its classpath. The parameters can then be specified in the +table below - as defined by your implementation. Two simple examples (JavaTest and SleepTest) are provided. +

    +

    +The JavaTest example sampler can be useful for checking test plans, because it allows one to set +values in almost all the fields. These can then be used by Assertions, etc. +The fields allow variables to be used, so the values of these can readily be seen. +

    +
    + +The Add/Delete buttons don't serve any purpose at present. + + + Descriptive name for this sampler + that is shown in the tree. + The specific implementation of + the JavaSamplerClient interface to be sampled. + A list of + arguments that will be passed to the sampled class. All arguments + are sent as Strings. + +
    + +

    The sleep time is calculated as follows:

    +
    +SleepTime is in milliseconds
    +SleepMask is used to add a "random" element to the time:
    +totalSleepTime = SleepTime + (System.currentTimeMillis() % SleepMask)
    +
    + + +

    This sampler lets you send a SOAP request to a webservice. It can also be +used to send XML-RPC over HTTP. It creates an HTTP POST request, with the specified XML as the +POST content. +To change the "Content-type" from the default of "text/xml", use a HeaderManager. +Note that the sampler will use all the headers from the HeaderManager. +If a SOAP action is specified, that will override any SOAPaction in the HeaderManager. +The primary difference between the soap sampler and +webservice sampler, is the soap sampler uses raw post and does not require conformance to +SOAP 1.1.

    +For versions of JMeter later than 2.2, the sampler no longer uses chunked encoding by default.
    +For screen input, it now always uses the size of the data.
    +File input uses the file length as determined by Java.
    +On some OSes this may not work for all files, in which case add a child Header Manager +with Content-Length set to the actual length of the file.
    +Or set Content-Length to -1 to force chunked encoding. +
    +
    + + + Descriptive name for this sampler + that is shown in the tree. + The URL to direct the SOAP request to. + Send a SOAP action header? (overrides the Header Manager) + If set, sends Connection: keep-alive, else sends Connection: close + The Soap XML message, or XML-RPC instructions. + Not used if the filename is provided. + + If specified, then the contents of the file are sent, and the Data field is ignored + + +
    + + +

    This sampler has been tested with IIS Webservice running .NET 1.0 and .NET 1.1. + It has been tested with SUN JWSDP, IBM webservices, Axis and gSoap toolkit for C/C++. + The sampler uses Apache SOAP driver to serialize the message and set the header + with the correct SOAPAction. Right now the sampler doesn't support automatic WSDL + handling, since Apache SOAP currently does not provide support for it. Both IBM + and SUN provide WSDL drivers. There are 3 options for the post data: text area, + external file, or directory. If you want the sampler to randomly select a message, + use the directory. Otherwise, use the text area or a file. The if either the + file or path are set, it will not use the message in the text area. If you need + to test a soap service that uses different encoding, use the file or path. If you + paste the message in to text area, it will not retain the encoding and will result + in errors. Save your message to a file with the proper encoding, and the sampler + will read it as java.io.FileInputStream.

    +

    An important note on the sampler is it will automatically use the proxy host + and port passed to JMeter from command line, if those fields in the sampler are + left blank. If a sampler has values in the proxy host and port text field, it + will use the ones provided by the user. This behavior may not be what users + expect.

    +

    By default, the webservice sampler sets SOAPHTTPConnection.setMaintainSession + (true). If you need to maintain the session, add a blank Header Manager. The + sampler uses the Header Manager to store the SOAPHTTPConnection object, since + the version of apache soap does not provide a easy way to get and set the cookies.

    +

    Note: If you are using CSVDataSet, do not check "Memory Cache". If memory + cache is checked, it will not iterate to the next value. That means all the requests + will use the first value.

    +

    Make sure you use &lt;soap:Envelope rather than &lt;Envelope. For example:

    +
    +&lt;?xml version="1.0" encoding="utf-8"?>
    +&lt;soap:Envelope 
    +xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    +xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    +xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    +&lt;soap:Body>
    +&lt;foo xmlns="http://clients-xlmns"/>
    +&lt;/soap:Body>
    +&lt;/soap:Envelope>
    +
    +The SOAP library that is used does not support SOAP 1.2, only SOAP 1.1. +Also the library does not provide access to the HTTP response code (e.g. 200) or message (e.g. OK). +To get round this, versions of JMeter after 2.3.2 check the returned message length. +If this is zero, then the request is marked as failed. + +
    + + + Descriptive name for this sampler + that is shown in the tree. + The WSDL URL with the service description. + Versions of JMeter after 2.3.1 support the file: protocol for local WSDL files. + + Will be populated from the WSDL when the Load WSDL button is pressed. + Select one of the methods and press the Configure button to populate the Protocol, Server, Port, Path and SOAPAction fields. + + HTTP or HTTPS are acceptable protocol. + The hostname or IP address. + Port Number. + Connection timeout. + Path for the webservice. + The SOAPAction defined in the webservice description or WSDL. + The Soap XML message + File containing soap message + Folder containing soap files. Files are choose randomly during test. + + When using external files, setting this causes the file to be processed once and caches the result. + This may use a lot of memory if there are many different large files. + + Read the SOAP reponse (consumes performance). Permit to have assertions or post-processors + Check box if http proxy should be used + Proxy hostname + Proxy host port + + +
    + + + This Sampler lets you send a different Ldap request(Add, Modify, Delete and Search) to an LDAP server. +

    If you are going to send multiple requests to the same LDAP server, consider + using an + Configuration Element so you do not have to enter the same information for each + LDAP Request.

    The same way the also using for Login and password. +
    + +

    There are two ways to create test cases for testing an LDAP Server.

    +
    1. Inbuilt Test cases.
    2. +
    3. User defined Test cases.
    + +

    There are four test scenarios of testing LDAP. The tests are given below:

    +
      +
    1. Add Test
    2. +
      1. Inbuilt test : +

        This will add a pre-defined entry in the LDAP Server and calculate + the execution time. After execution of the test, the created entry will be + deleted from the LDAP + Server.

      2. +
      3. User defined test : +

        This will add the entry in the LDAP Server. User has to enter all the + attributes in the table.The entries are collected from the table to add. The + execution time is calculated. The created entry will not be deleted after the + test.

      + +
    3. Modify Test
    4. +
      1. Inbuilt test : +

        This will create a pre-defined entry first, then will modify the + created entry in the LDAP Server.And calculate the execution time. After + execution + of the test, the created entry will be deleted from the LDAP Server.

      2. +
      3. User defined test +

        This will modify the entry in the LDAP Server. User has to enter all the + attributes in the table. The entries are collected from the table to modify. + The execution time is calculated. The entry will not be deleted from the LDAP + Server.

      + +
    5. Search Test
    6. +
      1. Inbuilt test : +

        This will create the entry first, then will search if the attributes + are available. It calculates the execution time of the search query. At the + end of the execution,created entry will be deleted from the LDAP Server.

      2. +
      3. User defined test +

        This will search the user defined entry(Search filter) in the Search + base (again, defined by the user). The entries should be available in the LDAP + Server. The execution time is calculated.

      + +
    7. Delete Test
    8. +
      1. Inbuilt test : +

        This will create a pre-defined entry first, then it will be deleted + from the LDAP Server. The execution time is calculated.

      2. + +
      3. User defined test +

        This will delete the user-defined entry in the LDAP Server. The entries + should be available in the LDAP Server. The execution time is calculated.

    + + Descriptive name for this controller that is shown in the tree. + Domain name or IP address of the LDAP server. + JMeter assumes the LDAP server is listening on the default port(389). + default port(389). + DN for the server to communicate + LDAP server username. + LDAP server password. (N.B. this is stored unencrypted in the test plan) + the name of the context to create or Modify; may not be empty Example: do you want to add cn=apache,ou=test + you have to add in table name=cn, value=apache + + the name of the context to Delete; may not be empty + the name of the context or object to search + the filter expression to use for the search; may not be null + this name, value pair to added in the given context object + this name, value pair to add or modify in the given context object + + + + Building an Ldap Test Plan + + + +
    + + + This Sampler can send all 8 different LDAP request to an LDAP server. It is an extended version of the LDAP sampler, + therefore it is harder to configure, but can be made much closer resembling a real LDAP session. +

    If you are going to send multiple requests to the same LDAP server, consider + using an + Configuration Element so you do not have to enter the same information for each + LDAP Request.

    + +

    There are nine test operations defined. These operations are given below:

    +
      +
    1. Thread bind
    2. +

      Any LDAP request is part of an LDAP session, so the first thing that should be done is starting a session to the LDAP server. + For starting this session a thread bind is used, which is equal to the LDAP "bind" operation. + The user is requested to give a username (Distinguished name) and password, + which will be used to initiate a session. + When no password, or the wrong password is specified, an anonymous session is started. Take care, + omitting the password will not fail this test, a wrong password will. + (N.B. this is stored unencrypted in the test plan)

      + + Descriptive name for this sampler that is shown in the tree. + The name (or IP-address) of the LDAP server. + The port number that the LDAP server is listening to. If this is omitted + JMeter assumes the LDAP server is listening on the default port(389). + The distinguished name of the base object that will be used for any subsequent operation. + It can be used as a starting point for all operations. You cannot start any operation on a higher level than this DN! + Full distinguished name of the user as which you want to bind. + Password for the above user. If omitted it will result in an anonymous bind. + If is is incorrect, the sampler will return an error and revert to an anonymous bind. (N.B. this is stored unencrypted in the test plan) + +
      +
    3. Thread unbind
    4. +

      This is simply the operation to end a session. + It is equal to the LDAP "unbind" operation.

      + + Descriptive name for this sampler that is shown in the tree. + + +
      +
    5. Single bind/unbind
    6. +

      This is a combination of the LDAP "bind" and "unbind" operations. + It can be used for an authentication request/password check for any user. It will open an new session, just to + check the validity of the user/password combination, and end the session again.

      + + Descriptive name for this sampler that is shown in the tree. + Full distinguished name of the user as which you want to bind. + Password for the above user. If omitted it will result in an anonymous bind. + If is is incorrect, the sampler will return an error. (N.B. this is stored unencrypted in the test plan) + + +
      +
    7. Rename entry
    8. +

      This is the LDAP "moddn" operation. It can be used to rename an entry, but + also for moving an entry or a complete subtree to a different place in + the LDAP tree.

      + + Descriptive name for this sampler that is shown in the tree. + The current distinguished name of the object you want to rename or move, + relative to the given DN in the thread bind operation. + The new distinguished name of the object you want to rename or move, + relative to the given DN in the thread bind operation. + + +
      +
    9. Add test
    10. +

      This is the ldap "add" operation. It can be used to add any kind of + object to the LDAP server.

      + + Descriptive name for this sampler that is shown in the tree. + Distinguished name of the object you want to add, relative to the given DN in the thread bind operation. + A list of attributes and their values you want to use for the object. + If you need to add a multiple value attribute, you need to add the same attribute with their respective + values several times to the list. + + +
      +
    11. Delete test
    12. +

      This is the LDAP "delete" operation, it can be used to delete an + object from the LDAP tree

      + + Descriptive name for this sampler that is shown in the tree. + Distinguished name of the object you want to delete, relative to the given DN in the thread bind operation. + + +
      +
    13. Search test
    14. +

      This is the LDAP "search" operation, and will be used for defining searches.

      + + Descriptive name for this sampler that is shown in the tree. + Distinguished name of the subtree you want your + search to look in, relative to the given DN in the thread bind operation. + searchfilter, must be specified in LDAP syntax. + Use 0 for baseobject-, 1 for onelevel- and 2 for a subtree search. (Default=0) + Specify the maximum number of results you want back from the server. (default=0, which means no limit.) When the sampler hits the maximum number of results, it will fail with errorcode 4 + Specify the maximum amount of (cpu)time (in miliseconds) that the server can spend on your search. Take care, this does not say anything about the responsetime. (default is 0, which means no limit) + Specify the attributes you want to have returned, seperated by a semicolon. An empty field will return all attributes + Whether the object will be returned (true) or not (false). Default=false + If true, it will dereference aliases, if false, it will not follow them (default=false) + + +
      +
    15. Modification test
    16. +

      This is the LDAP "modify" operation. It can be used to modify an object. It + can be used to add, delete or replace values of an attribute.

      + + Descriptive name for this sampler that is shown in the tree. + Distinguished name of the object you want to modify, relative + to the given DN in the thread bind operation + The attribute-value-opCode triples. The opCode can be any + valid LDAP operationCode (add, delete/remove or replace). If you don't specify a value with a delete operation, + all values of the given attribute will be deleted. If you do specify a value in a delete operation, only + the given value will be deleted. If this value is non-existent, the sampler will fail the test. + + +
      +
    17. Compare
    18. +

      This is the LDAP "compare" operation. It can be used to compare the value + of a given attribute with some already known value. In reality this is mostly + used to check whether a given person is a member of some group. In such a case + you can compare the DN of the user as a given value, with the values in the + attribute "member" of an object of the type groupOfNames. + If the compare operation fails, this test fails with errorcode 49.

      + + Descriptive name for this sampler that is shown in the tree. + The current distinguished name of the object of + which you want to compare an attribute, relative to the given DN in the thread bind operation. + In the form "attribute=value" + +
    + + + Building an LDAP Test Plan + + + +
    + + + + + +

    (Alpha Code)

    +

    AccessLogSampler was designed to read access logs and generate http requests. +For those not familiar with the access log, it is the log the webserver maintains of every +request it accepted. This means the every image and html file. The current implementation +is complete, but some features have not been enabled. There is a filter for the access +log parser, but I haven't figured out how to link to the pre-processor. Once I do, changes +to the sampler will be made to enable that functionality.

    +

    Tomcat uses the common format for access logs. This means any webserver that uses the +common log format can use the AccessLogSampler. Server that use common log format include: +Tomcat, Resin, Weblogic, and SunOne. Common log format looks +like this:

    +

    127.0.0.1 - - [21/Oct/2003:05:37:21 -0500] "GET /index.jsp?%2Findex.jsp= HTTP/1.1" 200 8343

    +

    The current implemenation of the parser only looks at the text within the quotes. +Everything else is stripped out and igored. For example, the response code is completely +ignored by the parser. For the future, it might be nice to filter out entries that +do not have a response code of 200. Extending the sampler should be fairly simple. There +are two interfaces you have to implement.

    +

    org.apache.jmeter.protocol.http.util.accesslog.LogParser

    +

    org.apache.jmeter.protocol.http.util.accesslog.Generator

    +

    The current implementation of AccessLogSampler uses the generator to create a new +HTTPSampler. The servername, port and get images are set by AccessLogSampler. Next, +the parser is called with integer 1, telling it to parse one entry. After that, +HTTPSampler.sample() is called to make the request. + +

    +            samp = (HTTPSampler) GENERATOR.generateRequest();
    +            samp.setDomain(this.getDomain());
    +            samp.setPort(this.getPort());
    +            samp.setImageParser(this.isImageParser());
    +            PARSER.parse(1);
    +            res = samp.sample();
    +            res.setSampleLabel(samp.toString());
    +
    + +The required methods in LogParser are: setGenerator(Generator) and parse(int). +Classes implementing Generator interface should provide concrete implementation +for all the methods. For an example of how to implement either interface, refer to +StandardGenerator and TCLogParser. +

    +
    + + + Descriptive name for this controller that is shown in the tree. + Domain name or IP address of the web server. + Port the web server is listening to. + The log parser class is responsible for parsing the logs. + The filter class is used to filter out certain lines. + The location of the access log file. + +

    +The TCLogParser processes the access log independently for each thread. +The SharedTCLogParser and OrderPreservingLogParser share access to the file, +i.e. each thread gets the next entry in the log. +

    +

    +The SessionFilter is intended to handle Cookies across threads. +It does not filter out any entries, but modifies the cookie manager so that the cookies for a given IP are +processed by a single thread at a time. If two threads try to process samples from the same client IP address, +then one will be forced to wait until the other has completed. +

    +

    +The LogFilter is intended to allow access log entries to be filtered by filename and regex, +as well as allowing for the replacement of file extensions. However, it is not currently possible +to configure this via the GUI, so it cannot really be used. +

    +
    + + +

    This sampler allows you to write a sampler using the BeanShell scripting language. +

    +For full details on using BeanShell, please see the BeanShell website. +

    +

    +The test element supports the ThreadListener and TestListener interface methods. +These must be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +

    +From JMeter version 2.5.1, the BeanShell sampler also supports the Interruptible interface. +The interrupt() method can be defined in the script or the init file. +

    +
    + + Descriptive name for this controller that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + This is intended for use with script files; for scripts defined in the GUI, you can use whatever + variable and function references you need within the script itself. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    + A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The BeanShell script to run. + The return value (if not null) is stored as the sampler result. +
    +

    +N.B. Each Sampler instance has its own BeanShell interpeter, +and Samplers are only called from a single thread +

    +If the property "beanshell.sampler.init" is defined, it is passed to the Interpreter +as the name of a sourced file. +This can be used to define common methods and variables. +There is a sample init file in the bin directory: BeanShellSampler.bshrc. +

    +If a script file is supplied, that will be used, otherwise the script will be used.

    +

    Before invoking the script, some variables are set up in the BeanShell interpreter: +

    +

    The contents of the Parameters field is put into the variable "Parameters". + The string is also split into separate tokens using a single space as the separator, and the resulting list + is stored in the String array bsh.args.

    +

    The full list of BeanShell variables that is set up is as follows:

    +
      +
    • log - the Logger
    • +
    • Label - the Sampler label
    • +
    • FileName - the file name, if any
    • +
    • Parameters - text from the Parameters field
    • +
    • bsh.args - the parameters, split as described above
    • +
    • SampleResult - pointer to the current SampleResult
    • +
    • ResponseCode = 200
    • +
    • ResponseMessage = "OK"
    • +
    • IsSuccess = true
    • +
    • ctx - JMeterContext
    • +
    • vars - JMeterVariables - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.remove("VAR3"); vars.putObject("OBJ1",new Object());
    • +
    • props - JMeterProperties (class java.util.Properties)- e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    +

    When the script completes, control is returned to the Sampler, and it copies the contents + of the following script variables into the corresponding variables in the SampleResult:

    +
      +
    • ResponseCode - for example 200
    • +
    • ResponseMessage - for example "OK"
    • +
    • IsSuccess - true/false
    • +
    +

    The SampleResult ResponseData is set from the return value of the script. + Since version 2.1.2, if the script returns null, it can set the response directly, by using the method + SampleResult.setResponseData(data), where data is either a String or a byte array. + The data type defaults to "text", but can be set to binary by using the method + SampleResult.setDataType(SampleResult.BINARY). +

    +

    The SampleResult variable gives the script full access to all the fields and + methods in the SampleResult. For example, the script has access to the methods + setStopThread(boolean) and setStopTest(boolean). + + Here is a simple (not very useful!) example script:

    + +
    +if (bsh.args[0].equalsIgnoreCase("StopThread")) {
    +    log.info("Stop Thread detected!");
    +    SampleResult.setStopThread(true);
    +}
    +return "Data from sample with Label "+Label;
    +//or, since version 2.1.2
    +SampleResult.setResponseData("My data");
    +return null;
    +
    +

    Another example:

    ensure that the property beanshell.sampler.init=BeanShellSampler.bshrc is defined in jmeter.properties. +The following script will show the values of all the variables in the ResponseData field: +

    +
    +return getVariables();
    +
    +

    +For details on the methods available for the various classes (JMeterVariables, SampleResult etc) please check the Javadoc or the source code. +Beware however that misuse of any methods can cause subtle faults that may be difficult to find ... +

    +
    + + + +

    This sampler allows you to write a sampler using a BSF scripting language.

    + See the Apache Bean Scripting Framework + website for details of the languages supported. + You may need to download the appropriate jars for the language; they should be put in the JMeter lib directory. +

    +

    By default, JMeter supports the following languages:

    +
      +
    • javascript
    • +
    • jexl (JMeter version 2.3.2 and later)
    • +
    • xslt
    • +
    + Unlike the BeanShell sampler, the interpreter is not saved between invocations. +
    + + Descriptive name for this controller that is shown in the tree. + Name of the BSF scripting language to be used. + N.B. Not all the languages in the drop-down list are supported by default. + The following are supported: jexl, javascript, xslt. + Others may be available if the appropriate jar is installed in the JMeter lib directory. + + Name of a file to be used as a BSF script + List of parameters to be passed to the script file or the script. + Script to be passed to BSF language + +

    +If a script file is supplied, that will be used, otherwise the script will be used.

    +

    +Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. +

    +
      +
    • log - the Logger
    • +
    • Label - the Sampler label
    • +
    • FileName - the file name, if any
    • +
    • Parameters - text from the Parameters field
    • +
    • args - the parameters, split as described above
    • +
    • SampleResult - pointer to the current SampleResult
    • +
    • sampler - pointer to current Sampler
    • +
    • ctx - JMeterContext
    • +
    • vars - JMeterVariables - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.remove("VAR3"); vars.putObject("OBJ1",new Object());
    • +
    • props - JMeterProperties (class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    +

    +The SampleResult ResponseData is set from the return value of the script. +If the script returns null, it can set the response directly, by using the method +SampleResult.setResponseData(data), where data is either a String or a byte array. +The data type defaults to "text", but can be set to binary by using the method +SampleResult.setDataType(SampleResult.BINARY). +

    +

    +The SampleResult variable gives the script full access to all the fields and +methods in the SampleResult. For example, the script has access to the methods +setStopThread(boolean) and setStopTest(boolean). +

    +

    +Unlike the Beanshell Sampler, the BSF Sampler does not set the ResponseCode, ResponseMessage and sample status via script variables. +Currently the only way to changes these is via the SampleResult methods: +

      +
    • SampleResult.setSuccessful(true/false)
    • +
    • SampleResult.setResponseCode("code")
    • +
    • SampleResult.setResponseMessage("message")
    • +
    +

    +
    + + + +

    +The JSR223 Sampler allows JSR223 script code to be used to perform a sample. +For details, see . +

    +Unlike the BeanShell sampler, the interpreter is not saved between invocations. +
    +
    + + + +

    + The TCP Sampler opens a TCP/IP connection to the specified server. + It then sends the text, and waits for a response. +

    + If "Re-use connection" is selected, connections are shared between Samplers in the same thread, + provided that the exact same host name string and port are used. + Different hosts/port combinations will use different connections, as will different threads. +

    + If an error is detected - or "Re-use connection" is not selected - the socket is closed. + Another socket will be reopened on the next sample. +

    + The following properties can be used to control its operation: +

    +
      +
    • tcp.status.prefix - text that precedes a status number
    • +
    • tcp.status.suffix - text that follows a status number
    • +
    • tcp.status.properties - name of property file to convert status codes to messages
    • +
    • tcp.handler - Name of TCP Handler class (default TCPClientImpl) - only used if not specified on the GUI
    • +
    + The class that handles the connection is defined by the GUI, failing that the property tcp.handler. + If not found, the class is then searched for in the package org.apache.jmeter.protocol.tcp.sampler. +

    + Users can provide their own implementation. + The class must extend org.apache.jmeter.protocol.tcp.sampler.TCPClient. +

    +

    + The following implementations are currently provided. +

      +
    • TCPClientImpl
    • +
    • BinaryTCPClientImpl
    • +
    • LengthPrefixedBinaryTCPClientImpl
    • +
    + The implementations behave as follows: +

    +

    TCPClientImpl

    + This implementation is fairly basic. + When reading the response, it reads until the end of line byte, if this is defined + by setting the property tcp.eolByte, otherwise until the end of the input stream. + You can control charset encoding by setting tcp.charset, which will default to Platform default encoding. +

    +

    BinaryTCPClientImpl

    + This implementation converts the GUI input, which must be a hex-encoded string, into binary, + and performs the reverse when reading the response. + When reading the response, it reads until the end of message byte, if this is defined + by setting the property tcp.BinaryTCPClient.eomByte, otherwise until the end of the input stream. +

    +

    LengthPrefixedBinaryTCPClientImpl

    + This implementation extends BinaryTCPClientImpl by prefixing the binary message data with a binary length byte. + The length prefix defaults to 2 bytes. + This can be changed by setting the property tcp.binarylength.prefix.length. +

    +

    Timeout handling + If the timeout is set, the read will be terminated when this expires. + So if you are using an eolByte/eomByte, make sure the timeout is sufficiently long, + otherwise the read will be terminated early. +

    +

    Response handling +

    + If tcp.status.prefix is defined, then the response message is searched for the text following + that up to the suffix. If any such text is found, it is used to set the response code. + The response message is then fetched from the properties file (if provided). +

    + For example, if the prefix = "[" and the suffix = "]", then the following repsonse: +

    + [J28] XI123,23,GBP,CR +

    + would have the response code J28. +

    + Response codes in the range "400"-"499" and "500"-"599" are currently regarded as failures; + all others are successful. [This needs to be made configurable!] +

    +The login name/password are not used by the supplied TCP implementations. +

    + Sockets are disconnected at the end of a test run. +
    + + Descriptive name for this element that is shown in the tree. + Name of the TCPClient class. Defaults to the property tcp.handler, failing that TCPClientImpl. + Name or IP of TCP server + Port to be used + If selected, the connection is kept open. Otherwise it is closed when the data has been read. + Connect Timeout (milliseconds, 0 disables). + Response Timeout (milliseconds, 0 disables). + See java.net.Socket.setTcpNoDelay(). + If selected, this will disable Nagle's algorithm, otherwise Nagle's algorithm will be used. + Text to be sent + User Name - not used by default implementation + Password - not used by default implementation (N.B. this is stored unencrypted in the test plan) + +
    + + +BETA CODE - the code is still subject to change + +

    + JMS Publisher will publish messages to a given destination (topic/queue). For those not + familiar with JMS, it is the J2EE specification for messaging. There are + numerous JMS servers on the market and several open source options. +

    +

    +JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory +
    + + Descriptive name for this element that is shown in the tree. + use jndi.properties. + Note that the file must be on the classpath - e.g. by updating the user.classpath JMeter property. + If this option is not selected, JMeter uses the "JNDI Initial Context Factory" and "Provider URL" fields + to create the connection. + + Name of the context factory + The URL for the jms provider + The message destination (topic or queue name) + The destination setup type. With At startup, the destination name is static (i.e. always same name during the test), with Each sample, the destination name is dynamic and is evaluate at each sample (i.e. the destination name may be a variable) + Authentication requirement for the JMS provider + User Name + Password (N.B. this is stored unencrypted in the test plan) + Number of samples to aggregate + Where to obtain the message + Text, Map or Object message + + Whether to set DeliveryMode.NON_PERSISTENT (defaults to false) + + + The JMS Properties are properties specific for the underlying messaging system. + For example: for WebSphere 5.1 web services you will need to set the JMS Property targetService to test + webservices through JMS. + + + +

    +For the MapMessage type, JMeter reads the source as lines of text. +Each line must have 3 fields, delimited by commas. +The fields are: +

      +
    • Name of entry
    • +
    • Object class name, e.g. "String" (assumes java.lang package if not specified)
    • +
    • Object string value
    • +
    +For each entry, JMeter adds an Object with the given name. +The value is derived by creating an instance of the class, and using the valueOf(String) method to convert the value if necessary. +For example: +
    +name,String,Example
    +size,Integer,1234
    +
    +This is a very simple implementation; it is not intended to support all possible object types. +

    +

    + +The Object message is implemented since 2.7 and works as follow: +

      +
    • Put the JAR that contain you object and its dependencies in jmeter_home/lib/ folder
    • +
    • Serialize your object as XML using XStream
    • +
    • Either put result in a file suffixed with .txt or .obj or put XML content direclty in Text Area
    • +
    +Note that if message is in an file, replacement of properties will not occur while it will happen if you use Text Area. + + +

    +

    +The following table shows some values which may be useful when configuring JMS: + + + + + + + + + + + + + + +
    Apache ActiveMQValue(s)Comment
    Context Factoryorg.apache.activemq.jndi.ActiveMQInitialContextFactory.
    Provider URLvm://localhost
    Provider URLvm:(broker:(vm://localhost)?persistent=false)Disable persistence
    Queue ReferencedynamicQueues/QUEUENAMEDynamically define the QUEUENAME to JNDI
    Topic ReferencedynamicTopics/TOPICNAMEDynamically define the TOPICNAME to JNDI
    +

    +
    + + +BETA CODE - the code is still subject to change + +

    + JMS Publisher will subscribe to messages in a given destination (topic or queue). For those not + familiar with JMS, it is the J2EE specification for messaging. There are + numerous JMS servers on the market and several open source options. +

    +

    +JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory +
    + + Descriptive name for this element that is shown in the tree. + use jndi.properties. + Note that the file must be on the classpath - e.g. by updating the user.classpath JMeter property. + If this option is not selected, JMeter uses the "JNDI Initial Context Factory" and "Provider URL" fields + to create the connection. + + Name of the context factory + The URL for the jms provider + the message destination (topic or queue name) + The ID to use for a durable subscription. On first + use the respective queue will automatically be generated by the JMS provider if it does not exist yet. + The Client ID to use when you you use a durable subscription. + Be sure to add a variable like ${__threadNum} when you have more than one Thread. + Message Selector as defined by JMS specification to extract only + messages that respect the Selector condition. Syntax uses subpart of SQL 92. + The destination setup type. With At startup, the destination name is static (i.e. always same name during the test), with Each sample, the destination name is dynamic and is evaluate at each sample (i.e. the destination name may be a variable) + Authentication requirement for the JMS provider + User Name + Password (N.B. this is stored unencrypted in the test plan) + number of samples to aggregate + should the sampler read the response. If not, only the response length is returned. + Specify the timeout to be applied, in milliseconds. 0=none. + This is the overall aggregate timeout, not per sample. + Which client implementation to use. + Both of them create connections which can read messages. However they use a different strategy, as described below: +
      +
    • MessageConsumer.receive() - calls receive() for every requested message. + Retains the connection between samples, but does not fetch messages unless the sampler is active. + This is best suited to Queue subscriptions. +
    • +
    • MessageListener.onMessage() - establishes a Listener that stores all incoming messages on a queue. + The listener remains active after the sampler completes. + This is best suited to Topic subscriptions.
    • +
    +
    + + If selected, then JMeter calls Connection.stop() at the end of each sample (and calls start() before each sample). + This may be useful in some cases where multiple samples/threads have connections to the same queue. + If not selected, JMeter calls Connection.start() at the start of the thread, and does not call stop() until the end of the thread. + + + Separator used to separate messages when there is more than one (related to setting Number of samples to aggregate). + Note that \n, \r, \t are accepted. + +
    +

    +NOTE: JMeter 2.3.4 and earlier used a different strategy for the MessageConsumer.receive() client. +Previously this started a background thread which polled for messages. This thread continued when the sampler +completed, so the net effect was similar to the MessageListener.onMessage() strategy. +

    +
    + + +BETA CODE - the code is still subject to change + +

    + This sampler sends and optionally receives JMS Messages through point-to-point connections (queues). + It is different from pub/sub messages and is generally used for handling transactions. +

    +

    + Versions of JMeter after 2.3.2 use the properties java.naming.security.[principal|credentials] - if present - + when creating the Queue Connection. If this behaviour is not desired, set the JMeter property + JMSSampler.useSecurity.properties=false +

    +

    +JMeter does not include any JMS implementation jar; this must be downloaded from the JMS provider and put in the lib directory +
    + + Descriptive name for this element that is shown in the tree. + + The JNDI name of the queue connection factory to use for connecting to the messaging system. + + + This is the JNDI name of the queue to which the messages are sent. + + + The JNDI name of the receiving queue. If a value is provided here and the communication style is Request Response + this queue will be monitored for responses to the requests sent. + + + Message Selector as defined by JMS specification to extract only + messages that respect the Selector condition. Syntax uses subpart of SQL 92. + + + The Communication style can be Request Only (also known as Fire and Forget) or Request Reply. + Request Only will only sent messages and will not monitor replies. As such it can be used to put load on a system. + Request Reply will sent messages and monitor the replies it receives. Behaviour is depended on the value of the JNDI Name Reply Queue. + If JNDI Name Reply Queue has a value, this queue is used to monitor the results. Matching of request and reply is done with + the message id of the request with the correlation id of the reply. If the JNDI Name Reply Queue is empty, then + temporary queues will be used for the communication between the requestor and the server. This is very different from + the fixed reply queue. With temporary queues the diffent threads will block until the reply message has been received. + + + These check-boxes select the fields which will be used for matching the response message with the original request. +
      +
    • Use Request Message Id - if selected, the request JMSMessageID will be used, + otherwise the request JMSCorrelationID will be used. + In the latter case the correlation id must be specified in the request.
    • +
    • Use Response Message Id - if selected, the response JMSMessageID will be used, + otherwise the response JMSCorrelationID will be used. +
    • +
    + There are two frequently used JMS Correlation patterns: +
      +
    • JMS Correlation ID Pattern - + i.e. match request and response on their correlation Ids + => deselect both checkboxes, and provide a correlation id.
    • +
    • JMS Message ID Pattern - + i.e. match request message id with response correlation id + => select "Use Request Message Id" only. +
    • +
    + In both cases the JMS application is responsible for populating the correlation ID as necessary. + Note: if the same queue is used to send and receive messages, + then the response message will be the same as the request message. + In which case, either provide a correlation id and clear both checkboxes; + or select both checkboxes to use the message Id for correlation. + This can be useful for checking raw JMS throughput. +
    + + The timeout in milliseconds for the reply-messages. If a reply has not been received within the specified + time, the specific testcase failes and the specific reply message received after the timeout is discarded. + + + Whether to set DeliveryMode.NON_PERSISTENT. + + + The content of the message. + + + The JMS Properties are properties specific for the underlying messaging system. + For example: for WebSphere 5.1 web services you will need to set the JMS Property targetService to test + webservices through JMS. + + + The Initial Context Factory is the factory to be used to look up the JMS Resources. + + + The JNDI Properties are the specific properties for the underlying JNDI implementation. + + + The URL for the jms provider. + +
    +
    + + + + + +The current implementation supports standard Junit convention and extensions. It also +includes extensions like oneTimeSetUp and oneTimeTearDown. The sampler works like the +JavaSampler with some differences. +

    1. rather than use Jmeter's test interface, it scans the jar files for classes extending junit's TestCase class. That includes any class or subclass. +

    2. Junit test jar files should be placed in jmeter/lib/junit instead of /lib directory. +In versions of JMeter after 2.3.1, you can also use the "user.classpath" property to specify where to look for TestCase classes. +

    3. Junit sampler does not use name/value pairs for configuration like the JavaSampler. The sampler assumes setUp and tearDown will configure the test correctly. +

    4. The sampler measures the elapsed time only for the test method and does not include setUp and tearDown. +

    5. Each time the test method is called, Jmeter will pass the result to the listeners. +

    6. Support for oneTimeSetUp and oneTimeTearDown is done as a method. Since Jmeter is multi-threaded, we cannot call oneTimeSetUp/oneTimeTearDown the same way Maven does it. +

    7. The sampler reports unexpected exceptions as errors. +There are some important differences between standard JUnit test runners and JMeter's +implementation. Rather than make a new instance of the class for each test, JMeter +creates 1 instance per sampler and reuses it. +This can be changed with checkbox "Create a new instance per sample".

    +The current implementation of the sampler will try to create an instance using the string constructor first. If the test class does not declare a string constructor, the sampler will look for an empty constructor. Example below:<br> +<br> +Empty Constructor:<br> +public class myTestCase {<br> + public myTestCase() {}<br> +}<br> +<br> +String Constructor:<br> +public class myTestCase {<br> + public myTestCase(String text) {<br> + super(text);<br> + }<br> +}<br> +By default, Jmeter will provide some default values for the success/failure code and message. Users should define a set of unique success and failure codes and use them uniformly across all tests.<br> +General Guidelines

    +If you use setUp and tearDown, make sure the methods are declared public. If you do not, the test may not run properly. +

    +Here are some general guidelines for writing Junit tests so they work well with Jmeter. Since Jmeter runs multi-threaded, it is important to keep certain things in mind.<br> +<br> +1. Write the setUp and tearDown methods so they are thread safe. This generally means avoid using static memebers.<br> +2. Make the test methods discrete units of work and not long sequences of actions. By keeping the test method to a descrete operation, it makes it easier to combine test methods to create new test plans.<br> +3. Avoid making test methods depend on each other. Since Jmeter allows arbitrary sequencing of test methods, the runtime behavior is different than the default Junit behavior.<br> +4. If a test method is configurable, be careful about where the properties are stored. Reading the properties from the Jar file is recommended.<br> +5. Each sampler creates an instance of the test class, so write your test so the setup happens in oneTimeSetUp and oneTimeTearDown. +
    + + Descriptive name for this element that is shown in the tree. + Select this to search for JUnit 4 tests (@Test annotations) + Comma separated list of packages to show. Example, org.apache.jmeter,junit.framework. + Fully qualified name of the JUnit test class. + String pass to the string constructor. If a string is set, the sampler will use the + string constructor instead of the empty constructor. + The method to test. + A descriptive message indicating what success means. + An unique code indicating the test was successful. + A descriptive message indicating what failure means. + An unique code indicating the test failed. + A description for errors. + Some code for errors. Does not need to be unique. + Set the sampler not to call setUp and tearDown. + By default, setUp and tearDown should be called. Not calling those methods could affect the test and make it inaccurate. + This option should only be used with calling oneTimeSetUp and oneTimeTearDown. If the selected method is oneTimeSetUp or oneTimeTearDown, + this option should be checked. + Whether or not to append assertion errors to the response message. + Whether or not to append runtime exceptions to the response message. Only applies if "Append assertion errors" is not selected. + Whether or not to create a new JUnit instance for each sample. Defaults to false, meaning JUnit TestCase is created one and reused. + +

    +The following JUnit4 annotations are recognised: +

      +
    • @Test - used to find test methods and classes. The "expected" and "timeout" attributes are supported.
    • +
    • @Before - treated the same as setUp() in JUnit3
    • +
    • @After - treated the same as tearDown() in JUnit3
    • +
    • @BeforeClass, @AfterClass - treated as test methods so they can be run independently as required
    • +
    +

    +

    +Note that JMeter currently runs the test methods directly, rather than leaving it to JUnit. +This is to allow the setUp/tearDown methods to be excluded from the sample time. +

    +
    + + + +

    +The Mail Reader Sampler can read (and optionally delete) mail messages using POP3(S) or IMAP(S) protocols. +

    +
    + +Descriptive name for this element that is shown in the tree. +The protocol used by the provider: e.g. pop3, pop3s, imap, imaps. +or another string representing the server protocol. +For example file for use with the read-only mail file provider. +The actual provider names for POP3 and IMAP are pop3 and imap + +Hostname or IP address of the server. See below for use with file protocol. +Port to be used to connect to the server (optional) +User login name +User login password (N.B. this is stored unencrypted in the test plan) +The IMAP(S) folder to use. See below for use with file protocol. +Set this to retrieve all or some messages +If set, messages will be deleted after retrieval +Whether to store the message as MIME. +If so, then the entire raw message is stored in the Response Data; the headers are not stored as they are available in the data. +If not, the message headers are stored as Response Headers. +A few headers are stored (Date, To, From, Subject) in the body. + +Indicates that the connection to the server does not use any security protocol. +Indicates that the connection to the server must use the SSL protocol. +Indicates that the connection to the server should attempt to start the TLS protocol. +If the server does not start the TLS protocol the connection will be terminated. +When selected it will accept all certificates independent of the CA. +When selected it will only accept certificates that are locally trusted. +Path to file containing the trusted certificates. +Relative paths are resolved against the current directory. +
    Failing that, against the directory containing the test script (JMX file). +
    +
    +

    +Messages are stored as subsamples of the main sampler. +In versions of JMeter after 2.3.4, multipart message parts are stored as subsamples of the message. +

    +

    +Special handling for "file" protocol:

    +The file JavaMail provider can be used to read raw messages from files. +The server field is used to specify the path to the parent of the folder. +Individual message files should be stored with the name n.msg, +where n is the message number. +Alternatively, the server field can be the name of a file which contains a single message. +The current implementation is quite basic, and is mainly intended for debugging purposes. +

    +
    + + + +The Test Action sampler is a sampler that is intended for use in a conditional controller. +Rather than generate a sample, the test element eithers pauses or stops the selected target. +

    This sampler can also be useful in conjunction with the Transaction Controller, as it allows +pauses to be included without needing to generate a sample. +For variable delays, set the pause time to zero, and add a Timer as a child.

    +

    +The "Stop" action stops the thread or test after completing any samples that are in progress. +The "Stop Now" action stops the test without waiting for samples to complete; it will interrupt any active samples. +If some threads fail to stop within the 5 second time-limit, a message will be displayed in GUI mode. +You can try using the Stop command to see if this will stop the threads, but if not, you should exit JMeter. +In non-GUI mode, JMeter will exit if some threads fail to stop within the 5 second time limit. +[This can be changed using the JMeter property jmeterengine.threadstop.wait] +

    +
    + + Descriptive name for this element that is shown in the tree. + Current Thread / All Threads (ignored for Pause) + Pause / Stop / Stop Now + How long to pause for (milliseconds) + +
    + + + + +

    +The SMTP Sampler can send mail messages using SMTP/SMTPS protocol. +It is possible to set security propocols for the connection (SSL and TLS), as well as user authentication. +If a security protocol is used a verification on the server certificate will occur.

    +Two alternatives to handle this verification are available:

    +

      +
    • Trust all certificates. This will ignore certificate chain verification
    • +
    • Use a local truststore. With this option the certificate chain will be validated against the local truststore file.
    • +
    +

    +
    + +Hostname or IP address of the server. See below for use with file protocol. +Port to be used to connect to the server. +Defaults are: SMTP=25, SSL=465, StartTLS=587 + +The from address that will appear in the e-mail +The destination e-mail address (multiple values separated by ";") +Carbon copy destinations e-mail address (multiple values separated by ";") +Blind carbon copy destinations e-mail address (multiple values separated by ";") +Alternate Reply-To address (multiple values separated by ";") +Indicates if the SMTP server requires user authentication +User login name +User login password (N.B. this is stored unencrypted in the test plan) +Indicates that the connection to the SMTP server does not use any security protocol. +Indicates that the connection to the SMTP server must use the SSL protocol. +Indicates that the connection to the SMTP server should attempt to start the TLS protocol. +If the server does not start the TLS protocol the connection will be terminated. +When selected it will accept all certificates independent of the CA. +When selected it will only accept certificates that are locally trusted. +Path to file containing the trusted certificates. +Relative paths are resolved against the current directory. +
    Failing that, against the directory containing the test script (JMX file). +
    +The e-mail message subject. +If selected, the "Subject:" header is omitted from the mail that is sent. +This is different from sending an empty "Subject:" header, though some e-mail clients may display it identically. +Includes the System.currentTimemilis() in the subject line. +Additional headers can be defined using this button. +The message body. + +If selected, then send the body as a plain message, i.e. not multipart/mixed, if possible. +If the message body is empty and there is a single file, then send the file contents as the message body. +Note: If the message body is not empty, and there is at least one attached file, then the body is sent as multipart/mixed. + +Files to be attached to the message. +If set, the .eml file will be sent instead of the entries in the Subject, Message, and Attached files +Calculates the message size and stores it in the sample result. +If set, then the "mail.debug" property is set to "true" +
    +
    + + + +

    +The OS Process Sampler is a sampler that can be used to execute commands on the local machine.

    +It should allow execution of any command that can be run from the command line.

    +Validation of the return code can be enabled, and the expected return code can be specified.

    +

    +

    +Note that OS shells generally provide command-line parsing. +This varies between OSes, but generally the shell will split parameters on white-space. +Some shells expand wild-card file names; some don't. +The quoting mechanism also varies between OSes. +The sampler deliberately does not do any parsing or quote handling. +The command and its parameters must be provided in the form expected by the executable. +This means that the sampler settings will not be portable between OSes. +

    +

    +Many OSes have some built-in commands which are not provided as separate executables. +For example the Windows DIR command is part of the command interpreter (CMD.EXE). +These built-ins cannot be run as independent programs, but have to be provided as arguments to the appropriate command interpreter. +
    +For example, the Windows command-line: DIR C:\TEMP needs to be specified as follows: +

    +command:   CMD
    +Param 1:   /C
    +Param 2:   DIR
    +Param 3:   C:\TEMP
    +
    +

    +
    + +If checked, sampler will compare return code with Expected Return Code. +Expected return code for System Call, required if "Check Return Code" is checked. +Directory from which command will be executed, defaults to folder referenced by "user.dir" System property +The System command or shell to execute. +Parameters passed to process. +Key/Value pairs added to environment when running command. + +
    + +^ + +
    + +
    + +
    Logic Controllers determine the order in which Samplers are processed.
    +
    + + + +

    The Simple Logic Controller lets you organize your Samplers and other +Logic Controllers. Unlike other Logic Controllers, this controller provides no functionality beyond that of a +storage device.

    +
    + + Descriptive name for this controller that is shown in the tree. + + + +

    Download this example (see Figure 6). +In this example, we created a Test Plan that sends two Ant HTTP requests and two +Log4J HTTP requests. We grouped the Ant and Log4J requests by placing them inside +Simple Logic Controllers. Remember, the Simple Logic Controller has no effect on how JMeter +processes the controller(s) you add to it. So, in this example, JMeter sends the requests in the +following order: Ant Home Page, Ant News Page, Log4J Home Page, Log4J History Page. +Note, the File Reporter +is configured to store the results in a file named "simple-test.dat" in the current directory.

    +
    Figure 6 Simple Controller Example
    + +
    +
    + + +

    If you add Generative or Logic Controllers to a Loop Controller, JMeter will +loop through them a certain number of times, in addition to the loop value you +specified for the Thread Group. For example, if you add one HTTP Request to a +Loop Controller with a loop count of two, and configure the Thread Group loop +count to three, JMeter will send a total of 2 * 3 = 6 HTTP Requests. +

    + + + Descriptive name for this controller that is shown in the tree. + + The number of times the subelements of this controller will be iterated each time + through a test run. +

    Special Case: The Loop Controller embedded in the Thread Group + element behaves slightly differently. Unless set to forever, it stops the test after + the given number of iterations have been done.

    +
    + + + +

    Download this example (see Figure 4). +In this example, we created a Test Plan that sends a particular HTTP Request +only once and sends another HTTP Request five times.

    + +
    Figure 4 - Loop Controller Example
    + +

    We configured the Thread Group for a single thread and a loop count value of +one. Instead of letting the Thread Group control the looping, we used a Loop +Controller. You can see that we added one HTTP Request to the Thread Group and +another HTTP Request to a Loop Controller. We configured the Loop Controller +with a loop count value of five.

    +

    JMeter will send the requests in the following order: Home Page, News Page, +News Page, News Page, News Page, and News Page. Note, the File Reporter +is configured to store the results in a file named "loop-test.dat" in the current directory.

    + +
    + +
    + + + +

    The Once Only Logic Controller tells JMeter to process the controller(s) inside it only once, and pass over any requests under it +during further iterations through the test plan.

    + +

    The Once Only Controller will now execute always during the first iteration of any looping parent controller. Thus, if the Once Only Controller is placed under a Loop Controller specified to loop 5 times, then the Once Only Controller will execute only on the first iteration through the Loop Controller (ie, every 5 times). Note this means the Once Only Controller will still behave as previously expected if put under a Thread Group (runs only once per test), but now the user has more flexibility in the use of the Once Only Controller.

    + +

    For testing that requires a login, consider placing the login request in this controller since each thread only needs +to login once to establish a session.

    +
    + + Descriptive name for this controller that is shown in the tree. + + + +

    Download this example (see Figure 5). +In this example, we created a Test Plan that has two threads that send HTTP request. +Each thread sends one request to the Home Page, followed by three requests to the Bug Page. +Although we configured the Thread Group to iterate three times, each JMeter thread only +sends one request to the Home Page because this request lives inside a Once Only Controller.

    +
    Figure 5. Once Only Controller Example
    +

    Each JMeter thread will send the requests in the following order: Home Page, Bug Page, +Bug Page, Bug Page. Note, the File Reporter is configured to store the results in a file named "loop-test.dat" in the current directory.

    + +
    +The behaviour of the Once Only controller under anything other than the +Thread Group or a Loop Controller is not currently defined. Odd things may happen. +
    + + +

    If you add Generative or Logic Controllers to an Interleave Controller, JMeter will alternate among each of the +other controllers for each loop iteration.

    +
    + + Descriptive name for this controller that is shown in the tree. + If checked, the interleave controller will treat sub-controllers like single request elements and only allow one request per controller at a time. + + + + + +

    Download this example (see Figure 1). In this example, +we configured the Thread Group to have two threads and a loop count of five, for a total of ten +requests per thread. See the table below for the sequence JMeter sends the HTTP Requests.

    + +
    Figure 1 - Interleave Controller Example 1
    + + + + + + + + + + + + + +
    Loop IterationEach JMeter Thread Sends These HTTP Requests
    1News Page
    1Log Page
    2FAQ Page
    2Log Page
    3Gump Page
    3Log Page
    4Because there are no more requests in the controller,

    JMeter starts over and sends the first HTTP Request, which is the News Page.
    4Log Page
    5FAQ Page
    5Log Page
    + + +
    + + + +

    Download another example (see Figure 2). In this +example, we configured the Thread Group +to have a single thread and a loop count of eight. Notice that the Test Plan has an outer Interleave Controller with +two Interleave Controllers inside of it.

    + +
    + Figure 2 - Interleave Controller Example 2 +
    + +

    The outer Interleave Controller alternates between the +two inner ones. Then, each inner Interleave Controller alternates between each of the HTTP Requests. Each JMeter +thread will send the requests in the following order: Home Page, Interleaved, Bug Page, Interleaved, CVS Page, Interleaved, and FAQ Page, Interleaved. +Note, the File Reporter is configured to store the results in a file named "interleave-test2.dat" in the current directory.

    + +
    + Figure 3 - Interleave Controller Example 3 +
    +

    If the two interleave controllers under the main interleave controller were instead simple controllers, then the order would be: Home Page, CVS Page, Interleaved, Bug Page, FAQ Page, Interleaved. However, if "ignore sub-controller blocks" was checked on the main interleave controller, then the order would be: Home Page, Interleaved, Bug Page, Interleaved, CVS Page, Interleaved, and FAQ Page, Interleaved.

    +
    +
    + + + +

    The Random Logic Controller acts similarly to the Interleave Controller, except that +instead of going in order through its sub-controllers and samplers, it picks one +at random at each pass.

    +Interactions between multiple controllers can yield complex behavior. +This is particularly true of the Random Controller. Experiment before you assume +what results any given interaction will give +
    + + Descriptive name for this controller that is shown in the tree. + + +
    + + + + + +

    The Random Order Controller is much like a Simple Controller in that it will execute each child + element at most once, but the order of execution of the nodes will be random.

    +
    + + Descriptive name for this controller that is shown in the tree. + +
    + + + +

    +This controller is badly named, as it does not control throughput. +Please refer to the for an element that can be used to adjust the throughput. +

    +

    The Throughput Controller allows the user to control how often it is executed. There are two modes - percent execution and total executions. Percent executions causes the controller to execute a certain percentage of the iterations through the test plan. Total +executions causes the controller to stop executing after a certain number of executions have occurred. Like the Once Only Controller, this +setting is reset when a parent Loop Controller restarts. +

    +
    +The Throughput Controller can yield very complex behavior when combined with other controllers - in particular with interleave or random controllers as parents (also very useful). + + Descriptive name for this controller that is shown in the tree. + Whether the controller will run in percent executions or total executions mode. + A number. for percent execution mode, a number from 0-100 that indicates the percentage of times the controller will execute. "50" means the controller will execute during half the iterations throught the test plan. for total execution mode, the number indicates the total number of times the controller will execute. + If checked, per user will cause the controller to calculate whether it should execute on a per user (per thread) basis. if unchecked, then the calculation will be global for all users. for example, if using total execution mode, and uncheck "per user", then the number given for throughput will be the total number of executions made. if "per user" is checked, then the total number of executions would be the number of users times the number given for throughput. + + +
    + + + +

    The Runtime Controller controls how long its children are allowed to run. +

    +
    + + Descriptive name for this controller that is shown in the tree, and used to name the transaction. + Desired runtime in seconds + +
    + + + +

    The If Controller allows the user to control whether the test elements below it (its children) are run or not.

    +

    + Prior to JMeter 2.3RC3, the condition was evaluated for every runnable element contained in the controller. + This sometimes caused unexpected behaviour, so 2.3RC3 was changed to evaluate the condition only once on initial entry. + However, the original behaviour is also useful, so versions of JMeter after 2.3RC4 have an additional + option to select the original behaviour. +

    +

    + Versions of JMeter after 2.3.2 allow the script to be processed as a variable expression, rather than requiring Javascript. + It was always possible to use functions and variables in the Javascript condition, so long as they evaluated to "true" or "false"; + now this can be done without the overhead of using Javascript as well. For example, previously one could use the condition: + ${__jexl(${VAR} == 23)} and this would be evaluated as true/false, the result would then be passed to Javascript + which would then return true/false. If the Variable Expression option is selected, then the expression is evaluated + and compared with "true", without needing to use Javascript. + Also, variable expressions can return any value, whereas the + Javascript condition must return "true"/"false" or an error is logged. +

    + + No variables are made available to the script when the condition is interpreted as Javascript. + If you need access to such variables, then select "Interpret Condition as Variable Expression?" and use + a __javaScript() function call. You can then use the objects "vars", "log", "ctx" etc. in the script. + +
    + + Descriptive name for this controller that is shown in the tree. + By default the condition is interpreted as Javascript code that returns "true" or "false", + but this can be overriden (see below) + If this is selected, then the condition must be an expression that evaluates to "true" (case is ignored). + For example, ${FOUND} or ${__jexl(${VAR} > 100)}. + Unlike the Javascript case, the condition is only checked to see if it matches "true" (case is ignored). + + + Should condition be evaluated for all children? + If not checked, then the condition is only evaluated on entry. + + +

    Examples (Javascript): +

      +
    • ${COUNT} < 10
    • +
    • "${VAR}" == "abcd"
    • +
    • ${JMeterThread.last_sample_ok} (check if last sample succeeded)
    • +
    + If there is an error interpreting the code, the condition is assumed to be false, and a message is logged in jmeter.log. +

    +

    Examples (Variable Expression): +

      +
    • ${__jexl(${COUNT} < 10)}
    • +
    • ${RESULT}
    • +
    +

    +
    + + + + + + + + + + +

    +The While Controller runs its children until the condition is "false". +

    + +

    Possible condition values:

    +
      +
    • blank - exit loop when last sample in loop fails
    • +
    • LAST - exit loop when last sample in loop fails. +If the last sample just before the loop failed, don't enter loop.
    • +
    • Otherwise - exit (or don't enter) the loop when the condition is equal to the string "false"
    • +
    + +The condition can be any variable or function that eventually evaluates to the string "false". +This allows the use of JavaScript, BeanShell, properties or variables as needed. + +

    + +Note that the is evaluated twice, once before starting sampling children and once at end of children sampling, so putting +non idempotent functions in Condition (like __counter) can introduce issues. + +

    +For example: +
      +
    • ${VAR} - where VAR is set to false by some other test element
    • +
    • ${__javaScript(${C}==10)}
    • +
    • ${__javaScript("${VAR2}"=="abcd")}
    • +
    • ${_P(property)} - where property is set to "false" somewhere else
    • +
    +
    + + Descriptive name for this controller that is shown in the tree, and used to name the transaction. + blank, LAST, or variable/function + +
    + + + +

    +The Switch Controller acts like the +in that it runs one of the subordinate elements on each iteration, but rather than +run them in sequence, the controller runs the element defined by the switch value. +

    +

    +Note: In versions of JMeter after 2.3.1, the switch value can also be a name. +

    +

    If the switch value is out of range, it will run the zeroth element, +which therefore acts as the default for the numeric case. +It also runs the zeroth element if the value is the empty string.

    +

    +If the value is non-numeric (and non-empty), then the Switch Controller looks for the +element with the same name (case is significant). +If none of the names match, then the element named "default" (case not significant) is selected. +If there is no default, then no element is selected, and the controller will not run anything. +

    +
    + + Descriptive name for this controller that is shown in the tree, and used to name the transaction. + The number (or name) of the subordinate element to be invoked. Elements are numbered from 0. + +
    + + +

    A ForEach controller loops through the values of a set of related variables. +When you add samplers (or controllers) to a ForEach controller, every sample sample (or controller) +is executed one or more times, where during every loop the variable has a new value. +The input should consist of several variables, each extended with an underscore and a number. +Each such variable must have a value. +So for example when the input variable has the name inputVar, the following variables should have been defined: +

      +
    • inputVar_1 = wendy
    • +
    • inputVar_2 = charles
    • +
    • inputVar_3 = peter
    • +
    • inputVar_4 = john
    • +
    +

    Note: the "_" separator is now optional.

    +When the return variable is given as "returnVar", the collection of samplers and controllers under the ForEach controller will be executed 4 consecutive times, +with the return variable having the respective above values, which can then be used in the samplers. +

    +

    +It is especially suited for running with the regular expression post-processor. +This can "create" the necessary input variables out of the result data of a previous request. +By omitting the "_" separator, the ForEach Controller can be used to loop through the groups by using +the input variable refName_g, and can also loop through all the groups in all the matches +by using an input variable of the form refName_${C}_g, where C is a counter variable. +

    +The ForEach Controller does not run any samples if inputVar_1 is null. +This would be the case if the Regular Expression returned no matches. +
    + + + Descriptive name for this controller that is shown in the tree. + Prefix for the variable names to be used as input. + + The name of the variable which can be used in the loop for replacement in the samplers + If not checked, the "_" separator is omitted. + + + + +

    Download this example (see Figure 7). +In this example, we created a Test Plan that sends a particular HTTP Request +only once and sends another HTTP Request to every link that can be found on the page.

    + +
    Figure 7 - ForEach Controller Example
    + +

    We configured the Thread Group for a single thread and a loop count value of +one. You can see that we added one HTTP Request to the Thread Group and +another HTTP Request to the ForEach Controller.

    +

    After the first HTTP request, a regular expression extractor is added, which extracts all the html links +out of the return page and puts them in the inputVar variable

    +

    In the ForEach loop, a HTTP sampler is added which requests all the links that were extracted from the first returned HTML page. +

    + +

    Here is another example you can download. +This has two Regular Expressions and ForEach Controllers. +The first RE matches, but the second does not match, +so no samples are run by the second ForEach Controller

    +
    Figure 8 - ForEach Controller Example 2
    +

    The Thread Group has a single thread and a loop count of two. +

    +Sample 1 uses the JavaTest Sampler to return the string "a b c d". +

    The Regex Extractor uses the expression (\w)\s which matches a letter followed by a space, +and returns the letter (not the space). Any matches are prefixed with the string "inputVar". +

    The ForEach Controller extracts all variables with the prefix "inputVar_", and executes its +sample, passing the value in the variable "returnVar". In this case it will set the variable to the values "a" "b" and "c" in turn. +

    The For 1 Sampler is another Java Sampler which uses the return variable "returnVar" as part of the sample Label +and as the sampler Data. +

    Sample 2, Regex 2 and For 2 are almost identical, except that the Regex has been changed to "(\w)\sx", +which clearly won't match. Thus the For 2 Sampler will not be run. +

    +
    +
    + + + +

    +The Module Controller provides a mechanism for substituting test plan fragments into the current test plan at run-time. +

    +

    +A test plan fragment consists of a Controller and all the test elements (samplers etc) contained in it. +The fragment can be located in any Thread Group, or on the . +If the fragment is located in a Thread Group, then its Controller can be disabled to prevent the fragment being run +except by the Module Controller. +Or you can store the fragments in a dummy Thread Group, and disable the entire Thread Group. +

    +

    +There can be multiple fragments, each with a different series of +samplers under them. The module controller can then be used to easily switch between these multiple test cases simply by choosing +the appropriate controller in its drop down box. This provides convenience for running many alternate test plans quickly and easily. +

    +

    +A fragment name is made up of the Controller name and all its parent names. +For example: +

    +Test Plan / Protocol: JDBC / Control / Interleave Controller (Module1)
    +
    +Any fragments used by the Module Controller must have a unique name, +as the name is used to find the target controller when a test plan is reloaded. +For this reason it is best to ensure that the Controller name is changed from the default +- as shown in the example above - +otherwise a duplicate may be accidentally created when new elements are added to the test plan. +

    +
    +The Module Controller should not be used with remote testing or non-gui testing in conjunction with Workbench components since the Workbench test elements are not part of test plan .jmx files. Any such test will fail. + + Descriptive name for this controller that is shown in the tree. + The module controller provides a list of all controllers loaded into the gui. Select + the one you want to substitute in at runtime. + +
    + + + +

    +The include controller is designed to use an external jmx file. To use it, create a Test Fragment +underneath the Test Plan and add any desired samplers, controllers etc. below it. +Then save the Test Plan. The file is now ready to be included as part of other Test Plans. +

    +

    +For convenience, a Thread Group can also be added in the external JMX file for debugging purposes. +A Module Controller can be used to reference the Test Fragment. The Thread Group will be ignored during the +include process. +

    +

    +If the test uses a Cookie Manager or User Defined Variables, these should be placed in the top-level +test plan, not the included file, otherwise they are not guaranteed to work. +

    + +This element does not support variables/functions in the filename field.

    +However, if the property includecontroller.prefix is defined, +the contents are used to prefix the pathname. +
    + +When using IncludeController and including the same JMX file, ensure you name the IncludeController differently to avoid facing known issue 50898. + +

    +If the file cannot be found at the location given by prefix+filename, then the controller +attempts to open the fileName relative to the JMX launch directory (versions of JMeter after 2.3.4). +

    +
    + + The file to include. + +
    + + + +

    + The Transaction Controller generates an additional + sample which measures the overall time taken to perform the nested test elements. + Note that this time by default includes all processing within the controller scope, not just + the samples, this can be changed by unchecking "Include duration of timer and pre-post processors in generated sample". +

    +

    + For JMeter versions after 2.3, there are two modes of operation +

      +
    • additional sample is added after the nested samples
    • +
    • additional sample is added as a parent of the nested samples
    • +
    +

    +

    + The generated sample time includes all the times for the nested samplers, and any timers etc. + Depending on the clock resolution, it may be slightly longer than the sum of the individual samplers plus timers. + The clock might tick after the controller recorded the start time but before the first sample starts. + Similarly at the end. +

    +

    The generated sample is only regarded as successful if all its sub-samples are successful.

    +

    + In parent mode, the individual samples can still be seen in the Tree View Listener, + but no longer appear as separate entries in other Listeners. + Also, the sub-samples do not appear in CSV log files, but they can be saved to XML files. +

    + + In parent mode, Assertions (etc) can be added to the Transaction Controller. + However by default they will be applied to both the individual samples and the overall transaction sample. + To limit the scope of the Assertions, use a Simple Controller to contain the samples, and add the Assertions + to the Simple Controller. + Parent mode controllers do not currently properly support nested transaction controllers of either type. + +
    + + Descriptive name for this controller that is shown in the tree, and used to name the transaction. + + If checked, then the sample is generated as a parent of the other samples, + otherwise the sample is generated as an independent sample. + + + Whether to include timer, pre- and post-processing delays in the generated sample. + Default is true to be compatible with the behaviour in previous versions of JMeter. + Setting it to false is a better option to get only response time of the sample. + + +
    + + + +

    The Recording Controller is a place holder indicating where the proxy server should +record samples to. During test run, it has no effect, similar to the Simple Controller. But during +recording using the , all recorded samples will by default +be saved under the Recording Controller.

    + +
    + + Descriptive name for this controller that is shown in the tree. + + +
    + + +^ + +
    + +
    + +

    +Most of the listeners perform several roles in addition to "listening" +to the test results. +They also provide means to view, save, and read saved test results. +

    Note that Listeners are processed at the end of the scope in which they are found.

    +

    +The saving and reading of test results is generic. The various +listeners have a panel whereby one can specify the file to +which the results will be written (or read from). +By default, the results are stored as XML +files, typically with a ".jtl" extension. +Storing as CSV is the most efficient option, but is less detailed than XML (the other available option). +

    +

    +Listeners do not process sample data in non-GUI mode, but the raw data will be saved if an output +file has been configured. +In order to analyse the data generated by a non-GUI test run, you need to load the file into the appropriate +Listener. +

    + +To read existing results and display them, use the file panel Browse button to open the file. + +

    +Versions of JMeter up to 2.3.2 used to clear any current data before loading the new file.

    +This is no longer done, thus allowing files to be merged. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. +

    +

    Results can be read from XML or CSV format files. +When reading from CSV results files, the header (if present) is used to determine which fields are present. +In order to interpret a header-less CSV file correctly, the appropriate properties must be set in jmeter.properties. +

    + +The file name can contain function and/or variable references. +However variable references do not work in client-server mode (functions work OK). + +

    Listeners can use a lot of memory if there are a lot of samples. +Most of the listeners currently keep a copy of every sample in their scope, apart from: +

    +
      +
    • Simple Data Writer
    • +
    • BeanShell/BSF Listener
    • +
    • Mailer Visualizer
    • +
    • Monitor Results
    • +
    • Summary Report
    • +
    +

    +The following Listeners no longer need to keep copies of every single sample. +Instead, samples with the same elapsed time are aggregated. +Less memory is now needed, especially if most samples only take a second or two at most. +

    +
      +
    • Aggregate Report
    • +
    • Aggregate Graph
    • +
    • Distribution Graph
    • +
    +

    To minimise the amount of memory needed, use the Simple Data Writer, and use the CSV format.

    +

    + +Versions of JMeter after 2.3.1 allow JMeter variables to be saved to the output files. +This can only be specified using a property. +See the Listener Sample Variables for details + +For full details on setting up the default items to be saved +see the Listener Default Configuration documentation. +For details of the contents of the output files, +see the CSV log format or +the XML log format. +

    +The entries in jmeter.properties are used to define the defaults; +these can be overriden for individual listeners by using the Configure button, +as shown below. +The settings in jmeter.properties also apply to the listener that is added +by using the -l command-line flag. + +

    + The figure below shows an example of the result file configuration panel +

    Result file configuration panel
    +

    + + Name of the file containing sample results. + The file name can be specified using either a relative or an absolute path name. + Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). + Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). + If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), + then the path is assumed to be relative to the JMX file location. + + File Browse Button + Select this to write/read only results with errors + Select this to write/read only results without errors. + If neither Errors nor Successes is selected, then all results are processed. + Configure Button, see below + +
    + + + +

    +Listeners can be configured to save different items to the result log files (JTL) by using the Config popup as shown below. +The defaults are defined as described in the Listener Default Configuration documentation. +Items with (CSV) after the name only apply to the CSV format; items with (XML) only apply to XML format. +CSV format cannot currently be used to save any items that include line-breaks. +

    +

    +Note that cookies, method and the query string are saved as part of the "Sampler Data" option. +

    +
    +
    + + +

    The Graph Results listener generates a simple graph that plots all sample times. Along +the bottom of the graph, the current sample (black), the current average of all samples(blue), the +current standard deviation (red), and the current throughput rate (green) are displayed in milliseconds.

    +

    The throughput number represents the actual number of requests/minute the server handled. This calculation +includes any delays you added to your test and JMeter's own internal processing time. The advantage +of doing the calculation like this is that this number represents something +real - your server in fact handled that many requests per minute, and you can increase the number of threads +and/or decrease the delays to discover your server's maximum throughput. Whereas if you made calculations +that factored out delays and JMeter's processing, it would be unclear what you could conclude from that +number.

    +

    The following table briefly describes the items on the graph. +Further details on the precise meaning of the statistical terms can be found on the web + - e.g. Wikipedia - or by consulting a book on statistics. +

    +
      +
    • Data - plot the actual data values
    • +
    • Average - plot the Average
    • +
    • Median - plot the Median (midway value)
    • +
    • Deviation - plot the Standard Deviation (a measure of the variation)
    • +
    • Throughput - plot the number of samples per unit of time
    • +
    +

    The individual figures at the bottom of the display are the current values. + "Latest Sample" is the current elapsed sample time, shown on the graph as "Data".

    +
    + + + +

    +The Spline Visualizer provides a view of all sample times from the start +of the test till the end, regardless of how many samples have been taken. The spline +has 10 points, each representing 10% of the samples, and connected using spline +logic to show a single continuous line. +

    +

    +The graph is automatically scaled to fit within the window. +This needs to be borne in mind when comparing graphs. +

    +
    +
    + + +

    The Assertion Results visualizer shows the Label of each sample taken. +It also reports failures of any Assertions that +are part of the test plan.

    + + + + +
    + + +The View Results Tree shows a tree of all sample responses, allowing you to view the +response for any sample. In addition to showing the response, you can see the time it took to get +this response, and some response codes. +Note that the Request panel only shows the headers added by JMeter. +It does not show any headers (such as Host) that may be added by the HTTP protocol implementation. +

    +There are several ways to view the response, selectable by a drop-down box at the bottom of the left hand panel.

    +
      +
    • HTML
    • +
    • HTML (download resources)
    • +
    • JSON
    • +
    • Regexp Tester
    • +
    • Text
    • +
    • XML
    • +
    +

    Scroll automatically? option permit to have last node display in tree selection

    +

    +Additional renderers can be created. +The class must implement the interface org.apache.jmeter.visualizers.ResultRenderer +and/or extend the abstract class org.apache.jmeter.visualizers.SamplerResultTab, and the +compiled code must be available to JMeter (e.g. by adding it to the lib/ext directory). +

    +

    +The default "Text" view shows all of the text contained in the response. +Note that this will only work if the response content-type is considered to be text. +If the content-type begins with any of the following, it is considered as binary, +otherwise it is considered to be text. +

    +image/
    +audio/
    +video/
    +
    +If there is no content-type provided, then the content +will not be displayed in the any of the Response Data panels. +You can use to save the data in this case. +Note that the response data will still be available in the sample result, +so can still be accessed using Post-Processors. +

    +

    If the response data is larger than 200K, then it won't be displayed. +To change this limit, set the JMeter property view.results.tree.max_size. +You can also use save the entire response to a file using +. +

    +

    The HTML view attempts to render the response as +HTML. The rendered HTML is likely to compare poorly to the view one +would get in any web browser; however, it does provide a quick +approximation that is helpful for initial result evaluation. +No images etc are downloaded. +If the HTML (download embedded resources) option is selected, the renderer +may download images and style-sheets etc referenced by the HTML. +

    +

    The XML view will show response in tree style. +Any DTD nodes or Prolog nodes will not show up in tree; however, response may contain those nodes. +

    +

    The JSON view will show the response in tree style (also handles JSON embedded in JavaScript).

    +

    +Most of the views also allow the displayed data to be searched; the result of the search will be high-lighted +in the display above. For example the Control panel screenshot below shows one result of searching for "Java". +Note that the search operates on the visible text, so you may get different results when searching +the Text and HTML views. +

    +

    The "Regexp Tester" view only works for text responses. It shows the plain text in the upper panel. +The "Test" button allows the user to apply the Regular Expression to the upper panel and the results +will be displayed in the lower panel. +For example, the RE (JMeter\w*).* applied to the current JMeter home page gives the following output: +

    +
    +Match count: 26
    +Match[1][0]=JMeter - Apache JMeter&lt;/title>
    +Match[1][1]=JMeter
    +Match[2][0]=JMeter" title="JMeter" border="0"/>&lt;/a>
    +Match[2][1]=JMeter
    +Match[3][0]=JMeterCommitters">Contributors&lt;/a>
    +Match[3][1]=JMeterCommitters
    +... and so on ...
    +
    +

    +The first number in [] is the match number; the second number is the group. +Group [0] is whatever matched the whole RE. +Group [1] is whatever matched the 1st group, i.e. (JMeter\w*) in this case. +See Figure 9b (below). +

    +
    +

    + The Control Panel (above) shows an example of an HTML display. + Figure 9 (below) shows an example of an XML display. +

    Figure 9 Sample XML display
    +
    Figure 9a Sample Regexp Test display
    +

    +
    + + +The aggregate report creates a table row for each differently named request in your +test. For each request, it totals the response information and provides request count, min, max, +average, error rate, approximate throughput (request/second) and Kilobytes per second throughput. +Once the test is done, the throughput is the actual through for the duration of the entire test. +

    +The thoughput is calculated from the point of view of the sampler target +(e.g. the remote server in the case of HTTP samples). +JMeter takes into account the total time over which the requests have been generated. +If other samplers and timers are in the same thread, these will increase the total time, +and therefore reduce the throughput value. +So two identical samplers with different names will have half the throughput of two samplers with the same name. +It is important to choose the sampler names correctly to get the best results from +the Aggregate Report. +

    +

    +Calculation of the Median and 90% Line (90th percentile) values requires additional memory. +For JMeter 2.3.4 and earlier, details of each sample were saved separately, which meant a lot of memory was needed. +JMeter now combines samples with the same elapsed time, so far less memory is used. +However, for samples that take more than a few seconds, the probability is that fewer samples will have identical times, +in which case more memory will be needed. +See the for a similar Listener that does not store individual samples and so needs constant memory. +

    +
      +
    • Label - The label of the sample. +If "Include group name in label?" is selected, then the name of the thread group is added as a prefix. +This allows identical labels from different thread groups to be collated separately if required. +
    • +
    • # Samples - The number of samples with the same label
    • +
    • Average - The average time of a set of results
    • +
    • Median - The median is the time in the middle of a set of results. +50% of the samples took no more than this time; the remainder took at least as long.
    • +
    • 90% Line - 90% of the samples took no more than this time. +The remaining samples at least as long as this. (90th percentile)
    • +
    • Min - The shortest time for the samples with the same label
    • +
    • Max - The longest time for the samples with the same label
    • +
    • Error % - Percent of requests with errors
    • +
    • Throughput - the Throughput is measured in requests per second/minute/hour. +The time unit is chosen so that the displayed rate is at least 1.0. +When the throughput is saved to a CSV file, it is expressed in requests/second, +i.e. 30.0 requests/minute is saved as 0.5. +
    • +
    • Kb/sec - The throughput measured in Kilobytes per second
    • +
    +

    Times are in milliseconds.

    +
    +
    +

    + The figure below shows an example of selecting the "Include group name" checkbox. +

    Sample "Include group name" display
    +

    +
    +
    + + +This visualizer creates a row for every sample result. +Like the , this visualizer uses a lot of memory. +

    +By default, it only displays the main (parent) samples; it does not display the sub-samples (child samples). +Versions of JMeter after 2.5.1 have a "Child Samples?" check-box. +If this is selected, then the sub-samples are displayed instead of the main samples. +

    +
    +
    + + +This listener can record results to a file +but not to the UI. It is meant to provide an efficient means of +recording data by eliminating GUI overhead. +When running in non-GUI mode, the -l flag can be used to create a data file. +The fields to save are defined by JMeter properties. +See the jmeter.properties file for details. + + + + + +

    Monitor Results is a new Visualizer for displaying server +status. It is designed for Tomcat 5, but any servlet container +can port the status servlet and use this monitor. There are two primary +tabs for the monitor. The first is the "Health" tab, which will show the +status of one or more servers. The second tab labled "Performance" shows +the performance for one server for the last 1000 samples. The equations +used for the load calculation is included in the Visualizer.

    +

    Currently, the primary limitation of the monitor is system memory. A +quick benchmark of memory usage indicates a buffer of 1000 data points for +100 servers would take roughly 10Mb of RAM. On a 1.4Ghz centrino +laptop with 1Gb of ram, the monitor should be able to handle several +hundred servers.

    +

    As a general rule, monitoring production systems should take care to +set an appropriate interval. Intervals shorter than 5 seconds are too +aggressive and have a potential of impacting the server. With a buffer of +1000 data points at 5 second intervals, the monitor would check the server +status 12 times a minute or 720 times a hour. This means the buffer shows +the performance history of each machine for the last hour.

    + +The monitor requires Tomcat 5 or above. +Use a browser to check that you can access the Tomcat status servlet OK. + +

    +For a detailed description of how to use the monitor, please refer to +Building a Monitor Test Plan +

    +
    +
    + + + +

    The distribution graph will display a bar for every unique response time. Since the +granularity of System.currentTimeMillis() is 10 milliseconds, the 90% threshold should be +within the width of the graph. The graph will draw two threshold lines: 50% and 90%. +What this means is 50% of the response times finished between 0 and the line. The same +is true of 90% line. Several tests with Tomcat were performed using 30 threads for 600K +requests. The graph was able to display the distribution without any problems and both +the 50% and 90% line were within the width of the graph. A performant application will +generally produce results that clump together. A poorly written application that has +memory leaks may result in wild fluctuations. In those situations, the threshold lines +may be beyond the width of the graph. The recommended solution to this specific problem +is fix the webapp so it performs well. If your test plan produces distribution graphs +with no apparent clumping or pattern, it may indicate a memory leak. The only way to +know for sure is to use a profiling tool.

    +
    +
    + + +The aggregate graph is similar to the aggregate report. The primary +difference is the aggregate graph provides an easy way to generate bar graphs and save +the graph as a PNG file. +
    +

    + The figure below shows an example of settings to draw this graph. +

    Aggregate graph settings
    +

    +
    +
    + + +

    The mailer visualizer can be set up to send email if a test run receives too many +failed responses from the server.

    + + Descriptive name for this element that is shown in the tree. + Email address to send messages from. + Email address to send messages to, comma-separated. + Email subject line for success messages. + Once this number of successful responses is exceeded + after previously reaching the failure limit, a success email + is sent. The mailer will thus only send out messages in a sequence of failed-succeeded-failed-succeeded, etc. + Email subject line for fail messages. + Once this number of failed responses is exceeded, a failure + email is sent - i.e. set the count to 0 to send an e-mail on the first failure. + + IP address or host name of SMTP server (email redirector) + server. + Port of SMTP server (defaults to 25). + Login used to authenticate. + Password used to authenticate. + Type of encryption for SMTP authentication (SSL, TLS or none). + + Press this button to send a test mail + A field that keeps a running total of number + of failures so far received. + +
    + + + +

    +The BeanShell Listener allows the use of BeanShell for processing samples for saving etc. +

    +

    +For full details on using BeanShell, please see the BeanShell website. +

    +

    +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +
    + + Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    + A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The BeanShell script to run. The return value is ignored. +
    +

    Before invoking the script, some variables are set up in the BeanShell interpreter:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • sampleResult, prev - (SampleResult) - gives access to the previous SampleResult
    • +
    • sampleEvent (SampleEvent) gives access to the current sample event
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +

    If the property beanshell.listener.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

    +
    + + +The summary report creates a table row for each differently named request in your +test. This is similar to the , except that it uses less memory. +

    +The thoughput is calculated from the point of view of the sampler target +(e.g. the remote server in the case of HTTP samples). +JMeter takes into account the total time over which the requests have been generated. +If other samplers and timers are in the same thread, these will increase the total time, +and therefore reduce the throughput value. +So two identical samplers with different names will have half the throughput of two samplers with the same name. +It is important to choose the sampler labels correctly to get the best results from +the Report. +

    +
      +
    • Label - The label of the sample. +If "Include group name in label?" is selected, then the name of the thread group is added as a prefix. +This allows identical labels from different thread groups to be collated separately if required. +
    • +
    • # Samples - The number of samples with the same label
    • +
    • Average - The average elapsed time of a set of results
    • +
    • Min - The lowest elapsed time for the samples with the same label
    • +
    • Max - The longest elapsed time for the samples with the same label
    • +
    • Std. Dev. - the Standard Deviation of the sample elapsed time
    • +
    • Error % - Percent of requests with errors
    • +
    • Throughput - the Throughput is measured in requests per second/minute/hour. +The time unit is chosen so that the displayed rate is at least 1.0. +When the throughput is saved to a CSV file, it is expressed in requests/second, +i.e. 30.0 requests/minute is saved as 0.5. +
    • +
    • Kb/sec - The throughput measured in Kilobytes per second
    • +
    • Avg. Bytes - average size of the sample response in bytes. (in JMeter 2.2 it wrongly showed the value in kB)
    • +
    +

    Times are in milliseconds.

    +
    +
    +

    + The figure below shows an example of selecting the "Include group name" checkbox. +

    Sample "Include group name" display
    +

    +
    +
    + + + +

    + This test element can be placed anywhere in the test plan. + For each sample in its scope, it will create a file of the response Data. + The primary use for this is in creating functional tests, but it can also + be useful where the response is too large to be displayed in the + Listener. + The file name is created from the specified prefix, plus a number (unless this is disabled, see below). + The file extension is created from the document type, if known. + If not known, the file extension is set to 'unknown'. + If numbering is disabled, and adding a suffix is disabled, then the file prefix is + taken as the entire file name. This allows a fixed file name to be generated if required. + The generated file name is stored in the sample response, and can be saved + in the test log output file if required. +

    +

    + The current sample is saved first, followed by any sub-samples (child samples). + If a variable name is provided, then the names of the files are saved in the order + that the sub-samples appear. See below. +

    +
    + + Descriptive name for this element that is shown in the tree. + Prefix for the generated file names; this can include a directory name. + Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). + Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). + If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), + then the path is assumed to be relative to the JMX file location. + + + Name of a variable in which to save the generated file name (so it can be used later in the test plan). + If there are sub-samples then a numeric suffix is added to the variable name. + E.g. if the variable name is FILENAME, then the parent sample file name is saved in the variable FILENAME, + and the filenames for the child samplers are saved in FILENAME1, FILENAME2 etc. + + If selected, then only failed responses are saved + If selected, then only successful responses are saved + If selected, then no number is added to the prefix. If you select this option, make sure that the prefix is unique or the file may be overwritten. + If selected, then no suffix is added. If you select this option, make sure that the prefix is unique or the file may be overwritten. + +
    + + + +

    +The BSF Listener allows BSF script code to be applied to sample results. +

    +
    + + Descriptive name for this element that is shown in the tree. + The BSF language to be used + Parameters to pass to the script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • args - String array containing parameters, split on white-space
    • +
    + A file containing the script to run. + The script to run. +
    +

    +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

    +

    +Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. +

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • Label - the String Label
    • +
    • Filename - the script file name (if any)
    • +
    • Parameters - the parameters (as a String)
    • +
    • args[] - the parameters as a String array (split on whitespace)
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • sampleResult, prev - (SampleResult) - gives access to the SampleResult
    • +
    • sampleEvent - (SampleEvent) - gives access to the SampleEvent
    • +
    • sampler - (Sampler)- gives access to the last sampler
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +
    + + + +

    +The JSR223 Listener allows JSR223 script code to be applied to sample results. +For details, see . +

    +
    +
    + + + This test element can be placed anywhere in the test plan. +Generates a summary of the test run so far to the log file and/or +standard output. Both running and differential totals are shown. +Output is generated every n seconds (default 3 minutes) on the appropriate +time boundary, so that multiple test runs on the same time will be synchronised. +See jmeter.properties file for the summariser configuration items: +
    +# Define the following property to automatically start a summariser with that name
    +# (applies to non-GUI mode only)
    +#summariser.name=summary
    +#
    +# interval between summaries (in seconds) default 3 minutes
    +#summariser.interval=180
    +#
    +# Write messages to log file
    +#summariser.log=true
    +#
    +# Write messages to System.out
    +#summariser.out=true
    +
    +This element is mainly intended for batch (non-GUI) runs. +The output looks like the following: +
    +label +   171 in  20.3s =    8.4/s Avg:  1129 Min:  1000 Max:  1250 Err:     0 (0.00%)
    +label +   263 in  31.3s =    8.4/s Avg:  1138 Min:  1000 Max:  1250 Err:     0 (0.00%)
    +label =   434 in  50.4s =    8.6/s Avg:  1135 Min:  1000 Max:  1250 Err:     0 (0.00%)
    +label +   263 in  31.0s =    8.5/s Avg:  1138 Min:  1000 Max:  1250 Err:     0 (0.00%)
    +label =   697 in  80.3s =    8.7/s Avg:  1136 Min:  1000 Max:  1250 Err:     0 (0.00%)
    +label +   109 in  12.4s =    8.8/s Avg:  1092 Min:    47 Max:  1250 Err:     0 (0.00%)
    +label =   806 in  91.6s =    8.8/s Avg:  1130 Min:    47 Max:  1250 Err:     0 (0.00%)
    +
    +The "label" is the the name of the element. +The "+" means that the line is a delta line, i.e. shows the changes since the last output. +The "=" means that the line is a totals line, i.e. it shows the running total. +Entries in the jmeter log file also include time-stamps. +The example "806 in 91.6s = 8.8/s" means that there were 806 samples recorded in 91.6 seconds, +and that works out at 8.8 samples per second. +The Avg (Average), Min(imum) and Max(imum) times are in milliseconds. +"Err" means number of errors (also shown as percentage). +The last two lines will appear at the end of a test. +They will not be synchronised to the appropriate time boundary. +Note that the initial and final deltas may be for less than the interval (in the example above this is 30 seconds). +The first delta will generally be lower, as JMeter synchronises to the interval boundary. +The last delta will be lower, as the test will generally not finish on an exact interval boundary. +

    +The label is used to group sample results together. +So if you have multiple Thread Groups and want to summarize across them all, then use the same label + - or add the summariser to the Test Plan (so all thread groups are in scope). +Different summary groupings can be implemented +by using suitable labels and adding the summarisers to appropriate parts of the test plan. +

    + +
    + + Descriptive name for this element that is shown in the tree. + It appears as the "label" in the output. Details for all elements with the same label will be added together. + + +
    + + + +The Comparison Assertion Visualizer shows the results of any elements. + + + Descriptive name for this element that is shown in the tree. + + + + +^ + +
    + +
    + +

    + Configuration elements can be used to set up defaults and variables for later use by samplers. + Note that these elements are processed at the start of the scope in which they are found, + i.e. before any samplers in the same scope. +

    +
    + + + +

    + CSV Data Set Config is used to read lines from a file, and split them into variables. + It is easier to use than the __CSVRead() and _StringFromFile() functions. + It is well suited to handling large numbers of variables, and is also useful for tesing with + "random" and unique values. + Generating unique random values at run-time is expensive in terms of CPU and memory, so just create the data + in advance of the test. If necessary, the "random" data from the file can be used in conjunction with + a run-time parameter to create different sets of values from each run - e.g. using concatenation - which is + much cheaper than generating everything at run-time. +

    +

    + Versions of JMeter after 2.3.1 allow values to be quoted; this allows the value to contain a delimiter. + Previously it was necessary to choose a delimiter that was not used in any values. + If "allow quoted data" is enabled, a value may be enclosed in double-quotes. + These are removed. To include double-quotes within a quoted field, use two double-quotes. + For example:

    +1,"2,3","4""5" =>
    +1
    +2,3
    +4"5
    +
    +

    +

    + Versions of JMeter after 2.3.4 support CSV files which have a header line defining the column names. + To enable this, leave the "Variable Names" field empty. The correct delimiter must be provided. +

    +

    + By default, the file is only opened once, and each thread will use a different line from the file. + However the order in which lines are passed to threads depends on the order in which they execute, + which may vary between iterations. + Lines are read at the start of each test iteration. + The file name and mode are resolved in the first iteration. +

    +

    + See the description of the Share mode below for additional options (JMeter 2.3.2+). + If you want each thread to have its own set of values, then you will need to create a set of files, + one for each thread. For example test1.csv, test2.csv,... testn.csv. Use the filename + test${__threadNum}.csv and set the "Sharing mode" to "Current thread". +

    + CSV Dataset variables are defined at the start of each test iteration. + As this is after configuration processing is completed, + they cannot be used for some configuration items - such as JDBC Config - + that process their contents at configuration time (see Bug 40394 ) + However the variables do work in the HTTP Auth Manager, as the username etc are processed at run-time. + +

    + As a special case, the string "\t" (without quotes) in the delimiter field is treated as a Tab. +

    +

    + When the end of file (EOF) is reached, and the recycle option is true, reading starts again with the first line of the file. +

    +

    + If the recycle option is false, and stopThread is false, then all the variables are set to &lt;EOF> when the end of file is reached. + This value can be changed by setting the JMeter property csvdataset.eofstring. +

    +

    + If the Recycle option is false, and Stop Thread is true, then reaching EOF will cause the thread to be stopped. +

    +
    + + Descriptive name for this element that is shown in the tree. + Name of the file to be read. + Relative file names are resolved with respect to the path of the active test plan. + Absolute file names are also supported, but note that they are unlikely to work in remote mode, + unless the remote server has the same directory structure. + If the same physical file is referenced in two different ways - e.g. csvdata.txt and ./csvdata.txt - + then these are treated as different files. + If the OS does not distinguish between upper and lower case, csvData.TXT would also be opened separately. + + The encoding to be used to read the file, if not the platform default. + List of variable names (comma-delimited). + Versions of JMeter after 2.3.4 support CSV header lines: + if the variable name field empty, then the first line of the file is read and interpreted as the list of column names. + The names must be separated by the delimiter character. They can be quoted using double-quotes. + + Delimiter to be used to split the records in the file. + If there are fewer values on the line than there are variables the remaining variables are not updated - + so they will retain their previous value (if any). + Should the CSV file allow values to be quoted? + If enabled, then values can be enclosed in " - double-quote - allowing values to contain a delimeter. + + Should the file be re-read from the beginning on reaching EOF? (default is true) + Should the thread be stopped on EOF, if Recycle is false? (default is false) + +
      +
    • All threads - (the default) the file is shared between all the threads.
    • +
    • Current thread group - each file is opened once for each thread group in which the element appears
    • +
    • Current thread - each file is opened separately for each thread
    • +
    • Identifier - all threads sharing the same identifier share the same file. + So for example if you have 4 thread groups, you could use a common id for two or more of the groups + to share the file between them. + Or you could use the thread number to share the file between the same thread numbers in different thread groups. +
    • +
    +
    +
    +
    + + + + + + +If there is more than one Authorization Manager in the scope of a Sampler, +there is currently no way to specify which one is to be used. + + +

    The Authorization Manager lets you specify one or more user logins for web pages that are +restricted using server authentication. You see this type of authentication when you use +your browser to access a restricted page, and your browser displays a login dialog box. JMeter +transmits the login information when it encounters this type of page.

    +

    +The Authorisation headers are not shown in the Tree View Listener. +

    +

    +In versions of JMeter after 2.2, the HttpClient sampler defaults to pre-emptive authentication +if the setting has not been defined. To disable this, set the values as below, in which case +authentication will only be performed in response to a challenge. +

    +jmeter.properties:
    +httpclient.parameters.file=httpclient.parameters
    +
    +httpclient.parameters:
    +http.authentication.preemptive$Boolean=false
    +
    +Note: the above settings only apply to the HttpClient sampler (and the SOAP samplers, which use Httpclient). +

    + +When looking for a match against a URL, JMeter checks each entry in turn, and stops when it finds the first match. +Thus the most specific URLs should appear first in the list, followed by less specific ones. +Duplicate URLs will be ignored. +If you want to use different usernames/passwords for different threads, you can use variables. +These can be set up using a Element (for example). + +
    + + + Descriptive name for this element that is shown in the tree. + A partial or complete URL that matches one or more HTTP Request URLs. As an example, +say you specify a Base URL of "http://jmeter.apache.org/restricted/" with a username of "jmeter" and +a password of "jmeter". If you send an HTTP request to the URL +"http://jmeter.apache.org/restricted/ant/myPage.html", the Authorization Manager sends the login +information for the user named, "jmeter". + The username to authorize. + The password for the user. (N.B. this is stored unencrypted in the test plan) + The domain to use for NTLM. + The realm to use for NTLM. + + +The Realm only applies to the HttpClient sampler. +In JMeter 2.2, the domain and realm did not have separate columns, and were encoded as part of +the user name in the form: [domain\]username[@realm]. +This was an experimental feature and has been removed. + +

    +Controls: +
      +
    • Add Button - Add an entry to the authorization table.
    • +
    • Delete Button - Delete the currently selected table entry.
    • +
    • Load Button - Load a previously saved authorization table and add the entries to the existing +authorization table entries.
    • +
    • Save As Button - Save the current authorization table to a file.
    • +
    + +When you save the Test Plan, JMeter automatically saves all of the authorization +table entries - including any passwords, which are not encrypted. + + + +

    Download this example. In this example, we created a Test Plan on a local server that sends three HTTP requests, two requiring a login and the +other is open to everyone. See figure 10 to see the makeup of our Test Plan. On our server, we have a restricted +directory named, "secret", which contains two files, "index.html" and "index2.html". We created a login id named, "kevin", +which has a password of "spot". So, in our Authorization Manager, we created an entry for the restricted directory and +a username and password (see figure 11). The two HTTP requests named "SecretPage1" and "SecretPage2" make requests +to "/secret/index.html" and "/secret/index2.html". The other HTTP request, named "NoSecretPage" makes a request to +"/index.html".

    + +
    Figure 10 - Test Plan
    +
    Figure 11 - Authorization Manager Control Panel
    + +

    When we run the Test Plan, JMeter looks in the Authorization table for the URL it is requesting. If the Base URL matches +the URL, then JMeter passes this information along with the request.

    + +You can download the Test Plan, but since it is built as a test for our local server, you will not +be able to run it. However, you can use it as a reference in constructing your own Test Plan. +
    + +
    + + + +This is a new element, and is liable to change + + +

    +The HTTP Cache Manager is used to add caching functionality to HTTP requests within its scope. +

    +

    +If a sample is successful (i.e. has response code 2xx) then the Last-Modified and Etag (and Expired if relevant) values are saved for the URL. +Before executing the next sample, the sampler checks to see if there is an entry in the cache, +and if so, the If-Last-Modified and If-None-Match conditional headers are set for the request. +

    +

    +Additionally, if the "Use Cache-Control/Expires header" option is selected, then the Cache-Control/Expires value is checked against the current time. +If the request is a GET request, and the timestamp is in the future, then the sampler returns immediately, +without requesting the URL from the remote server. This is intended to emulate browser behaviour. +Note that the Cache-Control header must be "public" and only the "max-age" expiry option is processed. +

    +

    +By default, Cache Manager will store up to 5000 items in cache using LRU algorithm. Use property to modify this value. +Note that the more you increase this value the more HTTP Cache Manager will consume memory, so be sure to adapt -Xmx option. +

    +

    +If the requested document has not changed since it was cached, then the response body will be empty. +Likewise if the Expires date is in the future. +This may cause problems for Assertions. +

    + +
    + + Descriptive name for this element that is shown in the tree. + + If selected, then the cache is cleared at the start of the thread. + + See description above. + See description above. + +
    + + + +If there is more than one Cookie Manager in the scope of a Sampler, +there is currently no way to specify which one is to be used. +Also, a cookie stored in one cookie manager is not available to any other manager, +so use multiple Cookie Managers with care. + +

    The Cookie Manager element has two functions:

    +First, it stores and sends cookies just like a web browser. If you have an HTTP Request and +the response contains a cookie, the Cookie Manager automatically stores that cookie and will +use it for all future requests to that particular web site. Each JMeter thread has its own +"cookie storage area". So, if you are testing a web site that uses a cookie for storing +session information, each JMeter thread will have its own session. +Note that such cookies do not appear on the Cookie Manager display, but they can be seen using +the Listener. +

    +

    +JMeter version 2.3.2 and earlier did not check that received cookies were valid for the URL. +This meant that cross-domain cookies were stored, and might be used later. +This has been fixed in later versions. +To revert to the earlier behaviour, define the JMeter property "CookieManager.check.cookies=false". +

    +

    +Received Cookies can be stored as JMeter thread variables +(versions of JMeter after 2.3.2 no longer do this by default). +To save cookies as variables, define the property "CookieManager.save.cookies=true". +Also, cookies names are prefixed with "COOKIE_" before they are stored (this avoids accidental corruption of local variables) +To revert to the original behaviour, define the property "CookieManager.name.prefix= " (one or more spaces). +If enabled, the value of a cookie with the name TEST can be referred to as ${COOKIE_TEST}. +

    +

    Second, you can manually add a cookie to the Cookie Manager. However, if you do this, +the cookie will be shared by all JMeter threads.

    +

    Note that such Cookies are created with an Expiration time far in the future

    +

    +Since version 2.0.3, cookies with null values are ignored by default. +This can be changed by setting the JMeter property: CookieManager.delete_null_cookies=false. +Note that this also applies to manually defined cookies - any such cookies will be removed from the display when it is updated. +Note also that the cookie name must be unique - if a second cookie is defined with the same name, it will replace the first. +

    +
    + + Descriptive name for this element that is shown in the tree. + If selected, all server-defined cookies are cleared each time the main Thread Group loop is executed. + In JMeter versions after 2.3, any cookies defined in the GUI are not cleared. + The cookie policy that will be used to manage the cookies. + "compatibility" is the default, and should work in most cases. + See http://hc.apache.org/httpclient-3.x/cookies.html and + http://hc.apache.org/httpclient-3.x/apidocs/org/apache/commons/httpclient/cookie/CookiePolicy.html + [Note: "ignoreCookies" is equivalent to omitting the CookieManager.] + + This + gives you the opportunity to use hardcoded cookies that will be used by all threads during the test execution. +

    + The "domain" is the hostname of the server (without http://); the port is currently ignored. +
    + Add an entry to the cookie table. + Delete the currently selected table entry. + Load a previously saved cookie table and add the entries to the existing +cookie table entries. + + Save the current cookie table to a file (does not save any cookies extracted from HTTP Responses). + +
    + +
    + + +

    This element lets you set default values that your HTTP Request controllers use. For example, if you are +creating a Test Plan with 25 HTTP Request controllers and all of the requests are being sent to the same server, +you could add a single HTTP Request Defaults element with the "Server Name or IP" field filled in. Then, when +you add the 25 HTTP Request controllers, leave the "Server Name or IP" field empty. The controllers will inherit +this field value from the HTTP Request Defaults element.

    + +In JMeter 2.2 and earlier, port 80 was treated specially - it was ignored if the sampler used the https protocol. +JMeter 2.3 and later treat all port values equally; a sampler that does not specify a port will use the HTTP Request Defaults port, if one is provided. + +
    + + + Descriptive name for this controller that is shown in the tree. + Domain name or IP address of the web server. e.g. www.example.com. [Do not include the http:// prefix. + Port the web server is listening to. + Connection Timeout. Number of milliseconds to wait for a connection to open. Requires Java 1.5 or later when using the default Java HTTP implementation. + Response Timeout. Number of milliseconds to wait for a response. Requires Java 1.5 or later when using the default Java HTTP implementation. + Java, HttpClient3.1, HttpClient4. + If not specified the default depends on the value of the JMeter property + jmeter.httpsampler, failing that, the Java implementation is used. + HTTP or HTTPS. + HTTP GET or HTTP POST. + The path to resource (for example, /servlets/myServlet). If the + resource requires query string parameters, add them below in the "Send Parameters With the Request" section. + Note that the path is the default for the full path, not a prefix to be applied to paths + specified on the HTTP Request screens. + + The query string will + be generated from the list of parameters you provide. Each parameter has a name and + value. The query string will be generated in the correct fashion, depending on + the choice of "Method" you made (ie if you chose GET, the query string will be + appended to the URL, if POST, then it will be sent separately). Also, if you are + sending a file using a multipart form, the query string will be created using the + multipart form specifications. + Hostname or IP address of a proxy server to perform request. [Do not include the http:// prefix.] + Port the proxy server is listening to. + (Optional) username for proxy server. + (Optional) password for proxy server. (N.B. this is stored unencrypted in the test plan) + Tell JMeter to parse the HTML file +and send HTTP/HTTPS requests for all images, Java applets, JavaScript files, CSSs, etc. referenced in the file. + + Use a pool of concurrent connections to get embedded resources. + Pool size for concurrent connections used to get embedded resources. + + +Note: radio buttons only have two states - on or off. +This makes it impossible to override settings consistently +- does off mean off, or does it mean use the current default? +JMeter uses the latter (otherwise defaults would not work at all). +So if the button is off, then a later element can set it on, +but if the button is on, a later element cannot set it off. + +
    + + + +

    The Header Manager lets you add or override HTTP request headers.

    +

    +Versions of JMeter up to 2.3.2 supported only one Header Manager per sampler; +if there were more in scope, then only the last one would be used. +

    +

    +JMeter now supports multiple Header Managers. The header entries are merged to form the list for the sampler. +If an entry to be merged matches an existing header name, it replaces the previous entry, +unless the entry value is empty, in which case any existing entry is removed. +This allows one to set up a default set of headers, and apply adjustments to particular samplers. +

    +
    + + + Descriptive name for this element that is shown in the tree. + Name of the request header. + Two common request headers you may want to experiment with +are "User-Agent" and "Referer". + Request header value. + Add an entry to the header table. + Delete the currently selected table entry. + Load a previously saved header table and add the entries to the existing +header table entries. + Save the current header table to a file. + + + + +

    Download this example. In this example, we created a Test Plan +that tells JMeter to override the default "User-Agent" request header and use a particular Internet Explorer agent string +instead. (see figures 12 and 13).

    + +
    Figure 12 - Test Plan
    +
    Figure 13 - Header Manager Control Panel
    +
    + +
    + + +

    The Java Request Defaults component lets you set default values for Java testing. See the .

    +
    + +
    + + + Creates a database connection (used by Sampler) + from the supplied JDBC Connection settings. The connection may be optionally pooled between threads. + Otherwise each thread gets its own connection. + The connection configuration name is used by the JDBC Sampler to select the appropriate + connection. + + + Descriptive name for the connection configuration that is shown in the tree. + The name of the variable the connection is tied to. + Multiple connections can be used, each tied to a different variable, allowing JDBC Samplers + to select the appropriate connection. + Each name must be different. If there are two configuration elements using the same name, + only one will be saved. JMeter versions after 2.3 log a message if a duplicate name is detected. + + + Maximum number of connections allowed in the pool. + In most cases, set this to zero (0). + This means that each thread will get its own pool with a single connection in it, i.e. + the connections are not shared betweeen threads. +
    + If you really want to use shared pooling (why?), then set the max count to the same as the number of threads + to ensure threads don't wait on each other. +
    + Pool throws an error if the timeout period is exceeded in the + process of trying to retrieve a connection + Uncertain what exactly this does. + Turn auto commit on or off for the connections. + Uncertain what exactly this does. + Uncertain what exactly this does. + A simple query used to determine if the database is still + responding. + JDBC Connection string for the database. + Fully qualified name of driver class. (Must be in + JMeter's classpath - easiest to copy .jar file into JMeter's /lib directory). + Name of user to connect as. + Password to connect with. (N.B. this is stored unencrypted in the test plan) +
    +

    Different databases and JDBC drivers require different JDBC settings. +The Database URL and JDBC Driver class are defined by the provider of the JDBC implementation.

    +

    Some possible settings are shown below. Please check the exact details in the JDBC driver documentation.

    + +

    +If JMeter reports No suitable driver, then this could mean either: +

      +
    • The driver class was not found. In this case, there will be a log message such as DataSourceElement: Could not load driver: {classname} java.lang.ClassNotFoundException: {classname}
    • +
    • The driver class was found, but the class does not support the connection string. This could be because of a syntax error in the connection string, or because the the wrong classname was used.
    • +
    +If the database server is not running or is not accessible, then JMeter will report a java.net.ConnectException. +

    + + + + + + + + +
    DatabaseDriver classDatabase URL
    MySQLcom.mysql.jdbc.Driverjdbc:mysql://host[:port]/dbname
    PostgreSQLorg.postgresql.Driverjdbc:postgresql:{dbname}
    Oracleoracle.jdbc.OracleDriverjdbc:oracle:thin:@//host:port/service +OR
    jdbc:oracle:thin:@(description=(address=(host={mc-name})(protocol=tcp)(port={port-no}))(connect_data=(sid={sid})))
    Ingres (2006)ingres.jdbc.IngresDriverjdbc:ingres://host:port/db[;attr=value]
    SQL Server (MS JDBC driver)com.microsoft.sqlserver.jdbc.SQLServerDriverjdbc:sqlserver://host:port;DatabaseName=dbname
    Apache Derbyorg.apache.derby.jdbc.ClientDriverjdbc:derby://server[:port]/databaseName[;URLAttributes=value[;...]]
    +The above may not be correct - please check the relevant JDBC driver documentation. +
    + + + +

    The Keystore Config Element lets you configure how Keystore will be loaded and which keys it will use. +This component is typically used in HTTPS scenarios where you don't want to take into account keystore initialization into account in response time.

    +

    To use this element, you need to setup first a Java Key Store with the client certificates you want to test, to do that: +

      +
    1. Create your certificates either with Java keytool utility or through your PKI
    2. +
    3. If created by PKI, import your keys in Java Key Store by converting them to a format acceptable by JKS
    4. +
    5. Then reference the keystore file through the 2 JVM properties (or add them in system.properties): +
        +
      • -Djavax.net.ssl.keyStore=path_to_keystore
      • +
      • -Djavax.net.ssl.keyStorePassword=password_of_keystore
      • +
      +
    6. +
    +

    +
    + + + Descriptive name for this element that is shown in the tree. + Wether or not to preload Keystore. + The index of the first key to use in Keystore. + The index of the last key to use in Keystore. + + +To make JMeter use more than one certificate you need to ensure that: +
      +
    • https.use.cached.ssl.context=false is set in jmeter.properties or user.properties
    • +
    • You use either HTTPClient 3.1 or 4 implementations for HTTP Request
    • +
    +
    +
    + + +

    The Login Config Element lets you add or override username and password settings in samplers that use username and password as part of their setup.

    +
    + + + Descriptive name for this element that is shown in the tree. + The default username to use. + The default password to use. (N.B. this is stored unencrypted in the test plan) + + +
    + + +

    The LDAP Request Defaults component lets you set default values for LDAP testing. See the .

    +
    + +
    + + +

    The LDAP Extended Request Defaults component lets you set default values for extended LDAP testing. See the .

    +
    + +
    + + + +

    + The TCP Sampler Config provides default data for the TCP Sampler +

    +
    + + Descriptive name for this element that is shown in the tree. + Name of the TCPClient class. Defaults to the property tcp.handler, failing that TCPClientImpl. + Name or IP of TCP server + Port to be used + If selected, the connection is kept open. Otherwise it is closed when the data has been read. + Connect Timeout (milliseconds, 0 disables). + Response Timeout (milliseconds, 0 disables). + Should the nodelay property be set? + Text to be sent + +
    + + +

    The User Defined Variables element lets you define an initial set of variables, just as in the . + +Note that all the UDV elements in a test plan - no matter where they are - are processed at the start. + +So you cannot reference variables which are defined as part of a test run, e.g. in a Post-Processor. +

    +

    + +UDVs should not be used with functions that generate different results each time they are called. +Only the result of the first function call will be saved in the variable. + +However, UDVs can be used with functions such as __P(), for example: +

    +HOST      ${__P(host,localhost)} 
    +
    +which would define the variable "HOST" to have the value of the JMeter property "host", defaulting to "localhost" if not defined. +

    +

    +For defining variables during a test run, see . +UDVs are processed in the order they appear in the Plan, from top to bottom. +

    +

    +For simplicity, it is suggested that UDVs are placed only at the start of a Thread Group +(or perhaps under the Test Plan itself). +

    +

    +Once the Test Plan and all UDVs have been processed, the resulting set of variables is +copied to each thread to provide the initial set of variables. +

    +

    +If a runtime element such as a User Parameters Pre-Processor or Regular Expression Extractor defines a variable +with the same name as one of the UDV variables, then this will replace the initial value, and all other test +elements in the thread will see the updated value. +

    +
    + +If you have more than one Thread Group, make sure you use different names for different values, as UDVs are shared between Thread Groups. +Also, the variables are not available for use until after the element has been processed, +so you cannot reference variables that are defined in the same element. +You can reference variables defined in earlier UDVs or on the Test Plan. + + + Descriptive name for this element that is shown in the tree. + Variable name/value pairs. The string under the "Name" + column is what you'll need to place inside the brackets in ${...} constructs to use the variables later on. The + whole ${...} will then be replaced by the string in the "Value" column. + +
    + + + +

    +The Random Variable Config Element is used to generate random numeric strings and store them in variable for use later. +It's simpler than using together with the __Random() function. +

    +

    +The output variable is constructed by using the random number generator, +and then the resulting number is formatted using the format string. +The number is calculated using the formula minimum+Random.nextInt(maximum-minimum+1). +Random.nextInt() requires a positive integer. +This means that maximum-minimum - i.e. the range - must be less than 2147483647, +however the minimum and maximum values can be any long values so long as the range is OK. +

    +
    + + + Descriptive name for this element that is shown in the tree. + The name of the variable in which to store the random string. + The java.text.DecimalFormat format string to be used. + For example "000" which will generate numbers with at least 3 digits, + or "USER_000" which will generate output of the form USER_nnn. + If not specified, the default is to generate the number using Long.toString() + The minimum value (long) of the generated random number. + The maximum value (long) of the generated random number. + The seed for the random number generator. Default is the current time in milliseconds. + If False, the generator is shared between all threads in the thread group. + If True, then each thread has its own random generator. + + +
    + + +

    Allows the user to create a counter that can be referenced anywhere +in the Thread Group. The counter config lets the user configure a starting point, a maximum, +and the increment. The counter will loop from the start to the max, and then start over +with the start, continuing on like that until the test is ended.

    +

    From version 2.1.2, the counter now uses a long to store the value, so the range is from -2^63 to 2^63-1.

    +
    + + Descriptive name for this element that is shown in the tree. + The starting number for the counter. The counter will equal this + number during the first iteration. + How much to increment the counter by after each + iteration. + If the counter exceeds the maximum, then it is reset to the Start value. + For versions after 2.2 the default is Long.MAX_VALUE (previously it was 0). + + Optional format, e.g. 000 will format as 001, 002 etc. + This is passed to DecimalFormat, so any valid formats can be used. + If there is a problem interpreting the format, then it is ignored. + [The default format is generated using Long.toString()] + + This controls how you refer to this value in other elements. Syntax is + as in user-defined values: $(reference_name}. + In other words, is this a global counter, or does each user get their + own counter? If unchecked, the counter is global (ie, user #1 will get value "1", and user #2 will get value "2" on + the first iteration). If checked, each user has an independent counter. + This option is only available when counter is tracked per User, if checked, + counter will be reset to Start value on each Thread Group iteration. This can be useful when Counter is inside a Loop Controller. + +
    + + +

    The Simple Config Element lets you add or override arbitrary values in samplers. You can choose the name of the value +and the value itself. Although some adventurous users might find a use for this element, it's here primarily for developers as a basic +GUI that they can use while developing new JMeter components.

    +
    + + + Descriptive name for this element that is shown in the tree. + The name of each parameter. These values are internal to JMeter's workings and + are not generally documented. Only those familiar with the code will know these values. + The value to apply to that parameter. + + +
    + + +^ + +
    + +
    + +

    + Assertions are used to perform additional checks on samplers, and are processed after every sampler + in the same scope. + To ensure that an Assertion is applied only to a particular sampler, add it as a child of the sampler. +

    +

    + Note: Unless documented otherwise, Assertions are not applied to sub-samples (child samples) - + only to the parent sample. + In the case of BSF and BeanShell Assertions, the script can retrieve sub-samples using the method + prev.getSubResults() which returns an array of SampleResults. + The array will be empty if there are none. +

    +

    + Versions of JMeter after 2.3.2 include the option to apply certain assertions + to either the main sample, the sub-samples or both. + The default is to apply the assertion to the main sample only. + If the Assertion supports this option, then there will be an entry on the GUI which looks like the following: +

    Assertion Scope
    + or the following +
    Assertion Scope
    + If a sub-sampler fails and the main sample is successful, + then the main sample will be set to failed status and an Assertion Result will be added. + If the JMeter variable option is used, it is assumed to relate to the main sample, and + any failure will be applied to the main sample only. +

    + + The variable JMeterThread.last_sample_ok is updated to + "true" or "false" after all assertions for a sampler have been run. + +
    + + +

    The response assertion control panel lets you add pattern strings to be compared against various + fields of the response. + The pattern strings are: +

      +
    • Contains, Matches: Perl5-style regular expressions
    • +
    • Equals, Substring: plain text, case-sensitive
    • +
    +

    +

    + A summary of the pattern matching characters can be found at http://jakarta.apache.org/oro/api/org/apache/oro/text/regex/package-summary.html +

    +

    You can also choose whether the strings will be expected +to match the entire response, or if the response is only expected to contain the +pattern. You can attach multiple assertions to any controller for additional flexibility.

    +

    Note that the pattern string should not include the enclosing delimiters, + i.e. use Price: \d+ not /Price: \d+/. +

    +

    + By default, the pattern is in multi-line mode, which means that the "." meta-character does not match newline. + In multi-line mode, "^" and "$" match the start or end of any line anywhere within the string + - not just the start and end of the entire string. Note that \s does match new-line. + Case is also significant. To override these settings, one can use the extended regular expression syntax. + For example: +

    +
    +	(?i) - ignore case
    +	(?s) - treat target as single line, i.e. "." matches new-line
    +	(?is) - both the above
    +    These can be used anywhere within the expression and remain in effect until overriden.  e.g.
    +    (?i)apple(?-i) Pie - matches "ApPLe Pie", but not "ApPLe pIe"
    +    (?s)Apple.+?Pie - matches Apple followed by Pie, which may be on a subsequent line.
    +    Apple(?s).+?Pie - same as above, but it's probably clearer to use the (?s) at the start.  
    +
    + +
    + + Descriptive name for this element that is shown in the tree. + + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
      +
    • Main sample only - assertion only applies to the main sample
    • +
    • Sub-samples only - assertion only applies to the sub-samples
    • +
    • Main sample and sub-samples - assertion applies to both.
    • +
    • JMeter Variable - assertion is to be applied to the contents of the named variable
    • +
    +
    + Instructs JMeter which field of the Response to test. +
      +
    • Text Response - the response text from the server, i.e. the body, excluing any HTTP headers.
    • +
    • URL sampled
    • +
    • Response Code - e.g. 200
    • +
    • Response Message - e.g. OK
    • +
    • Response Headers, including Set-Cookie headers (if any)
    • +
    +
    + Instructs JMeter to set the status to success initially. +

    + The overall success of the sample is determined by combining the result of the + assertion with the existing Response status. + When the Ignore Status checkbox is selected, the Response status is forced + to successful before evaluating the Assertion. +

    + HTTP Responses with statuses in the 4xx and 5xx ranges are normally + regarded as unsuccessful. + The "Ignore status" checkbox can be used to set the status successful before performing further checks. + Note that this will have the effect of clearing any previous assertion failures, + so make sure that this is only set on the first assertion. +
    + Indicates how the text being tested + is checked against the pattern. +
      +
    • Contains - true if the text contains the regular expression pattern
    • +
    • Matches - true if the whole text matches the regular expression pattern
    • +
    • Equals - true if the whole text equals the pattern string (case-sensitive)
    • +
    • Substring - true if the text contains the pattern string (case-sensitive)
    • +
    + Equals and Substring patterns are plain strings, not regular expressions. + NOT may also be selected to invert the result of the check.
    + A list of patterns to + be tested. + Each pattern is tested separately. + If a pattern fails, then further patterns are not checked. + There is no difference between setting up + one Assertion with multiple patterns and setting up multiple Assertions with one + pattern each (assuming the other options are the same). + However, when the Ignore Status checkbox is selected, this has the effect of cancelling any + previous assertion failures - so make sure that the Ignore Status checkbox is only used on + the first Assertion. + +
    +

    + The pattern is a Perl5-style regular expression, but without the enclosing brackets. +

    + +
    Figure 14 - Test Plan
    +
    Figure 15 - Assertion Control Panel with Pattern
    +
    Figure 16 - Assertion Listener Results (Pass)
    +
    Figure 17 - Assertion Listener Results (Fail)
    +
    + + +
    + + +

    The Duration Assertion tests that each response was received within a given amount +of time. Any response that takes longer than the given number of milliseconds (specified by the +user) is marked as a failed response.

    + + + Descriptive name for this element that is shown in the tree. + The maximum number of milliseconds + each response is allowed before being marked as failed. + +
    + + +

    The Size Assertion tests that each response contains the right number of bytes in it. You can specify that +the size be equal to, greater than, less than, or not equal to a given number of bytes.

    +Since JMeter 2.3RC3, an empty response is treated as being 0 bytes rather than reported as an error. +
    + + + Descriptive name for this element that is shown in the tree. + + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
      +
    • Main sample only - assertion only applies to the main sample
    • +
    • Sub-samples only - assertion only applies to the sub-samples
    • +
    • Main sample and sub-samples - assertion applies to both.
    • +
    • JMeter Variable - assertion is to be applied to the contents of the named variable
    • +
    +
    + The number of bytes to use in testing the size of the response (or value of the JMeter variable). + Whether to test that the response is equal to, greater than, less than, + or not equal to, the number of bytes specified. + +
    +
    + + +

    The XML Assertion tests that the response data consists of a formally correct XML document. It does not +validate the XML based on a DTD or schema or do any further validation.

    + + +Descriptive name for this element that is shown in the tree. + + +
    + + +

    The BeanShell Assertion allows the user to perform assertion checking using a BeanShell script. +

    +

    +For full details on using BeanShell, please see the BeanShell website. +

    +Note that a different Interpreter is used for each independent occurence of the assertion +in each thread in a test script, but the same Interpreter is used for subsequent invocations. +This means that variables persist across calls to the assertion. +

    +

    +All Assertions are called from the same thread as the sampler. +

    +

    +If the property "beanshell.assertion.init" is defined, it is passed to the Interpreter +as the name of a sourced file. This can be used to define common methods and variables. +There is a sample init file in the bin directory: BeanShellAssertion.bshrc +

    +

    +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +
    + + + Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    + A file containing the BeanShell script to run. This overrides the script. + The file name is stored in the script variable FileName + The BeanShell script to run. The return value is ignored. +
    +

    There's a sample script you can try.

    +

    +Before invoking the script, some variables are set up in the BeanShell interpreter. +These are strings unless otherwise noted: +

      +
    • log - the Logger Object. (e.g.) log.warn("Message"[,Throwable])
    • +
    • SampleResult - the SampleResult Object; read-write
    • +
    • Response - the response Object; read-write
    • +
    • Failure - boolean; read-write; used to set the Assertion status
    • +
    • FailureMessage - String; read-write; used to set the Assertion message
    • +
    • ResponseData - the response body (byte [])
    • +
    • ResponseCode - e.g. 200
    • +
    • ResponseMessage - e.g. OK
    • +
    • ResponseHeaders - contains the HTTP headers
    • +
    • RequestHeaders - contains the HTTP headers sent to the server
    • +
    • SampleLabel
    • +
    • SamplerData - data that was sent to the server
    • +
    • ctx - JMeterContext
    • +
    • vars - JMeterVariables - e.g. vars.get("VAR1"); vars.put("VAR2","value"); vars.putObject("OBJ1",new Object());
    • +
    • props - JMeterProperties (class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    +

    +

    The following methods of the Response object may be useful: +

      +
    • setStopThread(boolean)
    • +
    • setStopTest(boolean)
    • +
    • String getSampleLabel()
    • +
    • setSampleLabel(String)
    • +

    +
    + + +

    The MD5Hex Assertion allows the user to check the MD5 hash of the response data.

    + + +Descriptive name for this element that is shown in the tree. +32 hex digits representing the MD5 hash (case not significant) + + +
    + + +

    The HTML Assertion allows the user to check the HTML syntax of the response data using JTidy.

    + + +Descriptive name for this element that is shown in the tree. +omit/auto/strict/loose +HTML, XHTML or XML +Only take note of errors? +Number of errors allowed before classing the response as failed +Number of warnings allowed before classing the response as failed +Name of file to which report is written + + +
    + +

    The XPath Assertion tests a document for well formedness, has the option +of validating against a DTD, or putting the document through JTidy and testing for an +XPath. If that XPath exists, the Assertion is true. Using "/" will match any well-formed +document, and is the default XPath Expression. +The assertion also supports boolean expressions, such as "count(//*error)=2". +See http://www.w3.org/TR/xpath for more information +on XPath. +

    + + +Descriptive name for this element that is shown in the tree. +Use Tidy, i.e. be tolerant of XML/HTML errors +Sets the Tidy Quiet flag +If a Tidy error occurs, then set the Assertion accordingly +Sets the Tidy showWarnings option +Should namespaces be honoured? +Check the document against its schema. +Ignore Element Whitespace. +If selected, external DTDs are fetched. +XPath to match in the document. +True if a XPath expression is not matched + + +The non-tolerant parser can be quite slow, as it may need to download the DTD etc. + + +To overcome Xalan XPath parser implementation on which JMeter is based, you can help the parsing by +providing a Properties file which will contain: +
      +
    • prefix1=Full Namespace 1
    • +
    • prefix2=Full Namespace 2
    • +
    • ...
    • +
    + +You reference this file in jmeter.properties file using the property: +
      +
    • xpath.namespace.config
    • +
    +
    +
    + +

    The XML Schema Assertion allows the user to validate a response against an XML Schema.

    + + +Descriptive name for this element that is shown in the tree. +Specify XML Schema File Name + +
    + + + +

    +The BSF Assertion allows BSF script code to be used to check the status of the previous sample. +

    +
    + + Descriptive name for this element that is shown in the tree. + The BSF language to be used + Parameters to pass to the script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • args - String array containing parameters, split on white-space
    • +
    + A file containing the script to run. + The script to run. +
    +

    +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

    +

    The following variables are set up for use by the script:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • Label - the String Label
    • +
    • Filename - the script file name (if any)
    • +
    • Parameters - the parameters (as a String)
    • +
    • args[] - the parameters as a String array (split on whitespace)
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • SampleResult, prev - (SampleResult) - gives access to the previous SampleResult (if any)
    • +
    • sampler - (Sampler)- gives access to the current sampler
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    • AssertionResult - the assertion result
    • +
    +

    +The script can check various aspects of the SampleResult. +If an error is detected, the script should use AssertionResult.setFailureMessage("message") and AssertionResult.setFailure(true). +

    +

    For futher details of all the methods available on each of the above variables, please check the Javadoc

    +
    + + + +

    +The JSR223 Assertion allows JSR223 script code to be used to check the status of the previous sample. +For details, see . +

    +
    +
    + + + +The Compare Assertion can be used to compare sample results within its scope. +Either the contents or the elapsed time can be compared, and the contents can be filtered before comparison. +The assertion comparisons can be seen in the . + + + Descriptive name for this element that is shown in the tree. + Whether or not to compare the content (response data) + If the value is >=0, then check if the response time difference is no greater than the value. + I.e. if the value is 0, then the response times must be exactly equal. + Filters can be used to remove strings from the content comparison. + For example, if the page has a time-stamp, it might be matched with: "Time: \d\d:\d\d:\d\d" and replaced with a dummy fixed time "Time: HH:MM:SS". + + + + + + +The SMIME Assertion can be used to evaluate the sample results from the Mail Reader Sampler. +This assertion verifies if the body of a mime message is signed or not. The signature can also be verified against a specific signer certificate. +As this is a functionality that is not necessarily needed by most users, additional jars need to be downloaded and added to JMETER_HOME/lib :

    +
      +
    • bcmail-xxx.jar (BouncyCastle SMIME/CMS)
    • +
    • bcprov-xxx.jar (BouncyCastle Provider)
    • +
    +These need to be downloaded from BouncyCastle. +

    +If using the Mail Reader Sampler, +please ensure that you select "Store the message using MIME (raw)" otherwise the Assertion won't be able to process the message correctly. +

    +
    + + Descriptive name for this element that is shown in the tree. + If selected, the asertion will verify if it is a valid signature according to the parameters defined in the Signer Certificate box. + Whether or not to expect a signature in the message + "No Check" means that it wil not perform signature verification. "Check values" is used to verify the signature against the inputs provided. And "Certificate file" will perform the verification against a specific certificate file. + + The Mail sampler can retrieve multiple messages in a single sample. + Use this field to specify which message will be checked. + Messages are numbered from 0, so 0 means the first message. + Negative numbers count from the LAST message; -1 means LAST, -2 means penultimate etc. + + +
    + +^ + +
    + +
    + +

    +

    + Note that timers are processed before each sampler in the scope in which they are found; + if there are several timers in the same scope, all the timers will be processed before + each sampler. +

    + Timers are only processed in conjunction with a sampler. + A timer which is not in the same scope as a sampler will not be processed at all. +

    + To apply a timer to a single sampler, add the timer as a child element of the sampler. + The timer will be applied before the sampler is executed. + To apply a timer after a sampler, either add it to the next sampler, or add it as the + child of a Sampler. +

    +
    + + +

    If you want to have each thread pause for the same amount of time between +requests, use this timer.

    + + + Descriptive name for this timer that is shown in the tree. + Number of milliseconds to pause. + +
    + + + +

    This timer pauses each thread request for a random amount of time, with most +of the time intervals ocurring near a particular value. The total delay is the +sum of the Gaussian distributed value (with mean 0.0 and standard deviation 1.0) times +the deviation value you specify, and the offset value.

    + + + + Descriptive name for this timer that is shown in the tree + Deviation in milliseconds. + Number of milliseconds to pause in addition +to the random delay. + + +
    + + + +

    This timer pauses each thread request for a random amount of time, with +each time interval having the same probability of occurring. The total delay +is the sum of the random value and the offset value.

    + + + Descriptive name for this timer that is shown in the tree. + Maxium random number of milliseconds to +pause. + Number of milliseconds to pause in addition +to the random delay. + + +
    + + + +

    This timer introduces variable pauses, calculated to keep the total throughput (in terms of samples per minute) as close as possible to a give figure. Of course the throughput will be lower if the server is not capable of handling it, or if other timers or time-consuming test elements prevent it.

    +

    +N.B. although the Timer is called the Constant Throughput timer, the throughput value does not need to be constant. +It can be defined in terms of a variable or function call, and the value can be changed during a test. +The value can be changed in various ways: +

    +
      +
    • using a counter variable
    • +
    • using a JavaScript or BeanShell function to provide a changing value
    • +
    • using the remote BeanShell server to change a JMeter property
    • +
    +

    See Best Practices for further details. +Note that the throughput value should not be changed too often during a test +- it will take a while for the new value to take effect. +

    +
    + + Descriptive name for this timer that is shown in the tree. + Throughput we want the timer to try to generate. + +
      +
    • this thread only - each thread will try to maintain the target throughput. The overall throughput will be proportional to the number of active threads.
    • +
    • all active threads in current thread group - the target throughput is divided amongst all the active threads in the group. + Each thread will delay as needed, based on when it last ran.
    • +
    • all active threads - the target throughput is divided amongst all the active threads in all Thread Groups. + Each thread will delay as needed, based on when it last ran. + In this case, each other Thread Group will need a Constant Throughput timer with the same settings.
    • +
    • all active threads in current thread group (shared) - as above, but each thread is delayed based on when any thread in the group last ran.
    • +
    • all active threads (shared) - as above; each thread is delayed based on when any thread last ran.
    • +
    +
    +

    The shared and non-shared algorithms both aim to generate the desired thoughput, and will produce similar results. + The shared algorithm should generate a more accurate overall transaction rate. + The non-shared algortihm should generate a more even spread of transactions across threads.

    +
    +
    + + + + +

    +The purpose of the SyncTimer is to block threads until X number of threads have been blocked, and +then they are all released at once. A SyncTimer can thus create large instant loads at various +points of the test plan. +

    +
    + + + Descriptive name for this timer that is shown in the tree. + Number of threads to release at once. Setting it to 0 is equivalent to setting it to Number of threads in Thread Group. + + +
    + + + +

    +The BeanShell Timer can be used to generate a delay. +

    +

    +For full details on using BeanShell, please see the BeanShell website. +

    +

    +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +
    + + Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    +
    + + A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The return value is used as the number of milliseconds to wait. + + + The BeanShell script. The return value is used as the number of milliseconds to wait. + +
    +

    Before invoking the script, some variables are set up in the BeanShell interpreter:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • prev - (SampleResult) - gives access to the previous SampleResult (if any)
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +

    If the property beanshell.timer.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

    +
    + + + + +

    +The BSF Timer can be used to generate a delay using a BSF scripting language. +

    +
    + + Descriptive name for this element that is shown in the tree. + + The scripting language to be used. + + Parameters to pass to the script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • args - String array containing parameters, split on white-space
    • +
    +
    + + A file containing the script to run. + The return value is converted to a long integer and used as the number of milliseconds to wait. + + + The script. The return value is used as the number of milliseconds to wait. + +
    +

    Before invoking the script, some variables are set up in the script interpreter:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • sampler - the current Sampler
    • +
    • Label - the name of the Timer
    • +
    • Filename - the file name (if any)
    • +
    • OUT - System.out
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +
    + + + +

    +The JSR223 Timer can be used to generate a delay using a JSR223 scripting language, +For details, see . +

    +
    +
    + + + +

    This timer pauses each thread request for a random amount of time, with most +of the time intervals ocurring near a particular value. The total delay is the +sum of the Poisson distributed value, and the offset value.

    + + + + Descriptive name for this timer that is shown in the tree + Lambda value in milliseconds. + Number of milliseconds to pause in addition +to the random delay. + + +
    + +^ + +
    + +
    + +

    + Preprocessors are used to modify the Samplers in their scope. +

    +
    + + +

    This modifier parses HTML response from the server and extracts +links and forms. A URL test sample that passes through this modifier will be examined to +see if it "matches" any of the links or forms extracted +from the immediately previous response. It would then replace the values in the URL +test sample with appropriate values from the matching link or form. Perl-type regular +expressions are used to find matches.

    +
    + +Matches are performed using protocol, host, path and parameter names. +The target sampler cannot contain parameters that are not in the response links. + + +

    Consider a simple example: let's say you wanted JMeter to "spider" through your site, +hitting link after link parsed from the HTML returned from your server (this is not +actually the most useful thing to do, but it serves as a good example). You would create +a , and add the "HTML Link Parser" to it. Then, create an +HTTP Request, and set the domain to ".*", and the path likewise. This will +cause your test sample to match with any link found on the returned pages. If you wanted to +restrict the spidering to a particular domain, then change the domain value +to the one you want. Then, only links to that domain will be followed. +

    +
    + + +

    A more useful example: given a web polling application, you might have a page with +several poll options as radio buttons for the user to select. Let's say the values +of the poll options are very dynamic - maybe user generated. If you wanted JMeter to +test the poll, you could either create test samples with hardcoded values chosen, or you +could let the HTML Link Parser parse the form, and insert a random poll option into +your URL test sample. To do this, follow the above example, except, when configuring +your Web Test controller's URL options, be sure to choose "POST" as the +method. Put in hard-coded values for the domain, path, and any additional form parameters. +Then, for the actual radio button parameter, put in the name (let's say it's called "poll_choice"), +and then ".*" for the value of that parameter. When the modifier examines +this URL test sample, it will find that it "matches" the poll form (and +it shouldn't match any other form, given that you've specified all the other aspects of +the URL test sample), and it will replace your form parameters with the matching +parameters from the form. Since the regular expression ".*" will match with +anything, the modifier will probably have a list of radio buttons to choose from. It +will choose at random, and replace the value in your URL test sample. Each time through +the test, a new random value will be chosen.

    + +
    Figure 18 - Online Poll Example
    + +One important thing to remember is that you must create a test sample immediately +prior that will return an HTML page with the links and forms that are relevant to +your dynamic test sample. +
    + +
    + + +

    This modifier works similarly to the HTML Link Parser, except it has a specific purpose for which +it is easier to use than the HTML Link Parser, and more efficient. For web applications that +use URL Re-writing to store session ids instead of cookies, this element can be attached at the +ThreadGroup level, much like the . Simply give it the name +of the session id parameter, and it will find it on the page and add the argument to every +request of that ThreadGroup.

    +

    Alternatively, this modifier can be attached to select requests and it will modify only them. +Clever users will even determine that this modifier can be used to grab values that elude the +.

    +
    + + + Descriptive name given to this element in the test tree. + The name of the parameter to grab from + previous response. This modifier will find the parameter anywhere it exists on the page, and + grab the value assigned to it, whether it's in an HREF or a form. + Some web apps rewrite URLs by appending + a semi-colon plus the session id parameter. Check this box if that is so. + Some web apps rewrite URLs without using an "=" sign between the parameter name and value (such as Intershop Enfinity). + Prevents the query string to end up in the path extension (such as Intershop Enfinity). + + Should the value of the session Id be saved for later use when the session Id is not present? + + +
    + + +

    The HTML Parameter Mask is used to generate unique values for HTML arguments. By +specifying the name of the parameter, a value prefix and suffix, and counter parameters, this +modifier will generate values of the form "name=prefixcountersuffix". Any HTTP +Request that it modifies, it will replace any parameter with the same name or add the appropriate +parameter to the requests list of arguments.

    +The value of the argument in your HTTP Request must be a '*' in order for the HTML Parameter Mask +Modifier to replace it. +

    As an example, the username for a login script could be modified to send a series of values +such as:

    +user_1

    +user_2

    +user_3

    +user_4, etc.

    + + + Descriptive name given to this element in the test tree. + The name of the parameter to + modify or add to the HTTP Request. + A string value to prefix to every generated value. + A number value to start the counter at. + A number value to end the counter, at which point it restarts + with the Lower Bound value. + Value to increment the counter by each time through. + A string value to add as suffix to every generated vaue. + +
    + + +

    Allows the user to specify values for User Variables specific to individual threads.

    +

    User Variables can also be specified in the Test Plan but not specific to individual threads. This panel allows +you to specify a series of values for any User Variable. For each thread, the variable will be assigned one of the values from the series +in sequence. If there are more threads than values, the values get re-used. For example, this can be used to assign a distinct +user id to be used by each thread. User variables can be referenced in any field of any jMeter Component.

    + +

    The variable is specified by clicking the Add Variable button in the bottom of the panel and filling in the Variable name in the 'Name:' column. +To add a new value to the series, click the 'Add User' button and fill in the desired value in the newly added column.

    + +

    Values can be accessed in any test component in the same thread group, using the function syntax: ${variable}.

    +

    See also the element, which is more suitable for large numbers of parameters

    +
    + + + Descriptive name for this element that is shown in the tree. + A flag to indicate whether the User Paramters element + should update its variables only once per iteration. if you embed functions into the UP, then you may need greater + control over how often the values of the variables are updated. Keep this box checked to ensure the values are + updated each time through the UP's parent controller. Uncheck the box, and the UP will update the parameters for + every sample request made within its scope. + + +
    + + + +

    +The BeanShell PreProcessor allows arbitrary code to be applied before taking a sample. +

    +

    +For full details on using BeanShell, please see the BeanShell website. +

    +

    +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +
    + + Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    + A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The BeanShell script. The return value is ignored. +
    +

    Before invoking the script, some variables are set up in the BeanShell interpreter:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • prev - (SampleResult) - gives access to the previous SampleResult (if any)
    • +
    • sampler - (Sampler)- gives access to the current sampler
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +

    If the property beanshell.preprocessor.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

    +
    + + + +

    +The BSF PreProcessor allows BSF script code to be applied before taking a sample. +

    +
    + + Descriptive name for this element that is shown in the tree. + The BSF language to be used + Parameters to pass to the script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • args - String array containing parameters, split on white-space
    • +
    + A file containing the script to run. + The script to run. +
    +

    +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

    +

    The following BSF variables are set up for use by the script:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • Label - the String Label
    • +
    • Filename - the script file name (if any)
    • +
    • Parameters - the parameters (as a String)
    • +
    • args[] - the parameters as a String array (split on whitespace)
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • sampler - (Sampler)- gives access to the current sampler
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +
    + + + +

    +The JSR223 PreProcessor allows JSR223 script code to be applied before taking a sample. +For details, see . +

    +
    +
    + + + +

    +The JDBC PreProcessor enables you to run some SQL statement just before a sample runs. +This can be useful if your JDBC Sample requires some data to be in DataBase and you cannot compute this in a setup Thread group. +

    +

    +See the following Test plan: +

    + + Test Plan using JDBC Pre/Post Processor + +

    +In the linked test plan,"Create Price Cut-Off" JDBC PreProcessor calls a stored procedure to create a Price Cut-Off in Database, +this one will be used by "Calculate Price cut off". + +

    Create Price Cut-Off Preprocessor
    + +

    +
    +
    + +^ + +
    + +
    + +

    + As the name suggests, Post-Processors are applied after samplers. Note that they are + applied to all the samplers in the same scope, so to ensure that a post-processor + is applied only to a particular sampler, add it as a child of the sampler. +

    +

    + Note: Unless documented otherwise, Post-Processors are not applied to sub-samples (child samples) - + only to the parent sample. + In the case of BSF and BeanShell post-processors, the script can retrieve sub-samples using the method + prev.getSubResults() which returns an array of SampleResults. + The array will be empty if there are none. +

    +

    + Post-Processors are run before Assertions, so they do not have access to any Assertion Results, nor will + the sample status reflect the results of any Assertions. If you require access to Assertion Results, try + using a Listener instead. Also note that the variable JMeterThread.last_sample_ok is set to "true" or "false" + after all Assertions have been run. +

    +
    + +

    Allows the user to extract values from a server response using a Perl-type regular expression. As a post-processor, +this element will execute after each Sample request in its scope, applying the regular expression, extracting the requested values, +generate the template string, and store the result into the given variable name.

    + + Descriptive name for this element that is shown in the tree. + + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
      +
    • Main sample only - only applies to the main sample
    • +
    • Sub-samples only - only applies to the sub-samples
    • +
    • Main sample and sub-samples - applies to both.
    • +
    • JMeter Variable - assertion is to be applied to the contents of the named variable
    • +
    + Matching is applied to all qualifying samples in turn. + For example if there is a main sample and 3 sub-samples, each of which contains a single match for the regex, + (i.e. 4 matches in total). + For match number = 3, Sub-samples only, the extractor will match the 3rd sub-sample. + For match number = 3, Main sample and sub-samples, the extractor will match the 2nd sub-sample (1st match is main sample). + For match number = 0 or negative, all qualifying samples will be processed. + For match number > 0, matching will stop as soon as enough matches have been found. +
    + + The following response fields can be checked: +
      +
    • Body - the body of the response, e.g. the content of a web-page (excluding headers)
    • +
    • Body (unescaped) - the body of the response, with all Html escape codes replaced. + Note that Html escapes are processed without regard to context, so some incorrect substitutions + may be made. +
    • +
    • Headers - may not be present for non-HTTP samples
    • +
    • URL
    • +
    • Response Code - e.g. 200
    • +
    • Response Message - e.g. OK
    • +
    + Headers can be useful for HTTP samples; it may not be present for other sample types.
    + The name of the JMeter variable in which to store the result. Also note that each group is stored as [refname]_g#, where [refname] is the string you entered as the reference name, and # is the group number, where group 0 is the entire match, group 1 is the match from the first set of parentheses, etc. + The regular expression used to parse the response data. + This must contain at least one set of parentheses "()" to capture a portion of the string, unless using the group $0$. + Do not enclose the expression in / / - unless of course you want to match these characters as well. + + The template used to create a string from the matches found. This is an arbitrary string + with special elements to refer to groups within the regular expression. The syntax to refer to a group is: '$1$' to refer to + group 1, '$2$' to refer to group 2, etc. $0$ refers to whatever the entire expression matches. + Indicates which match to use. The regular expression may match multiple times. +
      +
    • Use a value of zero to indicate JMeter should choose a match at random.
    • +
    • A positive number N means to select the nth match.
    • +
    • Negative numbers are used in conjunction with the ForEach controller - see below.
    • +
    +
    + + If the regular expression does not match, then the reference variable will be set to the default value. + This is particularly useful for debugging tests. If no default is provided, then it is difficult to tell + whether the regular expression did not match, or the RE element was not processed or maybe the wrong variable + is being used. +

    + However, if you have several test elements that set the same variable, + you may wish to leave the variable unchanged if the expression does not match. + In this case, remove the default value once debugging is complete. +

    +
    +
    +

    + If the match number is set to a non-negative number, and a match occurs, the variables are set as follows: +

      +
    • refName - the value of the template
    • +
    • refName_gn, where n=0,1,2 - the groups for the match
    • +
    • refName_g - the number of groups in the Regex (excluding 0)
    • +
    + If no match occurs, then the refName variable is set to the default (unless this is absent). + Also, the following variables are removed: +
      +
    • refName_g0
    • +
    • refName_g1
    • +
    • refName_g
    • +
    +

    +

    + If the match number is set to a negative number, then all the possible matches in the sampler data are processed. + The variables are set as follows: +

      +
    • refName_matchNr - the number of matches found; could be 0
    • +
    • refName_n, where n = 1,2,3 etc - the strings as generated by the template
    • +
    • refName_n_gm, where m=0,1,2 - the groups for match n
    • +
    • refName - always set to the default value
    • +
    • refName_gn - not set
    • +
    + Note that the refName variable is always set to the default value in this case, + and the associated group variables are not set. +

    See also for some examples of how to specify modifiers, + and for further information on JMeter regular expressions.

    +

    +
    + + + This test element allows the user to extract value(s) from + structured response - XML or (X)HTML - using XPath + query language. + + + Descriptive name for this element that is shown in the tree. + + This is for use with samplers that can generate sub-samples, + e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller. +
      +
    • Main sample only - only applies to the main sample
    • +
    • Sub-samples only - only applies to the sub-samples
    • +
    • Main sample and sub-samples - applies to both.
    • +
    • JMeter Variable - assertion is to be applied to the contents of the named variable
    • +
    + XPath matching is applied to all qualifying samples in turn, and all the matching results will be returned. +
    + If checked use Tidy to parse HTML response into XHTML. +
      +
    • "Use Tidy" should be checked on for HTML response. Such response is converted to valid XHTML (XML compatible HTML) using Tidy
    • +
    • "Use Tidy" should be unchecked for both XHTML or XML response (for example RSS)
    • +
    +
    +Sets the Tidy Quiet flag +If a Tidy error occurs, then set the Assertion accordingly +Sets the Tidy showWarnings option + + If checked, then the XML parser will use namespace resolution. + Note that currently only namespaces declared on the root element will be recognised. + A later version of JMeter may support user-definition of additional workspace names. + Meanwhile, a work-round is to replace: +

    + //mynamespace:tagname +

    + by +

    + //*[local-name()='tagname' and namespace-uri()='uri-for-namespace'] +

    + where "uri-for-namespace" is the uri for the "mynamespace" namespace. + + (not applicable if Tidy is selected) +
    + Check the document against its schema. + Ignore Element Whitespace. + If selected, external DTDs are fetched. + + If selected, the fragment will be returned rather than the text content.

    + For example //title would return "&lt;title>Apache JMeter&lt;/title>" rather than "Apache JMeter".

    + In this case, //title/text() would return "Apache JMeter". +
    + The name of the JMeter variable in which to store the result. + Element query in XPath language. Can return more than one match. + Default value returned when no match found. + It is also returned if the node has no value and the fragment option is not selected. +
    +

    To allow for use in a ForEach Controller, the following variables are set on return:

    +
      +
    • refName - set to first (or only) match; if no match, then set to default
    • +
    • refName_matchNr - set to number of matches (may be 0)
    • +
    • refName_n - n=1,2,3 etc. Set to the 1st, 2nd 3rd match etc. +
    • +
    +

    Note: The next refName_n variable is set to null - e.g. if there are 2 matches, then refName_3 is set to null, + and if there are no matches, then refName_1 is set to null. +

    +

    XPath is query language targeted primarily for XSLT transformations. However it is usefull as generic query language for structured data too. See + XPath Reference or XPath specification for more information. Here are few examples: +

    +
    +
    /html/head/title
    +
    extracts title element from HTML response
    +
    /book/page[2]
    +
    extracts 2nd page from a book
    +
    /book/page
    +
    extracts all pages from a book
    +
    //form[@name='countryForm']//select[@name='country']/option[text()='Czech Republic'])/@value
    +
    extracts value attribute of option element that match text 'Czech Republic' + inside of select element with name attribute 'country' inside of + form with name attribute 'countryForm'
    +
    + When "Use Tidy" is checked on - resulting XML document may slightly differ from original HTML response: +
      +
    • All elements and attribute names are converted to lowercase
    • +
    • Tidy attempts to correct improperly nested elements. For example - original (incorrect) ul/font/li becomes correct ul/li/font
    • +
    + See Tidy homepage for more information. +
    + +
    + + + This test element allows the user to stop the thread or the whole test if the relevant sampler failed. + + + Descriptive name for this element that is shown in the tree. + + Determines what happens if a sampler error occurs, either because the sample itself failed or an assertion failed. + The possible choices are: +
      +
    • Continue - ignore the error and continue with the test
    • +
    • Stop Thread - current thread exits
    • +
    • Stop Test - the entire test is stopped at the end of any current samples.
    • +
    • Stop Test Now - the entire test is stopped abruptly. Any current samplers are interrupted if possible.
    • +
    +
    +
    +
    + + + +

    +The BeanShell PreProcessor allows arbitrary code to be applied after taking a sample. +

    +

    For JMeter versions after 2.2 the BeanShell Post-Processor no longer ignores samples with zero-length result data

    +

    +For full details on using BeanShell, please see the BeanShell website. +

    +

    +The test element supports the ThreadListener and TestListener methods. +These should be defined in the initialisation file. +See the file BeanShellListeners.bshrc for example definitions. +

    +
    + + Descriptive name for this element that is shown in the tree. + The name is stored in the script variable Label + + If this option is selected, then the interpreter will be recreated for each sample. + This may be necessary for some long running scripts. + For further information, see Best Practices - BeanShell scripting. + + Parameters to pass to the BeanShell script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • bsh.args - String array containing parameters, split on white-space
    • +
    + A file containing the BeanShell script to run. + The file name is stored in the script variable FileName + The BeanShell script. The return value is ignored. +
    +

    The following BeanShell variables are set up for use by the script:

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object());
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • prev - (SampleResult) - gives access to the previous SampleResult
    • +
    • data - (byte [])- gives access to the current sample data
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +

    If the property beanshell.postprocessor.init is defined, this is used to load an initialisation file, which can be used to define methods etc for use in the BeanShell script.

    +
    + + + +

    +The BSF PostProcessor allows BSF script code to be applied after taking a sample. +

    +
    + + Descriptive name for this element that is shown in the tree. + The BSF language to be used + Parameters to pass to the script. + The parameters are stored in the following variables: +
      +
    • Parameters - string containing the parameters as a single variable
    • +
    • args - String array containing parameters, split on white-space
    • +
    + A file containing the script to run. + The script to run. +
    +

    +The script (or file) is processed using the BSFEngine.exec() method, which does not return a value. +

    +

    +Before invoking the script, some variables are set up. +Note that these are BSF variables - i.e. they can be used directly in the script. +

    +
      +
    • log - (Logger) - can be used to write to the log file
    • +
    • Label - the String Label
    • +
    • Filename - the script file name (if any)
    • +
    • Parameters - the parameters (as a String)
    • +
    • args[] - the parameters as a String array (split on whitespace)
    • +
    • ctx - (JMeterContext) - gives access to the context
    • +
    • vars - (JMeterVariables) - gives read/write access to variables: vars.get(key); vars.put(key,val); vars.putObject("OBJ1",new Object()); vars.getObject("OBJ2");
    • +
    • props - (JMeterProperties - class java.util.Properties) - e.g. props.get("START.HMS"); props.put("PROP1","1234");
    • +
    • prev - (SampleResult) - gives access to the previous SampleResult (if any)
    • +
    • sampler - (Sampler)- gives access to the current sampler
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    +

    For details of all the methods available on each of the above variables, please check the Javadoc

    +
    + + + +

    +The JSR223 PostProcessor allows JSR223 script code to be applied after taking a sample. +For details, see the . +

    +
    +
    + + + +

    +The JDBC PostProcessor enables you to run some SQL statement just after a sample has run. +This can be useful if your JDBC Sample changes some data and you want to reset state to what it was before the JDBC sample run. +

    +
    +
    + + Test Plan using JDBC Pre/Post Processor + +

    +In the linked test plan,"JDBC PostProcessor" JDBC PostProcessor calls a stored procedure to delete from Database the Price Cut-Off that was created by PreProcessor. +

    JDBC PostProcessor
    +

    +
    + +
    + +

    +
    + + +

    +The Test Plan is where the overall settings for a test are specified. +

    +

    +Static variables can be defined for values that are repeated throughout a test, such as server names. +For example the variable SERVER could be defined as www.example.com, and the rest of the test plan +could refer to it as ${SERVER}. This simplifies changing the name later. +

    +

    +If the same variable name is reused on one of more + Configuration elements, +the value is set to the last definition in the test plan (reading from top to bottom). +Such variables should be used for items that may change between test runs, +but which remain the same during a test run. +

    +

    +Note that the Test Plan cannot refer to variables it defines. +If you need to construct other variables from the Test Plan variables, +use a test element. +

    +

    +Selecting Functional Testing instructs JMeter to save the additional sample information +- Response Data and Sampler Data - to all result files. +This increases the resources needed to run a test, and may adversely impact JMeter performance. +If more data is required for a particular sampler only, then add a Listener to it, and configure the fields as required. +[The option does not affect CSV result files, which cannot currently store such information.] +

    +

    Also, an option exists here to instruct JMeter to run the serially rather than in parallel.

    +

    +Test plan now provides an easy way to add classpath setting to a specific test plan. +The feature is additive, meaning that you can add jar files or directories, but removing an entry requires restarting JMeter. +Note that this cannot be used to add JMeter GUI plugins, because they are processed earlier. +However it can be useful for utility jars such as JDBC drivers. +

    +

    +JMeter properties also provides an entry for loading additional classpaths. +In jmeter.properties, edit "user.classpath" to include additional libraries. +

    +
    +
    + + + +

    A Thread Group defines a pool of users that will execute a particular test case against your server. In the Thread Group GUI, you can control the number of users simulated (num of threads), the ramp up time (how long it takes to start all the threads), the number of times to perform the test, and optionally, a start and stop time for the test.

    +

    +See also and . +

    +

    +When using the scheduler, JMeter runs the thread group until either the number of loops is reached or the duration/end-time is reached - whichever occurs first. +Note that the condition is only checked between samples; when the end condition is reached, that thread will stop. +JMeter does not interrupt samplers which are waiting for a response, so the end time may be delayed arbitrarily. +

    +
    + + + Descriptive name for this element that is shown in the tree. + + Determines what happens if a sampler error occurs, either because the sample itself failed or an assertion failed. + The possible choices are: +
      +
    • Continue - ignore the error and continue with the test
    • +
    • Start Next Loop - ignore the error, start next loop and continue with the test
    • +
    • Stop Thread - current thread exits
    • +
    • Stop Test - the entire test is stopped at the end of any current samples.
    • +
    • Stop Test Now - the entire test is stopped abruptly. Any current samplers are interrupted if possible.
    • +
    +
    + Number of users to simulate. + How long JMeter should take to get all the threads started. If there are 10 threads and a ramp-up time of 100 seconds, then each thread will begin 10 seconds after the previous thread started, for a total time of 100 seconds to get the test fully up to speed. + Number of times to perform the test case. Alternatively, "forever" can be selected causing the test to run until manually stopped. + If the scheduler checkbox is selected, one can choose an absolute start time. When you start your test, JMeter will wait until the specified start time to begin testing. + Note: the Startup Delay field over-rides this - see below. + + If the scheduler checkbox is selected, one can choose an absolute end time. When you start your test, JMeter will wait until the specified start time to begin testing, and it will stop at the specified end time. + Note: the Duration field over-rides this - see below. + + + If the scheduler checkbox is selected, one can choose a relative end time. + JMeter will use this to calculate the End Time, and ignore the End Time value. + + + If the scheduler checkbox is selected, one can choose a relative startup delay. + JMeter will use this to calculate the Start Time, and ignore the Start Time value. + +
    +
    + + + +

    The WorkBench simply provides a place to temporarily store test elements while not in use, for copy/paste purposes, or any other purpose you desire. +When you save your test plan, WorkBench items are not saved with it. +Your WorkBench can be saved independently, if you like (right-click on WorkBench and choose Save).

    +

    Certain test elements are only available on the WorkBench:

    +
      +
    • +
    • +
    • +
    +
    +
    + + +

    + The SSL Manager is a way to select a client certificate so that you can test + applications that use Public Key Infrastructure (PKI). + It is only needed if you have not set up the appropriate System properties. +

    + +Choosing a Client Certificate +

    + You may either use a Java Key Store (JKS) format key store, or a Public Key + Certificate Standard #12 (PKCS12) file for your client certificates. There + is a feature of the JSSE libraries that require you to have at least a six character + password on your key (at least for the keytool utility that comes with your + JDK). +

    +

    + To select the client certificate, choose Options->SSL Manager from the menu bar. + You will be presented with a file finder that looks for PKCS12 files by default. + Your PKCS12 file must have the extension '.p12' for SSL Manager to recognize it + as a PKCS12 file. Any other file will be treated like an average JKS key store. + If JSSE is correctly installed, you will be prompted for the password. The text + box does not hide the characters you type at this point--so make sure no one is + looking over your shoulder. The current implementation assumes that the password + for the keystore is also the password for the private key of the client you want + to authenticate as. +

    +

    Or you can set the appropriate System properties - see the system.properties file.

    +

    + The next time you run your test, the SSL Manager will examine your key store to + see if it has at least one key available to it. If there is only one key, SSL + Manager will select it for you. If there is more than one key, it currently selects the first key. + There is currently no way to select other entries in the keystore, so the desired key must be the first. +

    +Things to Look Out For +

    + You must have your Certificate Authority (CA) certificate installed properly + if it is not signed by one of the five CA certificates that ships with your + JDK. One method to install it is to import your CA certificate into a JKS + file, and name the JKS file "jssecacerts". Place the file in your JRE's + lib/security folder. This file will be read before the "cacerts" file in + the same directory. Keep in mind that as long as the "jssecacerts" file + exists, the certificates installed in "cacerts" will not be used. This may + cause problems for you. If you don't mind importing your CA certificate into + the "cacerts" file, then you can authenticate against all of the CA certificates + installed. +

    +
    + + +

    The Proxy Server allows JMeter to watch and record your actions while you browse your web application +with your normal browser. JMeter will create test sample objects and store them +directly into your test plan as you go (so you can view samples interactively while you make them).

    + +

    To use the proxy server, add the HTTP Proxy Server element to the workbench. +Select the WorkBench element in the tree, and right-click on this element to get the +Add menu (Add --> Non-Test Elements --> HTTP Proxy Server).

    +

    +You also need to set up your browser to use the JMeter proxy port as the proxy for HTTP and HTTPS requests. +Do not use JMeter as the proxy for any other request types - FTP, etc. - as the JMeter proxy cannot handle them. +

    +

    +When recording HTTPS, the JMeter proxy server uses a dummy certificate to enable it to accept the SSL connection from +the browser. This certificate is not one of the certificates that browsers normally trust, and will not be for the +correct host, so the browser should display a dialogue asking if you want to accept the certificate or not. For example: + +1) The server's name "www.example.com" does not match the certificate's name + "JMeter Proxy". Somebody may be trying to eavesdrop on you. +2) The certificate for "JMeter Proxy" is signed by the unknown Certificate Authority + "JMeter Proxy". It is not possible to verify that this is a valid certificate. + +You will need to accept the certificate in order to allow the JMeter Proxy to intercept the SSL traffic in order to +record it. You should only accept the certificate temporarily. +

    +

    +The following properties can be used to change the certificate that is used: +

      +
    • proxy.cert.directory - the directory in which to find the certificate (default = JMeter bin/)
    • +
    • proxy.cert.file - name of the keystore file (default "proxyserver.jks")
    • +
    • proxy.cert.keystorepass - keystore password (default "password")
    • +
    • proxy.cert.keypassword - certificate key password (default "password")
    • +
    • proxy.cert.type - the certificate type (default "JKS")
    • +
    • proxy.cert.factory - the factory (default "SunX509")
    • +
    • proxy.ssl.protocol - the protocol to be used (default "SSLv3")
    • +
    +

    + +If your browser currently uses a proxy (e.g. a company intranet may route all external requests via a proxy), +then you need to tell JMeter to use that proxy before starting JMeter, +using the command-line options -H and -P. +This setting will also be needed when running the generated test plan. + +
    + + + Descriptive name for this controller that is shown in the tree. + The port that the Proxy Server listens to. 8080 is the default, but you can change it. + + [Note: HTTPS spoofing should no longer be required] + When you enable HTTPS spoofing, the following happens: +
      +
    • All matching (see below) http requests from the client are turned into https (between the proxy + and the web server).
    • +
    • All text response data is scanned and any occurrence of the string "https://" + is replaced with "http://"; the default HTTPS port (443) is also removed if present.
    • +
    + So if you want to use this feature, while you are browsing in your client, + instead of typing "https://..." into the browser, type "http://...". + JMeter will request and record everything that matches as https, whether it should be or not. +
    + + If this is specified, it must be a regular expression (java.util.regex) which matches the + HTTP URL(s) to be spoofed. + For example, if you want to spoof http://a.b.c/service/ but not http://a.b.c/images, + then you could use the expression "http://a.b.c/service/.*". + Note that the expression ends in ".*" because it must match the whole URL. + + The controller where the proxy will store the generated samples. By default, it will look for a Recording Controller and store them there wherever it is. + Whether to group samplers for requests from a single "click" (requests received without significant time separation), and how to represent that grouping in the recording: +
      +
    • Do not group samplers: store all recorded samplers sequentially, without any grouping.
    • +
    • Add separators between groups: add a controller named "--------------" to create a visual separation between the groups. Otherwise the samplers are all stored sequentially.
    • +
    • Put each group in a new controller: create a new for each group, and store all samplers for that group in it.
    • +
    • Store 1st sampler of each group only: only the first request in each group will be recorded. The "Follow Redirects" and "Retrieve All Embedded Resources..." flags will be turned on in those samplers.
    • +
    • Put each group in a new transaction controller: create a new for each group, and store all samplers for that group in it.
    • +
    + The property proxy.pause determines the minimum gap that JMeter needs between requests + to treat them as separate "clicks". The default is 1000 (milliseconds) i.e. 1 second. + If you are using grouping, please ensure that you leave the required gap between clicks. +
    + + Should headers be added to the plan? + If specified, a Header Manager will be added to each HTTP Sampler. + The Proxy server always removes Cookie and Authorization headers from the generated Header Managers. + By default it also removes If-Modified-Since and If-None-Match headers. + These are used to determine if the browser cache items are up to date; + when recording one normally wants to download all the content. + To change which additional headers are removed, define the JMeter property proxy.headers.remove + as a comma-separated list of headers. + + Add a blank assertion to each sampler? + Use Regex Matching when replacing variables? If checked replacement will use word boundaries, ie it will only replace word matching values of variable, not part of a word. A word boundary follows Perl5 definition and is equivalent to \b. + Which type of sampler to generate (the Java default or HTTPClient) + Set Redirect Automatically in the generated samplers? + Set Follow Redirects in the generated samplers? + Set Use Keep-Alive in the generated samplers? + Set Retrieve all Embedded Resources in the generated samplers? + + Filter the requests based on the content-type - e.g. "text/html [;charset=utf-8 ]". + The fields are regular expressions which are checked to see if they are contained in the content-type. + [Does not have to match the entire field]. + The include filter is checked first, then the exclude filter. + Samples which are filtered out will not be stored. + + Regular expressions that are matched against the full URL that is sampled. Allows filtering of requests that are recorded. All requests pass through, but only + those that meet the requirements of the Include/Exclude fields are recorded. If both Include and Exclude are + left empty, then everything is recorded (which can result in dozens of samples recorded for each page, as images, stylesheets, + etc are recorded). If there is at least one entry in the Include field, then only requests that match one or more Include patterns are + recorded. + Regular expressions that are matched against the URL that is sampled. + Any requests that match one or more Exclude pattern are not recorded. + Start the proxy server. JMeter writes the following message to the console once the proxy server has started up and is ready to take requests: "Proxy up and running!". + Stop the proxy server. + Stops and restarts the proxy server. This is + useful when you change/add/delete an include/exclude filter expression. +
    + +

    The include and exclude patterns are treated as regular expressions (using Jakarta ORO). +They will be matched against the host name, port (actual or implied) path and query (if any) of each browser request. +If the URL you are browsing is

    +"http://jmeter.apache.org/jmeter/index.html?username=xxxx",

    +then the regular expression will be tested against the string:

    +"jmeter.apache.org:80/jmeter/index.html?username=xxxx".

    +Thus, if you want to include all .html files, your regular expression might look like:

    +".*\.html(\?.*)?" - or ".*\.html" +if you know that there is no query string or you only want html pages without query strings. +

    +

    +If there are any include patterns, then the URL must match at least one of the patterns +, otherwise it will not be recorded. +If there are any exclude patterns, then the URL must not match any of the patterns +, otherwise it will not be recorded. +Using a combination of includes and excludes, +you should be able to record what you are interested in and skip what you are not. +

    + +

    +N.B. the string that is matched by the regular expression must be the same as the whole host+path string.

    Thus "\.html" will not match j.a.o/index.html +

    + +

    +Versions of JMeter from 2.3.2 are able to capture binary POST data. +To configure which content-types are treated as binary, update the JMeter property proxy.binary.types. +The default settings are as follows: +

    +# These content-types will be handled by saving the request in a file:
    +proxy.binary.types=application/x-amf,application/x-java-serialized-object
    +# The files will be saved in this directory:
    +proxy.binary.directory=user.dir
    +# The files will be created with this file filesuffix:
    +proxy.binary.filesuffix=.binary
    +
    +

    + +

    It is also possible to have the proxy add timers to the recorded script. To +do this, create a timer directly within the HTTP Proxy Server component. +The proxy will place a copy of this timer into each sample it records, or into +the first sample of each group if you're using grouping. This copy will then be +scanned for occurences of variable ${T} in its properties, and any such +occurences will be replaced by the time gap from the previous sampler +recorded (in milliseconds).

    + +

    When you are ready to begin, hit "start".

    +You will need to edit the proxy settings of your browser to point at the +appropriate server and port, where the server is the machine JMeter is running on, and +the port # is from the Proxy Control Panel shown above. + +

    Where Do Samples Get Recorded?

    +

    JMeter places the recorded samples in the Target Controller you choose. If you choose the default option +"Use Recording Controller", they will be stored in the first Recording Controller found in the test object tree (so be +sure to add a Recording Controller before you start recording).

    + +

    +If the Proxy does not seem to record any samples, this could be because the browser is not actually using the proxy. +To check if this is the case, try stopping the proxy. +If the browser still downloads pages, then it was not sending requests via the proxy. +Double-check the browser options. +If you are trying to record from a server running on the same host, +then check that the browser is not set to "Bypass proxy server for local addresses" +(this example is from IE7, but there will be similar options for other browsers). +If JMeter does not record browser URLs such as http://localhost/ or http://127.0.0.1/, +try using the non-loopback hostname or IP address, e.g. http://myhost/ or http://192.168.0.2/. +

    + +

    Handling of HTTP Request Defaults

    +

    If the HTTP Proxy Server finds enabled directly within the +controller where samples are being stored, or directly within any of its parent controllers, the recorded samples +will have empty fields for the default values you specified. You may further control this behaviour by placing an +HTTP Request Defaults element directly within the HTTP Proxy Server, whose non-blank values will override +those in the other HTTP Request Defaults. See Best +Practices with the Proxy Server for more info.

    + +

    User Defined Variable replacement

    +

    Similarly, if the HTTP Proxy Server finds (UDV) directly within the +controller where samples are being stored, or directly within any of its parent controllers, the recorded samples +will have any occurences of the values of those variables replaced by the corresponding variable. Again, you can +place User Defined Variables directly within the HTTP Proxy Server to override the values to be replaced. See + Best Practices with the Proxy Server for more info.

    + +Please note that matching is case-sensitive. + +

    Replacement by Variables: by default, the Proxy server looks for all occurences of UDV values. +If you define the variable "WEB" with the value "www", for example, +the string "www" will be replaced by ${WEB} wherever it is found. +To avoid this happening everywhere, set the "Regex Matching" check-box. +This tells the proxy server to treat values as Regexes (using ORO). +

    +If you want to match a whole string only, enclose it in ^$, e.g. "^thus$". +

    +If you want to match /images at the start of a string only, use the value "^/images". +Jakarta ORO also supports zero-width look-ahead, so one can match /images/... +but retain the trailing / in the output by using "^/images(?=/)". +Note that the current version of Jakara ORO does not support look-behind - i.e. "(?&lt;=...) or (?&lt;!...)". +

    +If there are any problems interpreting any variables as patterns, these are reported in jmeter.log, +so be sure to check this if UDVs are not working as expected. +

    +

    When you are done recording your test samples, stop the proxy server (hit the "stop" button). Remember to reset +your browser's proxy settings. Now, you may want to sort and re-order the test script, add timers, listeners, a +cookie manager, etc.

    + +

    How can I record the server's responses too?

    +

    Just place a listener as a child of the Proxy Server and the responses will be displayed. +You can also add a Post-Processor which will save the responses to files. +

    + +

    Cookie Manager

    +

    +If the server you are testing against uses cookies, remember to add an to the test plan +when you have finished recording it. +During recording, the browser handles any cookies, but JMeter needs a Cookie Manager +to do the cookie handling during a test run. +The JMeter Proxy server passes on all cookies sent by the browser during recording, but does not save them to the test +plan because they are likely to change between runs. +

    +

    Authorization Manager

    +

    +The Proxy server passes on any Authorization headers sent by the browser, but does not save them in the test plan. +If the site requires Authorization, you will need to add an Authorization Manager and fill it in with the necessary entries. +

    +

    Uploading files

    +

    +Some browsers (e.g. Firefox and Opera) don't include the full name of a file when uploading files. +This can cause the JMeter proxy server to fail. +One solution is to ensure that any files to be uploaded are in the JMeter working directory, +either by copying the files there or by starting JMeter in the directory containing the files. +

    +

    Recording HTTP Based Non Textual Protocols not natively available in JMeter

    +

    +You may have to record an HTTP protocol that is not handled by default by JMeter (Custom Binary Protocol, Adobe Flex, Microsoft Silverlight... ). +Although JMeter does not provide a native proxy implementation to record these protocols, you have the ability to +record these protocols by implementing a custom SamplerCreator. This Sampler Creator will translate the binary format into a HTTPSamplerBase subclass +that can be added to the JMeter Test Case. +For more details see "Extending JMeter". +

    +
    + + + +

    +The HTTP Mirrror Server is a very simple HTTP server - it simply mirrors the data sent to it. +This is useful for checking the content of HTTP requests. +

    +

    +It uses default port 8081 since 2.6. +

    +
    + + Port on which Mirror server listens, defaults to 8081. + If set to a value > 0, number of threads serving requests will be limited to the configured number, if set to a value <=0 + a new thread will be created to serve each incoming request. Defaults to 0 + Size of queue used for holding tasks before they are executed by Thread Pool, when Thread pool is exceeded, incoming requests will + be held in this queue and discarded when this queue is full. This parameter is only used if Max Number of Threads is greater than 0. Defaults to 25 + + +Note that you can make simulate requests response time by adding an HTTP Header Manager with the following name/value pair: +
      +
    • X-Sleep=Time to sleep in ms
    • +
    +
    +
    + + + +

    +The Property Display shows the values of System or JMeter properties. +Values can be changed by entering new text in the Value column. +It is available only on the WorkBench. +

    +
    + + Descriptive name for this element that is shown in the tree. + +
    + + + +

    +The Debug Sampler generates a sample containing the values of all JMeter variables and/or properties. +

    +

    +The values can be seen in the Listener Response Data pane. +

    +
    + + Descriptive name for this element that is shown in the tree. + Include JMeter properties ? + Include JMeter variables ? + Include System properties ? + +
    + + + +

    +The Debug PostProcessor creates a subSample with the details of the previous Sampler properties, +JMeter variables, properties and/or System Properties. +

    +

    +The values can be seen in the Listener Response Data pane. +

    +
    + + Descriptive name for this element that is shown in the tree. + Whether to show JMeter properties (default false). + Whether to show JMeter variables (default false). + Whether to show Sampler properties (default true). + Whether to show System properties (default false). + +
    + + + +

    +The Test Fragment is used in conjunction with the and . +

    +
    + + Descriptive name for this element that is shown in the tree. + +
    + + + +

    + A special type of ThreadGroup that can be utilized to perform Pre-Test Actions. The behavior of these threads + is exactly like a normal element. The difference is that these type of threads + execute before the test proceeds to the executing of regular Thread Groups. +

    +
    +
    + + + +

    + A special type of ThreadGroup that can be utilized to perform Post-Test Actions. The behavior of these threads + is exactly like a normal element. The difference is that these type of threads + execute after the test has finished executing its regular Thread Groups. +

    +
    +
    + +^ + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/functions.xml b/ApacheJmeter/xdocs/usermanual/functions.xml new file mode 100644 index 0000000..7436f2b --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/functions.xml @@ -0,0 +1,1226 @@ + + + + +]> + + + + + User's Manual: Functions and Variables + + + + + + +
    +

    +JMeter functions are special values that can populate fields of any Sampler or other +element in a test tree. A function call looks like this:

    + +

    ${__functionName(var1,var2,var3)}

    + +

    +Where "__functionName" matches the name of a function. +

    +Parentheses surround the parameters sent to the function, for example ${__time(YMD)} +The actual parameters vary from function to function. +Functions that require no parameters can leave off the parentheses, for example ${__threadNum}. +

    + +

    +If a function parameter contains a comma, then be sure to escape this with "\", otherwise JMeter will treat it as a parameter delimiter. +For example: +

    +${__time(EEE\, d MMM yyyy)}
    +
    +

    +

    Variables are referenced as follows: +

    +${VARIABLE}
    +
    +

    +

    + +If an undefined function or variable is referenced, JMeter does not report/log an error - the reference is returned unchanged. +For example if UNDEF is not defined as a variable, then the value of ${UNDEF} is ${UNDEF}. + +Variables, functions (and properties) are all case-sensitive. + +Versions of JMeter after 2.3.1 trim spaces from variable names before use, so for example +${__Random(1,63, LOTTERY )} will use the variable 'LOTTERY' rather than ' LOTTERY '. + +

    + +Properties are not the same as variables. +Variables are local to a thread; properties are common to all threads, +and need to be referenced using the __P or __property function. + +

    List of functions, loosely grouped into types.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Type of functionNameCommentSince
    Information threadNumget thread number1.X
    Information samplerNameget the sampler name (label)2.5
    Information machineIPget the local machine IP address2.6
    Information machineNameget the local machine name1.X
    Information timereturn current time in various formats2.2
    Information loglog (or display) a message (and return the value)2.2
    Information lognlog (or display) a message (empty return value)2.2
    Input StringFromFileread a line from a file1.9
    Input FileToStringread an entire file2.4
    Input CSVReadread from CSV delimited file1.9
    Input XPathUse an XPath expression to read from a file2.0.3
    Calculation countergenerate an incrementing number1.X
    Calculation intSumadd int numbers1.8.1
    Calculation longSumadd long numbers2.3.2
    Calculation Randomgenerate a random number1.9
    Calculation RandomStringgenerate a random string2.6
    Scripting BeanShellrun a BeanShell script1.X
    Scripting javaScriptprocess JavaScript (Mozilla Rhino)1.9
    Scripting jexl, jexl2evaluate a Commons Jexl expressionjexl(2.2), jexl2(2.6)
    Properties property read a property2.0
    Properties Pread a property (shorthand method)2.0
    Properties setPropertyset a JMeter property2.1
    Variables splitSplit a string into variables2.0.2
    Variables Vevaluate a variable name2.3RC3
    Variables evalevaluate a variable expression2.3.1
    Variables evalVarevaluate an expression stored in a variable2.3.1
    String regexFunctionparse previous response using a regular expression1.X
    String chargenerate Unicode char values from a list of numbers2.3.3
    String unescapeProcess strings containing Java escapes (e.g. \n & \t)2.3.3
    String unescapeHtmlDecode HTML-encoded strings2.3.3
    String escapeHtmlEncode strings using HTML encoding2.3.3
    String TestPlanNameReturn name of current test plan2.6
    +

    + +

    There are two kinds of functions: user-defined static values (or variables), and built-in functions.

    +User-defined static values allow the user to define variables to be replaced with their static value when +a test tree is compiled and submitted to be run. This replacement happens once at the beginning of the test +run. This could be used to replace the DOMAIN field of all HTTP requests, for example - making it a simple +matter to change a test to target a different server with the same test. +

    +

    +Note that variables cannot currently be nested; i.e ${Var${N}} does not work. +The __V (variable) function (versions after 2.2) can be used to do this: ${__V(Var${N})}. +In earlier JMeter versions one can use ${__BeanShell(vars.get("Var${N}")}. +

    +

    This type of replacement is possible without functions, but was less convenient and less intuitive. +It required users to create default config elements that would fill in blank values of Samplers. +Variables allow one to replace only part of any given value, not just filling in blank values.

    +

    +With built-in functions users can compute new values at run-time based on previous response data, which +thread the function is in, the time, and many other sources. These values are generated fresh for every +request throughout the course of the test.

    +Functions are shared between threads. +Each occurrence of a function call in a test plan is handled by a separate function instance. +
    + + +

    +Functions and variables can be written into any field of any test component (apart from the TestPlan - see below). +Some fields do not allow random strings +because they are expecting numbers, and thus will not accept a function. However, most fields will allow +functions. +

    +

    +Functions which are used on the Test Plan have some restrictions. +JMeter thread variables will have not been fully set up when the functions are processed, +so variable names passed as parameters will not be set up, and variable references will not work, +so split() and regex() and the variable evaluation functions won't work. +The threadNum() function won't work (and does not make sense at test plan level). +The following functions should work OK on the test plan: +

      +
    • intSum
    • +
    • longSum
    • +
    • machineName
    • +
    • BeanShell
    • +
    • javaScript
    • +
    • jexl
    • +
    • random
    • +
    • time
    • +
    • property functions
    • +
    • log functions
    • +
    +

    +

    +Configuration elements are processed by a separate thread. +Therefore functions such as __threadNum do not work properly in elements such as User Defined Variables. +Also note that variables defined in a UDV element are not available until the element has been processed. +

    +When using variable/function references in SQL code (etc), +remember to include any necessary quotes for text strings, +i.e. use

    +SELECT item from table where name='${VAR}' +

    not +

    +SELECT item from table where name=${VAR} +

    (unless VAR itself contains the quotes) +
    +
    + + +

    Referencing a variable in a test element is done by bracketing the variable name with '${' and '}'.

    +

    Functions are referenced in the same manner, but by convention, the names of +functions begin with "__" to avoid conflict with user value names*. Some functions take arguments to +configure them, and these go in parentheses, comma-delimited. If the function takes no arguments, the parentheses can +be omitted.

    + +

    Argument values that themselves contain commas should be escaped as necessary. +If you need to include a comma in your parameter value, escape it like so: '\,'. +This applies for example to the scripting functions - Javascript, Beanshell, Jexl - where it is necessary to escape any commas +that may be needed in script method calls - e.g. +

    +
    +    ${__BeanShell(vars.put("name"\,"value"))}
    +
    +

    +Alternatively, you can define your script as a variable, e.g. on the Test Plan: +

    SCRIPT          vars.put("name","value")
    +The script can then be referenced as follows: +
    ${__BeanShell(${SCRIPT})}
    +There is no need to escape commas in the SCRIPT variable because the function call is parsed before the variable is replaced with its value. +This works well in conjunction with the BSF or BeanShell Samplers, as these can be used to test Javascript, Jexl and BeanShell scripts. +

    +

    +Functions can reference variables and other functions, for example +${__XPath(${__P(xpath.file),${XPATH})} +will use the property "xpath.file" as the file name +and the contents of the variable XPATH as the expression to search for. +

    +

    +JMeter provides a tool to help you construct +function calls for various built-in functions, which you can then copy-paste. +It will not automatically escape values for you, since functions can be parameters to other functions, and you should only escape values you intend as literal. +

    + +If a string contains a backslash('\') and also contains a function or variable reference, the backslash will be removed if +it appears before '$' or ',' or '\'. +This behaviour is necessary to allow for nested functions that include commas or the string ${. +Backslashes before '$' or ',' or '\' are not removed if the string does not contain a function or variable reference. + +

    +The value of a variable or function can be reported using the __logn() function. +The __logn() function reference can be used anywhere in the test plan after the variable has been defined. +Alternatively, the Java Request sampler can be used to create a sample containing variable references; +the output will be shown in the appropriate Listener. +For versions of JMeter later than 2.3, there is a +that can be used to display the values of variables etc in the Tree View Listener. +

    +*If you define a user-defined static variable with the same name as a built-in function, your static +variable will override the built-in function. +
    + + +

    The Function Helper dialog is available from JMeter's Tools menu.

    +
    Function Helper Dialog
    +

    Using the Function Helper, you can select a function from the pull down, and assign +values for its arguments. The left column in the table provides a brief description of the +argument, and the right column is where you write in the value for that argument. Different +functions take different arguments.

    +

    Once you have done this, click the "generate" button, and the appropriate string is generated +for you to copy-paste into your test plan wherever you like.

    +
    + + + + +

    The Regex Function is used to parse the previous response (or the value of a variable) using any regular +expression (provided by user). The function returns the template string with variable values filled +in.

    +

    The __regexFunction can also store values for future use. In the sixth parameter, you can specify +a reference name. After this function executes, the same values can be retrieved at later times +using the syntax for user-defined values. For instance, if you enter "refName" as the sixth +parameter you will be able to use: +

      +
    • ${refName} to refer to the computed result of the second parameter ("Template for the +replacement string") parsed by this function
    • +
    • ${refName_g0} to refer to the entire match parsed by this function.
    • +
    • ${refName_g1} to refer to the first group parsed by this function.
    • +
    • ${refName_g#} to refer to the nth group parsed by this function.
    • +
    • ${refName_matchNr} to refer to the number of groups found by this function.
    • +
    +

    +
    + + + The first argument is the regular expression + to be applied to the response data. It will grab all matches. Any parts of this expression + that you wish to use in your template string, be sure to surround in parentheses. Example: + &lt;a href="(.*)"&gt;. This will grab the value of the link and store it as the first group (there is + only 1 group). Another example: &lt;input type="hidden" name="(.*)" value="(.*)"&gt;. This will + grab the name as the first group, and the value as the second group. These values can be used + in your template string + This is the template string that will replace + the function at run-time. To refer to a group captured in the regular expression, use the syntax: + $[group_number]$. Ie: $1$, or $2$. Your template can be any string. + The third argument tells JMeter which match + to use. Your regular expression might find numerous matches. You have four choices: +
    • An integer - Tells JMeter to use that match. '1' for the first found match, '2' for the + second, and so on
    • +
    • RAND - Tells JMeter to choose a match at random.
    • +
    • ALL - Tells JMeter to use all matches, and create a template string for each one and then + append them all together. This option is little used.
    • +
    • A float number between 0 and 1 - tells JMeter to find the Xth match using the formula: + (number_of_matches_found * float_number) rounded to nearest integer.
    • +
    + If 'ALL' was selected for the above argument + value, then this argument will be inserted between each appended copy of the template value. + Default value returned if no match is found + A reference name for reusing the values parsed by this function.

    + Stored values are ${refName} (the replacement template string) and ${refName_g#} where "#" is the + group number from the regular expression ("0" can be used to refer to the entire match).
    + Input variable name. + If specified, then the value of the variable is used as the input instead of using the previous sample result. + +
    +
    + + +

    The counter generates a new number each time it is called, starting with 1 +and incrementing by +1 each time. The counter can be configured to keep each simulated user's values +separate, or to use the same counter for all users. If each user's values is incremented separately, +that is like counting the number of iterations through the test plan. A global counter is like +counting how many times that request was run. +

    +

    The counter uses an integer variable to hold the count, which therefore has a maximum of 2,147,483,647.

    +

    The counter function instances are now completely independent. +[JMeter 2.1.1 and earlier used a fixed thread variable to keep track of the per-user count, +so multiple counter functions operated on the same value.] +The global counter - "FALSE" - is separately maintained by each counter instance. +

    +

    + +Multiple __counter function calls in the same iteration won't increment the value further. + +
    +If you want to have a count that increments for each sample, use the function in a Pre-Processor such as . +

    +
    + + TRUE if you wish each simulated user's counter + to be kept independent and separate from the other users. FALSE for a global counter. + +A reference name for reusing the value created by this function.

    + Stored values are of the form ${refName}. This allows you to keep one counter and refer to its value in + multiple places. [For JMeter 2.1.1 and earlier this parameter was required.]
    +
    +
    + + +

    The thread number function simply returns the number of the thread currently +being executed. These numbers are independent of ThreadGroup, meaning thread #1 in one threadgroup +is indistinguishable from thread #1 in another threadgroup, from the point of view of this function.

    + +

    There are no arguments for this function.

    +
    + +This function does not work in any Configuration elements (e.g. User Defined Variables) as these are run from a separate thread. +Nor does it make sense to use it on the Test Plan. + +
    + + + +

    +The intSum function can be used to compute the sum of two or more integer values. +

    + +JMeter Versions 2.3.1 and earlier required the reference name to be present. +The reference name is now optional, but it must not be a valid integer. + +
    + + + The first int value. + The second int value. + The nth int value. + A reference name for reusing the value computed by this function. + If specified, the reference name must contain at least one non-numeric character otherwise it will + be treated as another int value to be added. + + +
    + + +

    The longSum function can be used to compute the sum of two or more long values. +

    + + + The first long value. + The second long value. + The nth long value. + A reference name for reusing the value computed by this function. + If specified, the reference name must contain at least one non-numeric character otherwise it will + be treated as another long value to be added. + + +
    + + + + + + +

    + The StringFromFile function can be used to read strings from a text file. + This is useful for running tests that require lots of variable data. + For example when testing a banking application, 100s or 1000s of different account numbers might be required. +

    +

    + See also the + CSV Data Set Config test element + which may be easier to use. However, that does not currently support multiple input files. +

    + +

    + Each time it is called it reads the next line from the file. + All threads share the same instance, so different threads will get different lines. + When the end of the file is reached, it will start reading again from the beginning, + unless the maximum loop count has been reached. + If there are multiple references to the function in a test script, each will open the file independently, + even if the file names are the same. + [If the value is to be used again elsewhere, use different variable names for each function call.] +

    + + Function instances are shared between threads, and the file is (re-)opened by whatever thread + happens to need the next line of input, so using the threadNumber as part of the file name + will result in unpredictable behaviour. + +

    If an error occurs opening or reading the file, then the function returns the string "**ERR**"

    + + + + Path to the file name. + (The path can be relative to the JMeter launch directory) + If using optional sequence numbers, the path name should be suitable for passing to DecimalFormat. + See below for examples. + + +A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. +Defaults to "StringFromFile_". + + Initial Sequence number (if omitted, the End sequence number is treated as a loop count) + Final sequence number (if omitted, seqence numbers can increase without limit) + +

    The file name parameter is resolved when the file is opened or re-opened.

    +

    The reference name parameter (if supplied) is resolved every time the function is executed.

    +

    Using sequence numbers:

    +

    When using the optional sequence numbers, the path name is used as the format string for java.text.DecimalFormat. + The current sequence number is passed in as the only parameter. + If the optional start number is not specified, the path name is used as is. + Useful formatting sequences are: +

    +

    + +# - insert the number, with no leading zeros or spaces

    +000 - insert the number packed out to 3 digits with leading zeros if necessary +

    +Examples:

    + pin#'.'dat -> pin1.dat, ... pin9.dat, pin10.dat, ... pin9999.dat

    + pin000'.'dat -> pin001.dat ... pin099.dat ... pin999.dat ... pin9999.dat

    + pin'.'dat# -> pin.dat1, ... pin.dat9 ... pin.dat999 +

    + If more digits are required than there are formatting characters, the number will be + expanded as necessary.

    + To prevent a formatting character from being interpreted, + enclose it in single quotes. Note that "." is a formatting character, + and must be enclosed in single quotes + (though #. and 000. work as expected in locales where the decimal point is also ".") +

    + In other locales (e.g. fr), the decimal point is "," - which means that "#." + becomes "nnn,".

    + See the documentation for DecimalFormat for full details.

    + If the path name does not contain any special formatting characters, + the current sequence number will be appended to the name, otherwise + the number will be inserted aaccording to the fomatting instructions.

    + If the start sequence number is omitted, and the end sequence number is specified, + the sequence number is interpreted as a loop count, and the file will be used at most "end" times. + In this case the filename is not formatted. + +

    + ${_StringFromFile(PIN#'.'DAT,,1,2)} - reads PIN1.DAT, PIN2.DAT

    + ${_StringFromFile(PIN.DAT,,,2)} - reads PIN.DAT twice

    +
    + Note that the "." in PIN.DAT above should not be quoted. + In this case the start number is omitted, so the file name is used exactly as is. +

    + + + +

    The machineName function returns the local host name

    + + + A reference name for reusing the value + computed by this function. + +
    + + +

    The machineIP function returns the local IP address

    + + + A reference name for reusing the value + computed by this function. + +
    + + + +

    +The javaScript function executes a piece of JavaScript (not Java!) code and returns its value +

    +

    +The JMeter Javascript function calls a standalone JavaScript interpreter. +Javascript is used as a scripting language, so you can do calculations etc.

    +

    +For details of the language, please see Mozilla Rhino Overview +

    +

    +The following variables are made available to the script: +

    +
      +
    • log - the logger for the function
    • +
    • ctx - JMeterContext object
    • +
    • vars - JMeterVariables object
    • +
    • threadName - String containing the current thread name (in 2.3.2 it was misspelt as "theadName")
    • +
    • sampler - current Sampler object (if any)
    • +
    • sampleResult - previous SampleResult object (if any)
    • +
    • props - JMeter Properties object
    • +
    +

    +Rhinoscript allows access to static methods via its Packages object. +See the Scripting Java documentation. +For example one can access the JMeterContextService static methods thus: +Packages.org.apache.jmeter.threads.JMeterContextService.getTotalThreads() +

    + +JMeter is not a browser, and does not interpret the JavaScript in downloaded pages. + +
    + + + The JavaScript expression to be executed. For example: +
      +
    • new Date() - return the current date and time
    • +
    • Math.floor(Math.random()*(${maxRandom}+1)) + - a random number between 0 and the variable maxRandom
    • +
    • ${minRandom}+Math.floor(Math.random()*(${maxRandom}-${minRandom}+1)) + - a random number between the variables minRandom and maxRandom
    • +
    • "${VAR}"=="abcd"
    • +
    +
    + A reference name for reusing the value + computed by this function. +
    +Remember to include any necessary quotes for text strings and JMeter variables. Also, if +the expression has commas, please make sure to escape them. For example in: +

    +${__javaScript('${sp}'.slice(7\,99999))} +

    +the comma after 7 is escaped.
    +
    + + +

    The random function returns a random number that lies between the given min and max values.

    + + + A number + A bigger number + A reference name for reusing the value + computed by this function. + +
    + + +

    The RandomString function returns a random String of length using characters in chars to use.

    + + + A number length of generated String + Chars used to generate String + A reference name for reusing the value + computed by this function. + +
    + + +

    The CSVRead function returns a string from a CSV file (c.f. StringFromFile)

    +

    NOTE: versions up to 1.9.1 only supported a single file. + JMeter versions since 1.9.1 support multiple file names. +

    +

    In most cases, the newer + CSV Data Set Config element + is easier to use.

    +

    + When a filename is first encountered, the file is opened and read into an internal + array. If a blank line is detected, this is treated as end of file - this allows + trailing comments to be used (N.B. this feature was introduced in versions after 1.9.1) +

    +

    All subsequent references to the same file name use the same internal array. + N.B. the filename case is significant to the function, even if the OS doesn't care, + so CSVRead(abc.txt,0) and CSVRead(aBc.txt,0) would refer to different internal arrays. +

    +

    + The *ALIAS feature allows the same file to be opened more than once, + and also allows for shorter file names. +

    +

    + Each thread has its own internal pointer to its current row in the file array. + When a thread first refers to the file it will be allocated the next free row in + the array, so each thread will access a different row from all other threads. + [Unless there are more threads than there are rows in the array.] +

    +

    + Note: the function splits the line at every comma by default. + If you want to enter columns containing commas, then you will need + to change the delimiter to a character that does not appear in any + column data, by setting the property: csvread.delimiter +

    +
    + + + The file (or *ALIAS) to read from + + The column number in the file. + 0 = first column, 1 = second etc. + "next" - go to next line of file. + *ALIAS - open a file and assign it to the alias + + +

    For example, you could set up some variables as follows: +

      +
    • COL1a ${__CSVRead(random.txt,0)}
    • +
    • COL2a ${__CSVRead(random.txt,1)}${__CSVRead(random.txt,next)}
    • +
    • COL1b ${__CSVRead(random.txt,0)}
    • +
    • COL2b ${__CSVRead(random.txt,1)}${__CSVRead(random.txt,next)}
    • +
    +This would read two columns from one line, and two columns from the next available line. +If all the variables are defined on the same User Parameters Pre-Processor, then the lines +will be consecutive. Otherwise, a different thread may grab the next line. +

    + +The function is not suitable for use with large files, as the entire file is stored in memory. +For larger files, use CSV Data Set Config element +or StringFromFile. + +
    + + +

    The property function returns the value of a JMeter property. + If the property value cannot be found, and no default has been supplied, it returns the property name. + When supplying a default value, there is no need to provide a function name - the parameter can be set to null, and it will be ignored. +

    For example:

    +

      +
    • ${__property(user.dir)} - return value of user.dir
    • +
    • ${__property(user.dir,UDIR)} - return value of user.dir and save in UDIR
    • +
    • ${__property(abcd,ABCD,atod)} - return value of property abcd (or "atod" if not defined) and save in ABCD
    • +
    • ${__property(abcd,,atod)} - return value of property abcd (or "atod" if not defined) but don't save it
    • +
    +

    +
    + + + The property name to be retrieved. + A reference name for reusing the value + computed by this function. + The default value for the property. + +
    + + +

    This is a simplified property function which is + intended for use with properties defined on the command line. + Unlike the __property function, there is no option to save the value in a variable, + and if no default value is supplied, it is assumed to be 1. + The value of 1 was chosen because it is valid for common test variables such + as loops, thread count, ramp up etc. +

    For example:

    + +Define the property value: +

    +jmeter -Jgroup1.threads=7 -Jhostname1=www.realhost.edu +

    + +Fetch the values: +

    +${__P(group1.threads)} - return the value of group1.threads +

    +${__P(group1.loops)} - return the value of group1.loops +

    +${__P(hostname,www.dummy.org)} - return value of property hostname or www.dummy.org if not defined +

    +
    +In the examples above, the first function call would return 7, +the second would return 1 and the last would return www.dummy.org +(unless those properties were defined elsewhere!) +

    +
    + + + The property name to be retrieved. + The default value for the property. + If omitted, the default is set to "1". + +
    + + + +

    + The log function logs a message, and returns its input string +

    +
    + + + A string + OUT, ERR, DEBUG, INFO (default), WARN or ERROR + If non-empty, creates a Throwable to pass to the logger + If present, it is displayed in the string. + Useful for identifying what is being logged. + +

    The OUT and ERR log level names are used to direct the output to System.out and System.err respectively. + In this case, the output is always printed - it does not depend on the current log setting. +

    +
    +For example:
    +     ${__log(Message)} - written to the log file as   "...thread Name : Message"
    +     ${__log(Message,OUT)} - written to console window
    +     ${__log(${VAR},,,VAR=)} - written to log file as "...thread Name VAR=value"
    +
    +
    + + + +

    + The logn function logs a message, and returns the empty string +

    +
    + + + A string + OUT, ERR, DEBUG, INFO (default), WARN or ERROR + If non-empty, creates a Throwable to pass to the logger + +

    The OUT and ERR log level names are used to direct the output to System.out and System.err respectively. + In this case, the output is always printed - it does not depend on the current log setting. +

    +
    +For example:
    +     ${__logn(VAR1=${VAR1},OUT)} - write the value of the variable to the console window
    +
    +
    + + + +

    + The BeanShell function evaluates the script passed to it, and returns the result. +

    +

    +For full details on using BeanShell, please see the BeanShell web-site at http://www.beanshell.org/ + +

    +

    +Note that a different Interpreter is used for each independent occurence of the function +in a test script, but the same Interpreter is used for subsequent invocations. +This means that variables persist across calls to the function. +

    +

    +A single instance of a function may be called from multiple threads. +However the function execute() method is synchronised. +

    +

    +If the property "beanshell.function.init" is defined, it is passed to the Interpreter +as the name of a sourced file. This can be used to define common methods and variables. There is a +sample init file in the bin directory: BeanShellFunction.bshrc. +

    +

    +The following variables are set before the script is executed: +

      +
    • log - the logger for the BeanShell function (*)
    • +
    • ctx - the current JMeter context variable
    • +
    • vars - the current JMeter variables
    • +
    • props - JMeter Properties object
    • +
    • threadName - the threadName (String)
    • +
    • Sampler the current Sampler, if any
    • +
    • SampleResult - the current SampleResult, if any
    • +
    +(*) means that this is set before the init file, if any, is processed. +Other variables vary from invocation to invocation. +

    +
    + + + A beanshell script (not a file name) + A reference name for reusing the value + computed by this function. + + +

    +Example: +

    +${__BeanShell(123*456)} - returns 56088
    +${__BeanShell(source("function.bsh"))} - processes the script in function.bsh
    +
    +

    + +Remember to include any necessary quotes for text strings and JMeter variables that represent text strings. + +
    + + + +

    + The split function splits the string passed to it according to the delimiter, + and returns the original string. If any delimiters are adjacent, "?" is returned as the value. + The split strings are returned in the variables ${VAR_1}, ${VAR_2} etc. + The count of variables is returned in ${VAR_n}. + From JMeter 2.1.2 onwards, a trailing delimiter is treated as a missing variable, and "?" is returned. + Also, to allow it to work better with the ForEach controller, + __split now deletes the first unused variable in case it was set by a previous split. +

    +

    + Example: + +

    + Define VAR="a||c|" in the test plan. +

    + ${__split(${VAR},VAR,|)} +

    + This will return the contents of VAR, i.e. "a||c|" and set the following variables: +

    + VAR_n=4 (3 in JMeter 2.1.1 and earlier) +

    + VAR_1=a +

    + VAR_2=? +

    + VAR_3=c +

    + VAR_4=? (null in JMeter 2.1.1 and earlier) +

    + VAR_5=null (in JMeter 2.1.2 and later) +
    + + + + A delimited string, e.g. "a|b|c" + A reference name for reusing the value + computed by this function. + The delimiter character, e.g. |. +If omitted, , is used. Note that , would need to be specified as \,. + + + + + + +

    + The XPath function reads an XML file and matches the XPath. + Each time the function is called, the next match will be returned. + At end of file, it will wrap around to the start. + If no nodes matched, then the function will return the empty string, + and a warning message will be written to the JMeter log file. + Note that the entire NodeList is held in memory. +

    +

    + Example: + +

    + +

    + ${__XPath(/path/to/build.xml, //target/@name)} +

    + This will match all targets in build.xml and return the contents of the next name attribute +
    + + + + a XML file to parse + a XPath expression to match nodes in the XML file + + + + + +

    The setProperty function sets the value of a JMeter property. + The default return value from the function is the empty string, + so the function call can be used anywhere functions are valid.

    +

    The original value can be returned by setting the optional 3rd parameter to "true".

    +

    Properties are global to JMeter, + so can be used to communicate between threads and thread groups

    +
    + + + The property name to be set. + The value for the property. + Should the original value be returned? + +
    + + + + +

    The time function returns the current time in various formats.

    +
    + + + + The format to be passed to SimpleDateFormat. + The function supports various shorthand aliases, see below. + + The name of the variable to set. + +

    If the format string is omitted, then the function returns the current time in milliseconds. +Otherwise, the current time is passed to SimpleDateFormat. +The following shorthand aliases are provided: +

    +
      +
    • YMD = yyyyMMdd
    • +
    • HMS = HHmmss
    • +
    • YMDHMS = yyyyMMdd-HHmmss
    • +
    • USER1 = whatever is in the Jmeter property time.USER1
    • +
    • USER2 = whatever is in the Jmeter property time.USER2
    • +
    +

    The defaults can be changed by setting the appropriate JMeter property, e.g. +time.YMD=yyMMdd +

    +
    + + + +

    The jexl function returns the result of evaluating a + Commons JEXL expression. + See links below for more information on JEXL expressions. +

    +

    The __jexl function uses Commons JEXL 1, and the __jexl2 function uses Commons JEXL 2

    + +
    + + + + The expression to be evaluated. For example, 6*(5+2) + + The name of the variable to set. + +

    +The following variables are made available to the script: +

    +
      +
    • log - the logger for the function
    • +
    • ctx - JMeterContext object
    • +
    • vars - JMeterVariables object
    • +
    • props - JMeter Properties object
    • +
    • threadName - String containing the current thread name (in 2.3.2 it was misspelt as "theadName")
    • +
    • sampler - current Sampler object (if any)
    • +
    • sampleResult - previous SampleResult object (if any)
    • +
    • OUT - System.out - e.g. OUT.println("message")
    • +
    +

    + Jexl can also create classes and call methods on them, for example: +

    +

    + + Systemclass=log.class.forName("java.lang.System");

    + now=Systemclass.currentTimeMillis(); +
    + Note that the Jexl documentation on the web-site wrongly suggests that "div" does integer division. + In fact "div" and "/" both perform normal division. One can get the same effect + as follows: + + i= 5 / 2; + i.intValue(); // or use i.longValue() + +

    + Versions of JMeter after 2.3.2 allow the expression to contain multiple statements. + JMeter 2.3.2 and earlier only processed the first statement (if there were multiple statements a warning was logged). + +
    + + + +

    The V (variable) function returns the result of evaluating a variable name expression. + This can be used to evaluate nested variable references (which are not currently supported). +

    +

    For example, if one has variables A1,A2 and N=1:

    +
      +
    • ${A1} - works OK
    • +
    • ${A${N}} - does not work (nested variable reference)
    • +
    • ${__V(A${N})} - works OK. A${N} becomes A1, and the __V function returns the value of A1
    • +
    +
    + + + + The variable to be evaluated. + + +
    + + + +

    The eval function returns the result of evaluating an expression stored in a variable. +

    +

    + This allows one to read a string from a file, and process any variable references in it. + For example, if the variable "query" contains "select ${column} from ${table}" + and "column" and "table" contain "name" and "customers", then ${__evalVar(query)} + will evaluate as "select name from customers". +

    +
    + + + + The variable to be evaluated. + + +
    + + +

    The eval function returns the result of evaluating a string expression. +

    +

    + This allows one to interpolate variable and function references in a string + which is stored in a variable. For example, given the following variables: +

      +
    • name=Smith
    • +
    • column=age
    • +
    • table=birthdays
    • +
    • SQL=select ${column} from ${table} where name='${name}'
    • +
    + then ${__eval(${SQL})} will evaluate as "select age from birthdays where name='Smith'". +

    +

    + This can be used in conjunction with CSV Dataset, for example + where the both SQL statements and the values are defined in the data file. +

    +
    + + + + The variable to be evaluated. + + +
    + + + +

    + The char function returns the result of evaluating a list of numbers as Unicode characters. + See also __unescape(), below. +

    +

    + This allows one to add arbitrary character values into fields. +

    +
    + + + + The decimal number (or hex number, if prefixed by 0x, or octal, if prefixed by 0) to be converted to a Unicode character. + + +

    Examples: +
    +${__char(13,10)} = ${__char(0xD,0xA)} = ${__char(015,012)} = CRLF +
    +${__char(165)} = &#165; (yen) +

    +
    + + + +

    + The unescape function returns the result of evaluating a Java-escaped string. See also __char() above. +

    +

    + This allows one to add characters to fields which are otherwise tricky (or impossible) to define via the GUI. +

    +
    + + + + The string to be unescaped. + + +

    Examples: +
    +${__unescape(\r\n)} = CRLF +
    +${__unescape(1\t2)} = 1[tab]2 +

    +
    + + + +

    +Function to unescape a string containing HTML entity escapes +to a string containing the actual Unicode characters corresponding to the escapes. +Supports HTML 4.0 entities. +

    +

    +For example, the string "&#38;lt;Fran&#38;ccedil;ais&#38;gt;" +will become "&lt;Fran&ccedil;ais&gt;". +

    +

    +If an entity is unrecognized, it is left alone, and inserted verbatim into the result string. +e.g. "&gt;&zzzz;x" will become ">&zzzz;x". +

    +

    + Uses StringEscapeUtils#unescapeHtml(String) from Commons Lang. +

    +
    + + + + The string to be unescaped. + + +
    + + + +

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

    +

    +For example,&quot;bread&quot; &amp; &quot;butter&quot; +becomes: +&#38;quot;bread&#38;quot; &#38;amp; &#38;quot;butter&#38;quot;. +

    +

    + Uses StringEscapeUtils#escapeHtml(String) from Commons Lang. +

    +
    + + + + The string to be escaped. + + +
    + + + + +

    + The FileToString function can be used to read an entire file. + Each time it is called it reads the entire file. +

    +

    If an error occurs opening or reading the file, then the function returns the string "**ERR**"

    +
    + + + Path to the file name. + (The path can be relative to the JMeter launch directory) + + + The encoding to be used to read the file. If not specified, the platform default is used. + + +A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. + + +

    The file name, encoding and reference name parameters are resolved every time the function is executed.

    +
    + + + + +

    + The samplerName function returns the name (i.e. label) of the current sampler. +

    +

    + The function does not work in Test elements that don't have an associated sampler. + For example the Test Plan. + Configuration elements also don't have an associated sampler. + However some Configuration elements are referenced directly by samplers, such as the HTTP Header Manager + and Http Cookie Manager, and in this case the functions are resolved in the context of the Http Sampler. + Pre-Processors, Post-Processors and Assertions always have an associated Sampler. +

    +
    + + + +A reference name - refName - for reusing the value created by this function. Stored values are of the form ${refName}. + + +
    + + + + +

    + The TestPlanName function returns the name of the current test plan (can be used in Including Plans to know the name of the calling test plan). +

    +
    +
    +
    + + +

    +Most variables are set by calling functions or by test elements such as User Defined Variables; +in which case the user has full control over the variable name that is used. +However some variables are defined internally by JMeter. These are listed below. +

    +
      +
    • COOKIE_cookiename - contains the cookie value (see )
    • +
    • JMeterThread.last_sample_ok - whether or not the last sample was OK - true/false. +Note: this is updated after PostProcessors and Assertions have been run. +
    • +
    • START variables (see next section)
    • +
    +
    + +

    +The set of JMeter properties is initialised from the system properties defined when JMeter starts; +additional JMeter properties are defined in jmeter.properties, user.properties or on the command line. +

    +

    +Some built-in properties are defined by JMeter. These are listed below. +For convenience, the START properties are also copied to variables with the same names. +

    +
      +
    • START.MS - JMeter start time in milliseconds
    • +
    • START.YMD - JMeter start time as yyyyMMdd
    • +
    • START.HMS - JMeter start time as HHmmss
    • +
    • TESTSTART.MS - test start time in milliseconds
    • +
    +

    +Please note that the START variables / properties represent JMeter startup time, not the test start time. +They are mainly intended for use in file names etc. +

    +
    +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/get-started.xml b/ApacheJmeter/xdocs/usermanual/get-started.xml new file mode 100644 index 0000000..87d1964 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/get-started.xml @@ -0,0 +1,548 @@ + + + + +]> + + + + + User's Manual: Getting Started + + + +
    +

    The easiest way to begin using JMeter is to first +download the latest production release and install it. +The release contains all of the files you need to build and run most types of tests, +e.g. Web (HTTP/HTTPS), FTP, JDBC, LDAP, Java, and JUnit.

    +

    If you want to perform JDBC testing, +then you will, of course, need the appropriate JDBC driver from your vendor. JMeter does not come with +any JDBC drivers.

    +

    +JMeter includes the JMS API jar, but does not include a JMS client implementation. +If you want to run JMS tests, you will need to download the appropriate jars from the JMS provider. +

    + +See the JMeter Classpath section for details on installing additional jars. + +

    Next, start JMeter and go through the Building a Test Plan section +of the User Guide to familiarize yourself with JMeter basics (for example, adding and removing elements).

    +

    Finally, go through the appropriate section on how to build a specific type of Test Plan. +For example, if you are interested in testing a Web application, then see the section +Building a Web Test Plan. +The other specific Test Plan sections are: +

    +

    +

    Once you are comfortable with building and running JMeter Test Plans, you can look into the +various configuration elements (timers, listeners, assertions, and others) which give you more control +over your Test Plans.

    +
    + +
    +

    JMeter requires your computing environment meets some minimum requirements.

    + + +JMeter requires a fully compliant JVM 1.5 or higher. + +

    Because JMeter uses only standard Java APIs, please do not file bug reports if your JRE fails to run +JMeter because of JRE implementation issues.

    +
    + + +

    JMeter is a 100% Java application and should run correctly on any system +that has a compliant Java implementation.

    +

    Operating systems tested with JMeter can be view on +this page +on JMeter wiki.

    +

    Even if your OS is not listed on the wiki page, JMeter should run on it provided that the JVM is compliant.

    +
    +
    + +
    +

    If you plan on doing JMeter development, then you will need one or more optional packages listed below.

    + + + +

    If you want to build the JMeter source or develop JMeter plugins, then you will need a fully compliant JDK 1.5 or higher.

    +
    + + +

    JMeter comes with Apache's Xerces XML parser. You have the option of telling JMeter +to use a different XML parser. To do so, include the classes for the third-party parser in JMeter's classpath, +and update the jmeter.properties file with the full classname of the parser +implementation.

    +
    + + +

    JMeter has extensive Email capabilities. +It can send email based on test results, and has a POP3(S)/IMAP(S) sampler. +It also has an SMTP sampler. +

    +
    + + +

    To test a web server using SSL encryption (HTTPS), JMeter requires that an +implementation of SSL be provided, as is the case with Sun Java 1.4 and above. +If your version of Java does not include SSL support, then it is possible to add an external implementation. +Include the necessary encryption packages in JMeter's classpath. +Also, update system.properties to register the SSL Provider.

    +

    +JMeter HTTP defaults to protocol level TLS. This can be changed by editting the JMeter property +"https.default.protocol" in jmeter.properties or user.properties. +

    +

    The JMeter HTTP samplers are configured to accept all certificates, +whether trusted or not, regardless of validity periods etc. +This is to allow the maximum flexibility in testing servers.

    +

    If the server requires a client certificate, this can be provided.

    +

    There is also the , for greater control of certificates.

    +The JMeter proxy server (see below) supports recording HTTPS (SSL) in versions after 2.3.4 +

    +The SMTP sampler can optionally use a local trust store or trust all certificates. +

    +
    + + +

    You will need to add your database vendor's JDBC driver to the classpath if you want to do JDBC testing. +Make sure the file is a jar file, not a zip. +

    +
    + + +

    +JMeter now includes the JMS API from Apache Geronimo, so you just need to add the appropriate JMS Client implementation +jar(s) from the JMS provider. Please refer to their documentation for details. +There may also be some information on the JMeter Wiki. +

    +
    + + +

    +At the time of writing, the current version of ActiveMQ is 5.3.2. +You will need to add the jar activemq-all-5.3.2.jar to your classpath, e.g. by storing it in the lib/ directory. +

    +

    +Alternatively, add the jar activemq-core-5.3.2.jar to the classpath; +this requires the javax/management/j2ee classes which can be found in the +Apache Geronimo jar geronimo-j2ee-management_1.0_spec-1.0.jar. +The other required jars (such as commons-logging) are already included with JMeter. +

    +

    +See http://activemq.apache.org/initial-configuration.html +for details. +

    +
    + + +See the JMeter Classpath section for more details on installing additional jars. + +
    + +
    + +

    We recommend that most users run the latest release.

    +

    To install a release build, simply unzip the zip/tar file into the directory +where you want JMeter to be installed. Provided that you have a JRE/JDK correctly installed +and the JAVA_HOME environment variable set, there is nothing more for you to do.

    +

    +Note: there can be problems (especially with client-server mode) if the directory path contains any spaces. +

    +

    +The installation directory structure should look something like this (for version 2.7): +

    +apache-jmeter-2.7
    +apache-jmeter-2.7/bin
    +apache-jmeter-2.7/docs
    +apache-jmeter-2.7/extras
    +apache-jmeter-2.7/lib/
    +apache-jmeter-2.7/lib/ext
    +apache-jmeter-2.7/lib/junit
    +apache-jmeter-2.7/printable_docs
    +
    +You can rename the parent directory (i.e. apache-jmeter-2.7) if you want, but do not change any of the sub-directory names. +

    +
    + +
    +
    +

    To run JMeter, run the jmeter.bat (for Windows) or jmeter (for Unix) file. +These files are found in the bin directory. +After a short pause, the JMeter GUI should appear. +

    + +

    +There are some additional scripts in the bin directory that you may find useful. +Windows script files (the .CMD files require Win2K or later): +

      +
    • jmeter.bat - run JMeter (in GUI mode by default)
    • +
    • jmeter-n.cmd - drop a JMX file on this to run a non-GUI test
    • +
    • jmeter-n-r.cmd - drop a JMX file on this to run a non-GUI test remotely
    • +
    • jmeter-t.cmd - drop a JMX file on this to load it in GUI mode
    • +
    • jmeter-server.bat - start JMeter in server mode
    • +
    • mirror-server.cmd - runs the JMeter Mirror Server in non-GUI mode
    • +
    • shutdown.cmd - Run the Shutdown client to stop a non-GUI instance gracefully
    • +
    • stoptest.cmd - Run the Shutdown client to stop a non-GUI instance abruptly
    • +
    +Note: the special name LAST can be used with jmeter-n.cmd, jmeter-t.cmd and jmeter-n-r.cmd +and means the last test plan that was run interactively. +

    + +

    +The environment variable JVM_ARGS can be used to override JVM settings in the jmeter.bat script. +For example: +

    +set JVM_ARGS="-Xms1024m -Xmx1024m -Dpropname=propvalue"
    +jmeter -t test.jmx ...
    +
    +

    + +

    +Un*x script files; should work on most Linux/Unix systems: +

      +
    • jmeter - run JMeter (in GUI mode by default). Defines some JVM settings which may not work for all JVMs.
    • +
    • jmeter-server - start JMeter in server mode (calls jmeter script with appropriate parameters)
    • +
    • jmeter.sh - very basic JMeter script with no JVM options specified.
    • +
    • mirror-server.sh - runs the JMeter Mirror Server in non-GUI mode
    • +
    • shutdown.sh - Run the Shutdown client to stop a non-GUI instance gracefully
    • +
    • stoptest.sh - Run the Shutdown client to stop a non-GUI instance abruptly
    • +
    +

    +

    +It may be necessary to edit the jmeter shell script if some of the JVM options are not supported +by the JVM you are using. +The JVM_ARGS environment variable can be used to override or set additional JVM options, for example: +

    +JVM_ARGS="-Xms1024m -Xmx1024m" jmeter -t test.jmx [etc.]
    +
    +will override the HEAP settings in the script. +

    + +

    JMeter automatically finds classes from jars in the following directories:

    +
      +
    • JMETER_HOME/lib - used for utility jars
    • +
    • JMETER_HOME/lib/ext - used for JMeter components and add-ons
    • +
    +

    If you have developed new JMeter components, +then you should jar them and copy the jar into JMeter's lib/ext directory. +JMeter will automatically find JMeter components in any jars found here. +

    +

    Support jars (libraries etc) should be placed in the lib directory.

    + +

    If you don't want to put JMeter extension jars in the lib/ext directory, +then define the property search_paths in jmeter.properties. +Do not use lib/ext for utility jars; it is only intended for JMeter components. +

    +

    +Other jars (such as JDBC, JMS implementations and any other support libaries needed by the JMeter code) + should be placed in the lib directory - not the lib/ext directory

    +

    Note: JMeter will only find .jar files, not .zip.

    +

    You can also install utility Jar files in $JAVA_HOME/jre/lib/ext, or (since 2.1.1) you can set the property user.classpath in jmeter.properties

    +

    Note that setting the CLASSPATH environment variable will have no effect. +This is because JMeter is started with "java -jar", +and the java command silently ignores the CLASSPATH variable, and the -classpath/-cp options when -jar is used. +[This occurs with all Java programs, not just JMeter.]

    +
    + + +

    If you are testing from behind a firewall/proxy server, you may need to provide JMeter with +the firewall/proxy server hostname and port number. To do so, run the jmeter.bat/jmeter file +from a command line with the following parameters:

    +

    +-H [proxy server hostname or ip address]
    +-P [proxy server port]
    +-N [nonproxy hosts] (e.g. *.apache.org|localhost)
    +-u [username for proxy authentication - if required]
    +-a [password for proxy authentication - if required]
    +

    +

    Example: jmeter -H my.proxy.server -P 8000 -u username -a password -N localhost

    +

    Alternatively, you can use --proxyHost, --proxyPort, --username, and --password

    +JMeter also has its own in-built HTTP Proxy Server, +which can be used for recording HTTP or HTTPS browser sessions. +This is not to be confused with the proxy settings described above, which are used when JMeter makes HTTP or HTTPS requests itself. +
    + + +

    For non-interactive testing, you may choose to run JMeter without the GUI. To do so, use +the following command options

    +

    -n This specifies JMeter is to run in non-gui mode

    +

    -t [name of JMX file that contains the Test Plan].

    +

    -l [name of JTL file to log sample results to].

    +

    -j [name of JMeter run log file].

    +

    -r Run the test in the servers specified by the JMeter property "remote_hosts"

    +

    -R [list of remote servers] Run the test in the specified remote servers

    +

    The script also lets you specify the optional firewall/proxy server information:

    +

    -H [proxy server hostname or ip address]
    +-P [proxy server port]

    +

    Example: jmeter -n -t my_test.jmx -l log.jtl -H my.proxy.server -P 8000

    +

    +If the property jmeterengine.stopfail.system.exit is set to true (default is false), +then JMeter will invoke System.exit(1) if it cannot stop all threads. +Normally this is not necessary. +

    +
    + + +

    For distributed testing, run JMeter in server mode on the remote node(s), and then control the server(s) from the GUI. +You can also use non-GUI mode to run remote tests. +To start the server(s), run jmeter-server/jmeter-server.bat on each server host.

    +

    The script also lets you specify the optional firewall/proxy server information:

    +

    -H [proxy server hostname or ip address]
    +-P [proxy server port]

    +

    Example: jmeter-server -H my.proxy.server -P 8000

    +

    If you want the server to exit after a single test has been run, then define the JMeter property server.exitaftertest=true. +

    +

    To run the test from the client in non-GUI mode, use the following command:

    +
    +jmeter -n -t testplan.jmx -r [-Gprop=val] [-Gglobal.properties] [-Z]
    +where:
    +-G is used to define JMeter properties to be set in the servers
    +-X means exit the servers at the end of the test
    +-Rserver1,server2 - can be used instead of -r to provide a list of servers to start
    +  Overrides remote_hosts, but does not define the property.
    +
    +

    +If the property jmeterengine.remote.system.exit is set to true (default is false), +then JMeter will invoke System.exit(0) after stopping RMI at the end of a test. +Normally this is not necessary. +

    +
    + + +

    Java system properties, JMeter properties, and logging properties can be overriden directly on the command line (instead of modifying jmeter.properties). +To do so, use the following options:

    +

    -D[prop_name]=[value] - defines a java system property value.

    +

    -J[prop name]=[value] - defines a local JMeter property.

    +

    -G[prop name]=[value] - defines a JMeter property to be sent to all remote servers.

    +

    -G[propertyfile] - defines a file containing JMeter properties to be sent to all remote servers.

    +

    -L[category]=[priority] - overrides a logging setting, setting a particular category to the given priority level.

    +

    The -L flag can also be used without the category name to set the root logging level.

    +

    Examples: +

    +jmeter -Duser.dir=/home/mstover/jmeter_stuff \
    +    -Jremote_hosts=127.0.0.1 -Ljmeter.engine=DEBUG
    +
    +jmeter -LDEBUG
    +

    +

    +N.B.
    + The command line properties are processed early in startup, but after the logging system has been set up. + Attempts to use the -J flag to update log_level or log_file properties will have no effect.
    +

    +
    + + + JMeter does not generally use pop-up dialog boxes for errors, as these would interfere with + running tests. Nor does it report any error for a mis-spelt variable or function; instead the + reference is just used as is. See Functions and Variables for more information. + +

    If JMeter detects an error during a test, a message will be written to the log file. + The log file name is defined in the jmeter.properties file (or using the -j option, see below). + It defaults to jmeter.log, and will be found in the directory from which JMeter was launched. +

    +

    + Since JMeter 2.6, menu Options > Log Viewer displays the log file in a bottom pane on main JMeter window. +

    +

    + Since JMeter 2.7 (GUI mode), the number of error/fatal messages logged in the log file is displayed at top-right. +

    +
    Error/fatal counter
    +

    + JMeter versions after 2.2 added a new command-line option, -j jmeterlogfile. + This is processed after the initial properties file is read, + and before any further properties are processed. + It therefore allows the default of jmeter.log to be overridden. + The jmeter scripts that take a test plan name as a parameter (e.g. jmeter-n.cmd) have been updated + to define the log file using the test plan name, + e.g. for the test plan Test27.jmx the log file is set to Test27.log. +

    +

    When running on Windows, the file may appear as just jmeter unless you have set Windows to show file extensions. + [Which you should do anyway, to make it easier to detect viruses and other nasties that pretend to be text files...] +

    +

    As well as recording errors, the jmeter.log file records some information about the test run. For example:

    +
    +
    +10/17/2003 12:19:20 PM INFO  - jmeter.JMeter: Version 1.9.20031002 
    +10/17/2003 12:19:45 PM INFO  - jmeter.gui.action.Load: Loading file: c:\mytestfiles\BSH.jmx 
    +10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Running the test! 
    +10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Starting 1 threads for group BSH. Ramp up = 1. 
    +10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Continue on error 
    +10/17/2003 12:19:52 PM INFO  - jmeter.threads.JMeterThread: Thread BSH1-1 started 
    +10/17/2003 12:19:52 PM INFO  - jmeter.threads.JMeterThread: Thread BSH1-1 is done 
    +10/17/2003 12:19:52 PM INFO  - jmeter.engine.StandardJMeterEngine: Test has ended 	
    +
    +
    +

    The log file can be helpful in determining the cause of an error, + as JMeter does not interrupt a test to display an error dialogue.

    +
    + +

    Invoking JMeter as "jmeter -?" will print a list of all the command-line options. +These are shown below.

    +
    +        -h, --help
    +                print usage information and exit
    +        -v, --version
    +                print the version information and exit
    +        -p, --propfile {argument}
    +                the jmeter property file to use
    +        -q, --addprop {argument}
    +                additional property file(s)
    +        -t, --testfile {argument}
    +                the jmeter test(.jmx) file to run
    +        -j, --jmeterlogfile {argument}
    +                the jmeter log file
    +        -l, --logfile {argument}
    +                the file to log samples to
    +        -n, --nongui
    +                run JMeter in nongui mode
    +        -s, --server
    +                run the JMeter server
    +        -H, --proxyHost {argument}
    +                Set a proxy server for JMeter to use
    +        -P, --proxyPort {argument}
    +                Set proxy server port for JMeter to use
    +        -u, --username {argument}
    +                Set username for proxy server that JMeter is to use
    +        -a, --password {argument}
    +                Set password for proxy server that JMeter is to use
    +        -J, --jmeterproperty {argument}={value}
    +                Define additional JMeter properties
    +        -G, --globalproperty (argument)[=(value)]
    +                Define Global properties (sent to servers)
    +                e.g. -Gport=123
    +                 or -Gglobal.properties
    +        -D, --systemproperty {argument}={value}
    +                Define additional System properties
    +        -S, --systemPropertyFile {filename}
    +                a property file to be added as System properties
    +        -L, --loglevel {argument}={value}
    +                Define loglevel: [category=]level 
    +                e.g. jorphan=INFO or jmeter.util=DEBUG
    +        -r, --runremote (non-GUI only)
    +                Start remote servers (as defined by the jmeter property remote_hosts)
    +        -R, --remotestart  server1,... (non-GUI only)
    +                Start these remote servers (overrides remote_hosts)
    +        -d, --homedir {argument}
    +                the jmeter home directory to use
    +        -X, --remoteexit
    +                Exit the remote servers at end of test (non-GUI)
    +
    +

    +Note: the JMeter log file name is formatted as a SimpleDateFormat (applied to the current date) +if it contains paired single-quotes, .e.g. 'jmeter_'yyyyMMddHHmmss'.log' +

    +

    +If the special name LAST is used for the -t, -j or -l flags, then JMeter takes that to mean the last test plan +that was run in interactive mode. +

    +
    + + +

    +Prior to version 2.5.1, JMeter invoked System.exit() when a non-GUI test completed. +This caused problems for applications that invoke JMeter directly, so JMeter no longer invokes System.exit() +for a normal test completion. [Some fatal errors may still invoke System.exit()] +JMeter will exit all the non-daemon threads it starts, but it is possible that some non-daemon threads +may still remain; these will prevent the JVM from exitting. +To detect this situation, JMeter starts a new daemon thread just before it exits. +This daemon thread waits a short while; if it returns from the wait, then clearly the +JVM has not been able to exit, and the thread prints a message to say why. +

    +

    +The property jmeter.exit.check.pause can be used to override the default pause of 2000ms (2secs). +If set to 0, then JMeter does not start the daemon thread. +

    +
    + +
    + + +
    +

    If you wish to modify the properties with which JMeter runs you need to + either modify the jmeter.properties in the /bin directory or create + your own copy of the jmeter.properties and specify it in the command line. +

    + + Note: since 2.2, you can define additional JMeter properties in the file defined by the + JMeter property user.properties which has the default value user.properties. + The file will be automatically loaded if it is found in the current directory + or if it is found in the JMeter bin directory. + Similarly, system.properties is used to update system properties. + + + You can specify the class for your SSL + implementation if you don't want to use the built-in Java implementation. + + You can specify an implementation as your XML + parser. The default value is: org.apache.xerces.parsers.SAXParser + Comma-delimited list of remote JMeter hosts (or host:port if required). + If you are running JMeter in a distributed environment, list the machines where + you have JMeter remote servers running. This will allow you to control those + servers from this machine's GUI + A list of components you do not want to see in + JMeter's menus. As JMeter has more and more components added, you may wish to + customize your JMeter to show only those components you are interested in. + You may list their classname or their class label (the string that appears + in JMeter's UI) here, and they will no longer appear in the menus. + + List of paths (separated by ;) that JMeter will search for JMeter add-on classes; + for example additional samplers. + This is in addition to any jars found in the lib/ext directory. + + + List of paths that JMeter will search for utility classes. + This is in addition to any jars found in the lib directory. + + + Name of file containing additional JMeter properties. + These are added after the initial property file, but before the -q and -J options are processed. + + + Name of file containing additional system properties. + These are added before the -S and -D options are processed. + + +

    + The command line options and properties files are processed in the following order: +

      +
    • -p propfile
    • +
    • jmeter.properties (or the file from the -p option) is then loaded
    • +
    • -j logfile
    • +
    • Logging is initialised
    • +
    • user.properties is loaded
    • +
    • system.properties is loaded
    • +
    • all other command-line options are processed
    • +
    +

    +

    +See also the comments in the jmeter.properties, user.properties and system.properties files for further information on other settings you can change. +

    +
    + + +
    + diff --git a/ApacheJmeter/xdocs/usermanual/glossary.xml b/ApacheJmeter/xdocs/usermanual/glossary.xml new file mode 100644 index 0000000..a46be6c --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/glossary.xml @@ -0,0 +1,104 @@ + + + +]> + + + + User's Manual: Glossary + + + + +
    + +

    +Elapsed time. JMeter measures the elapsed time from just before sending the request to +just after the last response has been received. +JMeter does not include the time needed to render the response, nor does JMeter process any client code, for example +Javascript. +

    + +

    +Latency. JMeter measures the latency from just before sending the request to +just after the first response has been received. Thus the time +includes all the processing needed to assemble the request as well as +assembling the first part of the response, which in general will be longer than one +byte. +Protocol analysers (such as Wireshark) measure the time when bytes are actually sent/received over the interface. +The JMeter time should be closer to that which is experienced by a +browser or other application client. +

    + +

    +Median is a number which divides the samples into two equal halves. +Half of the samples are smaller than the median, and half are larger. +[Some samples may equal the median.] +This is a standard statistical measure. +See, for example: Median entry at Wikipedia. +The Median is the same as the 50th Percentile +

    + +

    +90% Line (90th Percentile) is the value below which 90% of the samples fall. +The remaining samples too at least as long as the value. +This is a standard statistical measure. +See, for example: Percentile entry at Wikipedia. +

    + +

    +Standard Deviation is a measure of the variability +of a data set. This is a standard statistical measure. +See, for example: Standard Deviation entry at Wikipedia. +JMeter calculates the population standard deviation (e.g. STDEVP function in spreadheets), not the sample standard deviation (e.g. STDEV). +

    + +

    +The Thread Name as it appears in Listeners and logfiles +is derived from the Thread Group name and the thread within the group.
    +The name has the format +groupName + " " + groupIndex + "-" + threadIndex +where: +

      +
    • groupName - name of the Thread Group element
    • +
    • groupIndex - number of the Thread Group in the Test Plan, starting from 1
    • +
    • threadIndex - number of the thread within the Thread Group, starting from 1
    • +
    +A test plan with two Thread Groups each with two threads would use the names: +
    +Thread Group 1-1
    +Thread Group 1-2
    +Thread Group 2-1
    +Thread Group 2-2
    +
    +

    + +

    +Throughput is calculated as requests/unit of time. +The time is calculated from the start of the first sample to the end of the last sample. +This includes any intervals between samples, as it is supposed to represent the load on the server.
    +The formula is: Throughput = (number of requests) / (total time). +

    + + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/hints_and_tips.xml b/ApacheJmeter/xdocs/usermanual/hints_and_tips.xml new file mode 100644 index 0000000..06e5ec3 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/hints_and_tips.xml @@ -0,0 +1,99 @@ + + + +]> + + + + User's Manual: Hints and Tips + + + + +
    +

    +This section is a collection of various hints and tips that have been suggested by various questions on the JMeter User list. +If you don't find what you are looking for here, please check the JMeter Wiki. +Also, try search the JMeter User list; someone may well have already provided a solution. +

    + +

    +JMeter variables have thread scope. This is deliberate, so that threads can act indepently. +However sometimes there is a need to pass variables between different threads, in the same or different Thread Groups. +

    +

    +One way to do this is to use a property instead. +Properties are shared between all JMeter threads, so if one thread sets a property, +another thread can read the updated value. +

    +

    +If there is a lot of information that needs to be passed between threads, then consider using a file. +For example you could use the Save Responses to a file +listener or perhaps a BeanShell PostProcessor in one thread, and read the file using the HTTP Sampler "file:" protocol, +and extract the information using a PostProcessor or BeanShell element. +

    +

    +If you can derive the data before starting the test, then it may well be better to store it in a file, +read it using CSV Dataset. +

    +
    + + +

    +Most test elements include debug logging. If running a test plan from the GUI, +select the test element and use the Help Menu to enable or disable logging. +The Help Menu also has an option to display the GUI and test element class names. +You can use these to determine the correct property setting to change the logging level. +

    + +

    +It is sometimes very useful to see Log messages to debug dynamic scripting languages like BeanShell or +groovy used in JMeter. +Since 2.6, you can view log messages directly in JMeter GUI, to do so, use menu Options > Log Viewer, +a log console will appear at the bottom of the interface. +Note that log messages are cleared each time you disable this option. +By default this log console is disabled, you can enable it by changing in jmeter.properties: +

      +
    • jmeter.loggerpanel.display=true
    • +
    +

    +
    + + +

    +It is sometimes hard to find in a Test Plan tree and elements using a variable or containing a certain URL or parameter. +A new feature is now available since 2.6, you can access it in Menu Search. +It provides search with following options: +

      +
    • Case Sensitive : Makes search case sensitive
    • +
    • Regexp : Is text to search a regexp, if so Regexp will be searched in Tree of components, example "\btest\b" will match any component that contains test in searchable elements of the component
    • +
    +

    + +
    Figure 1 - Search raw text in TreeView
    +
    Figure 2 - Result in TreeView
    +
    Figure 3 - Search Regexp in TreeView (in this example we search whole word)
    +
    Figure 4 - Result in TreeView
    + +
    +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/include_controller_tutorial.sxw b/ApacheJmeter/xdocs/usermanual/include_controller_tutorial.sxw new file mode 100644 index 0000000000000000000000000000000000000000..d495c229aa6519eac84f8f0e1ca07950e5a9c33b GIT binary patch literal 10186 zcmb7q1z40%*Y~ol3Ift0l1nPm-KlhUD7nBAOD)}q2!hfQN=PgvT}qdvge)K-QW8re z2q-A;jrEW3d7tb3=3<6>W`5_~XU=_Q_RO5oQpJXlfzH!dwhI43KCdVp@O++efb0x+ zh9P}jVV170PH<~WB-{nT?S-)6^6*4(`8YdqdAq}rFn6tg@JNza`J8zf1Uk>001qv= zHPX`^=E2R&^S8t+%m1?`EX2cmo0nf$PM*sZVHc&Pp-6C<8XyG`C@U%GfIwKez_S+) z7%1009HW7MSROixa-gyy8Vpc@*vYEPfOFKo6A4i^O0}Xl94GVu!gJ>de|$h2*~TgJRmzIxJCB~+Z*pPJyoCix?L=~C`> zU|uNs_#!XXTO+Dc!L#Z#{)P#nSSPSpXxky=wpsyU#Q1x})Zox5$Cp|y6~W2+56@~% z+2e{&G&~Iornj0L8pO3-T8(juz@3nb>UK0;WexB*NIA=K`o;pKSd0XAvS7CM6NfE7 zv3Dp^^aRs-I?`w16%n`yCnmKFV)L=JHEmW|aA9Ctkvnw`8LL)hn*UI@_?viJA(5`L zdf5H8dk~)4GoIWIK^4`Rz|`$_S~WwWU7}+Zv?sRXII?H8$KG+ z&G)Jt0fFJT5-tsEfnvj)oNw(saVo=0i~G0x*IxN&Y0!sDqJfZWXrQ!#$W~S}i7>5$S#(t_C2Qh21S^b*DMm0Uomkn43CDP&!XAPqyV4~xae5T5#yIM%r*UW=Bu6UW{MPJ}Os`bgzXid>y|QZF)yEcj;hywoo7>BpUjr47{@+8P;0W;_>V?_-nXkX>_{N$2iSNSGVY)~Bl zdh|^%(CObq4LIl3=ky>Q3xcYqEF0=cBm-Mhxzbc8u27=$g-1<6JhRh9Sx#Q5+ocMc zb1pr|#Y%hrtOPH_*CkJU>VEHF|KI@+>QxSbw35oRj*|Sdgv#rjKIAA>m%Mt=OEquF z7~xWk3MK}(H<%pd?^um?u>volNQViesP57U(CIpvu^7m{Ll%IMmMkBN(&Y-**Nb1g zGGTf8Bxzf{q++x5#iiFH%ZfP>o{y_6;6f5C4h>yge!|Fd%urPsa!Qb!@nuzhi%QCa zQn5-0F=yUk@nzzdPII1CN5?NNu$h@!Dyl?lu$ zZx$;a6y&#tUmkEI_*9xc#2&DjuH?8OiN?%$%vMGuH%I2trCmg+ZsvH)6Q*u|O4rgi zv}sr)y`wMx%&B2?8VY|jTBMJbM2JZ?f4kg(O^xywo+Uln7QAEJ9<|8jAjY=UhvHWu z*&ve4O zi<&JPifvZy?K{|0xzgKeyh(6U%S`%{S4Y;HMVD@zL}Z|mU&|{yR90h$gtzXkU(J@9 zd3a>1Xd;>D_$gDjg0~-XY49o-ExP~2RvUaOP2!=Gs%-`=NCKho9p(^>*;S=nQ3y{4 zzYCm#B?PmQ$CLXKSd#3)=nu*ka5OD%Uce?|J)4k8?1*cSVVUkrt?)OxOjf*^;0c^;ZuLjlv}q4htv>nspzCiqbl5tyhAxp}^r z27^-(XM(J902#XS5(6mo8(_cR-lgI&fSNn?Q)1qh2Iy{}A? z2l`1-Q*~v{y#~1+5r@5>MsOhvyEvhCYTk6%1l$VJsGxg9m7cn=(?IDbQl4t1+eb*&L9GQ82a0Gob%JECS zBm1zjR+8K}zwHLdRPfG5$dpb(YMz@^sY%Ps)>3Qs8Zdey$dv~{^_Wd^K?kwGh5U*M z-s%l>*_5ed1venSA#T7V^6N@QpnnNVZrMGJ$RJW*jdJhHqlT2pLqPxVJ7kKRNsm~m z^vQ=TZFWrT!Bn+amV@!?9(?M|cPAFjVSFhxmeeO7b>gF^2IxIRMj22q{Xf(Te53Q9 zr~lhG(kR>0yh~)1ETgAX(#`8~my%b_kayMvXo_vC)QwfXZ{tk`YZh4fMKAbN!pO@%(-8{ z=jJ$E1ki({2FU(<*X#`xKVALFt+}i=bLKEyrF~_{N~(s<-k0~A+=0#d==+)OEjPv0 zgs;0@WmU33Vag6pbEt1|z%ns0F5zoIzJt6jOD7i}aoZIJ>4uxXNaacm5y(=QuSPkt z#a-!(IX}VrghSw8BYuTV2HNYba=146Yd#EA6 zq*>DM*o-Mx-F&i>zRicc8lnAu>h0yZYppVwU!h!Tlxi~Rnou4zvfHg#zX%ghZos(M zo#iIdxYOvZ&h}#NVcm_cvf~U?qb{NKFV6hx1v_Z{*k>JnFjDO&o%PS1q*t;6_|3bH5f%MI#`8zQ zx*;+dGQCJeYmv+@o;%3Z5jx;^RSKNCeKHYqRUQP^1`nrhuidAF0JfcsOXYsLsMxER ztm%&g+xlKfNyoj5Bxv|3iwuUtw?V`Mgf`NDVc4Iagl&; zRWliWIkX1y9ot}YJnzHv0Ykun1S#LrP$-kL2>Yj_J9YaDf*0sL&-g%gvh;QFL~>eN zTHC|0Kv*ExbMN=|HK4!(G6<9dczHgIAkZT!z!$v;0%;L|KveWVhY65>vc>_t?sGr< z9R522R3K{?1QLcop8MQergMlHA*Z$F9Qr1n= z z-1yUEE+9=Vr-fW>zzTZ^r!CLq)kM8uamh5ahHy+VYZx<`}dJ*%U?VHk5w1;*rhQW3LbB&4cL@0oO+lO?6HPSP1G@CcFsm@#CpY_XxhGpr zS!1c%*RFWn|KNV{v)Vi3!2A5qTbvm3* zEF_wE^!1p8JEaWp_tg{Qm8!#~Uc#5|rQb1)*$fb45}%22dS*sF<(m{gn-PO##?>u> z2rsN!onb6DcLKN$vjP{HxqDWU)U7AwdZ3K-E`F88&k%PT*V7`#$wTj)?Ujj3tF0rY z6UWkeYwxsC4?}BTf2UVXcyzm4uF5-`Mk8SI)5W`xn}r7xb}Z`D0hxT)FU;$5`Ggwf zgu^OCN0owSZ6zMQon95DUIN2& z{`4g6N>@7aMBDf44(1z{{?2{#KB3zb*TNQ=y9 z?o#Qc@mr1fHj*C>534D>@SPzG3Kz|gEWE58RVa;;QO1+>r!84cG#Ql)VVMo8KRS5w zjIJ%AI;imsc%p4f_;73I0K+LN9m4W08oggopI7g#+F{^BoGIDNLV2ko4-fPC;K>1@ zMW6A`CMj~EA`qnO*F5xHrA6o)?|ilgpu&vOP6MuL`1^w{k=FI4!viwvfm~$klD_|jsw=) z&~7142q9X0dc%kFO=qH**YMf1Jd)Ayy5&Rl81wSMth zPr^8QYc7z`yx})w&VH!&HtI$T0ZZ#J^@aqhTcde_s-~sf8()j4CRuN}ANjqie3OYy zhQ>ZqaqK=RbJ&c|+T(`JxO@u#CyKxdSamIj_-d5EeKz|dV*MF~En(-oIJh&R&Pr?} zsTB5%?p@beSfyWJ=7XMb>fgUhP>c1H=AMttf?67Ujdbly+br4$f7wyO0r#e)n(V;6 zkKwk`f}(~`y$$4Z`$%=%6@72Zu!gtQ48y2Q)~3Kvq&dR;0^VlTVwVnHYFw>gU5bhW zNhXHkt0+o+5pP^Z^(mf)=7p+kp%`(8Z&>MaxzDbT*N$@@Tztr7c^$2vz2(HU)os^! z|JBspH+?aD+KppAV_1YYB^y*ab+VmOnU@%P^qMmoc_r;y_||mR?g?UVMBL>8cL!o;YTv+? zdv2!ozRE2Z?jDfN;3c{X+3Nj__%A9isD5$EvoXzhfSi5o2VnlWTTOOrEio7QXe@;sA?Qe!K^mw586pKGjG~`*wsM4 zq352*k*f~e7M|_d6kh$H1;<}Iu1{M3md(+bz$!3$a!Ip|nl$XR9QD<7rYXlyCVHXA zBCZFgj`C<^F8I{=AbNL4`uh#tUZO4K%ak5AY6A8bbcF7ExZ$P_&^37vK)g?aulIJa zgPZpt%F(IN$+KdQx(o)e2aM}e~NuQi$U*PImK4yL9* zig;;FK%@3ygKjYJ#dKo#m{U_lo{hFswyJ|Z-Ge+L>bF+jHK5!pYHy@k>OOT{DbH$K z$KPsku5wg=AzHU?Vv1)F#7N?FoMqn`47ehfaCTi>arw6)N*84cw_n+=hW0@H~iU!^i3{`Ua1jg8UH+%ek+c6@U1VEtu698NjA|5 zi9XSH)8#=PExv&?R~|I@H4gH-s4FOonjWK-*Zjk8NuZo+cj@Ur!eeI_)N{t)!a z_jrFpJeCdjQ7`OG?hQ7z9Gz`G9Y1sW;Kmc5r7!F_Eo9+=ZwqlLOBh}TzWn@VXU3!t zTUd5?yL#tK_j~3imBE367T2IQ%lX7yr+WR1d^jRL3#BQ>R1u+Hw%%Pf@h2-Mrr_!` zc$`C?jkUB)JVR}{P}%lWy9NweqR864*gWOL`jr3Cv!T`f`Ph@jc}Yr9gw>R7(l^P- zcRF}5{T5~`36h(8bll5+%X|nNGhs%>>l90;aD{d7$1DI`OC^gJof=T=Q` z&Oj)=U>Qg~C_ekO&&P|4Gt+b8oa*DOG&h7;wGHl?!@)D;!>6_)57K?xt`F~x9g!f2 zaEIV^)nI8V!`2cgwe=2N^{BrMnZ^yw)2|mOn@-2E1i^C~KJ0}tA+v%MdMTU4XP!y& zZ?i1EU{i>!-rSx+u+RoFDR6W|X20#AoX^Zix;prLDuDIcj<|p0HZ@*v%?t|&3yUNND?C0pc4mK(s9KBmBy^KRP!hwt_pSlUx?tYn3 z?{hVlb1PX8q^dgtxx4f&&1&DP77{&_ov~2HeBbhtI`(E8UteAe#FmKJ?vk0B_@o z-NYOI=nfXm+=UhLx;uIL)jAKVo{q;0Eqjghmg4CreHK^Jq=%ssY82?5Pt-1FwM5e! ztJTf95f;@QC3KwB^PRrX&vx-WY7FSB{=_q5&AD=qRwIkIWbMHRkg6%SQ8;!Pg4T1%o0?Q7>TGc>A2% zq!{0$qVm$(;Q+zZFm-B1j*66v`0quzp3iN5Mnki%n0+#_mbd;qzG*f3VZl$51({sj zTfii0TtZfMblTBHtu5Rd zCT8Pe?dc5cVmX1Bk|l$dzMPt}JOh-In_JTrhS2NkZ+BNLMj#ZWrJkzevyD!3PANxPR863_l5c zoZtw@KX`9%Z!T~CAM)^uii&do==}|~vHpW|^>laofwHmYhB?8`#|K2ZxOlmLW6uZs z2mPO*ya^{WHFEh?K;6DP{*lz}zj7F78tQ)Eb~6UM>L!c0)LVpYOT^_rJgr zjGUZ80)ozheC%?ba3>ov5fMIt>ztgwxWW-|B;3-8)7l;OgSRUT2=jUhoz7hkC2!E zj|BJc&U2KLCBn|r(hepCLvZTr{^0j;t1lxE5 zDh387rKQKKBp|FVC;;&Oq0!%De-J+jJw0IVoHj69I09zF@B@gjbcRVnl@Ycs47^b8 zUuyoZFuwmAjQ{@z6ZixBGwnQ(z@R*kaOWB#$^m8#9Blm)>iRb%-!G)ArMsn_yQQoBZ!rIF9^NkQHh&rgez#iN0|eH9t^Mxe z6%`adm;Be<6gwB{uiBrX{Am8)kIc?b<2+!<^AG>iGc}oU&lMpGoGah`B$jMJ%C@A& zlIhS!X}f3|pu8x2mF$gzl0vWivvPCXo)1yjjm9%$2Bwz~1y>Sp-Z zBh_z}zRN}|TT1WziBZjZJE?Bw@3!LanB1A+j!n*EyyJqbO{fD)9EKN+bGyHVal=nx z;{ip3_aj@@#1g$Gv%F&5U%e|s$nAOSEnn$R5*#=Ch}tJ;wCQ_HFWSZs#}pb+284RozwPerJhi1Ga&mvt#YglBf0l`4$Fis| zmHk74ysqneOFAi>Q|-u{+z`KGTz{7y>8Ao1e!kG%<&&J=y}AZ!yt4PYhNtiEY5Cd| zQ)QJT&sus+23zTAddD0p6J6pDBh>g#RvT%P3^rfgyUUWies_eGH0*xbU8xhZlC20y z*i9Pcye*5T{4I@%|Qh+Hx%4ZA`&Q4}08dISl!u72O=O-NYUg#MV2C=wHSdS{I1NIU!$ zpFo#YWdaxNeDt#lj$hiZ*j|;pd{X1N)s$pphc8Bi1Mf;WWoILi9{o zUT5Cs<;tlgXuWz}6Z0ZXWiaT1XhY>j!NtIpQuR>BE2OK5_YQV!z{fs5E4&Rv2-op7Zf zHII`$lbgrm4xMlG-1cf?$zk~Ld3<+5HEvP2x4++N`tWQ|^}VZK z;|RaJ>)NK0aDSfGM)ihOz$`9_Ue*JDsVm8M_!6bfLH$J@+SK8Zhge@&Ll0#Vz&9AK zL#vf?3b$F;gZkxtx7)~5Y3;}7xs3PRlTz_KyRjo-R#@+Z3G9&;sO|b4vhO+zLA?(G zdLNv|+QillFE7*0U}Yyo)zrnew$8b25NInrS)%f|hKoF&RpW2#;&2xhQjp%W`Rs;L ze&7YnR1z{)?a^>(bV-wov7Sl_iN)(&uqPZzb9=lU9tp?^l4iT(u5;LmG31s&5mp< zt6#tH(Fd-A&l_QvZ)gPExAh@?_L?lCZX=H&0=x%}DP6aJ_L|rWuhYeiY!JIX4DcWhXt51jU-qEZt;0)Y*<8R+bF=JiDN{6~3(L~Ep z!@Esf-iTwDN@;Z85MLnvWZt7L`&I3+eC$9_QRfP~a;qJgu9HC2fnws@42Ql~SW-Z}_z^oN*IHmTs*rZ?=5a@XC(hvQC}VxNy|)G7^PH(L<~LU#+O6KY8&9%O3%aF&TT@CQztXe z_COZS+U5IZC@N}0(9Jr$!?zQ@LT#TC`9GjKV)(Wf>eXCS3|AdHjugBg77}x2$C}z? z@}?hUH|6^CJ3xZ3R{D8ubs%F#YA-7c`t;(01!<5J2DMk3?T?q3pD(4Lnhb7KgdO5u zx{@JabNw=;B>Lp#BefzgBUzd|8ONTxl=xY>KIDWE)xJJ*$i(l8l}v+#B^l%OV;zD= zdny7b6azViPW%QD9q#Gr&OP1SKH)5>LPNx@M;*o9PEpKXj~*%nu?-`7T&o2W&Cm6I zZ=CCuAP`s@#6H(M7MKh~2I%-dU*r1G@u&EGsq5cV|Gv2OhXAAiJpO-O1NdjJ3c literal 0 HcmV?d00001 diff --git a/ApacheJmeter/xdocs/usermanual/index.xml b/ApacheJmeter/xdocs/usermanual/index.xml new file mode 100644 index 0000000..e2ac4d0 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/index.xml @@ -0,0 +1,201 @@ + + + + +User's Manual + + + +
    +

    Click on the section name to go straight to the section. + Click on the "+" to go to the relevant section of the detailed section list, + where you can select individual subsections.

    + + + + + + + + +
    + +
    + diff --git a/ApacheJmeter/xdocs/usermanual/intro.xml b/ApacheJmeter/xdocs/usermanual/intro.xml new file mode 100644 index 0000000..555f73e --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/intro.xml @@ -0,0 +1,60 @@ + + + + + + User's Manual: Introduction + + + + +
    +

    +Apache JMeter is a 100% pure Java desktop application designed +to load test client/server software +(such as a web application). It may be used +to test performance both on static and dynamic +resources such as static files, Java Servlets, CGI scripts, Java objects, databases, +FTP servers, and more. JMeter can be used to simulate a heavy +load on a server, network or object to test its strength or to analyze +overall performance under different load types.

    +

    +Additionally, JMeter can help you regression test your application by letting you create +test scripts with assertions to validate that your application is returning the +results you expect. For maximum flexibility, JMeter lets you create these assertions using +regular expressions.

    + +

    But please note that JMeter is not a browser.

    + +

    Stefano Mazzocchi of the Apache Software Foundation was the original developer of JMeter. +He wrote it primarily to test the performance of Apache JServ (a project that has +since been replaced by the Apache Tomcat project). We redesigned JMeter to enhance the GUI +and to add functional-testing capabilities. +

    +
    + + +

    We hope to see JMeter's capabilities rapidly expand as developers take advantage of its +pluggable architecture. The primary goal of further development is to make JMeter the most +useful regression testing tool as possible, without compromising JMeter's load-testing +capabilities.

    +
    +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/ldapanswer_xml.xml b/ApacheJmeter/xdocs/usermanual/ldapanswer_xml.xml new file mode 100644 index 0000000..6847cfc --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/ldapanswer_xml.xml @@ -0,0 +1,211 @@ + + + + + User's Manual: LDAP answer XML description + Dolf Smits + + + +
    +

    + The extended LDAP sampler was built to support testing for very complex testpurposes. + It was aimed at supporting the LDAP operations as close as possible. + As the results are not passed back in a user-readable form, I invented my own xml definition to + construct an answer in xml encoding, so the results may be parsed with regextracter or alike functions. +

    + + +

    + The global structure is as follows:
    + +

    + +

    +The operation section defines the operation as it is sent to the LDAP Server. The +following tags (with a short explanation) are used + +

    +
    + + +

    +As the response code, the official LDAP error definitions are used, so this section +contains the error message as returned from the server. +A succesfull request always returns "Success" as the responsmessage. +The following tag is used: +

    +
    + +

    +As the response code, the official LDAP error definitions are used, so this section +contains the error number as returned from the server. +A succesfull request always returns 0 (zero) as the responscode. +The following tag is used: + +

    +
    + +

    +The following tag is used: +

    + +
    +
    +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/ldapops_tutor.xml b/ApacheJmeter/xdocs/usermanual/ldapops_tutor.xml new file mode 100644 index 0000000..ee44c83 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/ldapops_tutor.xml @@ -0,0 +1,116 @@ + + + + + JMeter - User's Manual: LDAP Operations + Dolf Smits + + + +
    +

    + The extended LDAP sampler was built to support testing for very complex testpurposes. + It was aimed at supporting the LDAP operations as close as possible. + In this short tutorial, I will explain which LDAP operations exist and what they do. + Per operation, I will shortly explain how these operations are implemented.
    + LDAP servers are some kind of hierarchical database, they store objects (entries) in a tree. The uppermost part of a tree is called the ROOT of the tree.
    + eg. When a tree starts with dc=com, the root equals dc=com.
    + The next level can exist under the root, eg dc=Siemens. The full name of this object (the "distinghuised name") is "dc=siemens,dc=com.
    + Again, a following level can be made, by adding the user "cn=admin" under dc=siemens,dc=com. This object has a DN (distinguished name) of "cn=admin,dc=siemens,dc=com".
    + The relative distinguished name (RDN) is the last part of the DN, eg. cn=admin.
    + The characteristics of an object are determined by the objectClasses, which can be seen as a collection of attributes.
    + The type of an object is determined by the "structural objectClass" eg person, organizationalUnit or country.
    + The attributes contain the data of an object, eg mailadress, name, streetadress etc. Each attribute can have 0, 1 or more values. +

    + +

    + Any contact with an LDAP server MUST start with a bind request. LDAP is a state dependent protocol. Without opening a session to + a LDAP server, no additional request can be made. + Due to some peculiarities in the JAVA libraries, 2 different bind operations are implemented. +

    + +

    + This bind is meant to open a session to a LDAP server. Any testplan should use this operation as the starting point from a session. + For each Thread (each virtual user) a seperate connection with the LDAP server is build, and so a seperate Thread bind is performed. +

    +
    + +

    + This bind is used for user authentication verification. + A proper developed LDAP client, who needs an authenticated user, perform a bind with a given distinguished name and password. + This Single bind/unbind operation is for this purpose. It builds it own seperate connection to the LDAP server, performs a + bind operation, and ends the connection again (by sending an unbind). +

    +
    +
    + +

    + To close a connection to a LDAP server, an unbind operation is needed. + As the Single bind/unbind operation already (implicitly) performs an unbind, only a Thread unbind operation is needed. + This Thread unbind just closes the connection and cleans up any resources it has used. +

    +
    + +

    + The compare operation needs the full distinguished name from a LDAP object, as well as a attribute and a value for the attribute. + It will simply check: "Has this object really this attribute with this value?". + Typical use is checking the membership of a certain user with a given group. +

    +
    + +

    + The search test simply searches for all objects which comply with a given search filter, eg. + all persons with a "employeeType=inactive" or "all persons with a userID equals user1" + +

    +
    + +

    + This simply add an object to the LDAP directory. + Off course the combination of attributes and distinguishedName must be valid! +

    +
    + +

    + This operation modifies one or more attributes from a given object. + It needs the distinghised name from the object, as well as the attributes and the new values for this attribute.
    + Three versions are available, add, for adding an attribute value
    + replace, for overwriting the old attribute value with a new value
    + delete, to delete a value form an attribute, or to delete all the values of an attribute
    +

    +
    + +

    + This operation deletes an object from the LDAP server. + It needs the distinghised name from the object. +

    +
    + +

    + This operation modifies the distinguished name from an object (it "moves" the object).
    + It comes in two flavours, just renaming an entry, then you specify a new RDN (relative distinguished name, this is the lowest part of the DN)
    + eg, you can rename "cn=admin,dc=siemens,dc=com" to cn=administrator,dc=Siemens,dc=com"
    + The second flavour is renaming (moving) a complete subtree by specifying a "new superior"
    + eg you can move a complete subtree "ou=retired,ou=people,dc=siemens,dc=com" to a new subtree "ou=retired people,dc=siemens,dc=com" by specifying + a new rdn "ou=retired people" and a new superior of "dc=siemens,dc=com" +

    +
    +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/listeners.xml b/ApacheJmeter/xdocs/usermanual/listeners.xml new file mode 100644 index 0000000..da0f023 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/listeners.xml @@ -0,0 +1,485 @@ + + + + +]> + + + + + User's Manual: Listeners + + + + +
    +

    A listener is a component that shows the results of the +samples. The results can be shown in a tree, tables, graphs or simply written to a log +file. To view the contents of a response from any given sampler, add either of the Listeners "View +Results Tree" or "View Results in table" to a test plan. To view the response time graphically, add +graph results, spline results or distribution graph. +The Listeners +section of the components page has full descriptions of all the listeners.

    + + +Different listeners display the response information in different ways. +However, they all write the same raw data to the output file - if one is specified. + +

    +The "Configure" button can be used to specify which fields to write to the file, and whether to +write it as CSV or XML. +CSV files are much smaller than XML files, so use CSV if you are generating lots of samples. +

    +

    +The file name can be specified using either a relative or an absolute path name. +Relative paths are resolved relative to the current working directory (which defaults to the bin/ directory). +Versions of JMeter after 2.4 also support paths relative to the directory containing the current test plan (JMX file). +If the path name begins with "~/" (or whatever is in the jmeter.save.saveservice.base_prefix JMeter property), +then the path is assumed to be relative to the JMX file location. +

    +

    +If you only wish to record certain samples, add the Listener as a child of the sampler. +Or you can use a Simple Controller to group a set of samplers, and add the Listener to that. +The same filename can be used by multiple samplers - but make sure they all use the same configuration! +

    +
    + +
    +

    +The default items to be saved can be defined in the jmeter.properties (or user.properties) file. +The properties are used as the initial settings for the Listener Config pop-up, and are also +used for the log file specified by the -l command-line flag (commonly used for non-GUI test runs). +

    +

    To change the default format, find the following line in jmeter.properties:

    +

    jmeter.save.saveservice.output_format=

    +

    +The information to be saved is configurable. For maximum information, choose "xml" as the format and specify "Functional Test Mode" on the Test Plan element. If this box is not checked, the default saved +data includes a time stamp (the number of milliseconds since midnight, +January 1, 1970 UTC), the data type, the thread name, the label, the +response time, message, and code, and a success indicator. If checked, all information, including the full response data will be logged.

    +

    +The following example indicates how to set +properties to get a vertical bar ("|") delimited format that will +output results like:.

    +

    + +

    +timeStamp|time|label|responseCode|threadName|dataType|success|failureMessage
    +02/06/03 08:21:42|1187|Home|200|Thread Group-1|text|true|
    +02/06/03 08:21:42|47|Login|200|Thread Group-1|text|false|Test Failed: 
    +	expected to contain: password etc.
    +
    +

    +

    +The corresponding jmeter.properties that need to be set are shown below. One oddity +in this example is that the output_format is set to csv, which +typically +indicates comma-separated values. However, the default_delimiter was +set to be a vertical bar instead of a comma, so the csv tag is a +misnomer in this case. (Think of CSV as meaning character separated values)

    +

    + +

    +jmeter.save.saveservice.output_format=csv
    +jmeter.save.saveservice.assertion_results_failure_message=true
    +jmeter.save.saveservice.default_delimiter=|
    +
    + +

    +The full set of properties that affect result file output is shown below. +

    + +
    +#---------------------------------------------------------------------------
    +# Results file configuration
    +#---------------------------------------------------------------------------
    +
    +# This section helps determine how result data will be saved.
    +# The commented out values are the defaults.
    +
    +# legitimate values: xml, csv, db.  Only xml and csv are currently supported.
    +#jmeter.save.saveservice.output_format=xml
    +
    +
    +# true when field should be saved; false otherwise
    +
    +# assertion_results_failure_message only affects CSV output
    +#jmeter.save.saveservice.assertion_results_failure_message=false
    +#
    +#jmeter.save.saveservice.data_type=true
    +#jmeter.save.saveservice.label=true
    +#jmeter.save.saveservice.response_code=true
    +# response_data is not currently supported for CSV output
    +#jmeter.save.saveservice.response_data=false
    +# Save ResponseData for failed samples
    +#jmeter.save.saveservice.response_data.on_error=false
    +#jmeter.save.saveservice.response_message=true
    +#jmeter.save.saveservice.successful=true
    +#jmeter.save.saveservice.thread_name=true
    +#jmeter.save.saveservice.time=true
    +#jmeter.save.saveservice.subresults=true
    +#jmeter.save.saveservice.assertions=true
    +#jmeter.save.saveservice.latency=true
    +#jmeter.save.saveservice.samplerData=false
    +#jmeter.save.saveservice.responseHeaders=false
    +#jmeter.save.saveservice.requestHeaders=false
    +#jmeter.save.saveservice.encoding=false
    +#jmeter.save.saveservice.bytes=true
    +#jmeter.save.saveservice.url=false
    +#jmeter.save.saveservice.filename=false
    +#jmeter.save.saveservice.hostname=false
    +#jmeter.save.saveservice.thread_counts=false
    +#jmeter.save.saveservice.sample_count=false
    +#jmeter.save.saveservice.idle_time=false
    +
    +# Timestamp format
    +# legitimate values: none, ms, or a format suitable for SimpleDateFormat
    +#jmeter.save.saveservice.timestamp_format=ms
    +#jmeter.save.saveservice.timestamp_format=MM/dd/yy HH:mm:ss
    +
    +# Put the start time stamp in logs instead of the end
    +sampleresult.timestamp.start=true
    +
    +# Whether to use System.nanoTime() - otherwise only use System.currentTimeMillis()
    +#sampleresult.useNanoTime=true
    +
    +# Use a background thread to calculate the nanoTime offset
    +# Set this to <= 0 to disable the background thread
    +#sampleresult.nanoThreadSleep=5000
    +
    +# legitimate values: none, first, all
    +#jmeter.save.saveservice.assertion_results=none
    +
    +# For use with Comma-separated value (CSV) files or other formats
    +# where the fields' values are separated by specified delimiters.
    +# Default:
    +#jmeter.save.saveservice.default_delimiter=,
    +# For TAB, since JMeter 2.3 one can use:
    +#jmeter.save.saveservice.default_delimiter=\t
    +
    +#jmeter.save.saveservice.print_field_names=false
    +
    +# Optional list of JMeter variable names whose values are to be saved in the result data files.
    +# Use commas to separate the names. For example:
    +#sample_variables=SESSION_ID,REFERENCE
    +# N.B. The current implementation saves the values in XML as attributes,
    +# so the names must be valid XML names.
    +# Versions of JMeter after 2.3.2 send the variable to all servers
    +# to ensure that the correct data is available at the client.
    +
    +# Optional xml processing instruction for line 2 of the file:
    +#jmeter.save.saveservice.xml_pi=&lt;?xml-stylesheet type="text/xsl" href="sample.xsl"?>
    +
    +# Prefix used to identify filenames that are relative to the current base
    +#jmeter.save.saveservice.base_prefix=~/
    +
    +

    +

    +The date format to be used for the timestamp_format is described in +SimpleDateFormat. +Bear in mind that choosing a date format other than "ms" is likely to +make it impossible for JMeter to interpret the value when it is read +in later for viewing purposes.

    + +

    +Versions of JMeter after 2.3.1 allow one to use the sample_variables +property to define a list of additional JMeter variables which are to be saved with +each sample in the JTL files. The values are written to CSV files as additional columns, +and as additional attributes in XML files. See above for an example. +

    +
    + + +

    +Listeners can be configured to save different items to the result log files (JTL) by using the Config popup as shown below. +The defaults are defined as described in the Listener Default Configuration section above. +Items with (CSV) after the name only apply to the CSV format; items with (XML) only apply to XML format. +CSV format cannot currently be used to save any items that include line-breaks. +

    +

    Configuration dialogue
    +
    +

    +Note that cookies, method and the query string are saved as part of the "Sampler Data" option. +

    +
    + +
    +

    +When running in non-GUI mode, the -l flag can be used to create a top-level listener for the test run. +This is in addition to any Listeners defined in the test plan. +The configuration of this listener is controlled by entries in the file jmeter.properties +as described in the previous section. +

    +

    +This feature can be used to specify different data and log files for each test run, for example: +

    +jmeter -n -t testplan.jmx -l testplan_01.jtl -j testplan_01.log
    +jmeter -n -t testplan.jmx -l testplan_02.jtl -j testplan_02.log
    +
    +

    +

    +Note that JMeter logging messages are written to the file jmeter.log by default. +This file is recreated each time, so if you want to keep the log files for each run, +you will need to rename it using the -j option as above. The -j option was added in version 2.3. +

    +

    Versions of JMeter after 2.3.1 support variables in the log file name. +If the filename contains paired single-quotes, then the name is processed +as a SimpleDateFormat format applied to the current date, for example: +log_file='jmeter_'yyyyMMddHHmmss'.tmp'. +This can be used to generate a unique name for each test run. +

    +
    + +
    +

    Listeners can use a lot of memory if there are a lot of samples. +Most of the listeners currently keep a copy of every sample they display, apart from: +

    +
      +
    • Simple Data Writer
    • +
    • BeanShell/BSF Listener
    • +
    • Mailer Visualizer
    • +
    • Monitor Results
    • +
    • Summary Report
    • +
    +

    +The following Listeners no longer need to keep copies of every single sample. +Instead, samples with the same elapsed time are aggregated. +Less memory is now needed, especially if most samples only take a second or two at most. +

    +
      +
    • Aggregate Report
    • +
    • Aggregate Graph
    • +
    • Distribution Graph
    • +
    +

    To minimise the amount of memory needed, use the Simple Data Writer, and use the CSV format.

    +
    + +
    +

    +The CSV log format depends on which data items are selected in the configuration. +Only the specified data items are recorded in the file. +The order of appearance of columns is fixed, and is as follows: +

    +
      +
    • timeStamp - in milliseconds since 1/1/1970
    • +
    • elapsed - in milliseconds
    • +
    • label - sampler label
    • +
    • responseCode - e.g. 200, 404
    • +
    • responseMessage - e.g. OK
    • +
    • threadName
    • +
    • dataType - e.g. text
    • +
    • success - true or false
    • +
    • failureMessage - if any
    • +
    • bytes - number of bytes in the sample
    • +
    • grpThreads - number of active threads in this thread group
    • +
    • allThreads - total number of active threads in all groups
    • +
    • URL
    • +
    • Filename - if Save Response to File was used
    • +
    • latency - time to first response
    • +
    • encoding
    • +
    • SampleCount - number of samples (1, unless multiple samples are aggregated)
    • +
    • ErrorCount - number of errors (0 or 1, unless multiple samples are aggregated)
    • +
    • Hostname where the sample was generated
    • +
    • IdleTime - number of milliseconds of 'Idle' time (normally 0)
    • +
    • Variables, if specified
    • +
    + +
    + +
    +

    +The format of the updated XML (2.1) is as follows (line breaks will be different): +

    +
    +&lt;?xml version="1.0" encoding="UTF-8"?>
    +&lt;testResults version="1.2">
    +
    +-- HTTP Sample, with nested samples 
    +
    +&lt;httpSample t="1392" lt="351" ts="1144371014619" s="true" 
    +     lb="HTTP Request" rc="200" rm="OK" 
    +     tn="Listen 1-1" dt="text" de="iso-8859-1" by="12407">
    +  &lt;httpSample t="170" lt="170" ts="1144371015471" s="true" 
    +        lb="http://www.apache.org/style/style.css" rc="200" rm="OK" 
    +        tn="Listen 1-1" dt="text" de="ISO-8859-1" by="1002">
    +    &lt;responseHeader class="java.lang.String">HTTP/1.1 200 OK
    +Date: Fri, 07 Apr 2006 00:50:14 GMT
    +...
    +Content-Type: text/css
    +&lt;/responseHeader>
    +    &lt;requestHeader class="java.lang.String">MyHeader: MyValue&lt;/requestHeader>
    +    &lt;responseData class="java.lang.String">body, td, th {
    +    font-size: 95%;
    +    font-family: Arial, Geneva, Helvetica, sans-serif;
    +    color: black;
    +    background-color: white;
    +}
    +...
    +&lt;/responseData>
    +    &lt;cookies class="java.lang.String">&lt;/cookies>
    +    &lt;method class="java.lang.String">GET&lt;/method>
    +    &lt;queryString class="java.lang.String">&lt;/queryString>
    +    &lt;url>http://www.apache.org/style/style.css&lt;/url>
    +  &lt;/httpSample>
    +  &lt;httpSample t="200" lt="180" ts="1144371015641" s="true" 
    +     lb="http://www.apache.org/images/asf_logo_wide.gif" 
    +     rc="200" rm="OK" tn="Listen 1-1" dt="bin" de="ISO-8859-1" by="5866">
    +    &lt;responseHeader class="java.lang.String">HTTP/1.1 200 OK
    +Date: Fri, 07 Apr 2006 00:50:14 GMT
    +...
    +Content-Type: image/gif
    +&lt;/responseHeader>
    +    &lt;requestHeader class="java.lang.String">MyHeader: MyValue&lt;/requestHeader>
    +    &lt;responseData class="java.lang.String">http://www.apache.org/asf.gif&lt;/responseData>
    +      &lt;responseFile class="java.lang.String">Mixed1.html&lt;/responseFile>
    +    &lt;cookies class="java.lang.String">&lt;/cookies>
    +    &lt;method class="java.lang.String">GET&lt;/method>
    +    &lt;queryString class="java.lang.String">&lt;/queryString>
    +    &lt;url>http://www.apache.org/asf.gif&lt;/url>
    +  &lt;/httpSample>
    +  &lt;responseHeader class="java.lang.String">HTTP/1.1 200 OK
    +Date: Fri, 07 Apr 2006 00:50:13 GMT
    +...
    +Content-Type: text/html; charset=ISO-8859-1
    +&lt;/responseHeader>
    +  &lt;requestHeader class="java.lang.String">MyHeader: MyValue&lt;/requestHeader>
    +  &lt;responseData class="java.lang.String"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    +               "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    +...
    +&amp;lt;html&amp;gt;
    + &amp;lt;head&amp;gt;
    +...
    + &amp;lt;/head&amp;gt;
    + &amp;lt;body&amp;gt;        
    +...
    + &amp;lt;/body&amp;gt;
    +&amp;lt;/html&amp;gt;
    +&lt;/responseData>
    +  &lt;cookies class="java.lang.String">&lt;/cookies>
    +  &lt;method class="java.lang.String">GET&lt;/method>
    +  &lt;queryString class="java.lang.String">&lt;/queryString>
    +  &lt;url>http://www.apache.org/&lt;/url>
    +&lt;/httpSample>
    +
    +-- nonHTTPP Sample
    +
    +&lt;sample t="0" lt="0" ts="1144372616082" s="true" lb="Example Sampler"
    +    rc="200" rm="OK" tn="Listen 1-1" dt="text" de="ISO-8859-1" by="10">
    +  &lt;responseHeader class="java.lang.String">&lt;/responseHeader>
    +  &lt;requestHeader class="java.lang.String">&lt;/requestHeader>
    +  &lt;responseData class="java.lang.String">Listen 1-1&lt;/responseData>
    +  &lt;responseFile class="java.lang.String">Mixed2.unknown&lt;/responseFile>
    +  &lt;samplerData class="java.lang.String">ssssss&lt;/samplerData>
    +&lt;/sample>
    +
    +&lt;/testResults>
    +
    +

    +Note that the sample node name may be either "sample" or "httpSample". +

    +
    + +
    +

    +The format of the JTL files is identical for 2.2 and 2.1. Format 2.2 only affects JMX files. +

    +
    + +
    +

    +The sample attributes have the following meaning: +

    + + + + + + + + + + + + + + + + + + + + +
    AttributeContent
    byBytes
    deData encoding
    dtData type
    ecError count (0 or 1, unless multiple samples are aggregated)
    hnHostname where the sample was generated
    itIdle Time = time not spent sampling (milliseconds) (generally 0)
    lbLabel
    ltLatency = time to initial response (milliseconds) - not all samplers support this
    naNumber of active threads for all thread groups
    ngNumber of active threads in this group
    rcResponse Code (e.g. 200)
    rmResponse Message (e.g. OK)
    sSuccess flag (true/false)
    scSample count (1, unless multiple samples are aggregated)
    tElapsed time (milliseconds)
    tnThread Name
    tstimeStamp (milliseconds since midnight Jan 1, 1970 UTC)
    varnameValue of the named variable (versions of JMeter after 2.3.1)
    +

    +Versions 2.1 and 2.1.1 of JMeter saved the Response Code as "rs", but read it back expecting to find "rc". +This has been corrected so that it is always saved as "rc"; either "rc" or "rs" can be read. +

    + +Versions of JMeter after 2.3.1 allow additional variables to be saved with the test plan. +Currently, the variables are saved as additional attributes. +The testplan variable name is used as the attribute name. +See Sample variables (above) for more information. + +
    + +
    +

    +As shown above, the response data can be saved in the XML log file if required. +However, this can make the file rather large, and the text has to be encoded so +that it is still valid XML. Also, images cannot be included. +
    +Another solution is to use the Post-Processor Save Responses to a file. +This generates a new file for each sample, and saves the file name with the sample. +The file name can then be included in the sample log output. +The data will be retrieved from the file if necessary when the sample log file is reloaded. +

    +
    +
    +

    To view an existing results file, you can use the File "Browse..." button to select a file. +If necessary, just create a dummy testplan with the appropriate Listener in it. +

    +

    Results can be read from XML or CSV format files. +When reading from CSV results files, the header (if present) is used to determine which fields were saved. +In order to interpret a header-less CSV file correctly, the appropriate JMeter properties must be set. +

    + +Versions of JMeter up to 2.3.2 used to clear any current data before loading the new file. +This is no longer done, thus allowing files to be merged. +If the previous behaviour is required, +use the menu item Run/Clear (Ctrl+Shift+E) or Run/Clear All (Ctrl+E) before loading the file. + +
    +
    +

    JMeter is capable of saving any listener as a PNG file. To do so, select the +listener in the left panel. Click edit -> Save As Image. A file dialog will +appear. Enter the desired name and save the listener. +

    +

    +The Listeners which generate output as tables can also be saved using Copy/Paste. +Select the desired cells in the table, and use the OS Copy short-cut (normally Control+C). +The data will be saved to the clipboard, from where it can be pasted into another application, +e.g. a spreadsheet or text editor. +

    +
    Figure 1 - Edit -> Save As Image
    + +
    + +
    diff --git a/ApacheJmeter/xdocs/usermanual/regular_expressions.xml b/ApacheJmeter/xdocs/usermanual/regular_expressions.xml new file mode 100644 index 0000000..b1302d5 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/regular_expressions.xml @@ -0,0 +1,247 @@ + + + +]> + + + + User's Manual: Regular Expressions + + + + +
    + +

    +JMeter includes the pattern matching software Apache Jakarta ORO +
    +There is some documentation for this on the Jakarta web-site, for example + +a summary of the pattern matching characters +

    +

    +There is also documentation on an older incarnation of the product at +OROMatcher User's guide, which might prove useful. +

    +

    +The pattern matching is very similar to the pattern matching in Perl. +A full installation of Perl will include plenty of documentation on regular expressions - look for perlrequick, perlretut, perlre, perlreref. +

    +

    +It is worth stressing the difference between "contains" and "matches", as used on the Response Assertion test element: +

    +
      +
    • +"contains" means that the regular expression matched at least some part of the target, +so 'alphabet' "contains" 'ph.b.' because the regular expression matches the substring 'phabe'. +
    • +
    • +"matches" means that the regular expression matched the whole target. +So 'alphabet' is "matched" by 'al.*t'. +
    • +
    +

    In this case, it is equivalent to wrapping the regular expression in ^ and $, viz '^al.*t$'. +

    +

    However, this is not always the case. +For example, the regular expression 'alp|.lp.*' is "contained" in 'alphabet', but does not match 'alphabet'. +

    +

    Why? Because when the pattern matcher finds the sequence 'alp' in 'alphabet', it stops trying any other combinations - and 'alp' is not the same as 'alphabet', as it does not include 'habet'. +

    +

    +Note: unlike Perl, there is no need to (i.e. do not) enclose the regular expression in //. +

    +

    +So how does one use the modifiers ismx etc if there is no trailing /? +The solution is to use extended regular expressions, i.e. /abc/i becomes (?i)abc. +See also Placement of modifiers below. +

    +
    + +

    Extract single string

    +

    +Suppose you want to match the following portion of a web-page: +
    +name="file" value="readme.txt"> +
    +and you want to extract readme.txt. +
    +A suitable regular expression would be: +
    +name="file" value="(.+?)"> +

    +The special characters above are: +

    +
      +
    • ( and ) - these enclose the portion of the match string to be returned
    • +
    • . - match any character
    • +
    • + - one or more times
    • +
    • ? - don't be greedy, i.e. stop when first match succeeds
    • +
    +

    +Note: without the ?, the .+ would continue past the first "> +until it found the last possible "> - which is probably not what was intended. +

    +

    +Note: although the above expression works, it's more efficient to use the following expression: +
    +name="file" value="([^"]+)"> +where

    +[^"] - means match anything except "

    +In this case, the matching engine can stop looking as soon as it sees the first ", +whereas in the previous case the engine has to check that it has found "> rather than say " >. +

    +

    Extract multiple strings

    +

    +Suppose you want to match the following portion of a web-page:
    +name="file.name" value="readme.txt" +and you want to extract both file.name and readme.txt. +
    +A suitable reqular expression would be: +
    +name="([^"]+)" value="([^"]+)" +
    +This would create 2 groups, which could be used in the JMeter Regular Expression Extractor template as $1$ and $2$. +

    +

    +The JMeter Regex Extractor saves the values of the groups in additional variables. +

    +

    +For example, assume: +

    +
      +
    • Reference Name: MYREF
    • +
    • Regex: name="(.+?)" value="(.+?)"
    • +
    • Template: $1$$2$
    • +
    +Do not enclose the regular expression in / / +

    +The following variables would be set: +

    +
      +
    • MYREF: file.namereadme.txt
    • +
    • MYREF_g0: name="file.name" value="readme.txt"
    • +
    • MYREF_g1: file.name
    • +
    • MYREF_g2: readme.txt
    • +
    +These variables can be referred to later on in the JMeter test plan, as ${MYREF}, ${MYREF_g1} etc +

    +
    + +

    The pattern matching behaves in various slightly different ways, +depending on the setting of the multi-line and single-line modifiers. +Note that the single-line and multi-line operators have nothing to do with each other; +they can be specified independently. +

    +

    Single-line mode

    +

    +Single-line mode only affects how the '.' meta-character is interpreted. +

    +

    +Default behaviour is that '.' matches any character except newline. +In single-line mode, '.' also matches newline. +

    + +

    Multi-line mode

    +

    +Multi-line mode only affects how the meta-characters '^' and '$' are interpreted. +

    +

    +Default behaviour is that '^' and '$' only match at the very beginning and end of the string. +When Multi-line mode is used, the '^' metacharacter matches at the beginning of every line, +and the '$' metacharacter matches at the end of every line.

    + +
    + + +

    +Regular expressions use certain characters as meta characters - these characters have a special meaning to the RE engine. +Such characters must be escaped by preceeding them with \ (backslash) in order to treat them as ordinary characters. +Here is a list of the meta characters and their meaning (please check the ORO documentation if in doubt). +

    +
      +
    • ( ) - grouping
    • +
    • [ ] - character classes
    • +
    • { } - repetition
    • +
    • * + ? - repetition
    • +
    • . - wild-card character
    • +
    • \ - escape character
    • +
    • | - alternatives
    • +
    • ^ $ - start and end of string or line
    • +
    +

    Please note that ORO does not support the \Q and \E meta-characters. +[In other RE engines, these can be used to quote a portion of an RE so that the meta-characters stand for themselves.]

    + +

    +The following Perl5 extended regular expressions are supported by ORO. + +

    +
    (?#text)
    +
    An embedded comment causing text to be ignored.
    +
    (?:regexp)
    +
    Groups things like "()" but doesn't cause the group match to be saved.
    +
    (?=regexp)
    +
    A zero-width positive lookahead assertion. For example, \w+(?=\s) matches a word followed by whitespace, without including whitespace in the MatchResult.
    +
    (?!regexp)
    +
    A zero-width negative lookahead assertion. For example foo(?!bar) matches any occurrence of "foo" that isn't followed by "bar". Remember that this is a zero-width assertion, which means that a(?!b)d will match ad because a is followed by a character that is not b (the d) and a d follows the zero-width assertion.
    +
    (?imsx)
    +
    One or more embedded pattern-match modifiers. i enables case insensitivity, m enables multiline treatment of the input, s enables single line treatment of the input, and x enables extended whitespace comments.
    +
    +Note that (?<=regexp) - lookbehind - is not supported. +

    + +
    + + +

    +Modifiers can be placed anywhere in the regex, and apply from that point onwards. +[A bug in ORO means that they cannot be used at the very end of the regex. +However they would have no effect there anyway.] +

    +

    +The single-line (?s) and multi-line (?m) modifiers are normally placed at the start of the regex. +

    +

    +The ignore-case modifier (?i) may be usefully applied to just part of a regex, +for example: +

    +Match ExAct case or (?i)ArBiTrARY(?-i) case
    +
    +

    +
    +
    + +
    +

    +Since JMeter 2.4, the listener View Results Tree +include a RegExp Tester to test regular expressions directly on sampler response data. +

    +

    +There is a demo applet for Apache JMeter ORO. +

    +

    +Another approach is to use a simple test plan to test the regular expressions. +The Java Request sampler can be used to generate a sample, or the HTTP Sampler can be used to load a file. +Add a Debug Sampler and a Tree View Listener and changes to the regular expression can be tested quickly, +without needing to access any external servers. +

    +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/remote-test.xml b/ApacheJmeter/xdocs/usermanual/remote-test.xml new file mode 100644 index 0000000..95eb1b7 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/remote-test.xml @@ -0,0 +1,290 @@ + + + + +]> + + + + + User's Manual: Remote (Distributed) Testing + + + + +
    + +

    In the event that your JMeter client machine is unable, performance-wise, to simulate +enough users to stress your server, an option exists to control multiple, remote JMeter +engines from a single JMeter GUI client. By running JMeter remotely, you can replicate +a test across many low-end computers and thus simulate a larger load on the server. One +instance of the JMeter GUI client can control any number of remote JMeter instances, and collect +all the data from them. This offers the following features: + + +

      +
    • Saving of test samples to the local machine
    • +
    • Managment of multiple JMeterEngines from a single machine
    • +
    • No need to copy the test plan to each server - the client sends it to all the servers
    • +
    +

    + +Note: The same test plan is run by all the servers. +JMeter does not distribute the load between servers, each runs the full test plan. + +

    +However, remote mode does use more resources than running the same number of non-GUI tests independently. +If many server instances are used, the client JMeter can become overloaded, as can the client network connection. +

    +

    Note that while you can execute the JMeterEngine on your application +server, you need to be mindful of the fact that this will be adding processing +overhead on the application server and thus your testing results will be +somewhat tainted. The recommended approach is to have one or more machines on +the same Ethernet segment as your application server that you configure to run +the JMeter Engine. This will minimize the impact of the network on the test +results without impacting the performance of the application serer +itself. +

    + +

    Step 0: Configure the nodes

    +

    +Make sure that all the nodes (client and servers) are running exactly the same version of JMeter. +As far as possible, also use the same version of Java on all systems. +Using different versions of Java may work - but is best avoided. +

    +

    +If the test uses any data files, note that these are not sent across by the client so +make sure that these are available in the appropriate directory on each server. +If necessary you can define different values for properties by editting the user.properties or system.properties +files on each server. These properties will be picked up when the server is started and may be +used in the test plan to affect its behaviour (e.g. connecting to a different remote server). +Alternatively use different content in any datafiles used by the test +(e.g. if each server must use unique ids, divide these between the data files) +

    + +

    Step 1: Start the servers

    +

    To run JMeter in remote node, start the JMeter server component on all machines you wish to run on by running the JMETER_HOME/bin/jmeter-server (unix) or JMETER_HOME/bin/jmeter-server.bat (windows) script.

    +

    Note that there can only be one JMeter server on each node unless different RMI ports are used.

    +

    Since JMeter 2.3.1, the JMeter server application starts the RMI registry itself; +there is no need to start RMI registry separately. +To revert to the previous behaviour, define the JMeter property server.rmi.create=false on the server host systems. +

    +

    +By default, RMI uses a dynamic port for the JMeter server engine. This can cause problems for firewalls, +so versions of JMeter after 2.3.2 will check for the JMeter property server.rmi.localport. +If this is non-zero, it will be used as the local port number for the server engine. +

    +

    Step 2: Add the server IP to your client's Properties File

    +

    Edit the properties file on the controlling JMeter machine. In /bin/jmeter.properties, find the property named, "remote_hosts", and +add the value of your running JMeter server's IP address. Multiple such servers can be added, comma-delimited.

    +

    Note that you can use the -R command line option +instead to specify the remote host(s) to use. This has the same effect as using -r and -Jremote_hosts={serverlist}. + E.g. jmeter -Rhost1,127.0.0.1,host2

    +

    If you define the JMeter property server.exitaftertest=true, then the server will exit after it runs a single test. +See also the -X flag (described below) +

    +

    Step 3a: Start the JMeter Client from a GUI client

    +

    Now you are ready to start the controlling JMeter client. For MS-Windows, start the client with the script "bin/jmeter.bat". For UNIX, +use the script "bin/jmeter". You will notice that the Run menu contains two new sub-menus: "Remote Start" and "Remote Stop" +(see figure 1). These menus contain the client that you set in the properties file. Use the remote start and stop instead of the +normal JMeter start and stop menu items.

    +
    Figure 1 - Run Menu
    + +

    Step 3b: Start the JMeter from a non-GUI Client

    +

    +As an alternative, you can start the remote server(s) from a non-GUI (command-line) client. +The command to do this is: +

    +jmeter -n -t script.jmx -r
    +or
    +jmeter -n -t script.jmx -R server1,server2...
    +
    +Other flags that may be useful:
    +-Gproperty=value - define a property in all the servers (may appear more than once)
    +-Z - Exit remote servers at the end of the test.
    +
    +The first example will start whatever servers are defined in the JMeter property remote_hosts; +the second example will define remote_hosts from the list of servers and then run the remote servers. +
    +The command-line client will exit when all the remote servers have stopped. +

    + + +

    In some cases, the jmeter-server script may not work for you (if you are using an OS platform not anticipated by the JMeter developers). Here is how to start the JMeter servers (step 1 above) with a more manual process:

    +

    Step 1a: Start the RMI Registry

    +

    +Since JMeter 2.3.1, the RMI registry is started by the JMeter server, so this section does not apply in the normal case. +To revert to the previous behaviour, define the JMeter property server.rmi.create=false on the server host systems +and follow the instructions below. +

    +

    JMeter uses Remote Method Invocation (RMI) as the remote communication mechanism. Therefore, you need +to run the RMI Registry application (which is named, "rmiregistry") that comes with the JDK and is located in the "bin" +directory. Before running rmiregistry, make sure that the following jars are in your system claspath: +

      +
    • JMETER_HOME/lib/ext/ApacheJMeter_core.jar
    • +
    • JMETER_HOME/lib/jorphan.jar
    • +
    • JMETER_HOME/lib/logkit-1.2.jar
    • +
    +The +rmiregistry application needs access to certain JMeter classes. Run rmiregistry with no parameters. By default the +application listens to port 1099.

    + +

    Step 1b: Start the JMeter Server

    +

    Once the RMI Registry application is running, start the JMeter Server. +Use the "-s" option with the jmeter startup script ("jmeter -s").

    + +

    Steps 2 and 3 remain the same.

    +
    + +

    +JMeter/RMI requires a connection from the client to the server. This will use the port you chose, default 1099.
    +JMeter/RMI also requires a reverse connection in order to return sample results from the server to the client.
    +This will use a high-numbered port.
    +This port can be controlled by jmeter property called client.rmi.localport in jmeter.properties.
    +If there are any firewalls or other network filters between JMeter client and server, +you will need to make sure that they are set up to allow the connections through. +If necessary, use monitoring software to show what traffic is being generated. +

    +

    If you're running Suse Linux, these tips may help. The default installation may enable the firewall. In that case, remote testing will not work properly. The following tips were contributed by Sergey Ten.

    +

    If you see connections refused, turn on debugging by passing the following options.

    + rmiregistry -J-Dsun.rmi.log.debug=true + -J-Dsun.rmi.server.exceptionTrace=true + -J-Dsun.rmi.loader.logLevel=verbose + -J-Dsun.rmi.dgc.logLevel=verbose + -J-Dsun.rmi.transport.logLevel=verbose + -J-Dsun.rmi.transport.tcp.logLevel=verbose +

    Since JMeter 2.3.1, the RMI registry is started by the server; however the options can still be passed in from the JMeter command line. +For example: "jmeter -s -Dsun.rmi.loader.logLevel=verbose" (i.e. omit the -J prefixes). +Alternatively the properties can be defined in the system.properties file. +

    +

    The solution to the problem is to remove the loopbacks 127.0.0.1 and 127.0.0.2 from etc/hosts. What happens is jmeter-server can't connect to rmiregistry if 127.0.0.2 loopback is not available. Use the following settings to fix the problem.

    +

    Replace

    +
      +
    • `dirname $0`/jmeter -s "$@"
    • +
    +

    With

    +
      +
    • HOST="-Djava.rmi.server.hostname=[computer_name][computer_domain]
    • +
    • -Djava.security.policy=`dirname $0`/[policy_file]"
    • +
    • `dirname $0`/jmeter $HOST -s "$@"
    • +
    +

    Also create a policy file and add [computer_name][computer_domain] line to /etc/hosts.

    + +

    In order to better support SSH-tunneling of the RMI communication channels used +in remote testing, since JMeter 2.6:

    +
      +
    • a new property "client.rmi.localport" can be set to control the RMI port used by the RemoteSampleListenerImpl
    • +
    • To support tunneling RMI traffic over an SSH tunnel as the remote endpoint using a port on the local machine, + loopback interface is now allowed to be used if it has been specified directly using the Java System Property "java.rmi.server.hostname" parameter.
    • +
    +
    + +

    By default, JMeter uses the standard RMI port 1099. It is possible to change this. For this to work successfully, all the following need to agree:

    +
      +
    • On the server, start rmiregistry using the new port number
    • +
    • On the server, start JMeter with the property server_port defined
    • +
    • On the client, update the remote_hosts property to include the new remote host:port settings
    • +
    + +

    Since Jmeter 2.1.1, the jmeter-server scripts provide support for changing the port. +For example, assume you want to use port 1664 (perhaps 1099 is already used).

    +
    +On Windows (in a DOS box)
    +C:\JMETER> SET SERVER_PORT=1664
    +C:\JMETER> JMETER-SERVER [other options]
    +
    +On Unix:
    +$ SERVER_PORT=1664 jmeter-server [other options]
    +[N.B. use upper case for the environment variable]
    +
    +

    +In both cases, the script starts rmiregistry on the specified port, +and then starts JMeter in server mode, having defined the "server_port" property. +

    +

    +The chosen port will be logged in the server jmeter.log file (rmiregistry does not create a log file). +

    +
    + + +

    +Listeners in the test plan send their results back to the client JMeter which writes the results to the specified files +By default, samples are sent back synchronously as they are generated. +This can affect the maximum throughput of the server test; the sample result has to be sent back before the thread can +continue. +There are some JMeter properties that can be set to alter this behaviour. +

    +
      +
    • mode - sample sending mode - default is Standard. This should be set on the client node.
    • +
        +
      • Standard - send samples synchronously as soon as they are generated
      • +
      • Hold - hold samples in an array until the end of a run. This may use a lot of memory on the server.
      • +
      • DiskStore - store samples in a disk file (under java.io.temp) until the end of a run. + The serialised data file is deleted on JVM exit.
      • +
      • Batch - send saved samples when either the count or time exceeds a threshold, + at which point the samples are sent synchronously. + The thresholds can be configured on the server using the following properties: +
          +
        • num_sample_threshold - number of samples to accumulate, default 100
        • +
        • time_threshold - time threshold, default 60000 ms = 60 seconds
        • +
        +
      • + See also the Asynch mode, described below. +
      • Statistical - send a summary sample when either the count or time exceeds a threshold. + The samples are summarised by thread group name and sample label. + The following fields are accumulated: +
          +
        • elapsed time
        • +
        • latency
        • +
        • bytes
        • +
        • sample count
        • +
        • error count
        • +
        + Other fields that vary between samples are lost. +
      • +
      • Stripped - remove responseData from succesful samples
      • +
      • StrippedBatch - remove responseData from succesful samples, and use Batch sender to send them.
      • +
      • Asynch - samples are temporarily stored in a local queue. A separate worker thread sends the samples. + This allows the test thread to continue without waiting for the result to be sent back to the client. + However, if samples are being created faster than they can be sent, the queue will eventually fill up, + and the sampler thread will block until some samples can be drained from the queue. + This mode is useful for smoothing out peaks in sample generation. + The queue size can be adjusted by setting the JMeter property + asynch.batch.queue.size (default 100) on the server node. +
      • +
      • Custom implementation : set the mode parameter to your custom sample sender class name. + This must implement the interface SampleSender and have a constructor which takes a single + parameter of type RemoteSampleListener. +
      • +
      +
    +

    The following properties apply to the Batch and Statistical modes:

    +
      +
    • num_sample_threshold - number of samples in a batch (default 100)
    • +
    • time_threshold - number of milliseconds to wait (default 60 seconds)
    • +
    +
    + + + + +
    + + +
    diff --git a/ApacheJmeter/xdocs/usermanual/test_plan.xml b/ApacheJmeter/xdocs/usermanual/test_plan.xml new file mode 100644 index 0000000..cdd54a6 --- /dev/null +++ b/ApacheJmeter/xdocs/usermanual/test_plan.xml @@ -0,0 +1,505 @@ + + + + +]> + + + + +User's Manual: Elements of a Test Plan + + + + +
    + +

    The Test Plan object has a checkbox called "Functional Testing". If selected, it +will cause JMeter to record the data returned from the server for each sample. If you have +selected a file in your test listeners, this data will be written to file. This can be useful if +you are doing a small run to ensure that JMeter is configured correctly, and that your server +is returning the expected results. The consequence is that the file will grow huge quickly, and +JMeter's performance will suffer. This option should be off if you are doing stress-testing (it +is off by default).

    +

    If you are not recording the data to file, this option makes no difference.

    +

    You can also use the Configuration button on a listener to decide what fields to save.

    + + +

    Thread group elements are the beginning points of any test plan. +All controllers and samplers must be under a thread group. +Other elements, e.g. Listeners, may be placed directly under the test plan, +in which case they will apply to all the thread groups. +As the name implies, the thread group +element controls the number of threads JMeter will use to execute your test. The +controls for a thread group allow you to: +

    • Set the number of threads
    • +
    • Set the ramp-up period
    • +
    • Set the number of times to execute the test
    • +

    + +

    Each thread will execute the test plan in its entirety and completely independently +of other test threads. Multiple threads are used to simulate concurrent connections +to your server application.

    + +

    The ramp-up period tells JMeter how long to take to "ramp-up" to the full number of +threads chosen. If 10 threads are used, and the ramp-up period is 100 seconds, then +JMeter will take 100 seconds to get all 10 threads up and running. Each thread will +start 10 (100/10) seconds after the previous thread was begun. If there are 30 threads +and a ramp-up period of 120 seconds, then each successive thread will be delayed by 4 seconds.

    + +

    Ramp-up needs to be long enough to avoid too large a work-load at the start +of a test, and short enough that the last threads start running before +the first ones finish (unless one wants that to happen). +

    +

    +Start with Ramp-up = number of threads and adjust up or down as needed. +

    + +

    By default, the thread group is configured to loop once through its elements.

    + +

    Version 1.9 introduces a test run scheduler. + Click the checkbox at the bottom of the Thread Group panel to reveal extra fields + in which you can enter the start and end times of the run. + When the test is started, JMeter will wait if necessary until the start-time has been reached. + At the end of each cycle, JMeter checks if the end-time has been reached, and if so, the run is stopped, + otherwise the test is allowed to continue until the iteration limit is reached.

    +

    Alternatively, one can use the relative delay and duration fields. + Note that delay overrides start-time, and duration over-rides end-time.

    +
    + + + +

    +JMeter has two types of Controllers: Samplers and Logical Controllers. +These drive the processing of a test. +

    + +

    Samplers tell JMeter to send requests to a server. For +example, add an HTTP Request Sampler if you want JMeter +to send an HTTP request. You can also customize a request by adding one +or more Configuration Elements to a Sampler. For more +information, see +Samplers.

    + +

    Logical Controllers let you customize the logic that JMeter uses to +decide when to send requests. For example, you can add an Interleave +Logic Controller to alternate between two HTTP Request Samplers. +For more information, see Logical Controllers.

    + +
    + + + +

    +Samplers tell JMeter to send requests to a server and wait for a response. +They are processed in the order they appear in the tree. +Controllers can be used to modify the number of repetitions of a sampler. +

    +

    +JMeter samplers include: +

      +
    • FTP Request
    • +
    • HTTP Request
    • +
    • JDBC Request
    • +
    • Java object request
    • +
    • LDAP Request
    • +
    • SOAP/XML-RPC Request
    • +
    • WebService (SOAP) Request
    • +
    +Each sampler has several properties you can set. +You can further customize a sampler by adding one or more Configuration Elements to the Test Plan. +

    + +

    If you are going to send multiple requests of the same type (for example, +HTTP Request) to the same server, consider using a Defaults Configuration +Element. Each controller has one or more Defaults elements (see below).

    + +

    Remember to add a Listener to your test plan to view and/or store the +results of your requests to disk.

    + +

    If you are interested in having JMeter perform basic validation on +the response of your request, add an Assertion to +the sampler. For example, in stress testing a web application, the server +may return a successful "HTTP Response" code, but the page may have errors on it or +may be missing sections. You could add assertions to check for certain HTML tags, +common error strings, and so on. JMeter lets you create these assertions using regular +expressions.

    + +

    JMeter's built-in samplers

    +
    + + +

    Logic Controllers let you customize the logic that JMeter uses to +decide when to send requests. +Logic Controllers can change the order of requests coming from their +child elements. They can modify the requests themselves, cause JMeter to repeat +requests, etc. +

    + +

    To understand the effect of Logic Controllers on a test plan, consider the +following test tree:

    + +

    +

      +
    • Test Plan
    • +
        +
      • Thread Group
      • +
          +
        • Once Only Controller
        • +
            +
          • Login Request (an )
          • +
          +
        • Load Search Page (HTTP Sampler)
        • +
        • Interleave Controller
        • +
            +
          • Search "A" (HTTP Sampler)
          • +
          • Search "B" (HTTP Sampler)
          • +
          • HTTP default request (Configuration Element)
          • +
          +
        • HTTP default request (Configuration Element)
        • +
        • Cookie Manager (Configuration Element)
        • +
        +
      +
    +

    + +

    The first thing about this test is that the login request will be executed only +the first time through. Subsequent iterations will skip it. This is due to the +effects of the .

    + +

    After the login, the next Sampler loads the search page (imagine a +web application where the user logs in, and then goes to a search page to do a search). This +is just a simple request, not filtered through any Logic Controller.

    + +

    After loading the search page, we want to do a search. Actually, we want to do +two different searches. However, we want to re-load the search page itself between +each search. We could do this by having 4 simple HTTP request elements (load search, +search "A", load search, search "B"). Instead, we use the which passes on one child request each time through the test. It keeps the +ordering (ie - it doesn't pass one on at random, but "remembers" its place) of its +child elements. Interleaving 2 child requests may be overkill, but there could easily have +been 8, or 20 child requests.

    + +

    Note the that +belongs to the Interleave Controller. Imagine that "Search A" and "Search B" share +the same PATH info (an HTTP request specification includes domain, port, method, protocol, +path, and arguments, plus other optional items). This makes sense - both are search requests, + hitting the same back-end search engine (a servlet or cgi-script, let's say). Rather than + configure both HTTP Samplers with the same information in their PATH field, we + can abstract that information out to a single Configuration Element. When the Interleave + Controller "passes on" requests from "Search A" or "Search B", it will fill in the blanks with + values from the HTTP default request Configuration Element. So, we leave the PATH field + blank for those requests, and put that information into the Configuration Element. In this +case, this is a minor benefit at best, but it demonstrates the feature.

    + +

    The next element in the tree is another HTTP default request, this time added to the +Thread Group itself. The Thread Group has a built-in Logic Controller, and thus, it uses +this Configuration Element exactly as described above. It fills in the blanks of any +Request that passes through. It is extremely useful in web testing to leave the DOMAIN +field blank in all your HTTP Sampler elements, and instead, put that information +into an HTTP default request element, added to the Thread Group. By doing so, you can +test your application on a different server simply by changing one field in your Test Plan. +Otherwise, you'd have to edit each and every Sampler.

    + +

    The last element is a . A Cookie Manager should be added to all web tests - otherwise JMeter will +ignore cookies. By adding it at the Thread Group level, we ensure that all HTTP requests +will share the same cookies.

    + +

    Logic Controllers can be combined to achieve various results. See the list of built-in +Logic Controllers.

    +
    + + +

    The Test Fragment element is a special type of controller that +exists on the Test Plan tree at the same level as the Thread Group element. It is distinguished +from a Thread Group in that it is not executed unless it is +referenced by either a or an . +

    +

    This element is purely for code re-use within Test Plans and was introduced in Version 2.5

    +
    + + +

    Listeners provide access to the information JMeter gathers about the test cases while +JMeter runs. The listener plots the response times on a graph. +The "View Results Tree" Listener shows details of sampler requests and responses, and can display basic HTML and XML representations of the response. +Other listeners provide summary or aggregation information. +

    + +

    +Additionally, listeners can direct the data to a file for later use. +Every listener in JMeter provides a field to indicate the file to store data to. +There is also a Configuration button which can be used to choose which fields to save, and whether to use CSV or XML format. +Note that all Listeners save the same data; the only difference is in the way the data is presented on the screen. +

    + +

    +Listeners can be added anywhere in the test, including directly under the test plan. +They will collect data only from elements at or below their level. +

    + +

    There are several listeners +that come with JMeter.

    +
    + + + +

    By default, a JMeter thread sends requests without pausing between each request. +We recommend that you specify a delay by adding one of the available timers to +your Thread Group. If you do not add a delay, JMeter could overwhelm your server by +making too many requests in a very short amount of time.

    + +

    The timer will cause JMeter to delay a certain amount of time before each +sampler which is in its scope.

    + +

    +If you choose to add more than one timer to a Thread Group, JMeter takes the sum of +the timers and pauses for that amount of time before executing the samplers to which the timers apply. +Timers can be added as children of samplers or controllers in order to restrict the samplers to which they are applied. +

    +

    +To provide a pause at a single place in a test plan, one can use the Sampler. +

    +
    + + + +

    Assertions allow you to assert facts about responses received from the +server being tested. Using an assertion, you can essentially "test" that your +application is returning the results you expect it to.

    + +

    For instance, you can assert that the response to a query will contain some +particular text. The text you specify can be a Perl-style regular expression, and +you can indicate that the response is to contain the text, or that it should match +the whole response.

    + +

    You can add an assertion to any Sampler. For example, you can +add an assertion to a HTTP Request that checks for the text, "&lt;/HTML&gt;". JMeter +will then check that the text is present in the HTTP response. If JMeter cannot find the +text, then it will mark this as a failed request.

    + +

    +Note that assertions apply to all samplers which are in its scope. +To restrict the assertion to a single sampler, add the assertion as a child of the sampler. +

    + +

    To view the assertion results, add an Assertion Listener to the Thread Group. +Failed Assertions will also show up in the Tree View and Table Listeners, +and will count towards the error %age for example in the Aggregate and Summary reports. +

    +
    + + +

    A configuration element works closely with a Sampler. Although it does not send requests +(except for ), it can add to or modify requests.

    + +

    A configuration element is accessible from only inside the tree branch where you place the element. +For example, if you place an HTTP Cookie Manager inside a Simple Logic Controller, the Cookie Manager will +only be accessible to HTTP Request Controllers you place inside the Simple Logic Controller (see figure 1). +The Cookie Manager is accessible to the HTTP requests "Web Page 1" and "Web Page 2", but not "Web Page 3".

    +

    Also, a configuration element inside a tree branch has higher precedence than the same element in a "parent" +branch. For example, we defined two HTTP Request Defaults elements, "Web Defaults 1" and "Web Defaults 2". +Since we placed "Web Defaults 1" inside a Loop Controller, only "Web Page 2" can access it. The other HTTP +requests will use "Web Defaults 2", since we placed it in the Thread Group (the "parent" of all other branches).

    + +
    Figure 1 - + Test Plan Showing Accessability of Configuration Elements
    + + +The Configuration element is different. +It is processed at the start of a test, no matter where it is placed. +For simplicity, it is suggested that the element is placed only at the start of a Thread Group. + +
    + + +

    A Pre-Processor executes some action prior to a Sampler Request being made. +If a Pre-Processor is attached to a Sampler element, then it will execute just prior to that sampler element running. +A Pre-Processor is most often used to modify the settings of a Sample Request just before it runs, or to update variables that aren't extracted from response text. +See the scoping rules for more details on when Pre-Processors are executed.

    +
    + + +

    A Post-Processor executes some action after a Sampler Request has been made. +If a Post-Processor is attached to a Sampler element, then it will execute just after that sampler element runs. +A Post-Processor is most often used to process the response data, often to extract values from it. +See the scoping rules for more details on when Post-Processors are executed.

    +
    + + +
      +
    1. Configuration elements
    2. +
    3. Pre-Processors
    4. +
    5. Timers
    6. +
    7. Sampler
    8. +
    9. Post-Processors (unless SampleResult is null)
    10. +
    11. Assertions (unless SampleResult is null)
    12. +
    13. Listeners (unless SampleResult is null)
    14. +
    + + +Please note that Timers, Assertions, Pre- and Post-Processors are only processed if there is a sampler to which they apply. +Logic Controllers and Samplers are processed in the order in which they appear in the tree. +Other test elements are processed according to the scope in which they are found, and the type of test element. +[Within a type, elements are processed in the order in which they appear in the tree]. + +

    +For example, in the following test plan: +

      +
    • Controller
    • +
        +
      • Post-Processor 1
      • +
      • Sampler 1
      • +
      • Sampler 2
      • +
      • Timer 1
      • +
      • Assertion 1
      • +
      • Pre-Processor 1
      • +
      • Timer 2
      • +
      • Post-Processor 2
      • +
      +
    +The order of execution would be: +
    +Pre-Processor 1
    +Timer 1
    +Timer 2
    +Sampler 1
    +Post-Processor 1
    +Post-Processor 2
    +Assertion 1
    +
    +Pre-Processor 1
    +Timer 1
    +Timer 2
    +Sampler 2
    +Post-Processor 1
    +Post-Processor 2
    +Assertion 1
    +
    +

    +
    + + +

    +The JMeter test tree contains elements that are both hierarchical and ordered. Some elements in the test trees are strictly hierarchical (Listeners, Config Elements, Post-Procesors, Pre-Processors, Assertions, Timers), and some are primarily ordered (controllers, samplers). When you create your test plan, you will create an ordered list of sample request (via Samplers) that represent a set of steps to be executed. These requests are often organized within controllers that are also ordered. Given the following test tree:

    +
    Example test tree
    +

    The order of requests will be, One, Two, Three, Four.

    +

    Some controllers affect the order of their subelements, and you can read about these specific controllers in the component reference.

    +

    Other elements are hierarchical. An Assertion, for instance, is hierarchical in the test tree. +If its parent is a request, then it is applied to that request. If its +parent is a Controller, then it affects all requests that are descendants of +that Controller. In the following test tree:

    +
    Hierarchy example
    +

    Assertion #1 is applied only to Request One, while Assertion #2 is applied to Requests Two and Three.

    +

    Another example, this time using Timers:

    +
    complex example
    +

    In this example, the requests are named to reflect the order in which they will be executed. Timer #1 will apply to Requests Two, Three, and Four (notice how order is irrelevant for hierarchical elements). Assertion #1 will apply only to Request Three. Timer #2 will affect all the requests.

    +

    Hopefully these examples make it clear how configuration (hierarchical) elements are applied. If you imagine each Request being passed up the tree branches, to its parent, then to its parent's parent, etc, and each time collecting all the configuration elements of that parent, then you will see how it works.

    + +The Configuration elements Header Manager, Cookie Manager and Authorization manager are +treated differently from the Configuration Default elements. +The settings from the Configuration Default elements are merged into a set of values that the Sampler has access to. +However, the settings from the Managers are not merged. +If more than one Manager is in the scope of a Sampler, +only one Manager is used, but there is currently no way to specify which is used. + +
    + + + + +

    +JMeter properties are defined in jmeter.properties (see Gettting Started - Configuring JMeter for more details). +

    +Properties are global to jmeter, and are mostly used to define some of the defaults JMeter uses. +For example the property remote_hosts defines the servers that JMeter will try to run remotely. +Properties can be referenced in test plans +- see Functions - read a property - +but cannot be used for thread-specific values. +

    +

    +JMeter variables are local to each thread. The values may be the same for each thread, or they may be different. +

    +If a variable is updated by a thread, only the thread copy of the variable is changed. +For example the Post-Processor +will set its variables according to the sample that its thread has read, and these can be used later +by the same thread. +For details of how to reference variables and functions, see Functions and Variables +

    +

    +Note that the values defined by the and the configuration element +are made available to the whole test plan at startup. +If the same variable is defined by multiple UDV elements, then the last one takes effect. +Once a thread has started, the initial set of variables is copied to each thread. +Other elements such as the + Pre-Processor or Post-Processor +may be used to redefine the same variables (or create new ones). These redefinitions only apply to the current thread. +

    +

    +The setProperty function can be used to define a JMeter property. +These are global to the test plan, so can be used to pass information between threads - should that be needed. +

    +Both variables and properties are case-sensitive. +
    + + +

    +Variables don't have to vary - they can be defined once, and if left alone, will not change value. +So you can use them as short-hand for expressions that appear frequently in a test plan. +Or for items which are constant during a run, but which may vary between runs. +For example, the name of a host, or the number of threads in a thread group. +

    +

    +When deciding how to structure a Test Plan, +make a note of which items are constant for the run, but which may change between runs. +Decide on some variable names for these - +perhaps use a naming convention such as prefixing them with C_ or K_ or using uppercase only +to distinguish them from variables that need to change during the test. +Also consider which items need to be local to a thread - +for example counters or values extracted with the Regular Expression Post-Processor. +You may wish to use a different naming convention for these. +

    +

    +For example, you might define the following on the Test Plan: +

    +HOST             www.example.com
    +THREADS          10
    +LOOPS            20
    +
    +You can refer to these in the test plan as ${HOST} ${THREADS} etc. +If you later want to change the host, just change the value of the HOST variable. +This works fine for small numbers of tests, but becomes tedious when testing lots of different combinations. +One solution is to use a property to define the value of the variables, for example: +
    +HOST             ${__P(host,www.example.com)}
    +THREADS          ${__P(threads,10)}
    +LOOPS            ${__P(loops,20)}
    +
    +You can then change some or all of the values on the command-line as follows: +
    +jmeter ... -Jhost=www3.example.org -Jloops=13
    +
    +

    +
    + +
    + + +
    diff --git a/ApacheJmeter/xdocs/velocity.properties b/ApacheJmeter/xdocs/velocity.properties new file mode 100644 index 0000000..6b6938e --- /dev/null +++ b/ApacheJmeter/xdocs/velocity.properties @@ -0,0 +1,18 @@ +# 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. + +resource.loader.1.resource.path = xdocs/stylesheets +# set true to debug vsl files +runtime.references.strict=true \ No newline at end of file