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.protocol.http.util.accesslog;
020:
021: import java.io.Serializable;
022: import java.util.ArrayList;
023:
024: import org.apache.jmeter.testelement.TestElement;
025: import org.apache.jmeter.util.JMeterUtils;
026: import org.apache.oro.text.MalformedCachePatternException;
027: import org.apache.oro.text.regex.Pattern;
028: import org.apache.oro.text.regex.Perl5Compiler;
029:
030: // For JUnit tests, @see TestLogFilter
031:
032: /**
033: * Description:<br>
034: * <br>
035: * LogFilter is a basic implementation of Filter interface. This implementation
036: * will keep a record of the filtered strings to avoid repeating the process
037: * unnecessarily.
038: * <p>
039: * The current implementation supports replacing the file extension. The reason
040: * for supporting this is from first hand experience porting an existing website
041: * to Tomcat + JSP. Later on we may want to provide the ability to replace the
042: * whole filename. If the need materializes, we can add it later.
043: * <p>
044: * Example of how to use it is provided in the main method. An example is
045: * provided below.
046: * <p>
047: *
048: * <pre>
049: * testf = new LogFilter();
050: * String[] incl = { "hello.html", "index.html", "/index.jsp" };
051: * String[] thefiles = { "/test/hello.jsp", "/test/one/hello.html", "hello.jsp", "hello.htm", "/test/open.jsp",
052: * "/test/open.html", "/index.jsp", "/index.jhtml", "newindex.jsp", "oldindex.jsp", "oldindex1.jsp",
053: * "oldindex2.jsp", "oldindex3.jsp", "oldindex4.jsp", "oldindex5.jsp", "oldindex6.jsp", "/test/index.htm" };
054: * testf.excludeFiles(incl);
055: * System.out.println(" ------------ exclude test -------------");
056: * for (int idx = 0; idx < thefiles.length; idx++) {
057: * boolean fl = testf.isFiltered(thefiles[idx]);
058: * String line = testf.filter(thefiles[idx]);
059: * if (line != null) {
060: * System.out.println("the file: " + line);
061: * }
062: * }
063: * </pre>
064: *
065: * As a general note. Both isFiltered and filter() have to be called. Calling
066: * either one will not produce the desired result. isFiltered(string) will tell
067: * you if a string should be filtered. The second step is to filter the string,
068: * which will return null if it is filtered and replace any part of the string
069: * that should be replaced.
070: * <p>
071: *
072: * @version $Revision: 571988 $ last updated $Date: 2007-09-02 15:19:10 +0100 (Sun, 02 Sep 2007) $ Created
073: * on: Jun 26, 2003<br>
074: */
075:
076: public class LogFilter implements Filter, Serializable {
077:
078: /** protected members used by class to filter * */
079: protected boolean CHANGEEXT = false;
080:
081: protected String OLDEXT = null;
082:
083: protected String NEWEXT = null;
084:
085: protected String[] INCFILE = null;
086:
087: protected String[] EXCFILE = null;
088:
089: protected boolean FILEFILTER = false;
090:
091: protected boolean USEFILE = true;
092:
093: protected String[] INCPTRN = null;
094:
095: protected String[] EXCPTRN = null;
096:
097: protected boolean PTRNFILTER = false;
098:
099: protected ArrayList EXCPATTERNS = new ArrayList();
100:
101: protected ArrayList INCPATTERNS = new ArrayList();
102:
103: protected String NEWFILE = null;
104:
105: /**
106: * The default constructor is empty
107: */
108: public LogFilter() {
109: super ();
110: }
111:
112: /**
113: * The method will replace the file extension with the new one. You can
114: * either provide the extension without the period ".", or with. The method
115: * will check for period and add it if it isn't present.
116: *
117: * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#setReplaceExtension(java.lang.String,
118: * java.lang.String)
119: */
120: public void setReplaceExtension(String oldext, String newext) {
121: if (oldext != null && newext != null) {
122: this .CHANGEEXT = true;
123: if (oldext.indexOf(".") < 0 && newext.indexOf(".") < 0) {
124: this .OLDEXT = "." + oldext;
125: this .NEWEXT = "." + newext;
126: } else {
127: this .OLDEXT = oldext;
128: this .NEWEXT = newext;
129: }
130: }
131: }
132:
133: /**
134: * Give the filter a list of files to include
135: *
136: * @param filenames
137: * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#includeFiles(java.lang.String[])
138: */
139: public void includeFiles(String[] filenames) {
140: if (filenames != null && filenames.length > 0) {
141: INCFILE = filenames;
142: this .FILEFILTER = true;
143: }
144: }
145:
146: /**
147: * Give the filter a list of files to exclude
148: *
149: * @param filenames
150: * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#excludeFiles(java.lang.String[])
151: */
152: public void excludeFiles(String[] filenames) {
153: if (filenames != null && filenames.length > 0) {
154: EXCFILE = filenames;
155: this .FILEFILTER = true;
156: }
157: }
158:
159: /**
160: * Give the filter a set of regular expressions to filter with for
161: * inclusion. This method hasn't been fully implemented and test yet. The
162: * implementation is not complete.
163: *
164: * @param regexp
165: * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#includePattern(String[])
166: */
167: public void includePattern(String[] regexp) {
168: if (regexp != null && regexp.length > 0) {
169: INCPTRN = regexp;
170: this .PTRNFILTER = true;
171: // now we create the compiled pattern and
172: // add it to the arraylist
173: for (int idx = 0; idx < INCPTRN.length; idx++) {
174: this .INCPATTERNS.add(this .createPattern(INCPTRN[idx]));
175: }
176: }
177: }
178:
179: /**
180: * Give the filter a set of regular expressions to filter with for
181: * exclusion. This method hasn't been fully implemented and test yet. The
182: * implementation is not complete.
183: *
184: * @param regexp
185: *
186: * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#excludePattern(String[])
187: */
188: public void excludePattern(String[] regexp) {
189: if (regexp != null && regexp.length > 0) {
190: EXCPTRN = regexp;
191: this .PTRNFILTER = true;
192: // now we create the compiled pattern and
193: // add it to the arraylist
194: for (int idx = 0; idx < EXCPTRN.length; idx++) {
195: this .EXCPATTERNS.add(this .createPattern(EXCPTRN[idx]));
196: }
197: }
198: }
199:
200: /**
201: * In the case of log filtering the important thing is whether the log entry
202: * should be used. Therefore, the method will only return true if the entry
203: * should be used. Since the interface defines both inclusion and exclusion,
204: * that means by default inclusion filtering assumes all entries are
205: * excluded unless it matches. In the case of exlusion filtering, it assumes
206: * all entries are included unless it matches, which means it should be
207: * excluded.
208: *
209: * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#isFiltered(String, TestElement)
210: * @param path
211: * @return boolean
212: */
213: public boolean isFiltered(String path, TestElement el) {
214: // we do a quick check to see if any
215: // filters are set. If not we just
216: // return false to be efficient.
217: if (this .FILEFILTER || this .PTRNFILTER || this .CHANGEEXT) {
218: if (this .FILEFILTER) {
219: return filterFile(path);
220: } else if (this .PTRNFILTER) {
221: return filterPattern(path);
222: } else {
223: return false;
224: }
225: } else {
226: return false;
227: }
228: }
229:
230: /**
231: * Filter the file. The implementation performs the exclusion first before
232: * the inclusion. This means if a file name is in both string arrays, the
233: * exclusion will take priority. Depending on how users expect this to work,
234: * we may want to change the priority so that inclusion is performed first
235: * and exclusion second. Another possible alternative is to perform both
236: * inclusion and exclusion. Doing so would make the most sense if the method
237: * throws an exception and tells the user the same filename is in both the
238: * include and exclude array.
239: *
240: * @param file
241: * @return boolean
242: */
243: protected boolean filterFile(String file) {
244: // double check this logic make sure it
245: // makes sense
246: if (this .EXCFILE != null) {
247: return excFile(file);
248: } else if (this .INCFILE != null) {
249: return !incFile(file);
250: }
251: return false;
252: }
253:
254: /**
255: * Method implements the logic for filtering file name inclusion. The method
256: * iterates through the array and uses indexOf. Once it finds a match, it
257: * won't bother with the rest of the filenames in the array.
258: *
259: * @param text
260: * @return boolean include
261: */
262: public boolean incFile(String text) {
263: // inclusion filter assumes most of
264: // the files are not wanted, therefore
265: // usefile is set to false unless it
266: // matches.
267: this .USEFILE = false;
268: for (int idx = 0; idx < this .INCFILE.length; idx++) {
269: if (text.indexOf(this .INCFILE[idx]) > -1) {
270: this .USEFILE = true;
271: break;
272: }
273: }
274: return this .USEFILE;
275: }
276:
277: /**
278: * Method implements the logic for filtering file name exclusion. The method
279: * iterates through the array and uses indexOf. Once it finds a match, it
280: * won't bother with the rest of the filenames in the array.
281: *
282: * @param text
283: * @return boolean exclude
284: */
285: public boolean excFile(String text) {
286: // exclusion filter assumes most of
287: // the files are used, therefore
288: // usefile is set to true, unless
289: // it matches.
290: this .USEFILE = true;
291: boolean exc = false;
292: for (int idx = 0; idx < this .EXCFILE.length; idx++) {
293: if (text.indexOf(this .EXCFILE[idx]) > -1) {
294: exc = true;
295: this .USEFILE = false;
296: break;
297: }
298: }
299: return exc;
300: }
301:
302: /**
303: * The current implemenation assumes the user has checked the regular
304: * expressions so that they don't cancel each other. The basic assumption is
305: * the method will return true if the text should be filtered. If not, it
306: * will return false, which means it should not be filtered.
307: *
308: * @param text
309: * @return boolean
310: */
311: protected boolean filterPattern(String text) {
312: if (this .INCPTRN != null) {
313: return !incPattern(text);
314: } else if (this .EXCPTRN != null) {
315: return excPattern(text);
316: }
317: return false;
318: }
319:
320: /**
321: * By default, the method assumes the entry is not included, unless it
322: * matches. In that case, it will return true.
323: *
324: * @param text
325: * @return true if text is included
326: */
327: protected boolean incPattern(String text) {
328: this .USEFILE = false;
329: for (int idx = 0; idx < this .INCPATTERNS.size(); idx++) {
330: if (JMeterUtils.getMatcher().contains(text,
331: (Pattern) this .INCPATTERNS.get(idx))) {
332: this .USEFILE = true;
333: break;
334: }
335: }
336: return this .USEFILE;
337: }
338:
339: /**
340: * The method assumes by default the text is not excluded. If the text
341: * matches the pattern, it will then return true.
342: *
343: * @param text
344: * @return true if text is excluded
345: */
346: protected boolean excPattern(String text) {
347: this .USEFILE = true;
348: boolean exc = false;
349: for (int idx = 0; idx < this .EXCPATTERNS.size(); idx++) {
350: if (JMeterUtils.getMatcher().contains(text,
351: (Pattern) this .EXCPATTERNS.get(idx))) {
352: exc = true;
353: this .USEFILE = false;
354: break;
355: }
356: }
357: return exc;
358: }
359:
360: /**
361: * Method uses indexOf to replace the old extension with the new extesion.
362: * It might be good to use regular expression, but for now this is a simple
363: * method. The method isn't designed to replace multiple instances of the
364: * text, since that isn't how file extensions work. If the string contains
365: * more than one instance of the old extension, only the first instance will
366: * be replaced.
367: *
368: * @param text
369: * @return boolean
370: */
371: public boolean replaceExtension(String text) {
372: int pt = text.indexOf(this .OLDEXT);
373: if (pt > -1) {
374: int extsize = this .OLDEXT.length();
375: this .NEWFILE = text.substring(0, pt) + this .NEWEXT
376: + text.substring(pt + extsize);
377: return true;
378: } else {
379: return false;
380: }
381: }
382:
383: /**
384: * The current implementation checks the boolean if the text should be used
385: * or not. isFilter( string) has to be called first.
386: *
387: * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#filter(java.lang.String)
388: */
389: public String filter(String text) {
390: if (this .CHANGEEXT) {
391: if (replaceExtension(text)) {
392: return this .NEWFILE;
393: } else {
394: return text;
395: }
396: } else if (this .USEFILE) {
397: return text;
398: } else {
399: return null;
400: }
401: }
402:
403: /**
404: * create a new pattern object from the string.
405: *
406: * @param pattern
407: * @return Pattern
408: */
409: public Pattern createPattern(String pattern) {
410: try {
411: return JMeterUtils.getPatternCache().getPattern(
412: pattern,
413: Perl5Compiler.READ_ONLY_MASK
414: | Perl5Compiler.SINGLELINE_MASK);
415: } catch (MalformedCachePatternException exception) {
416: exception.printStackTrace();
417: return null;
418: }
419: }
420:
421: /*
422: * (non-Javadoc)
423: *
424: * @see org.apache.jmeter.protocol.http.util.accesslog.Filter#reset()
425: */
426: public void reset() {
427:
428: }
429: }
|