KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cayenne > access > DataContext


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.access;
21
22 import java.io.IOException JavaDoc;
23 import java.io.ObjectInputStream JavaDoc;
24 import java.io.ObjectOutputStream JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.Collection JavaDoc;
27 import java.util.Collections JavaDoc;
28 import java.util.HashMap JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.List JavaDoc;
31 import java.util.Map JavaDoc;
32
33 import org.apache.cayenne.BaseContext;
34 import org.apache.cayenne.CayenneException;
35 import org.apache.cayenne.CayenneRuntimeException;
36 import org.apache.cayenne.DataChannel;
37 import org.apache.cayenne.DataObject;
38 import org.apache.cayenne.DataObjectUtils;
39 import org.apache.cayenne.DataRow;
40 import org.apache.cayenne.DeleteDenyException;
41 import org.apache.cayenne.Fault;
42 import org.apache.cayenne.LifecycleListener;
43 import org.apache.cayenne.ObjectContext;
44 import org.apache.cayenne.ObjectId;
45 import org.apache.cayenne.PersistenceState;
46 import org.apache.cayenne.Persistent;
47 import org.apache.cayenne.QueryResponse;
48 import org.apache.cayenne.access.event.DataContextEvent;
49 import org.apache.cayenne.access.util.IteratedSelectObserver;
50 import org.apache.cayenne.cache.QueryCache;
51 import org.apache.cayenne.cache.QueryCacheFactory;
52 import org.apache.cayenne.conf.Configuration;
53 import org.apache.cayenne.event.EventManager;
54 import org.apache.cayenne.event.EventSubject;
55 import org.apache.cayenne.graph.CompoundDiff;
56 import org.apache.cayenne.graph.GraphDiff;
57 import org.apache.cayenne.graph.GraphEvent;
58 import org.apache.cayenne.graph.GraphManager;
59 import org.apache.cayenne.map.DbJoin;
60 import org.apache.cayenne.map.DbRelationship;
61 import org.apache.cayenne.map.EntityResolver;
62 import org.apache.cayenne.map.ObjAttribute;
63 import org.apache.cayenne.map.ObjEntity;
64 import org.apache.cayenne.map.ObjRelationship;
65 import org.apache.cayenne.query.NamedQuery;
66 import org.apache.cayenne.query.ObjectIdQuery;
67 import org.apache.cayenne.query.Query;
68 import org.apache.cayenne.query.QueryMetadata;
69 import org.apache.cayenne.query.RefreshQuery;
70 import org.apache.cayenne.reflect.AttributeProperty;
71 import org.apache.cayenne.reflect.ClassDescriptor;
72 import org.apache.cayenne.reflect.PropertyVisitor;
73 import org.apache.cayenne.reflect.ToManyProperty;
74 import org.apache.cayenne.reflect.ToOneProperty;
75 import org.apache.cayenne.util.EventUtil;
76 import org.apache.cayenne.util.GenericResponse;
77 import org.apache.cayenne.util.Util;
78
79 /**
80  * Class that provides applications with access to Cayenne persistence features. In most
81  * cases this is the only access class directly used in the application.
82  * <p>
83  * Most common DataContext use pattern is to create one DataContext per session. "Session"
84  * may be a an HttpSession in a web application, or any other similar concept in a
85  * multiuser application.
86  * </p>
87  * <p>
88  * DataObjects are registered with DataContext either implicitly when they are fetched via
89  * a query, or read via a relationship from another object, or explicitly via calling
90  * {@link #newObject(Class)}during new DataObject creation. DataContext tracks changes
91  * made to its DataObjects in memory, and flushes them to the database when
92  * {@link #commitChanges()}is called. Until DataContext is committed, changes made to its
93  * objects are not visible in other DataContexts.
94  * </p>
95  * <p>
96  * Each DataObject can belong only to a single DataContext. To create a replica of an
97  * object from a different DataContext in a local context, use
98  * {@link #localObject(ObjectId, Object)} method.
99  * </p>
100  *
101  * @author Andrus Adamchik
102  */

