KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > access > DataRowStore


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56 package org.objectstyle.cayenne.access;
57
58 import java.io.IOException JavaDoc;
59 import java.io.ObjectInputStream JavaDoc;
60 import java.io.Serializable JavaDoc;
61 import java.util.Collection JavaDoc;
62 import java.util.Collections JavaDoc;
63 import java.util.HashMap JavaDoc;
64 import java.util.Iterator JavaDoc;
65 import java.util.List JavaDoc;
66 import java.util.Map JavaDoc;
67
68 import org.apache.commons.collections.ExtendedProperties;
69 import org.apache.commons.collections.map.LRUMap;
70 import org.apache.log4j.Logger;
71 import org.objectstyle.cayenne.CayenneRuntimeException;
72 import org.objectstyle.cayenne.DataRow;
73 import org.objectstyle.cayenne.ObjectId;
74 import org.objectstyle.cayenne.access.event.SnapshotEvent;
75 import org.objectstyle.cayenne.access.util.QueryUtils;
76 import org.objectstyle.cayenne.access.util.SelectObserver;
77 import org.objectstyle.cayenne.event.EventBridge;
78 import org.objectstyle.cayenne.event.EventBridgeFactory;
79 import org.objectstyle.cayenne.event.EventManager;
80 import org.objectstyle.cayenne.event.EventSubject;
81 import org.objectstyle.cayenne.query.SelectQuery;
82
83 /**
84  * A fixed size cache of DataRows keyed by ObjectId.
85  * <p>
86  * <strong>Synchronization Note: </strong> DataRowStore synchronizes most operations on
87  * its own instance.
88  * </p>
89  *
90  * @author Andrei Adamchik
91  * @since 1.1
92  */

