001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/event/tags/sakai_2-4-1/event-impl/impl/src/java/org/sakaiproject/event/impl/NotificationCache.java $
003: * $Id: NotificationCache.java 7088 2006-03-28 02:35:22Z ggolden@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.event.impl;
021:
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.Observable;
027: import java.util.Observer;
028: import java.util.Vector;
029:
030: import org.apache.commons.logging.Log;
031: import org.apache.commons.logging.LogFactory;
032: import org.sakaiproject.event.api.Event;
033: import org.sakaiproject.event.api.Notification;
034: import org.sakaiproject.event.cover.EventTrackingService;
035: import org.sakaiproject.memory.api.CacheRefresher;
036: import org.sakaiproject.memory.api.Cacher;
037: import org.sakaiproject.memory.cover.MemoryService;
038:
039: /**
040: * <p>
041: * A Cache of objects with keys with a limited lifespan.
042: * </p>
043: * <p>
044: * When the object expires, the cache calls upon a CacheRefresher to update the key's value. The update is done in a separate thread.
045: * </p>
046: */
047: public class NotificationCache implements Cacher, Observer {
048: /** Our logger. */
049: private static Log M_log = LogFactory
050: .getLog(NotificationCache.class);
051:
052: /** Map holding cached entries (by reference). */
053: protected Map m_map = null;
054:
055: /** Map of notification function to Set of notifications - same objects as in m_map. */
056: protected Map m_functionMap = null;
057:
058: /** The object that will deal with expired entries. */
059: protected CacheRefresher m_refresher = null;
060:
061: /** The string that all resources in this cache will start with. */
062: protected String m_resourcePattern = null;
063:
064: /** If true, we are disabled. */
065: protected boolean m_disabled = false;
066:
067: /** If true, we have all the entries that there are in the cache. */
068: protected boolean m_complete = false;
069:
070: /** If true, we are going to hold any events we see in the m_heldEvents list for later processing. */
071: protected boolean m_holdEventProcessing = false;
072:
073: /** The events we are holding for later processing. */
074: protected List m_heldEvents = new Vector();
075:
076: /**
077: * Construct the Cache. Attempts to keep complete on Event notification by calling the refresher.
078: *
079: * @param refresher
080: * The object that will handle refreshing of event notified modified or added entries.
081: * @param pattern
082: * The "startsWith()" string for all resources that may be in this cache.
083: */
084: public NotificationCache(CacheRefresher refresher, String pattern) {
085: m_map = new HashMap();
086: m_functionMap = new HashMap();
087:
088: // register as a cacher
089: MemoryService.registerCacher(this );
090:
091: m_refresher = refresher;
092: m_resourcePattern = pattern;
093:
094: // register to get events - first, before others
095: EventTrackingService.addPriorityObserver(this );
096:
097: } // NotificationCache
098:
099: /**
100: * Clean up.
101: */
102: protected void finalize() {
103: // unregister as a cacher
104: MemoryService.unregisterCacher(this );
105:
106: // ungister to get events
107: EventTrackingService.deleteObserver(this );
108:
109: } // finalize
110:
111: /**
112: * Cache an object.
113: *
114: * @param key
115: * The key with which to find the object.
116: * @param payload
117: * The object to cache.
118: * @param duration
119: * The time to cache the object (seconds).
120: */
121: public synchronized void put(Notification payload) {
122: if (disabled())
123: return;
124:
125: m_map.put(payload.getReference(), payload);
126:
127: // put in m_functionMap, too, for each function of the notification
128: List funcs = payload.getFunctions();
129: for (Iterator iFuncs = funcs.iterator(); iFuncs.hasNext();) {
130: String func = (String) iFuncs.next();
131:
132: List notifications = (List) m_functionMap.get(func);
133: if (notifications == null) {
134: notifications = new Vector();
135: m_functionMap.put(func, notifications);
136: }
137:
138: if (!notifications.contains(payload))
139: notifications.add(payload);
140: }
141:
142: } // cache
143:
144: /**
145: * Test for an entry in the cache.
146: *
147: * @param key
148: * The cache key.
149: * @return true if the key maps to an entry, false if not.
150: */
151: public synchronized boolean containsKey(Object key) {
152: if (disabled())
153: return false;
154:
155: // is it there?
156: if (m_map.containsKey(key)) {
157: return true;
158: }
159:
160: return false;
161:
162: } // containsKey
163:
164: /**
165: * Get the entry associated with the key, or null if not there
166: *
167: * @param key
168: * The cache key.
169: * @return The entry associated with the key, or null if the a null is cached, or the key is not found (Note: use containsKey() to remove this ambiguity).
170: */
171: public synchronized Notification get(Object key) {
172: if (disabled())
173: return null;
174:
175: return (Notification) m_map.get(key);
176:
177: } // get
178:
179: /**
180: * Get all the non-null entries.
181: *
182: * @return all the non-null entries, or an empty list if none.
183: */
184: public List getAll() {
185: List rv = new Vector();
186:
187: if (disabled())
188: return rv;
189: if (m_map.isEmpty())
190: return rv;
191:
192: // get the keys as of now
193: Object[] keys = m_map.keySet().toArray();
194:
195: // for each entry in the cache
196: for (int i = 0; i < keys.length; i++) {
197: Object payload = m_map.get(keys[i]);
198:
199: // skip nulls
200: if (payload == null)
201: continue;
202:
203: // we'll take it
204: rv.add(payload);
205: }
206:
207: return rv;
208:
209: } // getAll
210:
211: /**
212: * Get all the Notification entries that are watching this Event function.
213: *
214: * @param function
215: * The function to use to select Notifications.
216: * @return all the Notification entries that are watching this Event function.
217: */
218: public List getAll(String function) {
219: return (List) m_functionMap.get(function);
220:
221: } // getAll
222:
223: /**
224: * Get all the keys
225: *
226: * @return The List of key values (Object).
227: */
228: public List getKeys() {
229: List rv = new Vector();
230: rv.addAll(m_map.keySet());
231: return rv;
232:
233: } // getKeys
234:
235: /**
236: * Get all the keys, eache modified to remove the resourcePattern prefix. Note: only works with String keys.
237: *
238: * @return The List of keys converted from references to ids (String).
239: */
240: public List getIds() {
241: List rv = new Vector();
242:
243: List keys = new Vector();
244: keys.addAll(m_map.keySet());
245:
246: Iterator it = keys.iterator();
247: while (it.hasNext()) {
248: String key = (String) it.next();
249: int i = key.indexOf(m_resourcePattern);
250: if (i != -1)
251: key = key.substring(i + m_resourcePattern.length());
252: rv.add(key);
253: }
254:
255: return rv;
256:
257: } // getKeys
258:
259: /**
260: * Clear all entries.
261: */
262: public synchronized void clear() {
263: m_map.clear();
264: m_functionMap.clear();
265: m_complete = false;
266:
267: } // clear
268:
269: /**
270: * Remove this entry from the cache.
271: *
272: * @param key
273: * The cache key.
274: */
275: public synchronized void remove(Object key) {
276: if (disabled())
277: return;
278:
279: Notification payload = (Notification) m_map.get(key);
280: m_map.remove(key);
281:
282: if (payload == null)
283: return;
284:
285: // remove it from the function map for each function
286: List funcs = payload.getFunctions();
287: for (Iterator iFuncs = funcs.iterator(); iFuncs.hasNext();) {
288: String func = (String) iFuncs.next();
289:
290: List notifications = (List) m_functionMap.get(func);
291: if (notifications != null) {
292: notifications.remove(payload);
293: if (notifications.isEmpty()) {
294: m_functionMap.remove(func);
295: }
296: }
297: }
298:
299: } // remove
300:
301: /**
302: * Disable the cache.
303: */
304: public void disable() {
305: m_disabled = true;
306: EventTrackingService.deleteObserver(this );
307: clear();
308:
309: } // disable
310:
311: /**
312: * Enable the cache.
313: */
314: public void enable() {
315: m_disabled = false;
316: EventTrackingService.addPriorityObserver(this );
317:
318: } // enable
319:
320: /**
321: * Is the cache disabled?
322: *
323: * @return true if the cache is disabled, false if it is enabled.
324: */
325: public boolean disabled() {
326: return m_disabled;
327:
328: } // disabled
329:
330: /**
331: * Are we complete?
332: *
333: * @return true if we have all the possible entries cached, false if not.
334: */
335: public boolean isComplete() {
336: if (disabled())
337: return false;
338:
339: return m_complete;
340:
341: } // isComplete
342:
343: /**
344: * Set the cache to be complete, containing all possible entries.
345: */
346: public void setComplete() {
347: if (disabled())
348: return;
349:
350: m_complete = true;
351:
352: } // isComplete
353:
354: /**
355: * Set the cache to hold events for later processing to assure an atomic "complete" load.
356: */
357: public synchronized void holdEvents() {
358: m_holdEventProcessing = true;
359:
360: } // holdEvents
361:
362: /**
363: * Restore normal event processing in the cache, and process any held events now.
364: */
365: public synchronized void processEvents() {
366: m_holdEventProcessing = false;
367:
368: for (int i = 0; i < m_heldEvents.size(); i++) {
369: Event event = (Event) m_heldEvents.get(i);
370: continueUpdate(event);
371: }
372:
373: m_heldEvents.clear();
374:
375: } // holdEvents
376:
377: /**********************************************************************************************************************************************************************************************************************************************************
378: * Cacher implementation
379: *********************************************************************************************************************************************************************************************************************************************************/
380:
381: /**
382: * Clear out as much as possible anything cached; re-sync any cache that is needed to be kept.
383: */
384: public void resetCache() {
385: clear();
386:
387: } // resetCache
388:
389: /**
390: * Return the size of the cacher - indicating how much memory in use.
391: *
392: * @return The size of the cacher.
393: */
394: public long getSize() {
395: return m_map.size();
396: }
397:
398: /**
399: * Return a description of the cacher.
400: *
401: * @return The cacher's description.
402: */
403: public String getDescription() {
404: StringBuffer buf = new StringBuffer();
405: buf.append("NotificationCache:");
406: if (m_disabled) {
407: buf.append(" disabled:");
408: }
409: if (m_complete) {
410: buf.append(" complete:");
411: }
412: if (m_resourcePattern != null) {
413: buf.append(" pattern: " + m_resourcePattern);
414: }
415: if (m_refresher != null) {
416: buf.append(" refresher: " + m_refresher.toString());
417: }
418:
419: return buf.toString();
420: }
421:
422: /**********************************************************************************************************************************************************************************************************************************************************
423: * Observer implementation
424: *********************************************************************************************************************************************************************************************************************************************************/
425:
426: /**
427: * This method is called whenever the observed object is changed. An application calls an <tt>Observable</tt> object's <code>notifyObservers</code> method to have all the object's observers notified of the change. default implementation is to
428: * cause the courier service to deliver to the interface controlled by my controller. Extensions can override.
429: *
430: * @param o
431: * the observable object.
432: * @param arg
433: * an argument passed to the <code>notifyObservers</code> method.
434: */
435: public void update(Observable o, Object arg) {
436: if (disabled())
437: return;
438:
439: // arg is Event
440: if (!(arg instanceof Event))
441: return;
442: Event event = (Event) arg;
443:
444: // if this is just a read, not a modify event, we can ignore it
445: if (!event.getModify())
446: return;
447:
448: String key = event.getResource();
449:
450: // if this resource is not in my pattern of resources, we can ignore it
451: if (key != null && !key.startsWith(m_resourcePattern))
452: return;
453:
454: // if we are holding event processing
455: if (m_holdEventProcessing) {
456: m_heldEvents.add(event);
457: return;
458: }
459:
460: continueUpdate(event);
461:
462: } // update
463:
464: /**
465: * Complete the update, given an event that we know we need to act upon.
466: *
467: * @param event
468: * The event to process.
469: */
470: private void continueUpdate(Event event) {
471: String key = event.getResource();
472:
473: if (M_log.isDebugEnabled())
474: M_log.debug("update() [" + m_resourcePattern
475: + "] resource: " + key + " event: "
476: + event.getEvent());
477:
478: // do we have this in our cache?
479: Object oldValue = get(key);
480: if (m_map.containsKey(key)) {
481: // invalidate our copy
482: remove(key);
483: }
484:
485: // if we are being complete, we need to get this cached.
486: if (m_complete) {
487: // we can only get it cached if we have a refresher
488: if (m_refresher != null) {
489: // ask the refresher for the value
490: Notification value = (Notification) m_refresher
491: .refresh(key, oldValue, event);
492: if (value != null) {
493: put(value);
494: }
495: } else {
496: // we can no longer claim to be complete
497: m_complete = false;
498: }
499: }
500:
501: } // continueUpdate
502:
503: } // Cache
|