103 public class DataContext extends BaseContext implements DataChannel {
104
105     // DataContext events
106
public static final EventSubject WILL_COMMIT = EventSubject.getSubject(
107             DataContext.class,
108             "DataContextWillCommit");
109     public static final EventSubject DID_COMMIT = EventSubject.getSubject(
110             DataContext.class,
111             "DataContextDidCommit");
112     public static final EventSubject DID_ROLLBACK = EventSubject.getSubject(
113             DataContext.class,
114             "DataContextDidRollback");
115
116     /**
117      * A holder of a DataContext bound to the current thread.
118      *
119      * @since 1.1
120      */

121     // TODO: Andrus, 11/7/2005 - should we use InheritableThreadLocal instead?
122
protected static final ThreadLocal JavaDoc threadDataContext = new ThreadLocal JavaDoc();
123
124     // event posting default for new DataContexts
125
private static boolean transactionEventsEnabledDefault;
126
127     // enable/disable event handling for individual instances
128
private boolean transactionEventsEnabled;
129
130     // Set of DataContextDelegates to be notified.
131
private DataContextDelegate delegate;
132
133     protected boolean usingSharedSnaphsotCache;
134     protected boolean validatingObjectsOnCommit;
135     protected ObjectStore objectStore;
136     protected QueryCache queryCache;
137
138     // note that entity resolver is initialized from the parent channel the first time it
139
// is accessed, and later cached in the context
140
protected transient EntityResolver entityResolver;
141
142     protected transient DataContextMergeHandler mergeHandler;
143
144     DataContextGraphAction graphAction;
145
146     /**
147      * Stores user defined properties associated with this DataContext.
148      *
149      * @since 1.2
150      */

151     protected Map JavaDoc userProperties;
152
153     /**
154      * Stores the name of parent DataDomain. Used to defer initialization of the parent
155      * QueryEngine after deserialization. This helps avoid an issue with certain servlet
156      * engines (e.g. Tomcat) where HttpSessions with DataContext's are deserialized at
157      * startup before Cayenne stack is fully initialized.
158      */

159     protected transient String JavaDoc lazyInitParentDomainName;
160
161     /**
162      * Returns the DataContext bound to the current thread.
163      *
164      * @since 1.1
165      * @return the DataContext associated with caller thread.
166      * @throws IllegalStateException if there is no DataContext bound to the current
167      * thread.
168      * @see org.apache.cayenne.conf.WebApplicationContextFilter
169      */

170     public static DataContext getThreadDataContext() throws IllegalStateException JavaDoc {
171         DataContext dc = (DataContext) threadDataContext.get();
172         if (dc == null) {
173             throw new IllegalStateException JavaDoc("Current thread has no bound DataContext.");
174         }
175
176         return dc;
177     }
178
179     /**
180      * Binds a DataContext to the current thread. DataContext can later be retrieved by
181      * users in the same thread by calling {@link DataContext#getThreadDataContext}.
182      * Using null parameter will unbind currently bound DataContext.
183      *
184      * @since 1.1
185      */

186     public static void bindThreadDataContext(DataContext context) {
187         threadDataContext.set(context);
188     }
189
190     /**
191      * Factory method that creates and returns a new instance of DataContext based on
192      * default domain. If more than one domain exists in the current configuration,
193      * {@link DataContext#createDataContext(String)} must be used instead. ObjectStore
194      * associated with created DataContext will have a cache stack configured using parent
195      * domain settings.
196      */

197     public static DataContext createDataContext() {
198         return Configuration.getSharedConfiguration().getDomain().createDataContext();
199     }
200
201     /**
202      * Factory method that creates and returns a new instance of DataContext based on
203      * default domain. If more than one domain exists in the current configuration,
204      * {@link DataContext#createDataContext(String, boolean)} must be used instead.
205      * ObjectStore associated with newly created DataContext will have a cache stack
206      * configured according to the specified policy, overriding a parent domain setting.
207      *
208      * @since 1.1
209      */

210     public static DataContext createDataContext(boolean useSharedCache) {
211         return Configuration.getSharedConfiguration().getDomain().createDataContext(
212                 useSharedCache);
213     }
214
215     /**
216      * Factory method that creates and returns a new instance of DataContext using named
217      * domain as its parent. If there is no domain matching the name argument, an
218      * exception is thrown.
219      */

220     public static DataContext createDataContext(String JavaDoc domainName) {
221         DataDomain domain = Configuration.getSharedConfiguration().getDomain(domainName);
222         if (domain == null) {
223             throw new IllegalArgumentException JavaDoc("Non-existent domain: " + domainName);
224         }
225         return domain.createDataContext();
226     }
227
228     /**
229      * Creates and returns new DataContext that will use a named DataDomain as its parent.
230      * ObjectStore associated with newly created DataContext will have a cache stack
231      * configured according to the specified policy, overriding a parent domain setting.
232      *
233      * @since 1.1
234      */

235     public static DataContext createDataContext(String JavaDoc domainName, boolean useSharedCache) {
236
237         DataDomain domain = Configuration.getSharedConfiguration().getDomain(domainName);
238         if (domain == null) {
239             throw new IllegalArgumentException JavaDoc("Non-existent domain: " + domainName);
240         }
241         return domain.createDataContext(useSharedCache);
242     }
243
244     /**
245      * Creates a new DataContext that is not attached to the Cayenne stack.
246      */

247     public DataContext() {
248         this(null, null);
249     }
250
251     /**
252      * Creates a new DataContext with parent DataChannel and ObjectStore.
253      *
254      * @since 1.2
255      */

256     public DataContext(DataChannel channel, ObjectStore objectStore) {
257         // use a setter to properly initialize EntityResolver
258
setChannel(channel);
259
260         this.setTransactionEventsEnabled(transactionEventsEnabledDefault);
261
262         // inject self as parent context
263
if (objectStore != null) {
264             this.objectStore = objectStore;
265             objectStore.setContext(this);
266
267             DataDomain domain = getParentDataDomain();
268             this.usingSharedSnaphsotCache = domain != null
269                     && objectStore.getDataRowCache() == domain.getSharedSnapshotCache();
270         }
271
272         this.graphAction = new DataContextGraphAction(this);
273     }
274
275     /**
276      * Returns {@link QueryCache} used by this DataContext, creating it on the fly if
277      * needed. Uses parent DataDomain {@link QueryCacheFactory} to initialize the cache
278      * for the first time.
279      *
280      * @since 3.0
281      */

282     public QueryCache getQueryCache() {
283
284         if (queryCache == null) {
285             synchronized (this) {
286                 if (queryCache == null) {
287                     queryCache = getParentDataDomain()
288                             .getQueryCacheFactory()
289                             .getQueryCache(Collections.EMPTY_MAP);
290                 }
291             }
292         }
293
294         return queryCache;
295     }
296
297     /**
298      * Sets a QueryCache to be used for storing cached query results.
299      *
300      * @since 3.0
301      */

302     public void setQueryCache(QueryCache queryCache) {
303         this.queryCache = queryCache;
304     }
305
306     /**
307      * Returns a map of user-defined properties associated with this DataContext.
308      *
309      * @since 1.2
310      */

311     protected Map JavaDoc getUserProperties() {
312         // as not all users will take advantage of properties, creating the
313
// map on demand to keep DataContext lean...
314
if (userProperties == null) {
315             userProperties = new HashMap JavaDoc();
316         }
317
318         return userProperties;
319     }
320
321     /**
322      * Creates and returns a new child DataContext.
323      *
324      * @since 1.2
325      */

326     public DataContext createChildDataContext() {
327         DataContextFactory factory = getParentDataDomain().getDataContextFactory();
328
329         // child ObjectStore should not have direct access to snapshot cache, so do not
330
// pass it in constructor.
331
ObjectStore objectStore = new ObjectStore();
332
333         DataContext child = factory != null ? factory
334                 .createDataContext(this, objectStore) : new DataContext(
335                 (DataChannel) this,
336                 objectStore);
337
338         child.setValidatingObjectsOnCommit(isValidatingObjectsOnCommit());
339         child.usingSharedSnaphsotCache = isUsingSharedSnapshotCache();
340         return child;
341     }
342
343     /**
344      * Returns a user-defined property previously set via 'setUserProperty'. Note that it
345      * is a caller responsibility to synchronize access to properties.
346      *
347      * @since 1.2
348      */

349     public Object JavaDoc getUserProperty(String JavaDoc key) {
350         return getUserProperties().get(key);
351     }
352
353     /**
354      * Sets a user-defined property. Note that it is a caller responsibility to
355      * synchronize access to properties.
356      *
357      * @since 1.2
358      */

359     public void setUserProperty(String JavaDoc key, Object JavaDoc value) {
360         getUserProperties().put(key, value);
361     }
362
363     /**
364      * @since 1.2
365      */

366     public void setChannel(DataChannel channel) {
367         if (this.channel != channel) {
368
369             if (this.mergeHandler != null) {
370                 this.mergeHandler.setActive(false);
371             }
372
373             this.entityResolver = null;
374             this.mergeHandler = null;
375
376             this.channel = channel;
377
378             if (channel != null) {
379
380                 // cache entity resolver, as we have no idea how expensive it is to query
381
// it on the channel every time
382
this.entityResolver = channel.getEntityResolver();
383
384                 EventManager eventManager = channel.getEventManager();
385
386                 if (eventManager != null) {
387                     this.mergeHandler = new DataContextMergeHandler(this);
388
389                     // listen to our channel events...
390
// note that we must reset listener on channel switch, as there is no
391
// guarantee that a new channel uses the same EventManager.
392
EventUtil.listenForChannelEvents(channel, mergeHandler);
393                 }
394             }
395         }
396     }
397
398     public DataChannel getChannel() {
399         awakeFromDeserialization();
400         return super.getChannel();
401     }
402
403     /**
404      * Returns a DataDomain used by this DataContext. DataDomain is looked up in the
405      * DataChannel hierarchy. If a channel is not a DataDomain or a DataContext, null is
406      * returned.
407      *
408      * @return DataDomain that is a direct or indirect parent of this DataContext in the
409      * DataChannel hierarchy.
410      * @since 1.1
411      */

412     public DataDomain getParentDataDomain() {
413         awakeFromDeserialization();
414
415         if (channel == null) {
416             return null;
417         }
418
419         if (channel instanceof DataDomain) {
420             return (DataDomain) channel;
421         }
422
423         List JavaDoc response = channel.onQuery(this, new DataDomainQuery()).firstList();
424
425         if (response != null
426                 && response.size() > 0
427                 && response.get(0) instanceof DataDomain) {
428             return (DataDomain) response.get(0);
429         }
430
431         return null;
432     }
433
434     /**
435      * Sets a DataContextDelegate for this context. Delegate is notified of certain events
436      * in the DataContext lifecycle and can customize DataContext behavior.
437      *
438      * @since 1.1
439      */

440     public void setDelegate(DataContextDelegate delegate) {
441         this.delegate = delegate;
442     }
443
444     /**
445      * Returns a delegate currently associated with this DataContext.
446      *
447      * @since 1.1
448      */

449     public DataContextDelegate getDelegate() {
450         return delegate;
451     }
452
453     /**
454      * @return a delegate instance if it is initialized, or a shared noop implementation
455      * the context has no delegate. Useful to prevent extra null checks and
456      * conditional logic in the code.
457      * @since 1.1
458      */

459     DataContextDelegate nonNullDelegate() {
460         return (delegate != null) ? delegate : NoopDelegate.noopDelegate;
461     }
462
463     /**
464      * Returns ObjectStore associated with this DataContext.
465      */

466     public ObjectStore getObjectStore() {
467         return objectStore;
468     }
469
470     /**
471      * Returns <code>true</code> if there are any modified, deleted or new objects
472      * registered with this DataContext, <code>false</code> otherwise.
473      */

474     public boolean hasChanges() {
475         return getObjectStore().hasChanges();
476     }
477
478     /**
479      * Returns a list of objects that are registered with this DataContext and have a
480      * state PersistenceState.NEW
481      */

482     public Collection JavaDoc newObjects() {
483         return getObjectStore().objectsInState(PersistenceState.NEW);
484     }
485
486     /**
487      * Returns a list of objects that are registered with this DataContext and have a
488      * state PersistenceState.DELETED
489      */

490     public Collection JavaDoc deletedObjects() {
491         return getObjectStore().objectsInState(PersistenceState.DELETED);
492     }
493
494     /**
495      * Returns a list of objects that are registered with this DataContext and have a
496      * state PersistenceState.MODIFIED
497      */

498     public Collection JavaDoc modifiedObjects() {
499         return getObjectStore().objectsInState(PersistenceState.MODIFIED);
500     }
501
502     /**
503      * Returns a collection of all uncommitted registered objects.
504      *
505      * @since 1.2
506      */

507     public Collection JavaDoc uncommittedObjects() {
508
509         int len = getObjectStore().registeredObjectsCount();
510         if (len == 0) {
511             return Collections.EMPTY_LIST;
512         }
513
514         // guess target collection size
515
Collection JavaDoc objects = new ArrayList JavaDoc(len > 100 ? len / 2 : len);
516
517         Iterator JavaDoc it = getObjectStore().getObjectIterator();
518         while (it.hasNext()) {
519             Persistent object = (Persistent) it.next();
520             int state = object.getPersistenceState();
521             if (state == PersistenceState.MODIFIED
522                     || state == PersistenceState.NEW
523                     || state == PersistenceState.DELETED) {
524
525                 objects.add(object);
526             }
527         }
528
529         return objects;
530     }
531
532     /**
533      * Returns a DataRow reflecting current, possibly uncommitted, object state.
534      * <p>
535      * <strong>Warning:</strong> This method will return a partial snapshot if an object
536      * or one of its related objects that propagate their keys to this object have
537      * temporary ids. DO NOT USE this method if you expect a DataRow to represent a
538      * complete object state.
539      * </p>
540      *
541      * @since 1.1
542      */

543     public DataRow currentSnapshot(final Persistent object) {
544
545         // for a HOLLOW object return snapshot from cache
546
if (object.getPersistenceState() == PersistenceState.HOLLOW
547                 && object.getObjectContext() != null) {
548
549             return getObjectStore().getSnapshot(object.getObjectId());
550         }
551
552         ObjEntity entity = getEntityResolver().lookupObjEntity(object);
553         final ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(
554                 entity.getName());
555         final DataRow snapshot = new DataRow(10);
556
557         descriptor.visitProperties(new PropertyVisitor() {
558
559             public boolean visitAttribute(AttributeProperty property) {
560                 ObjAttribute objAttr = property.getAttribute();
561
562                 // processing compound attributes correctly
563
snapshot.put(objAttr.getDbAttributePath(), property
564                         .readPropertyDirectly(object));
565                 return true;
566             }
567
568             public boolean visitToMany(ToManyProperty property) {
569                 // do nothing
570
return true;
571             }
572
573             public boolean visitToOne(ToOneProperty property) {
574                 ObjRelationship rel = property.getRelationship();
575
576                 // if target doesn't propagates its key value, skip it
577
if (rel.isSourceIndependentFromTargetChange()) {
578                     return true;
579                 }
580
581                 Object JavaDoc targetObject = property.readPropertyDirectly(object);
582                 if (targetObject == null) {
583                     return true;
584                 }
585
586                 // if target is Fault, get id attributes from stored snapshot
587
// to avoid unneeded fault triggering
588
if (targetObject instanceof Fault) {
589                     DataRow storedSnapshot = getObjectStore().getSnapshot(
590                             object.getObjectId());
591                     if (storedSnapshot == null) {
592                         throw new CayenneRuntimeException(
593                                 "No matching objects found for ObjectId "
594                                         + object.getObjectId()
595                                         + ". Object may have been deleted externally.");
596                     }
597
598                     DbRelationship dbRel = (DbRelationship) rel.getDbRelationships().get(
599                             0);
600                     Iterator JavaDoc joins = dbRel.getJoins().iterator();
601                     while (joins.hasNext()) {
602                         DbJoin join = (DbJoin) joins.next();
603                         String JavaDoc key = join.getSourceName();
604                         snapshot.put(key, storedSnapshot.get(key));
605                     }
606
607                     return true;
608                 }
609
610                 // target is resolved and we have an FK->PK to it,
611
// so extract it from target...
612
Persistent target = (Persistent) targetObject;
613                 Map JavaDoc idParts = target.getObjectId().getIdSnapshot();
614
615                 // this may happen in uncommitted objects - see the warning in the JavaDoc
616
// of
617
// this method.
618
if (idParts.isEmpty()) {
619                     return true;
620                 }
621
622                 DbRelationship dbRel = (DbRelationship) rel.getDbRelationships().get(0);
623                 Map JavaDoc fk = dbRel.srcFkSnapshotWithTargetSnapshot(idParts);
624                 snapshot.putAll(fk);
625                 return true;
626             }
627         });
628
629         // process object id map
630
// we should ignore any object id values if a corresponding attribute
631
// is a part of relationship "toMasterPK", since those values have been
632
// set above when db relationships where processed.
633
Map JavaDoc thisIdParts = object.getObjectId().getIdSnapshot();
634         if (thisIdParts != null) {
635
636             // put only those that do not exist in the map
637
Iterator JavaDoc idIterator = thisIdParts.entrySet().iterator();
638             while (idIterator.hasNext()) {
639                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) idIterator.next();
640                 Object JavaDoc nextKey = entry.getKey();
641                 if (!snapshot.containsKey(nextKey)) {
642                     snapshot.put(nextKey, entry.getValue());
643                 }
644             }
645         }
646
647         return snapshot;
648     }
649
650     /**
651      * Converts a list of data rows to a list of DataObjects.
652      *
653      * @since 1.1
654      */

