KickJava   Java API By Example, From Geeks To Geeks.

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


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

7 package org.mmbase.cache;
8
9 import java.util.*;
10
11 import org.mmbase.core.event.Event;
12 import org.mmbase.core.event.NodeEvent;
13 import org.mmbase.core.event.NodeEventListener;
14 import org.mmbase.core.event.RelationEvent;
15 import org.mmbase.core.event.RelationEventListener;
16 import org.mmbase.module.core.*;
17 import org.mmbase.util.logging.*;
18
19 import org.mmbase.storage.search.*;
20
21 import org.mmbase.storage.search.implementation.database.BasicSqlHandler;
22 import org.mmbase.bridge.implementation.BasicQuery;
23
24 /**
25  * This cache provides a base implementation to cache the result of
26  * SearchQuery's. Such a cache links a SearchQuery object to a list of
27  * MMObjectNodes. A cache entry is automaticly invalidated if arbitrary node of
28  * one of the types present in the SearchQuery is changed (,created or deleted).
29  * This mechanism is not very subtle but it is garanteed to be correct. It means
30  * though that your cache can be considerably less effective for queries
31  * containing node types from which often node are edited.
32  *
33  * @author Daniel Ockeloen
34  * @author Michiel Meeuwissen
35  * @author Bunst Eunders
36  * @version $Id: QueryResultCache.java,v 1.34 2006/06/27 07:31:46 michiel Exp $
37  * @since MMBase-1.7
38  * @see org.mmbase.storage.search.SearchQuery
39  */

