/** * Redistribution and use of this software and associated documentation * ("Software"), with or without modification, are permitted provided * that the following conditions are met: * * 1. Redistributions of source code must retain copyright * statements and notices. Redistributions must also contain a * copy of this document. * * 2. Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. The name "Exolab" must not be used to endorse or promote * products derived from this Software without prior written * permission of Intalio, Inc. For written permission, * please contact info@exolab.org. * * 4. Products derived from this Software may not be called "Exolab" * nor may "Exolab" appear in their names without prior written * permission of Intalio, Inc. Exolab is a registered * trademark of Intalio, Inc. * * 5. Due credit should be given to the Exolab Project * (http://www.exolab.org/). * * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * Copyright 1999 (C) Intalio, Inc. All Rights Reserved. * * $Id$ */ package org.exolab.castor.persist; import java.sql.Connection; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.castor.core.util.Messages; import org.castor.cpa.util.JDOClassDescriptorResolver; import org.castor.jdo.util.ClassLoadingUtils; import org.castor.persist.ProposedEntity; import org.castor.persist.TransactionContext; import org.castor.persist.UpdateAndRemovedFlags; import org.castor.persist.UpdateFlags; import org.castor.persist.resolver.ResolverFactory; import org.castor.persist.resolver.ResolverStrategy; import org.exolab.castor.jdo.ObjectModifiedException; import org.exolab.castor.jdo.ObjectNotFoundException; import org.exolab.castor.jdo.PersistenceException; import org.exolab.castor.jdo.Persistent; import org.exolab.castor.jdo.TimeStampable; import org.exolab.castor.jdo.engine.JDOCallback; import org.exolab.castor.jdo.engine.nature.ClassDescriptorJDONature; import org.exolab.castor.jdo.engine.nature.FieldDescriptorJDONature; import org.exolab.castor.mapping.AccessMode; import org.exolab.castor.mapping.ClassDescriptor; import org.exolab.castor.mapping.FieldDescriptor; import org.exolab.castor.mapping.FieldHandler; import org.exolab.castor.mapping.MappingException; import org.exolab.castor.mapping.TypeConvertor; import org.exolab.castor.mapping.loader.ClassDescriptorHelper; import org.exolab.castor.mapping.loader.ClassDescriptorImpl; import org.exolab.castor.mapping.loader.FieldHandlerImpl; import org.exolab.castor.mapping.xml.ClassMapping; import org.exolab.castor.mapping.xml.NamedNativeQuery; import org.exolab.castor.persist.spi.CallbackInterceptor; import org.exolab.castor.persist.spi.Identity; import org.exolab.castor.persist.spi.Persistence; import org.exolab.castor.xml.ClassDescriptorResolver; import org.exolab.castor.xml.ResolverException; /** * ClassMolder is a 'binder' for one type of data object and its corresponding * {@link Persistence}. For example, when ClassMolder is asked to load * an object, it acquires the field values from {@link Persistence} and binds * them into the target object. * <p> * It resolves relations via {@link TransactionContext} and subsequently binds * these related objects into the target object, too. * <p> * Apart from loading, ClassMolder is also responsible for storing, removing, * creating an object to and from a persistence storage, as well as * reverting an object to its previous state. * <p> * Each instance of ClassMolder deals with exactly one persistable type, * interacts with one instance of Persistent and belongs to one * {@link LockEngine}. * <p> * * @author <a href="yip@intalio.com">Thomas Yip</a> * @author <a href="mailto:ferret AT frii dot com">Bruce Snyder</a> * @author <a href="mailto:werner DOT guttmann AT gmx DOT net">Werner Guttmann</a> */ public class ClassMolder { /** The <a href="http://jakarta.apache.org/commons/logging/">Jakarta * Commons Logging</a> instance used for all logging. */ private static Log _log = LogFactory.getFactory().getInstance(ClassMolder.class); /** The fully qualified name of the java data object class which this ClassMolder * corresponds to. We call it base class. */ private String _name; /** Associated identity <tt>FieldMolder</tt>s. */ private FieldMolder[] _ids; /** Associated field <tt>FieldMolder</tt>s. */ private FieldMolder[] _fhs; /** <tt>ClassMolder</tt> of the java data object class's ClassMolder which * the base class extends; null if this class does not extend any other * classes. */ private ClassMolder _extends; /** <tt>ClassMolder</tt> of the java data object class's ClassMolder which * the base class depends on. <tt>null</tt> if it class is an indenpendent * class. */ private ClassMolder _depends; /** A Vector of <tt>ClassMolder</tt>s for all the direct dependent class of the * base class. */ private Vector<ClassMolder> _dependent; /** A Vector of <tt>ClassMolder</tt>s for all the direct extending class of the * base class. */ private Vector<ClassMolder> _extendent; /** Default accessMode of the base class. */ private AccessMode _accessMode; /** Associated {@link Persistence} instance. */ private Persistence _persistence; /** Associated {@link LockEngine} instance. */ private LockEngine _engine; /** The CallbackInterceptor for the base class. */ private CallbackInterceptor _callback; /** The parameters to be used for caching freed instance of the base class. */ private Properties _cacheParams; /** Is a key kenerator used for the base class? */ public boolean _isKeyGenUsed; /** Create priority. */ private int _priority = -1; /** True if all {@link ResolverStrategy} have been reset. */ private boolean _resolversHaveBeenReset = false; /** All field resolver instances. */ private ResolverStrategy[] _resolvers; /** ClassDescriptor for the class this molder is responsible for. */ private final ClassDescriptor _clsDesc; /** * Creates an instance of this class. * * @param ds is the helper class for resolving depends and extends relationship * among all the ClassMolder in the same LockEngine. * @param classDescriptorResolver {@link ClassDescriptorResolver} instance * @param lock the lock engine. * @param classDescriptor the classDescriptor for the base class. * @param persistenceEngine the Persistence for the base class. * @throws ClassNotFoundException If a class cannot be loaded. * @throws MappingException if an error occurred with analyzing the mapping information. */ ClassMolder(final DatingService ds, final ClassDescriptorResolver classDescriptorResolver, final LockEngine lock, final ClassDescriptor classDescriptor, final Persistence persistenceEngine) throws ClassNotFoundException, MappingException { _engine = lock; _persistence = persistenceEngine; _clsDesc = classDescriptor; _name = classDescriptor.getJavaClass().getName(); ds.register(_name, this); if (classDescriptor.hasNature(ClassDescriptorJDONature.class.getName())) { ClassDescriptorJDONature nature = new ClassDescriptorJDONature(classDescriptor); _accessMode = nature.getAccessMode(); } dealWithExtendsAndDepends(ds, classDescriptor); if (classDescriptor.hasNature(ClassDescriptorJDONature.class.getName())) { ClassDescriptorJDONature nature = new ClassDescriptorJDONature(classDescriptor); _cacheParams = nature.getCacheParams(); _isKeyGenUsed = nature.getKeyGeneratorDescriptor() != null; } // construct <tt>FieldMolder</tt>s for each of the identity fields of // the base class. FieldDescriptor[] identityDescriptors = ClassDescriptorHelper.getIdFields(classDescriptor); _ids = new FieldMolder[identityDescriptors.length]; int m = 0; for (FieldDescriptor identityDescriptor : identityDescriptors) { _ids[m++] = new FieldMolder(ds, this, identityDescriptor); } // construct <tt>FieldModlers</tt>s for each of the non-transient fields // of the base class FieldDescriptor[] fieldDescriptors = ClassDescriptorHelper.getFullFields(classDescriptor); int numberOfNonTransientFieldMolders = 0; for (FieldDescriptor fieldDescriptor : fieldDescriptors) { if (!isFieldTransient(fieldDescriptor)) { numberOfNonTransientFieldMolders += 1; } } _fhs = new FieldMolder[numberOfNonTransientFieldMolders]; _resolvers = new ResolverStrategy[numberOfNonTransientFieldMolders]; int fieldMolderCount = 0; for (FieldDescriptor fieldDescriptor : fieldDescriptors) { // don't create field molder for transient fields if (isFieldTransient(fieldDescriptor)) { continue; } if (fieldDescriptor.hasNature(FieldDescriptorJDONature.class.getName()) && new FieldDescriptorJDONature(fieldDescriptor).getManyTable() != null) { FieldDescriptorJDONature nature = new FieldDescriptorJDONature(fieldDescriptor); // the fields is not primitive String[] relatedIdSQL = null; int[] relatedIdType = null; TypeConvertor[] relatedIdConvertTo = null; TypeConvertor[] relatedIdConvertFrom = null; String manyTable = nature.getManyTable(); String[] idSQL = new String[identityDescriptors.length]; int[] idType = new int[identityDescriptors.length]; TypeConvertor[] idConvertFrom = new TypeConvertor[identityDescriptors.length]; TypeConvertor[] idConvertTo = new TypeConvertor[identityDescriptors.length]; FieldDescriptor[] identityFieldDescriptors = ((ClassDescriptorImpl) classDescriptor).getIdentities(); int identityFieldCount = 0; for (FieldDescriptor identityFieldDescriptor : identityFieldDescriptors) { if (identityFieldDescriptor.hasNature(FieldDescriptorJDONature.class.getName())) { idSQL[identityFieldCount] = new FieldDescriptorJDONature(identityFieldDescriptor).getSQLName()[0]; int[] type = new FieldDescriptorJDONature(identityFieldDescriptor).getSQLType(); idType[identityFieldCount] = (type == null) ? 0 : type[0]; FieldHandlerImpl fieldHandler = (FieldHandlerImpl) identityFieldDescriptor.getHandler(); idConvertTo[identityFieldCount] = fieldHandler.getConvertTo(); idConvertFrom[identityFieldCount] = fieldHandler.getConvertFrom(); } else { throw new MappingException( "Identity type must contains sql information: " + _name); } identityFieldCount++; } ClassDescriptor relatedClassDescriptor = null; try { JDOClassDescriptorResolver jdoCDR = (JDOClassDescriptorResolver) classDescriptorResolver; relatedClassDescriptor = jdoCDR.resolve(fieldDescriptor.getFieldType().getName()); } catch (ResolverException e) { throw new MappingException("Problem resolving class descriptor for class " + fieldDescriptor.getClass().getName(), e); } if (relatedClassDescriptor.hasNature(ClassDescriptorJDONature.class.getName())) { FieldDescriptor[] relatedIdentityDescriptors = ((ClassDescriptorImpl) relatedClassDescriptor).getIdentities(); relatedIdSQL = new String[relatedIdentityDescriptors.length]; relatedIdType = new int[relatedIdentityDescriptors.length]; relatedIdConvertTo = new TypeConvertor[relatedIdentityDescriptors.length]; relatedIdConvertFrom = new TypeConvertor[relatedIdentityDescriptors.length]; int relatedIdentityCount = 0; for (FieldDescriptor relatedIdentityDescriptor : relatedIdentityDescriptors) { if (relatedIdentityDescriptor.hasNature(FieldDescriptorJDONature.class.getName())) { FieldDescriptorJDONature relatedNature = new FieldDescriptorJDONature(relatedIdentityDescriptor); String[] tempId = relatedNature.getSQLName(); relatedIdSQL[relatedIdentityCount] = (tempId == null) ? null : tempId[0]; int[] tempType = relatedNature.getSQLType(); relatedIdType[relatedIdentityCount] = (tempType == null) ? 0 : tempType[0]; FieldHandlerImpl fh = (FieldHandlerImpl) relatedIdentityDescriptors[relatedIdentityCount].getHandler(); relatedIdConvertTo[relatedIdentityCount] = fh.getConvertTo(); relatedIdConvertFrom[relatedIdentityCount] = fh.getConvertFrom(); } else { throw new MappingException( "Field type is not persistence-capable: " + relatedIdentityDescriptors[relatedIdentityCount].getFieldName()); } relatedIdentityCount++; } } // if many-key exist, idSQL is overridden String[] manyKey = nature.getManyKey(); if ((manyKey != null) && (manyKey.length != 0)) { if (manyKey.length != idSQL.length) { throw new MappingException( "The number of many-keys doesn't match referred object: " + classDescriptor.getJavaClass().getName()); } idSQL = manyKey; } // if name="" exist, relatedIdSQL is overridden String[] manyName = nature.getSQLName(); if ((manyName != null) && (manyName.length != 0)) { if (manyName.length != relatedIdSQL.length) { throw new MappingException( "The number of many-keys doesn't match referred object: " + relatedClassDescriptor.getJavaClass().getName()); } relatedIdSQL = manyName; } SQLRelationLoader loader = _persistence.createSQLRelationLoader( manyTable, idSQL, idType, idConvertTo, idConvertFrom, relatedIdSQL, relatedIdType, relatedIdConvertTo, relatedIdConvertFrom); _fhs[fieldMolderCount] = new FieldMolder(ds, this, fieldDescriptor, loader); } else { _fhs[fieldMolderCount] = new FieldMolder(ds, this, fieldDescriptor); } // create RelationResolver instance _resolvers[fieldMolderCount] = ResolverFactory.createRelationResolver( _fhs[fieldMolderCount], this, fieldMolderCount); fieldMolderCount += 1; } // ssa, FIXME : Are the two statements equivalents ? // if ( Persistent.class.isAssignableFrom( _base ) ) if (Persistent.class.isAssignableFrom(ds.resolve(_name))) { _callback = new JDOCallback(); } } /** * Remaining method that relies on the usage of {@link ClassMapping} to extract * information from the JDO mapping file rather than relying on {@link ClassDescriptor} * and associated JDO-specific natures. * * @param ds {@link DatingService} instance. * @param classDescriptor The {@link ClassDescriptor} instance used to describe the class at hand. * @throws MappingException If something unforeseen happens .... */ private void dealWithExtendsAndDepends(final DatingService ds, final ClassDescriptor classDescriptor) throws MappingException { ClassDescriptor dependClassDescriptor = ((ClassDescriptorImpl) classDescriptor).getDepends(); ClassDescriptor extendsClassDescriptor = ((ClassDescriptorImpl) classDescriptor).getExtends(); //if ( dep != null && ext != null ) // throw new MappingException("A JDO cannot both extends and depends on other objects"); if (dependClassDescriptor != null) { ds.pairDepends(this, dependClassDescriptor.getJavaClass().getName()); } if (extendsClassDescriptor != null) { ds.pairExtends(this, extendsClassDescriptor.getJavaClass().getName()); } } public ClassDescriptor getClassDescriptor() { return _clsDesc; } private boolean isFieldTransient(final FieldDescriptor fieldDescriptor) { boolean isFieldTransient = fieldDescriptor.isTransient(); if (fieldDescriptor.hasNature(FieldDescriptorJDONature.class.getName())) { FieldDescriptorJDONature nature = new FieldDescriptorJDONature(fieldDescriptor); isFieldTransient |= nature.isTransient(); } return isFieldTransient; } /** * Remove the reference of a related object from an object of * the base class. <p> * * If the related object is PersistanceCapable, the field will * be set null. If the related object is a Collection, then * the related object will be removed from the Collection. <p> * * If any changed occurred, transactionContext.markModified * will be called, to indicate the object is modified. <p> * * It method will iterate through all of the object's field and * try to remove all the occurrence. * * @param tx the TransactionContext of the transaction in action * @param object the target object of the base type of this ClassMolder * @param relatedMolder the ClassMolder of the related object to be * removed from the object * @param relatedObject the object to be removed */ public boolean removeRelation(final TransactionContext tx, final Object object, final ClassMolder relatedMolder, final Object relatedObject) { boolean removed = false; boolean updateCache = false; boolean updatePersist = false; UpdateAndRemovedFlags flags = null; for (int i = 0; i < _fhs.length; i++) { flags = _resolvers[i].removeRelation(tx, object, relatedMolder, relatedObject); updateCache |= flags.getUpdateCache(); updatePersist |= flags.getUpdatePersist(); removed |= flags.getRemoved(); } tx.markModified(object, updatePersist, updateCache); return removed; } /** * Determines the create priority of the data object class represented by * this ClassMolder. Concpetually, this method determines the order of * which data object should be created. * * A priority of 0 indicates that an object represented by this ClassMolder * can be created independently, without having to consider any other * data object. * * This method should only be called after DatingService is closed. */ public int getPriority() { if (_priority == -2) { // circular reference detected, do not loop return 0; } else if (_priority < 0) { // find root of extends hierarchy ClassMolder root = this; while (root._extends != null) { root = root._extends; } // find all class molders of extends hierarchy List<ClassMolder> molders = getAllExtendentMolders(root); // mark all class molders of extends hierarchy as work in progress for (ClassMolder molder : molders) { molder._priority = -2; } // preset maximum priority int maxPriority = 0; // calculate maximum priority from all classes referenced by the whole hierarchy for (ClassMolder molder : molders) { FieldMolder[] fhs = molder._fhs; for (int i = 0; i < fhs.length; i++) { FieldMolder fh = fhs[i]; if (fh.isPersistanceCapable() && fh.isStored() && (fh.getFieldClassMolder() != this)) { int refPriority = fh.getFieldClassMolder().getPriority() + 1; maxPriority = Math.max(maxPriority, refPriority); } } } // set priority of the whole hierarchy to the calculated maximum for (ClassMolder molder : molders) { molder._priority = maxPriority; } } return _priority; } private List<ClassMolder> getAllExtendentMolders(final ClassMolder root) { List<ClassMolder> molders = new ArrayList<ClassMolder>(); molders.add(root); if (root._extendent != null) { for (ClassMolder extendent : root._extendent) { molders.addAll(getAllExtendentMolders(extendent)); } } return molders; } public void loadTimeStamp(final TransactionContext tx, final DepositBox locker, final AccessMode suggestedAccessMode) throws PersistenceException { Object loadObject = null; try { loadObject = newInstance(tx.getClassLoader()); } catch (Exception ex) { throw new PersistenceException("failed to load object", ex); } ProposedEntity proposedObject = new ProposedEntity(this); proposedObject.setProposedEntityClass(loadObject.getClass()); proposedObject.setEntity(loadObject); OID oid = locker.getOID(); Object[] cachedFieldValues = locker.getObject(tx); proposedObject.setFields(cachedFieldValues); AccessMode accessMode = getAccessMode(suggestedAccessMode); // load the fields from the persistent storage if the cache is empty // or the access mode is DBLOCKED (thus guaranteeing that a lock at the // database level will be created) if (!proposedObject.isFieldsSet() || accessMode == AccessMode.DbLocked) { proposedObject.initializeFields(_fhs.length); Connection conn = tx.getConnection(oid.getMolder().getLockEngine()); _persistence.load(conn, proposedObject, oid.getIdentity(), accessMode); oid.setDbLock(accessMode == AccessMode.DbLocked); // store (new) field values to cache locker.setObject(tx, proposedObject.getFields(), System.currentTimeMillis()); } mold(tx, locker, proposedObject, accessMode); } /** * Loads the field values. * * @param tx Currently active transaction context * @param locker Current cache instance * @param proposedObject ProposedEntity instance * @param accessMode Suggested access mode * @param results OQL QueryResults instance * @throws ObjectNotFoundException If the object in question cannot be found. * @throws PersistenceException For any other persistence-related problem. */ public void load(final TransactionContext tx, final DepositBox locker, final ProposedEntity proposedObject, final AccessMode accessMode, final QueryResults results) throws PersistenceException { OID oid = locker.getOID(); if (oid.getIdentity() == null) { throw new PersistenceException( "The identities of the object to be loaded is null"); } proposedObject.initializeFields(_fhs.length); if (results != null) { results.getQuery().fetch(proposedObject); } else { Connection conn = tx.getConnection(oid.getMolder().getLockEngine()); _persistence.load(conn, proposedObject, oid.getIdentity(), accessMode); } oid.setDbLock(accessMode == AccessMode.DbLocked); // store (new) field values to cache locker.setObject(tx, proposedObject.getFields(), System.currentTimeMillis()); } public void mold(final TransactionContext tx, final DepositBox locker, final ProposedEntity proposedObject, final AccessMode accessMode) throws PersistenceException { OID oid = locker.getOID(); // Check for version field. if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) { ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature(_clsDesc); String versionField = jdoNature.getVersionField(); // Check if version field was set and has content. // TODO: that check should be moved to e.g. ClassDescriptorJDONature if (versionField != null && versionField.length() > 0) { // Find field descriptor for version field. FieldDescriptor versionFieldDescriptor = jdoNature.getField(versionField); FieldHandler fieldHandler = versionFieldDescriptor.getHandler(); // Set the entity's version to the locker's version. fieldHandler.setValue(proposedObject.getEntity(), locker.getVersion()); } } // set the timeStamp of the data object to locker's timestamp if (proposedObject.getEntity() instanceof TimeStampable) { ((TimeStampable) proposedObject.getEntity()).jdoSetTimeStamp(locker .getVersion()); } // set the identities into the target object setIdentity(tx, proposedObject.getEntity(), oid.getIdentity()); // iterates over all the field of the object and bind all field. for (int i = 0; i < _fhs.length; i++) { FieldPersistenceType fieldType = _fhs[i].getFieldPertsistenceType(); switch (fieldType) { case PRIMITIVE: case SERIALIZABLE: case PERSISTANCECAPABLE: case ONE_TO_MANY: case MANY_TO_MANY: _resolvers[i].load(tx, oid, proposedObject, accessMode); break; default: throw new PersistenceException("Unexpected field type!"); } } // Check for version field. if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) { ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature(_clsDesc); String versionField = jdoNature.getVersionField(); // Check if version field was set and has content. if (versionField != null && versionField.length() > 0) { // Find field descriptor for version field. FieldDescriptor versionFieldDescriptor = jdoNature.getField(versionField); FieldHandler fieldHandler = versionFieldDescriptor.getHandler(); // Set the version of the locker to the data object's version. locker.setVersion((Long) fieldHandler.getValue(proposedObject .getEntity())); } } // set the timeStamp of locker to the one of data object if (proposedObject.getEntity() instanceof TimeStampable) { locker.setVersion(((TimeStampable) proposedObject.getEntity()) .jdoGetTimeStamp()); } } /** * Create an object of the base class with specified identity into the persistence storage. * * @param tx transaction in action * @param oid the object identity of the object to be created. * @param locker the dirty checking cache of the object * @param object the object to be created * @return the identity of the object */ public Identity create(final TransactionContext tx, final OID oid, final DepositBox locker, final Object object) throws PersistenceException { if (_persistence == null) { throw new PersistenceException("non persistence capable: " + oid.getName()); } ProposedEntity entity = new ProposedEntity(); entity.initializeFields(_fhs.length); Identity ids = oid.getIdentity(); long timeStamp = System.currentTimeMillis(); // Check for version field. if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) { ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature(_clsDesc); String versionField = jdoNature.getVersionField(); // Check if version field was set and has content. // TODO: that check should be moved to e.g. ClassDescriptorJDONature if (versionField != null && versionField.length() > 0) { // Find field descriptor for version field. FieldDescriptor versionFieldDescriptor = jdoNature.getField(versionField); FieldHandler fieldHandler = versionFieldDescriptor.getHandler(); fieldHandler.setValue(object, timeStamp); } } // set the new timeStamp into the data object if (object instanceof TimeStampable) { ((TimeStampable) object).jdoSetTimeStamp(timeStamp); } // copy the object to cache should make a new field now, for (int i = 0; i < _fhs.length; i++) { FieldPersistenceType fieldPersistenceType = _fhs[i].getFieldPertsistenceType(); switch (fieldPersistenceType) { case PRIMITIVE: case PERSISTANCECAPABLE: case SERIALIZABLE: case ONE_TO_MANY: case MANY_TO_MANY: entity.setField(_resolvers[i].create(tx, object), i); break; default: throw new IllegalArgumentException("Field type invalid!"); } } // ask Persistent to create the object into the persistence storage Identity createdId = _persistence.create(tx.getDatabase(), tx.getConnection(oid.getMolder().getLockEngine()), entity, ids); if (createdId == null) { throw new PersistenceException("Identity can't be created!"); } // set the field values into the cache locker.setObject(tx, entity.getFields(), timeStamp); oid.setDbLock(true); // set the identity into the object setIdentity(tx, object, createdId); // after successful creation, add the entry in the relation table for // all many-to-many relationship //ASE: This is the source of problem with M:N relations. As we see at // this point, for every persisted object in any M:N relation, a // new entry to the relation table is created. But this happens not // only when both related objects are persisted but also after // persistence of the first. But that cannot work! // A solution would be to store a collection of relations which have // to persisted in the relation table after all objects are persisted // independently! for (int i = 0; i < _fhs.length; i++) { entity.setField(_resolvers[i].postCreate( tx, oid, object, entity.getField(i), createdId), i); } return createdId; } /** * Walk the object model and mark object that should be created. * * @param tx transaction in action * @param oid the object identity of the object to be created. * @param locker the dirty checking cache of the object * @param object the object to be created */ public void markCreate(final TransactionContext tx, final OID oid, final DepositBox locker, final Object object) throws PersistenceException { boolean updateCache = false; // iterate all the fields and mark all the dependent object. for (int i = 0; i < _fhs.length; i++) { updateCache |= _resolvers[i].markCreate(tx, oid, object); } tx.markModified(object, false, updateCache); } /** * Check the object for modification. If dpendent object is dereferenced, it * method will remove the object thru the transaction. If an related object * is dereferenced, it method will make sure the formally object will be * dereferenced from the other side as well. * * This method is called in prepare (for commit) state of the transaction. * This method indicates if the object needed to be persist or cache should * be update using TransactionContext.markDelete. * * @param tx transaction in action * @param oid the object identity of the object * @param locker the dirty check cache for the object * @param object the data object to be checked * @param timeout timeout of updating the lock if needed * * @return true if the object is modified */ public boolean preStore(final TransactionContext tx, final OID oid, final DepositBox locker, final Object object, final int timeout) throws PersistenceException { if (oid.getIdentity() == null) { throw new PersistenceException(Messages.format( "persist.missingIdentityForStore", _name)); } if (!oid.getIdentity().equals(getIdentity(tx, object))) { throw new PersistenceException(Messages.format( "jdo.identityChanged", _name, oid.getIdentity(), getIdentity(tx, object))); } Object[] fields = locker.getObject(tx); if (fields == null) { throw new PersistenceException( Messages.format("persist.objectNotFound", _name, oid)); } Object[] newfields = new Object[_fhs.length]; boolean updateCache = false; boolean updatePersist = false; // iterate thru all the data object fields for modification UpdateFlags flags; for (int i = 0; i < newfields.length; i++) { flags = _resolvers[i].preStore(tx, oid, object, timeout, fields[i]); updateCache |= flags.getUpdateCache(); updatePersist |= flags.getUpdatePersist(); newfields[i] = flags.getNewField(); } tx.markModified(object, updatePersist, updateCache); if (updateCache || updatePersist) { tx.writeLock(object, timeout); } return updateCache; } /** * Store a data object into the persistent storage of the base class of this * ClassMolder. * * @param tx Transaction in action * @param oid the object identity of the stored object * @param locker the dirty check cache of the object * @param object the object to be stored * @throws PersistenceException If identity is missing for storage * or the identity is modified */ public void store(final TransactionContext tx, final OID oid, final DepositBox locker, final Object object) throws PersistenceException { if (oid.getIdentity() == null) { throw new PersistenceException(Messages.format( "persist.missingIdentityForStore", _name)); } if (!oid.getIdentity().equals(getIdentity(tx, object))) { throw new PersistenceException(Messages.format( "jdo.identityChanged", _name, oid.getIdentity(), getIdentity(tx, object))); } // load field values from cache (if availabe) ProposedEntity oldentity = new ProposedEntity(); oldentity.setFields(locker.getObject(tx)); if (oldentity.getFields() == null) { throw new PersistenceException( Messages.format("persist.objectNotFound", _name, oid)); } long timeStamp = System.currentTimeMillis(); // Set the new timestamp into version field. if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) { ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature(_clsDesc); String versionField = jdoNature.getVersionField(); // Check if version field was set and has content. // TODO: that check should be moved to e.g. ClassDescriptorJDONature if (versionField != null && versionField.length() > 0) { // Find field descriptor for version field. FieldDescriptor versionFieldDescriptor = jdoNature.getField(versionField); FieldHandler fieldHandler = versionFieldDescriptor.getHandler(); fieldHandler.setValue(object, timeStamp); } } // set the new timeStamp into the data object if (object instanceof TimeStampable) { ((TimeStampable) object).jdoSetTimeStamp(timeStamp); } ProposedEntity newentity = new ProposedEntity(); newentity.initializeFields(_fhs.length); for (int i = 0; i < _fhs.length; i++) { newentity.setField(_resolvers[i].store(tx, object, oldentity.getField(i)), i); } // Gets connection reference Connection conn = tx.getConnection(oid.getMolder().getLockEngine()); // Current molder is leaf of extends hierarchy and therefore extending table is null String extendingTableName = null; // Start with current molder ClassMolder molder = this; // Loop over all extended molders and store all values of extends hierarchy while (molder != null) { // Gets name of current table String tableName = new ClassDescriptorJDONature( molder.getClassDescriptor()).getTableName(); // Only need to persist values if current table name is different than extending one if (!tableName.equals(extendingTableName)) { molder._persistence.store(conn, oid.getIdentity(), newentity, oldentity); } extendingTableName = tableName; molder = molder._extends; } } /** * Update the object which loaded or created in the other transaction to the * persistent storage. * * @param tx * Transaction in action * @param oid * the object identity of the stored object * @param locker * the dirty check cache of the object * @param object * the object to be stored * @return boolean true if the updating object should be created */ public boolean update(final TransactionContext tx, final OID oid, final DepositBox locker, final Object object, final AccessMode suggestedAccessMode) throws PersistenceException { AccessMode accessMode = getAccessMode(suggestedAccessMode); Object[] fields = locker.getObject(tx); boolean timeStampable = false; long objectTimestamp = 1; if (object instanceof TimeStampable) { timeStampable = true; objectTimestamp = ((TimeStampable) object).jdoGetTimeStamp(); } ClassDescriptorJDONature jdoNature = null; // Check for version field. if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) { jdoNature = new ClassDescriptorJDONature(_clsDesc); String versionField = jdoNature.getVersionField(); // TODO: that check should be moved to e.g. ClassDescriptorJDONature if (versionField != null && versionField.length() > 0) { objectTimestamp = getObjectVersion(versionField, jdoNature, object); timeStampable = true; } } if ((!isDependent()) && (!timeStampable)) { throw new IllegalArgumentException( "A master object that involves in a long transaction must be a TimeStampable!"); } long lockTimestamp = locker.getVersion(); if ((objectTimestamp > 0) && (oid.getIdentity() != null)) { // valid range of timestamp if ((timeStampable) && (lockTimestamp == TimeStampable.NO_TIMESTAMP)) { throw new PersistenceException(Messages.format( "persist.objectNotInCache", _name, oid.getIdentity())); } if (timeStampable && objectTimestamp != lockTimestamp) { throw new ObjectModifiedException("Timestamp mismatched!"); } if (!timeStampable && isDependent() && (fields == null)) { // allow a dependent object not implements timeStampable fields = new Object[_fhs.length]; Connection conn = tx.getConnection(oid.getMolder().getLockEngine()); ProposedEntity proposedObject = new ProposedEntity(this); proposedObject.setProposedEntityClass(object.getClass()); proposedObject.setEntity(object); proposedObject.setFields(fields); _persistence.load(conn, proposedObject, oid.getIdentity(), accessMode); fields = proposedObject.getFields(); oid.setDbLock(accessMode == AccessMode.DbLocked); locker.setObject(tx, proposedObject.getFields(), System .currentTimeMillis()); } // load the original field into the transaction. so, store will // have something to compare later. try { for (int i = 0; i < _fhs.length; i++) { _resolvers[i] .update(tx, oid, object, accessMode, fields[i]); } } catch (ObjectNotFoundException e) { _log.warn(e.getMessage(), e); throw new ObjectModifiedException( "dependent object deleted concurrently"); } return false; } else if ((objectTimestamp == TimeStampable.NO_TIMESTAMP) || (objectTimestamp == 1)) { // work almost like create, except update the sub field instead of // create // iterate all the fields and mark all the dependent object. boolean updateCache = false; for (int i = 0; i < _fhs.length; i++) { updateCache |= _resolvers[i].updateWhenNoTimestampSet(tx, oid, object, suggestedAccessMode); } tx.markModified(object, false, updateCache); return true; } else { if (_log.isWarnEnabled()) { _log.warn("object: " + object + " timestamp: " + objectTimestamp + " lockertimestamp: " + lockTimestamp); } throw new ObjectModifiedException( "Invalid object timestamp detected."); } } /** * Acquires the version of the specified object by accessing the given * field. * * @param versionField * the version field. * @param jdoNature * the {@link ClassDescriptorJDONature}. * @param object * the object. * @return the object's version. */ private Long getObjectVersion(final String versionField, final ClassDescriptorJDONature jdoNature, final Object object) { FieldDescriptor versionFieldDescriptor = jdoNature.getField(versionField); return (Long) versionFieldDescriptor.getHandler().getValue(object); } /** * Update the dirty checking cache. This method is called after a * transaction completed successfully. * * @param tx - transaction in action * @param oid - object's identity of the target object * @param locker - the dirty checking cache of the target object * @param object - the target object */ public void updateCache(final TransactionContext tx, final OID oid, final DepositBox locker, final Object object) { Object[] fields; if (oid.getIdentity() == null) { throw new IllegalStateException( Messages.format("persist.missingIdentityForCacheUpdate", _name)); } fields = new Object[_fhs.length]; for (int i = 0; i < _fhs.length; i++) { FieldPersistenceType fieldPersistenceType = _fhs[i].getFieldPertsistenceType(); switch (fieldPersistenceType) { case PRIMITIVE: case SERIALIZABLE: case PERSISTANCECAPABLE: case ONE_TO_MANY: case MANY_TO_MANY: fields[i] = _resolvers[i].updateCache(tx, oid, object); break; default: throw new IllegalArgumentException("Field type invalid!"); } } Long objectVersion = null; // Check for version field. if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) { ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature(_clsDesc); String versionField = jdoNature.getVersionField(); // TODO: that check should be moved to e.g. ClassDescriptorJDONature if (versionField != null && versionField.length() > 0) { objectVersion = getObjectVersion(versionField, jdoNature, object); } } // store new field values in cache if (object instanceof TimeStampable) { locker.setObject(tx, fields, ((TimeStampable) object).jdoGetTimeStamp()); } else if (objectVersion != null) { locker.setObject(tx, fields, objectVersion); } else { locker.setObject(tx, fields, System.currentTimeMillis()); } } /** * Delete an object of the base type from the persistence storage. * All object to be deleted by this method will be <tt>markDelete</tt> * before it method is called. * * @param tx - transaction in action * @param oid - the object identity of the target object */ public void delete(final TransactionContext tx, final OID oid) throws PersistenceException { Connection conn = tx.getConnection(oid.getMolder().getLockEngine()); Identity ids = oid.getIdentity(); for (int i = 0; i < _fhs.length; i++) { if (_fhs[i].isManyToMany()) { _fhs[i].getRelationLoader().deleteRelation(conn, ids); } } // Must delete record of extend path from extending to root class // In addition we remember the extend path to delete everything off the path ourself Vector<ClassMolder> extendPath = new Vector<ClassMolder>(); ClassMolder molder = this; while (molder != null) { molder._persistence.delete(conn, ids); extendPath.add(molder); molder = molder._extends; } ClassMolder base = _depends; while (base != null) { if (base._extendent != null) { for (int i = 0; i < base._extendent.size(); i++) { if (extendPath.contains(base._extendent.get(i))) { // NB: further INVESTIGATION } } } base = base._extends; } } /** * Prepare to delete an object with the specified identity. If any sub-object * should be deleted along with the target object, it should be deleted * by this method. * * @param tx - transaction in action * @param oid - object's identity of the target object * @param locker - the dirty checking cache of the target object * @param object - the target object */ public void markDelete(final TransactionContext tx, final OID oid, final DepositBox locker, final Object object) throws PersistenceException { Object[] fields = locker.getObject(tx); for (int i = 0; i < _fhs.length; i++) { FieldPersistenceType fieldType = _fhs[i].getFieldPertsistenceType(); switch (fieldType) { case PRIMITIVE: case SERIALIZABLE: case PERSISTANCECAPABLE: case ONE_TO_MANY: case MANY_TO_MANY: _resolvers[i].markDelete(tx, object, fields[i]); break; default: throw new PersistenceException("Invalid field type!"); } } } /** * Revert the object back to the state of begining of the transaction * If the object is loaded, it will be revert as it was loaded. If the * object is created, it will be revert as it was just created. * * @param tx - transaction in action * @param oid - the object identity of the target object * @param locker - the dirty checking cache of the target object * @param object - the target object */ public void revertObject(final TransactionContext tx, final OID oid, final DepositBox locker, final Object object) throws PersistenceException { if (oid.getIdentity() == null) { throw new PersistenceException( Messages.format("persist.missingIdentityForReverting", _name)); } Object[] fields = locker.getObject(tx); setIdentity(tx, object, oid.getIdentity()); for (int i = 0; i < _fhs.length; i++) { if (fields != null) { _resolvers[i].revertObject(tx, oid, object, fields[i]); } else { _resolvers[i].revertObject(tx, oid, object, null); } } } /** * Return a new instance of the base class with the provided ClassLoader object. * * @param loader the ClassLoader object to use to create a new object. * @return Object the object reprenseted by this ClassMolder, and instanciated with the * provided ClassLoader instance. * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException */ public Object newInstance(final ClassLoader loader) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Class<?> aClass = null; aClass = ClassLoadingUtils.loadClass(loader, _name); return aClass.newInstance(); } /** * Get the effective accessMode of the the base type. * * @param txMode - the default transaction accessMode. * @return the effective acessMode of the base type. */ public AccessMode getAccessMode(final AccessMode txMode) { if (txMode == null) { return _accessMode; } if (_accessMode == AccessMode.ReadOnly || txMode == AccessMode.ReadOnly) { return AccessMode.ReadOnly; } if (_accessMode == AccessMode.DbLocked || txMode == AccessMode.DbLocked) { return AccessMode.DbLocked; } if (_accessMode == AccessMode.Exclusive || txMode == AccessMode.Exclusive) { return AccessMode.Exclusive; } return txMode; } /** * Get the callback interceptor of the base type. */ public CallbackInterceptor getCallback() { return _callback; } /** * Test if the specified identity is the default value of the type. */ public boolean isDefaultIdentity(final Identity identity) { if (identity == null) { return true; } for (int i = 0; i < identity.size(); i++) { if (!_ids[i].isDefault(identity.get(i))) { return false; } } return true; } /** * Get the identity from a object of the base type. * If object isn't persistent and key generator is used, returns null. * * @param tx the transaction context. * @param o - object of the base type. * @return return an Object[] which contains the identity of the object. */ public Identity getIdentity(final TransactionContext tx, final Object o) { // [oleg] In the case where key generator is used, // the value of identity is dummy, set it to null if (isKeyGeneratorUsed() && !(tx.isPersistent(o) || tx.isReadOnly(o))) { return null; } return getActualIdentity(tx, o); } /** * Get the identity from a object of the base type. * * @param tx the transaction context. * @param o - object of the base type. * @return return an Object[] which contains the identity of the object. */ public Identity getActualIdentity(final TransactionContext tx, final Object o) { return getActualIdentity(tx.getClassLoader(), o); } /** * Get the identity from a object of the base type. * * @param loader the current class loader. * @param o - object of the base type. * @return return an Object[] which contains the identity of the object. */ public Identity getActualIdentity(final ClassLoader loader, final Object o) { Object[] ids = new Object[_ids.length]; for (int i = 0; i < ids.length; i++) { ids[i] = _ids[i].getValue(o, loader); } if (ids[0] == null) { return null; } return new Identity(ids); } /** * Set the identity into an object. * * @param tx the transaction context. * @param object the object to set the identity. * @param identity the new identity for the object. */ public void setIdentity(final TransactionContext tx, final Object object, final Identity identity) throws PersistenceException { if (identity.size() != _ids.length) { throw new PersistenceException("Identity size mismatched!"); } for (int i = 0; i < _ids.length; i++) { _ids[i].setValue(object, identity.get(i), tx.getClassLoader()); } } /** * Get the Persisetence of the base type. */ public Persistence getPersistence() { return _persistence; } /** * Get the base class of this ClassMolder given a ClassLoader. * * @param loader the classloader. * @return the <code>Class</code> instance. */ public Class<?> getJavaClass(final ClassLoader loader) { Class<?> result = null; try { result = ClassLoadingUtils.loadClass(loader, _name); } catch (ClassNotFoundException e) { _log.error("Unable to load base class of " + getName(), e); } return result; } // ssa, FIXME : is that method necessary ? /** * check if the current ClassModlder is assignable from the <code>class</code> * instance. * * @param cls the Class to check the assignation * @return true if assignable */ public boolean isAssignableFrom (final Class<?> cls) { ClassLoader loader = cls.getClassLoader(); Class<?> molderClass = null; try { molderClass = ClassLoadingUtils.loadClass(loader, _name); } catch (ClassNotFoundException e) { return false; } return molderClass.isAssignableFrom(cls); } /** * Get the fully qualified name of the base type of this ClassMolder. */ public String getName() { return _name; } /** * Get the extends class' ClassMolder. */ public ClassMolder getExtends() { return _extends; } /** * Get the depends class' ClassMolder. */ public ClassMolder getDepends() { return _depends; } /** * Get the LockEngine which this ClassMolder belongs to. */ public LockEngine getLockEngine() { return _engine; } /** * Returns the active cache parameters. * * @return Active cache parameters. */ public Properties getCacheParams() { return _cacheParams; } /** * Return true if the base type of this ClassMolder is an dependent class. */ public boolean isDependent() { return _depends != null; } /** * Mutator method to add a extent ClassMolder. */ void addExtendent(final ClassMolder ext) { if (_extendent == null) { _extendent = new Vector<ClassMolder>(); } _extendent.add(ext); } /** * Mutator method to add a dependent ClassMolder. */ void addDependent(final ClassMolder dep) { if (_dependent == null) { _dependent = new Vector<ClassMolder>(); } _dependent.add(dep); } /** * Mutator method to set the extends ClassMolder. */ void setExtends(final ClassMolder ext) { _extends = ext; ext.addExtendent(this); } /** * Mutator method to set the depends ClassMolder. */ void setDepends(final ClassMolder dep) { _depends = dep; dep.addDependent(this); } public String toString() { return "ClassMolder " + _name; } /** * Return true if a key generator is used for the base type of this ClassMolder. */ public boolean isKeyGenUsed() { return _isKeyGenUsed; } /** * Return true if a key generator is used for the base type of this ClassMolder. */ public boolean isKeyGeneratorUsed() { return _isKeyGenUsed || (_extends != null && _extends.isKeyGeneratorUsed()); } /** * Inspect the fields stored in the object passed as an argument for * contained objects. Request an expireCache for each contained object. * * @param tx The {@link org.castor.persist.TransactionContext} * @param locker The object that contains the fields to be inspected */ public void expireCache(final TransactionContext tx, final ObjectLock locker) throws PersistenceException { // get field values from cache Object[] fields = locker.getObject(); if (fields == null) { return; } // iterate through all the field values and expire contained objects for (int i = 0; i < _fhs.length; i++) { _resolvers[i].expireCache(tx, fields[i]); } } public void resetResolvers () { if (!_resolversHaveBeenReset) { for (int i = 0; i < _fhs.length; i++) { _resolvers[i] = ResolverFactory.createRelationResolver (_fhs[i], this, i); } _resolversHaveBeenReset = true; } } /** * Returns the actual (OQL) statement for the specified named query. * * @param name Named query name. * @return The actual (OQL) statement. */ public String getNamedQuery(final String name) { return new ClassDescriptorJDONature(_clsDesc).getNamedQueries().get(name); } /** * Returns the actual (SQL) statement for the specified named native query. * * @param name Named query name. * @return The actual (SQL) statement. */ public NamedNativeQuery getNamedNativeQuery(final String name) { return new ClassDescriptorJDONature(_clsDesc).getNamedNativeQueries().get(name); } }