001: /*
002: * This software is OSI Certified Open Source Software. OSI Certified is a
003: * certification mark of the Open Source Initiative. The license (Mozilla
004: * version 1.0) can be read at the MMBase site. See
005: * http://www.MMBase.org/license
006: */
007: package org.mmbase.cache;
008:
009: import java.util.*;
010:
011: import org.mmbase.core.event.Event;
012: import org.mmbase.core.event.NodeEvent;
013: import org.mmbase.core.event.NodeEventListener;
014: import org.mmbase.core.event.RelationEvent;
015: import org.mmbase.core.event.RelationEventListener;
016: import org.mmbase.module.core.*;
017: import org.mmbase.util.logging.*;
018:
019: import org.mmbase.storage.search.*;
020:
021: import org.mmbase.bridge.implementation.BasicQuery;
022:
023: /**
024: * This cache provides a base implementation to cache the result of
025: * SearchQuery's. Such a cache links a SearchQuery object to a list of
026: * MMObjectNodes. A cache entry is automaticly invalidated if arbitrary node of
027: * one of the types present in the SearchQuery is changed (,created or deleted).
028: * This mechanism is not very subtle but it is garanteed to be correct. It means
029: * though that your cache can be considerably less effective for queries
030: * containing node types from which often node are edited.
031: *
032: * @author Daniel Ockeloen
033: * @author Michiel Meeuwissen
034: * @author Bunst Eunders
035: * @version $Id: QueryResultCache.java,v 1.45 2007/09/17 16:53:01 pierre Exp $
036: * @since MMBase-1.7
037: * @see org.mmbase.storage.search.SearchQuery
038: */
039:
040: abstract public class QueryResultCache extends
041: Cache<SearchQuery, List<MMObjectNode>> implements
042: NodeEventListener, RelationEventListener {
043:
044: private static final Logger log = Logging
045: .getLoggerInstance(QueryResultCache.class);
046:
047: /**
048: * This map contains the possible counts of queries grouped by type in this cache.
049: * A query with multiple steps (types) will increase all counters.
050: * A relation role name is considered a type
051: * This cache will not invalidate when an event does not mention one of these types
052: * The cache will be evaluated when a parent type is in this map.
053: */
054: private Map<String, Integer> typeCounters = new HashMap<String, Integer>();
055:
056: /**
057: * This is the default release strategy. Actually it is a container for any
058: * number of 'real' release strategies
059: *
060: * @see ChainedReleaseStrategy
061: */
062: private final ChainedReleaseStrategy releaseStrategy;
063:
064: QueryResultCache(int size) {
065: super (size);
066: releaseStrategy = new ChainedReleaseStrategy();
067: log.service("Instantiated a " + this .getClass().getName()
068: + " (" + releaseStrategy + ")"); // should happen limited number of times
069: MMBase.getMMBase().addNodeRelatedEventsListener("object", this );
070: }
071:
072: /**
073: * @param strategies
074: */
075: public void addReleaseStrategies(List<ReleaseStrategy> strategies) {
076: if (strategies != null) {
077: for (ReleaseStrategy element : strategies) {
078: if (log.isDebugEnabled()) {
079: log.debug(("adding strategy " + element.getName()
080: + " to cache " + getName()));
081: }
082: addReleaseStrategy(element);
083: }
084: }
085: }
086:
087: /**
088: * This method lets you add a release strategy to the cache. It will in fact
089: * be added to <code>ChainedReleaseStrategy</code>, which
090: * is the default base release strategy.
091: * @param releaseStrategy A releaseStrategy to add.
092: */
093: public void addReleaseStrategy(ReleaseStrategy releaseStrategy) {
094: this .releaseStrategy.addReleaseStrategy(releaseStrategy);
095: }
096:
097: /**
098: * @return Returns the releaseStrategy.
099: */
100: public ChainedReleaseStrategy getReleaseStrategy() {
101: return releaseStrategy;
102: }
103:
104: /**
105: * Puts a search result in this cache.
106: */
107: public synchronized List<MMObjectNode> put(SearchQuery query,
108: List<MMObjectNode> queryResult) {
109: if (!checkCachePolicy(query))
110: return null;
111: if (query instanceof BasicQuery) {
112: query = ((BasicQuery) query).getQuery();
113: }
114: increaseCounters(query, typeCounters);
115: return super .put(query, queryResult);
116: }
117:
118: /**
119: * Removes an object from the cache. It alsos remove the watch from the
120: * observers which are watching this entry.
121: *
122: * @param key A SearchQuery object.
123: */
124: public synchronized List<MMObjectNode> remove(SearchQuery key) {
125: if (key instanceof BasicQuery) {
126: key = ((BasicQuery) key).getQuery();
127: }
128: List<MMObjectNode> result = super .remove(key);
129: decreaseCounters(key, typeCounters);
130: return result;
131: }
132:
133: private void increaseCounters(SearchQuery query,
134: Map<String, Integer> counters) {
135: for (Step step : query.getSteps()) {
136: String stepName = step.getTableName();
137: if (counters.containsKey(stepName)) {
138: int count = counters.get(stepName);
139: counters.put(stepName, count + 1);
140: } else {
141: counters.put(stepName, 1);
142: }
143: }
144: }
145:
146: private void decreaseCounters(SearchQuery query,
147: Map<String, Integer> counters) {
148: for (Step step : query.getSteps()) {
149: String stepName = step.getTableName();
150: if (counters.containsKey(stepName)) {
151: int count = counters.get(stepName);
152: if (count > 1) {
153: counters.put(stepName, count - 1);
154: } else {
155: counters.remove(stepName);
156: }
157: }
158: }
159: }
160:
161: public String toString() {
162: return this .getClass().getName() + " " + getName();
163: }
164:
165: /**
166: * @see org.mmbase.core.event.RelationEventListener#notify(org.mmbase.core.event.RelationEvent)
167: */
168: public void notify(RelationEvent event) {
169: if (containsType(event)) {
170: nodeChanged(event);
171: }
172: }
173:
174: private boolean containsType(RelationEvent event) {
175: if (typeCounters.containsKey("object")) {
176: return true;
177: }
178: if (typeCounters.containsKey(event.getRelationSourceType())
179: || typeCounters.containsKey(event
180: .getRelationDestinationType())) {
181: return true;
182: }
183: MMBase mmb = MMBase.getMMBase();
184: String roleName = mmb.getRelDef().getBuilderName(
185: Integer.valueOf(event.getRole()));
186: if (typeCounters.containsKey(roleName)) {
187: return true;
188: }
189: MMObjectBuilder srcbuilder = mmb.getBuilder(event
190: .getRelationSourceType());
191: if (srcbuilder == null) {
192: return false;
193: }
194: for (MMObjectBuilder parent : srcbuilder.getAncestors()) {
195: if (typeCounters.containsKey(parent.getTableName())) {
196: return true;
197: }
198: }
199: MMObjectBuilder destbuilder = mmb.getBuilder(event
200: .getRelationDestinationType());
201: if (destbuilder == null) {
202: return false;
203: }
204: for (MMObjectBuilder parent : destbuilder.getAncestors()) {
205: if (typeCounters.containsKey(parent.getTableName())) {
206: return true;
207: }
208: }
209: return false;
210: }
211:
212: /**
213: * @see org.mmbase.core.event.NodeEventListener#notify(org.mmbase.core.event.NodeEvent)
214: */
215: public void notify(NodeEvent event) {
216: if (containsType(event)) {
217: nodeChanged(event);
218: }
219: }
220:
221: private boolean containsType(NodeEvent event) {
222: if (typeCounters.containsKey("object")) {
223: return true;
224: }
225: if (typeCounters.containsKey(event.getBuilderName())) {
226: return true;
227: }
228: MMBase mmb = MMBase.getMMBase();
229: MMObjectBuilder destBuilder = mmb.getBuilder(event
230: .getBuilderName());
231: if (destBuilder == null) { // builder is not even available
232: return false;
233: }
234: for (MMObjectBuilder parent : destBuilder.getAncestors()) {
235: if (typeCounters.containsKey(parent.getTableName())) {
236: return true;
237: }
238: }
239: return false;
240: }
241:
242: protected int nodeChanged(Event event)
243: throws IllegalArgumentException {
244: if (log.isDebugEnabled()) {
245: log.debug("Considering " + event);
246: }
247: Set<SearchQuery> cacheKeys;
248: Map<String, Integer> oldTypeCounters;
249: synchronized (this ) {
250: cacheKeys = new HashSet<SearchQuery>(keySet());
251: oldTypeCounters = new HashMap<String, Integer>(typeCounters);
252: }
253:
254: Set<SearchQuery> removeKeys = new HashSet<SearchQuery>();
255: Map<String, Integer> foundTypeCounters = new HashMap<String, Integer>();
256:
257: evaluate(event, cacheKeys, removeKeys, foundTypeCounters);
258:
259: synchronized (this ) {
260: for (SearchQuery q : removeKeys) {
261: remove(q);
262: }
263:
264: for (String type : typeCounters.keySet()) {
265: if (foundTypeCounters.containsKey(type)) {
266: if (oldTypeCounters.containsKey(type)) {
267: // adjust counter
268: int oldValue = oldTypeCounters.get(type);
269: int guessedValue = typeCounters.get(type);
270: int foundValue = foundTypeCounters.get(type);
271: if (guessedValue - oldValue > 0) {
272: int newValue = foundValue
273: + (guessedValue - oldValue);
274: foundTypeCounters.put(type, newValue);
275: }
276: } else {
277: int guessedValue = typeCounters.get(type);
278: int foundValue = foundTypeCounters.get(type);
279: int newValue = foundValue + guessedValue;
280: foundTypeCounters.put(type, newValue);
281: }
282: } else {
283: Integer guessedValue = typeCounters.get(type);
284: foundTypeCounters.put(type, guessedValue);
285: }
286: }
287: typeCounters = foundTypeCounters;
288: }
289: return removeKeys.size();
290: }
291:
292: private void evaluate(Event event, Set<SearchQuery> cacheKeys,
293: Set<SearchQuery> removeKeys,
294: Map<String, Integer> foundTypeCounters) {
295: int evaluatedResults = cacheKeys.size();
296: long startTime = System.currentTimeMillis();
297:
298: if (log.isDebugEnabled()) {
299: log.debug("Considering " + cacheKeys.size()
300: + " objects in " + QueryResultCache.this .getName()
301: + " for flush because of " + event);
302: }
303: for (SearchQuery key : cacheKeys) {
304:
305: boolean shouldRelease;
306: if (releaseStrategy.isEnabled()) {
307: if (event instanceof NodeEvent) {
308: shouldRelease = releaseStrategy.evaluate(
309: (NodeEvent) event, key, get(key))
310: .shouldRelease();
311: } else if (event instanceof RelationEvent) {
312: shouldRelease = releaseStrategy.evaluate(
313: (RelationEvent) event, key, get(key))
314: .shouldRelease();
315: } else {
316: log.error("event " + event.getClass() + " " + event
317: + " is of unsupported type");
318: shouldRelease = false;
319: }
320: } else {
321: shouldRelease = true;
322: }
323:
324: if (shouldRelease) {
325: removeKeys.add(key);
326: } else {
327: increaseCounters(key, foundTypeCounters);
328: }
329: }
330: if (log.isDebugEnabled()) {
331: log.debug(QueryResultCache.this .getName()
332: + ": event analyzed in "
333: + (System.currentTimeMillis() - startTime)
334: + " milisecs. evaluating " + evaluatedResults
335: + ". Flushed " + removeKeys.size());
336: }
337: }
338:
339: public void clear() {
340: super.clear();
341: releaseStrategy.clear();
342: }
343: }
|