/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.slf4j.LoggerFactory;
import org.slf4j.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:<br>
* <br>
* LogFilter is a basic implementation of Filter interface. This implementation
* will keep a record of the filtered strings to avoid repeating the process
* unnecessarily.
* <p>
* 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.
* <p>
* Example of how to use it is provided in the main method. An example is
* provided below.
* <pre>
* 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);
* }
* }
* </pre>
*
* 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 = 241L;
private static final Logger log = LoggerFactory.getLogger(LogFilter.class);
// 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<Pattern> EXCPATTERNS = new ArrayList<>();
protected ArrayList<Pattern> 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)
*/
@Override
public void setReplaceExtension(String oldext, String newext) {
if (oldext != null && newext != null) {
this.CHANGEEXT = true;
if (!oldext.contains(".") && !newext.contains(".")) {
this.OLDEXT = "." + oldext;
this.NEWEXT = "." + newext;
} else {
this.OLDEXT = oldext;
this.NEWEXT = newext;
}
}
}
/**
* Give the filter a list of files to include
*
* @param filenames
* list of files to include
* @see org.apache.jmeter.protocol.http.util.accesslog.Filter#includeFiles(java.lang.String[])
*/
@Override
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
* list of files to exclude
* @see org.apache.jmeter.protocol.http.util.accesslog.Filter#excludeFiles(java.lang.String[])
*/
@Override
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
* list of regular expressions
* @see org.apache.jmeter.protocol.http.util.accesslog.Filter#includePattern(String[])
*/
@Override
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 (String includePattern : INCPTRN) {
this.INCPATTERNS.add(this.createPattern(includePattern));
}
}
}
/**
* 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
* list of regular expressions
*
* @see org.apache.jmeter.protocol.http.util.accesslog.Filter#excludePattern(String[])
*/
@Override
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 (String excludePattern : EXCPTRN) {
this.EXCPATTERNS.add(this.createPattern(excludePattern));
}
}
}
/**
* 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 exclusion 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 path to be tested
* @return <code>true</code> if entry should be excluded
*/
@Override
public boolean isFiltered(String path,TestElement el) {
if (this.FILEFILTER) {
return filterFile(path);
}
if (this.PTRNFILTER) {
return filterPattern(path);
}
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 the file to filter
* @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
* name of the file to tested (must not be <code>null</code>)
* @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 (String includeFile : this.INCFILE) {
if (text.contains(includeFile)) {
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
* name of the file to be tested (must not be null)
* @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 (String excludeFile : this.EXCFILE) {
if (text.contains(excludeFile)) {
exc = true;
this.USEFILE = false;
break;
}
}
return exc;
}
/**
* The current implementation 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 text to be checked
* @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 text to be checked
* @return <code>true</code> if text is included
*/
protected boolean incPattern(String text) {
this.USEFILE = false;
for (Pattern includePattern : this.INCPATTERNS) {
if (JMeterUtils.getMatcher().contains(text, includePattern)) {
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 text to be checked
* @return <code>true</code> if text is excluded
*/
protected boolean excPattern(String text) {
this.USEFILE = true;
boolean exc = false;
for (Pattern excludePattern : this.EXCPATTERNS) {
if (JMeterUtils.getMatcher().contains(text, excludePattern)) {
exc = true;
this.USEFILE = false;
break;
}
}
return exc;
}
/**
* Method uses indexOf to replace the old extension with the new extension.
* 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
* name of the file in which the extension should be replaced
* (must not be null)
* @return <code>true</code> if the extension could be replaced,
* <code>false</code> otherwise
*/
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)
*/
@Override
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
* string representation of the perl5 compatible regex pattern
* @return compiled Pattern, or <code>null</code> if no pattern could be
* compiled
*/
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}
*/
@Override
public void reset() {
}
}