93 public class DataRowStore implements Serializable JavaDoc {
94
95     private static Logger logObj = Logger.getLogger(DataRowStore.class);
96
97     // property keys
98
public static final String JavaDoc SNAPSHOT_EXPIRATION_PROPERTY = "cayenne.DataRowStore.snapshot.expiration";
99     public static final String JavaDoc SNAPSHOT_CACHE_SIZE_PROPERTY = "cayenne.DataRowStore.snapshot.size";
100     public static final String JavaDoc REMOTE_NOTIFICATION_PROPERTY = "cayenne.DataRowStore.remote.notify";
101     public static final String JavaDoc EVENT_BRIDGE_FACTORY_PROPERTY = "cayenne.DataRowStore.EventBridge.factory";
102
103     // default property values
104

105     // default expiration time is 2 hours
106
public static final long SNAPSHOT_EXPIRATION_DEFAULT = 2 * 60 * 60;
107     public static final int SNAPSHOT_CACHE_SIZE_DEFAULT = 10000;
108     public static final boolean REMOTE_NOTIFICATION_DEFAULT = false;
109
110     // use String for class name, since JavaGroups may not be around,
111
// causing CNF exceptions
112
public static final String JavaDoc EVENT_BRIDGE_FACTORY_DEFAULT = "org.objectstyle.cayenne.event.JavaGroupsBridgeFactory";
113
114     protected String JavaDoc name;
115     protected LRUMap snapshots;
116     protected LRUMap snapshotLists;
117     protected boolean notifyingRemoteListeners;
118
119     protected transient EventBridge remoteNotificationsHandler;
120
121     // IMPORTANT: EventSubject must be an ivar to avoid its deallocation
122
// too early, and thus disabling events.
123
protected transient EventSubject eventSubject;
124
125     /**
126      * Creates new named DataRowStore with default configuration.
127      */

128     public DataRowStore(String JavaDoc name) {
129         this(name, Collections.EMPTY_MAP);
130     }
131
132     /**
133      * Creates new DataRowStore with a specified name and a set of properties. If no
134      * properties are defined, default values are used.
135      *
136      * @param name DataRowStore name. Used to idenitfy this DataRowStore in events, etc.
137      * Can't be null.
138      * @param properties Properties map used to configure DataRowStore parameters. Can be
139      * null.
140      */

141     public DataRowStore(String JavaDoc name, Map JavaDoc properties) {
142         if (name == null) {
143             throw new IllegalArgumentException JavaDoc("DataRowStore name can't be null.");
144         }
145
146         this.name = name;
147         this.eventSubject = createSubject();
148         initWithProperties(properties);
149     }
150
151     private EventSubject createSubject() {
152         return EventSubject.getSubject(this.getClass(), name);
153     }
154
155     protected void initWithProperties(Map JavaDoc properties) {
156         ExtendedProperties propertiesWrapper = new ExtendedProperties();
157
158         if (properties != null) {
159             propertiesWrapper.putAll(properties);
160         }
161
162         long snapshotsExpiration = propertiesWrapper.getLong(
163                 SNAPSHOT_EXPIRATION_PROPERTY,
164                 SNAPSHOT_EXPIRATION_DEFAULT);
165
166         int snapshotsCacheSize = propertiesWrapper.getInt(
167                 SNAPSHOT_CACHE_SIZE_PROPERTY,
168                 SNAPSHOT_CACHE_SIZE_DEFAULT);
169
170         boolean notifyRemote = propertiesWrapper.getBoolean(
171                 REMOTE_NOTIFICATION_PROPERTY,
172                 REMOTE_NOTIFICATION_DEFAULT);
173
174         String JavaDoc eventBridgeFactory = propertiesWrapper.getString(
175                 EVENT_BRIDGE_FACTORY_PROPERTY,
176                 EVENT_BRIDGE_FACTORY_DEFAULT);
177
178         if (logObj.isDebugEnabled()) {
179             logObj.debug("DataRowStore property "
180                     + SNAPSHOT_EXPIRATION_PROPERTY
181                     + " = "
182                     + snapshotsExpiration);
183             logObj.debug("DataRowStore property "
184                     + SNAPSHOT_CACHE_SIZE_PROPERTY
185                     + " = "
186                     + snapshotsCacheSize);
187             logObj.debug("DataRowStore property "
188                     + REMOTE_NOTIFICATION_PROPERTY
189                     + " = "
190                     + notifyRemote);
191             logObj.debug("DataRowStore property "
192                     + EVENT_BRIDGE_FACTORY_PROPERTY
193                     + " = "
194                     + eventBridgeFactory);
195         }
196
197         // init ivars from properties
198
this.notifyingRemoteListeners = notifyRemote;
199
200         // TODO: ENTRY EXPIRATION is not supported by commons LRU Map
201
this.snapshots = new LRUMap(snapshotsCacheSize);
202
203         // TODO: cache size should really be a sum of all result lists sizes...
204
// so we must track it outside the LRUMap...
205
this.snapshotLists = new LRUMap(snapshotsCacheSize);
206
207         // init event bridge only if we are notifying remote listeners
208
if (notifyingRemoteListeners) {
209             try {
210                 EventBridgeFactory factory = (EventBridgeFactory) Class
211                         .forName(eventBridgeFactory)
212                         .newInstance();
213                 this.remoteNotificationsHandler = factory
214                         .createEventBridge(getSnapshotEventSubject(), properties);
215
216                 // listen to EventBridge ... must add itself as non-blocking listener
217
// otherwise a deadlock can occur as "processRemoteEvent" will attempt to
218
// obtain a lock on this object when the dispatch queue is locked... And
219
// another commit thread may have this object locked and attempt to lock
220
// dispatch queue
221

222                 EventManager.getDefaultManager().addNonBlockingListener(this,
223                         "processRemoteEvent",
224                         SnapshotEvent.class,
225                         getSnapshotEventSubject(),
226                         remoteNotificationsHandler);
227
228                 // start EventBridge - it will listen to all event sources for this
229
// subject
230
remoteNotificationsHandler.startup(EventManager.getDefaultManager(),
231                         EventBridge.RECEIVE_LOCAL_EXTERNAL);
232             }
233             catch (Exception JavaDoc ex) {
234                 throw new CayenneRuntimeException("Error initializing DataRowStore.", ex);
235             }
236         }
237     }
238
239     /**
240      * Returns current cache size.
241      */

242     public int size() {
243         return snapshots.size();
244     }
245
246     /**
247      * Returns maximum allowed cache size.
248      */

249     public int maximumSize() {
250         return snapshots.maxSize();
251     }
252
253     /**
254      * Shuts down any remote notification connections, and clears internal cache.
255      */

256     public void shutdown() {
257         if (remoteNotificationsHandler != null) {
258             try {
259                 remoteNotificationsHandler.shutdown();
260             }
261             catch (Exception JavaDoc ex) {
262                 logObj.info("Exception shutting down EventBridge.", ex);
263             }
264             remoteNotificationsHandler = null;
265         }
266
267         clear();
268     }
269
270     /**
271      * Returns the name of this SnapshotCache. Name allows to create EventSubjects for
272      * event notifications addressed to or sent from this SnapshotCache.
273      */

274     public String JavaDoc getName() {
275         return name;
276     }
277
278     public void setName(String JavaDoc name) {
279         this.name = name;
280     }
281
282     /**
283      * Returns cached snapshot or null if no snapshot is currently cached for the given
284      * ObjectId.
285      */

286     public synchronized DataRow getCachedSnapshot(ObjectId oid) {
287         return (DataRow) snapshots.get(oid);
288     }
289
290     /**
291      * Returns a snapshot for ObjectId. If snapshot is currently cached, it is returned.
292      * If not, a provided QueryEngine is used to fetch it from the database. If there is
293      * no database row for a given id, null is returned.
294      */

295     public synchronized DataRow getSnapshot(ObjectId oid, QueryEngine engine) {
296
297         // try cache
298
DataRow cachedSnapshot = getCachedSnapshot(oid);
299         if (cachedSnapshot != null) {
300             return cachedSnapshot;
301         }
302
303         if (logObj.isDebugEnabled()) {
304             logObj.debug("no cached snapshot for ObjectId: " + oid);
305         }
306
307         // try getting it from database
308
SelectQuery select = QueryUtils.selectObjectForId(oid);
309         SelectObserver observer = new SelectObserver();
310         engine.performQueries(Collections.singletonList(select), observer);
311         List JavaDoc results = observer.getResults(select);
312
313         if (results.size() > 1) {
314             throw new CayenneRuntimeException("More than 1 object found for ObjectId "
315                     + oid
316                     + ". Fetch matched "
317                     + results.size()
318                     + " objects.");
319         } else if (results.size() == 0) {
320             return null;
321         } else {
322             DataRow snapshot = (DataRow) results.get(0);
323             snapshots.put(oid, snapshot);
324             return snapshot;
325         }
326     }
327
328     /**
329      * Registers a list of snapshots with internal cache, using a String key.
330      */

331     public void cacheSnapshots(String JavaDoc key, List JavaDoc snapshots) {
332         snapshotLists.put(key, snapshots);
333     }
334
335     /**
336      * Returns a list of previously cached snapshots.
337      */

338     public List JavaDoc getCachedSnapshots(String JavaDoc key) {
339         if (key == null) {
340             return null;
341         }
342
343         return (List JavaDoc) snapshotLists.get(key);
344     }
345
346     /**
347      * Returns EventSubject used by this SnapshotCache to notify of snapshot changes.
348      */

349     public EventSubject getSnapshotEventSubject() {
350         return eventSubject;
351     }
352
353     /**
354      * Expires and removes all stored snapshots without sending any notification events.
355      */

356     public synchronized void clear() {
357         snapshots.clear();
358         snapshotLists.clear();
359     }
360
361     /**
362      * Evicts a snapshot from cache without generating any SnapshotEvents.
363      */

364     public synchronized void forgetSnapshot(ObjectId id) {
365         snapshots.remove(id);
366     }
367
368     /**
369      * Handles remote events received via EventBridge. Performs needed snapshot updates,
370      * and then resends the event to local listeners.
371      */

372     public void processRemoteEvent(SnapshotEvent event) {
373         if (event.getPostedBy() == this
374                 || event.getSource() != remoteNotificationsHandler) {
375             return;
376         }
377
378         if (logObj.isDebugEnabled()) {
379             logObj.debug("remote event: " + event);
380         }
381
382         Collection JavaDoc deletedSnapshotIds = event.getDeletedIds();
383         Collection JavaDoc invalidatedSnapshotIds = event.getInvalidatedIds();
384         Map JavaDoc diffs = event.getModifiedDiffs();
385         Collection JavaDoc indirectlyModifiedIds = event.getIndirectlyModifiedIds();
386
387         if (deletedSnapshotIds.isEmpty()
388                 && invalidatedSnapshotIds.isEmpty()
389                 && diffs.isEmpty()
390                 && indirectlyModifiedIds.isEmpty()) {
391             logObj.warn("processRemoteEvent.. bogus call... no changes.");
392             return;
393         }
394
395         synchronized (this) {
396             processDeletedIDs(deletedSnapshotIds);
397             processInvalidatedIDs(deletedSnapshotIds);
398             processUpdateDiffs(diffs);
399             sendUpdateNotification(
400                     event.getSource(),
401                     diffs,
402                     deletedSnapshotIds,
403                     invalidatedSnapshotIds,
404                     indirectlyModifiedIds);
405         }
406     }
407
408     /**
409      * Processes changes made to snapshots. Modifies internal cache state, and then sends
410      * the event to all listeners. Source of these changes is usually an ObjectStore.
411      * @deprecated
412      */

413     public void processSnapshotChanges(
414             Object JavaDoc source,
415             Map JavaDoc updatedSnapshots,
416             Collection JavaDoc deletedSnapshotIds,
417             Collection JavaDoc indirectlyModifiedIds) {
418
419         this.processSnapshotChanges(source, updatedSnapshots, deletedSnapshotIds, Collections.EMPTY_LIST, indirectlyModifiedIds);
420     }
421
422     /**
423      * Processes changes made to snapshots. Modifies internal cache state, and then sends
424      * the event to all listeners. Source of these changes is usually an ObjectStore.
425      */

426     public void processSnapshotChanges(
427             Object JavaDoc source,
428             Map JavaDoc updatedSnapshots,
429             Collection JavaDoc deletedSnapshotIds,
430             Collection JavaDoc invalidatedSnapshotIds,
431             Collection JavaDoc indirectlyModifiedIds) {
432
433         // update the internal cache, prepare snapshot event
434

435         if (deletedSnapshotIds.isEmpty()
436                 && invalidatedSnapshotIds.isEmpty()
437                 && updatedSnapshots.isEmpty()
438                 && indirectlyModifiedIds.isEmpty()) {
439             logObj.warn("postSnapshotsChangeEvent.. bogus call... no changes.");
440             return;
441         }
442
443         synchronized (this) {
444             processDeletedIDs(deletedSnapshotIds);
445             processInvalidatedIDs(invalidatedSnapshotIds);
446             Map JavaDoc diffs = processUpdatedSnapshots(updatedSnapshots);
447             sendUpdateNotification(
448                     source,
449                     diffs,
450                     deletedSnapshotIds,
451                     invalidatedSnapshotIds,
452                     indirectlyModifiedIds);
453         }
454     }
455
456     private void processDeletedIDs(Collection JavaDoc deletedSnapshotIDs) {
457         // DELETED: evict deleted snapshots
458
if (!deletedSnapshotIDs.isEmpty()) {
459             Iterator JavaDoc it = deletedSnapshotIDs.iterator();
460             while (it.hasNext()) {
461                 snapshots.remove(it.next());
462             }
463         }
464     }
465
466     private void processInvalidatedIDs(Collection JavaDoc invalidatedSnapshotIds) {
467         // INVALIDATED: forget snapshot, treat as expired from cache
468
if (!invalidatedSnapshotIds.isEmpty()) {
469             Iterator JavaDoc it = invalidatedSnapshotIds.iterator();
470             while (it.hasNext()) {
471                 snapshots.remove(it.next());
472             }
473         }
474     }
475
476     private Map JavaDoc processUpdatedSnapshots(Map JavaDoc updatedSnapshots) {
477         Map JavaDoc diffs = null;
478
479         // MODIFIED: replace/add snapshots, generate diffs for event
480
if (!updatedSnapshots.isEmpty()) {
481             Iterator JavaDoc it = updatedSnapshots.entrySet().iterator();
482             while (it.hasNext()) {
483                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
484                 
485                 ObjectId key = (ObjectId) entry.getKey();
486                 DataRow newSnapshot = (DataRow) entry.getValue();
487                 DataRow oldSnapshot = (DataRow) snapshots.put(key, newSnapshot);
488
489                 // generate diff for the updated event, if this not a new
490
// snapshot
491

492                 // The following cases should be handled here:
493

494                 // 1. There is no previously cached snapshot for a given id.
495
// 2. There was a previously cached snapshot for a given id,
496
// but it expired from cache and was removed. Currently
497
// handled as (1); what are the consequences of that?
498
// 3. There is a previously cached snapshot and it has the
499
// *same version* as the "replacesVersion" property of the
500
// new snapshot.
501
// 4. There is a previously cached snapshot and it has a
502
// *different version* from "replacesVersion" property of
503
// the new snapshot. It means that we don't know how to merge
504
// the two (we don't even know which one is newer due to
505
// multithreading). Just throw out this snapshot....
506

507                 if (oldSnapshot != null) {
508                     // case 4 above... have to throw out the snapshot since
509
// no good options exist to tell how to merge the two.
510
if (oldSnapshot.getVersion() != newSnapshot.getReplacesVersion()) {
511                         logObj
512                                 .debug("snapshot version changed, don't know what to do... Old: "
513                                         + oldSnapshot
514                                         + ", New: "
515                                         + newSnapshot);
516                         forgetSnapshot(key);
517                         continue;
518                     }
519
520                     Map JavaDoc diff = oldSnapshot.createDiff(newSnapshot);
521
522                     if (diff != null) {
523                         if (diffs == null) {
524                             diffs = new HashMap JavaDoc();
525                         }
526
527                         diffs.put(key, diff);
528                     }
529                 }
530             }
531         }
532
533         return diffs;
534     }
535
536     private void processUpdateDiffs(Map JavaDoc diffs) {
537         // apply snapshot diffs
538
if (!diffs.isEmpty()) {
539             Iterator JavaDoc it = diffs.entrySet().iterator();
540             while (it.hasNext()) {
541                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
542                 ObjectId key = (ObjectId) entry.getKey();
543                 DataRow oldSnapshot = (DataRow) snapshots.remove(key);
544
545                 if (oldSnapshot == null) {
546                     continue;
547                 }
548
549                 DataRow newSnapshot = oldSnapshot.applyDiff((DataRow) entry.getValue());
550                 snapshots.put(key, newSnapshot);
551             }
552         }
553     }
554
555     private void sendUpdateNotification(
556             Object JavaDoc source,
557             Map JavaDoc diffs,
558             Collection JavaDoc deletedSnapshotIDs,
559             Collection JavaDoc invalidatedSnapshotIDs,
560             Collection JavaDoc indirectlyModifiedIds) {
561
562         // do not send bogus events... e.g. inserted objects are not counted
563
if ((diffs != null && !diffs.isEmpty())
564                 || (deletedSnapshotIDs != null && !deletedSnapshotIDs.isEmpty())
565                 || (invalidatedSnapshotIDs != null && !invalidatedSnapshotIDs.isEmpty())
566                 || (indirectlyModifiedIds != null && !indirectlyModifiedIds.isEmpty())) {
567
568             SnapshotEvent event = new SnapshotEvent(
569                     source,
570                     this,
571                     diffs,
572                     deletedSnapshotIDs,
573                     invalidatedSnapshotIDs,
574                     indirectlyModifiedIds);
575
576             if (logObj.isDebugEnabled()) {
577                 logObj.debug("postSnapshotsChangeEvent: " + event);
578             }
579
580             // notify listeners
581

582             // send synchronously, relying on listeners to
583
// register as "non-blocking" if needed.
584
EventManager.getDefaultManager().postEvent(event, getSnapshotEventSubject());
585         }
586     }
587
588     public boolean isNotifyingRemoteListeners() {
589         return notifyingRemoteListeners;
590     }
591
592     public void setNotifyingRemoteListeners(boolean notifyingRemoteListeners) {
593         this.notifyingRemoteListeners = notifyingRemoteListeners;
594     }
595
596     // deserialization support
597
private void readObject(ObjectInputStream JavaDoc in) throws IOException JavaDoc,
598             ClassNotFoundException JavaDoc {
599
600         in.defaultReadObject();
601
602         // restore subjects
603
this.eventSubject = createSubject();
604     }
605 }
Popular Tags