0001: /*
0002:
0003: This software is OSI Certified Open Source Software.
0004: OSI Certified is a certification mark of the Open Source Initiative.
0005:
0006: The license (Mozilla version 1.0) can be read at the MMBase site.
0007: See http://www.MMBase.org/license
0008:
0009: */
0010: package org.mmbase.cache;
0011:
0012: import java.lang.reflect.*;
0013: import java.util.*;
0014:
0015: import org.mmbase.bridge.Node;
0016: import org.mmbase.bridge.implementation.BasicQuery;
0017: import org.mmbase.core.CoreField;
0018: import org.mmbase.core.event.*;
0019: import org.mmbase.datatypes.DataType;
0020: import org.mmbase.module.core.*;
0021: import org.mmbase.util.LinkMap;
0022: import org.mmbase.storage.search.*;
0023: import org.mmbase.storage.search.implementation.*;
0024: import org.mmbase.storage.search.implementation.database.BasicSqlHandler;
0025: import org.mmbase.util.Casting;
0026: import org.mmbase.util.logging.*;
0027:
0028: /**
0029: * This strategy will evaluate the constraint on a a query object against a NodeEvent. It will apply the following rules:<br>
0030: * <b>new node/delete node</b><br>
0031: * <ul>
0032: * <li>If the step of a constraint matches the type of the event, and the values of the node don't fall within the
0033: * constraint: don't flush.</li>
0034: * <li>If the step of a constraint matches the type of the event, and the values of the node don't fall within the
0035: * constraint: flush.</li>
0036: * <li>If no constraints have a step matching the type of the event: flush.
0037: * </ul>
0038: * <b>change node</b> Like the above, but an extra check has to be made:
0039: * <ul>
0040: * <li>if the node previously fell within the constraints but now doesn't: flush</li>
0041: * <li>if the node previously didn't fall within the constraints but now does: flush</li>
0042: * </ul>
0043: *
0044: * @author Ernst Bunders
0045: * @since MMBase-1.8
0046: * @version $Id: ConstraintsMatchingStrategy.java,v 1.36 2008/02/03 17:33:56 nklasens Exp $
0047: *
0048: */
0049: public class ConstraintsMatchingStrategy extends ReleaseStrategy {
0050:
0051: private static final Logger log = Logging
0052: .getLoggerInstance(ConstraintsMatchingStrategy.class);
0053: private static final BasicSqlHandler sqlHandler = new BasicSqlHandler();
0054: private final static String escapeChars = ".\\?+*$()[]{}^|&";
0055:
0056: /**
0057: * This field contains the characters that are being escaped for the 'like' comparison of strings,
0058: * where, the string that should match the other is converted to a regexp
0059: **/
0060: private static final Cache<SearchQuery, AbstractConstraintMatcher> constraintWrapperCache;
0061:
0062: static {
0063: constraintWrapperCache = new Cache<SearchQuery, AbstractConstraintMatcher>(
0064: 1000) {
0065: public String getName() {
0066: return "ConstraintMatcherCache";
0067: }
0068:
0069: public String getDescription() {
0070: return "Caches query constraint wrappers used by ConstraintsMatchingStrategy";
0071: }
0072: };
0073: CacheManager.putCache(constraintWrapperCache);
0074: }
0075:
0076: private static final Map<String, Constructor> constraintMatcherConstructors = new HashMap<String, Constructor>();
0077: static {
0078: Class[] innerClasses = ConstraintsMatchingStrategy.class
0079: .getDeclaredClasses();
0080: for (Class innerClass : innerClasses) {
0081: if (innerClass.getName().endsWith("Matcher")
0082: && !Modifier.isAbstract(innerClass.getModifiers())) {
0083: String matcherClassName = innerClass.getName();
0084: matcherClassName = matcherClassName
0085: .substring(matcherClassName.lastIndexOf("$") + 1);
0086: Constructor con = null;
0087: Constructor[] cons = innerClass.getConstructors();
0088: for (Constructor element0 : cons) {
0089: Class[] params = element0.getParameterTypes();
0090: if (params.length == 1
0091: && Constraint.class
0092: .isAssignableFrom(params[0])) {
0093: con = element0;
0094: break;
0095: }
0096: }
0097: if (con == null) {
0098: log.error("Class " + innerClass
0099: + " has no appropriate constructor");
0100: continue;
0101: }
0102:
0103: constraintMatcherConstructors
0104: .put(matcherClassName, con);
0105: log.debug("** found matcher: " + matcherClassName);
0106: }
0107: }
0108: }
0109:
0110: public ConstraintsMatchingStrategy() {
0111: super ();
0112: }
0113:
0114: public String getName() {
0115: return "Constraint matching strategy";
0116: }
0117:
0118: public String getDescription() {
0119: return "Checks wether a changed node has a matching step within the constraints of a query, and then checks "
0120: + "if the node falls within the constraint. For changed nodes a check is made if the node previously "
0121: + "fell within the constraint and if it does so now. Queries that exclude changed nodes by their constraints "
0122: + "will not be flushed.";
0123: }
0124:
0125: protected final boolean doEvaluate(NodeEvent event,
0126: SearchQuery query, List<MMObjectNode> cachedResult) {
0127: //no constraint, we release any way
0128: Constraint constraint = query.getConstraint();
0129: if (constraint == null)
0130: return true; //should release
0131:
0132: //try to get a wrapper from the cache
0133: AbstractConstraintMatcher matcher = constraintWrapperCache
0134: .get(query);
0135:
0136: //if not found, try to create one
0137: if (matcher == null) {
0138: try {
0139: matcher = findMatcherForConstraint(constraint);
0140: if (log.isTraceEnabled()) {
0141: log.trace("created constraint matcher: " + matcher);
0142: }
0143: // Unwrapping BasicQuery's. This avoids unnecessary references (mainly to BasicCloud instances).
0144: if (query instanceof BasicQuery) {
0145: constraintWrapperCache.put(((BasicQuery) query)
0146: .getQuery(), matcher);
0147: } else {
0148: constraintWrapperCache.put(query, matcher);
0149: }
0150: //if anything goes wrong constraintMatches is true, which means the query should be flushed
0151: } catch (Exception e) {
0152: log.error(
0153: "Could not create constraint matcher for constraint: "
0154: + constraint + "main reason: " + e, e);
0155: }
0156: } else {
0157: if (log.isTraceEnabled()) {
0158: log.trace("found matcher for query in cache. query: "
0159: + query);
0160: }
0161: }
0162:
0163: //we should have a matcher now
0164: if (matcher != null) {
0165: switch (event.getType()) {
0166: case Event.TYPE_NEW: {
0167: Map<String, Object> newValues = event.getNewValues();
0168: // we have to compare the constraint value with the new value of the changed field to see if the new
0169: // node falls within the constraint. it it does: flush
0170: if (matcher.eventApplies(newValues, event)) {
0171: boolean eventMatches = matcher
0172: .nodeMatchesConstraint(newValues, event);
0173: if (log.isDebugEnabled()) {
0174: logResult((eventMatches ? "" : "no ")
0175: + "flush: with matcher {" + matcher
0176: + "}:", query, event, null);
0177: }
0178: return eventMatches;
0179: } else {
0180: if (log.isDebugEnabled()) {
0181: logResult(
0182: "flush: event does not apply to wrapper {"
0183: + matcher + "}:", query, event,
0184: null);
0185: }
0186: return true;
0187: }
0188: }
0189: case Event.TYPE_CHANGE: {
0190: Map<String, Object> oldValues;
0191: Map<String, Object> newValues;
0192: //because composite constraints can also cover fields that are not in the changed field list of the node
0193: //let's find the node and get all the values.
0194: MMObjectNode node = MMBase.getMMBase().getBuilder(
0195: event.getBuilderName()).getNode(
0196: event.getNodeNumber());
0197: if (node != null) {
0198: //put all the (new) values in the value maps
0199: Map<String, Object> nodeValues = node.getValues();
0200: oldValues = new LinkMap<String, Object>(nodeValues,
0201: event.getOldValues());
0202: newValues = new LinkMap<String, Object>(nodeValues,
0203: event.getNewValues());
0204: } else {
0205: oldValues = event.getOldValues();
0206: newValues = event.getNewValues();
0207: }
0208:
0209: // we have to compare the old value and then the new value of the changed field to see if the status
0210: // has changed. if the node used to match the constraint but now doesn't or the reverse of this, flush.
0211: if (matcher.eventApplies(newValues, event)) {
0212: boolean eventMatches = matcher
0213: .nodeMatchesConstraint(oldValues, event)
0214: || // used to match
0215: matcher.nodeMatchesConstraint(newValues,
0216: event); // still matches
0217:
0218: // It may be important to check whether the changed fields of the node are
0219: // present in the field-list of the query. If they are not, and usedToMatch
0220: // && stillMaches, then we can still return false, if at least there is no
0221: // sort-order on a changed field, because even if the field itself is not in
0222: // the result, and it matches the constraint before and after the event, it
0223: // can still change the order of the result then.
0224: // If we can garantuee that field-values alway come from the node-cache (which we cannot, at the moment),
0225: // then things may become a bit different.
0226:
0227: if (log.isDebugEnabled()) {
0228: boolean usedToMatch = matcher
0229: .nodeMatchesConstraint(oldValues, event);
0230: boolean stillMatches = matcher
0231: .nodeMatchesConstraint(newValues, event);
0232: log.debug("** match with old values : "
0233: + (usedToMatch ? "match" : "no match"));
0234: log
0235: .debug("** match with new values : "
0236: + (stillMatches ? "match"
0237: : "no match"));
0238: log.debug("**old values: " + oldValues);
0239: log.debug("**new values: " + newValues);
0240: logResult((eventMatches ? "" : "no ")
0241: + "flush: with matcher {" + matcher
0242: + "}:", query, event, node);
0243: }
0244:
0245: return eventMatches;
0246: } else {
0247: if (log.isDebugEnabled()) {
0248: logResult(
0249: "flush: event does not apply to wrapper {"
0250: + matcher + "}:", query, event,
0251: node);
0252: }
0253: return true;
0254: }
0255: }
0256: case Event.TYPE_DELETE:
0257: Map<String, Object> oldValues = event.getOldValues();
0258: // we have to compare the old value of the field to see if the node used to fall within the
0259: // constraint. If it did: flush
0260: if (matcher.eventApplies(event.getOldValues(), event)) {
0261: boolean eventMatches = matcher
0262: .nodeMatchesConstraint(oldValues, event);
0263: if (log.isDebugEnabled()) {
0264: logResult((eventMatches ? "" : "no ")
0265: + "flush: with matcher {" + matcher
0266: + "}:", query, event, null);
0267: }
0268: return eventMatches;
0269: } else {
0270: if (log.isDebugEnabled()) {
0271: logResult(
0272: "flush: event does not apply to wrapper {"
0273: + matcher + "}:", query, event,
0274: null);
0275: }
0276: return true;
0277: }
0278: default:
0279: log.error("Unrecognized event-type " + event.getType());
0280: break;
0281: }
0282: }
0283: return true; //safe: should release
0284: }
0285:
0286: protected final boolean doEvaluate(RelationEvent event,
0287: SearchQuery query, List<MMObjectNode> cachedResult) {
0288: // TODO I don't think this strategy should handle these events
0289: //because the node event that preceeds the relation event takes care of it.
0290: return doEvaluate(event.getNodeEvent(), query, cachedResult);
0291: }
0292:
0293: /**
0294: * This method will find a constraint matcher that supports the given constraint, and will return the
0295: * UnsupportedConstraintMatcher if none is found.
0296: *
0297: * @param constraint
0298: */
0299: protected final static AbstractConstraintMatcher findMatcherForConstraint(
0300: Constraint constraint) throws InvocationTargetException,
0301: NoSuchMethodException, InstantiationException,
0302: IllegalAccessException {
0303: String constraintClassName = constraint.getClass().getName();
0304: constraintClassName = constraintClassName
0305: .substring(constraintClassName.lastIndexOf(".") + 1);
0306:
0307: // MM: I think the idea behind this is questionable.
0308: // How expensive is it?
0309: Constructor matcherConstructor = constraintMatcherConstructors
0310: .get(constraintClassName + "Matcher");
0311: if (matcherConstructor == null) {
0312: log.error("Could not match constraint of type "
0313: + constraintClassName);
0314: matcherConstructor = UnsupportedConstraintMatcher.class
0315: .getConstructors()[0];
0316: }
0317: if (log.isDebugEnabled()) {
0318: log.debug("finding matcher for constraint class name: "
0319: + constraintClassName + "Matcher");
0320: log.trace("matcher class found: "
0321: + matcherConstructor.getDeclaringClass().getName());
0322: }
0323:
0324: return (AbstractConstraintMatcher) matcherConstructor
0325: .newInstance(new Object[] { constraint });
0326:
0327: }
0328:
0329: private static abstract class AbstractConstraintMatcher {
0330:
0331: /**
0332: * @param valuesToMatch the field values that the constraint value will have to be matched against.
0333: * this will sometimes be the 'oldValues' and sometimes be the 'newValues' from the event.
0334: * @return true if the values of event falls within the limits of the constraint
0335: */
0336: abstract public boolean nodeMatchesConstraint(
0337: Map<String, Object> valuesToMatch, NodeEvent event);
0338:
0339: /**
0340: * @param valuesToMatch map of (changed) fields with their values
0341: * @param event the event that has occured
0342: * @return true if the wrapped constraint matches the node event
0343: */
0344: abstract public boolean eventApplies(
0345: Map<String, Object> valuesToMatch, NodeEvent event);
0346:
0347: abstract public String toString();
0348: }
0349:
0350: private static class BasicCompositeConstraintMatcher extends
0351: AbstractConstraintMatcher {
0352: private final List<AbstractConstraintMatcher> wrappedConstraints;
0353: private final BasicCompositeConstraint wrappedCompositeConstraint;
0354:
0355: public BasicCompositeConstraintMatcher(
0356: BasicCompositeConstraint constraint)
0357: throws NoSuchMethodException, InstantiationException,
0358: InvocationTargetException, IllegalAccessException {
0359: wrappedCompositeConstraint = constraint;
0360: wrappedConstraints = new ArrayList<AbstractConstraintMatcher>();
0361: for (Constraint c : wrappedCompositeConstraint.getChilds()) {
0362: wrappedConstraints.add(findMatcherForConstraint(c));
0363: }
0364: }
0365:
0366: public boolean nodeMatchesConstraint(
0367: Map<String, Object> valuesToMatch, NodeEvent event) {
0368: int matches = 0;
0369: for (AbstractConstraintMatcher acm : findRelevantConstraints(
0370: valuesToMatch, event)) {
0371: if (log.isDebugEnabled()) {
0372: log.debug("** relevant constraint found: " + acm);
0373: }
0374: if (acm.nodeMatchesConstraint(valuesToMatch, event)) {
0375: matches++;
0376: if (log.isDebugEnabled()) {
0377: log.debug("** constraint created a match on "
0378: + valuesToMatch);
0379: }
0380: } else if (log.isDebugEnabled()) {
0381: log.debug("** constraint created _NO_ match on "
0382: + valuesToMatch);
0383: }
0384: }
0385: if (wrappedCompositeConstraint.getLogicalOperator() == CompositeConstraint.LOGICAL_AND) {
0386: return (matches == wrappedConstraints.size()) != wrappedCompositeConstraint
0387: .isInverse();
0388: } else {
0389: return (matches > 0) != wrappedCompositeConstraint
0390: .isInverse();
0391: }
0392: }
0393:
0394: public String toString() {
0395: StringBuffer sb = new StringBuffer(
0396: "Composite Wrapper. type: ");
0397: sb
0398: .append(wrappedCompositeConstraint
0399: .getLogicalOperator() == CompositeConstraint.LOGICAL_AND ? "AND"
0400: : "OR");
0401: sb.append(" [");
0402: for (Iterator<AbstractConstraintMatcher> i = wrappedConstraints
0403: .iterator(); i.hasNext();) {
0404: sb.append("{");
0405: sb.append(i.next().toString());
0406: if (i.hasNext())
0407: sb.append("} {");
0408: }
0409: sb.append("}]");
0410: return sb.toString();
0411: }
0412:
0413: /**
0414: * for composite constraint wrappers the rule is that if the operator is AND and all
0415: * of it's constraints are relevant it is relevant, and if the opreator is OR and one or more of it's
0416: * constraints are relevant it is relevant
0417: */
0418: public boolean eventApplies(Map<String, Object> valuesToMatch,
0419: NodeEvent event) {
0420: List<AbstractConstraintMatcher> relevantConstraints = findRelevantConstraints(
0421: valuesToMatch, event);
0422: if (log.isDebugEnabled()) {
0423: log.debug("** relevant constraints: "
0424: + relevantConstraints);
0425: }
0426: if (wrappedCompositeConstraint.getLogicalOperator() == CompositeConstraint.LOGICAL_AND) {
0427: if (wrappedConstraints.size() == relevantConstraints
0428: .size()) {
0429: log
0430: .debug("** composite AND: all constraints match, event applies to query");
0431: return true;
0432: } else {
0433: log
0434: .debug("** composite AND: not all constraints match, so the event does not apply to this constraint");
0435: }
0436: } else {
0437: if (relevantConstraints.size() > 0) {
0438: log
0439: .debug("** composite OR: more than zero constraints match, so event applies to query");
0440: return true;
0441: } else {
0442: log
0443: .debug("** composite OR: zero constraints match, so event does not apply to query.");
0444: }
0445:
0446: }
0447: return false;
0448: }
0449:
0450: private List<AbstractConstraintMatcher> findRelevantConstraints(
0451: Map<String, Object> valuesToMatch, NodeEvent event) {
0452: List<AbstractConstraintMatcher> relevantConstraints = new ArrayList<AbstractConstraintMatcher>();
0453: for (AbstractConstraintMatcher matcher : wrappedConstraints) {
0454: if (matcher.eventApplies(valuesToMatch, event))
0455: relevantConstraints.add(matcher);
0456: }
0457: return relevantConstraints;
0458: }
0459:
0460: }
0461:
0462: private static class UnsupportedConstraintMatcher extends
0463: AbstractConstraintMatcher {
0464:
0465: final Constraint wrappedConstraint;
0466:
0467: public UnsupportedConstraintMatcher(Constraint constraint) {
0468: wrappedConstraint = constraint;
0469: }
0470:
0471: /**
0472: * Return true here, to make sure the query gets flushed.
0473: */
0474: public boolean nodeMatchesConstraint(
0475: Map<String, Object> valuesToMatch, NodeEvent event) {
0476: return true;
0477: }
0478:
0479: public String toString() {
0480: return "Unsupported Matcher. masking for constraint: "
0481: + wrappedConstraint.getClass().getName();
0482: }
0483:
0484: public boolean eventApplies(Map<String, Object> valuesToMatch,
0485: NodeEvent event) {
0486: return true;
0487: }
0488: }
0489:
0490: private static class BasicLegacyConstraintMatcher extends
0491: UnsupportedConstraintMatcher {
0492: public BasicLegacyConstraintMatcher(Constraint constraint) {
0493: super (constraint);
0494: }
0495: }
0496:
0497: /**
0498: * This class is a base for the field comparison constraints. it provides the means to perform all supported
0499: * comparisons on all supported data types.
0500: *
0501: * @author ebunders
0502: */
0503: private static abstract class FieldCompareConstraintMatcher extends
0504: AbstractConstraintMatcher {
0505:
0506: protected abstract int getOperator();
0507:
0508: protected boolean valueMatches(final Class<Object> fieldType,
0509: Object constraintValue, Object valueToCompare,
0510: final boolean isCaseSensitive) {
0511: if (log.isDebugEnabled()) {
0512: log.debug("**method: valueMatches() fieldtype: "
0513: + fieldType);
0514: }
0515: if (constraintValue == null)
0516: return valueToCompare == null;
0517:
0518: int operator = getOperator();
0519:
0520: if (fieldType.equals(Boolean.class)) {
0521: boolean constraintBoolean = Casting
0522: .toBoolean(constraintValue);
0523: boolean booleanToCompare = Casting
0524: .toBoolean(valueToCompare);
0525: switch (operator) {
0526: case FieldCompareConstraint.EQUAL:
0527: return booleanToCompare == constraintBoolean;
0528: case FieldCompareConstraint.NOT_EQUAL:
0529: return booleanToCompare != constraintBoolean;
0530: default:
0531: log
0532: .warn("operator "
0533: + FieldCompareConstraint.OPERATOR_DESCRIPTIONS[operator]
0534: + "is not supported for type Boolean");
0535: return true;
0536: }
0537: } else if (fieldType.equals(Float.class)) {
0538: float constraintFloat = Casting.toFloat(
0539: constraintValue, Float.MAX_VALUE);
0540: float floatToCompare = Casting.toFloat(valueToCompare,
0541: Float.MAX_VALUE);
0542: //if either value could not be cast to an int, return true, which is safe
0543: if (constraintFloat == Float.MAX_VALUE
0544: || floatToCompare == Float.MAX_VALUE) {
0545: log
0546: .warn("either "
0547: + constraintValue
0548: + " or "
0549: + valueToCompare
0550: + " could not be cast to type float (while that is supposed to be their type)");
0551: return true;
0552: }
0553: return floatMatches(constraintFloat, floatToCompare,
0554: operator);
0555: } else if (fieldType.equals(Double.class)) {
0556: double constraintDouble = Casting.toDouble(
0557: constraintValue, Double.MAX_VALUE);
0558: double doubleToCompare = Casting.toDouble(
0559: valueToCompare, Double.MAX_VALUE);
0560: //if either value could not be cast to an int, return true, which is safe
0561: if (constraintDouble == Double.MAX_VALUE
0562: || doubleToCompare == Double.MAX_VALUE) {
0563: log
0564: .warn("either "
0565: + constraintValue
0566: + " or "
0567: + valueToCompare
0568: + " could not be cast to type double (while that is supposed to be their type)");
0569: return true;
0570: }
0571: return floatMatches(constraintDouble, doubleToCompare,
0572: operator);
0573: } else if (fieldType.equals(Date.class)) {
0574: long constraintLong = Casting.toLong(constraintValue,
0575: Long.MAX_VALUE);
0576: long longToCompare = Casting.toLong(valueToCompare,
0577: Long.MAX_VALUE);
0578:
0579: //if either value could not be cast to an int, return true, which is safe
0580: if (constraintLong == Long.MAX_VALUE
0581: || longToCompare == Long.MAX_VALUE) {
0582: log
0583: .warn("either "
0584: + constraintValue
0585: + " or "
0586: + valueToCompare
0587: + " could not be cast to type long (while they are supposed to be of type Date supposed to be their type)");
0588: return true;
0589: }
0590: return intMatches(constraintLong, longToCompare,
0591: operator);
0592: } else if (fieldType.equals(Integer.class)) {
0593: int constraintInt = Casting.toInt(constraintValue,
0594: Integer.MAX_VALUE);
0595: int intToCompare = Casting.toInt(valueToCompare,
0596: Integer.MAX_VALUE);
0597:
0598: //if either value could not be cast to an int, return true, which is safe
0599: if (constraintInt == Integer.MAX_VALUE
0600: || intToCompare == Integer.MAX_VALUE) {
0601: log
0602: .warn("either "
0603: + constraintValue
0604: + " or "
0605: + valueToCompare
0606: + " could not be cast to type int (while that is supposed to be their type)");
0607: return true;
0608: }
0609: return intMatches(constraintInt, intToCompare, operator);
0610: } else if (fieldType.equals(Long.class)) {
0611: long constraintLong = Casting.toLong(constraintValue,
0612: Long.MAX_VALUE);
0613: long longToCompare = Casting.toLong(valueToCompare,
0614: Long.MAX_VALUE);
0615: // if either value could not be cast to a long, return true, which is safe
0616: if (constraintLong == Long.MAX_VALUE
0617: || longToCompare == Long.MAX_VALUE) {
0618: // how can this ever happen?
0619: log
0620: .warn("either ["
0621: + constraintValue
0622: + "] "
0623: + (constraintValue == null ? ""
0624: : "of type "
0625: + constraintValue
0626: .getClass()
0627: .getName())
0628: + " or ["
0629: + valueToCompare
0630: + "] of type "
0631: + (valueToCompare == null ? ""
0632: : "of type "
0633: + valueToCompare
0634: .getClass()
0635: .getName())
0636: + " could not be cast to type long (while that is supposed to be their type)");
0637: return true;
0638:
0639: }
0640: return intMatches(constraintLong, longToCompare,
0641: operator);
0642: } else if (fieldType.equals(Node.class)) {
0643: if (constraintValue instanceof MMObjectNode)
0644: constraintValue = ((MMObjectNode) constraintValue)
0645: .getNumber();
0646: if (valueToCompare instanceof MMObjectNode)
0647: valueToCompare = ((MMObjectNode) valueToCompare)
0648: .getNumber();
0649: int constraintInt = Casting.toInt(constraintValue,
0650: Integer.MAX_VALUE);
0651: int intToCompare = Casting.toInt(valueToCompare,
0652: Integer.MAX_VALUE);
0653: // if either value could not be cast to a Node, return true, which is safe
0654: if (constraintInt == Integer.MAX_VALUE
0655: || intToCompare == Integer.MAX_VALUE) {
0656: log
0657: .warn("either ["
0658: + constraintValue
0659: + "] "
0660: + (constraintValue == null ? ""
0661: : "of type "
0662: + constraintValue
0663: .getClass()
0664: .getName())
0665: + " or ["
0666: + valueToCompare
0667: + "] of type "
0668: + (valueToCompare == null ? ""
0669: : "of type "
0670: + valueToCompare
0671: .getClass()
0672: .getName())
0673: + " could not be cast to type int (while they should be type node)");
0674: return true;
0675:
0676: }
0677: return intMatches(constraintInt, intToCompare, operator);
0678: } else if (fieldType.equals(String.class)
0679: || fieldType.equals(org.w3c.dom.Document.class)) {
0680: String constraintString = Casting
0681: .toString(constraintValue);
0682: String stringToCompare = Casting
0683: .toString(valueToCompare);
0684: return stringMatches(constraintString, stringToCompare,
0685: operator, isCaseSensitive);
0686: }
0687:
0688: return false;
0689: }
0690:
0691: private boolean floatMatches(double constraintDouble,
0692: double doubleTocompare, int operator) {
0693: switch (operator) {
0694: case FieldCompareConstraint.EQUAL:
0695: return doubleTocompare == constraintDouble;
0696: case FieldCompareConstraint.GREATER:
0697: return doubleTocompare > constraintDouble;
0698: case FieldCompareConstraint.GREATER_EQUAL:
0699: return doubleTocompare >= constraintDouble;
0700: case FieldCompareConstraint.LESS:
0701: return doubleTocompare < constraintDouble;
0702: case FieldCompareConstraint.LESS_EQUAL:
0703: return doubleTocompare <= constraintDouble;
0704: case FieldCompareConstraint.NOT_EQUAL:
0705: return doubleTocompare != constraintDouble;
0706: default:
0707: log
0708: .warn("operator "
0709: + FieldCompareConstraint.OPERATOR_DESCRIPTIONS[operator]
0710: + "for any numeric type");
0711: return true;
0712:
0713: }
0714: }
0715:
0716: private boolean intMatches(long constraintLong,
0717: long longToCompare, int operator) {
0718: switch (operator) {
0719: case FieldCompareConstraint.EQUAL:
0720: return longToCompare == constraintLong;
0721: case FieldCompareConstraint.GREATER:
0722: return longToCompare > constraintLong;
0723: case FieldCompareConstraint.GREATER_EQUAL:
0724: return longToCompare >= constraintLong;
0725: case FieldCompareConstraint.LESS:
0726: return longToCompare < constraintLong;
0727: case FieldCompareConstraint.LESS_EQUAL:
0728: return longToCompare <= constraintLong;
0729: case FieldCompareConstraint.NOT_EQUAL:
0730: return longToCompare != constraintLong;
0731: default:
0732: log
0733: .warn("operator "
0734: + FieldCompareConstraint.OPERATOR_DESCRIPTIONS[operator]
0735: + "for any numeric type");
0736: return true;
0737: }
0738: }
0739:
0740: private boolean stringMatches(String constraintString,
0741: String stringToCompare, int operator,
0742: boolean isCaseSensitive) {
0743: switch (operator) {
0744: case FieldCompareConstraint.EQUAL:
0745: return stringToCompare.equals(constraintString);
0746: // TODO: MM: I think depending on the database configuration the case-sensitivity may be important in the following 4:
0747: case FieldCompareConstraint.GREATER:
0748: return stringToCompare.compareTo(constraintString) > 0;
0749: case FieldCompareConstraint.LESS:
0750: return stringToCompare.compareTo(constraintString) < 0;
0751: case FieldCompareConstraint.LESS_EQUAL:
0752: return stringToCompare.compareTo(constraintString) <= 0;
0753: case FieldCompareConstraint.GREATER_EQUAL:
0754: return stringToCompare.compareTo(constraintString) >= 0;
0755: case FieldCompareConstraint.LIKE:
0756: return likeMatches(constraintString, stringToCompare,
0757: isCaseSensitive);
0758: case FieldCompareConstraint.NOT_EQUAL:
0759: return !stringToCompare.equals(constraintString);
0760: default:
0761: log
0762: .warn("operator "
0763: + FieldCompareConstraint.OPERATOR_DESCRIPTIONS[operator]
0764: + "is not supported for type String");
0765: return true;
0766: }
0767: }
0768:
0769: private boolean likeMatches(String constraintString,
0770: String stringToCompare, boolean isCaseSensitive) {
0771: if (log.isTraceEnabled()) {
0772: log.trace("** method: likeMatches() stringToCompare: "
0773: + stringToCompare + ", constraintString: "
0774: + constraintString);
0775: }
0776: if (isCaseSensitive) {
0777: constraintString = constraintString.toLowerCase();
0778: stringToCompare = stringToCompare.toLowerCase();
0779: }
0780: char[] chars = constraintString.toCharArray();
0781: StringBuffer sb = new StringBuffer();
0782:
0783: for (char element : chars) {
0784: if (element == '?') {
0785: sb.append(".");
0786: } else if (element == '%') {
0787: sb.append(".*");
0788: } else if (escapeChars.indexOf(element) > -1) {
0789: sb.append("\\");
0790: sb.append(element);
0791: } else {
0792: sb.append(element);
0793: }
0794: }
0795: if (log.isDebugEnabled()) {
0796: log.trace("** new pattern: " + sb.toString());
0797: }
0798: return stringToCompare.matches(sb.toString());
0799: }
0800:
0801: protected Class<Object> getFieldTypeClass(StepField stepField) {
0802: MMBase mmbase = MMBase.getMMBase();
0803: // why it this checked anyway?
0804: CoreField field = mmbase.getBuilder(
0805: stepField.getStep().getTableName()).getField(
0806: stepField.getFieldName());
0807: DataType<Object> fieldType = field.getDataType();
0808: Class<Object> fieldTypeClass = fieldType.getTypeAsClass();
0809: if (fieldTypeClass.equals(Boolean.class)
0810: || fieldTypeClass.equals(Date.class)
0811: || fieldTypeClass.equals(Integer.class)
0812: || fieldTypeClass.equals(Long.class)
0813: || fieldTypeClass.equals(Float.class)
0814: || fieldTypeClass.equals(Double.class)
0815: || fieldTypeClass.equals(Node.class)
0816: || fieldTypeClass.equals(String.class)
0817: || fieldTypeClass
0818: .equals(org.w3c.dom.Document.class)) {
0819: if (log.isDebugEnabled()) {
0820: log.debug("** found field type: "
0821: + fieldTypeClass.getName());
0822: }
0823: } else {
0824: throw new RuntimeException("Field type "
0825: + fieldTypeClass + " is not supported");
0826: }
0827: return fieldTypeClass;
0828: }
0829:
0830: }
0831:
0832: private static class BasicFieldValueConstraintMatcher extends
0833: FieldCompareConstraintMatcher {
0834: private final Class<Object> fieldTypeClass;
0835: protected final StepField stepField;
0836: protected final BasicFieldValueConstraint wrappedFieldValueConstraint;
0837:
0838: public BasicFieldValueConstraintMatcher(
0839: BasicFieldValueConstraint constraint) {
0840: stepField = constraint.getField();
0841: if (log.isDebugEnabled()) {
0842: log.debug("** builder: "
0843: + stepField.getStep().getTableName()
0844: + ". field: " + stepField.getFieldName());
0845: }
0846: fieldTypeClass = getFieldTypeClass(stepField);
0847: wrappedFieldValueConstraint = constraint;
0848:
0849: }
0850:
0851: protected int getOperator() {
0852: return wrappedFieldValueConstraint.getOperator();
0853: }
0854:
0855: /**
0856: * Check the values to see if the values of the node matches the constraint.
0857: */
0858: public boolean nodeMatchesConstraint(
0859: Map<String, Object> valuesToMatch, NodeEvent event) {
0860: log.debug("**method: nodeMatchesConstraint");
0861: //if(! eventApplies(valuesToMatch, event)) throw new FieldComparisonException("constraint " + wrappedFieldCompareConstraint.toString() + "does not match event of type " +event.getBuilderName());
0862: boolean matches = valueMatches(fieldTypeClass,
0863: wrappedFieldValueConstraint.getValue(),
0864: valuesToMatch.get(stepField.getFieldName()),
0865: wrappedFieldValueConstraint.isCaseSensitive());
0866: return matches != wrappedFieldValueConstraint.isInverse();
0867: }
0868:
0869: public String toString() {
0870: return "Field Value Matcher. operator: "
0871: + FieldCompareConstraint.OPERATOR_DESCRIPTIONS[wrappedFieldValueConstraint
0872: .getOperator()] + ", value: "
0873: + wrappedFieldValueConstraint.getValue().toString()
0874: + ", step: " + stepField.getStep().getTableName()
0875: + ", field name: " + stepField.getFieldName();
0876: }
0877:
0878: /**
0879: * An event applies to a field value constraint wrapper if the wrapper is of the same type as the event, and the field
0880: * that is being checked is in the 'changed' fields map (valuesToMatch)
0881: */
0882: public boolean eventApplies(Map<String, Object> valuesToMatch,
0883: NodeEvent event) {
0884: return wrappedFieldValueConstraint.getField().getStep()
0885: .getTableName().equals(event.getBuilderName())
0886: && valuesToMatch
0887: .containsKey(wrappedFieldValueConstraint
0888: .getField().getFieldName());
0889: }
0890:
0891: }
0892:
0893: /**
0894: * @since MMBase-1.8.1
0895: */
0896: private static class BasicFieldValueBetweenConstraintMatcher extends
0897: AbstractConstraintMatcher {
0898:
0899: protected final StepField stepField;
0900: protected final BasicFieldValueBetweenConstraint wrappedFieldConstraint;
0901:
0902: public BasicFieldValueBetweenConstraintMatcher(
0903: BasicFieldValueBetweenConstraint constraint) {
0904: stepField = constraint.getField();
0905: wrappedFieldConstraint = constraint;
0906: }
0907:
0908: public boolean nodeMatchesConstraint(
0909: Map<String, Object> valuesToMatch, NodeEvent event) {
0910: return true;
0911: }
0912:
0913: public boolean eventApplies(Map<String, Object> valuesToMatch,
0914: NodeEvent event) {
0915: return true;
0916: }
0917:
0918: public String toString() {
0919: return "Field Value Between Matcher. operator: "
0920: + ", step: " + stepField.getStep().getTableName()
0921: + ", field name: " + stepField.getFieldName();
0922: }
0923:
0924: }
0925:
0926: /**
0927: * @since MMBase-1.8.1
0928: */
0929: private static class BasicFieldValueInConstraintMatcher extends
0930: FieldCompareConstraintMatcher {
0931: private final Class<Object> fieldTypeClass;
0932: protected final StepField stepField;
0933: protected final BasicFieldValueInConstraint wrappedFieldValueInConstraint;
0934:
0935: public BasicFieldValueInConstraintMatcher(
0936: BasicFieldValueInConstraint constraint) {
0937: stepField = constraint.getField();
0938: fieldTypeClass = getFieldTypeClass(stepField);
0939: wrappedFieldValueInConstraint = constraint;
0940: }
0941:
0942: protected int getOperator() {
0943: return FieldCompareConstraint.EQUAL;
0944: }
0945:
0946: /**
0947: * Check the values to see if the node's value matches the constraint.
0948: */
0949: public boolean nodeMatchesConstraint(
0950: Map<String, Object> valuesToMatch, NodeEvent event) {
0951: log.debug("**method: nodeMatchesConstraint");
0952: SortedSet<Object> values = wrappedFieldValueInConstraint
0953: .getValues();
0954: boolean matches = false;
0955: Iterator<Object> i = values.iterator();
0956: while (i.hasNext() && !matches) {
0957: Object value = i.next();
0958: matches = valueMatches(fieldTypeClass, value,
0959: valuesToMatch.get(stepField.getFieldName()),
0960: wrappedFieldValueInConstraint.isCaseSensitive());
0961: }
0962: return matches != wrappedFieldValueInConstraint.isInverse();
0963: }
0964:
0965: public String toString() {
0966: return "Field Value IN Matcher. operator: "
0967: + ", value: "
0968: + wrappedFieldValueInConstraint.getValues()
0969: .toString() + ", step: "
0970: + stepField.getStep().getTableName()
0971: + ", field name: " + stepField.getFieldName();
0972: }
0973:
0974: public boolean eventApplies(Map<String, Object> valuesToMatch,
0975: NodeEvent event) {
0976: return wrappedFieldValueInConstraint.getField().getStep()
0977: .getTableName().equals(event.getBuilderName())
0978: && valuesToMatch
0979: .containsKey(wrappedFieldValueInConstraint
0980: .getField().getFieldName());
0981: }
0982: }
0983:
0984: private void logResult(String comment, SearchQuery query,
0985: Event event, MMObjectNode node) {
0986: if (log.isDebugEnabled()) {
0987: String role = "";
0988: // a small hack to limit the output
0989: if (event instanceof RelationEvent) {
0990: //get the role name
0991: RelationEvent revent = (RelationEvent) event;
0992: MMObjectNode relDef = MMBase.getMMBase().getBuilder(
0993: "reldef").getNode(revent.getRole());
0994: role = " role: " + relDef.getStringValue("sname") + "/"
0995: + relDef.getStringValue("dname");
0996: //filter the 'object' events
0997: if (revent.getRelationSourceType().equals("object")
0998: || revent.getRelationDestinationType().equals(
0999: "object"))
1000: return;
1001: }
1002: try {
1003: log.debug("\n******** \n**"
1004: + comment
1005: + "\n**"
1006: + event.toString()
1007: + role
1008: + "\n**nodevalues: "
1009: + (node == null ? "NODE NULL" : ""
1010: + node.getValues()) + "\n**"
1011: + sqlHandler.toSql(query, sqlHandler)
1012: + "\n******");
1013: } catch (SearchQueryException e) {
1014: log.error(e);
1015: }
1016: }
1017: }
1018:
1019: }
|