KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cayenne > CayenneContext


1 /*****************************************************************
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  ****************************************************************/

19
20 package org.apache.cayenne;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.Collection JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.List JavaDoc;
26
27 import org.apache.cayenne.cache.MapQueryCache;
28 import org.apache.cayenne.cache.QueryCache;
29 import org.apache.cayenne.event.EventManager;
30 import org.apache.cayenne.graph.GraphDiff;
31 import org.apache.cayenne.graph.GraphManager;
32 import org.apache.cayenne.map.EntityResolver;
33 import org.apache.cayenne.map.ObjEntity;
34 import org.apache.cayenne.query.Query;
35 import org.apache.cayenne.reflect.ClassDescriptor;
36 import org.apache.cayenne.util.EventUtil;
37 import org.apache.cayenne.validation.ValidationException;
38 import org.apache.cayenne.validation.ValidationResult;
39
40 /**
41  * A default generic implementation of ObjectContext suitable for accessing Cayenne from
42  * either an ORM or a client tiers. Communicates with Cayenne via a
43  * {@link org.apache.cayenne.DataChannel}.
44  *
45  * @since 1.2
46  * @author Andrus Adamchik
47  */

48 public class CayenneContext extends BaseContext {
49
50     protected EntityResolver entityResolver;
51
52     CayenneContextGraphManager graphManager;
53
54     // note that it is important to reuse the same action within the property change
55
// thread to avoid a loop of "propertyChange" calls on handling reverse relationships.
56
// Here we go further and make action a thread-safe ivar that tracks its own thread
57
// state.
58
CayenneContextGraphAction graphAction;
59
60     // object that merges "backdoor" changes that come from the channel.
61
CayenneContextMergeHandler mergeHandler;
62
63     QueryCache queryCache;
64
65     /**
66      * Creates a new CayenneContext with no channel and disabled graph events.
67      */

68     public CayenneContext() {
69         this(null);
70     }
71
72     /**
73      * Creates a new CayenneContext, initializaing it with a channel instance.
74      * CayenneContext created using this constructor WILL NOT broadcast graph change
75      * events.
76      */

77     public CayenneContext(DataChannel channel) {
78         this(channel, false, false);
79     }
80
81     /**
82      * Creates a new CayenneContext, initializaing it with a channel. If
83      * <code>graphEventsEnabled</code> is true, this context will broadcast GraphEvents
84      * using ObjectContext.GRAPH_CHANGE_SUBJECT.
85      */

86     public CayenneContext(DataChannel channel, boolean changeEventsEnabled,
87             boolean syncEventsEnabled) {
88
89         this.graphAction = new CayenneContextGraphAction(this);
90         this.graphManager = new CayenneContextGraphManager(
91                 this,
92                 changeEventsEnabled,
93                 syncEventsEnabled);
94
95         setChannel(channel);
96     }
97
98     /**
99      * Returns {@link QueryCache}, creating it on the fly if needed.
100      *
101      * @since 3.0
102      */

103     QueryCache getQueryCache() {
104
105         if (queryCache == null) {
106             synchronized (this) {
107                 if (queryCache == null) {
108                     // TODO: andrus, 7/27/2006 - figure out the factory stuff like we have
109
// in DataContext
110
queryCache = new MapQueryCache();
111                 }
112             }
113         }
114
115         return queryCache;
116     }
117
118     /**
119      * Sets the context channel, setting up a listener for channel events.
120      */

121     public void setChannel(DataChannel channel) {
122         if (this.channel != channel) {
123
124             if (this.mergeHandler != null) {
125                 this.mergeHandler.active = false;
126                 this.mergeHandler = null;
127             }
128
129             this.channel = channel;
130
131             EventManager eventManager = (channel != null)
132                     ? channel.getEventManager()
133                     : null;
134             if (eventManager != null) {
135                 this.mergeHandler = new CayenneContextMergeHandler(this);
136
137                 // listen to our channel events...
138
// note that we must reset listener on channel switch, as there is no
139
// guarantee that a new channel uses the same EventManager.
140
EventUtil.listenForChannelEvents(channel, mergeHandler);
141             }
142         }
143     }
144
145     /**
146      * Returns true if this context posts individual object modification events. Subject
147      * used for these events is <code>ObjectContext.GRAPH_CHANGED_SUBJECT</code>.
148      */

149     public boolean isChangeEventsEnabled() {
150         return graphManager.changeEventsEnabled;
151     }
152
153     /**
154      * Returns true if this context posts lifecycle events. Subjects used for these events
155      * are
156      * <code>ObjectContext.GRAPH_COMMIT_STARTED_SUBJECT, ObjectContext.GRAPH_COMMITTED_SUBJECT,
157      * ObjectContext.GRAPH_COMMIT_ABORTED_SUBJECT, ObjectContext.GRAPH_ROLLEDBACK_SUBJECT.</code>.
158      */

159     public boolean isLifecycleEventsEnabled() {
160         return graphManager.lifecycleEventsEnabled;
161     }
162
163     /**
164      * Returns an EntityResolver that provides mapping information needed for
165      * CayenneContext operation. If EntityResolver is not set, this method would obtain
166      * and cache one from the underlying DataChannel.
167      */

168     public EntityResolver getEntityResolver() {
169         // load entity resolver on demand
170
if (entityResolver == null) {
171             synchronized (this) {
172                 if (entityResolver == null) {
173                     setEntityResolver(channel.getEntityResolver());
174                 }
175             }
176         }
177
178         return entityResolver;
179     }
180
181     public void setEntityResolver(EntityResolver entityResolver) {
182         this.entityResolver = entityResolver;
183     }
184
185     public GraphManager getGraphManager() {
186         return graphManager;
187     }
188
189     CayenneContextGraphManager internalGraphManager() {
190         return graphManager;
191     }
192
193     CayenneContextGraphAction internalGraphAction() {
194         return graphAction;
195     }
196
197     /**
198      * Commits changes to uncommitted objects. First checks if there are changes in this
199      * context and if any changes are detected, sends a commit message to remote Cayenne
200      * service via an internal instance of CayenneConnector.
201      */

202     public void commitChanges() {
203         doCommitChanges(true);
204     }
205
206     GraphDiff doCommitChanges(boolean cascade) {
207
208         int syncType = cascade
209                 ? DataChannel.FLUSH_CASCADE_SYNC
210                 : DataChannel.FLUSH_NOCASCADE_SYNC;
211
212         GraphDiff commitDiff = null;
213
214         synchronized (graphManager) {
215
216             if (graphManager.hasChanges()) {
217
218                 ValidationResult result = new ValidationResult();
219                 Iterator JavaDoc it = graphManager.dirtyNodes().iterator();
220                 while (it.hasNext()) {
221                     Persistent p = (Persistent) it.next();
222                     if (p instanceof Validating) {
223                         switch (p.getPersistenceState()) {
224                             case PersistenceState.NEW:
225                                 ((Validating) p).validateForInsert(result);
226                                 break;
227                             case PersistenceState.MODIFIED:
228                                 ((Validating) p).validateForUpdate(result);
229                                 break;
230                             case PersistenceState.DELETED:
231                                 ((Validating) p).validateForDelete(result);
232                                 break;
233                         }
234                     }
235                 }
236
237                 if (result.hasFailures()) {
238                     throw new ValidationException(result);
239                 }
240
241                 graphManager.graphCommitStarted();
242
243                 try {
244                     commitDiff = channel.onSync(this, graphManager
245                             .getDiffsSinceLastFlush(), syncType);
246                 }
247                 catch (Throwable JavaDoc th) {
248                     graphManager.graphCommitAborted();
249
250                     if (th instanceof CayenneRuntimeException) {
251                         throw (CayenneRuntimeException) th;
252                     }
253                     else {
254                         throw new CayenneRuntimeException("Commit error", th);
255                     }
256                 }
257
258                 graphManager.graphCommitted(commitDiff);
259             }
260         }
261
262         return commitDiff;
263     }
264
265     public void commitChangesToParent() {
266         doCommitChanges(false);
267     }
268
269     public void rollbackChanges() {
270         synchronized (graphManager) {
271             if (graphManager.hasChanges()) {
272
273                 GraphDiff diff = graphManager.getDiffs();
274                 graphManager.graphReverted();
275
276                 channel.onSync(this, diff, DataChannel.ROLLBACK_CASCADE_SYNC);
277             }
278         }
279     }
280
281     public void rollbackChangesLocally() {
282         synchronized (graphManager) {
283             if (graphManager.hasChanges()) {
284                 graphManager.graphReverted();
285             }
286         }
287     }
288
289     /**
290      * Deletes an object locally, scheduling it for future deletion from the external data
291      * store.
292      */

293     public void deleteObject(Object JavaDoc object) {
294         new ObjectContextDeleteAction(this).performDelete((Persistent) object);
295     }
296
297     /**
298      * Creates and registers a new Persistent object instance.
299      */

300     public Persistent newObject(Class JavaDoc persistentClass) {
301         if (persistentClass == null) {
302             throw new NullPointerException JavaDoc("Persistent class can't be null.");
303         }
304
305         ObjEntity entity = getEntityResolver().lookupObjEntity(persistentClass);
306         if (entity == null) {
307             throw new CayenneRuntimeException("No entity mapped for class: "
308                     + persistentClass);
309         }
310
311         ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(
312                 entity.getName());
313         Persistent object = (Persistent) descriptor.createObject();
314         registerNewObject(object, entity.getName(), descriptor);
315         return object;
316     }
317
318     /**
319      * @since 3.0
320      */

321     public void registerNewObject(Object JavaDoc object) {
322         if (object == null) {
323             throw new NullPointerException JavaDoc("An attempt to register null object.");
324         }
325
326         ObjEntity entity = getEntityResolver().lookupObjEntity(object.getClass());
327         ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(
328                 entity.getName());
329         registerNewObject((Persistent) object, entity.getName(), descriptor);
330     }
331
332     /**
333      * Runs a query, returning result as list.
334      */

335     public List JavaDoc performQuery(Query query) {
336         List JavaDoc result = onQuery(this, query).firstList();
337         return result != null ? result : new ArrayList JavaDoc(1);
338     }
339
340     public QueryResponse performGenericQuery(Query query) {
341         return onQuery(this, query);
342     }
343
344     // TODO: Andrus, 2/2/2006 - make public once CayenneContext is officially declared to
345
// support DataChannel API.
346
QueryResponse onQuery(ObjectContext context, Query query) {
347         return new CayenneContextQueryAction(this, context, query).execute();
348     }
349
350     /**
351      * Converts a list of Persistent objects registered in some other ObjectContext to a
352      * list of objects local to this ObjectContext.
353      * <p>
354      * <i>Current limitation: all objects in the source list must be either in COMMITTED
355      * or in HOLLOW state.</i>
356      * </p>
357      */

358     public Persistent localObject(ObjectId id, Object JavaDoc prototype) {
359
360         // TODO: Andrus, 1/26/2006 - this implementation is copied verbatim from
361
// DataContext. Somehow need to pull out the common code or implement inheritance
362

363         // ****** Copied from DataContext - start *******
364

365         if (id == null) {
366             throw new IllegalArgumentException JavaDoc("Null ObjectId");
367         }
368
369         ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(
370                 id.getEntityName());
371
372         Persistent cachedObject = (Persistent) getGraphManager().getNode(id);
373
374         // merge into an existing object
375
if (cachedObject != null) {
376
377             // TODO: Andrus, 1/24/2006 implement smart merge for modified objects...
378
if (cachedObject != prototype
379                     && cachedObject.getPersistenceState() != PersistenceState.MODIFIED
380                     && cachedObject.getPersistenceState() != PersistenceState.DELETED) {
381
382                 if (prototype != null
383                         && ((Persistent) prototype).getPersistenceState() != PersistenceState.HOLLOW) {
384
385                     descriptor.shallowMerge(prototype, cachedObject);
386
387                     if (cachedObject.getPersistenceState() == PersistenceState.HOLLOW) {
388                         cachedObject.setPersistenceState(PersistenceState.COMMITTED);
389                     }
390                 }
391             }
392
393             return cachedObject;
394         }
395         // create and merge into a new object
396
else {
397
398             // Andrus, 1/26/2006 - note that there is a tricky case of a temporary object
399
// passed from peer DataContext... In the past we used to throw an exception
400
// or return null. Now that we can have a valid (but generally
401
// indistinguishible) case of such object passed from parent, we let it
402
// slip... Not sure what's the best way of handling it that does not involve
403
// breaking encapsulation of the DataChannel to detect where in the hierarchy
404
// this context is.
405

406             Persistent localObject;
407             synchronized (getGraphManager()) {
408                 localObject = (Persistent) descriptor.createObject();
409
410                 localObject.setObjectContext(this);
411                 localObject.setObjectId(id);
412
413                 getGraphManager().registerNode(id, localObject);
414             }
415
416             if (prototype != null) {
417                 localObject.setPersistenceState(PersistenceState.COMMITTED);
418                 descriptor.shallowMerge(prototype, localObject);
419             }
420             else {
421                 localObject.setPersistenceState(PersistenceState.HOLLOW);
422             }
423
424             return localObject;
425         }
426
427         // ****** Copied from DataContext - end *******
428
}
429
430     public void propertyChanged(
431             Persistent object,
432             String JavaDoc property,
433             Object JavaDoc oldValue,
434             Object JavaDoc newValue) {
435
436         graphAction.handlePropertyChange(object, property, oldValue, newValue);
437     }
438
439     public Collection JavaDoc uncommittedObjects() {
440         synchronized (graphManager) {
441             return graphManager.dirtyNodes();
442         }
443     }
444
445     public Collection JavaDoc deletedObjects() {
446         synchronized (graphManager) {
447             return graphManager.dirtyNodes(PersistenceState.DELETED);
448         }
449     }
450
451     public Collection JavaDoc modifiedObjects() {
452         synchronized (graphManager) {
453             return graphManager.dirtyNodes(PersistenceState.MODIFIED);
454         }
455     }
456
457     public Collection JavaDoc newObjects() {
458         synchronized (graphManager) {
459             return graphManager.dirtyNodes(PersistenceState.NEW);
460         }
461     }
462
463     // ****** non-public methods ******
464

465     void registerNewObject(
466             Persistent object,
467             String JavaDoc entityName,
468             ClassDescriptor descriptor) {
469         ObjectId id = new ObjectId(entityName);
470
471         // must follow this exact order of property initialization per CAY-653, i.e. have
472
// the id and the context in place BEFORE setPersistence is called
473
object.setObjectId(id);
474         object.setObjectContext(this);
475         object.setPersistenceState(PersistenceState.NEW);
476
477         synchronized (graphManager) {
478             graphManager.registerNode(object.getObjectId(), object);
479             graphManager.nodeCreated(object.getObjectId());
480         }
481     }
482
483     Persistent createFault(ObjectId id) {
484         ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(
485                 id.getEntityName());
486
487         Persistent object;
488         synchronized (graphManager) {
489             object = (Persistent) descriptor.createObject();
490
491             object.setPersistenceState(PersistenceState.HOLLOW);
492             object.setObjectContext(this);
493             object.setObjectId(id);
494
495             graphManager.registerNode(id, object);
496         }
497
498         return object;
499     }
500 }
501
Popular Tags