KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > cache > ConstraintsMatchingStrategy


1 /*
2
3  This software is OSI Certified Open Source Software.
4  OSI Certified is a certification mark of the Open Source Initiative.
5
6  The license (Mozilla version 1.0) can be read at the MMBase site.
7  See http://www.MMBase.org/license
8
9  */

10 package org.mmbase.cache;
11
12 import java.lang.reflect.*;
13 import java.util.*;
14
15 import org.mmbase.bridge.Node;
16 import org.mmbase.bridge.implementation.BasicQuery;
17 import org.mmbase.core.CoreField;
18 import org.mmbase.core.event.*;
19 import org.mmbase.datatypes.DataType;
20 import org.mmbase.module.core.*;
21 import org.mmbase.storage.search.*;
22 import org.mmbase.storage.search.implementation.*;
23 import org.mmbase.storage.search.implementation.database.BasicSqlHandler;
24 import org.mmbase.util.Casting;
25 import org.mmbase.util.logging.*;
26
27 /**
28  * This strategy will evaluate the constraint on a a query object against a NodeEvent. It will apply the following rules:<br>
29  * <b>new node/delete node</b><br>
30  * <ul>
31  * <li>If the step of a constraint matches the type of the event, and the values of the node don't fall within the
32  * constraint: don't flush.</li>
33  * <li>If the step of a constraint matches the type of the event, and the values of the node don't fall within the
34  * constraint: flush.</li>
35  * <li>If no constraints have a step matching the type of the event: flush.
36  * </ul>
37  * <b>change node</b> Like the above, but an extra check has to be made:
38  * <ul>
39  * <li>if the node previously fell within the constraints but now doesn't: flush</li>
40  * <li>if the node previously didn't fall within the constraints but now does: flush</li>
41  * </ul>
42  *
43  * @author Ernst Bunders
44  * @since MMBase-1.8
45  * @version $Id: ConstraintsMatchingStrategy.java,v 1.30 2006/08/01 21:38:10 michiel Exp $
46  *
47  */

