0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018:
0019: package org.apache.jmeter.protocol.http.proxy;
0020:
0021: import java.util.Collection;
0022: import java.util.Enumeration;
0023: import java.util.HashSet;
0024: import java.util.Iterator;
0025: import java.util.LinkedList;
0026: import java.util.List;
0027:
0028: import org.apache.jmeter.assertions.ResponseAssertion;
0029: import org.apache.jmeter.assertions.gui.AssertionGui;
0030: import org.apache.jmeter.config.Arguments;
0031: import org.apache.jmeter.config.ConfigElement;
0032: import org.apache.jmeter.config.ConfigTestElement;
0033: import org.apache.jmeter.control.GenericController;
0034: import org.apache.jmeter.control.gui.LogicControllerGui;
0035: import org.apache.jmeter.engine.util.ValueReplacer;
0036: import org.apache.jmeter.exceptions.IllegalUserActionException;
0037: import org.apache.jmeter.functions.InvalidVariableException;
0038: import org.apache.jmeter.gui.GuiPackage;
0039: import org.apache.jmeter.gui.tree.JMeterTreeModel;
0040: import org.apache.jmeter.gui.tree.JMeterTreeNode;
0041: import org.apache.jmeter.protocol.http.control.HeaderManager;
0042: import org.apache.jmeter.protocol.http.control.RecordingController;
0043: import org.apache.jmeter.protocol.http.gui.HeaderPanel;
0044: import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase;
0045: import org.apache.jmeter.samplers.SampleEvent;
0046: import org.apache.jmeter.samplers.SampleListener;
0047: import org.apache.jmeter.samplers.SampleResult;
0048: import org.apache.jmeter.testelement.TestElement;
0049: import org.apache.jmeter.testelement.TestListener;
0050: import org.apache.jmeter.testelement.TestPlan;
0051: import org.apache.jmeter.testelement.WorkBench;
0052: import org.apache.jmeter.testelement.property.BooleanProperty;
0053: import org.apache.jmeter.testelement.property.CollectionProperty;
0054: import org.apache.jmeter.testelement.property.IntegerProperty;
0055: import org.apache.jmeter.testelement.property.JMeterProperty;
0056: import org.apache.jmeter.testelement.property.PropertyIterator;
0057: import org.apache.jmeter.testelement.property.StringProperty;
0058: import org.apache.jmeter.threads.ThreadGroup;
0059: import org.apache.jmeter.timers.Timer;
0060: import org.apache.jmeter.util.JMeterUtils;
0061: import org.apache.jorphan.logging.LoggingManager;
0062: import org.apache.log.Logger;
0063: import org.apache.oro.text.MalformedCachePatternException;
0064: import org.apache.oro.text.regex.Pattern;
0065: import org.apache.oro.text.regex.Perl5Compiler;
0066:
0067: //For unit tests, @see TestProxyControl
0068:
0069: /**
0070: * Class handles storing of generated samples, etc
0071: */
0072: public class ProxyControl extends GenericController {
0073:
0074: private static final Logger log = LoggingManager
0075: .getLoggerForClass();
0076:
0077: private static final String ASSERTION_GUI = AssertionGui.class
0078: .getName();
0079:
0080: private static final String LOGIC_CONTROLLER_GUI = LogicControllerGui.class
0081: .getName();
0082:
0083: private static final String HEADER_PANEL = HeaderPanel.class
0084: .getName();
0085:
0086: private transient Daemon server;
0087:
0088: public static final int DEFAULT_PORT = 8080;
0089:
0090: // and as a string
0091: public static final String DEFAULT_PORT_S = Integer
0092: .toString(DEFAULT_PORT);// Used by GUI
0093:
0094: //+ JMX file attributes
0095: private static final String PORT = "ProxyControlGui.port"; // $NON-NLS-1$
0096:
0097: private static final String EXCLUDE_LIST = "ProxyControlGui.exclude_list"; // $NON-NLS-1$
0098:
0099: private static final String INCLUDE_LIST = "ProxyControlGui.include_list"; // $NON-NLS-1$
0100:
0101: private static final String CAPTURE_HTTP_HEADERS = "ProxyControlGui.capture_http_headers"; // $NON-NLS-1$
0102:
0103: private static final String ADD_ASSERTIONS = "ProxyControlGui.add_assertion"; // $NON-NLS-1$
0104:
0105: private static final String GROUPING_MODE = "ProxyControlGui.grouping_mode"; // $NON-NLS-1$
0106:
0107: private static final String SAMPLER_TYPE_NAME = "ProxyControlGui.sampler_type_name"; // $NON-NLS-1$
0108:
0109: private static final String SAMPLER_REDIRECT_AUTOMATICALLY = "ProxyControlGui.sampler_redirect_automatically"; // $NON-NLS-1$
0110:
0111: private static final String SAMPLER_FOLLOW_REDIRECTS = "ProxyControlGui.sampler_follow_redirects"; // $NON-NLS-1$
0112:
0113: private static final String USE_KEEPALIVE = "ProxyControlGui.use_keepalive"; // $NON-NLS-1$
0114:
0115: private static final String SAMPLER_DOWNLOAD_IMAGES = "ProxyControlGui.sampler_download_images"; // $NON-NLS-1$
0116:
0117: private static final String REGEX_MATCH = "ProxyControlGui.regex_match"; // $NON-NLS-1$
0118:
0119: private static final String HTTPS_SPOOF = "ProxyControlGui.https_spoof"; // $NON-NLS-1$
0120:
0121: private static final String HTTPS_SPOOF_MATCH = "ProxyControlGui.https_spoof_match"; // $NON-NLS-1$
0122:
0123: private static final String CONTENT_TYPE_EXCLUDE = "ProxyControlGui.content_type_exclude"; // $NON-NLS-1$
0124:
0125: private static final String CONTENT_TYPE_INCLUDE = "ProxyControlGui.content_type_include"; // $NON-NLS-1$
0126: //- JMX file attributes
0127:
0128: public static final int GROUPING_NO_GROUPS = 0;
0129:
0130: public static final int GROUPING_ADD_SEPARATORS = 1;
0131:
0132: public static final int GROUPING_IN_CONTROLLERS = 2;
0133:
0134: public static final int GROUPING_STORE_FIRST_ONLY = 3;
0135:
0136: // Must agree with the order of entries in the drop-down
0137: // created in ProxyControlGui.createHTTPSamplerPanel()
0138: public static final int SAMPLER_TYPE_HTTP_SAMPLER = 0;
0139: public static final int SAMPLER_TYPE_HTTP_SAMPLER2 = 1;
0140:
0141: private long lastTime = 0;// When was the last sample seen?
0142:
0143: private static final long sampleGap = JMeterUtils.getPropDefault(
0144: "proxy.pause", 1000); // $NON-NLS-1$
0145: // Detect if user has pressed a new link
0146:
0147: private boolean addAssertions;
0148:
0149: private int groupingMode;
0150:
0151: private boolean samplerRedirectAutomatically;
0152:
0153: private boolean samplerFollowRedirects;
0154:
0155: private boolean useKeepAlive;
0156:
0157: private boolean samplerDownloadImages;
0158:
0159: private boolean regexMatch = false;// Should we match using regexes?
0160:
0161: /**
0162: * Tree node where the samples should be stored.
0163: * <p>
0164: * This property is not persistent.
0165: */
0166: private JMeterTreeNode target;
0167:
0168: public ProxyControl() {
0169: setPort(DEFAULT_PORT);
0170: setExcludeList(new HashSet());
0171: setIncludeList(new HashSet());
0172: setCaptureHttpHeaders(true); // maintain original behaviour
0173: }
0174:
0175: public void setPort(int port) {
0176: this .setProperty(new IntegerProperty(PORT, port));
0177: }
0178:
0179: public void setPort(String port) {
0180: setProperty(PORT, port);
0181: }
0182:
0183: public void setCaptureHttpHeaders(boolean capture) {
0184: setProperty(new BooleanProperty(CAPTURE_HTTP_HEADERS, capture));
0185: }
0186:
0187: public void setGroupingMode(int grouping) {
0188: this .groupingMode = grouping;
0189: setProperty(new IntegerProperty(GROUPING_MODE, grouping));
0190: }
0191:
0192: public void setAssertions(boolean b) {
0193: addAssertions = b;
0194: setProperty(new BooleanProperty(ADD_ASSERTIONS, b));
0195: }
0196:
0197: public void setSamplerTypeName(int samplerTypeName) {
0198: setProperty(new IntegerProperty(SAMPLER_TYPE_NAME,
0199: samplerTypeName));
0200: }
0201:
0202: public void setSamplerRedirectAutomatically(boolean b) {
0203: samplerRedirectAutomatically = b;
0204: setProperty(new BooleanProperty(SAMPLER_REDIRECT_AUTOMATICALLY,
0205: b));
0206: }
0207:
0208: public void setSamplerFollowRedirects(boolean b) {
0209: samplerFollowRedirects = b;
0210: setProperty(new BooleanProperty(SAMPLER_FOLLOW_REDIRECTS, b));
0211: }
0212:
0213: /**
0214: * @param b
0215: */
0216: public void setUseKeepAlive(boolean b) {
0217: useKeepAlive = b;
0218: setProperty(new BooleanProperty(USE_KEEPALIVE, b));
0219: }
0220:
0221: public void setSamplerDownloadImages(boolean b) {
0222: samplerDownloadImages = b;
0223: setProperty(new BooleanProperty(SAMPLER_DOWNLOAD_IMAGES, b));
0224: }
0225:
0226: public void setIncludeList(Collection list) {
0227: setProperty(new CollectionProperty(INCLUDE_LIST, new HashSet(
0228: list)));
0229: }
0230:
0231: public void setExcludeList(Collection list) {
0232: setProperty(new CollectionProperty(EXCLUDE_LIST, new HashSet(
0233: list)));
0234: }
0235:
0236: /**
0237: * @param b
0238: */
0239: public void setRegexMatch(boolean b) {
0240: regexMatch = b;
0241: setProperty(new BooleanProperty(REGEX_MATCH, b));
0242: }
0243:
0244: public void setHttpsSpoof(boolean b) {
0245: setProperty(new BooleanProperty(HTTPS_SPOOF, b));
0246: }
0247:
0248: public void setHttpsSpoofMatch(String s) {
0249: setProperty(new StringProperty(HTTPS_SPOOF_MATCH, s));
0250: }
0251:
0252: public void setContentTypeExclude(String contentTypeExclude) {
0253: setProperty(new StringProperty(CONTENT_TYPE_EXCLUDE,
0254: contentTypeExclude));
0255: }
0256:
0257: public void setContentTypeInclude(String contentTypeInclude) {
0258: setProperty(new StringProperty(CONTENT_TYPE_INCLUDE,
0259: contentTypeInclude));
0260: }
0261:
0262: public String getClassLabel() {
0263: return JMeterUtils.getResString("proxy_title"); // $NON-NLS-1$
0264: }
0265:
0266: public boolean getAssertions() {
0267: return getPropertyAsBoolean(ADD_ASSERTIONS);
0268: }
0269:
0270: public int getGroupingMode() {
0271: return getPropertyAsInt(GROUPING_MODE);
0272: }
0273:
0274: public int getPort() {
0275: return getPropertyAsInt(PORT);
0276: }
0277:
0278: public String getPortString() {
0279: return getPropertyAsString(PORT);
0280: }
0281:
0282: public int getDefaultPort() {
0283: return DEFAULT_PORT;
0284: }
0285:
0286: public boolean getCaptureHttpHeaders() {
0287: return getPropertyAsBoolean(CAPTURE_HTTP_HEADERS);
0288: }
0289:
0290: public int getSamplerTypeName() {
0291: return getPropertyAsInt(SAMPLER_TYPE_NAME);
0292: }
0293:
0294: public boolean getSamplerRedirectAutomatically() {
0295: return getPropertyAsBoolean(SAMPLER_REDIRECT_AUTOMATICALLY,
0296: false);
0297: }
0298:
0299: public boolean getSamplerFollowRedirects() {
0300: return getPropertyAsBoolean(SAMPLER_FOLLOW_REDIRECTS, true);
0301: }
0302:
0303: public boolean getUseKeepalive() {
0304: return getPropertyAsBoolean(USE_KEEPALIVE, true);
0305: }
0306:
0307: public boolean getSamplerDownloadImages() {
0308: return getPropertyAsBoolean(SAMPLER_DOWNLOAD_IMAGES, false);
0309: }
0310:
0311: public boolean getRegexMatch() {
0312: return getPropertyAsBoolean(REGEX_MATCH, false);
0313: }
0314:
0315: public boolean getHttpsSpoof() {
0316: return getPropertyAsBoolean(HTTPS_SPOOF, false);
0317: }
0318:
0319: public String getHttpsSpoofMatch() {
0320: return getPropertyAsString(HTTPS_SPOOF_MATCH, "");
0321: }
0322:
0323: public String getContentTypeExclude() {
0324: return getPropertyAsString(CONTENT_TYPE_EXCLUDE);
0325: }
0326:
0327: public String getContentTypeInclude() {
0328: return getPropertyAsString(CONTENT_TYPE_INCLUDE);
0329: }
0330:
0331: public Class getGuiClass() {
0332: return org.apache.jmeter.protocol.http.proxy.gui.ProxyControlGui.class;
0333: }
0334:
0335: public void addConfigElement(ConfigElement config) {
0336: }
0337:
0338: public void startProxy() {
0339: notifyTestListenersOfStart();
0340: server = new Daemon(getPort(), this );
0341: server.start();
0342: }
0343:
0344: public void addExcludedPattern(String pattern) {
0345: getExcludePatterns().addItem(pattern);
0346: }
0347:
0348: public CollectionProperty getExcludePatterns() {
0349: return (CollectionProperty) getProperty(EXCLUDE_LIST);
0350: }
0351:
0352: public void addIncludedPattern(String pattern) {
0353: getIncludePatterns().addItem(pattern);
0354: }
0355:
0356: public CollectionProperty getIncludePatterns() {
0357: return (CollectionProperty) getProperty(INCLUDE_LIST);
0358: }
0359:
0360: public void clearExcludedPatterns() {
0361: getExcludePatterns().clear();
0362: }
0363:
0364: public void clearIncludedPatterns() {
0365: getIncludePatterns().clear();
0366: }
0367:
0368: /**
0369: * @return the target controller node
0370: */
0371: public JMeterTreeNode getTarget() {
0372: return target;
0373: }
0374:
0375: /**
0376: * Sets the target node where the samples generated by the proxy have to be
0377: * stored.
0378: */
0379: public void setTarget(JMeterTreeNode target) {
0380: this .target = target;
0381: }
0382:
0383: /**
0384: * Receives the recorded sampler from the proxy server for placing in the
0385: * test tree. param serverResponse to be added to allow saving of the
0386: * server's response while recording. A future consideration.
0387: */
0388: public synchronized void deliverSampler(HTTPSamplerBase sampler,
0389: TestElement[] subConfigs, SampleResult result) {
0390: if (filterContentType(result) && filterUrl(sampler)) {
0391: JMeterTreeNode myTarget = findTargetControllerNode();
0392: Collection defaultConfigurations = findApplicableElements(
0393: myTarget, ConfigTestElement.class, false);
0394: Collection userDefinedVariables = findApplicableElements(
0395: myTarget, Arguments.class, true);
0396:
0397: removeValuesFromSampler(sampler, defaultConfigurations);
0398: replaceValues(sampler, subConfigs, userDefinedVariables);
0399: sampler.setAutoRedirects(samplerRedirectAutomatically);
0400: sampler.setFollowRedirects(samplerFollowRedirects);
0401: sampler.setUseKeepAlive(useKeepAlive);
0402: sampler.setImageParser(samplerDownloadImages);
0403:
0404: placeSampler(sampler, subConfigs, myTarget);
0405:
0406: notifySampleListeners(new SampleEvent(result, "WorkBench")); // TODO - is this the correct threadgroup name?
0407: } else {
0408: if (log.isDebugEnabled()) {
0409: log
0410: .debug("Sample excluded based on url or content-type: "
0411: + result.getUrlAsString()
0412: + " - "
0413: + result.getContentType());
0414: }
0415: result.setSampleLabel("[" + result.getSampleLabel() + "]");
0416: notifySampleListeners(new SampleEvent(result, "WorkBench")); // TODO - is this the correct threadgroup name?
0417: }
0418: }
0419:
0420: public void stopProxy() {
0421: if (server != null) {
0422: server.stopServer();
0423: try {
0424: server.join(1000); // wait for server to stop
0425: } catch (InterruptedException e) {
0426: }
0427: notifyTestListenersOfEnd();
0428: server = null;
0429: }
0430: }
0431:
0432: // Package protected to allow test case access
0433: boolean filterUrl(HTTPSamplerBase sampler) {
0434: String domain = sampler.getDomain();
0435: if (domain == null || domain.length() == 0) {
0436: return false;
0437: }
0438:
0439: String url = generateMatchUrl(sampler);
0440: CollectionProperty includePatterns = getIncludePatterns();
0441: if (includePatterns.size() > 0) {
0442: if (!matchesPatterns(url, includePatterns)) {
0443: return false;
0444: }
0445: }
0446:
0447: CollectionProperty excludePatterns = getExcludePatterns();
0448: if (excludePatterns.size() > 0) {
0449: if (matchesPatterns(url, excludePatterns)) {
0450: return false;
0451: }
0452: }
0453:
0454: return true;
0455: }
0456:
0457: // Package protected to allow test case access
0458: /**
0459: * Filter the response based on the content type.
0460: * If no include nor exclude filter is specified, the result will be included
0461: *
0462: * @param result the sample result to check
0463: */
0464: boolean filterContentType(SampleResult result) {
0465: String includeExp = getContentTypeInclude();
0466: String excludeExp = getContentTypeExclude();
0467: // If no expressions are specified, we let the sample pass
0468: if ((includeExp == null || includeExp.length() == 0)
0469: && (excludeExp == null || excludeExp.length() == 0)) {
0470: return true;
0471: }
0472:
0473: // Check that we have a content type
0474: String sampleContentType = result.getContentType();
0475: if (sampleContentType == null
0476: || sampleContentType.length() == 0) {
0477: if (log.isDebugEnabled()) {
0478: log.debug("No Content-type found for : "
0479: + result.getUrlAsString());
0480: }
0481:
0482: return true;
0483: }
0484:
0485: if (log.isDebugEnabled()) {
0486: log.debug("Content-type to filter : " + sampleContentType);
0487: }
0488: // Check if the include pattern is mathed
0489: if (includeExp != null && includeExp.length() > 0) {
0490: if (log.isDebugEnabled()) {
0491: log.debug("Include expression : " + includeExp);
0492: }
0493:
0494: Pattern pattern = null;
0495: try {
0496: pattern = JMeterUtils.getPatternCache().getPattern(
0497: includeExp,
0498: Perl5Compiler.READ_ONLY_MASK
0499: | Perl5Compiler.SINGLELINE_MASK);
0500: if (!JMeterUtils.getMatcher().contains(
0501: sampleContentType, pattern)) {
0502: return false;
0503: }
0504: } catch (MalformedCachePatternException e) {
0505: log.warn("Skipped invalid content include pattern: "
0506: + includeExp, e);
0507: }
0508: }
0509:
0510: // Check if the exclude pattern is mathed
0511: if (excludeExp != null && excludeExp.length() > 0) {
0512: if (log.isDebugEnabled()) {
0513: log.debug("Exclude expression : " + excludeExp);
0514: }
0515:
0516: Pattern pattern = null;
0517: try {
0518: pattern = JMeterUtils.getPatternCache().getPattern(
0519: excludeExp,
0520: Perl5Compiler.READ_ONLY_MASK
0521: | Perl5Compiler.SINGLELINE_MASK);
0522: if (JMeterUtils.getMatcher().contains(
0523: sampleContentType, pattern)) {
0524: return false;
0525: }
0526: } catch (MalformedCachePatternException e) {
0527: log.warn("Skipped invalid content exclude pattern: "
0528: + includeExp, e);
0529: }
0530: }
0531:
0532: return true;
0533: }
0534:
0535: /*
0536: * Helper method to add a Response Assertion
0537: */
0538: private void addAssertion(JMeterTreeModel model, JMeterTreeNode node)
0539: throws IllegalUserActionException {
0540: ResponseAssertion ra = new ResponseAssertion();
0541: ra.setProperty(TestElement.GUI_CLASS, ASSERTION_GUI);
0542: ra.setName("Check response");
0543: ra.setTestFieldResponseData();
0544: model.addComponent(ra, node);
0545: }
0546:
0547: /*
0548: * Helper method to add a Divider
0549: */
0550: private void addDivider(JMeterTreeModel model, JMeterTreeNode node)
0551: throws IllegalUserActionException {
0552: GenericController sc = new GenericController();
0553: sc.setProperty(TestElement.GUI_CLASS, LOGIC_CONTROLLER_GUI);
0554: sc.setName("-------------------"); // $NON-NLS-1$
0555: model.addComponent(sc, node);
0556: }
0557:
0558: /**
0559: * Helper method to add a Simple Controller to contain the samplers.
0560: *
0561: * @param model
0562: * Test component tree model
0563: * @param node
0564: * Node in the tree where we will add the Controller
0565: * @param name
0566: * A name for the Controller
0567: */
0568: private void addSimpleController(JMeterTreeModel model,
0569: JMeterTreeNode node, String name)
0570: throws IllegalUserActionException {
0571: GenericController sc = new GenericController();
0572: sc.setProperty(TestElement.GUI_CLASS, LOGIC_CONTROLLER_GUI);
0573: sc.setName(name);
0574: model.addComponent(sc, node);
0575: }
0576:
0577: /**
0578: * Helpler method to replicate any timers found within the Proxy Controller
0579: * into the provided sampler, while replacing any occurences of string _T_
0580: * in the timer's configuration with the provided deltaT.
0581: *
0582: * @param model
0583: * Test component tree model
0584: * @param node
0585: * Sampler node in where we will add the timers
0586: * @param deltaT
0587: * Time interval from the previous request
0588: */
0589: private void addTimers(JMeterTreeModel model, JMeterTreeNode node,
0590: long deltaT) {
0591: TestPlan variables = new TestPlan();
0592: variables.addParameter("T", Long.toString(deltaT)); // $NON-NLS-1$
0593: ValueReplacer replacer = new ValueReplacer(variables);
0594: JMeterTreeNode mySelf = model.getNodeOf(this );
0595: Enumeration children = mySelf.children();
0596: while (children.hasMoreElements()) {
0597: JMeterTreeNode templateNode = (JMeterTreeNode) children
0598: .nextElement();
0599: if (templateNode.isEnabled()) {
0600: TestElement template = templateNode.getTestElement();
0601: if (template instanceof Timer) {
0602: TestElement timer = (TestElement) template.clone();
0603: try {
0604: replacer.undoReverseReplace(timer);
0605: model.addComponent(timer, node);
0606: } catch (InvalidVariableException e) {
0607: // Not 100% sure, but I believe this can't happen, so
0608: // I'll log and throw an error:
0609: log.error("Program error", e);
0610: throw new Error(e);
0611: } catch (IllegalUserActionException e) {
0612: // Not 100% sure, but I believe this can't happen, so
0613: // I'll log and throw an error:
0614: log.error("Program error", e);
0615: throw new Error(e);
0616: }
0617: }
0618: }
0619: }
0620: }
0621:
0622: /**
0623: * Finds the first enabled node of a given type in the tree.
0624: *
0625: * @param type
0626: * class of the node to be found
0627: *
0628: * @return the first node of the given type in the test component tree, or
0629: * <code>null</code> if none was found.
0630: */
0631: private JMeterTreeNode findFirstNodeOfType(Class type) {
0632: JMeterTreeModel treeModel = GuiPackage.getInstance()
0633: .getTreeModel();
0634: List nodes = treeModel.getNodesOfType(type);
0635: Iterator iter = nodes.iterator();
0636: while (iter.hasNext()) {
0637: JMeterTreeNode node = (JMeterTreeNode) iter.next();
0638: if (node.isEnabled()) {
0639: return node;
0640: }
0641: }
0642: return null;
0643: }
0644:
0645: /**
0646: * Finds the controller where samplers have to be stored, that is:
0647: * <ul>
0648: * <li>The controller specified by the <code>target</code> property.
0649: * <li>If none was specified, the first RecordingController in the tree.
0650: * <li>If none is found, the first ThreadGroup in the tree.
0651: * <li>If none is found, the Workspace.
0652: * </ul>
0653: *
0654: * @return the tree node for the controller where the proxy must store the
0655: * generated samplers.
0656: */
0657: private JMeterTreeNode findTargetControllerNode() {
0658: JMeterTreeNode myTarget = getTarget();
0659: if (myTarget != null)
0660: return myTarget;
0661: myTarget = findFirstNodeOfType(RecordingController.class);
0662: if (myTarget != null)
0663: return myTarget;
0664: myTarget = findFirstNodeOfType(ThreadGroup.class);
0665: if (myTarget != null)
0666: return myTarget;
0667: myTarget = findFirstNodeOfType(WorkBench.class);
0668: if (myTarget != null)
0669: return myTarget;
0670: log.error("Program error: proxy recording target not found.");
0671: return null;
0672: }
0673:
0674: /**
0675: * Finds all configuration objects of the given class applicable to the
0676: * recorded samplers, that is:
0677: * <ul>
0678: * <li>All such elements directly within the HTTP Proxy Server (these have
0679: * the highest priority).
0680: * <li>All such elements directly within the target controller (higher
0681: * priority) or directly within any containing controller (lower priority),
0682: * including the Test Plan itself (lowest priority).
0683: * </ul>
0684: *
0685: * @param myTarget
0686: * tree node for the recording target controller.
0687: * @param myClass
0688: * Class of the elements to be found.
0689: * @param ascending
0690: * true if returned elements should be ordered in ascending
0691: * priority, false if they should be in descending priority.
0692: *
0693: * @return a collection of applicable objects of the given class.
0694: */
0695: private Collection findApplicableElements(JMeterTreeNode myTarget,
0696: Class myClass, boolean ascending) {
0697: JMeterTreeModel treeModel = GuiPackage.getInstance()
0698: .getTreeModel();
0699: LinkedList elements = new LinkedList();
0700:
0701: // Look for elements directly within the HTTP proxy:
0702: Enumeration kids = treeModel.getNodeOf(this ).children();
0703: while (kids.hasMoreElements()) {
0704: JMeterTreeNode subNode = (JMeterTreeNode) kids
0705: .nextElement();
0706: if (subNode.isEnabled()) {
0707: TestElement element = (TestElement) subNode
0708: .getUserObject();
0709: if (myClass.isInstance(element)) {
0710: if (ascending)
0711: elements.addFirst(element);
0712: else
0713: elements.add(element);
0714: }
0715: }
0716: }
0717:
0718: // Look for arguments elements in the target controller or higher up:
0719: for (JMeterTreeNode controller = myTarget; controller != null; controller = (JMeterTreeNode) controller
0720: .getParent()) {
0721: kids = controller.children();
0722: while (kids.hasMoreElements()) {
0723: JMeterTreeNode subNode = (JMeterTreeNode) kids
0724: .nextElement();
0725: if (subNode.isEnabled()) {
0726: TestElement element = (TestElement) subNode
0727: .getUserObject();
0728: if (myClass.isInstance(element)) {
0729: log
0730: .debug("Applicable: "
0731: + element
0732: .getPropertyAsString(TestElement.NAME));
0733: if (ascending)
0734: elements.addFirst(element);
0735: else
0736: elements.add(element);
0737: }
0738:
0739: // Special case for the TestPlan's Arguments sub-element:
0740: if (element instanceof TestPlan) {
0741: TestPlan tp = (TestPlan) element;
0742: Arguments args = tp.getArguments();
0743: if (myClass.isInstance(args)) {
0744: if (ascending)
0745: elements.addFirst(args);
0746: else
0747: elements.add(args);
0748: }
0749: }
0750: }
0751: }
0752: }
0753:
0754: return elements;
0755: }
0756:
0757: private void placeSampler(HTTPSamplerBase sampler,
0758: TestElement[] subConfigs, JMeterTreeNode myTarget) {
0759: try {
0760: JMeterTreeModel treeModel = GuiPackage.getInstance()
0761: .getTreeModel();
0762:
0763: boolean firstInBatch = false;
0764: long now = System.currentTimeMillis();
0765: long deltaT = now - lastTime;
0766: if (deltaT > sampleGap) {
0767: if (!myTarget.isLeaf()
0768: && groupingMode == GROUPING_ADD_SEPARATORS) {
0769: addDivider(treeModel, myTarget);
0770: }
0771: if (groupingMode == GROUPING_IN_CONTROLLERS) {
0772: addSimpleController(treeModel, myTarget, sampler
0773: .getName());
0774: }
0775: firstInBatch = true;// Remember this was first in its batch
0776: }
0777: if (lastTime == 0)
0778: deltaT = 0; // Decent value for timers
0779: lastTime = now;
0780:
0781: if (groupingMode == GROUPING_STORE_FIRST_ONLY) {
0782: if (!firstInBatch)
0783: return; // Huh! don't store this one!
0784:
0785: // If we're not storing subsequent samplers, we'll need the
0786: // first sampler to do all the work...:
0787: sampler.setFollowRedirects(true);
0788: sampler.setImageParser(true);
0789: }
0790:
0791: if (groupingMode == GROUPING_IN_CONTROLLERS) {
0792: // Find the last controller in the target to store the
0793: // sampler there:
0794: for (int i = myTarget.getChildCount() - 1; i >= 0; i--) {
0795: JMeterTreeNode c = (JMeterTreeNode) myTarget
0796: .getChildAt(i);
0797: if (c.getTestElement() instanceof GenericController) {
0798: myTarget = c;
0799: break;
0800: }
0801: }
0802: }
0803:
0804: JMeterTreeNode newNode = treeModel.addComponent(sampler,
0805: myTarget);
0806:
0807: if (firstInBatch) {
0808: if (addAssertions) {
0809: addAssertion(treeModel, newNode);
0810: }
0811: addTimers(treeModel, newNode, deltaT);
0812: firstInBatch = false;
0813: }
0814:
0815: for (int i = 0; subConfigs != null && i < subConfigs.length; i++) {
0816: if (subConfigs[i] instanceof HeaderManager) {
0817: subConfigs[i].setProperty(TestElement.GUI_CLASS,
0818: HEADER_PANEL);
0819: treeModel.addComponent(subConfigs[i], newNode);
0820: }
0821: }
0822: } catch (IllegalUserActionException e) {
0823: JMeterUtils.reportErrorToUser(e.getMessage());
0824: }
0825: }
0826:
0827: /**
0828: * Remove from the sampler all values which match the one provided by the
0829: * first configuration in the given collection which provides a value for
0830: * that property.
0831: *
0832: * @param sampler
0833: * Sampler to remove values from.
0834: * @param configurations
0835: * ConfigTestElements in descending priority.
0836: */
0837: private void removeValuesFromSampler(HTTPSamplerBase sampler,
0838: Collection configurations) {
0839: for (PropertyIterator props = sampler.propertyIterator(); props
0840: .hasNext();) {
0841: JMeterProperty prop = props.next();
0842: String name = prop.getName();
0843: String value = prop.getStringValue();
0844:
0845: // There's a few properties which are excluded from this processing:
0846: if (name.equals(TestElement.ENABLED)
0847: || name.equals(TestElement.GUI_CLASS)
0848: || name.equals(TestElement.NAME)
0849: || name.equals(TestElement.TEST_CLASS)) {
0850: continue; // go on with next property.
0851: }
0852:
0853: for (Iterator configs = configurations.iterator(); configs
0854: .hasNext();) {
0855: ConfigTestElement config = (ConfigTestElement) configs
0856: .next();
0857:
0858: String configValue = config.getPropertyAsString(name);
0859:
0860: if (configValue != null && configValue.length() > 0) {
0861: if (configValue.equals(value))
0862: sampler.setProperty(name, ""); // $NON-NLS-1$
0863: // Property was found in a config element. Whether or not
0864: // it matched the value in the sampler, we're done with
0865: // this property -- don't look at lower-priority configs:
0866: break;
0867: }
0868: }
0869: }
0870: }
0871:
0872: private String generateMatchUrl(HTTPSamplerBase sampler) {
0873: StringBuffer buf = new StringBuffer(sampler.getDomain());
0874: buf.append(':'); // $NON-NLS-1$
0875: buf.append(sampler.getPort());
0876: buf.append(sampler.getPath());
0877: if (sampler.getQueryString().length() > 0) {
0878: buf.append('?'); // $NON-NLS-1$
0879: buf.append(sampler.getQueryString());
0880: }
0881: return buf.toString();
0882: }
0883:
0884: private boolean matchesPatterns(String url,
0885: CollectionProperty patterns) {
0886: PropertyIterator iter = patterns.iterator();
0887: while (iter.hasNext()) {
0888: String item = iter.next().getStringValue();
0889: Pattern pattern = null;
0890: try {
0891: pattern = JMeterUtils.getPatternCache().getPattern(
0892: item,
0893: Perl5Compiler.READ_ONLY_MASK
0894: | Perl5Compiler.SINGLELINE_MASK);
0895: if (JMeterUtils.getMatcher().matches(url, pattern)) {
0896: return true;
0897: }
0898: } catch (MalformedCachePatternException e) {
0899: log.warn("Skipped invalid pattern: " + item, e);
0900: }
0901: }
0902: return false;
0903: }
0904:
0905: /**
0906: * Scan all test elements passed in for values matching the value of any of
0907: * the variables in any of the variable-holding elements in the collection.
0908: *
0909: * @param sampler
0910: * A TestElement to replace values on
0911: * @param configs
0912: * More TestElements to replace values on
0913: * @param variables
0914: * Collection of Arguments to use to do the replacement, ordered
0915: * by ascending priority.
0916: */
0917: private void replaceValues(TestElement sampler,
0918: TestElement[] configs, Collection variables) {
0919: // Build the replacer from all the variables in the collection:
0920: ValueReplacer replacer = new ValueReplacer();
0921: for (Iterator vars = variables.iterator(); vars.hasNext();) {
0922: replacer.addVariables(((Arguments) vars.next())
0923: .getArgumentsAsMap());
0924: }
0925:
0926: try {
0927: replacer.reverseReplace(sampler, regexMatch);
0928: for (int i = 0; i < configs.length; i++) {
0929: if (configs[i] != null) {
0930: replacer.reverseReplace(configs[i], regexMatch);
0931: }
0932:
0933: }
0934: } catch (InvalidVariableException e) {
0935: log.warn(
0936: "Invalid variables included for replacement into recorded "
0937: + "sample", e);
0938: }
0939: }
0940:
0941: /**
0942: * This will notify sample listeners directly within the Proxy of the
0943: * sampling that just occured -- so that we have a means to record the
0944: * server's responses as we go.
0945: *
0946: * @param event
0947: * sampling event to be delivered
0948: */
0949: private void notifySampleListeners(SampleEvent event) {
0950: JMeterTreeModel treeModel = GuiPackage.getInstance()
0951: .getTreeModel();
0952: JMeterTreeNode myNode = treeModel.getNodeOf(this );
0953: Enumeration kids = myNode.children();
0954: while (kids.hasMoreElements()) {
0955: JMeterTreeNode subNode = (JMeterTreeNode) kids
0956: .nextElement();
0957: if (subNode.isEnabled()) {
0958: TestElement testElement = subNode.getTestElement();
0959: if (testElement instanceof SampleListener) {
0960: ((SampleListener) testElement)
0961: .sampleOccurred(event);
0962: }
0963: }
0964: }
0965: }
0966:
0967: /**
0968: * This will notify test listeners directly within the Proxy that the 'test'
0969: * (here meaning the proxy recording) has started.
0970: */
0971: private void notifyTestListenersOfStart() {
0972: JMeterTreeModel treeModel = GuiPackage.getInstance()
0973: .getTreeModel();
0974: JMeterTreeNode myNode = treeModel.getNodeOf(this );
0975: Enumeration kids = myNode.children();
0976: while (kids.hasMoreElements()) {
0977: JMeterTreeNode subNode = (JMeterTreeNode) kids
0978: .nextElement();
0979: if (subNode.isEnabled()) {
0980: TestElement testElement = subNode.getTestElement();
0981: if (testElement instanceof TestListener) {
0982: ((TestListener) testElement).testStarted();
0983: }
0984: }
0985: }
0986: }
0987:
0988: /**
0989: * This will notify test listeners directly within the Proxy that the 'test'
0990: * (here meaning the proxy recording) has ended.
0991: */
0992: private void notifyTestListenersOfEnd() {
0993: JMeterTreeModel treeModel = GuiPackage.getInstance()
0994: .getTreeModel();
0995: JMeterTreeNode myNode = treeModel.getNodeOf(this );
0996: Enumeration kids = myNode.children();
0997: while (kids.hasMoreElements()) {
0998: JMeterTreeNode subNode = (JMeterTreeNode) kids
0999: .nextElement();
1000: if (subNode.isEnabled()) {
1001: TestElement testElement = subNode.getTestElement();
1002: if (testElement instanceof TestListener) {
1003: ((TestListener) testElement).testEnded();
1004: }
1005: }
1006: }
1007: }
1008:
1009: public boolean canRemove() {
1010: return null == server;
1011: }
1012: }
|