655     public List JavaDoc objectsFromDataRows(
656             ObjEntity entity,
657             List JavaDoc dataRows,
658             boolean refresh,
659             boolean resolveInheritanceHierarchy) {
660
661         // TODO: andrus, 10/11/2006 - instead of doing ClassDescriptor lookup, deprecate
662
// this method, replacing it with the one that takes CD as argument. Or get rid of
663
// it all together
664
ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(
665                 entity.getName());
666         return new ObjectResolver(this, descriptor, refresh, resolveInheritanceHierarchy)
667                 .synchronizedObjectsFromDataRows(dataRows);
668     }
669
670     /**
671      * Converts a list of DataRows to a List of DataObject registered with this
672      * DataContext. Internally calls
673      * {@link #objectsFromDataRows(ObjEntity,List,boolean,boolean)}.
674      *
675      * @since 1.1
676      * @see DataRow
677      */

678     public List JavaDoc objectsFromDataRows(
679             Class JavaDoc objectClass,
680             List JavaDoc dataRows,
681             boolean refresh,
682             boolean resolveInheritanceHierarchy) {
683         ObjEntity entity = this.getEntityResolver().lookupObjEntity(objectClass);
684
685         if (entity == null) {
686             throw new CayenneRuntimeException("Unmapped Java class: " + objectClass);
687         }
688
689         return objectsFromDataRows(entity, dataRows, refresh, resolveInheritanceHierarchy);
690     }
691
692     /**
693      * Creates a DataObject from DataRow. This is a convenience shortcut to
694      * {@link #objectsFromDataRows(Class,java.util.List,boolean,boolean)}.
695      *
696      * @see DataRow
697      */