48 public class ConstraintsMatchingStrategy extends ReleaseStrategy {
49
50     private static final Logger log = Logging.getLoggerInstance(ConstraintsMatchingStrategy.class);
51     private static final BasicSqlHandler sqlHandler = new BasicSqlHandler();
52     private final static String JavaDoc escapeChars=".\\?+*$()[]{}^|&";
53
54     /**
55     * This field contains the characters that are being escaped for the 'like' comparison of strings,
56     * where, the string that should match the other is converted to a regexp
57     **/

58     private static final Cache constraintWrapperCache;
59
60
61     static {
62         constraintWrapperCache = new Cache(1000) {
63                 public String JavaDoc getName(){ return "ConstraintMatcherCache";}
64                 public String JavaDoc getDescription() {return "Caches query constraint wrappers used by ConstraintsMatchingStrategy";}
65         };
66         Cache.putCache(constraintWrapperCache);
67     }
68
69     private static final Map constraintMatcherConstructors = new HashMap();
70     static {
71         Class JavaDoc[] innerClasses = ConstraintsMatchingStrategy.class.getDeclaredClasses();
72         for (int i = 0; i < innerClasses.length; i++) {
73             Class JavaDoc innerClass = innerClasses[i];
74             if (innerClass.getName().endsWith("Matcher") && ! Modifier.isAbstract(innerClass.getModifiers())) {
75                 String JavaDoc matcherClassName = innerClass.getName();
76                 matcherClassName = matcherClassName.substring(matcherClassName.lastIndexOf("$") + 1);
77                 Constructor con = null;
78                 Constructor[] cons = innerClass.getConstructors();
79                 for (int j = 0; j < cons.length; j++) {
80                     Class JavaDoc [] params = cons[j].getParameterTypes();
81                     if(params.length == 1 && Constraint.class.isAssignableFrom(params[0])) {
82                         con = cons[j];
83                         break;
84                     }
85                 }
86                 if (con == null) {
87                     log.error("Class " + innerClass + " has no appropriate constructor");
88                     continue;
89                 }
90
91                 constraintMatcherConstructors.put(matcherClassName, con);
92                 log.debug("** found matcher: " + matcherClassName);
93             }
94         }
95     }
96
97     public ConstraintsMatchingStrategy() {
98         super();
99     }
100
101     public String JavaDoc getName() {
102         return "Constraint matching strategy";
103     }
104
105     public String JavaDoc getDescription() {
106         return "Checks wether a changed node has a matching step within the constraints of a query, and then checks "
107                 + "if the node falls within the constraint. For changed nodes a check is made if the node previously "
108                 + "fell within the constraint and if it does so now. Queries that exclude changed nodes by their constraints "
109                 + "will not be flushed.";
110     }
111
112     protected final boolean doEvaluate(NodeEvent event, SearchQuery query, List cachedResult) {
113         //no constraint, we release any way
114
Constraint constraint = query.getConstraint();
115         if(constraint == null) return true; //should release
116

117         //try to get a wrapper from the cache
118
AbstractConstraintMatcher matcher = (AbstractConstraintMatcher) constraintWrapperCache.get(query);
119
120         //if not found, try to create one
121
if(matcher == null){
122             try {
123                 matcher = findMatcherForConstraint(constraint);
124                 if (log.isTraceEnabled()) {
125                     log.trace("created constraint matcher: " + matcher);
126                 }
127                 // Unwrapping BasicQuery's. This avoids unnecessary references (mainly to BasicCloud instances).
128
if (query instanceof BasicQuery) {
129                     constraintWrapperCache.put(((BasicQuery) query).getQuery(), matcher);
130                 } else {
131                     constraintWrapperCache.put(query, matcher);
132                 }
133                 //if anything goes wrong constraintMatches is true, which means the query should be flushed
134
} catch (Exception JavaDoc e) {
135                 log.error("Could not create constraint matcher for constraint: " + constraint + "main reason: " + e, e);
136             }
137         } else {
138             if(log.isTraceEnabled()){
139                 log.trace("found matcher for query in cache. query: " + query);
140             }
141         }
142
143         //we should have a matcher now
144
if(matcher != null){
145             switch(event.getType()) {
146             case Event.TYPE_NEW: {
147                 Map newValues = event.getNewValues();
148                 // we have to compare the constraint value with the new value of the changed field to see if the new
149
// node falls within the constraint. it it does: flush
150
if(matcher.eventApplies(newValues, event)){
151                     boolean eventMatches = matcher.nodeMatchesConstraint(newValues, event);
152                     if (log.isDebugEnabled()) {
153                         logResult((eventMatches ? "" : "no ") + "flush: with matcher {" + matcher + "}:", query, event, null);
154                     }
155                     return eventMatches;
156                 } else {
157                     if (log.isDebugEnabled()) {
158                         logResult("flush: event does not apply to wrapper {" + matcher + "}:", query, event, null);
159                     }
160                     return true;
161                 }
162             }
163             case Event.TYPE_CHANGE: {
164                 Map oldValues;
165                 Map newValues;
166                 //because composite constraints can also cover fields that are not in the changed field list of the node
167
//let's find the node and get all the values.
168
MMObjectNode node = MMBase.getMMBase().getBuilder(event.getBuilderName()).getNode(event.getNodeNumber());
169                 if(node != null){
170                     //put all the (new) values in the value maps
171
Map nodeValues = node.getValues();
172                     oldValues = new LinkMap(nodeValues, event.getOldValues());
173                     newValues = new LinkMap(nodeValues, event.getNewValues());
174                 } else {
175                     oldValues = event.getOldValues();
176                     newValues = event.getNewValues();
177                 }
178
179                 // we have to compare the old value and then the new value of the changed field to see if the status
180
// has changed. if the node used to match the constraint but now doesn't or the reverse of this, flush.
181
if(matcher.eventApplies(newValues, event)){
182                     boolean eventMatches =
183                         matcher.nodeMatchesConstraint(oldValues, event) || // used to match
184
matcher.nodeMatchesConstraint(newValues, event); // still matches
185

186                     // It may be important to check whether the changed fields of the node are
187
// present in the field-list of the query. If they are not, and usedToMatch
188
// && stillMaches, then we can still return false, if at least there is no
189
// sort-order on a changed field, because even if the field itself is not in
190
// the result, and it matches the constraint before and after the event, it
191
// can still change the order of the result then.
192
// If we can garantuee that field-values alway come from the node-cache (which we cannot, at the moment),
193
// then things may become a bit different.
194

195                     if (log.isDebugEnabled()) {
196                         boolean usedToMatch = matcher.nodeMatchesConstraint(oldValues, event);
197                         boolean stillMatches = matcher.nodeMatchesConstraint(newValues, event);
198                         log.debug("** match with old values : " + (usedToMatch ? "match" : "no match"));
199                         log.debug("** match with new values : " + (stillMatches ? "match" : "no match"));
200                         log.debug("**old values: " + oldValues);
201                         log.debug("**new values: " + newValues);
202                         logResult((eventMatches ? "" : "no ") + "flush: with matcher {" + matcher + "}:", query, event, node);
203                     }
204
205                     return eventMatches;
206                 } else {
207                     if (log.isDebugEnabled()) {
208                         logResult("flush: event does not apply to wrapper {" + matcher + "}:", query, event, node);
209                     }
210                     return true;
211                 }
212             }
213             case Event.TYPE_DELETE:
214                 Map oldValues = event.getOldValues();
215                 // we have to compare the old value of the field to see if the node used to fall within the
216
// constraint. If it did: flush
217
if(matcher.eventApplies(event.getOldValues(), event)){
218                     boolean eventMatches = matcher.nodeMatchesConstraint(oldValues, event);
219                     if (log.isDebugEnabled()) {
220                         logResult( (eventMatches ? "" : "no ") + "flush: with matcher {"+matcher+"}:", query, event, null);
221                     }
222                     return eventMatches;
223                 } else {
224                     if (log.isDebugEnabled()) {
225                         logResult("flush: event does not apply to wrapper {" + matcher + "}:", query, event, null);
226                     }
227                     return true;
228                 }
229             default:
230                 log.error("Unrecognized event-type " + event.getType());
231                 break;
232             }
233         }
234         return true; //safe: should release
235
}
236
237     protected final boolean doEvaluate(RelationEvent event, SearchQuery query, List cachedResult) {
238         // TODO I don't think this strategy should handle these events
239
//because the node event that preceeds the relation event takes care of it.
240
return doEvaluate(event.getNodeEvent(), query, cachedResult);
241     }
242
243     /**
244      * This method will find a constraint matcher that supports the given constraint, and will return the
245      * UnsupportedConstraintMatcher if none is found.
246      *
247      * @param constraint
248      */

249     protected final static AbstractConstraintMatcher findMatcherForConstraint(Constraint constraint) throws InvocationTargetException, NoSuchMethodException JavaDoc, InstantiationException JavaDoc, IllegalAccessException JavaDoc {
250         String JavaDoc constraintClassName = constraint.getClass().getName();
251         constraintClassName = constraintClassName.substring(constraintClassName.lastIndexOf(".") + 1);
252
253
254         // MM: I think the idea behind this is questionable.
255
// How expensive is it?
256
Constructor matcherConstructor = (Constructor) constraintMatcherConstructors.get(constraintClassName + "Matcher");
257         if (matcherConstructor == null) {
258             log.error("Could not match constraint of type " + constraintClassName);
259             matcherConstructor = UnsupportedConstraintMatcher.class.getConstructors()[0];
260         }
261         if (log.isDebugEnabled()) {
262             log.debug("finding matcher for constraint class name: " + constraintClassName + "Matcher");
263             log.trace("matcher class found: " + matcherConstructor.getDeclaringClass().getName());
264         }
265
266         return (AbstractConstraintMatcher) matcherConstructor.newInstance(new Object JavaDoc[] { constraint });
267
268     }
269
270     /**
271      * @param args
272      */

273     public static void main(String JavaDoc[] args) {
274         Logging.getLoggerInstance(ConstraintsMatchingStrategy.class).setLevel(Level.DEBUG);
275
276         Class JavaDoc cl = UnsupportedConstraintMatcher.class;
277         try {
278             Constructor c = cl.getConstructor(new Class JavaDoc[] { Constraint.class });
279             AbstractConstraintMatcher matcherInstance;
280             //matcherInstance = (AbstractConstraintMatcher) c.newInstance(new Object[] { constraint });
281
} catch (Exception JavaDoc e) {
282             System.out.println(e.toString());
283         }
284
285     }
286
287     private static abstract class AbstractConstraintMatcher {
288
289         /**
290          * @param valuesToMatch the field values that the constraint value will have to be matched against.
291          * this will sometimes be the 'oldValues' and sometimes be the 'newValues' from the event.
292          * @return true if the values of event falls within the limits of the constraint
293          */

294         abstract public boolean nodeMatchesConstraint(Map valuesToMatch, NodeEvent event);
295         /**
296          * @param valuesToMatch map of (changed) fields with their values
297          * @param event the event that has occured
298          * @return true if the wrapped constraint matches the node event
299          */

300         abstract public boolean eventApplies(Map valuesToMatch, NodeEvent event);
301         abstract public String JavaDoc toString();
302     }
303
304
305
306
307
308
309     private static class BasicCompositeConstraintMatcher extends AbstractConstraintMatcher {
310         private final List wrappedConstraints;
311         private final BasicCompositeConstraint wrappedCompositeConstraint;
312
313         public BasicCompositeConstraintMatcher(BasicCompositeConstraint constraint) throws NoSuchMethodException JavaDoc, InstantiationException JavaDoc, InvocationTargetException, IllegalAccessException JavaDoc {
314             wrappedCompositeConstraint = constraint;
315             wrappedConstraints = new ArrayList();
316             for (Iterator i = wrappedCompositeConstraint.getChilds().iterator(); i.hasNext();) {
317                 Constraint c = (Constraint) i.next();
318                 wrappedConstraints.add(findMatcherForConstraint(c));
319             }
320         }
321
322         public boolean nodeMatchesConstraint(Map valuesToMatch, NodeEvent event) {
323             int matches = 0;
324             for (Iterator i = findRelevantConstraints(valuesToMatch, event).iterator(); i.hasNext();) {
325                 AbstractConstraintMatcher acm = (AbstractConstraintMatcher) i.next();
326                 if (log.isDebugEnabled()) {
327                     log.debug("** relevant constraint found: " + acm);
328                 }
329                 if (acm.nodeMatchesConstraint(valuesToMatch, event)){
330                     matches ++;
331                     if (log.isDebugEnabled()) {
332                         log.debug("** constraint created a match on " + valuesToMatch);
333                     }
334                 } else if (log.isDebugEnabled()) {
335                     log.debug("** constraint created _NO_ match on " + valuesToMatch);
336                 }
337             }
338             if (wrappedCompositeConstraint.getLogicalOperator() == BasicCompositeConstraint.LOGICAL_AND) {
339                 return (matches == wrappedConstraints.size()) != wrappedCompositeConstraint.isInverse();
340             } else {
341                 return (matches > 0) != wrappedCompositeConstraint.isInverse();
342             }
343         }
344
345         public String JavaDoc toString(){
346             StringBuffer JavaDoc sb = new StringBuffer JavaDoc("Composite Wrapper. type: ");
347             sb.append(wrappedCompositeConstraint.getLogicalOperator() == BasicCompositeConstraint.LOGICAL_AND ? "AND" : "OR");
348             sb.append(" [");
349             for (Iterator i = wrappedConstraints.iterator(); i.hasNext();) {
350                 sb.append("{");
351                 sb.append(((AbstractConstraintMatcher)i.next()).toString());
352                 if(i.hasNext()) sb.append("} {");
353             }
354             sb.append("}]");
355             return sb.toString();
356         }
357
358         /**
359          * for composite constraint wrappers the rule is that if the operator is AND and all
360          * of it's constraints are relevant it is relevant, and if the opreator is OR and one or more of it's
361          * constraints are relevant it is relevant
362          */

363         public boolean eventApplies(Map valuesToMatch, NodeEvent event) {
364             List relevantConstraints = findRelevantConstraints(valuesToMatch, event);
365             if (log.isDebugEnabled()) {
366                 log.debug("** relevant constraints: " + relevantConstraints);
367             }
368             if(wrappedCompositeConstraint.getLogicalOperator() == BasicCompositeConstraint.LOGICAL_AND){
369                 if(wrappedConstraints.size() == relevantConstraints.size()) {
370                     log.debug("** composite AND: all constraints match, event applies to query");
371                     return true;
372                 } else {
373                     log.debug("** composite AND: not all constraints match, so the event does not apply to this constraint");
374                 }
375             } else {
376                 if(relevantConstraints.size() > 0){
377                     log.debug("** composite OR: more than zero constraints match, so event applies to query");
378                     return true;
379                 }else{
380                     log.debug("** composite OR: zero constraints match, so event does not apply to query.");
381                 }
382
383             }
384             return false;
385         }
386
387
388         private List findRelevantConstraints(Map valuesToMatch, NodeEvent event){
389             List relevantConstraints = new ArrayList();
390             for (Iterator i = wrappedConstraints.iterator(); i.hasNext();) {
391                 AbstractConstraintMatcher matcher = (AbstractConstraintMatcher ) i.next();
392                 if(matcher.eventApplies(valuesToMatch, event))relevantConstraints.add(matcher);
393             }
394             return relevantConstraints;
395         }
396
397     }
398
399
400
401
402
403     private static class UnsupportedConstraintMatcher extends AbstractConstraintMatcher {
404
405         final Constraint wrappedConstraint;
406         public UnsupportedConstraintMatcher(Constraint constraint) {
407             wrappedConstraint = constraint;
408         }
409
410         /**
411          * Return true here, to make sure the query gets flushed.
412          */

413         public boolean nodeMatchesConstraint(Map valuesToMatch, NodeEvent event) {
414             return true;
415         }
416
417         public String JavaDoc toString(){
418             return "Unsupported Matcher. masking for constraint: " + wrappedConstraint.getClass().getName();
419         }
420
421         public boolean eventApplies(Map valuesToMatch, NodeEvent event) {
422             return true;
423         }
424     }
425
426
427     private static class BasicLegacyConstraintMatcher extends UnsupportedConstraintMatcher {
428         public BasicLegacyConstraintMatcher(Constraint constraint) {
429             super(constraint);
430         }
431     }
432
433
434
435
436     /**
437      * This class is a base for the field comparison constraints. it provides the means to perform all supported
438      * comparisons on all supported data types.
439      *
440      * @author ebunders
441      */

442     private static abstract class FieldCompareConstraintMatcher extends AbstractConstraintMatcher {
443
444         protected abstract int getOperator();
445
446         protected boolean valueMatches(final Class JavaDoc fieldType, Object JavaDoc constraintValue, Object JavaDoc valueToCompare, final boolean isCaseSensitive) {
447             if (log.isDebugEnabled()) {
448                 log.debug("**method: valueMatches() fieldtype: " + fieldType);
449             }
450             if (constraintValue == null) return valueToCompare == null;
451
452             int operator = getOperator();
453
454             if (fieldType.equals(Boolean JavaDoc.class)) {
455                 boolean constraintBoolean = Casting.toBoolean(constraintValue);
456                 boolean booleanToCompare = Casting.toBoolean(valueToCompare);
457                 switch(operator) {
458                 case FieldCompareConstraint.EQUAL: return booleanToCompare == constraintBoolean;
459                 case FieldCompareConstraint.NOT_EQUAL: return booleanToCompare != constraintBoolean;
460                 default:
461                     log.warn("operator " + FieldCompareConstraint.OPERATOR_DESCRIPTIONS[operator] + "is not supported for type Boolean");
462                     return true;
463                 }
464             } else if (fieldType.equals(Float JavaDoc.class)) {
465                 float constraintFloat = Casting.toFloat(constraintValue, Float.MAX_VALUE);
466                 float floatToCompare = Casting.toFloat(valueToCompare, Float.MAX_VALUE);
467                 //if either value could not be cast to an int, return true, which is safe
468
if(constraintFloat == Float.MAX_VALUE || floatToCompare == Float.MAX_VALUE) {
469                     log.warn("either " + constraintValue + " or " + valueToCompare + " could not be cast to type float (while that is supposed to be their type)");
470                     return true;
471                 }
472                 return floatMatches(constraintFloat, floatToCompare, operator);
473             } else if (fieldType.equals(Double JavaDoc.class)) {
474                 double constraintDouble = Casting.toDouble(constraintValue, Double.MAX_VALUE);
475                 double doubleToCompare = Casting.toDouble(valueToCompare, Double.MAX_VALUE);
476                 //if either value could not be cast to an int, return true, which is safe
477
if(constraintDouble == Double.MAX_VALUE || doubleToCompare == Double.MAX_VALUE) {
478                     log.warn("either " + constraintValue + " or " + valueToCompare + " could not be cast to type double (while that is supposed to be their type)");
479                     return true;
480                 }
481                 return floatMatches(constraintDouble, doubleToCompare, operator);
482             } else if (fieldType.equals(Date.class)) {
483                 long constraintLong = Casting.toLong(constraintValue, Long.MAX_VALUE);
484                 long longToCompare = Casting.toLong(valueToCompare, Long.MAX_VALUE);
485
486                 //if either value could not be cast to an int, return true, which is safe
487
if(constraintLong == Long.MAX_VALUE || longToCompare == Long.MAX_VALUE) {
488                     log.warn("either " + constraintValue + " or " + valueToCompare + " could not be cast to type long (while they are supposed to be of type Date supposed to be their type)");
489                     return true;
490                 }
491                 return intMatches(constraintLong, longToCompare, operator);
492             } else if (fieldType.equals(Integer JavaDoc.class)) {
493                 int constraintInt = Casting.toInt(constraintValue, Integer.MAX_VALUE);
494                 int intToCompare = Casting.toInt(valueToCompare, Integer.MAX_VALUE);
495
496                 //if either value could not be cast to an int, return true, which is safe
497
if(constraintInt == Integer.MAX_VALUE || intToCompare == Integer.MAX_VALUE) {
498                     log.warn("either " + constraintValue + " or " + valueToCompare + " could not be cast to type int (while that is supposed to be their type)");
499                     return true;
500                 }
501                 return intMatches(constraintInt, intToCompare, operator);
502             } else if (fieldType.equals(Long JavaDoc.class)) {
503                 long constraintLong = Casting.toLong(constraintValue, Long.MAX_VALUE);
504                 long longToCompare = Casting.toLong(valueToCompare, Long.MAX_VALUE);
505 // if either value could not be cast to a long, return true, which is safe
506
if(constraintLong == Long.MAX_VALUE || longToCompare == Long.MAX_VALUE) {
507                     // how can this ever happen?
508
log.warn("either [" + constraintValue +"] " + (constraintValue == null ? "": "of type " + constraintValue.getClass().getName()) +
509                              " or [" + valueToCompare + "] of type " + (valueToCompare == null ? "": "of type " + valueToCompare.getClass().getName()) +
510                              " could not be cast to type long (while that is supposed to be their type)");
511                     return true;
512
513                 }
514                 return intMatches(constraintLong, longToCompare, operator);
515             } else if (fieldType.equals(Node.class)) {
516                 if(constraintValue instanceof MMObjectNode) constraintValue = new Integer JavaDoc(((MMObjectNode)constraintValue).getNumber());
517                 if(valueToCompare instanceof MMObjectNode) valueToCompare = new Integer JavaDoc(((MMObjectNode)valueToCompare).getNumber());
518                 int constraintInt = Casting.toInt(constraintValue, Integer.MAX_VALUE);
519                 int intToCompare = Casting.toInt(valueToCompare, Integer.MAX_VALUE);
520 // if either value could not be cast to a Node, return true, which is safe
521
if(constraintInt == Integer.MAX_VALUE || intToCompare == Integer.MAX_VALUE) {
522                     log.warn("either [" + constraintValue +"] " + (constraintValue == null ? "": "of type " + constraintValue.getClass().getName()) +
523                              " or [" + valueToCompare + "] of type " + (valueToCompare == null ? "": "of type " + valueToCompare.getClass().getName()) +
524                              " could not be cast to type int (while they should be type node)");
525                     return true;
526
527                 }
528                 return intMatches(constraintInt, intToCompare, operator);
529             } else if (fieldType.equals(String JavaDoc.class) || fieldType.equals(org.w3c.dom.Document JavaDoc.class)) {
530                 String JavaDoc constraintString = Casting.toString(constraintValue);
531                 String JavaDoc stringToCompare = Casting.toString(valueToCompare);
532                 return stringMatches(constraintString, stringToCompare, operator, isCaseSensitive);
533             }
534
535             return false;
536         }
537
538         private boolean floatMatches(double constraintDouble, double doubleTocompare, int operator) {
539             switch(operator) {
540             case FieldCompareConstraint.EQUAL: return doubleTocompare == constraintDouble;
541             case FieldCompareConstraint.GREATER: return doubleTocompare > constraintDouble;
542             case FieldCompareConstraint.GREATER_EQUAL: return doubleTocompare >= constraintDouble;
543             case FieldCompareConstraint.LESS: return doubleTocompare < constraintDouble;
544             case FieldCompareConstraint.LESS_EQUAL: return doubleTocompare <= constraintDouble;
545             case FieldCompareConstraint.NOT_EQUAL: return doubleTocompare != constraintDouble;
546             default:
547                 log.warn("operator " + FieldCompareConstraint.OPERATOR_DESCRIPTIONS[operator] + "for any numeric type");
548                 return true;
549
550             }
551         }
552
553         private boolean intMatches(long constraintLong, long longToCompare, int operator) {
554             switch(operator) {
555             case FieldCompareConstraint.EQUAL: return longToCompare == constraintLong;
556             case FieldCompareConstraint.GREATER: return longToCompare > constraintLong;
557             case FieldCompareConstraint.GREATER_EQUAL: return longToCompare >= constraintLong;
558             case FieldCompareConstraint.LESS: return longToCompare < constraintLong;
559             case FieldCompareConstraint.LESS_EQUAL: return longToCompare <= constraintLong;
560             case FieldCompareConstraint.NOT_EQUAL: return longToCompare != constraintLong;
561             default:
562                 log.warn("operator " + FieldCompareConstraint.OPERATOR_DESCRIPTIONS[operator] + "for any numeric type");
563                 return true;
564             }
565         }
566
567         private boolean stringMatches(String JavaDoc constraintString, String JavaDoc stringToCompare, int operator, boolean isCaseSensitive) {
568             switch(operator) {
569             case FieldCompareConstraint.EQUAL: return stringToCompare.equals(constraintString);
570                 // TODO: MM: I think depending on the database configuration the case-sensitivity may be important in the following 4:
571
case FieldCompareConstraint.GREATER: return stringToCompare.compareTo(constraintString) > 0;
572             case FieldCompareConstraint.LESS: return stringToCompare.compareTo(constraintString) < 0;
573             case FieldCompareConstraint.LESS_EQUAL: return stringToCompare.compareTo(constraintString) <= 0;
574             case FieldCompareConstraint.GREATER_EQUAL: return stringToCompare.compareTo(constraintString) >= 0;
575             case FieldCompareConstraint.LIKE: return likeMatches(constraintString, stringToCompare, isCaseSensitive);
576             case FieldCompareConstraint.NOT_EQUAL: return ! stringToCompare.equals(constraintString);
577             default:
578                 log.warn("operator " + FieldCompareConstraint.OPERATOR_DESCRIPTIONS[operator] + "is not supported for type String");
579                 return true;
580             }
581         }
582
583         private boolean likeMatches(String JavaDoc constraintString, String JavaDoc stringToCompare, boolean isCaseSensitive){
584             if (log.isTraceEnabled()) {
585                 log.trace("** method: likeMatches() stringToCompare: " + stringToCompare + ", constraintString: " + constraintString );
586             }
587             if(isCaseSensitive){
588                 constraintString = constraintString.toLowerCase();
589                 stringToCompare = stringToCompare.toLowerCase();
590             }
591             char[] chars = constraintString.toCharArray();
592             StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
593
594             for(int i = 0; i < chars.length; i++){
595                 if(chars[i] == '?'){
596                     sb.append(".");
597                 } else if(chars[i] == '%'){
598                     sb.append(".*");
599                 } else if(escapeChars.indexOf(chars[i]) > -1){
600                     sb.append("\\");
601                     sb.append(chars[i]);
602                 } else{
603                     sb.append(chars[i]);
604                 }
605             }
606             if (log.isDebugEnabled()) {
607                 log.trace("** new pattern: " + sb.toString());
608             }
609             return stringToCompare.matches(sb.toString());
610         }
611
612         protected Class JavaDoc getFieldTypeClass(StepField stepField) {
613             MMBase mmbase = MMBase.getMMBase();
614             // why it this checked anyway?
615
CoreField field = mmbase.getBuilder(stepField.getStep().getTableName()).getField(stepField.getFieldName());
616             DataType fieldType = field.getDataType();
617             Class JavaDoc fieldTypeClass = fieldType.getTypeAsClass();
618             if( fieldTypeClass.equals(Boolean JavaDoc.class) ||
619                 fieldTypeClass.equals(Date.class) ||
620                 fieldTypeClass.equals(Integer JavaDoc.class) ||
621                 fieldTypeClass.equals(Long JavaDoc.class) ||
622                 fieldTypeClass.equals(Float JavaDoc.class) ||
623                 fieldTypeClass.equals(Double JavaDoc.class) ||
624                 fieldTypeClass.equals(Node.class) ||
625                 fieldTypeClass.equals(String JavaDoc.class) ||
626                 fieldTypeClass.equals(org.w3c.dom.Document JavaDoc.class)) {
627                 if (log.isDebugEnabled()) {
628                     log.debug("** found field type: " + fieldTypeClass.getName());
629                 }
630             } else {
631                 throw new RuntimeException JavaDoc("Field type " + fieldTypeClass + " is not supported");
632             }
633             return fieldTypeClass;
634         }
635
636     }
637
638
639
640     private static class BasicFieldValueConstraintMatcher extends FieldCompareConstraintMatcher {
641         private final Class JavaDoc fieldTypeClass;
642         protected final StepField stepField;
643         protected final BasicFieldValueConstraint wrappedFieldValueConstraint;
644
645         public BasicFieldValueConstraintMatcher(BasicFieldValueConstraint constraint) {
646             stepField = constraint.getField();
647             if (log.isDebugEnabled()) {
648                 log.debug("** builder: " + stepField.getStep().getTableName()+". field: " + stepField.getFieldName());
649             }
650             fieldTypeClass = getFieldTypeClass(stepField);
651             wrappedFieldValueConstraint = constraint;
652
653         }
654
655         protected int getOperator() {
656             return wrappedFieldValueConstraint.getOperator();
657         }
658         /**
659          * Check the values to see if the values of the node matches the constraint.
660          */

661         public boolean nodeMatchesConstraint(Map valuesToMatch, NodeEvent event) {
662             log.debug("**method: nodeMatchesConstraint");
663             //if(! eventApplies(valuesToMatch, event)) throw new FieldComparisonException("constraint " + wrappedFieldCompareConstraint.toString() + "does not match event of type " +event.getBuilderName());
664
boolean matches = valueMatches(fieldTypeClass,
665                                            wrappedFieldValueConstraint.getValue(),
666                                            valuesToMatch.get(stepField.getFieldName()),
667                                            wrappedFieldValueConstraint.isCaseSensitive());
668             return matches != wrappedFieldValueConstraint.isInverse();
669         }
670
671         public String JavaDoc toString(){
672             return "Field Value Matcher. operator: " + FieldCompareConstraint.OPERATOR_DESCRIPTIONS[wrappedFieldValueConstraint.getOperator()] +
673             ", value: " + wrappedFieldValueConstraint.getValue().toString() + ", step: " +stepField.getStep().getTableName() +
674             ", field name: " + stepField.getFieldName();
675         }
676
677
678         /**
679          * An event applies to a field value constraint wrapper if the wrapper is of the same type as the event, and the field
680          * that is being checked is in the 'changed' fields map (valuesToMatch)
681          */

682         public boolean eventApplies(Map valuesToMatch, NodeEvent event) {
683             return
684                 wrappedFieldValueConstraint.getField().getStep().getTableName().equals(event.getBuilderName()) &&
685                 valuesToMatch.containsKey(wrappedFieldValueConstraint.getField().getFieldName());
686         }
687
688     }
689
690
691
692
693     /**
694      * @since MMBase-1.8.1
695      */

696     private static class BasicFieldValueBetweenConstraintMatcher extends AbstractConstraintMatcher {
697
698         protected final StepField stepField;
699         protected final BasicFieldValueBetweenConstraint wrappedFieldConstraint;
700
701         public BasicFieldValueBetweenConstraintMatcher(BasicFieldValueBetweenConstraint constraint) {
702             stepField = constraint.getField();
703             wrappedFieldConstraint = constraint;
704         }
705
706         public boolean nodeMatchesConstraint(Map valuesToMatch, NodeEvent event) {
707             return true;
708         }
709
710         public boolean eventApplies(Map valuesToMatch, NodeEvent event) {
711             return true;
712         }
713
714         public String JavaDoc toString() {
715             return "Field Value Between Matcher. operator: " +
716             ", step: " +stepField.getStep().getTableName() +
717             ", field name: " + stepField.getFieldName();
718         }
719
720     }
721
722     /**
723      * @since MMBase-1.8.1
724      */

725     private static class BasicFieldValueInConstraintMatcher extends FieldCompareConstraintMatcher {
726         private final Class JavaDoc fieldTypeClass;
727         protected final StepField stepField;
728         protected final BasicFieldValueInConstraint wrappedFieldValueInConstraint;
729
730         public BasicFieldValueInConstraintMatcher(BasicFieldValueInConstraint constraint) {
731             stepField = constraint.getField();
732             fieldTypeClass = getFieldTypeClass(stepField);
733             wrappedFieldValueInConstraint = constraint;
734         }
735
736         protected int getOperator() {
737             return FieldCompareConstraint.EQUAL;
738         }
739
740         /**
741          * Check the values to see if the node's value matches the constraint.
742          */

743         public boolean nodeMatchesConstraint(Map valuesToMatch, NodeEvent event) {
744             log.debug("**method: nodeMatchesConstraint");
745             SortedSet values = wrappedFieldValueInConstraint.getValues();
746             boolean matches = false;
747             Iterator i = values.iterator();
748             while (i.hasNext() && !matches) {
749                 Object JavaDoc value = i.next();
750                 matches = valueMatches(fieldTypeClass,
751                                        value,
752                                        valuesToMatch.get(stepField.getFieldName()),
753                                        wrappedFieldValueInConstraint.isCaseSensitive());
754             }
755             return matches != wrappedFieldValueInConstraint.isInverse();
756         }
757
758         public String JavaDoc toString(){
759             return "Field Value IN Matcher. operator: " +
760                 ", value: " + wrappedFieldValueInConstraint.getValues().toString() + ", step: " +stepField.getStep().getTableName() +
761                 ", field name: " + stepField.getFieldName();
762         }
763
764
765
766         public boolean eventApplies(Map valuesToMatch, NodeEvent event) {
767             return
768                 wrappedFieldValueInConstraint.getField().getStep().getTableName().equals(event.getBuilderName()) &&
769                 valuesToMatch.containsKey(wrappedFieldValueInConstraint.getField().getFieldName());
770         }
771     }
772
773
774     private void logResult(String JavaDoc comment, SearchQuery query, Event event, MMObjectNode node){
775         if(log.isDebugEnabled()){
776             String JavaDoc role="";
777             // a small hack to limit the output
778
if (event instanceof RelationEvent) {
779                 //get the role name
780
RelationEvent revent = (RelationEvent) event;
781                 MMObjectNode relDef = MMBase.getMMBase().getBuilder("reldef").getNode(revent.getRole());
782                 role = " role: " + relDef.getStringValue("sname") + "/" + relDef.getStringValue("dname");
783                 //filter the 'object' events
784
if (revent.getRelationSourceType().equals("object")
785                         || revent.getRelationDestinationType().equals("object"))
786                     return;
787             }
788             try {
789                 log.debug("\n******** \n**" + comment + "\n**" + event.toString() + role + "\n**nodevalues: " + (node == null ? "NODE NULL" : "" + node.getValues()) + "\n**"
790                         + sqlHandler.toSql(query, sqlHandler) + "\n******");
791             } catch (SearchQueryException e) {
792                 log.error(e);
793             }
794         }
795     }
796
797     /**
798      * Combines to Maps to one new map. One map is 'leading' and determins wich keys are mapped. The second map can override values, if it contains the same mapping.
799      * @since MMBase-1.8.1
800      */

801     private static class LinkMap extends AbstractMap {
802         private final Map map1;
803         private final Map map2;
804         LinkMap(Map m1, Map m2) {
805             map1 = m1; map2 = m2;
806         }
807         public Set entrySet() {
808             return new AbstractSet() {
809                 public Iterator iterator() {
810                     final Iterator i = map1.entrySet().iterator();
811                     return new Iterator() {
812                         public boolean hasNext() {
813                             return i.hasNext();
814                         }
815                         public Object JavaDoc next() {
816                             final Map.Entry entry1 = (Map.Entry) i.next();
817                             final Object JavaDoc key = entry1.getKey();
818                             return new Map.Entry() {
819                                 public Object JavaDoc getKey() {
820                                     return key;
821                                 }
822                                 public Object JavaDoc getValue() {
823                                     if (map2.containsKey(key)) {
824                                         return map2.get(key);
825                                     } else {
826                                         return entry1.getValue();
827                                     }
828                                 }
829                                 public Object JavaDoc setValue(Object JavaDoc v) {
830                                     throw new UnsupportedOperationException JavaDoc();
831                                 }
832                             };
833                         }
834                         public void remove() {
835                             throw new UnsupportedOperationException JavaDoc();
836                         }
837                     };
838                 }
839                 public int size() {
840                     return map1.size();
841                 }
842             };
843         }
844         public int size() {
845             return map1.size();
846         }
847         public Object JavaDoc get(Object JavaDoc key) {
848             if (map2.containsKey(key)) {
849                 return map2.get(key);
850             } else {
851                 return map1.get(key);
852             }
853         }
854         public boolean containsKey(Object JavaDoc key) {
855             return map1.containsKey(key);
856         }
857     }
858
859 }
860
Popular Tags