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.functions;
020:
021: import java.io.BufferedReader;
022: import java.io.FileReader;
023: import java.io.IOException;
024: import java.io.Serializable;
025: import java.text.DecimalFormat;
026: import java.util.Collection;
027: import java.util.LinkedList;
028: import java.util.List;
029:
030: import org.apache.jmeter.engine.event.LoopIterationEvent;
031: import org.apache.jmeter.engine.util.CompoundVariable;
032: import org.apache.jmeter.samplers.SampleResult;
033: import org.apache.jmeter.samplers.Sampler;
034: import org.apache.jmeter.testelement.TestListener;
035: import org.apache.jmeter.threads.JMeterVariables;
036: import org.apache.jmeter.util.JMeterUtils;
037: import org.apache.jorphan.logging.LoggingManager;
038: import org.apache.jorphan.util.JMeterStopThreadException;
039: import org.apache.log.Logger;
040:
041: /**
042: * StringFromFile Function to read a String from a text file.
043: *
044: * Parameters:
045: * - file name
046: * - variable name (optional - defaults to StringFromFile_)
047: *
048: * Returns:
049: * - the next line from the file
050: * - or **ERR** if an error occurs
051: * - value is also saved in the variable for later re-use.
052: *
053: * Ensure that different variable names are used for each call to the function
054: *
055: *
056: * Notes:
057: * - JMeter instantiates a copy of each function for every reference in a
058: * Sampler or elsewhere; each instance will open its own copy of the the file
059: * - the file name is resolved at file (re-)open time
060: * - the output variable name is resolved every time the function is invoked
061: *
062: */
063: public class StringFromFile extends AbstractFunction implements
064: Serializable, TestListener {
065: private static final Logger log = LoggingManager
066: .getLoggerForClass();
067:
068: private static final long serialVersionUID = 1L;
069:
070: private static final List desc = new LinkedList();
071:
072: private static final String KEY = "__StringFromFile";//$NON-NLS-1$
073:
074: // Function name (only 1 _)
075:
076: static final String ERR_IND = "**ERR**";//$NON-NLS-1$
077:
078: static {
079: desc
080: .add(JMeterUtils
081: .getResString("string_from_file_file_name"));//$NON-NLS-1$
082: desc.add(JMeterUtils.getResString("function_name_param"));//$NON-NLS-1$
083: desc
084: .add(JMeterUtils
085: .getResString("string_from_file_seq_start"));//$NON-NLS-1$
086: desc
087: .add(JMeterUtils
088: .getResString("string_from_file_seq_final"));//$NON-NLS-1$
089: }
090:
091: private static final int MIN_PARAM_COUNT = 1;
092:
093: private static final int PARAM_NAME = 2;
094:
095: private static final int PARAM_START = 3;
096:
097: private static final int PARAM_END = 4;
098:
099: private static final int MAX_PARAM_COUNT = 4;
100:
101: transient private String myValue;
102:
103: transient private String myName;
104:
105: transient private Object[] values;
106:
107: transient private BufferedReader myBread = null; // Buffered reader
108:
109: transient private FileReader fis; // keep this round to close it
110:
111: transient private boolean firstTime = false; // should we try to open the
112: // file?
113:
114: transient private String fileName; // needed for error messages
115:
116: public StringFromFile() {
117: init();
118: if (log.isDebugEnabled()) {
119: log.debug("++++++++ Construct " + this );
120: }
121: }
122:
123: private void init() {
124: myValue = ERR_IND;
125: myName = "StringFromFile_";//$NON-NLS-1$
126: }
127:
128: private Object readResolve() {
129: init();
130: return this ;
131: }
132:
133: public Object clone() throws CloneNotSupportedException {
134: StringFromFile newReader = (StringFromFile) super .clone();
135: if (log.isDebugEnabled()) { // Skip expensive parameter creation ..
136: log
137: .debug(
138: this + "::StringFromFile.clone()", new Throwable("debug"));//$NON-NLS-1$
139: }
140:
141: return newReader;
142: }
143:
144: /*
145: * Warning: the file will generally be left open at the end of a test run.
146: * This is because functions don't (yet) have any way to find out when a
147: * test has ended ...
148: */
149: private void closeFile() {
150: if (myBread == null)
151: return;
152: String tn = Thread.currentThread().getName();
153: log.info(tn + " closing file " + fileName);//$NON-NLS-1$
154: try {
155: myBread.close();
156: fis.close();
157: } catch (IOException e) {
158: log.error("closeFile() error: " + e.toString());//$NON-NLS-1$
159: }
160: }
161:
162: private static final int COUNT_UNUSED = -2;
163:
164: private int myStart = COUNT_UNUSED;
165:
166: private int myCurrent = COUNT_UNUSED;
167:
168: private int myEnd = COUNT_UNUSED;
169:
170: private void openFile() {
171: String tn = Thread.currentThread().getName();
172: fileName = ((CompoundVariable) values[0]).execute();
173:
174: String start = "";
175: if (values.length >= PARAM_START) {
176: start = ((CompoundVariable) values[PARAM_START - 1])
177: .execute();
178: try {
179: myStart = Integer.valueOf(start).intValue();
180: } catch (NumberFormatException e) {
181: myStart = COUNT_UNUSED;// Don't process invalid numbers
182: }
183: }
184: // Have we used myCurrent yet?
185: // Set to 1 if start number is missing (to allow for end without start)
186: if (myCurrent == COUNT_UNUSED)
187: myCurrent = myStart == COUNT_UNUSED ? 1 : myStart;
188:
189: if (values.length >= PARAM_END) {
190: String tmp = ((CompoundVariable) values[PARAM_END - 1])
191: .execute();
192: try {
193: myEnd = Integer.valueOf(tmp).intValue();
194: } catch (NumberFormatException e) {
195: myEnd = COUNT_UNUSED;// Don't process invalid numbers
196: // (including "")
197: }
198:
199: }
200:
201: if (values.length >= PARAM_START) {
202: log
203: .info(tn
204: + " Start = " + myStart + " Current = " + myCurrent + " End = " + myEnd);//$NON-NLS-1$
205: if (myEnd != COUNT_UNUSED) {
206: if (myCurrent > myEnd) {
207: log
208: .info(tn
209: + " No more files to process, " + myCurrent + " > " + myEnd);//$NON-NLS-1$
210: myBread = null;
211: return;
212: }
213: }
214: /*
215: * DecimalFormat adds the number to the end of the format if there
216: * are no formatting characters, so we need a way to prevent this
217: * from messing up the file name.
218: *
219: */
220: if (myStart != COUNT_UNUSED) // Only try to format if there is a
221: // number
222: {
223: log.info(tn + " using format " + fileName);
224: try {
225: DecimalFormat myFormatter = new DecimalFormat(
226: fileName);
227: fileName = myFormatter.format(myCurrent);
228: } catch (NumberFormatException e) {
229: log.warn("Bad file name format ", e);
230: }
231: }
232: myCurrent++;// for next time
233: }
234:
235: log.info(tn + " opening file " + fileName);//$NON-NLS-1$
236: try {
237: fis = new FileReader(fileName);
238: myBread = new BufferedReader(fis);
239: } catch (Exception e) {
240: log.error("openFile() error: " + e.toString());//$NON-NLS-1$
241: myBread = null;
242: }
243: }
244:
245: /*
246: * (non-Javadoc)
247: *
248: * @see org.apache.jmeter.functions.Function#execute(SampleResult, Sampler)
249: */
250: public synchronized String execute(SampleResult previousResult,
251: Sampler currentSampler) throws InvalidVariableException {
252:
253: if (values.length >= PARAM_NAME) {
254: myName = ((CompoundVariable) values[PARAM_NAME - 1])
255: .execute();
256: }
257:
258: myValue = ERR_IND;
259:
260: /*
261: * To avoid re-opening the file repeatedly after an error, only try to
262: * open it in the first execute() call (It may be re=opened at EOF, but
263: * that will cause at most one failure.)
264: */
265: if (firstTime) {
266: openFile();
267: firstTime = false;
268: }
269:
270: if (null != myBread) { // Did we open the file?
271: try {
272: String line = myBread.readLine();
273: if (line == null) { // EOF, re-open file
274: String tn = Thread.currentThread().getName();
275: log.info(tn + " EOF on file " + fileName);//$NON-NLS-1$
276: closeFile();
277: openFile();
278: if (myBread != null) {
279: line = myBread.readLine();
280: } else {
281: line = ERR_IND;
282: if (myEnd != COUNT_UNUSED) {// Are we processing a file
283: // sequence?
284: log.info(tn + " Detected end of sequence.");
285: throw new JMeterStopThreadException(
286: "End of sequence");
287: }
288: }
289: }
290: myValue = line;
291: } catch (IOException e) {
292: String tn = Thread.currentThread().getName();
293: log.error(tn + " error reading file " + e.toString());//$NON-NLS-1$
294: }
295: } else { // File was not opened successfully
296: if (myEnd != COUNT_UNUSED) {// Are we processing a file sequence?
297: String tn = Thread.currentThread().getName();
298: log.info(tn + " Detected end of sequence.");
299: throw new JMeterStopThreadException("End of sequence");
300: }
301: }
302:
303: if (myName.length() > 0) {
304: JMeterVariables vars = getVariables();
305: vars.put(myName, myValue);
306: }
307:
308: if (log.isDebugEnabled()) {
309: String tn = Thread.currentThread().getName();
310: log.debug(tn + " name:" //$NON-NLS-1$
311: + myName + " value:" + myValue);//$NON-NLS-1$
312: }
313:
314: return myValue;
315:
316: }
317:
318: /*
319: * (non-Javadoc) Parameters: - file name - variable name (optional) - start
320: * index (optional) - end index or count (optional)
321: *
322: * @see org.apache.jmeter.functions.Function#setParameters(Collection)
323: */
324: public synchronized void setParameters(Collection parameters)
325: throws InvalidVariableException {
326:
327: log.debug(this + "::StringFromFile.setParameters()");//$NON-NLS-1$
328:
329: values = parameters.toArray();
330:
331: if ((values.length > MAX_PARAM_COUNT)
332: || (values.length < MIN_PARAM_COUNT)) {
333: throw new InvalidVariableException(
334: "Wrong number of parameters");//$NON-NLS-1$
335: }
336:
337: StringBuffer sb = new StringBuffer(40);
338: sb.append("setParameters(");//$NON-NLS-1$
339: for (int i = 0; i < values.length; i++) {
340: if (i > 0)
341: sb.append(",");
342: sb
343: .append(((CompoundVariable) values[i])
344: .getRawParameters());
345: }
346: sb.append(")");//$NON-NLS-1$
347: log.info(sb.toString());
348:
349: // N.B. setParameters is called before the test proper is started,
350: // and thus variables are not interpreted at this point
351: // So defer the file open until later to allow variable file names to be
352: // used.
353: firstTime = true;
354: }
355:
356: /*
357: * (non-Javadoc)
358: *
359: * @see org.apache.jmeter.functions.Function#getReferenceKey()
360: */
361: public String getReferenceKey() {
362: return KEY;
363: }
364:
365: /*
366: * (non-Javadoc)
367: *
368: * @see org.apache.jmeter.functions.Function#getArgumentDesc()
369: */
370: public List getArgumentDesc() {
371: return desc;
372: }
373:
374: public void testStarted() {
375: //
376: }
377:
378: public void testStarted(String host) {
379: //
380: }
381:
382: public void testEnded() {
383: this .testEnded(""); //$NON-NLS-1$
384: }
385:
386: public void testEnded(String host) {
387: closeFile();
388: }
389:
390: public void testIterationStart(LoopIterationEvent event) {
391: //
392: }
393: }
|