698     public DataObject objectFromDataRow(
699             Class JavaDoc objectClass,
700             DataRow dataRow,
701             boolean refresh) {
702         List JavaDoc list = objectsFromDataRows(
703                 objectClass,
704                 Collections.singletonList(dataRow),
705                 refresh,
706                 true);
707         return (DataObject) list.get(0);
708     }
709
710     /**
711      * @deprecated since 3.0, use {@link #newObject(String)} instead.
712      */

713     public DataObject createAndRegisterNewObject(String JavaDoc objEntityName) {
714         return (DataObject) newObject(objEntityName);
715     }
716
717     /**
718      * Creates and registers a new persistent object.
719      *
720      * @since 1.2
721      */

722     public Persistent newObject(Class JavaDoc persistentClass) {
723         if (persistentClass == null) {
724             throw new NullPointerException JavaDoc("Null 'persistentClass'");
725         }
726
727         ObjEntity entity = getEntityResolver().lookupObjEntity(persistentClass);
728         if (entity == null) {
729             throw new IllegalArgumentException JavaDoc("Class is not mapped with Cayenne: "
730                     + persistentClass.getName());
731         }
732
733         return newObject(entity.getName());
734     }
735
736     /**
737      * Instantiates a new object and registers it with this context. Object class is
738      * determined from the mapped entity. Object class must have a default constructor.
739      * <p/> <i>Note: in most cases {@link #newObject(Class)} method should be used,
740      * however this method is helpful when generic persistent classes are used.</i>
741      *
742      * @since 3.0
743      */

744     public Persistent newObject(String JavaDoc entityName) {
745         ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(entityName);
746         if (descriptor == null) {
747             throw new IllegalArgumentException JavaDoc("Invalid entity name: " + entityName);
748         }
749
750         Persistent object;
751         try {
752             object = (Persistent) descriptor.createObject();
753         }
754         catch (Exception JavaDoc ex) {
755             throw new CayenneRuntimeException("Error instantiating object.", ex);
756         }
757
758         // this will initialize to-many lists
759
descriptor.injectValueHolders(object);
760
761         ObjectId id = new ObjectId(entityName);
762
763         // note that the order of initialization of persistence artifacts below is
764
// important - do not change it lightly
765
object.setObjectId(id);
766         object.setObjectContext(this);
767         object.setPersistenceState(PersistenceState.NEW);
768         getObjectStore().registerNode(id, object);
769         getObjectStore().nodeCreated(id);
770
771         return object;
772     }
773
774     /**
775      * Instantiates new object and registers it with itself. Object class must have a
776      * default constructor.
777      *
778      * @since 1.1
779      * @deprecated since 3.0, use {@link #newObject(Class)} instead.
780      */

781     public DataObject createAndRegisterNewObject(Class JavaDoc objectClass) {
782         if (objectClass == null) {
783             throw new NullPointerException JavaDoc("DataObject class can't be null.");
784         }
785
786         ObjEntity entity = getEntityResolver().lookupObjEntity(objectClass);
787         if (entity == null) {
788             throw new IllegalArgumentException JavaDoc("Class is not mapped with Cayenne: "
789                     + objectClass.getName());
790         }
791
792         return createAndRegisterNewObject(entity.getName());
793     }
794
795     /**
796      * Registers a transient object with the context, recursively registering all
797      * transient persistent objects attached to this object via relationships. <p/><i>Note
798      * that since 3.0 this method takes Object as an argument instead of a
799      * {@link DataObject}.</i>
800      *
801      * @param object new object that needs to be made persistent.
802      */

803     public void registerNewObject(Object JavaDoc object) {
804         if (object == null) {
805             throw new NullPointerException JavaDoc("Can't register null object.");
806         }
807
808         ObjEntity entity = getEntityResolver().lookupObjEntity(object);
809         if (entity == null) {
810             throw new IllegalArgumentException JavaDoc(
811                     "Can't find ObjEntity for DataObject class: "
812                             + object.getClass().getName()
813                             + ", class is likely not mapped.");
814         }
815
816         final Persistent persistent = (Persistent) object;
817
818         // sanity check - maybe already registered
819
if (persistent.getObjectId() != null) {
820             if (persistent.getObjectContext() == this) {
821                 // already registered, just ignore
822
return;
823             }
824             else if (persistent.getObjectContext() != null) {
825                 throw new IllegalStateException JavaDoc(
826                         "DataObject is already registered with another DataContext. "
827                                 + "Try using 'localObjects()' instead.");
828             }
829         }
830         else {
831             persistent.setObjectId(new ObjectId(entity.getName()));
832         }
833
834         persistent.setObjectContext(this);
835         persistent.setPersistenceState(PersistenceState.NEW);
836
837         getObjectStore().registerNode(persistent.getObjectId(), object);
838         getObjectStore().nodeCreated(persistent.getObjectId());
839
840         // now we need to find all arc changes, inject missing value holders and pull in
841
// all transient connected objects
842

843         ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(
844                 entity.getName());
845         if (descriptor == null) {
846             throw new IllegalArgumentException JavaDoc("Invalid entity name: " + entity.getName());
847         }
848
849         descriptor.visitProperties(new PropertyVisitor() {
850
851             public boolean visitToMany(ToManyProperty property) {
852                 property.injectValueHolder(persistent);
853
854                 if (!property.isFault(persistent)) {
855                     Iterator JavaDoc it = ((Collection JavaDoc) property.readProperty(persistent))
856                             .iterator();
857                     while (it.hasNext()) {
858                         Object JavaDoc target = it.next();
859
860                         if (target instanceof DataObject) {
861                             DataObject targetDO = (DataObject) target;
862
863                             // make sure it is registered
864
registerNewObject(targetDO);
865                             getObjectStore().arcCreated(
866                                     persistent.getObjectId(),
867                                     targetDO.getObjectId(),
868                                     property.getName());
869                         }
870                     }
871                 }
872                 return true;
873             }
874
875             public boolean visitToOne(ToOneProperty property) {
876                 Object JavaDoc target = property.readPropertyDirectly(persistent);
877
878                 if (target instanceof DataObject) {
879
880                     DataObject targetDO = (DataObject) target;
881
882                     // make sure it is registered
883
registerNewObject(targetDO);
884                     getObjectStore().arcCreated(
885                             persistent.getObjectId(),
886                             targetDO.getObjectId(),
887                             property.getName());
888                 }
889                 return true;
890             }
891
892             public boolean visitAttribute(AttributeProperty property) {
893                 return true;
894             }
895         });
896     }
897
898     /**
899      * Unregisters a Collection of DataObjects from the DataContext and the underlying
900      * ObjectStore. This operation also unsets DataContext and ObjectId for each object
901      * and changes its state to TRANSIENT.
902      *
903      * @see #invalidateObjects(Collection)
904      */

