001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.cache;
011:
012: import java.util.Iterator;
013: import java.util.List;
014: import java.util.Set;
015:
016: import org.mmbase.core.event.*;
017: import org.mmbase.module.core.*;
018: import org.mmbase.storage.search.*;
019: import org.mmbase.storage.search.implementation.database.BasicSqlHandler;
020: import org.mmbase.util.logging.Logger;
021: import org.mmbase.util.logging.Logging;
022:
023: /**
024: * This release strategy is a bit better than 'BasicReleaseStrategy, and also a bit more sophisticated.
025: *
026: * @since MMBase 1.8
027: * @author Ernst Bunders
028: * @version $Id: BetterStrategy.java,v 1.31 2007/12/07 14:41:59 ernst Exp $
029: */
030: public class BetterStrategy extends ReleaseStrategy {
031:
032: //public BetterStrategy() {}
033: private static final BasicSqlHandler sqlHandler = new BasicSqlHandler();
034: private static final Logger log = Logging
035: .getLoggerInstance(BetterStrategy.class);
036:
037: private static final Logger nodeEventLog = Logging
038: .getLoggerInstance(BetterStrategy.class.getName()
039: + ".nodeevent");
040: private static final Logger relationEventLog = Logging
041: .getLoggerInstance(BetterStrategy.class.getName()
042: + ".relationevent");
043:
044: // inheritdoc
045: public String getName() {
046: return "Better Release Strategy";
047: }
048:
049: /*
050: * (non-Javadoc)
051: *
052: * @see org.mmbase.cache.QueryResultCacheReleaseStrategy#getDescription()
053: */
054: public String getDescription() {
055: return "This strategy performs all kinds of checks to test if the node or relation event actually matches the query. "
056: + "For node events the type is checked, as well as some other things. For relation events the type is checked as well as "
057: + "the source and destination. Then there are some other things like: 'new node events should not flush queries with "
058: + "more than one step, because they have no relation yet'. It also checks if a certain change in a node actually can affect the "
059: + "outcome of a query.";
060: }
061:
062: protected boolean doEvaluate(RelationEvent event,
063: SearchQuery query, List<MMObjectNode> cachedResult) {
064: return shouldRelease(event, query);
065: }
066:
067: /**
068: * @see org.mmbase.cache.ReleaseStrategy#doEvaluate(org.mmbase.core.event.NodeEvent,
069:
070: * org.mmbase.storage.search.SearchQuery, java.util.List)
071: *
072: * @return true if query should be released
073: */
074: protected final boolean doEvaluate(NodeEvent event,
075: SearchQuery query, List<MMObjectNode> cachedResult) {
076: if (log.isDebugEnabled()) {
077: log.debug(event.toString());
078: }
079: return shouldRelease(event, query);
080: }
081:
082: /**
083: * Check all the rules that concern node events. if no rules match we return <code>true</code>.
084: * @param event
085: * @param query
086: * @return
087: */
088: private boolean shouldRelease(NodeEvent event, SearchQuery query) {
089: switch (event.getType()) {
090: case Event.TYPE_NEW:
091: // query has more than one step, all 'new node' events can be ignored, because this
092: // node has no relations yet.
093: if (query.getSteps().size() > 1) {
094: logResult(
095: "no flush: 'new node' event in multistep query",
096: query, event);
097: return false; // don't release
098: }
099: if (!checkSteps(event, query)) {
100: logResult(
101: "no flush: the query has nodes set and this event's node is not one of them, or this step has no steps of corresponding type",
102: query, event);
103: return false;
104: }
105: break;
106:
107: case Event.TYPE_DELETE:
108: if (!checkSteps(event, query)) {
109: logResult(
110: "no flush: the query has nodes set and this event's node is not one of them, or this step has no steps of corresponding type",
111: query, event);
112: return false;
113: }
114: break;
115:
116: case Event.TYPE_CHANGE:
117: if (!checkSteps(event, query)) {
118: logResult(
119: "no flush: the query has nodes set and this event's node is not one of them, or this step has no steps of corresponding type",
120: query, event);
121: return false;
122: }
123: //if the changed field(s) do not occur in the fields or constraint section
124: //of the query, it does not have to be flushed
125: if (!checkChangedFieldsMatch(event, query)) {
126: logResult(
127: "no flush: the fields that have changed are not used in the query",
128: query, event);
129: return false;
130: }
131:
132: //if the query is aggregating, and of type count, and the changed fields(s) do
133: //not occur in the constraint: don't flush the query
134: if (checkAggregationCount(event, query)) {
135: logResult(
136: "query is aggregating and fields are of type count, changed fields do not affect the query result",
137: query, event);
138: return false;
139: }
140:
141: }
142: logResult("flush: no reason not to", query, event);
143: return true;
144: }
145:
146: /**
147: * check all the rules that concern relation events. if no rules match we return
148: * <code>true</code>.
149: * @param event
150: * @param query
151: * @return
152: */
153: private boolean shouldRelease(RelationEvent event, SearchQuery query) {
154:
155: /*
156: * Here are all the preconditions that must be met to proceed. Basic checks to determin
157: * if this event has to be evaluated on this query at all
158: */
159:
160: //query has one step and the event is a relation event
161: if (query.getSteps().size() == 1) {
162: logResult(
163: "no flush: query has one step and event is relation event",
164: query, event);
165: return false;//don't release
166: }
167:
168: // if a query has more steps that one and the event is a relation event
169: // we check if the role of the relation is allso in the query.
170: if (!checkPathMatches(event, query)) {
171: logResult(
172: "no flush: either source, destination or role does not match to the query",
173: query, event);
174: return false;
175: }
176:
177: switch (event.getType()) {
178: case Event.TYPE_NEW:
179: log.debug(">> relation event type new");
180: /*
181: * Put all rules here that apply to new relation events
182: */
183:
184: break;
185:
186: case Event.TYPE_DELETE:
187: log.debug(">> relation event type delete");
188: /*
189: * Put all rules here that apply to removed relation events
190: */
191:
192: break;
193:
194: case Event.TYPE_CHANGE:
195: log.debug(">> relation event type changed");
196: /*
197: * Put all rules here that apply to changed relation events
198: */
199:
200: //if the changed field(s) do not occur in the fields or constraint section
201: //of the query, it does not have to be flushed
202: if (!checkChangedFieldsMatch(event.getNodeEvent(), query)) {
203: logResult(
204: "no flush: the changed relation fields do not match the fields or constraints of the query",
205: query, event);
206: return false;
207: }
208:
209: break;
210:
211: }
212: logResult("flush: no reason not to", query, event);
213: return true;
214: }
215:
216: /**
217: * @param event
218: * @param query
219: * @return true if query is aggragating, of type count, and the changed fields do
220: * not occur in the constraint (no flush)
221: */
222: private boolean checkAggregationCount(NodeEvent event,
223: SearchQuery query) {
224: log.debug("method: checkAggregationCount()");
225: if (!query.isAggregating()) {
226: return false;
227: }
228: //test if all changed fields are aggreagting and of type count, if not: return false;
229: for (StepField field : query.getFields()) {
230: if (event.getChangedFields().contains(field.getFieldName())) {
231: if (!(field instanceof AggregatedField)) {
232: return false;
233: }
234: if (!(((AggregatedField) field).getAggregationType() == AggregatedField.AGGREGATION_TYPE_COUNT)) {
235: return false;
236: }
237: }
238: }
239: //now check the constraints: if there are any constraints for any of the changed fields: false;
240: Constraint constraint = query.getConstraint();
241: if (constraint == null) {
242: return true;
243: }
244: MMObjectBuilder eventBuilder = MMBase.getMMBase().getBuilder(
245: event.getBuilderName());
246: for (String fieldName : event.getChangedFields()) {
247: if (getConstraintsForField(fieldName, eventBuilder,
248: constraint, query).size() > 0) {
249: return false;
250: }
251: }
252: //all tests survived, query should not be flushed
253: return true;
254: }
255:
256: /**
257: * @param event
258: * @param query
259: * @return true if sourcetype, role and destination from relation event match query
260: */
261: private boolean checkPathMatches(RelationEvent event,
262: SearchQuery query) {
263: // check if the path in the query maches the relation event:
264: // - the source and destination objects should be there
265: // - the role either matches or is not specified
266: if (log.isDebugEnabled()) {
267: log.debug("method: checkPathMatches()");
268: log.debug(event.toString());
269: log.debug("query: " + query.toString());
270: }
271: MMBase mmb = MMBase.getMMBase();
272: String eventSourceType = event.getRelationSourceType();
273: String eventDestType = event.getRelationDestinationType();
274: MMObjectBuilder eventSource = mmb.getBuilder(eventSourceType);
275: MMObjectBuilder eventDest = mmb.getBuilder(eventDestType);
276:
277: Iterator<Step> i = query.getSteps().iterator();
278: Step prevStep = i.next();
279: String stepDest = prevStep.getTableName();
280: while (i.hasNext()) {
281: String stepSource = stepDest;
282: RelationStep step = (RelationStep) i.next();
283: Step nextStep = i.next();
284: stepDest = nextStep.getTableName();
285: //when source or destination are null (no active builders), this event can not affect the cache of this mmbase app
286: boolean matchesProper = (eventSource != null && eventDest != null)
287: && (eventSourceType.equals(stepSource) || eventSource
288: .isExtensionOf(mmb.getBuilder(stepSource)))
289: && (eventDestType.equals(stepDest) || eventDest
290: .isExtensionOf(mmb.getBuilder(stepDest)));
291: boolean matches = matchesProper || ( // matchesInverse
292: (eventSource != null && eventDest != null)
293: && (eventDestType.equals(stepSource) || eventDest
294: .isExtensionOf(mmb
295: .getBuilder(stepSource))) && (eventSourceType
296: .equals(stepDest) || eventSource
297: .isExtensionOf(mmb.getBuilder(stepDest))));
298:
299: Integer role = step.getRole();
300: if (matches
301: && (role == null || role.intValue() == event
302: .getRole())) {
303: return true;
304: }
305: }
306: return false;
307: }
308:
309: /**
310: * Checks if a query object contains reference to (one of) the changed field(s).
311: * Matches are looked for in the stepfields and in the constraints.
312: * @param event
313: * @param query
314: * @return true if the type of the node for this event matches either a stepfield or a constriant
315: */
316: private boolean checkChangedFieldsMatch(NodeEvent event,
317: SearchQuery query) {
318: if (log.isDebugEnabled()) {
319: log
320: .debug("method: checkChangedFieldsMatch(). changed fields: "
321: + event.getChangedFields().size());
322: }
323: boolean constraintsFound = false;
324: boolean fieldsFound = false;
325: boolean sortordersFound = false;
326: String eventBuilderName = event.getBuilderName();
327: MMBase mmb = MMBase.getMMBase();
328: MMObjectBuilder eventBuilder = mmb.getBuilder(eventBuilderName);
329: search: for (String fieldName : event.getChangedFields()) {
330: //first test the constraints
331: List<Constraint> constraintsForFieldList = getConstraintsForField(
332: fieldName, eventBuilder, query.getConstraint(),
333: query);
334: if (constraintsForFieldList.size() > 0) {
335: constraintsFound = true;
336: if (log.isDebugEnabled()) {
337: log.debug("matching constraint found: "
338: + constraintsForFieldList.size());
339: }
340: break search;
341: }
342:
343: for (StepField field : query.getFields()) {
344: if (field.getFieldName().equals(fieldName)
345: && (field.getStep().getTableName().equals(
346: eventBuilderName) || eventBuilder
347: .isExtensionOf(mmb.getBuilder(field
348: .getStep().getTableName())))) {
349: fieldsFound = true;
350: if (log.isDebugEnabled()) {
351: log.debug("matching field found: "
352: + field.getStep().getTableName() + "."
353: + field.getFieldName());
354: }
355: break search;
356: }
357: }
358:
359: //test the sortorders
360: List<SortOrder> sortordersForFieldList = getSortordersForField(
361: fieldName, eventBuilder, query.getSortOrders(),
362: query);
363: if (sortordersForFieldList.size() > 0) {
364: sortordersFound = true;
365: if (log.isDebugEnabled()) {
366: log.debug("matching sortorders found: "
367: + sortordersForFieldList.size());
368: }
369: break search;
370: }
371: }
372: if (log.isDebugEnabled()) {
373: String logMsg = "";
374: if (!sortordersFound)
375: logMsg = logMsg + " no matching sortorders found";
376: if (!fieldsFound)
377: logMsg = "no matching fields found, ";
378: if (!constraintsFound)
379: logMsg = logMsg + " no matching constraints found";
380: log.debug(logMsg);
381: }
382: //now test the result
383: return sortordersFound || fieldsFound || constraintsFound;
384: }
385:
386: /**
387: * This method investigates all the steps of a query that correspond to the nodetype of the
388: * node event. for each step a check is made if this step has 'nodes' set, and so, if the changed
389: * node is one of them.
390: *
391: * Also it checks if the step is of a corresponding type. It returns also false if no step
392: * matched the type of the node event.
393: * @param event a NodeEvent
394: * @param query
395: * @return true if (all) the step(s) matching this event have nodes set, and non of these
396: * match the number of the changed node (in which case the query should not be flused)
397: */
398: private boolean checkSteps(NodeEvent event, SearchQuery query) {
399: //this simple optimization only works for nodeEvents
400: MMBase mmb = MMBase.getMMBase();
401: String eventTable = event.getBuilderName();
402: MMObjectBuilder eventBuilder = mmb.getBuilder(eventTable);
403: //perhaps the builder of the event is locally inactive
404: if (eventBuilder != null) {
405: for (Step step : query.getSteps()) {
406: String table = step.getTableName();
407: if (!(table.equals(eventTable) || eventBuilder
408: .isExtensionOf(mmb.getBuilder(table))))
409: continue;
410: Set<Integer> nodes = step.getNodes();
411: if (nodes == null || nodes.size() == 0
412: || nodes.contains(event.getNodeNumber())) {
413: return true;
414: }
415: }
416: }
417: return false;
418: }
419:
420: private void logResult(String comment, SearchQuery query,
421: Event event) {
422: if (log.isDebugEnabled() || nodeEventLog.isDebugEnabled()
423: || relationEventLog.isDebugEnabled()) {
424: String role = "";
425: Logger logger;
426: if (event instanceof RelationEvent) {
427: logger = relationEventLog;
428: } else if (event instanceof NodeEvent) {
429: logger = nodeEventLog;
430: } else {
431: logger = log;
432: }
433:
434: // a small hack to limit the output
435: if (event instanceof RelationEvent) {
436: //get the role name
437: RelationEvent revent = (RelationEvent) event;
438: MMObjectNode relDef = MMBase.getMMBase().getBuilder(
439: "reldef").getNode(revent.getRole());
440: role = " role: " + relDef.getStringValue("sname") + "/"
441: + relDef.getStringValue("dname");
442: //filter the 'object' events
443: if (revent.getRelationSourceType().equals("object")
444: || revent.getRelationDestinationType().equals(
445: "object"))
446: return;
447: }
448: try {
449: logger.debug("\n******** \n**" + comment + "\n**"
450: + event.toString() + role + "\n**"
451: + sqlHandler.toSql(query, sqlHandler)
452: + "\n******");
453: } catch (SearchQueryException e) {
454: logger.warn(e);
455: }
456: }
457: }
458: }
|