/*
* ALMA - Atacama Large Millimiter Array
* (c) European Southern Observatory, 2002
* Copyright by ESO (in the framework of the ALMA collaboration)
* and Cosylab 2002, All rights reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
package com.cosylab.logging.engine;
import java.util.Date;
import alma.ACSErrTypeCommon.wrappers.AcsJIllegalArgumentEx;
import alma.acs.logging.level.AcsLogLevelDefinition;
import com.cosylab.logging.engine.log.ILogEntry;
import com.cosylab.logging.engine.log.LogField;
import com.cosylab.logging.engine.log.LogTypeHelper;
/**
* A Filter is a class used for filtering LogEntries. It has the following characteristics:
* <UL><LI>Filterable field: A LogEntryXML's attribute to be used as a filterable criteria.</LI>
* <LI>Constraint type: A type of filter to apply, e.g. regular expression for Strings, lower bound for integers, etc</LI>
* <LI>Constraints: Specific constraints to be used for this filter. Number and class of constraints depends on
* Constraint type</LI>
* <LI>isLethal: A boolean attribute that specifies when the filter is applied. If the filter is lethal, it is applied
* at the engine level - the logs that do not get through are discarded immediately and can never reach the data holders.
* If the filter is not lethal, the GUI determines whether the logs can be seen or not. The GUI should make a clear
* distinction between the two types of filters.</LI>
* <LI>notFilter: A boolean that the filter has to be used as a not specification
* (i.e. log entries pass if DO NOT match the filter)</LI>
* </UL>
* <P>
* The constraint must match they type of the field to match as defined in {@link LogField#fieldClass}.
* <code>Filter</code> allows to accepts different value types for the following cases ({@link #checkAndConvertObjectType(LogField, Object)}):
* <UL>
* <LI><code>ENTRYTYPE</code>: can be a {@link LogTypeHelper} as well as a {@link Integer} (the latter is used internally for comparison)
* <LI><code>TIMESTAMP</code>: can be a {@link Date} as well as a {@link Long} (the latter is used internally for comparison)
* </UL>
*/
public abstract class Filter {
/**
* The possible comparison types
*
* @author acaproni
*
*/
public enum Constraint {
MINIMUM,
MAXIMUM,
MINMAX,
EXACT,
STRING_WILDCHAR;
/**
* Build a Constraint from its name as returned by {@link #name()}.
*
* @param name The name of the constraint
* @return The Constraint with the passed name
* @throws InvalidFilterConstraintException If the passed name does not represent any Constraint
*/
public static Constraint fromName(String name) throws InvalidFilterConstraintException {
if (name==null || name.isEmpty()) {
throw new IllegalArgumentException("Invalid name to build a Constraint");
}
for (Constraint c: Constraint.values()) {
if (c.name().equalsIgnoreCase(name.trim())) {
return c;
}
}
throw new InvalidFilterConstraintException("No Constraint with name "+name);
}
}
/**
* Filterable field
*/
protected final LogField field;
/**
* Constraint type
*/
protected Constraint constraint;
/**
* Lethalicity
*/
protected final boolean isLethal;
/**
* The boolean that specifies how to use the filter (NOT policy)
* The variable defaults to false (the filter is normal)
*/
protected final boolean notFilter;
/**
* Constructor.
*
* @param field The field
* @param constraint The constraint
* @param isLethal The activation state of the filter
* @param notFilter Usage of the filter (normal or not)
*/
protected Filter(
LogField field,
Constraint constraint,
boolean isLethal,
boolean notFilter) throws InvalidFilterConstraintException {
if (field==null) {
throw new InvalidFilterConstraintException("No log field specified in filter");
}
if (constraint==null) {
throw new InvalidFilterConstraintException("No constraint specified in filter");
}
this.field = field;
this.constraint = constraint;
this.isLethal = isLethal;
this.notFilter = notFilter;
}
/**
* Sometimes for optimization, it is possible to have a file of one class
* and an object of another type.
* For example for the timestamp it is possible to pass a {@link Date} or a {@link Long} and both
* should work upon conversion.
*
* @param field The filed used by this Filter for comparison
* @param obj The object to compare; can be <code>null</code>
* @return The object of the converted type
* @throws InvalidFilterConstraintException In case of mismatch
*/
protected Comparable checkAndConvertObjectType(LogField field, Comparable obj) throws InvalidFilterConstraintException {
if (field==null) {
throw new IllegalArgumentException("The passed LogFiled can't be null");
}
if (obj==null) {
return null;
}
switch (field) {
case ENTRYTYPE: {
if (obj instanceof LogTypeHelper) {
return obj;
} else if (obj instanceof Integer) {
LogTypeHelper ret;
try {
ret=LogTypeHelper.fromAcsCoreLevel(AcsLogLevelDefinition.fromInteger((Integer)obj));
} catch (AcsJIllegalArgumentEx ex) {
throw new InvalidFilterConstraintException("Invalid definition for LogEntryType: "+obj);
}
return ret;
} else {
throw new InvalidFilterConstraintException("Invalid class for ENTRYTYPE object "+obj.toString()+": "+obj.getClass().getName());
}
}
case TIMESTAMP: {
if (obj instanceof Date) {
return Long.valueOf(((Date)obj).getTime());
} else if (obj instanceof Long) {
return obj;
} else {
throw new InvalidFilterConstraintException("Invalid class for TIMESTAMP object "+obj.toString()+": "+obj.getClass().getName());
}
}
default: {
return obj;
}
}
}
/**
* The most important method of this class. Returns true if LogEntryXML
* passes through the filter and false otherwise.
*
* If this instance is a non-lethal filter and is called in lethal
* circumstances (at an engine level), this filter always returns true.
*
* If this instance is a lethal filter and is called in non-lethal
* circumstances (at a GUI level), this filter always returns true.
*/
public boolean applyTo(ILogEntry logEntry, boolean lethalCircumstances) {
if (lethalCircumstances != isLethal) {
return true;
}
boolean ret=applyTo(logEntry.getField(field));
return ret;
}
/**
* Apply the filter to the passed object.
*
* @param obj The object to apply the filter to
* @return <code>true</code> if the object matches the filter
*/
abstract protected boolean applyTo(Object obj);
/**
* Append to the buffer, the XML for the specific type of filter.
*
* @param buffer The buffer to append data into.
*/
protected abstract void appendSpecializedXML(StringBuffer buffer);
/**
* Build an XML representation of the filter.
* <P>
* The initial part of the XML of each filter is common to all the types of filters.
* The part depending on the specialized filter is added by {@link #appendSpecializedXML(StringBuffer)}.
*
* @return The XML representing the filter
*/
public String toXMLString() {
StringBuffer buffer = new StringBuffer("\t<FILTER type=\"");
if (constraint!=null) {
buffer.append(constraint.name());
} else {
buffer.append("UNDEFINED");
}
buffer.append("\">\n");
// LogField
buffer.append("\t\t<FIELD>" + field.getName() + "</FIELD>\n");
// IsLethal
buffer.append("\t\t<LETHAL>");
if (isLethal) {
buffer.append(1);
} else {
buffer.append(0);
}
buffer.append("\t\t</LETHAL>\n");
// ApplyAsNot
buffer.append("\t\t<APPLYNOT>");
if (notFilter) {
buffer.append(1);
} else {
buffer.append(0);
}
buffer.append("\t\t</APPLYNOT>\n");
appendSpecializedXML(buffer);
buffer.append("\t</FILTER>\n");
return buffer.toString();
}
/**
* Return the NOT policy
*
* @return True if the filter is used with not policy (i.e. The log entries
* that pass are those who do NOT satify the constraints)
*/
public boolean notPolicyApplyed() {
return this.notFilter;
}
/**
*
* @return The value of IsLethal
*/
public boolean getIsLethal() {
return this.isLethal;
}
/**
* Build a Filter object All the parameters are String objects. Before
* building the object, the value of each parameter is checked This method
* is too long (and boring) for my taste but it is very easy
*
* @param type The type of filter to build
* @param field The filed parameter fo Filter
* @param lethal The isLethal parameter of Filter
* @param not The applyAsNOT parameter of Filter
* @param min The minimum parameter of Filter
* @param minType The type of minimum
* @param max The max parameter of Filter
* @param maxType The type of max
* @param exact The exact parameter of Filter
* @param exactType The type of exact
* @param wildChar The regularExpression parameter of Filter
* @return The Filter object built or null if an error occurred decoding the
* parameters
* @throws Exception in case of error building the filter
*/
public static Filter buildFilter(Constraint type, LogField field, String lethal, String not,
String min, String minType, String max, String maxType,
String exact, String exactType, String wildChar) throws Exception {
// Trim all the strings
if (lethal != null) {
lethal = lethal.trim();
}
if (not != null) {
not = not.trim();
}
if (min != null) {
min = min.trim();
}
if (minType != null) {
minType = minType.trim();
}
if (max != null) {
max = max.trim();
}
if (maxType != null) {
maxType = maxType.trim();
}
if (exact != null) {
exact = exact.trim();
}
if (exactType != null) {
exactType = exactType.trim();
}
if (wildChar != null) {
wildChar = wildChar.trim();
}
// Read the int from field
if (field == null) {
throw new IllegalArgumentException("Parameter field can't be null");
}
// Translate lethal into boolean
int temp;
if (lethal == null) {
throw new IllegalArgumentException("Parameter lethal can't be null");
}
temp = Integer.parseInt(lethal);
boolean isLethal = (temp == 1);
// Translate not into boolean
if (not == null) {
throw new IllegalArgumentException("Parameter not can't be null");
}
temp = Integer.parseInt(not);
boolean notPolicy = (temp == 1);
// If wildChar is defined then min, max and exact should not
if (wildChar != null && (min != null || max != null || exact != null)) {
throw new IllegalArgumentException(
"Ambiguous parameters: wildChar, min, max, exact");
}
// If wild char is not defined then at least one between min, max and
// exat must be not null
if (wildChar == null && min == null && max == null && exact == null) {
throw new IllegalArgumentException(
"Ambiguous null params: wildChar, min, max, exact");
}
// If exact is defined then min and max should not
if (exact != null && (min != null || max != null)) {
throw new IllegalArgumentException(
"Ambiguous parameters: exact, min, max");
}
// For min, max and exact the type must be specified
if (exact != null && exactType == null) {
throw new IllegalArgumentException("Exact parameter can't be null");
}
if (min != null && minType == null) {
throw new IllegalArgumentException("Min - minType parameters wrong");
}
if (max != null && maxType == null) {
throw new IllegalArgumentException("Max - maxType parameters wrong");
}
// If both min and max are specified they must have the same type
if (minType != null && maxType != null) {
if (minType.compareTo(maxType) != 0) {
throw new IllegalArgumentException("minType- maxType mismatch");
}
}
//
// Finally build the filter
//
if (type==Constraint.STRING_WILDCHAR) {
return new RegExpFilter(field, isLethal, wildChar, notPolicy);
} else if (type==Constraint.EXACT) {
if (exact!=null && !exact.isEmpty() && exactType!=null && !exactType.isEmpty()) {
if (exactType.equals(Integer.class.getName())) {
Integer integer;
try {
integer = new Integer(exact);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Wrong int parameter "
+ exact);
}
return new ExactFilter(field, isLethal, integer, notPolicy);
} else if (exactType.equals(Long.class.getName())) {
Long date = null;
try {
date = Long.decode(exact);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Wrong date parameter "+ exact);
}
return new ExactFilter(field, isLethal, date, notPolicy);
} else if (exactType.equals(String.class.getName())) {
return new ExactFilter(field, isLethal, exact, notPolicy);
} else if (exactType.equals(LogTypeHelper.class.getName())) {
LogTypeHelper logType=LogTypeHelper.fromLogTypeDescription(exact);
return new ExactFilter(field, isLethal, logType, notPolicy);
} else {
// Unrecognized type
throw new IllegalArgumentException("Unrecognized type "+exactType);
}
} else {
throw new IllegalArgumentException("Value and type can't be null/empty to build a ExactFilter");
}
} else if (type==Constraint.MINMAX || type==Constraint.MINIMUM || type==Constraint.MAXIMUM) {
if (minType == null) {
minType = maxType;
} else {
maxType = minType;
}
if (minType.equals(String.class.getName())) {
return new MinMaxFilter(field, isLethal, min, max, notPolicy);
} else if (minType.equals(Long.class.getName())) {
Long minDate = null;
Long maxDate = null;
try {
if (min != null) {
minDate = Long.decode(min);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Wrong min date parameter "
+ min);
}
try {
if (max != null) {
maxDate = Long.decode(max);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Wrong max date parameter "
+ max);
}
return new MinMaxFilter(field, isLethal, minDate, maxDate, notPolicy);
} else if (minType.equals(Integer.class.getName())) {
Integer minInt = null;
Integer maxInt = null;
try {
if (min != null) {
minInt = new Integer(min);
}
if (max != null) {
maxInt = new Integer(max);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid min/max " + min
+ "/" + max);
}
return new MinMaxFilter(field, isLethal, minInt, maxInt, notPolicy);
} else if (minType.equals(LogTypeHelper.class.getName())) {
LogTypeHelper minLogType = null;
LogTypeHelper maxLogType = null;
try {
if (min != null) {
minLogType= LogTypeHelper.fromLogTypeDescription(min);
}
if (max != null) {
maxLogType= LogTypeHelper.fromLogTypeDescription(max);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid min/max " + min
+ "/" + max);
}
return new MinMaxFilter(field, isLethal, minLogType, maxLogType, notPolicy);
} else {
throw new IllegalArgumentException("Min/Max values/type can't be null/empty to build a MinMaxFilter");
}
} else {
throw new Exception("Error building a filter");
}
}
/**
* Return the type of the filter
*
* @return The type of the filter
*/
public LogField getField() {
return field;
}
/**
* @return the constraint
*/
public Constraint getConstraint() {
return constraint;
}
}