905     public void unregisterObjects(Collection JavaDoc dataObjects) {
906         getObjectStore().objectsUnregistered(dataObjects);
907     }
908
909     /**
910      * "Invalidates" a Collection of persistent objects. This operation would remove each
911      * object's snapshot from cache and change object's state to HOLLOW. On the next
912      * access to this object, it will be refetched.
913      *
914      * @see #unregisterObjects(Collection)
915      * @see RefreshQuery
916      */

917     public void invalidateObjects(Collection JavaDoc objects) {
918         performGenericQuery(new RefreshQuery(objects));
919     }
920
921     /**
922      * Schedules all objects in the collection for deletion on the next commit of this
923      * DataContext. Object's persistence state is changed to PersistenceState.DELETED;
924      * objects related to this object are processed according to delete rules, i.e.
925      * relationships can be unset ("nullify" rule), deletion operation is cascaded
926      * (cascade rule).
927      * <p>
928      * <i>"Nullify" delete rule side effect: </i> passing a collection representing
929      * to-many relationship with nullify delete rule may result in objects being removed
930      * from collection.
931      * </p>
932      *
933      * @since 1.2
934      */

935     public void deleteObjects(Collection JavaDoc objects) {
936         if (objects.isEmpty()) {
937             return;
938         }
939
940         // clone object list... this maybe a relationship collection with nullify delete
941
// rule, so modifying
942
Iterator JavaDoc it = new ArrayList JavaDoc(objects).iterator();
943         while (it.hasNext()) {
944             Persistent object = (Persistent) it.next();
945             deleteObject(object);
946         }
947     }
948
949     /**
950      * Schedules an object for deletion on the next commit of this DataContext. Object's
951      * persistence state is changed to PersistenceState.DELETED; objects related to this
952      * object are processed according to delete rules, i.e. relationships can be unset
953      * ("nullify" rule), deletion operation is cascaded (cascade rule).
954      *
955      * @param object a persistent object that we want to delete.
956      * @throws DeleteDenyException if a DENY delete rule is applicable for object
957      * deletion.
958      * @throws NullPointerException if object is null.
959      */

960     public void deleteObject(Object JavaDoc object) throws DeleteDenyException {
961         new DataContextDeleteAction(this).performDelete((Persistent) object);
962     }
963
964     /**
965      * Refetches object data for ObjectId. This method is used internally by Cayenne to
966      * resolve objects in state <code>PersistenceState.HOLLOW</code>. It can also be
967      * used to refresh certain objects.
968      *
969      * @throws CayenneRuntimeException if object id doesn't match any records, or if there
970      * is more than one object is fetched.
971      * @deprecated since 3.0 use {@link ObjectIdQuery} with appropraite refresh settings.
972      */

973     public DataObject refetchObject(ObjectId oid) {
974
975         if (oid == null) {
976             throw new NullPointerException JavaDoc("Null ObjectId");
977         }
978
979         if (oid.isTemporary()) {
980             throw new CayenneRuntimeException("Can't refetch ObjectId "
981                     + oid
982                     + ", as it is a temporary id.");
983         }
984
985         synchronized (getObjectStore()) {
986             DataObject object = (DataObject) objectStore.getNode(oid);
987
988             // clean up any cached data for this object
989
if (object != null) {
990                 this.invalidateObjects(Collections.singleton(object));
991             }
992         }
993
994         DataObject object = (DataObject) DataObjectUtils.objectForQuery(
995                 this,
996                 new ObjectIdQuery(oid));
997
998         if (object == null) {
999             throw new CayenneRuntimeException(
1000                    "Refetch failure: no matching objects found for ObjectId " + oid);
1001        }
1002
1003        return object;
1004    }
1005
1006    /**
1007     * If the parent channel is a DataContext, reverts local changes to make this context
1008     * look like the parent, if the parent channel is a DataDomain, reverts all changes.
1009     *
1010     * @since 1.2
1011     */

1012    // TODO: Andrus, 1/19/2006: implement for nested DataContexts
1013
public void rollbackChangesLocally() {
1014        if (getChannel() instanceof DataDomain) {
1015            rollbackChanges();
1016        }
1017        else {
1018            throw new CayenneRuntimeException("Implementation pending.");
1019        }
1020    }
1021
1022    /**
1023     * Reverts any changes that have occurred to objects registered with DataContext; also
1024     * performs cascading rollback of all parent DataContexts.
1025     */

1026    public void rollbackChanges() {
1027
1028        if (objectStore.hasChanges()) {
1029            GraphDiff diff = getObjectStore().getChanges();
1030
1031            // call channel with changes BEFORE reverting them, so that any interceptors
1032
// could record them
1033

1034            if (channel != null) {
1035                channel.onSync(this, diff, DataChannel.ROLLBACK_CASCADE_SYNC);
1036            }
1037
1038            getObjectStore().objectsRolledBack();
1039            fireDataChannelRolledback(this, diff);
1040        }
1041    }
1042
1043    /**
1044     * "Flushes" the changes to the parent {@link DataChannel}. If the parent channel is
1045     * a DataContext, it updates its objects with this context's changes, without a
1046     * database update. If it is a DataDomain (the most common case), the changes are
1047     * written to the database. To cause cascading commit all the way to the database, one
1048     * must use {@link #commitChanges()}.
1049     *
1050     * @since 1.2
1051     * @see #commitChanges()
1052     */

1053    public void commitChangesToParent() {
1054        flushToParent(false);
1055    }
1056
1057    /**
1058     * Synchronizes object graph with the database. Executes needed insert, update and
1059     * delete queries (generated internally).
1060     */

1061    public void commitChanges() throws CayenneRuntimeException {
1062        flushToParent(true);
1063    }
1064
1065    /**
1066     * Returns EventManager associated with the ObjectStore.
1067     *
1068     * @since 1.2
1069     */

1070    public EventManager getEventManager() {
1071        return channel != null ? channel.getEventManager() : null;
1072    }
1073
1074    /**
1075     * An implementation of a {@link DataChannel} method that is used by child contexts to
1076     * synchronize state with this context. Not intended for direct use.
1077     *
1078     * @since 1.2
1079     */

1080    public GraphDiff onSync(
1081            ObjectContext originatingContext,
1082            GraphDiff changes,
1083            int syncType) {
1084        // sync client changes
1085
switch (syncType) {
1086            case DataChannel.ROLLBACK_CASCADE_SYNC:
1087                return onContextRollback(originatingContext);
1088            case DataChannel.FLUSH_NOCASCADE_SYNC:
1089                return onContextFlush(originatingContext, changes, false);
1090            case DataChannel.FLUSH_CASCADE_SYNC:
1091                return onContextFlush(originatingContext, changes, true);
1092            default:
1093                throw new CayenneRuntimeException("Unrecognized SyncMessage type: "
1094                        + syncType);
1095        }
1096    }
1097
1098    GraphDiff onContextRollback(ObjectContext originatingContext) {
1099        rollbackChanges();
1100        return new CompoundDiff();
1101    }
1102
1103    GraphDiff onContextFlush(
1104            ObjectContext originatingContext,
1105            GraphDiff changes,
1106            boolean cascade) {
1107
1108        if (this != originatingContext && changes != null) {
1109            changes.apply(new ChildDiffLoader(this));
1110            fireDataChannelChanged(originatingContext, changes);
1111        }
1112
1113        return (cascade) ? flushToParent(true) : new CompoundDiff();
1114    }
1115
1116    /**
1117     * Synchronizes with the parent channel, performing a flush or a commit.
1118     *
1119     * @since 1.2
1120     */

