001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.jmeter.extractor;
020:
021: import java.io.Serializable;
022: import java.util.ArrayList;
023: import java.util.Iterator;
024: import java.util.LinkedList;
025: import java.util.List;
026:
027: import org.apache.jmeter.processor.PostProcessor;
028: import org.apache.jmeter.samplers.SampleResult;
029: import org.apache.jmeter.testelement.AbstractTestElement;
030: import org.apache.jmeter.testelement.property.IntegerProperty;
031: import org.apache.jmeter.threads.JMeterContext;
032: import org.apache.jmeter.threads.JMeterVariables;
033: import org.apache.jmeter.util.JMeterUtils;
034: import org.apache.jorphan.logging.LoggingManager;
035: import org.apache.log.Logger;
036: import org.apache.oro.text.MalformedCachePatternException;
037: import org.apache.oro.text.regex.MatchResult;
038: import org.apache.oro.text.regex.Pattern;
039: import org.apache.oro.text.regex.PatternMatcher;
040: import org.apache.oro.text.regex.PatternMatcherInput;
041: import org.apache.oro.text.regex.Perl5Compiler;
042: import org.apache.oro.text.regex.Perl5Matcher;
043: import org.apache.oro.text.regex.Util;
044:
045: // @see org.apache.jmeter.extractor.TestRegexExtractor for unit tests
046:
047: public class RegexExtractor extends AbstractTestElement implements
048: PostProcessor, Serializable {
049:
050: private static final Logger log = LoggingManager
051: .getLoggerForClass();
052:
053: // What to match against. N.B. do not change the string value or test plans will break!
054: private static final String MATCH_AGAINST = "RegexExtractor.useHeaders"; // $NON-NLS-1$
055: /*
056: * Permissible values:
057: * true - match against headers
058: * false or absent - match against body (this was the original default)
059: * URL - match against URL
060: * These are passed to the setUseField() method
061: *
062: * Do not change these values!
063: */
064: public static final String USE_HDRS = "true"; // $NON-NLS-1$
065: public static final String USE_BODY = "false"; // $NON-NLS-1$
066: public static final String USE_URL = "URL"; // $NON-NLS-1$
067: public static final String USE_CODE = "code"; // $NON-NLS-1$
068: public static final String USE_MESSAGE = "message"; // $NON-NLS-1$
069:
070: private static final String REGEX = "RegexExtractor.regex"; // $NON-NLS-1$
071:
072: private static final String REFNAME = "RegexExtractor.refname"; // $NON-NLS-1$
073:
074: private static final String MATCH_NUMBER = "RegexExtractor.match_number"; // $NON-NLS-1$
075:
076: private static final String DEFAULT = "RegexExtractor.default"; // $NON-NLS-1$
077:
078: private static final String TEMPLATE = "RegexExtractor.template"; // $NON-NLS-1$
079:
080: private static final String REF_MATCH_NR = "_matchNr"; // $NON-NLS-1$
081:
082: private static final String UNDERSCORE = "_"; // $NON-NLS-1$
083:
084: private Object[] template = null;
085:
086: /**
087: * Parses the response data using regular expressions and saving the results
088: * into variables for use later in the test.
089: *
090: * @see org.apache.jmeter.processor.PostProcessor#process()
091: */
092: public void process() {
093: initTemplate();
094: JMeterContext context = getThreadContext();
095: SampleResult previousResult = context.getPreviousResult();
096: if (previousResult == null) {
097: return;
098: }
099: log.debug("RegexExtractor processing result");
100:
101: // Fetch some variables
102: JMeterVariables vars = context.getVariables();
103: String refName = getRefName();
104: int matchNumber = getMatchNumber();
105:
106: final String defaultValue = getDefaultValue();
107: if (defaultValue.length() > 0) {// Only replace default if it is provided
108: vars.put(refName, defaultValue);
109: }
110:
111: Perl5Matcher matcher = JMeterUtils.getMatcher();
112: String inputString = useUrl() ? previousResult.getUrlAsString() // Bug 39707
113: : useHeaders() ? previousResult.getResponseHeaders()
114: : useCode() ? previousResult.getResponseCode() //Bug 43451
115: : useMessage() ? previousResult
116: .getResponseMessage() //Bug 43451
117: : previousResult
118: .getResponseDataAsString() // Bug 36898
119: ;
120: if (log.isDebugEnabled()) {
121: log.debug("Input = " + inputString);
122: }
123: PatternMatcherInput input = new PatternMatcherInput(inputString);
124: String regex = getRegex();
125: if (log.isDebugEnabled()) {
126: log.debug("Regex = " + regex);
127: }
128: try {
129: Pattern pattern = JMeterUtils.getPatternCache().getPattern(
130: regex, Perl5Compiler.READ_ONLY_MASK);
131: List matches = new ArrayList();
132: int x = 0;
133: boolean done = false;
134: do {
135: if (matcher.contains(input, pattern)) {
136: log.debug("RegexExtractor: Match found!");
137: matches.add(matcher.getMatch());
138: } else {
139: done = true;
140: }
141: x++;
142: } while (x != matchNumber && !done);
143:
144: try {
145: MatchResult match;
146: if (matchNumber >= 0) {// Original match behaviour
147: match = getCorrectMatch(matches, matchNumber);
148: if (match != null) {
149: vars.put(refName, generateResult(match));
150: saveGroups(vars, refName, match);
151: } else {
152: vars.remove(refName + "_g"); // $NON-NLS-1$
153: vars.remove(refName + "_g0"); // $NON-NLS-1$
154: vars.remove(refName + "_g1"); // $NON-NLS-1$
155: //TODO - remove other groups if present?
156: }
157: } else // < 0 means we save all the matches
158: {
159: int prevCount = 0;
160: String prevString = vars
161: .get(refName + REF_MATCH_NR);
162: if (prevString != null) {
163: try {
164: prevCount = Integer.parseInt(prevString);
165: } catch (NumberFormatException e1) {
166: log.warn("Could not parse " + prevString
167: + " " + e1);
168: }
169: }
170: vars.put(refName + REF_MATCH_NR, ""
171: + matches.size());// Save the count
172: for (int i = 1; i <= matches.size(); i++) {
173: match = getCorrectMatch(matches, i);
174: if (match != null) {
175: vars.put(refName + UNDERSCORE + i,
176: generateResult(match));
177: saveGroups(vars, refName + UNDERSCORE + i,
178: match);
179: }
180: }
181: for (int i = matches.size() + 1; i <= prevCount; i++) {
182: vars.remove(refName + UNDERSCORE + i);
183: // Remove known groups
184: vars.remove(refName + UNDERSCORE + i + "_g0"); // $NON-NLS-1$
185: vars.remove(refName + UNDERSCORE + i + "_g1"); // $NON-NLS-1$
186: // TODO remove other groups if present?
187: }
188: }
189: } catch (RuntimeException e) {
190: log.warn("Error while generating result");
191: }
192: } catch (MalformedCachePatternException e) {
193: log.warn("Error in pattern: " + regex);
194: }
195: }
196:
197: private void saveGroups(JMeterVariables vars, String basename,
198: MatchResult match) {
199: StringBuffer buf = new StringBuffer();
200: buf.append(basename);
201: buf.append("_g"); // $NON-NLS-1$
202: int pfxlen = buf.length();
203: //Note: match.groups() includes group 0
204: for (int x = 0; x < match.groups(); x++) {
205: buf.append(x);
206: vars.put(buf.toString(), match.group(x));
207: buf.setLength(pfxlen);
208: }
209: vars.put(buf.toString(), Integer.toString(match.groups() - 1));
210: }
211:
212: public Object clone() {
213: RegexExtractor cloned = (RegexExtractor) super .clone();
214: cloned.template = this .template;
215: return cloned;
216: }
217:
218: private String generateResult(MatchResult match) {
219: StringBuffer result = new StringBuffer();
220: for (int a = 0; a < template.length; a++) {
221: log.debug("RegexExtractor: Template piece #" + a + " = "
222: + template[a]);
223: if (template[a] instanceof String) {
224: result.append(template[a]);
225: } else {
226: result.append(match.group(((Integer) template[a])
227: .intValue()));
228: }
229: }
230: log.debug("Regex Extractor result = " + result.toString());
231: return result.toString();
232: }
233:
234: private void initTemplate() {
235: if (template != null) {
236: return;
237: }
238: List pieces = new ArrayList();
239: List combined = new LinkedList();
240: String rawTemplate = getTemplate();
241: PatternMatcher matcher = JMeterUtils.getMatcher();
242: Pattern templatePattern = JMeterUtils.getPatternCache()
243: .getPattern(
244: "\\$(\\d+)\\$" // $NON-NLS-1$
245: ,
246: Perl5Compiler.READ_ONLY_MASK
247: & Perl5Compiler.SINGLELINE_MASK);
248: log.debug("Pattern = " + templatePattern);
249: log.debug("template = " + rawTemplate);
250: Util.split(pieces, matcher, templatePattern, rawTemplate);
251: PatternMatcherInput input = new PatternMatcherInput(rawTemplate);
252: boolean startsWith = isFirstElementGroup(rawTemplate);
253: log.debug("template split into " + pieces.size()
254: + " pieces, starts with = " + startsWith);
255: if (startsWith) {
256: pieces.remove(0);// Remove initial empty entry
257: }
258: Iterator iter = pieces.iterator();
259: while (iter.hasNext()) {
260: boolean matchExists = matcher.contains(input,
261: templatePattern);
262: if (startsWith) {
263: if (matchExists) {
264: combined.add(new Integer(matcher.getMatch()
265: .group(1)));
266: }
267: combined.add(iter.next());
268: } else {
269: combined.add(iter.next());
270: if (matchExists) {
271: combined.add(new Integer(matcher.getMatch()
272: .group(1)));
273: }
274: }
275: }
276: if (matcher.contains(input, templatePattern)) {
277: log.debug("Template does end with template pattern");
278: combined.add(new Integer(matcher.getMatch().group(1)));
279: }
280: template = combined.toArray();
281: }
282:
283: private boolean isFirstElementGroup(String rawData) {
284: try {
285: Pattern pattern = JMeterUtils.getPatternCache().getPattern(
286: "^\\$\\d+\\$" // $NON-NLS-1$
287: ,
288: Perl5Compiler.READ_ONLY_MASK
289: & Perl5Compiler.SINGLELINE_MASK);
290: return (JMeterUtils.getMatcher())
291: .contains(rawData, pattern);
292: } catch (RuntimeException e) {
293: log.error("", e);
294: return false;
295: }
296: }
297:
298: /**
299: * Grab the appropriate result from the list.
300: *
301: * @param matches
302: * list of matches
303: * @param entry
304: * the entry number in the list
305: * @return MatchResult
306: */
307: private MatchResult getCorrectMatch(List matches, int entry) {
308: int matchSize = matches.size();
309:
310: if (matchSize <= 0 || entry > matchSize)
311: return null;
312:
313: if (entry == 0) // Random match
314: {
315: return (MatchResult) matches.get(JMeterUtils
316: .getRandomInt(matchSize));
317: }
318:
319: return (MatchResult) matches.get(entry - 1);
320: }
321:
322: public void setRegex(String regex) {
323: setProperty(REGEX, regex);
324: }
325:
326: public String getRegex() {
327: return getPropertyAsString(REGEX);
328: }
329:
330: public void setRefName(String refName) {
331: setProperty(REFNAME, refName);
332: }
333:
334: public String getRefName() {
335: return getPropertyAsString(REFNAME);
336: }
337:
338: /**
339: * Set which Match to use. This can be any positive number, indicating the
340: * exact match to use, or 0, which is interpreted as meaning random.
341: *
342: * @param matchNumber
343: */
344: public void setMatchNumber(int matchNumber) {
345: setProperty(new IntegerProperty(MATCH_NUMBER, matchNumber));
346: }
347:
348: public void setMatchNumber(String matchNumber) {
349: setProperty(MATCH_NUMBER, matchNumber);
350: }
351:
352: public int getMatchNumber() {
353: return getPropertyAsInt(MATCH_NUMBER);
354: }
355:
356: public String getMatchNumberAsString() {
357: return getPropertyAsString(MATCH_NUMBER);
358: }
359:
360: /**
361: * Sets the value of the variable if no matches are found
362: *
363: * @param defaultValue
364: */
365: public void setDefaultValue(String defaultValue) {
366: setProperty(DEFAULT, defaultValue);
367: }
368:
369: public String getDefaultValue() {
370: return getPropertyAsString(DEFAULT);
371: }
372:
373: public void setTemplate(String template) {
374: setProperty(TEMPLATE, template);
375: }
376:
377: public String getTemplate() {
378: return getPropertyAsString(TEMPLATE);
379: }
380:
381: public boolean useHeaders() {
382: return USE_HDRS
383: .equalsIgnoreCase(getPropertyAsString(MATCH_AGAINST));
384: }
385:
386: // Allow for property not yet being set (probably only applies to Test cases)
387: public boolean useBody() {
388: String prop = getPropertyAsString(MATCH_AGAINST);
389: return prop.length() == 0 || USE_BODY.equalsIgnoreCase(prop);// $NON-NLS-1$
390: }
391:
392: public boolean useUrl() {
393: String prop = getPropertyAsString(MATCH_AGAINST);
394: return USE_URL.equalsIgnoreCase(prop);
395: }
396:
397: public boolean useCode() {
398: String prop = getPropertyAsString(MATCH_AGAINST);
399: return USE_CODE.equalsIgnoreCase(prop);
400: }
401:
402: public boolean useMessage() {
403: String prop = getPropertyAsString(MATCH_AGAINST);
404: return USE_MESSAGE.equalsIgnoreCase(prop);
405: }
406:
407: public void setUseField(String actionCommand) {
408: setProperty(MATCH_AGAINST, actionCommand);
409: }
410: }
|