40
41 abstract public class QueryResultCache extends Cache {
42
43     private static final Logger log = Logging.getLoggerInstance(QueryResultCache.class);
44
45     /**
46      * Need reference to all existing these caches, to be able to invalidate
47      * them.
48      */

49     private static final Map queryCaches = new HashMap();
50
51     /**
52      * This is the default release strategy. Actually it is a container for any
53      * number of 'real' release strategies
54      *
55      * @see ChainedReleaseStrategy
56      */

57
58     /**
59      * this is only used for logging, to create readable queries out of query objects.
60      */

61     final BasicSqlHandler sqlHandler = new BasicSqlHandler();
62
63     private final ChainedReleaseStrategy releaseStrategy;
64
65     /**
66      * Explicitely invalidates all Query caches for a certain builder. This is
67      * used in MMObjectBuilder for 'local' changes, to ensure that imediate
68      * select after update always works.
69      *
70      * @return number of entries invalidated
71      */

72
73
74     // Keep a map of the existing Observers, for each nodemanager one.
75
// @todo I think it can be done with one Observer instance too, (in which
76
// case we can as well
77
// let QueryResultCache implement MMBaseObserver itself)
78
private final Map observers = new HashMap();
79
80     QueryResultCache(int size) {
81         super(size);
82         releaseStrategy = new ChainedReleaseStrategy();
83         log.debug("Instantiated a " + this.getClass().getName() + " (" + releaseStrategy + ")"); // should happen limited number of times
84
if (queryCaches.put(this.getName(), this) != null) {
85             log.error("" + queryCaches + "already containing " + this + "!!");
86         }
87     }
88
89     /**
90      * @param strategies
91      */

92     public void addReleaseStrategies(List strategies) {
93         if (strategies != null) {
94             for (Iterator iter = strategies.iterator(); iter.hasNext();) {
95                 ReleaseStrategy element = (ReleaseStrategy) iter.next();
96                 log.debug(("adding strategy " + element.getName() + " to cache " + getName()));
97                 addReleaseStrategy(element);
98             }
99         }
100     }
101
102     /**
103      * This method lets you add a release strategy to the cache. It will in fact
104      * be added to <code>ChainedReleaseStrategy</code>, which
105      * is the default base release strategy.
106      * @param releaseStrategy A releaseStrategy to add.
107      */

108     public void addReleaseStrategy(ReleaseStrategy releaseStrategy) {
109         this.releaseStrategy.addReleaseStrategy(releaseStrategy);
110     }
111
112     /**
113      * @return Returns the releaseStrategy.
114      */

115     public ChainedReleaseStrategy getReleaseStrategy() {
116         return releaseStrategy;
117     }
118
119     /**
120      * @return an iterator of all observer instances
121      */

122     public Iterator observerIterator(){
123         List observerList = new ArrayList();
124         synchronized(this){
125             observerList.addAll(observers.values());
126         }
127         return observerList.iterator();
128     }
129
130     /**
131      * @throws ClassCastException if key not a SearchQuery or value not a List.
132      */

133     public synchronized Object JavaDoc put(Object JavaDoc key, Object JavaDoc value) {
134         if (key instanceof BasicQuery) {
135             return put(((BasicQuery) key).getQuery(), (List) value);
136         }
137
138         return put((SearchQuery) key, (List) value);
139     }
140
141     /**
142      * Puts a search result in this cache.
143      */

144     public synchronized Object JavaDoc put(SearchQuery query, List queryResult) {
145         if (!checkCachePolicy(query)) return null;
146
147         List n = (List) super.get(query);
148         if (n == null) {
149             addObservers(query);
150         }
151         return super.put(query, queryResult);
152     }
153
154     /**
155      * Removes an object from the cache. It alsos remove the watch from the
156      * observers which are watching this entry.
157      *
158      * @param key A SearchQuery object.
159      */

160     public synchronized Object JavaDoc remove(Object JavaDoc key) {
161         Object JavaDoc result = super.remove(key);
162
163         if (result != null) { // remove the key also from the observers.
164
Iterator i = observers.values().iterator();
165             while (i.hasNext()) {
166                 Observer o = (Observer) i.next();
167                 o.stopObserving(key);
168             }
169         }
170         return result;
171     }
172
173     /**
174      * Adds observers on the entry
175      */

176     private void addObservers(SearchQuery query) {
177         MMBase.getMMBase();
178
179         Iterator i = query.getSteps().iterator();
180         while (i.hasNext()) {
181             Step step = (Step) i.next();
182             //if we want to test constraints on relaion steps we have to have observers for them
183
// if (step instanceof RelationStep) {
184
// continue;
185
// }
186
String JavaDoc type = step.getTableName();
187
188             Observer o = (Observer) observers.get(type);
189             if (o == null) {
190                 o = new Observer(type);
191                 synchronized(this){
192                     observers.put(type, o);
193                 }
194             }
195             o.observe(query);
196         }
197     }
198
199     public String JavaDoc toString() {
200         return this.getClass().getName() + " " + getName();
201     }
202
203     /**
204      * This observer subscribes itself to builder changes, and invalidates the
205      * multilevel cache entries which are dependent on that specific builder.
206      */

207
208     private class Observer implements NodeEventListener, RelationEventListener {
209         /**
210          * This set contains the types (as a string) which are to be
211          * invalidated.
212          */

213         private Set cacheKeys = new HashSet(); // using java default for
214
// initial size. Someone tried 50.
215

216         private String JavaDoc type;
217
218         /**
219          * Creates a multilevel cache observer for the speficied type
220          *
221          * @param type Name of the builder which is to be observed.
222          */

223         private Observer(String JavaDoc type) {
224             this.type = type;
225             MMBase mmb = MMBase.getMMBase();
226             // when the type is a role, we need to subscribe
227
// the builder it belongs to..
228
if (mmb.getMMObject(type) == null) {
229                 int builderNumber = mmb.getRelDef().getNumberByName(type);
230                 String JavaDoc newType = mmb.getRelDef().getBuilder(builderNumber).getTableName();
231                 if (log.isDebugEnabled()) {
232                     log.debug("replaced the type: " + type + " with type:" + newType);
233                 }
234                 type = newType;
235             }
236             mmb.addNodeRelatedEventsListener(type, this);
237         }
238
239         /**
240          * Start watching the entry with the specified key of this
241          * MultilevelCache (for this type).
242          *
243          * @return true if it already was observing this entry.
244          */

245         protected synchronized boolean observe(Object JavaDoc key) {
246             // assert(MultilevelCache.this.containsKey(key));
247
return cacheKeys.add(key);
248         }
249
250         /**
251          * Stop observing this key of multilevelcache
252          */

253         protected synchronized boolean stopObserving(Object JavaDoc key) {
254             return cacheKeys.remove(key);
255         }
256
257         /*
258          * (non-Javadoc)
259          *
260          * @see org.mmbase.core.event.RelationEventListener#notify(org.mmbase.core.event.RelationEvent)
261          */

262         public void notify(RelationEvent event) {
263             nodeChanged(event);
264         }
265
266         /*
267          * (non-Javadoc)
268          *
269          * @see org.mmbase.core.event.NodeEventListener#notify(org.mmbase.core.event.NodeEvent)
270          */

271         public void notify(NodeEvent event) {
272             nodeChanged(event);
273         }
274
275         protected int nodeChanged(Event event) throws IllegalArgumentException JavaDoc{
276             if (log.isDebugEnabled()) {
277                 log.debug("Considering " + event);
278             }
279             int evaluatedResults = cacheKeys.size();
280             Set removeKeys = new HashSet();
281             long startTime = System.currentTimeMillis();
282             synchronized (QueryResultCache.this) {
283                 Iterator i = cacheKeys.iterator();
284                 if (log.isDebugEnabled()) {
285                     log.debug("Considering " + cacheKeys.size() + " objects in " + QueryResultCache.this.getName() + " for flush because of " + event);
286                 }
287                 while(i.hasNext()) {
288                     SearchQuery key = (SearchQuery) i.next();
289
290                     boolean shouldRelease;
291                     if(releaseStrategy.isEnabled()){
292                         if(event instanceof NodeEvent){
293                             shouldRelease = releaseStrategy.evaluate((NodeEvent)event, key, (List) get(key)).shouldRelease();
294                         } else if (event instanceof RelationEvent){
295                             shouldRelease = releaseStrategy.evaluate((RelationEvent)event, key, (List) get(key)).shouldRelease();
296                         } else {
297                             log.error("event " + event.getClass() + " " + event + " is of unsupported type");
298                             shouldRelease = false;
299                         }
300                     } else {
301                         shouldRelease = true;
302                     }
303
304                     if (shouldRelease) {
305                         removeKeys.add(key);
306                         i.remove();
307                     }
308
309                 }
310
311                 // ernst: why is this in a separate loop?
312
// why not chuck em out in the first one?
313
i = removeKeys.iterator();
314                 while(i.hasNext()) {
315                     QueryResultCache.this.remove(i.next());
316                 }
317             }
318             if (log.isDebugEnabled()) {
319                 log.debug(QueryResultCache.this.getName() + ": event analyzed in " + (System.currentTimeMillis() - startTime) + " milisecs. evaluating " + evaluatedResults + ". Flushed " + removeKeys.size());
320             }
321             return removeKeys.size();
322         }
323
324         public String JavaDoc toString() {
325             return "QueryResultCacheObserver for " + type + " watching " + cacheKeys.size() + " queries";
326         }
327
328         public void clear() {
329             cacheKeys.clear();
330         }
331     }
332
333     public void clear(){
334         super.clear();
335         releaseStrategy.clear();
336         Iterator i = observers.values().iterator();
337         while (i.hasNext()) {
338             Observer o = (Observer) i.next();
339             o.clear();
340         }
341     }
342 }
343
Popular Tags