1121    GraphDiff flushToParent(boolean cascade) {
1122
1123        if (this.getChannel() == null) {
1124            throw new CayenneRuntimeException(
1125                    "Cannot commit changes - channel is not set.");
1126        }
1127
1128        int syncType = cascade
1129                ? DataChannel.FLUSH_CASCADE_SYNC
1130                : DataChannel.FLUSH_NOCASCADE_SYNC;
1131        
1132        ObjectStore objectStore = getObjectStore();
1133
1134        // prevent multiple commits occuring simulteneously
1135
synchronized (objectStore) {
1136
1137            DataContextFlushEventHandler eventHandler = null;
1138
1139            ObjectStoreGraphDiff changes = objectStore.getChanges();
1140            boolean noop = isValidatingObjectsOnCommit()
1141                    ? changes.validateAndCheckNoop()
1142                    : changes.isNoop();
1143
1144            if (noop) {
1145                // need to clear phantom changes
1146
objectStore.postprocessAfterPhantomCommit();
1147                return new CompoundDiff();
1148            }
1149
1150            if (isTransactionEventsEnabled()) {
1151                eventHandler = new DataContextFlushEventHandler(this);
1152                eventHandler.registerForDataContextEvents();
1153                fireWillCommit();
1154            }
1155
1156            try {
1157                GraphDiff returnChanges = getChannel().onSync(this, changes, syncType);
1158
1159                // note that this is a hack resulting from a fix to CAY-766... To support
1160
// valid object state in PostPersist callback, 'postprocessAfterCommit' is
1161
// invoked by DataDomain.onSync(..). Unless the parent is DataContext, and
1162
// this method is not invoked!! As a result, PostPersist will contain temp
1163
// ObjectIds in nested contexts and perm ones in flat contexts.
1164
// Pending better callback design .....
1165
if (objectStore.hasChanges()) {
1166                    objectStore.postprocessAfterCommit(returnChanges);
1167                }
1168
1169                // this is a legacy event ... will deprecate in 2.0
1170
fireTransactionCommitted();
1171
1172                // this event is caught by peer nested DataContexts to synchronize the
1173
// state
1174
fireDataChannelCommitted(this, changes);
1175
1176                // this event is caught by child DataContexts to update temporary
1177
// ObjectIds with permanent
1178
if (!returnChanges.isNoop()) {
1179                    fireDataChannelCommitted(getChannel(), returnChanges);
1180                }
1181
1182                return returnChanges;
1183            }
1184            // "catch" is needed to unwrap OptimisticLockExceptions
1185
catch (CayenneRuntimeException ex) {
1186                fireTransactionRolledback();
1187
1188                Throwable JavaDoc unwound = Util.unwindException(ex);
1189
1190                if (unwound instanceof CayenneRuntimeException) {
1191                    throw (CayenneRuntimeException) unwound;
1192                }
1193                else {
1194                    throw new CayenneRuntimeException("Commit Exception", unwound);
1195                }
1196            }
1197            finally {
1198
1199                if (isTransactionEventsEnabled()) {
1200                    eventHandler.unregisterFromDataContextEvents();
1201                }
1202            }
1203        }
1204    }
1205
1206    /**
1207     * Performs a single database select query returning result as a ResultIterator. It is
1208     * caller's responsibility to explicitly close the ResultIterator. A failure to do so
1209     * will result in a database connection not being released. Another side effect of an
1210     * open ResultIterator is that an internal Cayenne transaction that originated in this
1211     * method stays open until the iterator is closed. So users should normally close the
1212     * iterator within the same thread that opened it.
1213     */

1214    public ResultIterator performIteratedQuery(Query query) throws CayenneException {
1215        if (Transaction.getThreadTransaction() != null) {
1216            return internalPerformIteratedQuery(query);
1217        }
1218        else {
1219
1220            // manually manage a transaction, so that a ResultIterator wrapper could close
1221
// it when it is done.
1222
Transaction tx = getParentDataDomain().createTransaction();
1223            Transaction.bindThreadTransaction(tx);
1224
1225            ResultIterator result;
1226            try {
1227                result = internalPerformIteratedQuery(query);
1228            }
1229            catch (Exception JavaDoc e) {
1230                Transaction.bindThreadTransaction(null);
1231                tx.setRollbackOnly();
1232                throw new CayenneException(e);
1233            }
1234            finally {
1235                // note: we are keeping the transaction bound to the current thread on
1236
// success - iterator will unbind it. Unsetting a transaction here would
1237
// result in some strangeness, at least on Ingres
1238

1239                if (tx.getStatus() == Transaction.STATUS_MARKED_ROLLEDBACK) {
1240                    try {
1241                        tx.rollback();
1242                    }
1243                    catch (Exception JavaDoc rollbackEx) {
1244                    }
1245                }
1246            }
1247
1248            return new TransactionResultIteratorDecorator(result, tx);
1249        }
1250    }
1251
1252    /**
1253     * Runs an iterated query in transactional context provided by the caller.
1254     *
1255     * @since 1.2
1256     */

1257    ResultIterator internalPerformIteratedQuery(Query query) throws CayenneException {
1258        // note that for now DataChannel API does not support cursors (aka
1259
// ResultIterator), so we have to go directly to the DataDomain.
1260
IteratedSelectObserver observer = new IteratedSelectObserver();
1261        getParentDataDomain().performQueries(Collections.singletonList(query), observer);
1262        return observer.getResultIterator();
1263    }
1264
1265    /**
1266     * Executes a query returning a generic response.
1267     *
1268     * @since 1.2
1269     */

1270    public QueryResponse performGenericQuery(Query query) {
1271
1272        query = nonNullDelegate().willPerformGenericQuery(this, query);
1273        if (query == null) {
1274            return new GenericResponse();
1275        }
1276
1277        if (this.getChannel() == null) {
1278            throw new CayenneRuntimeException(
1279                    "Can't run query - parent DataChannel is not set.");
1280        }
1281
1282        return onQuery(this, query);
1283    }
1284
1285    /**
1286     * Performs a single selecting query. Various query setting control the behavior of
1287     * this method and the results returned:
1288     * <ul>
1289     * <li>Query caching policy defines whether the results are retrieved from cache or
1290     * fetched from the database. Note that queries that use caching must have a name that
1291     * is used as a caching key.</li>
1292     * <li>Query refreshing policy controls whether to refresh existing data objects and
1293     * ignore any cached values.</li>
1294     * <li>Query data rows policy defines whether the result should be returned as
1295     * DataObjects or DataRows.</li>
1296     * </ul>
1297     * <p>
1298     * <i>Since 1.2 takes any Query parameter, not just GenericSelectQuery</i>
1299     * </p>
1300     *
1301     * @return A list of DataObjects or a DataRows, depending on the value returned by
1302     * {@link QueryMetadata#isFetchingDataRows()}.
1303     */

1304    public List JavaDoc performQuery(Query query) {
1305        query = nonNullDelegate().willPerformQuery(this, query);
1306        if (query == null) {
1307            return new ArrayList JavaDoc(1);
1308        }
1309
1310        List JavaDoc result = onQuery(this, query).firstList();
1311        return result != null ? result : new ArrayList JavaDoc(1);
1312    }
1313
1314    /**
1315     * An implementation of a {@link DataChannel} method that is used by child contexts to
1316     * execute queries. Not intended for direct use.
1317     *
1318     * @since 1.2
1319     */

