0001: /**********************************************************************
0002: Copyright (c) 2006 Andy Jefferson and others. All rights reserved.
0003: Licensed under the Apache License, Version 2.0 (the "License");
0004: you may not use this file except in compliance with the License.
0005: You may obtain a copy of the License at
0006:
0007: http://www.apache.org/licenses/LICENSE-2.0
0008:
0009: Unless required by applicable law or agreed to in writing, software
0010: distributed under the License is distributed on an "AS IS" BASIS,
0011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0012: See the License for the specific language governing permissions and
0013: limitations under the License.
0014:
0015: Contributors:
0016: 2007 Xuan Baldauf - added the use of srm.findObject() to cater for different object lifecycle management policies (in RDBMS and DB4O databases)
0017: 2007 Xuan Baldauf - changes to allow the disabling of clearing of fields when transitioning from PERSISTENT_NEW to TRANSIENT.
0018: ...
0019: **********************************************************************/package org.jpox;
0020:
0021: import java.lang.reflect.Constructor;
0022: import java.lang.reflect.Modifier;
0023: import java.util.ArrayList;
0024: import java.util.Collection;
0025: import java.util.HashMap;
0026: import java.util.HashSet;
0027: import java.util.Iterator;
0028: import java.util.List;
0029: import java.util.Map;
0030: import java.util.Set;
0031:
0032: import org.jpox.api.ApiAdapter;
0033: import org.jpox.cache.CachedPC;
0034: import org.jpox.cache.Level1Cache;
0035: import org.jpox.cache.Level2Cache;
0036: import org.jpox.exceptions.ClassNotDetachableException;
0037: import org.jpox.exceptions.ClassNotPersistableException;
0038: import org.jpox.exceptions.ClassNotResolvedException;
0039: import org.jpox.exceptions.JPOXException;
0040: import org.jpox.exceptions.JPOXObjectNotFoundException;
0041: import org.jpox.exceptions.JPOXOptimisticException;
0042: import org.jpox.exceptions.JPOXUserException;
0043: import org.jpox.exceptions.NoPersistenceInformationException;
0044: import org.jpox.exceptions.ObjectDetachedException;
0045: import org.jpox.exceptions.RollbackStateTransitionException;
0046: import org.jpox.exceptions.TransactionActiveOnCloseException;
0047: import org.jpox.exceptions.TransactionNotActiveException;
0048: import org.jpox.identity.OID;
0049: import org.jpox.identity.OIDFactory;
0050: import org.jpox.jdo.JDOAdapter;
0051: import org.jpox.jdo.JPOXJDOHelper;
0052: import org.jpox.jdo.exceptions.CommitStateTransitionException;
0053: import org.jpox.metadata.AbstractClassMetaData;
0054: import org.jpox.metadata.AbstractMemberMetaData;
0055: import org.jpox.metadata.IdentityType;
0056: import org.jpox.metadata.MetaDataManager;
0057: import org.jpox.metadata.TransactionType;
0058: import org.jpox.state.CallbackHandler;
0059: import org.jpox.state.DetachState;
0060: import org.jpox.store.Extent;
0061: import org.jpox.state.FetchPlanState;
0062: import org.jpox.state.StateManagerFactory;
0063: import org.jpox.store.FieldValues;
0064: import org.jpox.store.SCOID;
0065: import org.jpox.store.query.Query;
0066: import org.jpox.store.StoreManager;
0067: import org.jpox.util.JPOXLogger;
0068: import org.jpox.util.Localiser;
0069: import org.jpox.util.StringUtils;
0070: import org.jpox.util.WeakValueMap;
0071:
0072: /**
0073: * Representation of an ObjectManager.
0074: * An object manager provides management for the persistence of objects into a datastore
0075: * and the retrieval of these objects using various methods.
0076: * <h3>Caching</h3>
0077: * <p>
0078: * An ObjectManager has its own Level 1 cache. This stores objects against their identity. The Level 1 Cache
0079: * is typically a weak referenced map and so cached objects can be garbage collected. Objects are similarly
0080: * placed in the Level 2 cache of the owning PersistenceManagerFactory. This Level 2 cache caches objects across
0081: * multiple ObjectManagers, and so we can use this to get objects retrieved by other ObjectManagers. The Caches
0082: * exist for performance enhancement.
0083: * <h3>Transactions</h3>
0084: * <p>
0085: * Has a single transaction (the "current" transaction).
0086: * </p>
0087: * <h3>Persisted Objects</h3>
0088: * <p>
0089: * When an object involved in the current transaction it is <i>enlisted</i> (calling enlistInTransaction()).
0090: * Its' identity is saved (in "txEnlistedIds") for use later in any "persistenceByReachability" process run at commit.
0091: * Any object that is passed via makePersistent() will be stored (as an identity) in "txKnownPersistedIds" and objects
0092: * persisted due to reachability from these objects will also have their identity stored (in "txFlushedNewIds").
0093: * All of this information is used in the "persistence-by-reachability-at-commit" process which detects if some objects
0094: * originally persisted are no longer reachable and hence should not be persistent after all.
0095: * </p>
0096: * <h3>Queries</h3>
0097: * <p>
0098: * ALl queries run during a transaction are registered here. When the transaction commits/rolls back then
0099: * all queries are notified that the connection is closing so that they can read in any remaining results
0100: * (and the queries remain usable after). When this manager closes all queries registered here will also be closed
0101: * meaning that a query has its lifetime defined by the owning PersistenceManager.
0102: * </p>
0103: *
0104: * @version $Revision: 1.100 $
0105: */
0106: public class ObjectManagerImpl implements ObjectManager {
0107: /** Localisation utility for output messages */
0108: protected static final Localiser LOCALISER = Localiser
0109: .getInstance("org.jpox.Localisation");
0110:
0111: /** Factory for this ObjectManager. */
0112: private final ObjectManagerFactoryImpl omf;
0113:
0114: /** The owning PersistenceManager/EntityManager object. */
0115: private Object owner;
0116:
0117: /** State variable for whether the ObjectManager is closed. */
0118: private boolean closed;
0119:
0120: /** Current FetchPlan for the ObjectManager. */
0121: private FetchPlan fetchPlan;
0122:
0123: /** The ClassLoader resolver to use for class loading issues. */
0124: private ClassLoaderResolver clr = null;
0125:
0126: /** Callback handler for this ObjectManager. */
0127: private CallbackHandler callbacks;
0128:
0129: //-- cache level 1 --//
0130: /** Level 1 Cache */
0131: private Level1Cache cache;
0132:
0133: /** Whether to ignore the cache */
0134: private boolean ignoreCache;
0135:
0136: //-- convenience operations --//
0137: /** State variable used when searching for the StateManager for an object, representing the object. */
0138: private Object objectLookingForStateManager = null;
0139:
0140: /** State variable used when searching for the StateManager for an object, representing the StateManager. */
0141: private StateManager foundStateManager = null;
0142:
0143: //-- transaction --//
0144: /** Current transaction */
0145: private Transaction tx;
0146:
0147: /** Cache of StateManagers enlisted in the current transaction, keyed by the object id. */
0148: private Map enlistedSMCache = new WeakValueMap();
0149:
0150: /** Set of the object ids for all objects enlisted in this transaction. Used in reachability at commit to determine what to check. */
0151: private Set txEnlistedIds = new HashSet();
0152:
0153: /** List of StateManagers for all current dirty objects managed by this ObjectManager. */
0154: private List dirtySMs = new ArrayList(10);
0155:
0156: /** List of StateManagers for all current dirty objects made dirty by reachability. Only used by delete process currently. */
0157: private List indirectDirtySMs = new ArrayList();
0158:
0159: /** StateManagers of all objects that have had managed relationships changed in the last flush-cycle. */
0160: HashSet managedRelationDirtySMs = null;
0161:
0162: /** State indicator whether we are currently managing the relations. */
0163: private boolean managingRelations = false;
0164:
0165: /** Set of Object Ids of objects persisted using persistObject, or known as already persistent in the current transaction. */
0166: private Set txKnownPersistedIds = new HashSet();
0167:
0168: /** Set of Object Ids of objects deleted using deleteObject. */
0169: private Set txKnownDeletedIds = new HashSet();
0170:
0171: /** Set of Object Ids of objects newly persistent in the current transaction */
0172: private Set txFlushedNewIds = new HashSet();
0173:
0174: //-- configuration --//
0175: /** Whether to detach objects on close of the ObjectManager. */
0176: private boolean detachOnClose;
0177:
0178: /** Whether to detach all objects on commit of the transaction. */
0179: private boolean detachAllOnCommit;
0180:
0181: /** Whether to copy objects on attaching. */
0182: private boolean copyOnAttach;
0183:
0184: /** Whether to operate multithreaded */
0185: private boolean multithreaded;
0186:
0187: /** State variable for whether the ObjectManager is currently flushing its operations. */
0188: private boolean flushing = false;
0189:
0190: /** State variable for whether we are currently running detachAllOnCommit. */
0191: private boolean runningDetachAllOnCommit = false;
0192:
0193: /**
0194: * Map containing any thread-specific state information, keyed by the thread number string.
0195: * Used where we don't want to pass information down through a large number of method calls.
0196: */
0197: private HashMap contextInfoByThread = new HashMap();
0198:
0199: /**
0200: * Constructor.
0201: * @param omf Object Manager Factory
0202: * @param owner The owning PM/EM
0203: * @param userName Username for the datastore
0204: * @param password Password for the datastore
0205: * @throws JPOXUserException if an error occurs allocating the necessary requested components
0206: */
0207: public ObjectManagerImpl(ObjectManagerFactoryImpl omf,
0208: Object owner, String userName, String password) {
0209: this .owner = owner;
0210: this .omf = omf;
0211: closed = false;
0212:
0213: // Set up class loading
0214: ClassLoader contextLoader = Thread.currentThread()
0215: .getContextClassLoader();
0216: clr = omf.getOMFContext().getClassLoaderResolver(contextLoader);
0217: try {
0218: ImplementationCreator ic = omf.getOMFContext()
0219: .getImplementationCreator();
0220: if (ic != null) {
0221: clr.registerClassLoader(ic.getClassLoader());
0222: }
0223: } catch (Exception ex) {
0224: // do nothing
0225: }
0226:
0227: // Set up StoreManager, using that of the OMF
0228: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
0229: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("010000", this ,
0230: omf.getOMFContext().getStoreManager()));
0231: }
0232:
0233: PersistenceConfiguration config = omf.getOMFContext()
0234: .getPersistenceConfiguration();
0235:
0236: // Configuration from OMF
0237: setIgnoreCache(config.getIgnoreCache());
0238: setDetachOnClose(config.getDetachOnClose());
0239: setDetachAllOnCommit(config.getDetachAllOnCommit());
0240: setCopyOnAttach(config.getCopyOnAttach());
0241: setMultithreaded(config.getMultithreaded());
0242:
0243: // Set up FetchPlan
0244: fetchPlan = new FetchPlan(omf, clr).setMaxFetchDepth(omf
0245: .getMaxFetchDepth());
0246:
0247: // Set up the Level 1 Cache
0248: initialiseLevel1Cache();
0249:
0250: // Set up the transaction suitable for this ObjectManagerFactory
0251: if (TransactionType.JTA.toString().equalsIgnoreCase(
0252: config.getTransactionType())) {
0253: tx = new JTATransactionImpl(this );
0254: } else {
0255: tx = new TransactionImpl(this );
0256: }
0257: }
0258:
0259: /**
0260: * Method to initialise the L1 cache.
0261: * @throws JPOXUserException if an error occurs setting up the L1 cache
0262: */
0263: protected void initialiseLevel1Cache() {
0264: // Find the L1 cache class name from its plugin name
0265: String level1Type = omf.getPersistenceConfiguration()
0266: .getCacheLevel1Type();
0267: String level1ClassName = getOMFContext().getPluginManager()
0268: .getAttributeValueForExtension("org.jpox.cache_level1",
0269: "name", level1Type, "class-name");
0270: if (level1ClassName == null) {
0271: // Plugin of this name not found
0272: throw new JPOXUserException(LOCALISER.msg("003001",
0273: level1Type)).setFatal();
0274: }
0275:
0276: try {
0277: // Create an instance of the L1 Cache
0278: Class level1CacheClass = clr.classForName(level1ClassName,
0279: OMFContext.class.getClassLoader());
0280: cache = (Level1Cache) level1CacheClass.newInstance();
0281: if (JPOXLogger.CACHE.isDebugEnabled()) {
0282: JPOXLogger.CACHE.debug(LOCALISER.msg("003003",
0283: level1Type));
0284: }
0285: } catch (Exception e) {
0286: // Class name for this L1 cache plugin is not found!
0287: throw new JPOXUserException(LOCALISER.msg("003002",
0288: level1Type, level1ClassName), e).setFatal();
0289: }
0290: }
0291:
0292: /**
0293: * Accessor for whether this ObjectManager is closed.
0294: * @return Whether this manager is closed.
0295: */
0296: public boolean isClosed() {
0297: return closed;
0298: }
0299:
0300: /**
0301: * Accessor for the ClassLoaderResolver
0302: * @return the ClassLoaderResolver
0303: */
0304: public ClassLoaderResolver getClassLoaderResolver() {
0305: return clr;
0306: }
0307:
0308: /**
0309: * Accessor for the Store Manager.
0310: * @return StoreManager
0311: */
0312: public StoreManager getStoreManager() {
0313: return getOMFContext().getStoreManager();
0314: }
0315:
0316: /**
0317: * Accessor for the API adapter.
0318: * @return API adapter.
0319: */
0320: public ApiAdapter getApiAdapter() {
0321: return getOMFContext().getApiAdapter();
0322: }
0323:
0324: /**
0325: * Acessor for the current FetchPlan
0326: * @return FetchPlan
0327: * @since 1.1
0328: */
0329: public FetchPlan getFetchPlan() {
0330: // TODO this should be enabled, but with detachAllOnCommit=true it raises exception
0331: //assertIsOpen();
0332: return fetchPlan;
0333: }
0334:
0335: /**
0336: * Method to return the owner PM/EM object.
0337: * For JDO this will return the PersistenceManager owning this ObjectManager.
0338: * For JPA this will return the EntityManager owning this ObjectManager.
0339: * @return The owner manager object
0340: */
0341: public Object getOwner() {
0342: return owner;
0343: }
0344:
0345: /**
0346: * Gets the context in which this ObjectManager is running
0347: * @return Returns the context.
0348: */
0349: public OMFContext getOMFContext() {
0350: return omf.getOMFContext();
0351: }
0352:
0353: /**
0354: * Accessor for the MetaDataManager for this ObjectManager (and its factory).
0355: * This is used as the interface to MetaData in the ObjectManager/Factory.
0356: * @return Returns the MetaDataManager.
0357: */
0358: public MetaDataManager getMetaDataManager() {
0359: return getOMFContext().getMetaDataManager();
0360: }
0361:
0362: /**
0363: * Mutator for whether the Persistence Manager is multithreaded.
0364: * @param flag Whether to run multithreaded.
0365: */
0366: public void setMultithreaded(boolean flag) {
0367: assertIsOpen();
0368: this .multithreaded = flag;
0369: }
0370:
0371: /**
0372: * Accessor for whether the Persistence Manager is multithreaded.
0373: * @return Whether to run multithreaded.
0374: */
0375: public boolean getMultithreaded() {
0376: assertIsOpen();
0377: return this .multithreaded;
0378: }
0379:
0380: /**
0381: * Mutator for whether to detach objects on close of the ObjectManager.
0382: * <B>This is a JPOX extension and will not work when in JCA mode.</B>
0383: * @param flag Whether to detach on close.
0384: */
0385: public void setDetachOnClose(boolean flag) {
0386: assertIsOpen();
0387: this .detachOnClose = flag;
0388: }
0389:
0390: /**
0391: * Accessor for whether to detach objects on close of the ObjectManager.
0392: * <B>This is a JPOX extension and will not work when in JCA mode.</B>
0393: * @return Whether to detach on close.
0394: */
0395: public boolean getDetachOnClose() {
0396: assertIsOpen();
0397: return detachOnClose;
0398: }
0399:
0400: /**
0401: * Mutator for whether to detach all objects on commit of the transaction.
0402: * @param flag Whether to detach all on commit.
0403: */
0404: public void setDetachAllOnCommit(boolean flag) {
0405: assertIsOpen();
0406: this .detachAllOnCommit = flag;
0407: }
0408:
0409: /**
0410: * Accessor for whether to detach all objects on commit of the transaction.
0411: * @return Whether to detach all on commit.
0412: */
0413: public boolean getDetachAllOnCommit() {
0414: assertIsOpen();
0415: return detachAllOnCommit;
0416: }
0417:
0418: /**
0419: * Mutator for whether to copy on attaching.
0420: * @param flag Whether to copy on attaching
0421: */
0422: public void setCopyOnAttach(boolean flag) {
0423: assertIsOpen();
0424: this .copyOnAttach = flag;
0425: }
0426:
0427: /**
0428: * Accessor for whether to copy on attaching.
0429: * @return Whether to copy on attaching
0430: */
0431: public boolean getCopyOnAttach() {
0432: assertIsOpen();
0433: return copyOnAttach;
0434: }
0435:
0436: /**
0437: * Mutator for whether to ignore the cache.
0438: * @param flag Whether to ignore the cache.
0439: */
0440: public void setIgnoreCache(boolean flag) {
0441: assertIsOpen();
0442: this .ignoreCache = flag;
0443: }
0444:
0445: /**
0446: * Accessor for whether to ignore the cache.
0447: * @return Whether to ignore the cache.
0448: */
0449: public boolean getIgnoreCache() {
0450: assertIsOpen();
0451: return ignoreCache;
0452: }
0453:
0454: /**
0455: * Whether the datastore operations are delayed until commit/flush.
0456: * In optimistic transactions this is automatically enabled. In datastore transactions there is a
0457: * persistence property to enable it.
0458: * If we are committing/flushing then will return false since the delay is no longer required.
0459: * @return true if datastore operations are delayed until commit/flush
0460: */
0461: public boolean isDelayDatastoreOperationsEnabled() {
0462: if (flushing || tx.isCommitting()) {
0463: // Already sending to the datastore so return false to not confuse things
0464: return false;
0465: }
0466:
0467: if (tx.getOptimistic()) {
0468: return true;
0469: } else {
0470: return getOMFContext().getPersistenceConfiguration()
0471: .getDatastoreTransactionsDelayOperations();
0472: }
0473: }
0474:
0475: /**
0476: * Tests whether this persistable object is in the process of being inserted.
0477: * @param pc the object to verify the status
0478: * @return true if this instance is inserting.
0479: */
0480: public boolean isInserting(Object pc) {
0481: StateManager sm = findStateManager(pc);
0482: if (sm == null) {
0483: return false;
0484: }
0485: return sm.isInserting();
0486: }
0487:
0488: /**
0489: * Convenience method to find if the specified field of the specified object has been inserted yet.
0490: * @param pc The object
0491: * @param fieldNumber The absolute field number
0492: * @return Whether it has been inserted into the datastore
0493: */
0494: public boolean isInserted(Object pc, int fieldNumber) {
0495: StateManager sm = findStateManager(pc);
0496: if (sm == null) {
0497: return false;
0498: }
0499: return sm.isInserted(fieldNumber);
0500: }
0501:
0502: /**
0503: * Convenience method to find if the specified object is inserted down to the specified class level yet.
0504: * @param pc The object
0505: * @param className Name of the class that we want to check the insertion level to
0506: * @return Whether it has been inserted into the datastore
0507: */
0508: public boolean isInserted(Object pc, String className) {
0509: StateManager sm = findStateManager(pc);
0510: if (sm == null) {
0511: return false;
0512: }
0513: return sm.isInserted(className);
0514: }
0515:
0516: /**
0517: * Accessor for the current transaction.
0518: * @return The transaction
0519: */
0520: public Transaction getTransaction() {
0521: assertIsOpen();
0522: return tx;
0523: }
0524:
0525: /**
0526: * Method to enlist the specified StateManager in the current transaction.
0527: * @param sm The StateManager
0528: */
0529: public synchronized void enlistInTransaction(StateManager sm) {
0530: assertActiveTransaction();
0531: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
0532: JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015017",
0533: StringUtils.toJVMIDString(sm.getObject()), sm
0534: .getInternalObjectId().toString()));
0535: }
0536:
0537: if (omf.getPersistenceByReachabilityAtCommit()) {
0538: if (getApiAdapter().isNew(sm.getObject())) {
0539: // Add this object to the list of new objects in this transaction
0540: txFlushedNewIds.add(sm.getInternalObjectId());
0541: } else if (getApiAdapter().isPersistent(sm.getObject())
0542: && !getApiAdapter().isDeleted(sm.getObject())) {
0543: // Add this object to the list of known valid persisted objects (unless it is a known new object)
0544: if (!txFlushedNewIds.contains(sm.getInternalObjectId())) {
0545: txKnownPersistedIds.add(sm.getInternalObjectId());
0546: }
0547: }
0548: }
0549:
0550: // Add the object to those enlisted
0551: if (omf.getPersistenceByReachabilityAtCommit()
0552: && !runningPBRAtCommit) {
0553: // Keep a note of the id for use by persistence-by-reachability-at-commit
0554: txEnlistedIds.add(sm.getInternalObjectId());
0555: }
0556: enlistedSMCache.put(sm.getInternalObjectId(), sm);
0557: }
0558:
0559: /**
0560: * Method to evict the specified StateManager from the current transaction.
0561: * @param sm The StateManager
0562: */
0563: public synchronized void evictFromTransaction(StateManager sm) {
0564: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
0565: JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015019",
0566: StringUtils.toJVMIDString(sm.getObject()), sm
0567: .getInternalObjectId().toString()));
0568: }
0569:
0570: if (enlistedSMCache.remove(sm.getInternalObjectId()) == null) {
0571: //probably because the object was garbage collected
0572: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
0573: JPOXLogger.TRANSACTION.debug(LOCALISER.msg("010023",
0574: StringUtils.toJVMIDString(sm.getObject()), sm
0575: .getInternalObjectId()));
0576: }
0577: }
0578: }
0579:
0580: /**
0581: * Method to return if an object is enlisted in the current transaction.
0582: * This is only of use when running "persistence-by-reachability-at-commit"
0583: * @param id Identity for the object
0584: * @return Whether it is enlisted in the current transaction
0585: */
0586: public boolean isEnlistedInTransaction(Object id) {
0587: if (id == null) {
0588: return false;
0589: }
0590: return txEnlistedIds.contains(id);
0591: }
0592:
0593: /**
0594: * Method to add the object managed by the specified StateManager to the cache.
0595: * @param sm The StateManager
0596: */
0597: public synchronized void addStateManager(StateManager sm) {
0598: // Add to the Level 1/2 Caches
0599: putObjectIntoCache(sm, true, true);
0600: }
0601:
0602: /**
0603: * Method to remove the object managed by the specified StateManager from the cache.
0604: * @param sm The StateManager
0605: */
0606: public synchronized void removeStateManager(StateManager sm) {
0607: Object pc = sm.getObject();
0608:
0609: // Remove from the Level 1 Cache
0610: removeObjectFromCache(pc, sm.getInternalObjectId(), true, false);
0611:
0612: // Remove it from any transaction
0613: enlistedSMCache.remove(sm.getInternalObjectId());
0614: }
0615:
0616: /**
0617: * Accessor for the StateManager of an object given the object id.
0618: * @param id Id of the object.
0619: * @return The StateManager
0620: */
0621: public synchronized StateManager getStateManagerById(Object id) {
0622: assertIsOpen();
0623: return findStateManager(getObjectFromCache(id));
0624: }
0625:
0626: /**
0627: * Method to find the StateManager for an object.
0628: * @param pc The object we require the StateManager for.
0629: * @return The StateManager, null if StateManager not found.
0630: * See JDO spec for the calling behavior when null is returned
0631: */
0632: public synchronized StateManager findStateManager(Object pc) {
0633: StateManager sm = null;
0634: Object previousLookingFor = objectLookingForStateManager;
0635: StateManager previousFound = foundStateManager;
0636: try {
0637: objectLookingForStateManager = pc;
0638: foundStateManager = null;
0639: // We call "ObjectManagerHelper.getObjectManager(pc)".
0640: // This then calls "JDOHelper.getPersistenceManager(pc)".
0641: // Which calls "StateManager.getPersistenceManager(pc)".
0642: // That then calls "hereIsStateManager(sm, pc)" which sets "foundStateManager".
0643: ObjectManager om = ObjectManagerHelper.getObjectManager(pc);
0644: if (om != null && this != om) {
0645: throw new JPOXUserException(LOCALISER.msg("010007",
0646: getApiAdapter().getIdForObject(pc)));
0647: }
0648: sm = foundStateManager;
0649: } finally {
0650: objectLookingForStateManager = previousLookingFor;
0651: foundStateManager = previousFound;
0652: }
0653: return sm;
0654: }
0655:
0656: /**
0657: * Method to add the StateManager for an object to this ObjectManager's list.
0658: * @param sm The StateManager
0659: * @param pc The object managed by the StateManager
0660: */
0661: public synchronized void hereIsStateManager(StateManager sm,
0662: Object pc) {
0663: if (objectLookingForStateManager == pc) {
0664: foundStateManager = sm;
0665: }
0666: }
0667:
0668: /**
0669: * Method to close the Persistence Manager.
0670: */
0671: public synchronized void close() {
0672: if (closed) {
0673: throw new JPOXUserException(LOCALISER.msg("010002"));
0674: }
0675: if (tx.isActive()) {
0676: throw new TransactionActiveOnCloseException(this );
0677: }
0678:
0679: if (detachOnClose) {
0680: // JPOX "detach-on-close" extension, detaching all currently cached objects.
0681: performDetachOnClose();
0682: }
0683:
0684: ObjectManagerListener[] listener = omf.getOMFContext()
0685: .getObjectManagerListeners();
0686:
0687: for (int i = 0; i < listener.length; i++) {
0688: listener[i].objectManagerPreClose(this );
0689: }
0690:
0691: // Disconnect remaining resources
0692: disconnectSMCache();
0693: disconnectLifecycleListener();
0694:
0695: // Reset the Fetch Plan to its DFG setting
0696: getFetchPlan().clearGroups().addGroup(FetchPlan.DEFAULT);
0697: }
0698:
0699: public void postClose() {
0700: closed = true;
0701: tx = null;
0702:
0703: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
0704: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("010001", this ));
0705: }
0706: }
0707:
0708: /**
0709: * Disconnect SM instances, clear cache and reset settings
0710: */
0711: public void disconnectSMCache() {
0712: // Clear out the cache (use separate list since sm.disconnect will remove the object from "cache" so we avoid
0713: // any ConcurrentModification issues)
0714: Collection cachedSMsClone = new HashSet(cache.values());
0715: Iterator iter = cachedSMsClone.iterator();
0716: while (iter.hasNext()) {
0717: StateManager sm = (StateManager) iter.next();
0718: if (sm != null) {
0719: sm.disconnect();
0720: }
0721: }
0722: cache.clear();
0723: if (JPOXLogger.CACHE.isDebugEnabled()) {
0724: JPOXLogger.CACHE.debug(LOCALISER.msg("003011"));
0725: }
0726:
0727: }
0728:
0729: // ----------------------------- Lifecycle Methods ------------------------------------
0730:
0731: /**
0732: * Internal method to evict an object from L1 cache.
0733: * @param obj The object
0734: * @throws JPOXException if an error occurs evicting the object
0735: */
0736: public void evictObject(Object obj) {
0737: if (obj == null) {
0738: return;
0739: }
0740:
0741: try {
0742: clr.setPrimary(obj.getClass().getClassLoader());
0743: assertClassPersistable(obj.getClass());
0744: assertNotDetached(obj);
0745:
0746: // we do not directly remove from cache level 1 here. The cache level 1 will be evicted
0747: // automatically on garbage collection, if the object can be evicted. it means not all
0748: // jdo states allows the object to be evicted.
0749: StateManager sm = findStateManager(obj);
0750: if (sm == null) {
0751: throw new JPOXUserException(LOCALISER.msg("010007",
0752: getApiAdapter().getIdForObject(obj)));
0753: }
0754: sm.evict();
0755: } finally {
0756: clr.unsetPrimary();
0757: }
0758: }
0759:
0760: /**
0761: * Method to evict all objects of the specified type (and optionaly its subclasses).
0762: * @param cls Type of persistable object
0763: * @param subclasses Whether to include subclasses
0764: */
0765: public void evictObjects(Class cls, boolean subclasses) {
0766: assertIsOpen();
0767:
0768: ArrayList stateManagersToEvict = new ArrayList();
0769: stateManagersToEvict.addAll(cache.values());
0770: Iterator smIter = stateManagersToEvict.iterator();
0771: while (smIter.hasNext()) {
0772: StateManager sm = (StateManager) smIter.next();
0773: Object pc = sm.getObject();
0774: boolean evict = false;
0775: if (!subclasses && pc.getClass() == cls) {
0776: evict = true;
0777: } else if (subclasses
0778: && cls.isAssignableFrom(pc.getClass())) {
0779: evict = true;
0780: }
0781:
0782: if (evict) {
0783: sm.evict();
0784: removeObjectFromCache(pc, getApiAdapter()
0785: .getIdForObject(pc), true, false);
0786: }
0787: }
0788: }
0789:
0790: /**
0791: * Method to evict all current objects from L1 cache.
0792: */
0793: public synchronized void evictAllObjects() {
0794: assertIsOpen();
0795:
0796: // All persistent non-transactional instances should be evicted here, but not yet supported
0797: ArrayList stateManagersToEvict = new ArrayList();
0798: stateManagersToEvict.addAll(cache.values());
0799:
0800: // Evict StateManagers and remove objects from cache
0801: // Performed in separate loop to avoid ConcurrentModificationException
0802: Iterator smIter = stateManagersToEvict.iterator();
0803: while (smIter.hasNext()) {
0804: StateManager sm = (StateManager) smIter.next();
0805: Object pc = sm.getObject();
0806: sm.evict();
0807:
0808: // Evict from L1
0809: removeObjectFromCache(pc, getApiAdapter()
0810: .getIdForObject(pc), true, false);
0811: }
0812: }
0813:
0814: /**
0815: * Method to do a refresh of an object, updating it from its
0816: * datastore representation. Also updates the object in the L1/L2 caches.
0817: * @param obj The Object
0818: */
0819: public void refreshObject(Object obj) {
0820: if (obj == null) {
0821: return;
0822: }
0823: try {
0824: clr.setPrimary(obj.getClass().getClassLoader());
0825: assertClassPersistable(obj.getClass());
0826: assertNotDetached(obj);
0827:
0828: StateManager sm = findStateManager(obj);
0829: if (sm == null) {
0830: throw new JPOXUserException(LOCALISER.msg("010007",
0831: getApiAdapter().getIdForObject(obj)));
0832: }
0833:
0834: if (getApiAdapter().isPersistent(obj)
0835: && sm.isWaitingToBeFlushedToDatastore()) {
0836: // Persistent but not yet flushed so nothing to "refresh" from!
0837: return;
0838: }
0839:
0840: sm.refresh();
0841:
0842: // Refresh L2 cache object since it is a copy of this object
0843: putObjectIntoCache(sm, false, true);
0844: } finally {
0845: clr.unsetPrimary();
0846: }
0847: }
0848:
0849: /**
0850: * Method to do a refresh of all objects.
0851: * @throws JPOXUserException thrown if instances could not be refreshed.
0852: */
0853: public synchronized void refreshAllObjects() {
0854: assertIsOpen();
0855:
0856: Set toRefresh = new HashSet();
0857: toRefresh.addAll(enlistedSMCache.values());
0858: toRefresh.addAll(dirtySMs);
0859: toRefresh.addAll(indirectDirtySMs);
0860: if (!tx.isActive()) {
0861: toRefresh.addAll(cache.values());
0862: }
0863:
0864: List failures = null;
0865: Iterator iter = toRefresh.iterator();
0866: while (iter.hasNext()) {
0867: try {
0868: Object obj = iter.next();
0869: StateManager sm;
0870: if (getApiAdapter().isPersistable(obj)) {
0871: sm = findStateManager(obj);
0872: } else {
0873: sm = (StateManager) obj;
0874: }
0875: sm.refresh();
0876:
0877: // Refresh L2 cache object since it is a copy of this object
0878: putObjectIntoCache(sm, false, true);
0879: } catch (RuntimeException e) {
0880: if (failures == null) {
0881: failures = new ArrayList();
0882: }
0883: failures.add(e);
0884: }
0885: }
0886: if (failures != null && !failures.isEmpty()) {
0887: throw new JPOXUserException(LOCALISER.msg("011002"),
0888: (Exception[]) failures
0889: .toArray(new Exception[failures.size()]));
0890: }
0891: }
0892:
0893: /**
0894: * Method to retrieve an object.
0895: * @param obj The object
0896: * @param fgOnly Whether to retrieve the current fetch group fields only
0897: */
0898: public void retrieveObject(Object obj, boolean fgOnly) {
0899: if (obj == null) {
0900: return;
0901: }
0902: try {
0903: clr.setPrimary(obj.getClass().getClassLoader());
0904: assertClassPersistable(obj.getClass());
0905: assertNotDetached(obj);
0906:
0907: StateManager sm = findStateManager(obj);
0908: if (sm == null) {
0909: throw new JPOXUserException(LOCALISER.msg("010007",
0910: getApiAdapter().getIdForObject(obj)));
0911: }
0912: sm.retrieve(fgOnly);
0913: } finally {
0914: clr.unsetPrimary();
0915: }
0916: }
0917:
0918: /**
0919: * Context info for a particular thread. Can be used for storing state information for the current
0920: * thread where we don't want to pass it through large numbers of method calls (e.g persistence by
0921: * reachability) where such argument passing would damage the structure of JPOX.
0922: */
0923: class ThreadContextInfo {
0924: /** Map of attached PC object keyed by the id. Present when performing a persist operation. */
0925: HashMap attachedPCById = null;
0926: }
0927:
0928: /**
0929: * Accessor for the thread context information, for the current thread.
0930: * If the current thread is not present, will add an info context for it.
0931: * @return The thread context information
0932: */
0933: protected ThreadContextInfo getThreadContextInfo() {
0934: String threadId = "" + Thread.currentThread().getId();
0935: synchronized (contextInfoByThread) {
0936: ThreadContextInfo threadInfo = (ThreadContextInfo) contextInfoByThread
0937: .get(threadId);
0938: if (threadInfo == null) {
0939: // Thread not present, so add it
0940: threadInfo = new ThreadContextInfo();
0941: contextInfoByThread.put(threadId, threadInfo);
0942: }
0943: return threadInfo;
0944: }
0945: }
0946:
0947: /**
0948: * Method to remove the current thread context info for the current thread.
0949: */
0950: protected void removeThreadContextInfo() {
0951: String threadId = "" + Thread.currentThread().getId();
0952: synchronized (contextInfoByThread) {
0953: contextInfoByThread.remove(threadId);
0954: }
0955: }
0956:
0957: /**
0958: * Method to make an object persistent.
0959: * NOT to be called by internal JPOX methods. Only callable by external APIs (JDO/JPA).
0960: * @param obj The object
0961: * @return The persisted object
0962: * @throws JPOXUserException if the object is managed by a different manager
0963: */
0964: public Object persistObject(Object obj) {
0965: assertIsOpen();
0966: assertWritable();
0967: if (obj == null) {
0968: return null;
0969: }
0970:
0971: // Make sure we have attachedPC lookup info initialised in case we need to attach objects multiple times
0972: ThreadContextInfo threadInfo = getThreadContextInfo();
0973: if (threadInfo.attachedPCById == null) {
0974: threadInfo.attachedPCById = new HashMap();
0975: }
0976:
0977: boolean detached = getApiAdapter().isDetached(obj);
0978:
0979: // Persist the object
0980: Object persistedPc = persistObjectInternal(obj, null, null, -1,
0981: StateManager.PC);
0982:
0983: // If using reachability at commit and appropriate save it for reachability checks when we commit
0984: StateManager sm = findStateManager(persistedPc);
0985: if (sm != null) {
0986: if (indirectDirtySMs.contains(sm)) {
0987: dirtySMs.add(sm);
0988: indirectDirtySMs.remove(sm);
0989: } else if (!dirtySMs.contains(sm)) {
0990: dirtySMs.add(sm);
0991: }
0992:
0993: if (omf.getPersistenceByReachabilityAtCommit()) {
0994: if (detached || getApiAdapter().isNew(persistedPc)) {
0995: txKnownPersistedIds.add(sm.getInternalObjectId());
0996: }
0997: }
0998: }
0999:
1000: // Deallocate attached PC lookup
1001: threadInfo.attachedPCById.clear();
1002: threadInfo.attachedPCById = null;
1003: removeThreadContextInfo(); // Currently remove the whole thread context since only used for this
1004:
1005: return persistedPc;
1006: }
1007:
1008: /**
1009: * Method to make an object persistent which should be called from internal calls only.
1010: * All PM/EM calls should go via persistObject(Object obj).
1011: * @param obj The object
1012: * @param preInsertChanges Any changes to make before inserting
1013: * @param ownerSM StateManager of the owner when embedded
1014: * @param ownerFieldNum Field number in the owner where this is embedded (or -1 if not embedded)
1015: * @param objectType Type of object (see org.jpox.StateManager, e.g StateManager.PC)
1016: * @return The persisted object
1017: * @throws JPOXUserException if the object is managed by a different manager
1018: */
1019: public Object persistObjectInternal(Object obj,
1020: FieldValues preInsertChanges, StateManager ownerSM,
1021: int ownerFieldNum, int objectType) {
1022: if (obj == null) {
1023: return null;
1024: }
1025: assertIsOpen();
1026: assertWritable();
1027: // TODO Support embeddedOwner/objectType, so we can add StateManager for embedded objects here
1028:
1029: try {
1030: clr.setPrimary(obj.getClass().getClassLoader());
1031: assertClassPersistable(obj.getClass());
1032: if (!getApiAdapter().isDetached(obj)
1033: && JPOXLogger.PERSISTENCE.isDebugEnabled()) {
1034: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("010015",
1035: StringUtils.toJVMIDString(obj)));
1036: }
1037: ObjectManager om = ObjectManagerHelper
1038: .getObjectManager(obj);
1039: if (om != null && om != this ) {
1040: // Object managed by a different manager
1041: throw new JPOXUserException(LOCALISER
1042: .msg("010007", obj));
1043: }
1044:
1045: Object persistedPc = obj; // Persisted object is the passed in pc (unless being attached as a copy)
1046: if (getApiAdapter().isDetached(obj)) {
1047: // Detached : attach it
1048: assertDetachable(obj);
1049: if (copyOnAttach) {
1050: // Attach a copy and return the copy
1051: persistedPc = attachObjectCopy(obj, getApiAdapter()
1052: .getIdForObject(obj) == null);
1053: } else {
1054: // Attach the object
1055: attachObject(obj, getApiAdapter().getIdForObject(
1056: obj) == null);
1057: persistedPc = obj;
1058: }
1059: } else if (getApiAdapter().isTransactional(obj)
1060: && !getApiAdapter().isPersistent(obj)) {
1061: // TransientTransactional : persist it
1062: StateManager sm = findStateManager(obj);
1063: if (sm == null) {
1064: throw new JPOXUserException(LOCALISER.msg("010007",
1065: getApiAdapter().getIdForObject(obj)));
1066: }
1067: sm.makePersistentTransactionalTransient();
1068: } else if (!getApiAdapter().isPersistent(obj)) {
1069: // Transient : persist it
1070: if (this .multithreaded) {
1071: synchronized (obj) {
1072: StateManager sm = findStateManager(obj);
1073: if (sm == null) {
1074: if (objectType != StateManager.PC
1075: && ownerSM != null) {
1076: // SCO object
1077: sm = StateManagerFactory
1078: .newStateManagerForEmbedded(
1079: this , obj, false);
1080: sm.addEmbeddedOwner(ownerSM,
1081: ownerFieldNum);
1082: sm.setPcObjectType(objectType);
1083: sm.makePersistent();
1084: } else {
1085: // FCO object
1086: sm = StateManagerFactory
1087: .newStateManagerForPersistentNew(
1088: this , obj,
1089: preInsertChanges);
1090: sm.makePersistent();
1091: }
1092: } else {
1093: if (sm.getReferencedPC() == null) {
1094: // Persist it
1095: sm.makePersistent();
1096: } else {
1097: // Being attached, so use the attached object
1098: persistedPc = sm.getReferencedPC();
1099: }
1100: }
1101: }
1102: } else {
1103: StateManager sm = findStateManager(obj);
1104: if (sm == null) {
1105: if (objectType != StateManager.PC
1106: && ownerSM != null) {
1107: // SCO Object
1108: sm = StateManagerFactory
1109: .newStateManagerForEmbedded(this ,
1110: obj, false);
1111: sm.addEmbeddedOwner(ownerSM, ownerFieldNum);
1112: sm.setPcObjectType(objectType);
1113: sm.makePersistent();
1114: } else {
1115: // FCO object
1116: sm = StateManagerFactory
1117: .newStateManagerForPersistentNew(
1118: this , obj, preInsertChanges);
1119: sm.makePersistent();
1120: }
1121: } else {
1122: if (sm.getReferencedPC() == null) {
1123: // Persist it
1124: sm.makePersistent();
1125: } else {
1126: // Being attached, so use the attached object
1127: persistedPc = sm.getReferencedPC();
1128: }
1129: }
1130: }
1131: } else if (getApiAdapter().isPersistent(obj)
1132: && getApiAdapter().getIdForObject(obj) == null) {
1133: // Embedded/Serialised : have SM but no identity, allow persist in own right
1134: // Should we be making a copy of the object here ?
1135: if (this .multithreaded) {
1136: synchronized (obj) {
1137: StateManager sm = findStateManager(obj);
1138: sm.makePersistent();
1139: }
1140: } else {
1141: StateManager sm = findStateManager(obj);
1142: sm.makePersistent();
1143: }
1144: } else if (getApiAdapter().isDeleted(obj)) {
1145: // Deleted : (re)-persist it (permitted in JPA, but not JDO - see StateManager)
1146: StateManager sm = findStateManager(obj);
1147: sm.makePersistent();
1148: } else {
1149: if (getApiAdapter().isPersistent(obj)
1150: && getApiAdapter().isTransactional(obj)
1151: && getApiAdapter().isDirty(obj)
1152: && isDelayDatastoreOperationsEnabled()) {
1153: // Object provisionally persistent (but not in datastore) so re-run reachability maybe
1154: StateManager sm = findStateManager(obj);
1155: sm.makePersistent();
1156: }
1157: }
1158:
1159: return persistedPc;
1160: } finally {
1161: clr.unsetPrimary();
1162: }
1163: }
1164:
1165: /**
1166: * Method to delete an object from the datastore.
1167: * NOT to be called by internal JPOX methods. Only callable by external APIs (JDO/JPA).
1168: * @param obj The object
1169: */
1170: public void deleteObject(Object obj) {
1171: StateManager sm = findStateManager(obj);
1172: if (sm != null) {
1173: // Add the object to the relevant list of dirty StateManagers
1174: if (indirectDirtySMs.contains(sm)) {
1175: // Object is dirty indirectly, but now user-requested so move to direct list of dirty objects
1176: indirectDirtySMs.remove(sm);
1177: dirtySMs.add(sm);
1178: } else if (!dirtySMs.contains(sm)) {
1179: dirtySMs.add(sm);
1180: }
1181: }
1182:
1183: // Delete the object
1184: deleteObjectInternal(obj);
1185:
1186: if (omf.getPersistenceByReachabilityAtCommit()) {
1187: if (sm != null) {
1188: if (getApiAdapter().isDeleted(obj)) {
1189: txKnownDeletedIds.add(sm.getInternalObjectId());
1190: }
1191: }
1192: }
1193: }
1194:
1195: /**
1196: * Method to delete an object from persistence which should be called from internal calls only.
1197: * All PM/EM calls should go via deleteObject(Object obj).
1198: * @param obj Object to delete
1199: */
1200: public void deleteObjectInternal(Object obj) {
1201: if (obj == null) {
1202: return;
1203: }
1204: assertIsOpen();
1205: assertWritable();
1206:
1207: try {
1208: clr.setPrimary(obj.getClass().getClassLoader());
1209: assertClassPersistable(obj.getClass());
1210:
1211: Object pc = obj;
1212: if (getApiAdapter().isDetached(obj)) {
1213: // Load up the attached instance with this identity
1214: pc = findObject(getApiAdapter().getIdForObject(obj),
1215: true, true, null);
1216: }
1217:
1218: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
1219: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("010019",
1220: StringUtils.toJVMIDString(pc)));
1221: }
1222:
1223: // Check that the object is valid for deleting
1224: if (getApiAdapter() instanceof JDOAdapter) // TODO Refactor this
1225: {
1226: // JDO doesnt allow deletion of transient
1227: if (!getApiAdapter().isPersistent(pc)
1228: && !getApiAdapter().isTransactional(pc)) {
1229: throw new JPOXUserException(LOCALISER.msg("010020"));
1230: } else if (!getApiAdapter().isPersistent(pc)
1231: && getApiAdapter().isTransactional(pc)) {
1232: throw new JPOXUserException(LOCALISER.msg("010021"));
1233: }
1234: }
1235:
1236: // Delete it
1237: StateManager sm = findStateManager(pc);
1238: if (sm == null) {
1239: if (!getApiAdapter().allowDeleteOfNonPersistentObject()) {
1240: // Not permitted by the API
1241: throw new JPOXUserException(LOCALISER.msg("010007",
1242: getApiAdapter().getIdForObject(pc)));
1243: }
1244:
1245: // Put StateManager around object so it is P_NEW (unpersisted), then P_NEW_DELETED soon after
1246: sm = StateManagerFactory
1247: .newStateManagerForPNewToBeDeleted(this , pc);
1248: }
1249:
1250: // Move to deleted state
1251: sm.deletePersistent();
1252: } finally {
1253: clr.unsetPrimary();
1254: }
1255: }
1256:
1257: /**
1258: * Method to delete an array of objects from the datastore.
1259: * @param objs The objects
1260: * @throws JPOXUserException Thrown if an error occurs during the deletion process.
1261: * Any exception could have several nested exceptions for each failed object deletion
1262: */
1263: public void deleteObjects(Object[] objs) {
1264: if (objs == null) {
1265: return;
1266: }
1267:
1268: ArrayList failures = null;
1269: for (int i = 0; i < objs.length; i++) {
1270: try {
1271: deleteObject(objs[i]);
1272: } catch (RuntimeException e) {
1273: if (failures == null) {
1274: failures = new ArrayList();
1275: }
1276: failures.add(e);
1277: }
1278: }
1279: if (failures != null && !failures.isEmpty()) {
1280: throw new JPOXUserException(LOCALISER.msg("011005"),
1281: (Exception[]) failures
1282: .toArray(new Exception[failures.size()]));
1283: }
1284: }
1285:
1286: /**
1287: * Method to delete an array of objects from the datastore.
1288: * @param objs The objects
1289: * @throws JPOXUserException Thrown if an error occurs during the deletion process.
1290: * Any exception could have several nested exceptions for each failed object deletion
1291: */
1292: public void deleteObjects(Collection objs) {
1293: if (objs == null) {
1294: return;
1295: }
1296:
1297: ArrayList failures = null;
1298: Iterator iter = objs.iterator();
1299: while (iter.hasNext()) {
1300: Object obj = iter.next();
1301: try {
1302: deleteObject(obj);
1303: } catch (RuntimeException e) {
1304: if (failures == null) {
1305: failures = new ArrayList();
1306: }
1307: failures.add(e);
1308: }
1309: }
1310: if (failures != null && !failures.isEmpty()) {
1311: throw new JPOXUserException(LOCALISER.msg("011005"),
1312: (Exception[]) failures
1313: .toArray(new Exception[failures.size()]));
1314: }
1315: }
1316:
1317: /**
1318: * Method to migrate an object to transient state.
1319: * @param obj The object
1320: * @param state Object containing the state of the fetch plan process (if any)
1321: * @throws JPOXException When an error occurs in making the object transient
1322: */
1323: public synchronized void makeObjectTransient(Object obj,
1324: FetchPlanState state) {
1325: if (obj == null) {
1326: return;
1327: }
1328:
1329: try {
1330: clr.setPrimary(obj.getClass().getClassLoader());
1331: assertClassPersistable(obj.getClass());
1332: assertNotDetached(obj);
1333:
1334: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
1335: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("010022",
1336: StringUtils.toJVMIDString(obj)));
1337: }
1338:
1339: if (getApiAdapter().isPersistent(obj)) {
1340: StateManager sm = findStateManager(obj);
1341: sm.makeTransient(state);
1342: }
1343: } finally {
1344: clr.unsetPrimary();
1345: }
1346: }
1347:
1348: /**
1349: * Method to make an object transactional.
1350: * @param obj The object
1351: * @throws JPOXException Thrown when an error occurs
1352: */
1353: public void makeObjectTransactional(Object obj) {
1354: if (obj == null) {
1355: return;
1356: }
1357: try {
1358: clr.setPrimary(obj.getClass().getClassLoader());
1359: assertClassPersistable(obj.getClass());
1360: assertNotDetached(obj);
1361:
1362: if (getApiAdapter().isPersistent(obj)) {
1363: assertActiveTransaction();
1364: }
1365: StateManager sm = findStateManager(obj);
1366: if (sm == null) {
1367: sm = StateManagerFactory
1368: .newStateManagerForTransactionalTransient(this ,
1369: obj);
1370: }
1371: sm.makeTransactional();
1372: } finally {
1373: clr.unsetPrimary();
1374: }
1375: }
1376:
1377: /**
1378: * Method to make an object nontransactional.
1379: * @param obj The object
1380: */
1381: public void makeObjectNontransactional(Object obj) {
1382: if (obj == null) {
1383: return;
1384: }
1385: try {
1386: clr.setPrimary(obj.getClass().getClassLoader());
1387: assertClassPersistable(obj.getClass());
1388: if (!getApiAdapter().isPersistent(obj)
1389: && getApiAdapter().isTransactional(obj)
1390: && getApiAdapter().isDirty(obj)) {
1391: throw new JPOXUserException(LOCALISER.msg("010024"));
1392: }
1393:
1394: StateManager sm = findStateManager(obj);
1395: sm.makeNontransactional();
1396: } finally {
1397: clr.unsetPrimary();
1398: }
1399: }
1400:
1401: /**
1402: * Method to attach a persistent detached object.
1403: * If a different object with the same identity as this object exists in the L1 cache then an exception
1404: * will be thrown.
1405: * @param pc The persistable object
1406: * @param sco Whether the PC object is stored without an identity (embedded/serialised)
1407: */
1408: public synchronized void attachObject(Object pc, boolean sco) {
1409: assertIsOpen();
1410: assertClassPersistable(pc.getClass());
1411:
1412: ApiAdapter api = getApiAdapter();
1413: Object id = api.getIdForObject(pc);
1414: if (id != null && isInserting(pc)) {
1415: // Object is being inserted in this transaction so just return
1416: return;
1417: } else if (id == null && !sco) {
1418: // Transient object so needs persisting
1419: persistObjectInternal(pc, null, null, -1, StateManager.PC);
1420: return;
1421: }
1422:
1423: if (api.isDetached(pc)) {
1424: // Detached, so migrate to attached
1425: StateManager l1CachedSM = (StateManager) cache.get(id);
1426: if (l1CachedSM != null && l1CachedSM.getObject() != pc) {
1427: // attached object with the same id already present in the L1 cache so cannot attach in-situ
1428: throw new JPOXUserException(LOCALISER.msg("010017",
1429: StringUtils.toJVMIDString(pc)));
1430: }
1431:
1432: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
1433: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("010016",
1434: StringUtils.toJVMIDString(pc)));
1435: }
1436: StateManager sm = StateManagerFactory
1437: .newStateManagerForDetached(this , pc, id, api
1438: .getVersionForObject(pc));
1439: sm.attach(sco);
1440: } else {
1441: // Not detached so can't attach it. Just return
1442: return;
1443: }
1444: }
1445:
1446: /**
1447: * Method to attach a persistent detached object returning an attached copy of the object.
1448: * If the object is of class that is not detachable, a ClassNotDetachableException will be thrown.
1449: * @param pc The object
1450: * @param sco Whether it has no identity (second-class object)
1451: * @return The attached object
1452: */
1453: public synchronized Object attachObjectCopy(Object pc, boolean sco) {
1454: assertIsOpen();
1455: assertClassPersistable(pc.getClass());
1456: assertDetachable(pc);
1457:
1458: ApiAdapter api = getApiAdapter();
1459: Object id = api.getIdForObject(pc);
1460: if (id != null && isInserting(pc)) {
1461: // Object is being inserted in this transaction
1462: return pc;
1463: } else if (id == null && !sco) {
1464: // Object was not persisted before so persist it
1465: return persistObjectInternal(pc, null, null, -1,
1466: StateManager.PC);
1467: } else if (api.isPersistent(pc)) {
1468: // Already persistent hence can't be attached
1469: return pc;
1470: }
1471:
1472: // Object should exist in this datastore now
1473: Object pcTarget = null;
1474: if (sco) {
1475: // SCO PC (embedded/serialised)
1476: boolean detached = getApiAdapter().isDetached(pc);
1477: StateManager smTarget = StateManagerFactory
1478: .newStateManagerForEmbedded(this , pc, true);
1479: pcTarget = smTarget.getObject();
1480: if (detached) {
1481: // If the object is detached, re-attach it
1482: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
1483: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg(
1484: "010018", StringUtils.toJVMIDString(pc),
1485: StringUtils.toJVMIDString(pcTarget)));
1486: }
1487: smTarget.attachCopy(pc, sco);
1488: }
1489: } else {
1490: // FCO PC
1491: boolean detached = getApiAdapter().isDetached(pc);
1492: pcTarget = findObject(id, false, false, pc.getClass()
1493: .getName());
1494: if (detached) {
1495: Object obj = null;
1496: HashMap attachedPCById = getThreadContextInfo().attachedPCById; // For the current thread
1497: if (attachedPCById != null) // Only used by persistObject process
1498: {
1499: obj = attachedPCById.get(getApiAdapter()
1500: .getIdForObject(pc));
1501: }
1502: if (obj != null) {
1503: pcTarget = obj;
1504: } else {
1505: // If the object is detached, re-attach it
1506: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
1507: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg(
1508: "010018",
1509: StringUtils.toJVMIDString(pc),
1510: StringUtils.toJVMIDString(pcTarget)));
1511: }
1512: findStateManager(pcTarget).attachCopy(pc, sco);
1513:
1514: // Save the detached-attached PCs for later reference
1515: if (attachedPCById != null) // Only used by persistObject process
1516: {
1517: attachedPCById.put(getApiAdapter()
1518: .getIdForObject(pc), pcTarget);
1519: }
1520: }
1521: }
1522: }
1523:
1524: return pcTarget;
1525: }
1526:
1527: /**
1528: * Method to detach a persistent object without making a copy. Note that
1529: * also all the objects which are refered to from this object are detached.
1530: * If the object is of class that is not detachable a ClassNotDetachableException
1531: * will be thrown. If the object is not persistent a JDOUserException is thrown.
1532: * <B>For internal use only</B>
1533: * @param obj The object
1534: * @param state State for the detachment process
1535: */
1536: public synchronized void detachObject(Object obj,
1537: FetchPlanState state) {
1538: assertIsOpen();
1539: assertClassPersistable(obj.getClass());
1540: assertDetachable(obj); // Is this required?
1541:
1542: if (getApiAdapter().isDetached(obj)) {
1543: return;
1544: }
1545:
1546: if (!getApiAdapter().isPersistent(obj)) {
1547: // Transient object passed so persist it before thinking about detaching
1548: if (tx.isActive()) {
1549: persistObjectInternal(obj, null, null, -1,
1550: StateManager.PC);
1551: }
1552: }
1553:
1554: StateManager sm = findStateManager(obj);
1555: if (sm == null) {
1556: throw new JPOXUserException(LOCALISER.msg("010007",
1557: getApiAdapter().getIdForObject(obj)));
1558: }
1559: sm.detach(state);
1560: }
1561:
1562: /**
1563: * Detach a copy of the passed persistent object using the provided detach state.
1564: * If the object is of class that is not detachable it will be detached as transient.
1565: * If it is not yet persistent it will be first persisted.
1566: * @param pc The object
1567: * @param state State for the detachment process
1568: * @return The detached object
1569: */
1570: public Object detachObjectCopy(Object pc, FetchPlanState state) {
1571: assertIsOpen();
1572: assertClassPersistable(pc.getClass());
1573:
1574: Object thePC = pc;
1575: try {
1576: clr.setPrimary(pc.getClass().getClassLoader());
1577: if (!getApiAdapter().isPersistent(pc)
1578: && !getApiAdapter().isDetached(pc)) {
1579: // Transient object passed so persist it before thinking about detaching
1580: if (tx.isActive()) {
1581: thePC = persistObjectInternal(pc, null, null, -1,
1582: StateManager.PC);
1583: } else {
1584: // JDO2 [12.6.8] "If a detachCopy method is called outside an active transaction, the reachability algorithm
1585: // will not be run; if any transient instances are reachable via persistent fields, a JDOUserException is thrown
1586: // for each persistent instance containing such fields.
1587: throw new JPOXUserException(LOCALISER.msg("010014"));
1588: }
1589: }
1590:
1591: if (getApiAdapter().isDetached(thePC)) {
1592: // Passing in a detached (dirty or clean) instance, so get a persistent copy to detach
1593: thePC = findObject(getApiAdapter()
1594: .getIdForObject(thePC), false, true, null);
1595: }
1596:
1597: Object detached = ((DetachState) state)
1598: .getDetachedCopyObject(thePC);
1599: if (detached == null) {
1600: StateManager sm = findStateManager(thePC);
1601: if (sm == null) {
1602: throw new JPOXUserException(LOCALISER.msg("010007",
1603: getApiAdapter().getIdForObject(thePC)));
1604: }
1605:
1606: detached = sm.detachCopy(state);
1607: ((DetachState) state).setDetachedCopyObject(detached,
1608: sm.getExternalObjectId(sm.getObject()));
1609: } else {
1610: // What if the fetch-group here implies a different depth through this object ? we need to continue detaching
1611: // TODO Check the detach and proceed further where necessary
1612: }
1613: return detached;
1614: } finally {
1615: clr.unsetPrimary();
1616: }
1617: }
1618:
1619: /**
1620: * Method to detach all objects in the ObjectManager.
1621: */
1622: public void detachAll() {
1623: Object[] sms = this .enlistedSMCache.values().toArray();
1624: FetchPlanState fps = new FetchPlanState();
1625: for (int i = 0; i < sms.length; i++) {
1626: ((StateManager) sms[i]).detach(fps);
1627: }
1628: }
1629:
1630: // ----------------------------- New Instances ----------------------------------
1631:
1632: /**
1633: * Method to generate an instance of an interface, abstract class, or concrete PC class.
1634: * @param cls The class of the interface or abstract class, or concrete class defined in MetaData
1635: * @return The instance of this type
1636: */
1637: public Object newInstance(Class cls) {
1638: assertIsOpen();
1639:
1640: if (getApiAdapter().isPersistable(cls)
1641: && !Modifier.isAbstract(cls.getModifiers())) {
1642: // Concrete PC class so instantiate here
1643: try {
1644: return cls.newInstance();
1645: } catch (IllegalAccessException iae) {
1646: throw new JPOXUserException(iae.toString(), iae);
1647: } catch (InstantiationException ie) {
1648: throw new JPOXUserException(ie.toString(), ie);
1649: }
1650: }
1651:
1652: // Use ImplementationCreator
1653: assertHasImplementationCreator();
1654: return getOMFContext().getImplementationCreator().newInstance(
1655: cls, getMetaDataManager(), clr);
1656: }
1657:
1658: // ----------------------------- Object Retrieval by Id ----------------------------------
1659:
1660: /**
1661: * Method to return if the specified object exists in the datastore.
1662: * @param obj The (persistable) object
1663: * @return Whether it exists
1664: */
1665: public boolean exists(Object obj) {
1666: if (obj == null) {
1667: return false;
1668: }
1669:
1670: Object id = getApiAdapter().getIdForObject(obj);
1671: if (id == null) {
1672: return false;
1673: }
1674:
1675: try {
1676: findObject(id, true, false, obj.getClass().getName());
1677: } catch (JPOXObjectNotFoundException onfe) {
1678: return false;
1679: }
1680:
1681: return true;
1682: }
1683:
1684: /**
1685: * Accessor for the currently managed objects for the current transaction.
1686: * If the transaction is not active this returns null.
1687: * @return Collection of managed objects enlisted in the current transaction
1688: */
1689: public Set getManagedObjects() {
1690: if (!tx.isActive()) {
1691: return null;
1692: }
1693:
1694: Set objs = new HashSet();
1695: Collection sms = enlistedSMCache.values();
1696: Iterator smsIter = sms.iterator();
1697: while (smsIter.hasNext()) {
1698: StateManager sm = (StateManager) smsIter.next();
1699: objs.add(sm.getObject());
1700: }
1701: return objs;
1702: }
1703:
1704: /**
1705: * Accessor for the currently managed objects for the current transaction.
1706: * If the transaction is not active this returns null.
1707: * @param classes Classes that we want the enlisted objects for
1708: * @return Collection of managed objects enlisted in the current transaction
1709: */
1710: public Set getManagedObjects(Class[] classes) {
1711: if (!tx.isActive()) {
1712: return null;
1713: }
1714:
1715: Set objs = new HashSet();
1716: Collection sms = enlistedSMCache.values();
1717: Iterator smsIter = sms.iterator();
1718: while (smsIter.hasNext()) {
1719: StateManager sm = (StateManager) smsIter.next();
1720: for (int i = 0; i < classes.length; i++) {
1721: if (classes[i] == sm.getObject().getClass()) {
1722: objs.add(sm.getObject());
1723: break;
1724: }
1725: }
1726: }
1727: return objs;
1728: }
1729:
1730: /**
1731: * Accessor for the currently managed objects for the current transaction.
1732: * If the transaction is not active this returns null.
1733: * @param states States that we want the enlisted objects for
1734: * @return Collection of managed objects enlisted in the current transaction
1735: */
1736: public Set getManagedObjects(String[] states) {
1737: if (!tx.isActive()) {
1738: return null;
1739: }
1740:
1741: Set objs = new HashSet();
1742: Collection sms = enlistedSMCache.values();
1743: Iterator smsIter = sms.iterator();
1744: while (smsIter.hasNext()) {
1745: StateManager sm = (StateManager) smsIter.next();
1746: for (int i = 0; i < states.length; i++) {
1747: if (getApiAdapter().getObjectState(sm.getObject())
1748: .equals(states[i])) {
1749: objs.add(sm.getObject());
1750: break;
1751: }
1752: }
1753: }
1754: return objs;
1755: }
1756:
1757: /**
1758: * Accessor for the currently managed objects for the current transaction.
1759: * If the transaction is not active this returns null.
1760: * @param states States that we want the enlisted objects for
1761: * @param classes Classes that we want the enlisted objects for
1762: * @return Collection of managed objects enlisted in the current transaction
1763: */
1764: public Set getManagedObjects(String[] states, Class[] classes) {
1765: if (!tx.isActive()) {
1766: return null;
1767: }
1768:
1769: Set objs = new HashSet();
1770: Collection sms = enlistedSMCache.values();
1771: Iterator smsIter = sms.iterator();
1772: while (smsIter.hasNext()) {
1773: boolean matches = false;
1774: StateManager sm = (StateManager) smsIter.next();
1775: for (int i = 0; i < states.length; i++) {
1776: if (getApiAdapter().getObjectState(sm.getObject())
1777: .equals(states[i])) {
1778: for (int j = 0; i < classes.length; i++) {
1779: if (classes[j] == sm.getObject().getClass()) {
1780: matches = true;
1781: objs.add(sm.getObject());
1782: break;
1783: }
1784: }
1785: }
1786: if (matches) {
1787: break;
1788: }
1789: }
1790: }
1791: return objs;
1792: }
1793:
1794: /**
1795: * Accessor for the StateManager of an object given the object AID.
1796: * @param pcClass The class of the PC object
1797: * @param fv The field values to be loaded
1798: * @param ignoreCache true if it must ignore the cache
1799: * @param checkInheritance Whether look to the database to determine which
1800: * class this object is. This parameter is a hint. Set false, if it's
1801: * already determined the correct pcClass for this pc "object" in a certain
1802: * level in the hierarchy. Set to true and it will look to the database.
1803: * @return Object
1804: */
1805: public synchronized Object findObjectUsingAID(Class pcClass,
1806: FieldValues fv, boolean ignoreCache,
1807: boolean checkInheritance) {
1808: assertIsOpen();
1809:
1810: // Create StateManager to generate an identity
1811: // TODO Provide a more efficient way of doing this. It creates a PC object just to get the id!
1812: StateManager sm = StateManagerFactory
1813: .newStateManagerForHollowPopulatedAppId(this , pcClass,
1814: fv);
1815: if (!ignoreCache) {
1816: // Check the cache
1817: Object oid = sm.getInternalObjectId();
1818: Object pc = getObjectFromCache(oid);
1819: if (pc != null) {
1820: sm = findStateManager(pc);
1821: sm.loadFieldValues(fv); // Load the values retrieved by the query
1822: return pc;
1823: }
1824: if (checkInheritance) {
1825: ApiAdapter api = getApiAdapter();
1826: if (oid instanceof OID
1827: || api.isSingleFieldIdentity(oid)) {
1828: // Check if this id for any known subclasses is in the cache to save searching
1829: String[] subclasses = getMetaDataManager()
1830: .getSubclassesForClass(pcClass.getName(),
1831: true);
1832: if (subclasses != null) {
1833: for (int i = 0; i < subclasses.length; i++) {
1834: if (oid instanceof OID) {
1835: oid = OIDFactory.getInstance(this ,
1836: subclasses[i], ((OID) oid)
1837: .getKeyValue());
1838: } else if (api.isSingleFieldIdentity(oid)) {
1839: oid = api
1840: .getNewSingleFieldIdentity(
1841: oid.getClass(),
1842: clr
1843: .classForName(subclasses[i]),
1844: api
1845: .getTargetKeyForSingleFieldIdentity(oid));
1846: }
1847: pc = getObjectFromCache(oid);
1848: if (pc != null) {
1849: sm = findStateManager(pc);
1850: sm.loadFieldValues(fv); // Load the values retrieved by the query
1851: return pc;
1852: }
1853: }
1854: }
1855: }
1856: }
1857: }
1858:
1859: if (checkInheritance) {
1860: sm.checkInheritance(fv); // Find the correct PC class for this object, hence updating the object id
1861: if (!ignoreCache) {
1862: // Check the cache in case this updated object id is present (since we should use that if available)
1863: Object oid = sm.getInternalObjectId();
1864: Object pc = getObjectFromCache(oid);
1865: if (pc != null) {
1866: // We have an object with this new object id already so return it with the retrieved field values imposed
1867: sm = findStateManager(pc);
1868: sm.loadFieldValues(fv); // Load the values retrieved by the query
1869: return pc;
1870: }
1871: }
1872: }
1873: putObjectIntoCache(sm, true, true);
1874:
1875: return sm.getObject();
1876: }
1877:
1878: /**
1879: * Accessor for an object given the object id.
1880: * @param id Id of the object.
1881: * @param fv Field values for the object
1882: * @param cls the type which the object is. This type will be used to instanciat the object
1883: * @param ignoreCache true if it must ignore the cache
1884: * @return The Object
1885: */
1886: public synchronized Object findObject(Object id, FieldValues fv,
1887: Class cls, boolean ignoreCache) {
1888: assertIsOpen();
1889:
1890: Object pc = null;
1891: if (!ignoreCache) {
1892: pc = getObjectFromCache(id);
1893: }
1894:
1895: if (pc == null) {
1896: pc = getStoreManager().findObject(this , id);
1897: }
1898:
1899: if (pc == null) {
1900: // Object not found so load the requested fields into a new object
1901: StateManager sm = StateManagerFactory
1902: .newStateManagerForHollowPopulated(this , cls, id,
1903: fv);
1904: // TODO verify it
1905:
1906: pc = sm.getObject();
1907:
1908: // Save the object
1909: putObjectIntoCache(sm, true, true);
1910: } else {
1911: // Object found in the cache so load the requested fields
1912: StateManager sm = findStateManager(pc);
1913: if (sm != null) {
1914: // Load the requested fields
1915: fv.fetchNonLoadedFields(sm);
1916: }
1917: }
1918:
1919: return pc;
1920: }
1921:
1922: /**
1923: * Accessor for an object given the object id. Checks the inheritance level of the object
1924: * @param id Id of the object.
1925: * @param fv Field values for the object
1926: * @return The Object
1927: */
1928: public synchronized Object findObject(Object id, FieldValues fv) {
1929: assertIsOpen();
1930:
1931: Object pc = getObjectFromCache(id);
1932: if (pc == null) {
1933: // Find it direct from the store if the store supports that
1934: // NOTE : This ignores the provided FieldValues!
1935: pc = getStoreManager().findObject(this , id);
1936:
1937: if (pc == null) {
1938: // Find the class name for this object id so we can attempt to generate it
1939: // className is null when id class exists, and object has been validated and doesn't exist
1940: String className = getStoreManager()
1941: .getClassNameForObjectID(id, clr, this );
1942: if (className == null) {
1943: throw new JPOXObjectNotFoundException(LOCALISER
1944: .msg("010026"), id);
1945: }
1946:
1947: if (id instanceof OID) {
1948: id = OIDFactory.getInstance(this , className,
1949: ((OID) id).getKeyValue());
1950:
1951: // try again to read object from cache
1952: pc = getObjectFromCache(id);
1953: }
1954: if (pc == null) {
1955: // Still not found so create a Hollow instance with the supplied field values
1956: try {
1957: Class pcClass = clr.classForName(className, id
1958: .getClass().getClassLoader());
1959: StateManager sm = StateManagerFactory
1960: .newStateManagerForHollowPopulated(
1961: this , pcClass, id, fv);
1962: pc = sm.getObject();
1963: putObjectIntoCache(sm, true, true);
1964: } catch (ClassNotResolvedException e) {
1965: JPOXLogger.PERSISTENCE.warn(LOCALISER.msg(
1966: "010027", id));
1967: throw new JPOXUserException(LOCALISER.msg(
1968: "010027", id), e);
1969: }
1970: }
1971: }
1972: }
1973: return pc;
1974: }
1975:
1976: /**
1977: * Accessor for an object given the object id. If validate is false, we return the object
1978: * if found in the cache, or otherwise a Hollow object with that id. If validate is true
1979: * we check with the datastore and return an object with the FetchPlan fields loaded (unless
1980: * the object doesnt exist in the datastore, where we throw a JPOXObjectNotFoundException).
1981: * @param id Id of the object.
1982: * @param validate Whether to validate the object state
1983: * @param checkInheritance Whether look to the database to determine which
1984: * class this object is.
1985: * @param objectClassName Class name for the object with this id (if known, optional)
1986: * @return The Object with this id
1987: */
1988: public synchronized Object findObject(Object id, boolean validate,
1989: boolean checkInheritance, String objectClassName) {
1990: assertIsOpen();
1991: if (id == null) {
1992: throw new JPOXUserException(LOCALISER.msg("011010"));
1993: }
1994:
1995: // try to find object in cache(s)
1996: Object pc = getObjectFromCache(id);
1997: boolean fromCache = true;
1998:
1999: ApiAdapter api = getApiAdapter();
2000: if (id instanceof SCOID && pc != null) {
2001: if (api.isPersistent(pc) && !api.isNew(pc)
2002: && !api.isDeleted(pc) && !api.isTransactional(pc)) {
2003: // JDO2 [5.4.4] Cant return HOLLOW nondurable objects
2004: throw new JPOXUserException(LOCALISER.msg("010005"));
2005: }
2006: }
2007:
2008: if (pc != null && api.isTransactional(pc)) {
2009: // JDO2 [12.6.5] If there's already an object with the same id and it's transactional, return it
2010: return pc;
2011: }
2012:
2013: StateManager sm = null;
2014: if (pc == null) {
2015: // Find it direct from the store if the store supports that
2016: pc = getStoreManager().findObject(this , id);
2017:
2018: if (pc == null) {
2019: // Object not found in cache(s) with this identity
2020: String className = null;
2021: String originalClassName = null;
2022: boolean checkedClassName = false;
2023: if (id instanceof SCOID) {
2024: throw new JPOXUserException(LOCALISER.msg("010006"));
2025: } else if (id instanceof OID) {
2026: // OID, so check that the implied class is managed
2027: originalClassName = getStoreManager()
2028: .manageClassForIdentity(id,
2029: getClassLoaderResolver());
2030: } else if (api.isSingleFieldIdentity(id)) {
2031: // SingleFieldIdentity, so check that the implied class is managed
2032: originalClassName = getStoreManager()
2033: .manageClassForIdentity(id,
2034: getClassLoaderResolver());
2035: } else if (objectClassName != null) {
2036: // Object class name specified so use that directly
2037: originalClassName = objectClassName;
2038: } else {
2039: // We dont know the object class so try to deduce it from what is known by the StoreManager
2040: originalClassName = getStoreManager()
2041: .getClassNameForObjectID(id, clr, this );
2042: checkedClassName = true;
2043: }
2044:
2045: if (checkInheritance) {
2046: // Verify if correct class inheritance level is set
2047: if (!checkedClassName) {
2048: className = getStoreManager()
2049: .getClassNameForObjectID(id, clr, this );
2050: } else {
2051: // We just checked the name of the class in the section above so just use that
2052: className = originalClassName;
2053: }
2054:
2055: if (className == null) {
2056: throw new JPOXObjectNotFoundException(LOCALISER
2057: .msg("010026"), id);
2058: }
2059:
2060: if (originalClassName != null
2061: && !originalClassName.equals(className)) {
2062: // Inheritance checking has found a different inherited
2063: // object with this identity so create new id
2064: if (id instanceof OID) {
2065: // Create new OID using correct target class
2066: id = OIDFactory
2067: .getInstance(this , className,
2068: ((OID) id).getKeyValue());
2069:
2070: // try again to read object from cache with this id
2071: pc = getObjectFromCache(id);
2072: } else if (api.isSingleFieldIdentity(id)) {
2073: // Create new SingleFieldIdentity using correct targetClass
2074: id = api
2075: .getNewSingleFieldIdentity(
2076: id.getClass(),
2077: getClassLoaderResolver()
2078: .classForName(
2079: className),
2080: api
2081: .getTargetKeyForSingleFieldIdentity(id));
2082:
2083: // try again to read object from cache with this id
2084: pc = getObjectFromCache(id);
2085: }
2086: }
2087: } else {
2088: className = originalClassName;
2089: }
2090:
2091: if (pc == null) {
2092: // Still not found so create a Hollow instance with the supplied field values
2093: try {
2094: Class pcClass = clr.classForName(className,
2095: (id instanceof OID) ? null : id
2096: .getClass().getClassLoader());
2097: sm = StateManagerFactory
2098: .newStateManagerForHollow(this ,
2099: pcClass, id);
2100: pc = sm.getObject();
2101: fromCache = false;
2102: } catch (ClassNotResolvedException e) {
2103: JPOXLogger.PERSISTENCE.warn(LOCALISER.msg(
2104: "010027", id));
2105: throw new JPOXUserException(LOCALISER.msg(
2106: "010027", id), e);
2107: }
2108: }
2109: }
2110: }
2111:
2112: if (validate) {
2113: // User requests validation of the instance so go to the datastore to validate it
2114: // loading any fetchplan fields that are needed in the process.
2115: if (sm == null) {
2116: sm = findStateManager(pc);
2117: }
2118: sm.validate();
2119:
2120: if (!fromCache) {
2121: // We created a Hollow PC earlier but then went to the datastore and let it find the real object
2122: // This allows the datastore to replace this temporary Hollow object with the real datastore object if required
2123: // This doesnt change with RDBMS datastores since we just pull in fields, but for DB4O we pull in object graphs
2124: pc = sm.getObject();
2125: }
2126: }
2127:
2128: if (sm != null) {
2129: // Cache the object (update it if already present)
2130: putObjectIntoCache(sm, !fromCache, true);
2131: }
2132:
2133: return pc;
2134: }
2135:
2136: /**
2137: * This method returns an object id instance corresponding to the pcClass and key arguments.
2138: * Operates in 2 modes :-
2139: * <ul>
2140: * <li>The class uses SingleFieldIdentity and the key is the value of the key field</li>
2141: * <li>In all other cases the key is the String form of the object id instance</li>
2142: * </ul>
2143: * @param pcClass Class of the PersistenceCapable to create the identity for
2144: * @param key Value of the key for SingleFieldIdentity (or the toString value)
2145: * @return The new object-id instance
2146: */
2147: public Object newObjectId(Class pcClass, Object key) {
2148: assertIsOpen();
2149: if (pcClass == null) {
2150: throw new JPOXUserException(LOCALISER.msg("010028"));
2151: }
2152: assertClassPersistable(pcClass);
2153:
2154: AbstractClassMetaData cmd = getMetaDataManager()
2155: .getMetaDataForClass(pcClass, clr);
2156: if (cmd == null) {
2157: throw new NoPersistenceInformationException(pcClass
2158: .getName());
2159: }
2160:
2161: // If the class is not yet managed, manage it
2162: if (!getStoreManager().managesClass(cmd.getFullClassName())) {
2163: getStoreManager().addClass(cmd.getFullClassName(), clr);
2164: }
2165:
2166: Object id = null;
2167: if (cmd.usesSingleFieldIdentityClass()) {
2168: // Single Field Identity
2169: Class idType = clr.classForName(cmd.getObjectidClass());
2170: id = getApiAdapter().getNewSingleFieldIdentity(idType,
2171: pcClass, key);
2172: } else if (key instanceof java.lang.String) {
2173: // String-based PK (datastore identity or application identity)
2174: if (cmd.getIdentityType() == IdentityType.APPLICATION) {
2175: if (Modifier.isAbstract(pcClass.getModifiers())
2176: && cmd.getObjectidClass() != null) {
2177: try {
2178: Constructor c = clr
2179: .classForName(cmd.getObjectidClass())
2180: .getDeclaredConstructor(
2181: new Class[] { java.lang.String.class });
2182: id = c
2183: .newInstance(new Object[] { (String) key });
2184: } catch (Exception e) {
2185: String msg = LOCALISER.msg("010030", cmd
2186: .getObjectidClass(), cmd
2187: .getFullClassName());
2188: JPOXLogger.PERSISTENCE.error(msg);
2189: JPOXLogger.PERSISTENCE.error(e);
2190:
2191: throw new JPOXUserException(msg);
2192: }
2193: } else {
2194: clr.classForName(pcClass.getName(), true);
2195: id = getApiAdapter()
2196: .getNewApplicationIdentityObjectId(pcClass,
2197: key);
2198: }
2199: } else {
2200: id = OIDFactory.getInstance(this , (String) key);
2201: }
2202: } else {
2203: // Key is not a string, and is not SingleFieldIdentity
2204: throw new JPOXUserException(LOCALISER.msg("010029", pcClass
2205: .getName(), key.getClass().getName()));
2206: }
2207:
2208: return id;
2209: }
2210:
2211: /**
2212: * Method to clear an object from the list of dirty objects.
2213: * @param sm The StateManager
2214: */
2215: public synchronized void clearDirty(StateManager sm) {
2216: dirtySMs.remove(sm);
2217: indirectDirtySMs.remove(sm);
2218: }
2219:
2220: /**
2221: * Method to clear all objects marked as dirty (whether directly or indirectly).
2222: */
2223: public synchronized void clearDirty() {
2224: dirtySMs.clear();
2225: indirectDirtySMs.clear();
2226: }
2227:
2228: /**
2229: * Method to mark an object (StateManager) as dirty.
2230: * @param sm The StateManager
2231: * @param directUpdate Whether the object has had a direct update made on it (if known)
2232: */
2233: public synchronized void markDirty(StateManager sm,
2234: boolean directUpdate) {
2235: if (tx.isCommitting() && !tx.isActive()) {
2236: //post commit cannot change objects (sanity check - avoid changing avoids on detach)
2237: throw new JPOXException(
2238: "Cannot change objects when transaction is no longer active.");
2239: }
2240:
2241: boolean isInDirty = dirtySMs.contains(sm);
2242: boolean isInIndirectDirty = indirectDirtySMs.contains(sm);
2243: if (!isDelayDatastoreOperationsEnabled()
2244: && !isInDirty
2245: && !isInIndirectDirty
2246: && dirtySMs.size() >= getOMFContext()
2247: .getPersistenceConfiguration()
2248: .getDatastoreTransactionFlushLimit()) {
2249: // Reached flush limit so flush
2250: flushInternal(false);
2251: }
2252:
2253: if (directUpdate) {
2254: if (isInIndirectDirty) {
2255: indirectDirtySMs.remove(sm);
2256: dirtySMs.add(sm);
2257: } else if (!isInDirty) {
2258: dirtySMs.add(sm);
2259: }
2260: } else {
2261: if (!isInDirty && !isInIndirectDirty) {
2262: // Register as an indirect dirty
2263: indirectDirtySMs.add(sm);
2264: }
2265: }
2266: }
2267:
2268: /**
2269: * Method to mark the specified StateManager as needing an update due to managed relation constraints.
2270: * @param sm The StateManager
2271: */
2272: public void markManagedRelationDirty(StateManager sm) {
2273: if (managedRelationDirtySMs == null) {
2274: managedRelationDirtySMs = new HashSet();
2275: }
2276: managedRelationDirtySMs.add(sm);
2277: }
2278:
2279: /**
2280: * Returns whether this ObjectManager is currently performing the manage relationships task.
2281: * @return Whether in the process of managing relations
2282: */
2283: public boolean isManagingRelations() {
2284: return managingRelations;
2285: }
2286:
2287: /**
2288: * Method to perform managed relationships tasks.
2289: * @throws JPOXUserException if a consistency check fails
2290: */
2291: protected void performManagedRelationships() {
2292: if (getOMFContext().getPersistenceConfiguration()
2293: .getManageRelationships()
2294: && managedRelationDirtySMs != null
2295: && managedRelationDirtySMs.size() > 0) {
2296: try {
2297: managingRelations = true;
2298:
2299: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
2300: JPOXLogger.PERSISTENCE.debug(LOCALISER
2301: .msg("013000"));
2302: }
2303:
2304: if (getOMFContext().getPersistenceConfiguration()
2305: .getManageRelationshipsChecks()) {
2306: // Tests for negative situations where inconsistently assigned
2307: Iterator iter = managedRelationDirtySMs.iterator();
2308: while (iter.hasNext()) {
2309: StateManager sm = (StateManager) iter.next();
2310: sm.checkManagedRelations();
2311: }
2312: }
2313:
2314: // Process updates to manage the other side of the relations
2315: Iterator iter = managedRelationDirtySMs.iterator();
2316: while (iter.hasNext()) {
2317: StateManager sm = (StateManager) iter.next();
2318: sm.processManagedRelations();
2319: sm.clearManagedRelations();
2320: }
2321: managedRelationDirtySMs.clear();
2322:
2323: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
2324: JPOXLogger.PERSISTENCE.debug(LOCALISER
2325: .msg("013001"));
2326: }
2327: } finally {
2328: managingRelations = false;
2329: }
2330: }
2331: }
2332:
2333: /**
2334: * Returns whether the ObjectManager is in the process of flushing.
2335: * @return true if the ObjectManager is flushing
2336: */
2337: public boolean isFlushing() {
2338: return flushing;
2339: }
2340:
2341: /**
2342: * Method callable from external APIs for user-management of flushing.
2343: * Called by JDO PM.flush, or JPA EM.flush().
2344: * Performs management of relations, prior to performing internal flush of all dirty/new/deleted
2345: * instances to the datastore.
2346: */
2347: public void flush() {
2348: assertIsOpen();
2349: if (tx.isActive()) {
2350: // Managed Relationships
2351: performManagedRelationships();
2352:
2353: // Perform internal flush
2354: flushInternal(true);
2355: }
2356: }
2357:
2358: /**
2359: * This method flushes all dirty, new, and deleted instances to the
2360: * datastore. It has no effect if a transaction is not active. If a
2361: * datastore transaction is active, this method synchronizes the cache with
2362: * the datastore and reports any exceptions. If an optimistic transaction is
2363: * active, this method obtains a datastore connection and synchronizes the
2364: * cache with the datastore using this connection. The connection obtained
2365: * by this method is held until the end of the transaction.
2366: * @param flushToDatastore Whether to ensure any changes reach the datastore
2367: * Otherwise they will be flushed to the datastore manager and leave it to
2368: * decide the opportune moment to actually flush them to the datastore
2369: * @throws JPOXOptimisticException when optimistic locking error(s) occur
2370: */
2371: public synchronized void flushInternal(boolean flushToDatastore) {
2372: assertIsOpen();
2373:
2374: if (tx.isActive()) {
2375: if (!flushToDatastore && dirtySMs.size() == 0
2376: && indirectDirtySMs.size() == 0) {
2377: // Nothing to flush so abort now
2378: return;
2379: }
2380:
2381: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
2382: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("010003",
2383: (dirtySMs.size() + indirectDirtySMs.size())));
2384: }
2385: flushing = true;
2386: try {
2387: List optimisticFailures = null;
2388:
2389: // Flush all dirty, new, deleted instances to the datastore when transaction is active
2390: Object[] toFlushDirect;
2391: synchronized (dirtySMs) {
2392: toFlushDirect = dirtySMs.toArray();
2393: dirtySMs.clear();
2394: }
2395:
2396: Object[] toFlushIndirect;
2397: synchronized (indirectDirtySMs) {
2398: toFlushIndirect = indirectDirtySMs.toArray();
2399: indirectDirtySMs.clear();
2400: }
2401:
2402: // a). direct dirty objects
2403: for (int i = 0; i < toFlushDirect.length; i++) {
2404: StateManager sm = (StateManager) toFlushDirect[i];
2405: try {
2406: sm.flush();
2407: } catch (JPOXOptimisticException oe) {
2408: if (optimisticFailures == null) {
2409: optimisticFailures = new ArrayList();
2410: }
2411: optimisticFailures.add(oe);
2412: }
2413: }
2414:
2415: // b). indirect dirty objects
2416: for (int i = 0; i < toFlushIndirect.length; i++) {
2417: StateManager sm = (StateManager) toFlushIndirect[i];
2418: try {
2419: sm.flush();
2420: } catch (JPOXOptimisticException oe) {
2421: if (optimisticFailures == null) {
2422: optimisticFailures = new ArrayList();
2423: }
2424: optimisticFailures.add(oe);
2425: }
2426: }
2427:
2428: // Make sure flushes its changes to the datastore
2429: if (flushToDatastore) {
2430: tx.flush();
2431: }
2432: if (optimisticFailures != null) {
2433: // Throw a single JPOXOptimisticException containing all optimistic failures
2434: throw new JPOXOptimisticException(
2435: LOCALISER.msg("010031"),
2436: (Throwable[]) optimisticFailures
2437: .toArray(new Throwable[optimisticFailures
2438: .size()]));
2439: }
2440: } finally {
2441: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
2442: JPOXLogger.PERSISTENCE.debug(LOCALISER
2443: .msg("010004"));
2444: }
2445: flushing = false;
2446: }
2447: }
2448: }
2449:
2450: /** Flag for whether running persistence-by-reachability-at-commit */
2451: private boolean runningPBRAtCommit = false;
2452:
2453: /**
2454: * Method to perform any post-begin checks.
2455: */
2456: public synchronized void postBegin() {
2457: StateManager[] sms = (StateManager[]) dirtySMs
2458: .toArray(new StateManager[dirtySMs.size()]);
2459: for (int i = 0; i < sms.length; i++) {
2460: sms[i].preBegin(tx);
2461: }
2462: sms = (StateManager[]) indirectDirtySMs
2463: .toArray(new StateManager[indirectDirtySMs.size()]);
2464: for (int i = 0; i < sms.length; i++) {
2465: sms[i].preBegin(tx);
2466: }
2467: }
2468:
2469: /**
2470: * Method to perform any pre-commit checks.
2471: */
2472: public synchronized void preCommit() {
2473: // Make sure all is flushed before we start
2474: flush();
2475:
2476: if (omf.getPersistenceByReachabilityAtCommit()) {
2477: // Persistence-by-reachability at commit
2478: try {
2479: runningPBRAtCommit = true;
2480: performReachabilityAtCommit();
2481: getTransaction().flush();
2482: } catch (Throwable t) {
2483: JPOXLogger.PERSISTENCE.error(t);
2484: if (t instanceof JPOXException) {
2485: throw (JPOXException) t;
2486: } else {
2487: throw new JPOXException(
2488: "Unexpected error during precommit", t);
2489: }
2490: } finally {
2491: runningPBRAtCommit = false;
2492: }
2493: }
2494:
2495: if (detachAllOnCommit) {
2496: // "detach-on-commit"
2497: performDetachAllOnCommitPreparation();
2498: }
2499: }
2500:
2501: /**
2502: * Method to perform persistence-by-reachability at commit.
2503: * Utilises txKnownPersistedIds, and txFlushedNewIds, together with txKnownDeletedIds
2504: * and runs reachability, performing any necessary delettions of no longer reachable objects.
2505: */
2506: private void performReachabilityAtCommit() {
2507: if (JPOXLogger.REACHABILITY.isDebugEnabled()) {
2508: JPOXLogger.REACHABILITY.debug(LOCALISER.msg("010032"));
2509: }
2510:
2511: // If we have some new objects in this transaction, and we have some known persisted objects (either
2512: // from makePersistent in this txn, or enlisted existing objects) then run reachability checks
2513: if (txKnownPersistedIds.size() > 0
2514: && txFlushedNewIds.size() > 0) {
2515: Set currentReachables = new HashSet();
2516:
2517: // Run "reachability" on all known persistent objects for this txn
2518: Object ids[] = txKnownPersistedIds.toArray();
2519: Set objectNotFound = new HashSet();
2520: for (int i = 0; i < ids.length; i++) {
2521: if (!txKnownDeletedIds.contains(ids[i])) {
2522: if (JPOXLogger.REACHABILITY.isDebugEnabled()) {
2523: JPOXLogger.REACHABILITY
2524: .debug("Performing reachability algorithm on object with id \""
2525: + ids[i] + "\"");
2526: }
2527: try {
2528: StateManager sm = findStateManager(findObject(
2529: ids[i], true, true, null));
2530: sm.runReachability(currentReachables);
2531: if (i % 10000 == 0 || i == ids.length - 1) {
2532: // Flush every 10000 or on the last one to make sure tx cache is empty
2533: flushInternal(true);
2534: }
2535: } catch (JPOXObjectNotFoundException ex) {
2536: objectNotFound.add(ids[i]);
2537: }
2538: } else {
2539: // Was deleted earlier so ignore
2540: }
2541: }
2542:
2543: // Remove any of the "reachable" instances that are no longer "reachable"
2544: txFlushedNewIds.removeAll(currentReachables);
2545:
2546: Object nonReachableIds[] = txFlushedNewIds.toArray();
2547: if (nonReachableIds != null && nonReachableIds.length > 0) {
2548: // For all of instances no longer reachable we need to delete them from the datastore
2549: // A). Nullify all of their fields.
2550: // TODO See CORE-3276 for a possible change to this
2551: for (int i = 0; i < nonReachableIds.length; i++) {
2552: if (JPOXLogger.REACHABILITY.isDebugEnabled()) {
2553: JPOXLogger.REACHABILITY.debug(LOCALISER.msg(
2554: "010033", nonReachableIds[i]));
2555: }
2556: try {
2557: if (!objectNotFound
2558: .contains(nonReachableIds[i])) {
2559: StateManager sm = findStateManager(findObject(
2560: nonReachableIds[i], true, true,
2561: null));
2562: sm.nullifyFields();
2563:
2564: if (i % 10000 == 0
2565: || i == nonReachableIds.length - 1) {
2566: // Flush every 10000 or on the last one to clear out dirties
2567: flushInternal(true);
2568: }
2569: }
2570: } catch (JPOXObjectNotFoundException ex) {
2571: // just ignore if the object does not exist anymore
2572: }
2573: }
2574:
2575: // B). Remove the objects
2576: for (int i = 0; i < nonReachableIds.length; i++) {
2577: try {
2578: if (!objectNotFound
2579: .contains(nonReachableIds[i])) {
2580: StateManager sm = findStateManager(findObject(
2581: nonReachableIds[i], true, true,
2582: null));
2583: sm.deletePersistent();
2584: if (i % 10000 == 0
2585: || i == nonReachableIds.length - 1) {
2586: // Flush every 10000 or on the last one to clear out dirties
2587: flushInternal(true);
2588: }
2589: }
2590: } catch (JPOXObjectNotFoundException ex) {
2591: //just ignore if the file does not exist anymore
2592: }
2593: }
2594: }
2595: }
2596:
2597: if (JPOXLogger.REACHABILITY.isDebugEnabled()) {
2598: JPOXLogger.REACHABILITY.debug(LOCALISER.msg("010034"));
2599: }
2600: }
2601:
2602: /**
2603: * Temporary array of StateManagers to detach at commit (to prevent garbage collection).
2604: * Set up in preCommit() and used in postCommit().
2605: */
2606: private StateManager[] detachAllOnCommitSMs = null;
2607:
2608: /**
2609: * Method to perform all necessary preparation for detach-all-on-commit.
2610: * Identifies all objects affected and makes sure that all fetch plan fields are loaded.
2611: */
2612: private void performDetachAllOnCommitPreparation() {
2613: // JDO2 spec 12.7.3 "Root instances"
2614: // "Root instances are parameter instances for retrieve, detachCopy, and refresh; result
2615: // instances for queries. Root instances for DetachAllOnCommit are defined explicitly by
2616: // the user via the FetchPlan property DetachmentRoots or DetachmentRootClasses.
2617: // If not set explicitly, the detachment roots consist of the union of all root instances of
2618: // methods executed since the last commit or rollback."
2619: Collection sms = new ArrayList();
2620: Collection roots = fetchPlan.getDetachmentRoots();
2621: Class[] rootClasses = fetchPlan.getDetachmentRootClasses();
2622: if (roots != null && roots.size() > 0) {
2623: // Detachment roots specified
2624: Iterator rootsIter = roots.iterator();
2625: while (rootsIter.hasNext()) {
2626: Object obj = rootsIter.next();
2627: sms.add(findStateManager(obj));
2628: }
2629: } else if (rootClasses != null && rootClasses.length > 0) {
2630: // Detachment root classes specified
2631: StateManager[] txSMs = (StateManager[]) enlistedSMCache
2632: .values().toArray(
2633: new StateManager[enlistedSMCache.size()]);
2634: for (int i = 0; i < txSMs.length; i++) {
2635: for (int j = 0; j < rootClasses.length; j++) {
2636: // Check if object is of this root type
2637: if (txSMs[i].getObject().getClass() == rootClasses[j]) {
2638: // This SM is for a valid root object
2639: sms.add(txSMs[i]);
2640: break;
2641: }
2642: }
2643: }
2644: } else {
2645: // Detach all objects in the L1 cache
2646: sms.addAll(cache.values());
2647: }
2648:
2649: // Make sure that all FetchPlan fields are loaded
2650: Iterator smsIter = sms.iterator();
2651: while (smsIter.hasNext()) {
2652: StateManager sm = (StateManager) smsIter.next();
2653: Object pc = sm.getObject();
2654: if (pc != null && !getApiAdapter().isDetached(pc)
2655: && !getApiAdapter().isDeleted(pc)) {
2656: // Load all fields (and sub-objects) in the FetchPlan
2657: FetchPlanState state = new FetchPlanState();
2658: try {
2659: sm.loadFieldsInFetchPlan(state);
2660: } catch (JPOXObjectNotFoundException onfe) {
2661: // This object doesnt exist in the datastore at this point.
2662: // Either the user has some other process that has deleted it or they have
2663: // defined datastore based cascade delete and it has been deleted that way
2664: JPOXLogger.PERSISTENCE.warn(LOCALISER.msg("010013",
2665: StringUtils.toJVMIDString(pc), sm
2666: .getInternalObjectId()));
2667: smsIter.remove();
2668: // TODO Move the object state to P_DELETED for consistency
2669: }
2670: }
2671: }
2672: detachAllOnCommitSMs = (StateManager[]) sms
2673: .toArray(new StateManager[sms.size()]);
2674: }
2675:
2676: /**
2677: * Method to perform detach-all-on-commit, using the data identified by
2678: * performDetachAllOnCommitPreparation().
2679: */
2680: private void performDetachAllOnCommit() {
2681: try {
2682: runningDetachAllOnCommit = true;
2683:
2684: // Detach all detachment root objects (causes recursion through the fetch plan)
2685: StateManager[] smsToDetach = detachAllOnCommitSMs;
2686: DetachState state = new DetachState(getApiAdapter());
2687: for (int i = 0; i < smsToDetach.length; i++) {
2688: Object pc = smsToDetach[i].getObject();
2689: if (pc != null) {
2690: smsToDetach[i].detach(state);
2691: }
2692: }
2693: detachAllOnCommitSMs = null; // No longer need these references
2694: } finally {
2695: runningDetachAllOnCommit = false;
2696: }
2697: }
2698:
2699: /**
2700: * Accessor for whether this ObjectManager is currently running detachAllOnCommit.
2701: * @return Whether running detachAllOnCommit
2702: */
2703: public boolean isRunningDetachAllOnCommit() {
2704: return runningDetachAllOnCommit;
2705: }
2706:
2707: /**
2708: * Method to perform detach on close (of the ObjectManager).
2709: */
2710: private void performDetachOnClose() {
2711: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("010011"));
2712: try {
2713: // Start a transaction in case we need to load any unloaded fields
2714: tx.begin();
2715:
2716: List toDetach = new ArrayList();
2717: toDetach.addAll(cache.values());
2718: Iterator iter = toDetach.iterator();
2719: while (iter.hasNext()) {
2720: Object obj = iter.next();
2721: StateManager sm = (StateManager) obj;
2722: if (sm != null) {
2723: //TODO why SM is in cache if null state ...
2724: if (sm != null
2725: && sm.getObject() != null
2726: && !sm.getObjectManager().getApiAdapter()
2727: .isDeleted(sm.getObject())) {
2728: try {
2729: sm.detach(new DetachState(getApiAdapter()));
2730: } catch (JPOXObjectNotFoundException onfe) {
2731: // Catch exceptions for any objects that are deleted in other managers whilst having this open
2732: }
2733: }
2734: }
2735: }
2736: tx.commit();
2737: } finally {
2738: if (tx.isActive()) {
2739: tx.rollback();
2740: }
2741: }
2742: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("010012"));
2743: }
2744:
2745: /**
2746: * Commit any changes made to objects managed by the persistence manager to the database.
2747: */
2748: public synchronized void postCommit() {
2749: if (detachAllOnCommit) {
2750: // Detach-all-on-commit
2751: performDetachAllOnCommit();
2752: }
2753:
2754: List failures = null;
2755: try {
2756: // Commit all enlisted StateManagers
2757: ApiAdapter api = getApiAdapter();
2758: StateManager[] sms = (StateManager[]) enlistedSMCache
2759: .values().toArray(
2760: new StateManager[enlistedSMCache.size()]);
2761: for (int i = 0; i < sms.length; ++i) {
2762: try {
2763: // Perform any operations required after committing
2764: //TODO this if is due to sms that can have lc == null, why?, should not be here then
2765: if (sms[i] != null
2766: && sms[i].getObject() != null
2767: && (api.isPersistent(sms[i].getObject()) || api
2768: .isTransactional(sms[i].getObject()))) {
2769: sms[i].postCommit(getTransaction());
2770:
2771: // TODO Change this check so that we remove all objects that are no longer suitable for caching
2772: if (detachAllOnCommit
2773: && api.isDetachable(sms[i].getObject())) {
2774: // "DetachAllOnCommit" - Remove the object from the L1 cache since it is now detached
2775: removeStateManager(sms[i]);
2776: }
2777: }
2778: } catch (RuntimeException e) {
2779: if (failures == null) {
2780: failures = new ArrayList();
2781: }
2782: failures.add(e);
2783: }
2784: }
2785: } finally {
2786: enlistedSMCache.clear();
2787: txEnlistedIds.clear();
2788: txKnownPersistedIds.clear();
2789: txKnownDeletedIds.clear();
2790: txFlushedNewIds.clear();
2791: fetchPlan.resetDetachmentRoots();
2792: if (managedRelationDirtySMs != null) {
2793: managedRelationDirtySMs.clear();
2794: }
2795: }
2796: if (failures != null && !failures.isEmpty()) {
2797: throw new CommitStateTransitionException(
2798: (Exception[]) failures
2799: .toArray(new Exception[failures.size()]));
2800: }
2801: }
2802:
2803: /**
2804: * Rollback any changes made to objects managed by the persistence manager
2805: * to the database.
2806: */
2807: public synchronized void preRollback() {
2808: ArrayList failures = null;
2809: try {
2810: Collection sms = enlistedSMCache.values();
2811: Iterator smsIter = sms.iterator();
2812: while (smsIter.hasNext()) {
2813: StateManager sm = (StateManager) smsIter.next();
2814: try {
2815: sm.preRollback(getTransaction());
2816: } catch (RuntimeException e) {
2817: if (failures == null) {
2818: failures = new ArrayList();
2819: }
2820: failures.add(e);
2821: }
2822: }
2823: clearDirty();
2824: } finally {
2825: enlistedSMCache.clear();
2826: txEnlistedIds.clear();
2827: txKnownPersistedIds.clear();
2828: txKnownDeletedIds.clear();
2829: txFlushedNewIds.clear();
2830: if (managedRelationDirtySMs != null) {
2831: managedRelationDirtySMs.clear();
2832: }
2833: }
2834: if (failures != null && !failures.isEmpty()) {
2835: throw new RollbackStateTransitionException(
2836: (Exception[]) failures
2837: .toArray(new Exception[failures.size()]));
2838: }
2839: }
2840:
2841: // -------------------------------------- Cache Management ---------------------------------------
2842:
2843: /**
2844: * Replace the previous object id for a persistable object with a new one.
2845: * This is used where we have already added the object to the cache(s) and/or enlisted it in the txn before
2846: * its real identity was fixed (attributed in the datastore).
2847: * @param pc The Persistable object
2848: * @param oldID the old id it was known by
2849: * @param newID the new id
2850: */
2851: public synchronized void replaceObjectId(Object pc, Object oldID,
2852: Object newID) {
2853: if (pc == null || getApiAdapter().getIdForObject(pc) == null) {
2854: JPOXLogger.CACHE.warn(LOCALISER.msg("003006"));
2855: return;
2856: }
2857:
2858: Object o = cache.get(oldID); //use get() because a cache.remove operation returns a weakReference instance
2859: if (o != null) {
2860: // Remove the old variant
2861: if (JPOXLogger.CACHE.isDebugEnabled()) {
2862: JPOXLogger.CACHE.debug(LOCALISER.msg("003012",
2863: StringUtils.toJVMIDString(pc), oldID, newID));
2864: }
2865: cache.remove(oldID);
2866: }
2867:
2868: // Put into both caches
2869: StateManager sm = findStateManager(pc);
2870: if (sm != null) {
2871: putObjectIntoCache(sm, true, true);
2872: }
2873:
2874: if (enlistedSMCache.get(oldID) != null) {
2875: // Swap the enlisted object identity
2876: if (sm != null) {
2877: enlistedSMCache.remove(oldID);
2878: enlistedSMCache.put(newID, sm);
2879: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
2880: JPOXLogger.TRANSACTION.debug(LOCALISER.msg(
2881: "015018", StringUtils.toJVMIDString(pc),
2882: oldID, newID));
2883: }
2884: }
2885: }
2886:
2887: if (omf.getPersistenceByReachabilityAtCommit()) {
2888: if (txEnlistedIds.remove(oldID)) {
2889: txEnlistedIds.add(newID);
2890: }
2891: if (txFlushedNewIds.remove(oldID)) {
2892: txFlushedNewIds.add(newID);
2893: }
2894: if (txKnownPersistedIds.remove(oldID)) {
2895: txKnownPersistedIds.add(newID);
2896: }
2897: if (txKnownDeletedIds.remove(oldID)) {
2898: txKnownDeletedIds.add(newID);
2899: }
2900: }
2901: }
2902:
2903: /**
2904: * Convenience method to add an object to the cache(s).
2905: * @param sm The State Manager
2906: * @param level1 Whether to put it in the L1 cache
2907: * @param level2 Whether to put it in the L2 cache
2908: */
2909: public synchronized void putObjectIntoCache(StateManager sm,
2910: boolean level1, boolean level2) {
2911: Object id = sm.getInternalObjectId();
2912: if (id == null || sm.getObject() == null) {
2913: JPOXLogger.CACHE.warn(LOCALISER.msg("003006"));
2914: return;
2915: }
2916:
2917: // Put into Level 1 Cache
2918: if (level1) {
2919: Object oldSM = cache.put(sm.getInternalObjectId(), sm);
2920: if (JPOXLogger.CACHE.isDebugEnabled()) {
2921: if (oldSM == null) {
2922: JPOXLogger.CACHE.debug(LOCALISER.msg("003004",
2923: StringUtils.toJVMIDString(sm.getObject()),
2924: sm.getInternalObjectId()));
2925: } else {
2926: JPOXLogger.CACHE.debug(LOCALISER.msg("003005",
2927: StringUtils.toJVMIDString(sm.getObject()),
2928: sm.getInternalObjectId()));
2929: }
2930: }
2931: }
2932:
2933: // Put into Level 2 Cache
2934: if (level2 && this .omf.getCacheLevel2()) {
2935: boolean storeInL2Cache = true;
2936: AbstractClassMetaData acmd = getMetaDataManager()
2937: .getMetaDataForClass(sm.getObject().getClass(), clr);
2938: if (acmd != null
2939: && acmd.getIdentityType() == IdentityType.APPLICATION) {
2940: // If using compound identity dont put it in the L2 Cache (the id uses a PC which we can't link to)
2941: int[] pkFieldNumbers = acmd.getPKMemberPositions();
2942: for (int i = 0; i < pkFieldNumbers.length; i++) {
2943: AbstractMemberMetaData fmd = acmd
2944: .getMetaDataForManagedMemberAtAbsolutePosition(pkFieldNumbers[i]);
2945: if (getApiAdapter().isPersistable(fmd.getType())) {
2946: storeInL2Cache = false;
2947: }
2948: }
2949: }
2950:
2951: if (storeInL2Cache) {
2952: if (JPOXLogger.CACHE.isDebugEnabled()) {
2953: JPOXLogger.CACHE.debug(LOCALISER.msg("004003",
2954: StringUtils.toJVMIDString(sm.getObject()),
2955: id));
2956: }
2957: // Store an L2 cacheable form of this object
2958: CachedPC pcCopy = sm.getL2CacheableObject();
2959: this .omf.getLevel2Cache().put(id, pcCopy);
2960: }
2961: }
2962: }
2963:
2964: /**
2965: * Convenience method to evict an object from the cache(s).
2966: * @param pc The Persistpable object
2967: * @param id The Persistable object id
2968: * @param level1 Whether to evict from the L1 cache
2969: * @param level2 Whether to evict from the L2 cache
2970: */
2971: public synchronized void removeObjectFromCache(Object pc,
2972: Object id, boolean level1, boolean level2) {
2973: // Evict from the L1 cache
2974: if (level1 && id != null) {
2975: if (JPOXLogger.CACHE.isDebugEnabled()) {
2976: JPOXLogger.CACHE.debug(LOCALISER.msg("003009",
2977: StringUtils.toJVMIDString(pc), id, String
2978: .valueOf(cache.size())));
2979: }
2980: Object pcRemoved = cache.remove(id);
2981: if (pcRemoved == null && JPOXLogger.CACHE.isDebugEnabled()) {
2982: // For some reason the object isn't in the L1 cache - garbage collected maybe ?
2983: JPOXLogger.CACHE.debug(LOCALISER.msg("003010",
2984: StringUtils.toJVMIDString(pc), id));
2985: }
2986: }
2987:
2988: // Evict from the L2 cache
2989: if (level2 && omf.getCacheLevel2()
2990: && getApiAdapter().getIdForObject(pc) != null) {
2991: Level2Cache l2Cache = omf.getLevel2Cache();
2992: if (JPOXLogger.CACHE.isDebugEnabled()) {
2993: JPOXLogger.CACHE.debug(LOCALISER.msg("004007",
2994: StringUtils.toJVMIDString(pc), getApiAdapter()
2995: .getIdForObject(pc), String
2996: .valueOf(l2Cache.getSize())));
2997: }
2998: l2Cache.evict(getApiAdapter().getIdForObject(pc));
2999: }
3000: }
3001:
3002: /**
3003: * Convenience method to access an object in the cache.
3004: * Firstly looks in the L1 cache for this ObjectManager, and if not found looks in the L2 cache.
3005: * @param id Id of the object
3006: * @return Persistence Capable object (with connected StateManager).
3007: */
3008: public synchronized Object getObjectFromCache(Object id) {
3009: Object pc = null;
3010:
3011: // Try Level 1 first
3012: StateManager sm = (StateManager) cache.get(id);
3013: if (sm != null) {
3014: pc = sm.getObject();
3015:
3016: if (JPOXLogger.CACHE.isDebugEnabled()) {
3017: JPOXLogger.CACHE.debug(LOCALISER.msg("003008",
3018: StringUtils.toJVMIDString(pc), id, ""
3019: + cache.size()));
3020: }
3021: // Wipe the detach state that may have been added if the object has been serialised in the meantime
3022: sm.resetDetachState();
3023:
3024: return pc;
3025: } else {
3026: if (JPOXLogger.CACHE.isDebugEnabled()) {
3027: JPOXLogger.CACHE.debug(LOCALISER.msg("003007", id, ""
3028: + cache.size()));
3029: }
3030: }
3031:
3032: // Try Level 2 since not in Level 1
3033: if (omf.getCacheLevel2()) {
3034: Level2Cache l2Cache = omf.getLevel2Cache();
3035: CachedPC cachedPC = l2Cache.get(id);
3036:
3037: // Copy the cached object and connect to a StateManager, with the same object id
3038: if (cachedPC != null) {
3039: sm = StateManagerFactory.newStateManagerForCachedPC(
3040: this , cachedPC.getPersistableObject(), id,
3041: cachedPC.getLoadedFields());
3042: pc = sm.getObject();
3043: if (JPOXLogger.CACHE.isDebugEnabled()) {
3044: JPOXLogger.CACHE.debug(LOCALISER.msg("004006",
3045: StringUtils.toJVMIDString(pc), id, ""
3046: + l2Cache.getSize()));
3047: }
3048: cache.put(id, sm); // Put into L1 cache for easy referencing
3049: return pc;
3050: } else {
3051: if (JPOXLogger.CACHE.isDebugEnabled()) {
3052: JPOXLogger.CACHE.debug(LOCALISER.msg("004005", id,
3053: "" + l2Cache.getSize()));
3054: }
3055: }
3056: }
3057:
3058: return null;
3059: }
3060:
3061: // ------------------------------------- Queries/Extents --------------------------------------
3062:
3063: /**
3064: * Extents are collections of datastore objects managed by the datastore,
3065: * not by explicit user operations on collections. Extent capability is a
3066: * boolean property of classes that are persistence capable. If an instance
3067: * of a class that has a managed extent is made persistent via reachability,
3068: * the instance is put into the extent implicitly.
3069: * @param pcClass The class to query
3070: * @param subclasses Whether to include subclasses in the query.
3071: * @return returns an Extent that contains all of the instances in the
3072: * parameter class, and if the subclasses flag is true, all of the instances
3073: * of the parameter class and its subclasses.
3074: */
3075: public synchronized Extent getExtent(Class pcClass,
3076: boolean subclasses) {
3077: assertIsOpen();
3078: ClassLoaderResolver clr = getClassLoaderResolver();
3079: try {
3080: clr.setPrimary(pcClass.getClassLoader());
3081: assertClassPersistable(pcClass);
3082:
3083: return getStoreManager().getExtent(this , pcClass,
3084: subclasses);
3085: } catch (JPOXException jpe) {
3086: // Convert any JPOX exceptions into what JDO expects
3087: throw JPOXJDOHelper.getJDOExceptionForJPOXException(jpe);
3088: } finally {
3089: clr.unsetPrimary();
3090: }
3091: }
3092:
3093: /**
3094: * Construct an empty query instance.
3095: * @return The query
3096: */
3097: public synchronized Query newQuery() {
3098: return getOMFContext().getQueryManager().newQuery(
3099: "javax.jdo.query.JDOQL", this , null);
3100: }
3101:
3102: // ------------------------------------- Callback Listeners --------------------------------------
3103:
3104: /**
3105: * This method removes all previously registered lifecycle listeners.
3106: * It is necessary to make sure, that a cached ObjectManager (in j2ee environment)
3107: * will have no listener before the listeners are copied from the OMF.
3108: * Otherwise they might be registered multiple times.
3109: */
3110: public void removeAllInstanceLifecycleListeners() {
3111: if (callbacks != null) {
3112: callbacks.close();
3113: }
3114: }
3115:
3116: /**
3117: * Retrieve the callback handler for this ObjectManager.
3118: * @return the callback handler
3119: */
3120: public CallbackHandler getCallbackHandler() {
3121: if (callbacks != null) {
3122: return callbacks;
3123: }
3124:
3125: String callbackHandlerClassName = getOMFContext()
3126: .getPluginManager().getAttributeValueForExtension(
3127: "org.jpox.callbackhandler", "name",
3128: getOMFContext().getApi(), "class-name");
3129: if (callbackHandlerClassName != null) {
3130: try {
3131: callbacks = (CallbackHandler) clr.classForName(
3132: callbackHandlerClassName,
3133: OMFContext.class.getClassLoader())
3134: .newInstance();
3135: return callbacks;
3136: } catch (InstantiationException e) {
3137: JPOXLogger.PERSISTENCE.error(LOCALISER.msg("025000",
3138: callbackHandlerClassName, e));
3139: } catch (IllegalAccessException e) {
3140: JPOXLogger.PERSISTENCE.error(LOCALISER.msg("025000",
3141: callbackHandlerClassName, e));
3142: }
3143: }
3144:
3145: return null;
3146: }
3147:
3148: /**
3149: * Method to register a listener for instances of the specified classes.
3150: * @param listener The listener to sends events to
3151: * @param classes The classes that it is interested in
3152: */
3153: public void addListener(Object listener, Class[] classes) {
3154: assertIsOpen();
3155: if (listener == null) {
3156: return;
3157: }
3158: getCallbackHandler().addListener(listener, classes);
3159: }
3160:
3161: /**
3162: * Method to remove a currently registered listener.
3163: * @param listener The listener to remove.
3164: */
3165: public void removeListener(Object listener) {
3166: assertIsOpen();
3167: if (listener != null) {
3168: getCallbackHandler().removeListener(listener);
3169: }
3170: }
3171:
3172: /**
3173: * Disconnect the registered LifecycleListener
3174: */
3175: public void disconnectLifecycleListener() {
3176: // Clear out lifecycle listeners that were registered
3177: if (callbacks != null) {
3178: callbacks.close();
3179: }
3180: }
3181:
3182: // ------------------------------- Assert Utilities ---------------------------------
3183:
3184: /**
3185: * Method to assert if this Object Manager is open.
3186: * Throws a JPOXUserException if the ObjectManager is closed.
3187: */
3188: protected void assertIsOpen() {
3189: if (isClosed()) {
3190: throw new JPOXUserException(LOCALISER.msg("010002"))
3191: .setFatal();
3192: }
3193: }
3194:
3195: /**
3196: * Method to assert if the specified class is Persistence Capable.
3197: * @param cls The class to check
3198: * @throws ClassNotPersistableException if class is not persistable
3199: * @throws NoPersistenceInformationException if no metadata/annotations are found for class
3200: */
3201: public void assertClassPersistable(Class cls) {
3202: if (cls != null
3203: && !getOMFContext().getApiAdapter().isPersistable(cls)
3204: && !cls.isInterface()) {
3205: throw new ClassNotPersistableException(cls.getName());
3206: }
3207: if (!hasPersistenceInformationForClass(cls)) {
3208: throw new NoPersistenceInformationException(cls.getName());
3209: }
3210: }
3211:
3212: /**
3213: * Method to assert if the specified object is Detachable.
3214: * Throws a ClassNotDetachableException if not capable
3215: * @param object The object to check
3216: */
3217: protected void assertDetachable(Object object) {
3218: if (object != null && !getApiAdapter().isDetachable(object)) {
3219: throw new ClassNotDetachableException(object.getClass()
3220: .getName());
3221: }
3222: }
3223:
3224: /**
3225: * Method to assert if the specified object is detached.
3226: * Throws a ObjectDetachedException if it is detached.
3227: * @param object The object to check
3228: */
3229: protected void assertNotDetached(Object object) {
3230: if (object != null && getApiAdapter().isDetached(object)) {
3231: throw new ObjectDetachedException(object.getClass()
3232: .getName());
3233: }
3234: }
3235:
3236: /**
3237: * Method to assert if the current transaction is active. Throws a
3238: * TransactionNotActiveException if not active
3239: */
3240: protected void assertActiveTransaction() {
3241: if (!tx.isActive()) {
3242: throw new TransactionNotActiveException();
3243: }
3244: }
3245:
3246: /**
3247: * Method to assert if the current transaction is active or non transactional
3248: * writes are allowed.
3249: * @throws a TransactionNotActiveException if not active and non transactional writes are disabled
3250: */
3251: protected void assertWritable() {
3252: if (!getTransaction().isActive()
3253: && !getTransaction().getNontransactionalWrite()) {
3254: throw new TransactionNotActiveException();
3255: }
3256: }
3257:
3258: /**
3259: * Validates that an ImplementationCreator instance is accessible.
3260: * @throws JPOXUserException if an ImplementationCreator instance does not exist
3261: */
3262: protected void assertHasImplementationCreator() {
3263: if (getOMFContext().getImplementationCreator() == null) {
3264: throw new JPOXUserException(LOCALISER.msg("010035"));
3265: }
3266: }
3267:
3268: /**
3269: * Method to assert if no active transaction and nontransactionalRead is not set.
3270: * @param operation The operation being performed (used in error reporting)
3271: * @throws JPOXUserException if the tx is not active and no non-transactional read is available
3272: */
3273: protected void assertActiveTransactionOrNontransactionRead(
3274: String operation) {
3275: if (!tx.isActive() && !tx.getNontransactionalRead()) {
3276: throw new JPOXUserException(LOCALISER.msg("011009",
3277: operation));
3278: }
3279: }
3280:
3281: /**
3282: * Utility method to check if the specified class has reachable metadata or annotations.
3283: * @param cls The class to check
3284: * @return Whether the class has reachable metadata or annotations
3285: */
3286: public boolean hasPersistenceInformationForClass(Class cls) {
3287: if (cls == null) {
3288: return false;
3289: }
3290:
3291: if ((getMetaDataManager().getMetaDataForClass(cls,
3292: getClassLoaderResolver()) != null)) {
3293: return true;
3294: }
3295:
3296: if (cls.isInterface()) {
3297: // JDO2 "persistent-interface"
3298: // Try to create an implementation of the interface at runtime.
3299: // It will register the MetaData and make an implementation available
3300: try {
3301: newInstance(cls);
3302: } catch (RuntimeException ex) {
3303: JPOXLogger.PERSISTENCE.warn(ex);
3304: }
3305: return getMetaDataManager().getMetaDataForClass(cls,
3306: getClassLoaderResolver()) != null;
3307: }
3308: return false;
3309: }
3310: }
|