1320    public QueryResponse onQuery(ObjectContext context, Query query) {
1321        return new DataContextQueryAction(this, context, query).execute();
1322    }
1323
1324    /**
1325     * Performs a single database query that does not select rows. Returns an array of
1326     * update counts.
1327     *
1328     * @since 1.1
1329     */

1330    public int[] performNonSelectingQuery(Query query) {
1331        int[] count = performGenericQuery(query).firstUpdateCount();
1332        return count != null ? count : new int[0];
1333    }
1334
1335    /**
1336     * Performs a named mapped query that does not select rows. Returns an array of update
1337     * counts.
1338     *
1339     * @since 1.1
1340     */

1341    public int[] performNonSelectingQuery(String JavaDoc queryName) {
1342        return performNonSelectingQuery(new NamedQuery(queryName));
1343    }
1344
1345    /**
1346     * Performs a named mapped non-selecting query using a map of parameters. Returns an
1347     * array of update counts.
1348     *
1349     * @since 1.1
1350     */

1351    public int[] performNonSelectingQuery(String JavaDoc queryName, Map JavaDoc parameters) {
1352        return performNonSelectingQuery(new NamedQuery(queryName, parameters));
1353    }
1354
1355    /**
1356     * Returns a list of objects or DataRows for a named query stored in one of the
1357     * DataMaps. Internally Cayenne uses a caching policy defined in the named query. If
1358     * refresh flag is true, a refresh is forced no matter what the caching policy is.
1359     *
1360     * @param queryName a name of a GenericSelectQuery defined in one of the DataMaps. If
1361     * no such query is defined, this method will throw a
1362     * CayenneRuntimeException.
1363     * @param expireCachedLists A flag that determines whether refresh of <b>cached lists</b>
1364     * is required in case a query uses caching.
1365     * @since 1.1
1366     */

1367    public List JavaDoc performQuery(String JavaDoc queryName, boolean expireCachedLists) {
1368        return performQuery(queryName, Collections.EMPTY_MAP, expireCachedLists);
1369    }
1370
1371    /**
1372     * Returns a list of objects or DataRows for a named query stored in one of the
1373     * DataMaps. Internally Cayenne uses a caching policy defined in the named query. If
1374     * refresh flag is true, a refresh is forced no matter what the caching policy is.
1375     *
1376     * @param queryName a name of a GenericSelectQuery defined in one of the DataMaps. If
1377     * no such query is defined, this method will throw a
1378     * CayenneRuntimeException.
1379     * @param parameters A map of parameters to use with stored query.
1380     * @param expireCachedLists A flag that determines whether refresh of <b>cached lists</b>
1381     * is required in case a query uses caching.
1382     * @since 1.1
1383     */

1384    public List JavaDoc performQuery(String JavaDoc queryName, Map JavaDoc parameters, boolean expireCachedLists) {
1385        NamedQuery query = new NamedQuery(queryName, parameters);
1386        query.setForceNoCache(expireCachedLists);
1387        return performQuery(query);
1388    }
1389
1390    /**
1391     * Returns EntityResolver. EntityResolver can be null if DataContext has not been
1392     * attached to an DataChannel.
1393     */

1394    public EntityResolver getEntityResolver() {
1395        awakeFromDeserialization();
1396        return entityResolver;
1397    }
1398
1399    /**
1400     * Sets default for posting transaction events by new DataContexts.
1401     *
1402     * @deprecated since 3.0M1 in favor of {@link LifecycleListener}. Will be removed in
1403     * later 3.0 milestones.
1404     */

1405    public static void setTransactionEventsEnabledDefault(boolean flag) {
1406        transactionEventsEnabledDefault = flag;
1407    }
1408
1409    /**
1410     * Enables or disables posting of transaction events by this DataContext.
1411     *
1412     * @deprecated since 3.0M1 in favor of {@link LifecycleListener}. Will be removed in
1413     * later 3.0 milestones.
1414     */

1415    public void setTransactionEventsEnabled(boolean flag) {
1416        this.transactionEventsEnabled = flag;
1417    }
1418
1419    /**
1420     * @deprecated since 3.0M1 in favor of {@link LifecycleListener}. Will be removed in
1421     * later 3.0 milestones.
1422     */

1423    public boolean isTransactionEventsEnabled() {
1424        return this.transactionEventsEnabled;
1425    }
1426
1427    /**
1428     * Returns <code>true</code> if the ObjectStore uses shared cache of a parent
1429     * DataDomain.
1430     *
1431     * @since 1.1
1432     */

1433    public boolean isUsingSharedSnapshotCache() {
1434        return usingSharedSnaphsotCache;
1435    }
1436
1437    /**
1438     * Returns whether this DataContext performs object validation before commit is
1439     * executed.
1440     *
1441     * @since 1.1
1442     */

1443    public boolean isValidatingObjectsOnCommit() {
1444        return validatingObjectsOnCommit;
1445    }
1446
1447    /**
1448     * Sets the property defining whether this DataContext should perform object
1449     * validation before commit is executed.
1450     *
1451     * @since 1.1
1452     */

1453    public void setValidatingObjectsOnCommit(boolean flag) {
1454        this.validatingObjectsOnCommit = flag;
1455    }
1456
1457    /**
1458     * @deprecated since 3.0M1 in favor of {@link LifecycleListener}. Will be removed in
1459     * later 3.0 milestones.
1460     */

1461    void fireWillCommit() {
1462        // post event: WILL_COMMIT
1463
if (this.transactionEventsEnabled) {
1464            DataContextEvent commitChangesEvent = new DataContextEvent(this);
1465            getEventManager().postEvent(commitChangesEvent, DataContext.WILL_COMMIT);
1466        }
1467    }
1468
1469    /**
1470     * @deprecated since 3.0M1 in favor of {@link LifecycleListener}. Will be removed in
1471     * later 3.0 milestones.
1472     */

1473    void fireTransactionRolledback() {
1474        // post event: DID_ROLLBACK
1475
if ((this.transactionEventsEnabled)) {
1476            DataContextEvent commitChangesEvent = new DataContextEvent(this);
1477            getEventManager().postEvent(commitChangesEvent, DataContext.DID_ROLLBACK);
1478        }
1479    }
1480
1481    /**
1482     * @deprecated since 3.0M1 in favor of {@link LifecycleListener}. Will be removed in
1483     * later 3.0 milestones.
1484     */

1485    void fireTransactionCommitted() {
1486        // old-style event
1487
if ((this.transactionEventsEnabled)) {
1488            DataContextEvent commitChangesEvent = new DataContextEvent(this);
1489            getEventManager().postEvent(commitChangesEvent, DataContext.DID_COMMIT);
1490        }
1491
1492    }
1493
1494    /**
1495     * @since 1.2
1496     */

1497    void fireDataChannelCommitted(Object JavaDoc postedBy, GraphDiff changes) {
1498        EventManager manager = getEventManager();
1499
1500        if (manager != null) {
1501            GraphEvent e = new GraphEvent(this, postedBy, changes);
1502            manager.postEvent(e, DataChannel.GRAPH_FLUSHED_SUBJECT);
1503        }
1504    }
1505
1506    /**
1507     * @since 1.2
1508     */

1509    void fireDataChannelRolledback(Object JavaDoc postedBy, GraphDiff changes) {
1510        EventManager manager = getEventManager();
1511
1512        if (manager != null) {
1513            GraphEvent e = new GraphEvent(this, postedBy, changes);
1514            manager.postEvent(e, DataChannel.GRAPH_ROLLEDBACK_SUBJECT);
1515        }
1516    }
1517
1518    /**
1519     * @since 1.2
1520     */

1521    void fireDataChannelChanged(Object JavaDoc postedBy, GraphDiff changes) {
1522        EventManager manager = getEventManager();
1523
1524        if (manager != null) {
1525            GraphEvent e = new GraphEvent(this, postedBy, changes);
1526            manager.postEvent(e, DataChannel.GRAPH_CHANGED_SUBJECT);
1527        }
1528    }
1529
1530    // ---------------------------------------------
1531
// Serialization Support
1532
// ---------------------------------------------
1533

1534    private void writeObject(ObjectOutputStream JavaDoc out) throws IOException JavaDoc {
1535        out.defaultWriteObject();
1536
1537        // If the "parent" of this datacontext is a DataDomain, then just write the
1538
// name of it. Then when deserialization happens, we can get back the DataDomain
1539
// by name, from the shared configuration (which will either load it if need be,
1540
// or return an existing one.
1541

1542        if (this.channel == null && this.lazyInitParentDomainName != null) {
1543            out.writeObject(lazyInitParentDomainName);
1544        }
1545        else if (this.channel instanceof DataDomain) {
1546            DataDomain domain = (DataDomain) this.channel;
1547            out.writeObject(domain.getName());
1548        }
1549        else {
1550            // Hope that whatever this.parent is, that it is Serializable
1551
out.writeObject(this.channel);
1552        }
1553
1554        // Serialize local snapshots cache
1555
if (!isUsingSharedSnapshotCache()) {
1556            out.writeObject(objectStore.getDataRowCache());
1557        }
1558    }
1559
1560    // serialization support
1561
private void readObject(ObjectInputStream JavaDoc in) throws IOException JavaDoc,
1562            ClassNotFoundException JavaDoc {
1563
1564        // 1. read non-transient properties
1565
in.defaultReadObject();
1566
1567        // 2. read parent or its name
1568
Object JavaDoc value = in.readObject();
1569        if (value instanceof DataChannel) {
1570            // A real QueryEngine object - use it
1571
this.channel = (DataChannel) value;
1572        }
1573        else if (value instanceof String JavaDoc) {
1574            // The name of a DataDomain - use it
1575
this.lazyInitParentDomainName = (String JavaDoc) value;
1576        }
1577        else {
1578            throw new CayenneRuntimeException(
1579                    "Parent attribute of DataContext was neither a QueryEngine nor "
1580                            + "the name of a valid DataDomain:"
1581                            + value);
1582        }
1583
1584        // 3. Deserialize local snapshots cache
1585
if (!isUsingSharedSnapshotCache()) {
1586            DataRowStore cache = (DataRowStore) in.readObject();
1587            objectStore.setDataRowCache(cache);
1588        }
1589
1590        // CayenneDataObjects have a transient datacontext
1591
// because at deserialize time the datacontext may need to be different
1592
// than the one at serialize time (for programmer defined reasons).
1593
// So, when a dataobject is resurrected because it's datacontext was
1594
// serialized, it will then set the objects datacontext to the correctone
1595
// If deserialized "otherwise", it will not have a datacontext (good)
1596

1597        synchronized (getObjectStore()) {
1598            Iterator JavaDoc it = objectStore.getObjectIterator();
1599            while (it.hasNext()) {
1600                Persistent object = (Persistent) it.next();
1601                object.setObjectContext(this);
1602            }
1603        }
1604    }
1605
1606    // Re-attaches itself to the parent domain with previously stored name.
1607
//
1608
// TODO: Andrus 11/7/2005 - this is one of the places where Cayenne
1609
// serialization relies on shared config... This is bad. We need some
1610
// sort of thread-local solution that would allow to use an alternative configuration.
1611
//
1612
private final void awakeFromDeserialization() {
1613        if (channel == null && lazyInitParentDomainName != null) {
1614
1615            // call a setter to ensure EntityResolver is extracted from channel
1616
setChannel(Configuration.getSharedConfiguration().getDomain(
1617                    lazyInitParentDomainName));
1618        }
1619    }
1620
1621    /**
1622     * @since 1.2
1623     */

1624    public void propertyChanged(
1625            Persistent object,
1626            String JavaDoc property,
1627            Object JavaDoc oldValue,
1628            Object JavaDoc newValue) {
1629        graphAction.handlePropertyChange(object, property, oldValue, newValue);
1630    }
1631
1632    /**
1633     * Returns this context's ObjectStore.
1634     *
1635     * @since 1.2
1636     */

1637    public GraphManager getGraphManager() {
1638        return objectStore;
1639    }
1640
1641    /**
1642     * Returns an object local to this DataContext and matching the ObjectId. If
1643     * <code>prototype</code> is not null, local object is refreshed with the prototype
1644     * values.
1645     * <p>
1646     * In case you pass a non-null second parameter, you are responsible for setting
1647     * correct persistence state of the returned local object, as generally there is no
1648     * way for Cayenne to determine the resulting local object state.
1649     *
1650     * @since 1.2
1651     */

1652    public Persistent localObject(ObjectId id, Object JavaDoc prototype) {
1653
1654        // ****** Warning: when changing the code below, don't forget to change
1655
// CayenneContext's implementation which right now relies on copy/paste "reuse"
1656

1657        if (id == null) {
1658            throw new IllegalArgumentException JavaDoc("Null ObjectId");
1659        }
1660
1661        // note that per-object ClassDescriptor lookup is needed as even if all
1662
// objects where fetched as a part of the same query, as they may belong to
1663
// different subclasses
1664
ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(
1665                id.getEntityName());
1666
1667        Persistent cachedObject = (Persistent) getGraphManager().getNode(id);
1668
1669        // merge into an existing object
1670
if (cachedObject != null) {
1671
1672            int state = cachedObject.getPersistenceState();
1673
1674            // TODO: Andrus, 1/24/2006 implement smart merge for modified objects...
1675
if (cachedObject != prototype
1676                    && state != PersistenceState.MODIFIED
1677                    && state != PersistenceState.DELETED) {
1678
1679                descriptor.injectValueHolders(cachedObject);
1680
1681                if (prototype != null
1682                        && ((Persistent) prototype).getPersistenceState() != PersistenceState.HOLLOW) {
1683
1684                    descriptor.shallowMerge(prototype, cachedObject);
1685
1686                    if (state == PersistenceState.HOLLOW) {
1687                        cachedObject.setPersistenceState(PersistenceState.COMMITTED);
1688                    }
1689                }
1690            }
1691
1692            return cachedObject;
1693        }
1694        // create and merge into a new object
1695
else {
1696
1697            Persistent localObject;
1698
1699            synchronized (getGraphManager()) {
1700                localObject = (Persistent) descriptor.createObject();
1701
1702                localObject.setObjectContext(this);
1703                localObject.setObjectId(id);
1704
1705                getGraphManager().registerNode(id, localObject);
1706            }
1707
1708            if (prototype != null
1709                    && ((Persistent) prototype).getPersistenceState() != PersistenceState.HOLLOW) {
1710                localObject.setPersistenceState(PersistenceState.COMMITTED);
1711                descriptor.injectValueHolders(localObject);
1712                descriptor.shallowMerge(prototype, localObject);
1713            }
1714            else {
1715                localObject.setPersistenceState(PersistenceState.HOLLOW);
1716            }
1717
1718            return localObject;
1719        }
1720    }
1721}
1722
Popular Tags